Structure classification and refinement of interview questions

  • Runtime issues

    1. Runtime structure model
    2. Memory management
    3. Method Swizzle associated with the attribute or hook

Runtime issues

Objc-runtime source address objC4 official source address

1. Runtime structure model

Introduce the Runtime memory model (ISA, object, class, metaclass, structure storage information, etc.)

object

Typepedef struct objc_object * ID; As can be seen from its structure, it includes an ISA pointer, which points to the Class object of this object. An object instance finds its own Class through this ISA, and this Class stores the method list, attribute list, member variable list and other related information of this instance.

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
Copy the code

The objc_Object implementation is longer and can be viewed here

class

A Class in OC is represented by a Class, which actually points to an objc_class pointer type, typedef struct objc_class *Class; The corresponding structure is as follows:

struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if ! __OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif }Copy the code

Class and object summary

From the variables defined in the structure, the Class types of OC include the following:

  • Data (i.e., metadatametadata) :super_class(parent class object);
  • Name (name of the class object);
  • Version, INFO (version and related information);
  • Instance_size (instance memory size);
  • Ivars (list of instance variables);
  • MethodLists;
  • Cache;
  • Protocols (list of implemented protocols);

Isa refers to a metaclass. The metaclass holds all the information about the method that created the Class object.

As you can see from the figure, the metaclass object ISA of NSObject, the final base class, points to itself, forming a closed loop. A Meta Class is a Class of Class objects that contains information about Class methods. Objc_ivar_list stores member variables of the class, which can be obtained using object_getIvar or class_copyIvarList. The other two methods are class_getProperty and class_copyPropertyList, which are used to get the class’s property list. Properties and member variables are different.

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}                                                            OBJC2_UNAVAILABLE;

struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
} 
Copy the code

Objc_method_list: Stores a list of methods for the class, which can be obtained via class_copyMethodList.

The structure is as follows:

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
} 
Copy the code

Objc_protocol_list: list of protocols that store classes, which can be obtained by class_copyProtocolList.

The structure is as follows:

struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};
Copy the code

The runtime memory model (ISA, object, class, metaclass, structure storage information, etc.)

Why design Metaclass?

Conclusion: In order to better reuse messages, Metaclass simply needs to implement reuse messaging as a tool. All objective-C classes default to the same MetaClass(which ends up pointing to MetaClass via an ISA pointer). Because objective-C features were essentially copied from Smalltalk, the MetaClass design in Smalltalk was added in Smalltalk-80. So objective-C has a metaclass design.

Essentially because the highlight of Smalltalk’s object-oriented nature is its messaging mechanism.

Before we answer that question let’s take a look back at objective-C’s object prototype inheritance chain!

From the picture above, we can understand the following key points:

  • Instance method functions of instances exist in the class structure
  • Class method functions exist in the metaclass structure

An Objective-C method call (message) finds the corresponding method based on the list of methods in the Class object to which the ISA pointer points.

The class isa points to is the type of class we created the instance of.

Why is MetaClass in Objective-C? One important concept we learned in this article is that, unlike Objective-C, not every class in Python has a MetaClass, but all classes in Objective-C have the same MetaClass by default.

The metaclass Smalltalk

Smalltalk, recognized as the second object-oriented language in history, was notable for its messaging mechanism. The MetaClass design in Smalltalk was added in Smalltalk-80. In Smalltalk-76, instead of having a MetaClass for every Class, all classes had isa Pointers to a special Class called Class(a design that was later borrowed from Java). Each class has its own MetaClass design. The reason for this was that, because in Smalltalk, classes are objects and objects can respond to messages, the methods that respond to messages of classes should be stored by classes of classes, and each MetaClass holds the class methods of each class.

What does each MetaClass ISA pointer point to?

If MetaClass followed MetaClass, the relationship would be infinite. The solution in Smalltalk was to point to the same class called MetaClass.

What does the ISA pointer to MetaClass point to?

The isa that points to his instance, the instance, points to MetaClass, and the MetaClassisa points to the instance, pointing to each other.

Smalltalk inheritance is similar to Objective-C inheritance.

An important question arises at this point. Is it possible to remove MetaClass and put class methods inside other classes?

I have pondered this question for a long time and found that it is actually a philosophical problem of object orientation. To draw a conclusion to this problem, I have to talk about object orientation again

Relearning object orientation from Smalltalk

Before talking about object-oriented, always mentioned, object-oriented three characteristics: encapsulation, inheritance, polymorphism. But in fact, object-oriented schools, such as C++, which comes from Simula, are more focused on class division, because method calls are static. Something like Objective-C, which borrows from Smalltalk, is more about messaging, dynamic response messages.

But face object three kinds of characteristic, more is based on the division of class but put forward.

The biggest difference between these two ideas, I think, is top-down and bottom-up thinking.

  • Class division requires class designers to design the class at a very high level, extract the characteristics and essence of the class, and build the class. You need to know the type to send a message to an object.
  • Messaging requires the class designer to build the class from the message as a starting point, that is, to respond to changes in the outside world, regardless of their own type, design the interface. Try to understand the message and special process if it cannot be processed. Instead of discussing the pros and cons of the two approaches, I’ll focus on Smalltalk as a design.

Messaging for object oriented design is really a solution to messages. Reuse, one of the benefits of object-oriented design, is more about reuse solutions than classes themselves. The idea is similar to designing components, with interfaces, compositions and not classes. In fact, MetaClass design, MY understanding is not the first MetaClass, but in Smalltalk, everything is an object, the basic solution of sending messages to objects is unified, hope to reuse. The flow between instances and classes of storing a list of methods and a solution for querying methods in Class singletons that are pointed to by isa Pointers is supposed to be reused on classes, and MetaClass comes naturally.

Why design the Metaclass summary

Going back to the original question, why design MetaClass, is it ok to get rid of class methods and put them inside classes?

My understanding is, yes, but not Smalltalk. Such design is C++ that kind of top-down design, class method is also a kind of characteristic description of the class. The essence of Smalltalk is messaging, reusing messaging is the fundamental purpose, and MetaClass is just a tool needed for that.

Why is MetaClass in Objective-C?

class_copyIvarList()  & class_copyPropertyList()The difference between

First the conclusion:

  • class_copyIvarList()Get all member variables, including variables in curly braces (.hand.mWill be included).
  • class_copyPropertyList()Can only get to@propertyAttributes in the keyword declaration (.hand.mWill be included)

The difference between:

  • class_copyIvarList()Gets variables that are underlined by default
  • class_copyPropertyList()Gets variable names that are ununderlined by default.

However, both methods can only get the attributes and variables of the current class.


For example:

We declare a ClassA implemented by debugging code

#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface ClassA : NSObject { int _a; int _b; int _c; CGFloat d; } @property (nonatomic, strong) NSArray *arrayA; @property (nonatomic, copy ) NSString *stringA; @property (nonatomic, assign) dispatch_queue_t testQueue; @end @implementation ClassA @endCopy the code

If obtained through the class_copyIvarList() function, the following result is printed.

- class_copyIvarList left left left - _a _b _c d _arrayA _stringA _testQueue -- -- -- -- -- -- -- -- -- -- -- -- -- -- END -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -Copy the code

If obtained through the class_copyPropertyList() function, the following result is printed.

- class_copyPropertyList left left left - arrayA stringA testQueue -- -- -- -- -- -- -- -- -- -- -- -- -- -- END -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -Copy the code

The debug code is as follows:

- (void)printIvarOrProperty {NSLog(@"-- class_copyPropertyList ↓↓-- "); - (void)printIvarOrProperty {NSLog(@"-- class_copyPropertyList ↓↓-- "); ClassA *classA = [[ClassA alloc] init]; unsigned int propertyCount; objc_property_t *result = class_copyPropertyList(object_getClass(classA), &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { objc_property_t objc_property_name = result[i]; NSLog(@"%@",[NSString stringWithFormat:@"%s", property_getName(objc_property_name)]); } free(result); NSLog(@"--------------END----------------"); NSLog (@ "- class_copyIvarList left left left -- -"); Ivar *iv = class_copyIvarList(object_getClass(classA), &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { Ivar ivar = iv[i]; NSLog(@"%@",[NSString stringWithFormat:@"%s", ivar_getName(ivar)]); } free(iv); NSLog(@"--------------END----------------"); }Copy the code

Let’s take a look at objc’s source code

The following code is in objC-Runtime-new.mm

/*********************************************************************** * class_copyPropertyList. Returns a heap block containing the * properties declared in the class, or nil if the class * declares no properties. Caller must free the block. * Does not copy any superclass's properties. *  Locking: read-locks runtimeLock **********************************************************************/ objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount) { if (! cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); checkIsKnownClass(cls); ASSERT(cls->isRealized()); auto rw = cls->data(); property_t **result = nil; unsigned int count = rw->properties.count(); if (count > 0) { result = (property_t **)malloc((count + 1) * sizeof(property_t *)); count = 0; for (auto& prop : rw->properties) { result[count++] = &prop; } result[count] = nil; } if (outCount) *outCount = count; return (objc_property_t *)result; }Copy the code

We can see through the source code

auto rw = cls->data(); rw->properties; // Go to properties via rWCopy the code

Use the RW to get properties directly, and then conveniently pull out the desired variable name with the @property keyword.

Properties asynchronous runtime source code to see where the space limit is not verbose.

/*********************************************************************** * class_copyIvarList * fixme * Locking: read-locks runtimeLock **********************************************************************/ Ivar * class_copyIvarList(Class cls, unsigned int *outCount) { const ivar_list_t *ivars; Ivar *result = nil; unsigned int count = 0; if (! cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(runtimeLock); ASSERT(cls->isRealized()); if ((ivars = cls->data()->ro->ivars) && ivars->count) { result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar)); for (auto& ivar : *ivars) { if (! ivar.offset) continue; // anonymous bitfield result[count++] = &ivar; } result[count] = nil; } if (outCount) *outCount = count; return result; }Copy the code

So here’s the key point

ivars = cls->data()->ro->ivars
Copy the code

Get the ivars.

The two apis are different because they get different members.

class_rw_t 和 class_ro_tThe difference between

First the conclusion:

  • Both structures hold the attributes, instance variables, methods, protocols, and so on of the current class.
  • class_ro_tThe store is determined at compile time.
  • whileclass_rw_tIt’s determined at Runtime, it’s going to takeclass_ro_tAnd then copy the properties, methods, and so on of the current class classification into it. So you can sayclass_rw_tisclass_ro_tOf course, the actual access to class methods, attributes, and so on are also accessedclass_rw_tThe content of.

Now let me get into the details of what are the two

Objc_class has a member variable called ISA. We are going to introduce bits, another member variable of objc_class.

Objc_class has the following structure:

Bits stores information about attributes, methods, and protocols of a class. It is a class_datA_bits_t type

Class_data_bits_t is as follows:

struct class_data_bits_t {
    uintptr_t bits;
    // method here
}
Copy the code

This constructor has only one bit of bits in it.

  • is_swift: the first bit that determines whether the class is Swift
  • has_default_rrThe second bit determines whether the current class or parent class has a defaultretain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReferencemethods
  • require_raw_isaThe third bit determines whether an instance of the current class is neededraw_isa
  • dataBit 4-48 holds a pointer to the class_rw_t structure, which contains attributes, methods, protocols, and so on. Why only use 44bit to store the address

class_rw_t 和 class_ro_t

Let’s start by looking at the internal member variables of the two structures

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;
};
Copy the code
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};
Copy the code

The class_rw_T structure body has a pointer to the class_ro_t structure.

Each class should have a class_RO_T and a class_rw_T structure for each pair. At compile time, the class_ro_t structure is determined, and the bits data section of objC_class holds the address of this structure. After runtime runs, specifically when runtime’s realizeClass method is run, the class_rw_t structure is generated, which contains class_ro_t, and the data section is updated with the address of the class_rw_t structure.

Here are two pictures to illustrate the process:

Class realizeClass before running:

After class realizeClass is run:

A closer look at the member variables of the two structures reveals a lot in common: they both hold the attributes, instance variables, methods, protocols, and so on of the current class. The difference is that class_ro_t stores what is determined at compile time; Class_rw_t is determined at runtime. It copies the contents of class_ro_t first, and then copies the attributes, methods, and so on of the current class. So class_rw_t is a superset of class_ro_t. Of course, the actual methods, attributes, and so on are also accessed in class_rw_T

Properties are stored in class_rw_T, and instance variables are stored in class_ro_T.

For details, see Objective-C Runtime – Properties and Methods

How does a category get loaded, the load order of the two category methods, the load order of the two category methods with the same name

Conclusion:

  1. The category is sorealizeClass -> methodizeClass() -> attachCategories()Being loaded step by step.
  2. The loading order of the main class and the classification is: the main class takes precedence over the classification loading, regardless of the compilation order.
  3. The order in which the classes are loaded depends on the order in which they are compiled: compile before they are loaded, compile after they are loaded.

How are categories loaded

Objc-runtimenew.mm objc-runtimenew.mm objc-runtimenew.mm objc-runtimenew.mm

static Class realizeClassWithoutSwift(Class cls, Class previously) { ... // Attach categories to methodizeClass(CLS, previously); return cls; }Copy the code

realizeClass -> methodizeClass() -> attachCategories()

The core is implemented in the methodizeClass() function.

static void methodizeClass(Class cls) { runtimeLock.assertLocked(); bool isMeta = cls->isMetaClass(); auto rw = cls->data(); auto ro = rw->ro; . property_list_t *proplist = ro->baseProperties; if (proplist) { rw->properties.attachLists(&proplist, 1); }... // Attach categories. category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); attachCategories(cls, cats, false /*don't flush caches*/); . if (cats) free(cats); }Copy the code

Ro ->baseProperties; , baseProperties, category,

property_list_t *proplist = ro->baseProperties;
if (proplist) {
  rw->properties.attachLists(&proplist, 1);
}
Copy the code

But the method that determines the order is RW -> properties.attachlists ().

Void attachLists(List* const * addedLists, uint32_t addedCount) {if (addedCount == 0) return; if (hasArray()) { // many lists -> many lists uint32_t oldCount = array()->count; uint32_t newCount = oldCount + addedCount; setArray((array_t *)realloc(array(), array_t::byteSize(newCount))); array()->count = newCount; /* struct array_t {uint32_t count; List* lists[0]; }; */ memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0])); memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); } else if (! list && addedCount == 1) { // 0 lists -> 1 list list = addedLists[0]; } else { // 1 list -> many lists List* oldList = list; uint32_t oldCount = oldList ? 1:0; uint32_t newCount = oldCount + addedCount; setArray((array_t *)malloc(array_t::byteSize(newCount))); array()->count = newCount; if (oldList) array()->lists[addedCount] = oldList; memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0])); }}Copy the code

So the category property is always first, and the baseClass property is shifted back.

The load order of the load methods of the two categories

A class's +load method is called after all of its superclasses' +load methods. A class's own +load method calls A category +load method after its parent's +load method. A Category's +load method is called after the class's own +load method is extended by itCopy the code

Conclusion: The loading order of main class and classification is: main class takes precedence over classification, regardless of compilation order.

Load order of methods with the same name for two categories

The process of Category resolution when an application image is loaded into memory, Note the following while (I -) cycle Here will be in reverse chronological order agreement method in the category of property added to the rw = CLS – > data in the the methods/properties/separate protocols ().

static void attachCategories(Class cls, category_list *cats, bool flush_caches) { if (! cats) return; if (PrintReplacedMethods) printReplacements(cls, cats); bool isMeta = cls->isMetaClass(); // fixme rearrange to remove these intermediate allocations method_list_t **mlists = (method_list_t **) malloc(cats->count * sizeof(*mlists)); property_list_t **proplists = (property_list_t **) malloc(cats->count * sizeof(*proplists)); protocol_list_t **protolists = (protocol_list_t **) malloc(cats->count * sizeof(*protolists)); // Count backwards through cats to get newest categories first int mcount = 0; int propcount = 0; int protocount = 0; int i = cats->count; bool fromBundle = NO; while (i--) { auto& entry = cats->list[i]; method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { mlists[mcount++] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { proplists[propcount++] = proplist; } protocol_list_t *protolist = entry.cat->protocols; if (protolist) { protolists[protocount++] = protolist; } } auto rw = cls->data(); Preparelists (CLS, mLists, McOunt, NO, fromBundle); prepareLists (CLS, mLists, McOunt, NO, fromBundle); rw->methods.attachLists(mlists, mcount); free(mlists); if (flush_caches && mcount > 0) flushCaches(cls); rw->properties.attachLists(proplists, propcount); free(proplists); rw->protocols.attachLists(protolists, protocount); free(protolists); }Copy the code

So the conclusion is that the load order between classes depends on the order in which they are compiled: compile before they are loaded, compile after they are loaded

There are many examples on the Internet but not many here.

category & extensionDifference, can I add Extension to NSObject? What’s the result

category

  • Add classification properties/protocols/methods at run time
  • Class-added methods “override” the original class method because method lookup is done from start to finish and stops once it is found
  • The namesake classification method is effective depending on the order in which the image is compiled. The information read by image is in reverse order, so the later the image is compiled, the earlier the image is read
  • Classes with the same name will cause compilation errors;

extension

  • Compile-time resolution
  • Exists only as a declaration, in most cases in.m files;
  • Extensions cannot be added to system classes

You can add member variables to a class, but they’re private you can add methods to a class, but they’re private and the properties and methods that you add are part of the class, and they’re determined at compile time. The @Interface in the compiler and header file and the @Implement in the implementation file together form a complete class. With the creation of the class, but also with the disappearance of the class

You must have the class source code to add extension to a class!!

category & extensionThe difference between

  • Category has a name in the parentheses, while Extension does not;
  • A Category can only extend methods, not member variables and attributes.
  • If a Category declares a property, then the Category will only generate the declaration of the property’s set and get methods, and it won’t be implemented. So for system classes like NSString, you can’t add class extensions to NSObject. You can’t add Extension to NSObject because methods or attributes added in Extension must be implemented in the.m file of the source class. That is, you must have the source code of a class to add a classextension

Can I add Extension to NSObject? What’s the result?

Not because there’s no.m source file for NSObject.

If so, it should not be called Extension. Or we could build our own ExtensionDIY through the runtime API. The result is that you’re not using an Extension at all, but an API call.

Message forwarding mechanism, message forwarding mechanism and the advantages and disadvantages of other languages message mechanism comparison

Introduction: Before we look at message forwarding, it is important to understand some of the messaging mechanisms in objectivCE-C

Messaging mechanism

In objectivCE-C, if we invoke a method with an instance variable (object) or a class method name, we are actually sending a message

id returnValue = [someObject messageName:parameter]; Id returnValue = [ClassA messageName:parameter]; // Class invocationCopy the code

The above someObject and ClassA are the receiver, and the messageName is the selector. Together, the selector and parameter are called the message. When the compiler sees this message, it turns it into a standard C function call that is at the heart of the messaging mechanism: objc_msgSend().

void objc_msgSend(id self, SEL cmd, ...)
Copy the code

The first argument represents the receiver, the second argument represents the selector, and the subsequent arguments are those in the message. The compiler converts the message from the previous example to the following function:

id returnValue = objc_msgSend(someObject, @selector(messageName:),parameter);
id returnValue = objc_msgSend(ClassA, @selector(messageName:),parameter);
Copy the code

The objc_msgSend() function calls the appropriate method depending on the type of receiver and selector. To do this, the method needs to search the recipient’s class for its “list of methods” (aka method_list in class_ro_t). If it finds it, it jumps to the real code; otherwise, it moves up the inheritance hierarchy, and if it doesn’t, it forwards the message. Other “boundary cases” are handled by other functions in the Objective-C runtime environment:

Objc_msgSend_stret // When the message to be sent returns the structure objc_msgSend_fpret // the message returns the floating-point objc_msgSendSuper // if you want to send a message to the superclassCopy the code

Message forwarding mechanism

In objective-C, if an object is sent a Message that it cannot process, it enters the Message Forwarding process described below

In objc forward need to experience three stages resolveInstanceMethod – > forwardingTargetForSelectoer – > forwardInvocation – > message failed to deal with.

  • The first stage is Dynamic Method Resolution, which means that the recipient is consulted in the class to see whether it can dynamically add methods to deal with the current unknown selector
  • Phase 2: Replace the message receiver for fast forwarding
  • Stage 3: Full message forwarding mechanism

Stage 1:Dynamic Method Resolution

When an object receives an undecipherable message, it first calls the following class methods of its class:

+ (BOOL)resolveClassMethod:(SEL) SEL OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); + (BOOL)resolveInstanceMethod: SEL OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);Copy the code

These two methods are in nsobject.h

Returns a Boolean type indicating whether the class can add an instance method to handle the selector.

During message forwarding, we can use resolveInstanceMethod: to dynamically add a method to a class.

Example The following example code:

@implementation MyClass + (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) {  class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSEL]; } @endCopy the code

Here we use a run-time function class_addMethod().

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { if (! cls) return NO; mutex_locker_t lock(runtimeLock); return ! addMethod(cls, name, imp, types ? : "", NO); }Copy the code
  • class_addMethod()The last parameter is calledtypesIs a string that describes the type of argument to a method.
  • vOn behalf ofvoid
  • @Representing the object orId type
  • :(The colon) represents method selector SEL

Objective-c Runtime Programming Guide->Type Encodings

DynamicMethodIMP (void, id, SEL, etc.);

The point of this phase is to dynamically provide a method implementation for a class that, strictly speaking, has not yet entered the message forwarding process.

ResolveInstanceMethod: controls whether the following two methods are called

  • respondsToSelector:
  • instancesRespondToSelector:

That is to say, if resolveInstanceMethod: returned to YES, then respondsToSelector: and instancesRespondToSelector: will return YES.

Phase 2: Replace the message receiver (fast forwarding)

If the first phase of resolveInstanceMethod: return NO, is called forwardingTargetForSelector: ask if the message is forwarded to another object. The recipient of the message changes.

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return someOtherObject;
}
Copy the code

Stage 3: Full message forwarding mechanism

If the second stage forwardingTargetForSelector: returns nil, it entered the so-called completely message forwarding mechanism.

First call methodSignatureForSelector: return to forward the message correct signature:

- (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"forwardInvocation"); SomeOtherObject *someOtherObject = [SomeOtherObject new]; if ([someOtherObject respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:someOtherObject]; } else { [super forwardInvocation:anInvocation]; }}Copy the code

The above code forwards the message to other objects, which is exactly what the sample code in the second phase does. The difference is that there is an NSInvocation object at this stage. NSInvocation is an object used to store and forward messages. It contains all the elements of an Objective-C message: a target, a selector, arguments, and return values. Each element can be set directly.

NSInvocation is simply an object that stores both the selector method we use and the object, and then which is the pointer to the object we need to invoke.

So unlike stage 2, in this stage you can:

  • Store the message and forward it when you see fit, or leave the message alone.
  • Modify message target, selector, parameters, etc
  • Forward the message multiple times to multiple objects

Obviously at this stage, you can do more with an OC message


Comparison between message forwarding mechanism and message mechanism of other languages

This does not yet go into the run-time layers of other programming languages, such as C or C++ or Java’s underlying messaging. Here is an Android article that looks like message forwarding

At method invocation time, what is done before method query -> dynamic resolution -> message forwarding

Objective-c instance objects perform method steps

  1. Gets the corresponding Class of receiver
  2. In the Class cache list (i.eobjc_classIn thecache_ttoclass_ro_tMethod list) according to the selectorsselectorTo find theIMP
  3. If not found in the cache, continue the search in the method list.
  4. If the list of methods does not exist, look from the parent class and repeat the above steps.
  5. If no message is found, the message is forwarded.
  • Methods need to know the receiver and selector before querying them. The main thing is to figure out which instance we’re calling which method.
  • Dynamic resolution Before parsing, the recipient is asked in the class to see if it can dynamically add methods to handle the current unknown selector.
  • Before forwarding a message, ask whether to forward the message to another object.

A deeper understanding would be why objc_msgSend() is implemented in assembly, and which instructions are executed before the above methods call the assembly

Here are two articles that you can refer to for an in-depth look at the objective-C message sending and forwarding process written in assembly language

IMP,SEL,MethodThe differences and usage scenarios

  • IMP: is the concrete implementation of the method (pointer)
  • SEL: method name
  • Method : is a pointer of type objc_method, which is a structure as follows:
  struct objc_method {
      SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
      char * _Nullable method_types                            OBJC2_UNAVAILABLE;
      IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
  }
Copy the code

Usage scenarios

  • Like when a Button adds a Target and a Selector. Or the implementation classswizzleI’m going to use, passclass_getInstanceMethod(class, SEL)To get the method of the classMethod, in which SEL is used as the method name
  • For example, to dynamically add a method to a Class, we need to call class_addMethod(Class, SEL, IMP, types), which requires us to pass an implementation of the method IMP, for example:
Static void funcName(id receiver, SEL CMD, method parameter...) {// Method specific implementation}Copy the code

SEL is equivalent to the method type key.

load,initializeWhat’s the difference between methods? What is the difference between them in inheritance

When an Objective-C class is loaded and initialized, it can receive method callbacks.

- (void)load;
- (void)initialize;
Copy the code

+load

The +load method is called when the file (which is the subclass you copied) is loaded by the program. Files that appear in Xcode Compile Sources are always loaded, regardless of whether the class is used, so the +load method is always called before main().

The call time is early, and the operating environment is uncertain. To be specific, loading is usually done when App starts on iOS. However, when load is called, it is not guaranteed that all classes are loaded and available. If necessary, you have to be responsible for auto release processing.

To add to the above point, +load of the dependent class is called first for both libraries that have dependencies. But within a library, there is an order between parent, subclass, and class calls, and the order between classes is uncertain.

  • About inheritance: for a class, no +load method is called without an implementation, no inheritance on NSObject is considered, that is, no +load from the parent class is used.
  • Parent class and its invocation: Methods of the parent class take precedence over methods of subclasses. The +load method of a class is not specified[super load], the parent class will receive the call.
  • Class and Category calls: Methods of this class take precedence over methods of the Category. The +load of a Category is also called, but in order after the +load of this class is called.
  • The initialize call does not trigger directly.

+initialize

The + Initialize method is called before the class or its subclasses receive the first message, which includes calls to instance methods and class methods, and is called only once. The Initialize method is actually a lazy load call, which means that if a class is never used, its Initialize method will not be called, which helps save resources.

The runtime calls the +initialize method by sending the objc_msgSend message. In other words, the +initialize method is called in the same way as the normal method, following the process of sending messages. In other words, if the subclass does not implement the +initialize method, the implementation inherited from the parent class will be called; If a class’s classification implements the +initialize method, then the implementation in that class is overridden (override).

  • The natural call to Initialize is the first active use of the current class.
  • When the Initialize method is called, the runtime environment is basically sound.
  • On inheritance: Unlike load, if a subclass does not implement the initialize method, it inherits the parent’s implementation and calls it again, using the parent’s +initialize. (In the superclass method, self still refers to the subclass)
  • Parent and class calls: the +initialize method of the parent class is fired when +initialize is called, so there is no need to specify [super initialize] in the subclass. (If the parent +initialize method has already been called, it will not be called again.)
  • Class and Category calls: the +initialize method in a Category overrides the methods of the class, and only one Category + Initialize method is executed.

Here is a table compiled to help explain these two methods:

See the difference between the class methods Load and Initialize

What is the difference between them in inheritance

The super method is called successfully, but this is redundant because runtime automatically calls the parent’s +load method, while +initialize automatically fires the parent’s method (as described in the Apple documentation) with no need to display the call. On the other hand, if a method in a parent class uses self (as in the example), it still refers to the class itself, not the parent class

Talk about the pros and cons of the message forwarding mechanism

Advantages:

  • Message forwarding mechanism can be used to implement multiple proxies without code intrusion, so that different objects can simultaneously broker the same callback, and then handle the corresponding processing in their respective areas of responsibility, reducing the degree of code coupling.
  • While at sign synthesize automatically generates getter and setter methods for @property (as it does in the current version of Xcode), at sign dynamic tells the compiler not to generate getter and setter methods. When using @dynamic, we can use the message forwarding mechanism to dynamically add getters and setters. Of course you can do it in other ways.

Disadvantages:

  • Objective-c itself does not support multiple inheritance, because the message mechanism name lookup occurs at runtime, not compile time. It is difficult to solve the ambiguity caused by multiple base classes, but you can use the message forwarding mechanism to create objects with multiple functions internally and forward functions that cannot be implemented to other objects. This creates the illusion of multiple inheritance. Forwarding is similar to inheritance and can be used to add some multi-inheritance effects to OC programming, with one object forwarding messages as if it were taking over or “inheriting” another object. Message forwarding compensates for objC’s lack of support for multiple inheritance and avoids the bloated complexity of a single class caused by multiple inheritance.

conclusion

This section describes the structural model of runtime questions in the interview questions. The next chapter is intended to continue with the runtime issues related to memory management, so as to gradually cover the relevant interview articles.

I have to say that this kind of interview is really challenging, by the way I also spray Ali Toutiao hope to be kind, you can have questions but also have answers. This incident made me observe that the two company executives have no end, good at beginning and not good at ending. Since the | address

Present a copy of the latest iOS interview questions, collected some of their own think good information to share with you, iOS books +2021 “latest often asked iOS interview questions and answers”, to obtain a detailed factory interview information for your job hopping add a guarantee.

Thanks for watching.