Moment For Technology

Objective-c Runtime goes from application to principle

Posted on Sept. 27, 2022, 1:28 p.m. by Annette Knight
Category: ios Tag: ios The source code

The interview outline

What is it: the OC dynamic language runtime mechanism

Objc_msgSend message process:

  • Message send find
  • Dynamic method analysis
  • forward

Application:

  • associations
  • Methods to confuse
  • forward

Application 1.

1.1 Associated Objects

Application scenario: Add member variables to an existing class by category

You can write @property in a class, but you won't automatically generate private properties or set/ GET implementations. You'll only generate set/ GET declarations, which you'll need to implement yourself.

#import Foundation/Foundation.h
#import objc/message.h
@interface NSObject (YHName)
@property (nonatomic.strong) NSString *name;
@end
@implementation NSObject (YHName)
- (NSString *)name {
    return objc_getAssociatedObject(self.@"name_key");
}
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self.@"name_key", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
Copy the code
  • Implementation principles of associated objects:

An instance object corresponds to an ObjectAssociationMap, which stores the keys and ObjcAssociation of multiple associated objects of the instance object. ObjcAssociation stores the values and policies of associated objects. The associated objects are not placed in the original object, but a global map is maintained to store each object and its corresponding associated property table. If you set the associated object to nil, you remove the associated object.

1.2 Method Swizzling

(1) Method addition

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
// CLS: adds the class of the method
//name: name of the added method, @selector(methodName)
//imp: method implementation, function entry, function must be at least two arguments self and _cmd
//types: a string of the types of arguments and return values, using specific symbols
Copy the code

Adding methods dynamically to a class in this way is equivalent to a lazy method loading mechanism. Many methods in the class are not needed at the moment, so they are not loaded until they are needed.

(2) Method substitution

#import objc/message.h
@implementation UIImage (YHImage)
// Load is called only once when the class is loaded into memory
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(onceToken, ^{
        Method imageNamedMethod = class_getClassMethod(self.@selector(imageNamed:));
        Method yh_imageNamedMethod = class_getClassMethod(self.@selector(yh_imageNamed:));
        method_exchangeImplementations(imageNamedMethod, yh_imageNamedMethod);
    });
}

+ (UIImage *)yh_imageNamed:(NSString *)name {
	  // Call yh_imageNamed to avoid an infinite loop
    UIImage *image = [UIImage yh_imageNamed: name];
    // Add some custom processing
    return image;
}
@end
Copy the code

1.3 Method calls performSelector

PerformSelector: withObject: you can pass any message to an object, and you don't need to declare the methods at compile time (so there's a risk of crash, so use it with responseToSelector). Objc_msgSend, essentially

  • PerformSelector with timer:- (void)performSelector:(SEL)aSelector withObject:(id)arg; afterDelay:(NSTimeInterval)delay;
    • AfterDelay adds a timer in NSDefaultRunLoopMode, so the child thread cannot execute without a runloop.
    • ASelector is added to the end of the queue, and the selector will not execute until the method that is currently calling it has completed its execution;
  • Application: Prevent button from being clicked more than once
// Set it to the unclickable state after clicking, and restore it after 1 second- (void)buttonClicked:(id)sender{
    self.button.enabled = NO;
    [selfperformSelector:@selector(changeButtonStatus)withObject:nilafterDelay:1.0f];// Prevent repeated clicks} - (void)changeButtonStatus{
    self.button.enabled =YES;
}
Copy the code

1.4 Traversing all member variables of the class (private API, dictionary transfer model, automatic archive unfile)

KVC: traversing all keys of the dictionary, looking for the corresponding attribute name in the Model, based on the key of the dictionary; Runtime: Iterates over all property names in the Model and finds the corresponding key in the dictionary. The Runtime code is as follows:

+ (instancetype)modelWithDict:(NSDictionary *)dict {
    id objc = [[self alloc] init]; // 1. Create an object for the corresponding class
    unsigned int count = 0; // count: total number of member attributes
    // Get the list of member attributes and the number of member attributes
    Ivar *ivarList = class_copyIvarList(self, count);
    for (int i = 0 ; i  count; i++) {
        Ivar ivar = ivarList[i];
        NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSString *key = [propertyName substringFromIndex:1];
        id value = dict[key];
        // Get the member attribute type
        NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        if ([value isKindOfClass:[NSDictionary class]]  ![propertyType containsString:@"NS"]) {
            // Secondary conversion is required when value is a dictionary and the member attribute of value is not a system class
            NSRange range = [propertyType rangeOfString:@ "\" "]; // @\"Mode\"
            propertyType = [propertyType substringFromIndex:range.location + range.length];
            range = [propertyType rangeOfString:@ "\" "]; // Mode\" remove \"
            propertyType = [propertyType substringToIndex:range.location];
            // Get the class object that needs to be converted, and convert the string to the class name
            Class modelClass =  NSClassFromString(propertyType);
            if (modelClass) {
                value =  [modelClass modelWithDict:value]; // Return the value assigned to the secondary model}}if(value) { [objc setValue:value forKey:key]; }}return objc;
}
Copy the code

1.5 Use message forwarding to solve the circular reference problem of NSTimer

Runtime related methods for resolving circular references: Separate the target into a independent WeakProxy proxy object. The target of NSTimer is set as a WeakProxy object. WeakProxy is the proxy object passed to the Timer object. All messages sent to WeakProxy will be forwarded to the object passed to timer, which can achieve the purpose that NSTimer does not directly hold the object.

2. Principle: Object Indicates an Object

2.1 Underlying Structure

Every object is essentially a structure that stores a pointer to the object's class.

struct objc_object {
    struct objc_class *isa;
};
Copy the code

How much memory does an NSObject take up?

An NSObjec object has only one isa pointer member, which points to the objC_class structure. The memory of the pointer is 8 bytes (64 bits) /4 bytes (32 bits).

[Interview Question] How much memory does inheritance occupy? (64)

@interface Person : NSObject {
    int _age;
}
@end
@implementation Person
@end

@interface Student : Person {
    int _no;
}
@end
@implementation Student
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"%zd %zd", class_getInstanceSize([Person class]), class_getInstanceSize([Student class]));
    }
    return 0;
}
Copy the code

Both are 16 bytes due to memory alignment

  • Person: ISA 8 bytes + Age 4 bytes + Memory alignment = 16 bytes
  • Student: ISA 8 bytes + Age 4 bytes + No 4 bytes (aligned) = 16 bytes

When the compiler creates space for a structure, it first finds the widest basic data type in the structure, and then looks for the location where the memory address can be an integral multiple of the basic data type as the first address of the structure. The size of the widest base data type is used as the alignment modulus. Open space of a member of the structure before the compiler first check first address relative to the structure in the process of the open space first address offset is the member of integer times, if so, deposit, our members, on the other hand, is a member on the members and between fill certain bytes, to meet the requirements of integer times, and in the process of the open space is the first address backwards a few bytes.

  • Memory alignment summary:
  1. The front address must be a positive multiple of the back address
  2. The address of the entire Struct must be an integer multiple of the maximum byte

3. Principle: Class Class

The class object class and metaclass object Mete -class are of type class, and the underlying object is a pointer to the objC_class structure

3.1 Underlying Structure

struct objc_class {
    struct objc_class *isa;        / / isa pointer
    struct objc_class *superclass; // A pointer to the parent class
    cache_t cache;                 // Method cache
    class_data_bits_t bits;        // More class information can be obtained through the mask
};
Copy the code

Objc_class inherits from objc_object, and ISA is actually provided by the superclass objc_object

  • class_rw_tClass (obtained from objc_objec's bits+ mask)
struct class_rw_t {
    const class_ro_t *ro;             // Read-only class information
    method_list_t *methods;           // The list of methods
    property_list_t *properties;      // Attribute list
    const protocol_list_t *protocols; // Protocol list.// Omit other non-key members
};
Copy the code

Methods, Properties, and Protocols are read-write two-dimensional arrays that contain the contents of classes and classifications.

  • class_ro_tClass: read-only, storing member variable information and "non-dynamically added" methods, attributes, and protocols.
struct class_rw_t {
    const char *name;                     / / the name of the class
    const ivar_list_t *ivars              // The list of member variables
    method_list_t *baseMethodList;        // The list of methods
    property_list_t *baseProperties;      // Attribute list
    const protocol_list_t *baseProtocols; // Protocol list
Copy the code

Class methods, attributes, member variables, attribute protocols, and so on are stored in class_ro_t. When the program runs and needs to merge the list in the class with the original list of the class, the list in class_ro_t is merged with the list in the class and stored in class_rw_t.

The ISA pointer and superClass pointer in 3.2 Class point to

Class inheritance: NyanCat: Cat: NSObjectInstance object: Stores the values of ISA Pointers and member variables

  • The ISA pointer points to class objects

The value of a member variable is stored in the instance object, because a value is assigned to a member variable only when the instance object is created. But you only need one copy of the name and type of the member variable, so they are stored in the class object.

Class object Class: stores ISA pointer, superClass pointer, attribute member variable information, object method protocol method information

  • The ISA pointer points to a metaclass object
  • A superClass pointer to a subclass points to a superClass, and a superClass pointer to a base class points to nil

Meta Class: stores information about isa Pointers, superClass Pointers, and class methods

  • The ISA pointer to all metaclass objects points to the metaclass object of the base class (the ISA pointer to the metaclass object of the base class also points to itself)
  • A superClass pointer to a metaclass object of a subclass points to a metaclass object of a parent class, and a superClass pointer to a metaclass object of a base class points to a class object of a base class;

[Interview Question] isKindOfClass isMemberClass

YHPerson is a subclass of NSObject
BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]].BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]].BOOL res3 = [[YHPerson class] isKindOfClass:[YHPerson class]].BOOL res4 = [[YHPerson class] isMemberOfClass:[YHPerson class]].Copy the code

Res1-4 are: YES; NO ; NO ; NO; Cause: A is A class object and B is A metaclass object, so res2/3 are both NO but res1, the superClass pointer to the metaclass object of base class NSObject is YES

  • IsKindOfClass isMemberClass
+ (BOOL)isMemberOfClass:(Class)cls {
    return self-ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self-ISA(); tcls; tcls = tcls-superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls-superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
Copy the code

Point 1: The logical difference between the two methods

  • A isKindOfClass B: select A class object or isa pointer from A and check whether its superClass is B
  • A isMemberOfClass B: Takes A class object or isa pointer from A and determines whether it is B

Knowledge point 2: Different operations for callers

  • For instance object A, take the class object: person - [person class]
  • [Person class] - object_getClass([Person class])

A superClass pointer to a base class metaclass object points to a base class object

The output class for calling Super

YHPerson is a subclass of NSObject
// Call the following method within YHPerson (subclass)
NSLog(@ "% @"[self class]);
NSLog(@ "% @"[super class]);
NSLog(@ "% @"[self superclass]);
NSLog(@ "% @"[super superclass]);
Copy the code

Cause: When a method is called with super, the compiler sees the super flag and tells the current object to call the superclass method, which is essentially what the current object is calling, just going to the superclass to find the implementation. Super is just a compile indicator. While the receiver of the message is still self, the superclass simply tells the message which class to look for (starting with the class object of the superclass), and finally when NSObject gets the ISA pointer, it still gets self's ISA.

The method_t structure in Class 3.3

struct method_t {
    SEL name;  // Function name (selector)
    const char *types;  // Encoding string (return value type, parameter type)
    IMP imp; // The actual implementation of the function, pointer to the function (store the function address)
};
Copy the code

An SEL with the same method name in different classes is globally unique.

Cache method in 3.4 Class

Used to cache methods that have been called to speed up method lookups

struct cache_t {
    struct bucket_t* _buckets; // Hash table with multiple groups of keys and values
    mask_t _mask; // Hash table length -1
    mask_t _occupied; // Number of cached methods
};
Copy the code

Bucket_t stores a list of methods as an array:

struct bucket_t {
private:
    cache_key_t _key; // Use SEL as the Key
    IMP _imp; // The memory address of the function is used as value
};
Copy the code

4. How it works: Category

Put the methods, properties, and protocol data from the category into the category_t structure, and then copy the method list from the structure to the method list of the class object. There are no member variables in the class structure. Although we can write @property in the class, we will not automatically generate private properties and the implementation of the set/ GET method. We will only generate the declaration of the set/get method. At compile time, the classification method, attribute, and protocol list is placed before the original stored method, attribute, and protocol list in the class object to ensure that the classification method is called first. So instead of overriding this class method per se, this class method still exists.

[Interview question] The difference between classification and expansion

  1. Extensions can be understood as anonymous categories and are usually written directly into the main class file
  2. Extensions are loaded at compile time, and classifications are loaded at run time.

What can I do if I don't want to call the implementation of the class when the class method has the same name as the original class method

Change the order in which methods are found to look for methods of the original class object first.

5. Principle: KVO

5.1 Listening Principles

To addObserver A1 the object instance variable a, will modify A1 instance objects isa pointer, from point to a class object to point to an a subclass of NSKVONotifying_A (through the Runtime dynamically created), subclass have their own set method implementation, will, in turn, call: WillChangeValueForKey - original set implementation - didChangeValueForKey. DidChangeValueForKey will then call the listener observeValueForKeyPath: ofObject: change: context: monitoring method

Dynamically generated NSKVONotifying_A structure: isa, superClass, setAge method, class method

  • SetAge method: Set method that overrides the observed variable
  • Class method: overwrite to hide its presence (returning one level above) so that an instance of A calling class still returns A instead of the real class NSKVONotifying_A

5.2 How do I Manually Trigger KVO without Changing the Value

Call willChangeValueForKey and didChangeValueForKey.

[p1 willChangeValueForKey: @ "age"); [p1 didChangeValueForKey: @ "age");Copy the code

6. Principle: Method call procedure objc_msgSend

The process of calling a method, underneath, is actually a method call from objc_msgSend, sending a message to the method caller: Objc_msgSend (object, sel_registerName(" method name ")) where the sel_registerName(" method name ") passed in is actually the everyday @selector(" method name "), which is an sel.

6.1 Summary of the three phases of objc_msgSend

  1. Message sending and lookup
  2. Dynamic method analysis
  3. forward

Failed to find the method at all three stages - "Unrecognized Selector sent to instance" crashed

Each OC method is ultimately a C function, and by default any method takes two arguments: self: method caller _cmd: calling method number

Objc_msgSend: objc-msg-arm64.s ENTRY - END_ENTRY

6.2 Phase 1: Message sending and finding

Find the method cache list of the recipient's class (obtained from the ISA pointer). If the method is hit, call the method directly. If not, a binary lookup/traversal lookup (depending on whether the methods list is ordered) is made to the methods list in the receiver class_rw_t. If found, the method cache is filled and called. If it is not found, the method cache and method list of each parent class (receiverClass finds superClass through the superClass pointer) are searched one by one. If the parent class cache/method is found, it is filled into the cache of the recipient class and called. If not, move on to the next step, dynamic method analysis.

6.3 Phase 2: Dynamic Method Resolver

Dynamic method parsing is performed only once, and if it has already been parsed, it is skipped directly into message forwarding

  • If metaclass object: callresolveClassMethod
  • If class object: callresolveInstanceMethod

Whether or not the developer overrides the implementation of dynamic method resolution, it is retry back into the message sending process

Application: Dynamically add methods to resolveInstanceMethod/resolveClassMethod

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) { //test: the implementation method needs to be added dynamically
        Method method = class_getInstanceMethod(self.@selector(other));//other: want to replace the implementation of other methods; Method is actually a pointer to the method_t structure
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEdcoding(method)); // Self is a class object, not a metaclass
        // If implemented within resolveClassMethod, the first argument should be the metaclass object object_getClass(self).
        return YES; // Return YES to indicate that the method was added dynamically
    }
    return [super resolveInstanceMethod:sel];
}
Copy the code

6.4 Phase 3: Message forwarding Method Forward

  1. callforwardingTargetForSelectorMethod, you can override this method and forward it to another class for processing, or if you return an object, send a message to that objectObjc_msgSend (object, sel_registerName(" method name ")); If this method returns null, go to the next step.
  2. Call methodSignatureForSelector method, obtaining a method signature (return value type and parameter types information), can be dealt with here revising object.
  3. If methodSignatureForSelector returns null, call forward forwardInvocation method Invocation. (You can override the Invocation parameter, again, to modify the processing object)

NSInvocation encapsulates a method invocation, including the method invocation, method, and method parameters. It's easier to forward the message than to blur the method when handling all methods


7. WWDC 2020 Runtime optimization

7.1 Class data structure optimization: split the dynamic part of class_rw_t

Splitting the dynamic part of class_rw_t into class_rw_ext_t saves memory

7.2 Method List storage addresses are optimized to relative addresses

Memory savings

For methods obfuscation where absolute addresses are needed, a global mapping table is used to maintain the absolute addresses of obfuscation methods.

7.3 Tagged Pointer Format changes

Tagged Pointer: Sets the last bit of the Pointer to a special flag bit and stores data directly in the Pointer itself. Optimization: change the ARM64 from the lowest bit flag to the highest bit flag. This is an optimization for objc_msgSend. The highest bit can be checked only once by comparison.

A selection of articles about Runtime for review

Classes and objects in the Objective - C | Garan no dou messages and forwarding of Objective - C | Garan no dou

Search
About
mo4tech.com (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.