The three processes

When sending a message to an object in OC, such as the following code:

NSObject *obj = [NSObject new];
Copy the code

When compiled into C++ code, you can see that the underlying calls are objc_msgSend:

NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
Copy the code

Objc_msgSend consists of three steps: message sending, dynamic parsing, and message forwarding. Before going through these three steps, it’s worth talking about the underlying structure of ISA and class.

Isa and the class underlying structure

isa

Before the ARM64 architecture, ISA was a pointer to a class object. With ARM64, it became a community with more responsibilities.

The following is aObjc4-818.2 -Isa_t information in:

  • nonpointer
    • The value 0 represents a common pointer that stores the memory addresses of class and metaclass objects.
    • A value of 1 indicates that it has been optimized to use bitfields to store more information.
  • Has_assoc: Whether to set the associated object, otherwise the object will be released faster.
  • Has_cxx_dtor: does C++ destructor exist? If not, object release is faster.
  • Shiftcls: Memory address for class and metaclass objects.
  • Magic: Determines whether an object is initialized during debugging.
  • Weakly_referenced: Whether the object is pointed to by a weak reference, if not, the object is released faster.
  • Unused: unused.
  • Has_sidetable_rc:
    • With a value of 0, the reference count can be stored in EXTRA_RC.
    • A value of 1 indicates that the reference count is too large and needs to be stored in the properties of the class in SideTable.
  • Extra_rc: Stores the value as an object’s reference count minus one.

Class

  • Objc_class: The underlying structure of a class object.
struct objc_class : objc_object { ...... // Class ISA; Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; . }Copy the code
  • Cache_t: Method cache for hash table implementations.
struct cache_t { ...... unsigned capacity() const; Struct bucket *buckets() const; // hash Class CLS () const; // class or metaclass mask_t occupied() const; // Number of occupied...... }Copy the code
  • class_data_bits_t
struct class_data_bits_t { ...... public: class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); }... };Copy the code
  • Class_rw_t: stores the list of methods, attributes, and protocols of class objects, which can be read or written.
struct class_rw_t { explicit_atomic<uintptr_t> ro_or_rw_ext; const method_array_t methods() const { ... } const property_array_t properties() const { ... } const protocol_array_t protocols() const { ... }};Copy the code

Now that you know the underlying structure of ISA and class, let’s elaborate on these three steps.

Message is sent

  • _objc_msgSend (objc-msg-arm64.s)
ENTRY _objc_msgSend //1. Check whether the message receiver is empty CMP P0. #0 // nil check and tagged pointer check #if SUPPORT_TAGGED_POINTERS b.le LNilOrTagged // (MSB tagged pointer looks negative) ...... #if SUPPORT_TAGGED_POINTERS LNilOrTagged: b.eq LReturnZero // nil check GetTaggedClass b LGetIsaDoneCopy the code
  • lookUpImpOrForward (objc-runtime-new.mm)
Reasonableclasscount ();) {/ / 2, in the class object cache lookup the if (curClass - > cache. IsConstantOptimizedCache strict (/ * * / true)) {# if CONFIG_USE_PREOPT_CACHES imp = cache_getImp(curClass, sel); // If (imp) goto done_unlock; curClass = curClass->cache.preoptFallbackClass(); Method list. Meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { imp = meth->imp(false); goto done; } // the method is not found yet. Slowpath ((curClass = curClass->getSuperclass()) == nil)) {// No implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; }}... Imp = cache_getImp(curClass, sel); if (slowpath(imp == forward_imp)) { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } // select * from parent; If (fastPath (imp)) {// Found the method in a superclass. Cache it in this class.goto done; If (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); }Copy the code

1. In the process of message sending, the receiver will first judge whether it is empty. If so, it will exit directly. Based on this, sending a message to nil in OC is error free:

NSObject *obj = nil;
[obj class];
Copy the code

2. If the receiver is not empty, it will find the class object according to the ISA of the receiver and search it in the cache of the class object. If it finds it, it will call it directly. If you can’t find it, look for it in class_rw_t of the class object.

3. If it is found in class_rw_t, it is directly called and cached in the cache of the class object. If not, the superclass pointer is used to look for the superclass object in the cache.

4. If it is found in superclass cache, it is directly called and cached in the cache of receiver’s class object; If not, look in class_rw_t of the superclass object.

5. If found in class_rw_t, it is directly called and cached in the cache of the class object of the receiver; If not, the superclass pointer is used to look up the superclass object in the cache.

6, since then, has been repeated 4, 5 steps, until the base class, if to the base class still can not find the method to implement, first see whether dynamic parsing, not dynamic parsing, will enter the second step: dynamic parsing.

7. If it has been dynamically resolved, enter the third part: message forwarding.

Flow chart:

The dynamic analysis

1. In the stage of dynamic parsing, first judge whether dynamic parsing has been carried out. If so, enter the next stage: message forwarding; If not, call +resolveInstanceMethod: or +resolveClassMethod: for dynamic resolution.

2. Mark it as dynamically resolved.

3. Enter phase 1 again – message sending.

forward

1, call forwardingTargetForSelector: method, the return value is nil, called objc_msgSend (return value, SEL); Return nil, go to the next step.

MethodSignatureForSelector: 2, calls, the return value is nil, call forwardInvocation: method; The return value is nil, call doesNotRecognizeSelector: program crashes.

At this point, the message flow completes.