The super keyword

Super is actually a keyword provided by OC. It is mainly used to call the attributes and methods inherited from the parent class in the inheritance system. It is just a mark. Let’s take a closer look at the underlying implementation of Super through source code.

The source code interpretation

  • First, create the XLPerson class and add the following methods to xlPerson. m:
- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"init");
    }
    return self;
}
Copy the code
  • Then turn xlperson.m into C++ code with the following instructions
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XLPerson.m
Copy the code
  • Look at the generated xlperson.cpp file and find the corresponding init method as follows:
static instancetype _I_XLPerson_init(XLPerson * self, SEL _cmd) {
    self = objc_msgSendSuper((__rw_objc_super){
        (id)self,
        (id)class_getSuperclass(objc_getClass("XLPerson"))
    }, sel_registerName("init"));
    if (self) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_XLPerson_d841fc_mi_0);
    }
    return self;
}
Copy the code
  • You can see that in the init method, [super init] ends up being converted to the objc_msgSendSuper function, which takes two arguments.

  • Then go to objC source code to find the implementation of objc_msgSendSuper, found in the source code there are objc_msgSendSuper and objc_msgSendSuper2 two functions, so in the end which function is super executed? You can use Xcode breakpoints to look at the assembly code to find the method that was eventually called.

    • First we create a breakpoint in the init function of XLPerson, then create an XLPerson object in the main function, and open Xcode with the following Settings

    • Then I run the program and look at the assembly code, and it’s clear that super ends up calling the objc_msgSendSuper2 function

  • The first two arguments to objc_msgSendSuper2 are __rw_objc_super and sel_registerName(“init”).

OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
Copy the code
  • Objc_super has two member variables, one of which is receiver, corresponding to self in the structure above. One is super_class, which corresponds to class_getSuperclass(objc_getClass(“XLPerson”) in the structure above.
// Specifies the superclass of an instance. struct objc_super {// __unsafe_unretained _Nonnull ID receiver; __unsafe_unretained _Nonnull Class super_class; /* super_class is the first class to search */ };Copy the code
  • Although the first parameter in the objc_msgSendSuper2 declaration is a structure of type objc_super, the first parameter of the objc_msgSendSuper2 function actually passes an object of type objc_super2. This can be seen in the assembly code below.
Struct objc_super2 {struct objc_super2; // The current Class object Class current_class; };Copy the code
  • The objc_msgSendSuper2 function is implemented in assembly with the following assembly code
ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame p0 is actually the x0 register, p16 is actually the X16 register. LDP p0, p16, [x0] // P0 = real receiver, p16 = class Reassign to register LDR p16, [x16,#SUPERCLASS]	// p16 = class->superclass// call CacheLookup and p16 is CacheLookup NORMAL END_ENTRY _objc_msgSendSuper2Copy the code

In objc_msgSendSuper2, the first argument is an objc_super2 structure. In ARM64 assembly, register X0 is usually used to store parameters or return values. In this case, register X0 holds the address value of structure objC_super2.

LDP P0, P16, [x0] indicates that from the addresses stored in register X0, the first 8 bytes are assigned to P0, and the last 8 bytes are assigned to P16. In this case, the value of register P0 is the address of receiver in objC_super2. The value of p16 is the address of current_class in the objc_super2 structure.

LDR p16, [x16, #SUPERCLASS], [x16, #SUPERCLASS], [x16, #SUPERCLASS], In the Class underlying structure, the first member variable is an ISA pointer, which takes up 8 bytes, and the second member is the superclass. X16 is the address of the superclass. If x16 is offset by 8 bytes, it is the superclass pointer. This confirms that the first argument to the objc_msgSendSuper2 function is an objc_super2 structure.

SUPERCLASS is the number of bytes used for Pointers in the current architecture. In arm64, pointer types are 8 bytes.

Once you get the superclass pointer, store the superclass address in register P16, which is the parameter to CacheLookup, and the cache will look up the corresponding method in the superclass method list. However, the actual message receiver is still the current receiver.

Super summary

  • When a method is called through super, the underlying conversion is to the objc_msgSendSuper2 function
  • The objc_msgSendSuper2 function passes at least two arguments. The first argument is an objc_super2 structure with two internal members. The first member is the current instance object itself, receiver, and the second member is the current instance object’s corresponding class object, current_class.
  • When the [super XXXX] method method is called, a message is actually sent not to the superclass of the receiver, but to the current receiver. But when a message lookup is performed, the superclass for current_class is taken first, and then the methods are queried in the superclass method cache and method list.
  • That is, objc_super2’s first member variable, receiver, determines who is the real message receiver, while the superclass of its second member variable, current_class, actually determines where the current message should be looked up from.

Normally, when a message is sent to the object receiver, it is first searched in the receiver’s cache list or method list. If it cannot be found, it is searched in the Superclass. Super means that a message is first sent to the receiver. But the method lookup will be done in the superclass of Recever first. That’s why using super calls the superclass method.

The Demo parsing

First create the XLPerson class, then create the XLTeacher class inherited from the XLPerson class, and then add the following code to xlTeacher.m

@implementation XLTeacher

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"% @", [self class]);     
        NSLog(@"% @", [super class]);    
        
        NSLog(@"% @", [self superclass]);
        NSLog(@"% @", [super superclass]);
    }
    return self;
}

@end
Copy the code

To see the print results, you also need to know the underlying implementation of class and superclass, which can be seen in objc source nsobject.mm, as shown below

@implementation NSObject

+ (id)self {
    return (id)self;
}

- (id)self {
    return self;
}

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

+ (Class)superclass {
    return self->superclass;
}

- (Class)superclass {
    return [self class]->superclass;
}

@end
Copy the code
  1. [self class], calling the class method with self (the current XLTeacher instance object) is essentially sending an @selector(class) message to self. Since neither XLTeacher’s instance object nor its parent implements a class method, So you end up looking in the list of methods of the base class NSObject, and you end up finding the method implementation that returns object_getClass(self); , which is the current class object XLTeacher.
  2. [super class], which is essentially sending a @selector(class) message to self, but it’s going to look for the method implementation in the parent class of XLTea, and since the parent class doesn’t implement a class method, it’s going to find the class method of the base class NSObject, Return object_getClass (self); , the result is also XLTeacher.
  3. [self superclass], sends self a @selector(superclass), first looks in XLTeacher’s cache and list of methods, XLTeacher doesn’t implement this method, so looks in XLTeacher’s superclass, The superclass is also not implemented, and eventually you find the superclass method in NSObject, which returns [self Class]->superclass, which is XLPerson.
  4. [super superclass], using super to call the superclass method, actually sends @selector(superclass) to self, but looks for the method implementation directly from XLTeacher’s parent XLPerson. Since XLPerson does not implement this method, NSObject’s superclass method is eventually called, returning [self Class]->superclass, which is XLPerson.

IsKindOfClass and isMemberOfClass

The source code parsing

First, look at the implementation of nsobject.mm in the source code

@implementation NSObject + (BOOL)isMemberOfClass:(Class) CLS {// use object_getClass to get the metaclass object pointed to by isa of the current Class object and compare with CLSreturnobject_getClass((id)self) == cls; } - (BOOL)isMemberOfClass:(Class) CLS {// compare the Class object pointed to by isa of the current instance object with CLSreturn[self class] == cls; } + (BOOL)isKindOfClass:(Class) CLSfor (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    returnNO; } - (BOOL)isKindOfClass:(Class) CLSfor (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
@end
Copy the code
  • Class method +isMemberOfClass: gets the metaclass object of the current class object through ISA, compares it with the object in the argument, and returns YES if it is equal.
  • Object method -isMemberofClass: gets the class object of the current instance object through ISA, compares it with the object in the argument, and returns YES if it is equal.
  • Class method +isKindOfClass: iterates through the metaclass of the current class object and the metaclass of the parent class, and returns YES if the metaclass is equal to the object in the argument.
  • Object method -isKindOfClass: iterates through the corresponding class object of the current instance object and its parent, and returns YES if the iterated class object is equal to the object in the argument.

The Demo parsing

Create an XLPerson object that inherits from NSObject. Then add the following code to the main function

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        BOOL result1 = [NSObject isMemberOfClass:[NSObject class]];
        BOOL result2 = [NSObject isKindOfClass:[NSObject class]];
        BOOL result3 = [XLPerson isMemberOfClass:[XLPerson class]];
        BOOL result4 = [XLPerson isKindOfClass:[XLPerson class]];
        
        NSLog(@"%d %d %d %d", result1, result2, result3, result4);
        
        XLPerson *person = [[XLPerson alloc] init];
        BOOL result5 = [person isMemberOfClass:[NSObject class]];
        BOOL result6 = [person isKindOfClass:[NSObject class]];
        BOOL result7 = [person isMemberOfClass:[XLPerson class]];
        BOOL result8 = [person isKindOfClass:[XLPerson class]];
        
        NSLog(@"%d %d %d %d", result5, result6, result7, result8);
    }
    return 0;
}
Copy the code

Class method

  1. [NSObject isMemberOfClass:[NSObject Class]] returns NO because object_getClass(NSObject) takes a metaclass object and [NSObject Class] is a class object and the two are not equal.
  2. [NSObject isKindOfClass:[NSObject class]] will return YES, the metaclass that originally got NSObject is not equal to [NSObject class]. But the special point is that the superclass of NSObject’s metaclass object points to its class object, so YES is returned here. Here’s a summary of ISA and Superclass in one of objective-C basics (in-depth understanding of OC objects).
  3. [XLPerson isMemberOfClass:[XLPerson class]] returns NO, the metaclass object obtained from XLPerson is not equal to [XLPerson class].
  4. [XLPerson isKindOfClass:[XLPerson class]] [XLPerson isKindOfClass:[XLPerson class]]] returns NO. Then I get the superclass of XLPerson and compare it with [XLPerson Class] and find that it’s not equal, all the way up to the metaclass of NSObject, and the superclass of NSObject points to the class object of NSObject, So the last traversal is comparing NSObject’s class object to [XLPerson Class], which is definitely not equal, so return NO.

Instance methods

  1. [Person isMemberOfClass:[NSObject class]] returns NO because the class object of Person is not equal to NSObject.
  2. [Person isKindOfClass:[NSObject class]] returns YES, because XLPerson originally inherits from NSObject, IsKindOfClass is an inheritance chain that finally finds the class object of NSObject and compares it to the parameter [NSObject Class], which is equal.
  3. [person isMemberOfClass:[XLPerson class]] returns YES, the class object of person is XLPerson.
  4. [person isKindOfClass:[XLPerson Class]] returns YES for the same reason.

The Runtime application

Use Runtime to view private member variables

Using Runtime, we can iterate through all the properties or member variables of a class. When we get the properties or member variables, we can do a lot of things, such as assigning values to private properties of system class objects in conjunction with KVC, or converting dictionaries to models, etc.

Access the private properties of the system class and assign values

To assign a value to a private property of a system class, if you need to know what private properties the system class has, first create a class of NSObject, NSObject+Ivar, as follows

@interface NSObject (Ivar)

+ (void)logIvar;

@end

@implementation NSObject (Ivar)

+ (void)logIvar{
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for(int i = 0; i < count; Ivar = ivars[I]; Ivar = ivars[I]; NSString *ivarName = [[NSString alloc] initWithUTF8String:ivar_getName(ivar)]; NSLog(@"% @", ivarName);
    }
    free(ivars);
}

@end
Copy the code

@tagTag @tagtag @tagtag @tagtag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag @tagTag Then use KVC to get the corresponding properties for setting

UITextField *field = [[UITextField alloc] initWithFrame:CGRectMake(100, 100, 200, 30)];
field.placeholder = @"I am the cue.";
[self.view addSubview:field];
[field setValue:[UIColor redColor] forKey:@"_placeholderLabel.textColor"];
Copy the code

Access to system class private member variables is not allowed after iOS 13.

Dictionary model

OC development process, involves a lot of dictionary model requirements, especially with the background interaction. If you don’t use runtime, you need to create your own methods and implement them yourself. The following

@interface XLPerson : NSObject

@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)int age;

+ (instancetype)personWithDic:(NSDictionary *)dic;

@end

@implementation XLPerson

+ (instancetype)personWithDic:(NSDictionary *)dic{
    XLPerson *person = [[XLPerson alloc] init];
    person.name = dic[@"name"];
    person.age = [dic[@"age"] intValue];
    
    return person;
}

@end
Copy the code

The above method can realize the requirement of dictionary transformation model, but if there is such a situation that the dictionary covers the dictionary, then the above method will be very troublesome to implement. Therefore, the role of Runtime is reflected in the above method. Typical dictionary transformation tool such as MJExtension also uses Runtime to implement.

Create a class of NSObject, NSObject+Json, and add the following method

@interface NSObject (Json) + (instancetype)objectWithDictionary:(NSDictionary *)dictionary; @end @implementation NSObject (Json) + (instancetype)objectWithDictionary:(NSDictionary *)dictionary{ id object = [[self  alloc] init]; unsigned int count; Ivar *ivars = class_copyIvarList(self, &count);for(int i = 0; i < count; Ivar = ivars[I]; Ivar = ivars[I]; NSMutableString *ivarName = [[NSMutableString alloc] initWithUTF8String:ivar_getName(ivar)]; / / remove the string before _ [ivarName deleteCharactersInRange: NSMakeRange (0, 1)]; Id value = dictionary[ivarName];if (value) {
            [object setValue:value forKey:ivarName];
        }
    }
    free(ivars);
    return object;
}

@end
Copy the code

Use as follows

NSDictionary *dic = @{@"name" : @"Zhang"The @"age": @}; XLPerson *person = [XLPerson objectWithDictionary:dic]; NSLog(@"%@ %d", person.name, person.age);
Copy the code

If you want to implement a dictionary nested dictionary-to-model approach, refer to the MJExtension implementation.

Method Swizzing of Runtime

As mentioned in the previous article, all object methods of a class are stored in the class object’s method list, that is, class_rw_t in objc_class. The source code for class_rw_t is shown below

struct class_rw_t { uint32_t flags; // Uint32_t version is used to store some basic information of the class; // Version number const class_ro_t *ro; //class ro_t type pointer method_array_t methods; // Method list property_array_t properties; // Protocol_array_t protocols; // Protocol list}Copy the code

Method_array_t is a two-dimensional array containing method_list_t. Each method_list_T contains all the methods of a classification. The last method_list_t in the two-dimensional array contains all the methods that were generated when the class was compiled. Method_list_t contains method_T, with each method corresponding to a method_T structure as follows

struct method_t { SEL name; // Method selector (method name) const char *types; // Method signature MethodListIMP IMP; // Method implementation address}Copy the code

In method_t, IMPs represent the address of the method implementation, so if you want to implement method swapping, you just need to change the IMP, and the Runtime does this by swapping two IMPs for method_T.

Create the XLPerson class as follows

@interface XLPerson : NSObject

- (void)run;
- (void)sleep;

@end

@implementation XLPerson

+ (void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method methodRun = class_getInstanceMethod(self, @selector(run));
        Method methodSleep = class_getInstanceMethod(self, @selector(sleep));
        method_exchangeImplementations(methodRun, methodSleep);
    });
}

- (void)run{
    
    NSLog(@"%s", __func__);
}

- (void)sleep{
    NSLog(@"%s", __func__);
}
@end

Copy the code

Calling the run and sleep methods will find that their implementations have been swapped. What is shown here is the exchange of object methods. Class method exchange is similar to object method exchange.

The runtime Api commonly used

Dynamic class

// create a class dynamically (parameter: Parent class, class name, Extra memory space) Class objc_allocateClassPair(Class superclass, const char *name, // Register a Class (add member variables before Class registration) void objc_registerClassPair(Class CLS Objc_disposeClassPair (Class CLS) // Obtain the Class Class object_getClass(id obj) that isa points to // set the Class Class object_setClass(id) that ISA points to obj, Class CLS) // Determine whether an OC object isClass BOOL object_isClass(id obj) // Determine whether aClass is a metaclass BOOL class_isMetaClass(Class CLS) // Obtain the parent Class Class class_getSuperclass(Class cls)Copy the code

Member variables

Ivar class_getInstanceVariable(Class CLS, Ivar *class_copyIvarList(Class CLS, const char *name) // Copy the Ivar *class_copyIvarList(Class CLS, Void object_setIvar(id obj, Ivar Ivar, id value) id object_getIvar(id obj, BOOL class_addIvar(Class CLS, const char * name, size_t size, BOOL class_addIvar(Class CLS, const char * name, size_t size, uint8_t alignment, Const char *ivar_getName(Ivar v) const char *ivar_getTypeEncoding(Ivar v)Copy the code

Attribute operation

Objc_property_t class_getProperty(Class CLS, Const char *name) // Copy the property list (class_property_t *class_copyPropertyList(Class CLS, Unsigned int *outCount) BOOL class_addProperty(Class CLS, const char *name, const objc_property_attribute_t *attributes, Void class_replaceProperty(Class CLS, const char *name, const objc_property_attribute_t *attributes, Const char *property_getName(objC_property_t property) const char *property_getAttributes(objc_property_t property)Copy the code

Method of operation

Method class_getInstanceMethod(Class CLS, SEL name) Method class_getInstanceMethod(Class CLS, SEL name) IMP class_getMethodImplementation(Class CLS, SEL name) IMP class_getMethodImplementation(Method m, IMP imp) void method_exchangeImplementations(Method m1, Method *class_copyMethodList(Class CLS, Unsigned int *outCount) BOOL class_addMethod(Class CLS, SEL name, IMP IMP, Const char *types) IMP class_replaceMethod(Class CLS, SEL name, IMP IMP, SEL method_getName(Method m) IMP method_getImplementation(Method m) const char *method_getTypeEncoding(Method m) unsigned int method_getNumberOfArguments(Method m) char *method_copyReturnType(Method m) char *method_copyArgumentType(Method m, Unsigned int index) // selector related const char *sel_getName(SEL SEL) SEL sel_registerName(const char * STR imp_implementationWithBlock(id block) id imp_getBlock(IMP anImp) BOOL imp_removeBlock(IMP anImp)Copy the code