IOS Interview Investigation (1) : Runtime related questions

IOS interview Investigation (9) : Performance optimization related questions

@[TOC]

1. IOS Interview Investigation (1) : Runtime related questions

1.1 Runtime Issues

Runtime is the core knowledge of iOS development, and if the following problems are solved, then you have a deep understanding of Runtime. Runtime is already open source. There is a source code for ObjC-Runtime that others have debugged and run. You can also find ObjC4 on objC4’s official website

When the official code is downloaded to make it work, it takes a little time to fill in the holes. Here’s the objC4_750 code I’ve filled in to compile and run directly:

  • Objc4_750 can compile the code: link: pan.baidu.com/s/1y0GgOOpF… Password: GMLX
  • Objc4_756. 2 can be compiled code: link: pan.baidu.com/s/15hzKJ12U… Password: azet

Think about these questions first and see how many you can answer:

  1. How does runtime add properties, methods, etc.
  2. How does the Runtime implement the weak attribute
  3. How does runtime find the corresponding IMP address by selector? (Consider class and instance methods respectively)
  4. Do objects associated with the Runtime Associate method need to be released when the main object is dealloc?
  5. What does the _objc_msgForward function do? What happens when you call it directly?
  6. Can I add instance variables to the compiled class? Can I add instance variables to classes created at run time? Why is that?
  7. A brief description of how methods are called in Objective-C (Runtime)
  8. What is Method Swizzling?

Let’s start with a quick look at three simple runtime problems:

  1. What is the runtime?
  2. Why use Runtime?
  3. What does runtime do?
    1. What is the runtime?
  1. Runtime is essentially a set of low-level C, C++, assembly API. We have what we call runtime, and many of the implementations at the bottom of runtime are directly in assembly code for performance efficiency.
  2. The OC code we normally write, which needs Runtime to create classes and objects, send and forward messages, will eventually be converted to Runtime’S C code.
  3. Runtime postpones the determination of data types from compile time to runtime.
    1. Why use Runtime?
  1. OC is a dynamic language that puts some of the work into runtime rather than compile-time, so a compiler is not enough. We also need a runtime system to handle compiled code.
  2. Runtime is basically written in C and assembly language, and Apple and GNU each maintain an open source version of runtime that is highly consistent with each other.
    1. What does runtime do?
  1. Message passing and forwarding
  2. Access private variables –eg:(UITextFiled modification)
  3. Switch system method –eg:(block — prevent button clicks)
  4. Dynamic increment method
  5. Add attributes for categories
  6. Dictionary to model — eg:(YYModel, MJModel)

Here are some answers to the Runtime interview questions.

1.1.1 How do I add attributes and methods to runtime

Ivar represents the member variable class_addIvar class_addMethod class_addProperty class_addProtocol class_replaceProperty

1.1.1.1 Dynamically Adding Attributes

  • Requirements:NSObjectAdd a name attribute, dynamically add the attribute -> Runtime

Ideas:

  1. Add a class to NSObject, add attributes to the class. @property generates only get and set declarations, not get and set implementations and underline member properties, so implement setter/getter methods in a. M file and store slipper properties static, so that when the object is destroyed, the property cannot be destroyed.
  2. Using runtime to dynamically add attributes: The essence is to create an association between attributes and an object.

The implementation code is as follows:

#import <objc/message.h>
@implementation NSObject (Property)
//static NSString *_name; // Add member attributes dynamically because static does not destroy the attributes in the cache pool
// Whenever you want to call the Runtime method, think: whose business
-(void)setName:(NSString *)name
{
    / / save the name
    // Dynamically add attributes = essence: Associate an attribute of an object with a value
    /* objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>) */
    objc_setAssociatedObject(self."name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// _name = name;

}

- (NSString *)name
{
    return objc_getAssociatedObject(self."name");
// return _name;

}
@end
Copy the code
1.1.1.1.1 Automatically Generate attributes

If there are hundreds of keys that need to be used, many @property member attributes need to be written. Here is a universal method that can directly convert the dictionary array to all @property member attributes and print them out, so that they can be copied directly into the model.

  • The automatic generated property code is as follows:
#import "NSDictionary+PropertyCode.h"
@implementation NSDictionary (PropertyCode)

// This method automatically converts the dictionary into the property code needed in the model

// Private API: real, but Apple doesn't show it to you. If it's a BOOL, you can print __NSCFBoolean, but you can't type it out, so NSClassFromString(@"__NSCFBoolean")

// isKindOfClass: BOOL is a subclass of NSNumber, BOOL is a subclass of NSNumber
- (void)createPropetyCode
{
    // Attributes in the model are based on dictionary keys
    // How many keys are there, how many attributes are generated
    NSMutableString *codes = [NSMutableString string];
    // walk through the dictionary
    [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {
        NSString *code = nil;
        

// NSLog(@"%@",[value class]);
        
        if ([value isKindOfClass:[NSString class]]) {
          code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
        } else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
        } else if ([value isKindOfClass:[NSNumber class]]) {
             code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
        } else if ([value isKindOfClass:[NSArray class]]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
        } else if ([value isKindOfClass:[NSDictionary class]]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
        }
        
        // Concatenates a string
        [codes appendFormat:@"%@\n",code];

    }];
    
    NSLog(@"% @",codes);
    
}

@end
Copy the code
1.1.1.1.2 KVC dictionary to model
  • Requirement: During development, the background usually gives you a lot of data, but not all data is useful. Do you need to save the unused data in the model? There are many third-party frameworks implemented based on these principles, such asYYModel.MJModel.

The implementation code is as follows:

@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
    // Create the model
    Status *s = [[self alloc] init];
    
    // save dictionary value to model attribute
    [s setValuesForKeysWithDictionary:dict];

// s.reposts_count = dict[@"reposts_count"];
    // 4️ MJExtension: Dictionary transformation model can be carried on, and the number of properties in the model can be directly extracted from the dictionary and assigned to the model without one-to-one correspondence with the properties in the dictionary. Runtime runs over the number of properties in the model
    
    / / 1 ️ ⃣ setValuesForKeysWithDictionary: the underlying implementation methods: iterates through all the key in the dictionary, to model finds the corresponding attribute, the value to the model attribute assignment, namely, calling the following methods:
    /* [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, [s setValue:dict[@"source"] forKey:@"source"]; // The underlying implementation is: 2️ [s setValue:dict[@"source"] forKey:@"source"]; 1. First, we will search for setSource method in the model and directly call set method [s setSource:dict[@"source"]]; 2. Source = dict[@"source"] _source = dict[@"source"] _source = dict[@"source" SetValue: forUndefinedKey: direct error [s setValue: obj forKey: key];}]; * /
    return s;
}
     

// 3️ KVC, do not want the system to report errors, rewrite the system method idea:
// 1. Want to add functionality to system methods
// 2. Do not want system implementation
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}


@end
Copy the code
1.1.1.1.3 low-level implementation of MJExtention
#import "NSObject+Model.h"
#import <objc/message.h>

// class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) retrieves the property list

@implementation NSObject (Model)

/** dictionary to model @param dict passes in the dictionary to model @return assigned model */

+ (instancetype)modelWithDict:(NSDictionary *)dict

{
    id objc = [[self alloc] init];

    // Add a value to the model. // Add a value to the model
    // 1. Get all attributes in the model -> save to class
    // ivar: underline member variable and Property: Property
    // Get the list of member variables
    // class: Gets the list of class member variables
    // count: total number of member variables
    // This method gets an array of member variables
    //class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
    
    int count = 0;
    // The array of member variables points to the 0th element of the array
    Ivar *ivarList = class_copyIvarList(self, &count);


    // Iterate over all member variables
    for (int i = 0; i < count; i++) {
        
        // Get the member variable user
        Ivar ivar = ivarList[i];
        // Get the member variable name, that is, C characters into OC strings
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // Get the type of the member variable used to get the model name of the second-level dictionary
        NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        // Convert a string like type @"@\"User\"" to @"User"
        type = [type stringByReplacingOccurrencesOfString:@"@ \" " withString:@""];
        type = [type stringByReplacingOccurrencesOfString:@"\" " withString:@""];
        
        // Member variable name conversion key, that is, remove the underscore before the member variable
        NSString *key = [ivarName substringFromIndex:1];
        
        // Fetch the corresponding value dict[@"user"] -> dictionary
        id value = dict[key];
        
        // Secondary conversion
        // If it is a custom type, it needs to be converted
        if ([value isKindOfClass:[NSDictionary class]] && ![type containsString: @"NS"]) { // Only dictionaries need to be converted
           
            Class className = NSClassFromString(type);
            
            // dictionary to model
            value = [className modelWithDict:value];
        }
        
        // Assign values to attributes in the model key:user value: dictionary -> model
        if(value) { [objc setValue:value forKey:key]; }}return objc;

}

@end
Copy the code
1.1.1.1.4 Automatic serialization
  • Use the functions provided by Runtime to traverse all the attributes of the Model itself, and encode and decode the attributes.

Override methods in Model base classes:

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int outCount;
        Ivar * ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i+ +){
            Ivar ivar = ivars[i];
            NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [selfsetValue:[aDecoder decodeObjectForKey:key] forKey:key]; }}return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount;
    Ivar * ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i+ +){
        Ivar ivar = ivars[i];
        NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[selfvalueForKey:key] forKey:key]; }}Copy the code

1.1.1.2 Dynamic Add Method

  • Development usage scenario: if a class method is very many, loading the class into memory is also relatively expensive resources, need to generate mapping table for each method, you can use dynamic to a class, add method to solve.

Possible interview question: do you use performSelector, but the main question is have you ever added a method dynamically.

Dynamically add method code implementation:

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    Person *p = [[Person alloc] init];

    // The default person, which does not implement the eat method, can be called via performSelector, but will report an error.
    // Dynamic add methods do not report errors
    [p performSelector:@selector(eat)];
}

@end


@implementation Person
// void(*)()
// The default method takes two implicit arguments,
void eat(id self.SEL sel)
{
    NSLog(@"% @ % @".self.NSStringFromSelector(sel));
}

// When an object calls an unimplemented method, the method is called and the corresponding list of methods is passed.
// This can be used to determine if an unimplemented method is a method that we want to add dynamically
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // add the eat method dynamically

        // The first argument: which class to add a method to
        // The second argument: add the method number
        // Add the function implementation of the method (function address)
        V :void @: object ->self: SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}
@end
Copy the code

1.1.1.3 Class, object associated object

OC classes allow adding attributes to classes, but do not automatically generate getters and setters.

Instead of adding attributes or member variables to the class \ object (which cannot be obtained via ivarList or propertyList after setting the association), the associative object adds an associated object to the class. It is usually used to store class information, such as an array of class property lists, for the convenience of dictionary transformation in the future.

Runtime associated object API:

// Associate objects
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// Get the associated object
id objc_getAssociatedObject(id object, const void *key)
// Remove the associated object
void objc_removeAssociatedObjects(id object)
Copy the code
  • Add attributes to categories:
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Add the system NSObject class property name dynamically
    NSObject *objc = [[NSObject alloc] init];
    objc.name = @"Kong Yulu";
    NSLog(@"% @",objc.name);
}
@end


// Define the associated key
static const char *key = "name";

@implementation NSObject (Property)

- (NSString *)name
{
    // Get the associated value based on the associated key.
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name
{
    // The first argument: which object to add the association to
    // The second argument: the associated key, obtained by this key
    // The third argument: the associated value
    // Fourth argument: associated policy
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
Copy the code
  • Add an associated object to an object: an alertView, for example, usually passes a value using the alertView tag attribute. We want to pass more parameters to the alertView agent:
/** * delete click * @param recId cart ID */
- (void)shopCartCell:(BSShopCartCell *)shopCartCell didDeleteClickedAtRecId:(NSString *)recId
{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"Confirm to delete this baby." delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Sure".nil];
    / * objc_setAssociatedObject method of parameter interpretation: the first parameter: id object, the current object The second parameter: const void * key, the associated key, is the c string The third argument: Id value, the value of the object to be associated. Parameter 4: objc_AssociationPolicy Policy Specifies the rule to be associated with. */
    // Pass multiple parameters
    objc_setAssociatedObject(alert, "suppliers_id"The @"1".OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_setAssociatedObject(alert, "warehouse_id"The @"2".OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    alert.tag = [recId intValue];
    [alert show];
}

/** * Confirm delete operation */
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 1) {
        
        NSString *warehouse_id = objc_getAssociatedObject(alertView, "warehouse_id");
        NSString *suppliers_id = objc_getAssociatedObject(alertView, "suppliers_id");
        NSString *recId = [NSString stringWithFormat:@"%ld",(long)alertView.tag]; }}Copy the code

1.1.2 How does the Runtime Implement the Weak attribute

  • The weak attribute is weak.

The weak policy indicates that this attribute defines a nonowning relationship. When you set a new value for this property, the setting method neither preserves the new value nor frees the old value. This attribute is similar to assign; However, property values are nil out when the object to which the property refers is destroyed.

  • How does the Runtime automatically set weak to nil?

The Runtime lays out the registered classes and puts the weak object into a hash table. When the reference count of this object is 0, the dealloc method will be called. If the memory address of the object pointed to by weak is A, the dealloc method will be searched in the hash table with a as key. Find all weak objects with key A and set them to nil.

  • Does the weak property need to be nil in dealloc?

In the ARC environment you don’t have to set the strong pointer or the weak pointer to nil in dealloc, ARC will do it for you automatically even if the compiler doesn’t do it for you, weak doesn’t have to set nil in Dealloc and the value of the property will be cleared if the object that the property refers to gets destroyed

objc// The setter method for weak is similar to the following
- (void)setObject:(NSObject *)object{   

  objc_setAssociatedObject(self."object", object, OBJC_ASSOCIATION_ASSIGN);

 [object cyl_runAtDealloc:^{ _object = nil; }];
 }

Copy the code

Objc /objc-weak.h objc/ weak.h

/** * The internal structure stored in the weak references table. * It maintains and stores * a hash set of weak references pointing to an object. * If out_of_line==0, the set is instead a small inline array. */
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
   DisguisedPtr<objc_object> referent;
   union {
       struct {
           weak_referrer_t *referrers;
           uintptr_t        out_of_line : 1;
           uintptr_t        num_refs : PTR_MINUS_1;
           uintptr_t        mask;
           uintptr_t        max_hash_displacement;
       };
       struct {
           // out_of_line=0 is LSB of one of these (don't care which)
           weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
       };
   };
};

/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
   weak_entry_t *weak_entries;
   size_t    num_entries;
   uintptr_t mask;
   uintptr_t max_hash_displacement;
};
Copy the code

We can design a function (pseudocode) to represent the above mechanism:

The objc_storeWeak(&a, b) function:

The objc_storeWeak function registers the memory address of the assignment object (b) as key and the memory address of the weak variable (a) as value. If the second argument (b) is 0 (nil), then remove the memory address (&a) of variable (a) from the weak table.

You can think of objc_storeWeak(&a, b) as objc_storeWeak(value, key), and set value to nil when key goes nil.

A and B point to the same memory address when B is non-nil, and a becomes nil when B becomes nil. Sending a message to A does not crash at this point: it is safe to send a message to nil in Objective-C.

If a is assigned, then a and B refer to the same memory address when B is not nil. If B becomes nil, A refers to the same memory address again. In this case, sending messages to A may crash.

Here we will use pseudocode to simulate how the Runtime implements the weak property based on the objc_storeWeak(&a, b) function:

// Using pseudo-code simulation: How does the Runtime implement the weak attribute
 id obj1;
 objc_initWeak(&obj1, obj);
/*obj reference count becomes 0, variable scope ends */
 objc_destroyWeak(&obj1);
Copy the code

The objc_initWeak and objc_destroyWeak methods are explained below:

In general, it initializes the “variable with the weak modifier (obj1)” with the objc_initWeak function, and releases it at the end of the scope with the objc_destoryWeak function (obj1).

The internal implementation of the methods is described below:

The implementation of the objc_initWeak function looks like this: after initializing the variable with the weak modifier (obj1) to 0 (nil), the objc_storeWeak function is called with the assignment object (obj) as an argument.

obj1 = 0; obj_storeWeak(&obj1, obj);Copy the code

That is: The pointer to the weak modifier defaults to nil (it is safe to send messages to nil in Objective-C). The obj_destroyWeak function then calls the objc_storeWeak function, taking 0 (nil) as an argument. objc_storeWeak(&obj1, 0); The previous source code is the same as the following.

// Using pseudo-code simulation: How does the Runtime implement the weak attribute
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/ *... Obj's reference count becomes 0, set to nil... * /
objc_storeWeak(&obj1, 0);
Copy the code

The objc_storeWeak function takes the memory address of the assignment object (obj) as the key value and registers the memory address of the property variable (obj1) modified with weak as the first argument into the weak table. If the second argument (obj) is 0 (nil), remove the address of the variable (obj1) from the weak table,

The use of pseudo-code is for easy understanding, let’s “real” implementation below:

  • How to make @property, which does not use the weak modifier, have the weak effect.

Let’s start with setter methods:

(Note that the following implementation of the cyl_runAtDealloc method is only for simulation. If you want to use it in a project, you need to consider more complex scenarios. If you want to use it in a real project, you can use a library, CYLDeallocBlockExecutor, written by ChenYilong.)

- (void)setObject:(NSObject *)object
{
   objc_setAssociatedObject(self."object", object, OBJC_ASSOCIATION_ASSIGN);
   [object cyl_runAtDealloc:^{
       _object = nil;
   }];
}
Copy the code

There are two steps:

(1) Do the following in the setter method:

objc_setAssociatedObject(self."object", object, OBJC_ASSOCIATION_ASSIGN);

Copy the code

(2) When the object to which the attribute refers is destroyed, the value of the attribute is nil out. To do this, again use runtime:

// The target object to destroy
id objectToBeDeallocated;
// Can be interpreted as an "event" : an "event" that will occur simultaneously when the above target object is destroyed.
id objectWeWantToBeReleasedWhenThatHappens;
objc_setAssociatedObject(objectToBeDeallocted,
                        someUniqueKey,
                        objectWeWantToBeReleasedWhenThatHappens,
                        OBJC_ASSOCIATION_RETAIN);
Copy the code

With that in mind, we start implementing the cyl_runAtDealloc method in two parts:

  • Part one: Create a class, which you can think of as an “event” : an “event” that will occur simultaneously when the target object is destroyed. Perform “events” with blocks.
// This class can be understood as an "event" : an "event" that occurs simultaneously when the target object is destroyed. Perform "events" with blocks.

typedef void (^voidBlock)(void);

@interface CYLBlockExecutor : NSObject
- (id)initWithBlock:(voidBlock)block;
@end

@interface CYLBlockExecutor() {
   voidBlock _block;
}
@implementation CYLBlockExecutor

- (id)initWithBlock:(voidBlock)aBlock
{
   self = [super init];
   if (self) {
       _block = [aBlock copy];
   }
   return self;
}

- (void)dealloc
{
   _block ? _block() : nil;
}

@end
Copy the code
  • Part two: Core code: Use runtime to implement the cyl_runAtDealloc method
// Use runtime to implement the cyl_runAtDealloc method

#import "CYLBlockExecutor.h"

const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;

@interface NSObject (CYLRunAtDealloc)
- (void)cyl_runAtDealloc:(voidBlock)block;
@end

@implementation NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block
{
   if (block) {
       CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];
       
       objc_setAssociatedObject(self,
                                runAtDeallocBlockKey,
                                executor,
                                OBJC_ASSOCIATION_RETAIN);
   }
}

@end
Copy the code

To use: import #import “CYLNSObject+ runatdealloc.h “and then use:

NSObject *foo = [[NSObject alloc] init];

[foo cyl_runAtDealloc:^{
   NSLog(@"Releasing Foo!");
}];
Copy the code

1.1.2.1 How does Runtime automatically set weak variables to nil?

The Runtime lays out the registered classes and puts weak objects into a hash table. If the reference count of this object is 0, dealloc will be dealloc. If the memory address of the weak object is A, then a will be searched in the weak table, find all the weak objects with a as the key, and set to nil.

We can design a function (pseudocode) to represent the above mechanism:

The objc_storeWeak(&a, b) function:

  • The objc_storeWeak function registers the memory address of the assignment object (b) as key and the memory address of the weak variable (a) as value. If the second argument (b) is 0 (nil), then remove the memory address (&a) of variable (a) from the weak table.

  • You can think of objc_storeWeak(&a, b) as objc_storeWeak(value, key), and set value to nil when key goes nil.

  • A and B point to the same memory address when B is non-nil, and a becomes nil when B becomes nil. Sending a message to A does not crash at this point: it is safe to send a message to nil in Objective-C.

  • If a is assigned, then a and B refer to the same memory address when B is not nil. If B becomes nil, A refers to the same memory address again. In this case, sending messages to A may crash.

Here we will use pseudocode to simulate how the Runtime implements the weak property based on the objc_storeWeak(&a, b) function:

 id obj1;
 objc_initWeak(&obj1, obj);
/*obj reference count becomes 0, variable scope ends */
 objc_destroyWeak(&obj1);
Copy the code

The objc_initWeak and objc_destroyWeak methods are explained below:

In general, it initializes the “variable with the weak modifier (obj1)” with the objc_initWeak function, and releases it at the end of the scope with the objc_destoryWeak function (obj1).

The internal implementation of the methods is described below:

The implementation of the objc_initWeak function looks like this: after initializing the variable with the weak modifier (obj1) to 0 (nil), the objc_storeWeak function is called with the assignment object (obj) as an argument.

obj1 = 0; obj_storeWeak(&obj1, obj);Copy the code

That is: the weak modifier’s pointer defaults to nil (it is safe to send messages to nil in Objective-C) and then the obj_destroyWeak function takes 0 (nil) as an argument and calls objc_storeWeak.

objc_storeWeak(&obj1, 0);
Copy the code
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/ *... Obj's reference count becomes 0, set to nil... * /
objc_storeWeak(&obj1, 0);
Copy the code

The objc_storeWeak function takes the memory address of the assignment object (obj) as the key value and registers the memory address of the property variable (obj1) modified with weak as the first argument into the weak table. If the second argument (obj) is 0 (nil), remove the address of the variable (obj1) from the weak table.

1.1.3 How does runtime find the corresponding IMP address by selector? (Consider class and instance methods respectively)

  1. Each class object has a list of object methods (object method cache)
  2. The list of class methods is stored in metaclass objects that isa Pointers to in class objects (class method cache)
  3. In the method list, each method structure records the name of the method, the method implementation, and the parameter type. In fact, the selector is essentially the method name, through which you can find the corresponding method implementation in the method list.
  4. When we send a message to an NSObject object, the message is looked up in the list of methods on the object’s class object
  5. When we send a message to a Class, the message will be looked up in the list of methods in the Class’s Meta Class object

A metaclass, like the class before it, is an object, and all metaclasses use the root metaclass (the metaclass of the class at the top of the inheritance hierarchy) as their class. This means that the metaclass of all subclasses of NSObject (most classes) takes NSObject’s metaclass as their class. According to this rule, all metaclass use the root metaclass as their class, and the root metaclass is itself. That is, the isa pointer to the metaclass of the base class points to itself.

1.1.4 Should objects associated with the Runtime Associate method be dealloc?

Not required either under MRC or ARC. Associated objects are released much later in their life cycle than the objects themselves in object_Dispose () method called by nsobject-dealloc

  1. Call-release: Reference count goes to zero The object is being destroyed and its life cycle is coming to an end. There can be no new __weak reference, otherwise it will point to nil. Call [self dealloc]
  2. The parent class is called -dealloc and the most directly inherited parent class in the inheritance relationship is called -dealloc. If it is MRC code, the instance variable will be manually released. (iVars) The parent class of each layer in the inheritance relationship is called -dealloc
  3. NSObject call-dealloc does only one thing: call object_Dispose () in the Objective-C Runtime
  4. Call object_dispose() for C++ instance variables (iVars) and call destructors -release for ARC instance variables (iVars) to disengage all use of runtime The Associate method removes all __weak references to the object and calls free().

1.1.5_objc_msgforward what does the function do? What happens when you call it directly?

_objc_msgForward is an IMP type for message forwarding: when a message is sent to an object and it is not implemented, _objc_msgForward will attempt to forward the message. Calling _objc_msgForward is a very dangerous thing to do. It’s a two-edged knife, and if it’s used badly it will Crash the program, but if it’s used well, JSPatch can do a lot of really cool things by calling _objc_msgForward directly to implement its core functionality, which is explained in the first question answered here

Create a _objc_msgForward object like this: IMP msgForwardIMP = _objc_msgForward; .

We know the role of objc_msgSend in “messaging.” In the “message passing” process, the action of objc_msgSend is clear:

  1. First look for the IMP in the cache of Class (initialize the cache if there is no cache),
  2. If not, look for the parent Class.
  3. If the root class is still not implemented, use the _objc_msgForward function pointer instead of IMP.
  4. Finally, execute the IMP.

Look for lookUpImpOrForward in the objc-Runtime-new. mm file in the objc4_750 source code

/*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails) * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) * Most callers should use initialize==YES and cache==YES. * inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. * If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/
Copy the code

Objc-runtime-new. mm file for _objc_msgForward:

id objc_msgSend(id self.SEL op, ...) {
    if (!self) return nil;
	IMP imp = class_getMethodImplementation(self->isa, SEL op);
	imp(self, op, ...) ;// Call this function, pseudo code...
}
 
/ / find the IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if(! cls || ! sel)return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if(! imp)return _objc_msgForward; //_objc_msgForward is used to forward messages
    return imp;
}
 
IMP lookUpImpOrNil(Class cls, SEL sel) {
    if(! cls->initialize()) { _class_initialize(cls); }Class curClass = cls;
    IMP imp = nil;
    do { // Check the cache first, if there is no cache, rebuild, if there is still no parent class query
        if(! curClass)break;
        if(! curClass->cache) fill_cache(cls, curClass); imp = cache_getImp(curClass, sel);if (imp) break;
    } while (curClass = curClass->superclass);
 
    return imp;
}
Copy the code

Although Apple did not release the source code for _objc_msgForward, we can conclude that:

  1. _objc_msgForwardIs a function pointer (andIMPIs used for message forwarding: when a message is sent to an object and it is not implemented,_objc_msgForwardWill attempt to do message forwarding.
  2. objc_msgSendRole in messaging: During messaging,objc_msgSend(1) First of all inClassCache lookup inIMPIf no cache is found, initialize the cacheClassLookup. (3) If the root class is still not found, then use_objc_msgForwardFunction pointer substitutionIMP. (4) Finally, execute thisIMP

To demonstrate the action of message forwarding, try sending an error message to an object and see how _objc_msgForward forwards.

First open the debug mode, sent messages: to print out all the runtime can perform the following method in the code: (void) instrumentObjcMessageSends (YES);

Because this function is in objc-internal.h, and the file is not open, it is called with a declaration first. The purpose is to tell the compiler that the object file contains the method and let the compilation pass:

OBJC_EXPORT void
instrumentObjcMessageSends(BOOL flag)
OBJC_AVAILABLE(10.0.2.0.9.0.1.0.2.0);
Copy the code

Or breakpoint to suspend the program runs, and enter the following command in the GDB: call (void) instrumentObjcMessageSends (YES)

In the second case, the operations are as follows:

After that, all messages sent by the runtime are printed to the/TMP/msgsend-xxxx file.

Enter the command to go to: open /private/ TMP

You may see more than one, find the latest generated, double-click open

Execute the following statement on the emulator (this debugging scheme only applies to the emulator, the real machine is not available, about the debugging scheme extension link: Can the messages be sent to an object in objective-c be monitored or printed out?) Send an error message to an object:

In conjunction with the Official NSObject documentation, which excludes what NSObject does, there are just a few things that _objc_msgForward message forwarding does:

  1. callresolveInstanceMethod:Method (orresolveClassMethod:). Allows the user at this time for theClassDynamically add the implementation. If there is an implementation, it is called and returnedYESSo start againobjc_msgSendProcess. This time the object responds to the selector, usually because it has already been calledclass_addMethod. If not, proceed with the following action.
  2. callforwardingTargetForSelector: method to try to find an object that can respond to the message. If it does, the message is forwarded directly to it, returning a non-nil object. Otherwise return nil and continue. Notice, don’t return hereselfOtherwise, an endless loop will be formed.
  3. callmethodSignatureForSelector: method, trying to get a method signature. If not, call it directlydoesNotRecognizeSelectorThrow an exception. If it can get, return nonil: Create aNSlnvocationAnd to theforwardInvocation:.
  4. callforwardInvocationWrap the method signature obtained in step 3 as: methodInvocationPassed in, how to handle that is in there, and return non-nil.
  5. calldoesNotRecognizeSelectorThe default implementation throws an exception. If step 3 fails to obtain a method signature, perform this step.

The above four methods are template methods that developers can override and call from the Runtime. The most common implementation of message forwarding is overriding methods 3 and 4, where it is ok to swallow a message or delegate it to another object. In other words, _objc_msgForward involves the following methods in the process of message forwarding:

  1. ResolveInstanceMethod: method (or resolveClassMethod:).
  2. Method of forwardingTargetForSelector:
  3. Method of methodSignatureForSelector:
  4. Method of forwardInvocation:
  5. Method of doesNotRecognizeSelector:
  • Let’s answer the second question, “What happens when you call it directly _objc_msgForward?”

Calling _objc_msgForward directly is a very dangerous thing to do and will Crash the program if used badly, but it can do a lot of cool things if used well.

Like parkour, do good, called “play cool”, do not call “die”.

As mentioned above, _objc_msgForward is an IMP type for message forwarding: when a message is sent to an object and it is not implemented, _objc_msgForward will attempt to forward the message.

How do I call _objc_msgForward? _objc_msgForward belongs to THE C language and has three parameters:

_objc_msgForward parameters type
1 Subordinate to the object Id type
2 The method name SEL type
3 Variable parameter Variable parameter type

First understand how to call IMP type method, IMP type is the following format:

For intuition, we can define an IMP type as follows:

typedef void (*voidIMP)(id, SEL,...).Copy the code

Once _objc_msgForward is called, the IMP lookup process is skipped and “message forwarding” is triggered,

If you call _objc_msgForward, even if the object actually implements the method, you’ll tell objc_msgSend: “I don’t find an implementation of this method in this object.”

Imagine what objc_msgSend would do? In general, this is the difference between calling the objc_msgSend procedure and calling _objc_msgForward directly:

1.1.6 Can I add instance variables to compiled classes? Can I add instance variables to classes created at run time? Why is that?

You cannot add instance variables to compiled classes. Ability to add instance variables to classes created at run time

Because the compiled class is registered with the Runtime, the linked list of objc_iVAR_list instance variables and the memory size of instance_size instance variables in the class structure have been determined, At the same time, the Runtime calls class_setIvarLayout or class_setWeakIvarLayout to handle strong weak references, so you can’t add instance variables to existing classes. Call class_addIvar, but after objc_allocateClassPair and before objc_registerClassPair, for the same reason.

1.1.7 How to Call methods in Objective-C (Runtime)

Runtime makes Objective-C a dynamic language, and makes C object-oriented. Classes, objects, and their corresponding methods can be created, checked, and modified during program running. These operations can be implemented using the corresponding methods in Runtime.

Objective-c is a dynamic language. Each method is dynamically converted to send messages when it is run, namely, objc_msgSend(receiver, selector). The whole process is described as follows:

  1. When objC sends a message to an object, the Runtime library finds out which class the object actually belongs to based on the object’s ISA pointer
  2. It then looks for methods to run in the list of methods in that class and in the list of methods in its parent class
  3. If, at run time, the program hangs and throws an exception unrecognized selector sent to XXX if no method is found in the top-level parent class (usually NSObject)
  4. But before that, objC’s runtime gives you three chances to save the program from crashing. The three opportunities are: (1) In the process of dynamic method parsing: object method dynamic parsing (+(BOOL)resolveInstanceMethod:(SEL)sel) and class method dynamic resolution (+(BOOL)resolveClassMethod:(SEL)sel(2) If dynamic parsing fails, message forwarding process will be entered, and message forwarding can be divided into fast forwarding and slow forwarding. (3) The implementation of fast forwarding isforwardingTargetForSelectorAnd let other objects that can respond to the lookup message do the work. (4) The realization of slow forwarding ismethodSignatureForSelectorforwardInvocationThe combination provides finer – grained control by first returning method signatures toRuntimeAnd then letanInvocationTo send the message to the supplied object, and finally to theRuntimeThe results are extracted and passed to the original message sender. (5) If the opportunity is saved in 3 times:resolveInstanceMethod.forwardingTargetForSelector.forwardInvocationWhen nothing is done, it is reportedunrecognized selector sent to XXXThe exception. The program will crash.

  • aboutresolveInstanceMethodMethod is also called object method dynamic resolution, and its flow is roughly as follows:
  1. Check if this is implemented+(BOOL)resolveInstanceMethod:(SEL)selClass method that returns directly if not implemented (throughcls->ISA()Get the metaclass, because class methods are object methods stored on the metaclass.
  2. If currently implemented+(BOOL)resolveInstanceMethod:(SEL)selClass method, passesobjc_msgSendCall the class method manually.
  3. After the call is complete, query againclsIn theimp.
  4. ifimpIf it does, it logs the success of the dynamic resolution object method.
  5. ifimpIf not, the output is implemented+(BOOL)resolveInstanceMethod:(SEL)selAnd returnedYES.

The corresponding source code is as follows:

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/.YES/*cache*/.NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class.SEL.SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/.YES/*cache*/.NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
  • If the object method dynamic resolution is not implemented, it will actually be called along the ISA pointer_class_resolveClassMethodDynamic parsing process by class method:
  1. Check if it is a metaclass. If not, exit.
  2. Check if this is implemented+(BOOL)resolveClassMethod:(SEL)selClass method that returns directly if not implemented (throughcls-Because of the currentclsMetaclass, because class methods are object methods stored on metaclass.)
  3. If currently implemented+(BOOL)resolveClassMethod:(SEL)selClass method, passesobjc_msgSendCall the class method by hand. Note that unlike the dynamic object resolution method, you need to find the class by metaclass and object, i.e_class_getNonMetaClass.
  4. After the call is complete, query againclsIn theimp.
  5. ifimpIf it does, it logs the success of the dynamic resolution object method.
  6. ifimpIf not, the output is implemented+(BOOL)resolveClassMethod:(SEL)selAnd returnedYES“, but did not find itimpThe log

The corresponding source code is as follows:

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/.YES/*cache*/.NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class.SEL.SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/.YES/*cache*/.NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code
  • aboutforwardingTargetForSelectorMethod also known as fast message forwarding:
  1. forwardingTargetForSelectorIs a fast message forwarding process that directly causes other objects to respond to unknown messages.
  2. forwardingTargetForSelectorCan’t returnselfOtherwise it will fall into an infinite loop because of returnselfGo back to the current instance object and go through the message lookup process, and obviously come backforwardingTargetForSelector.
  3. forwardingTargetForSelectorThis applies to the forwarding of messages to other objects that can respond to unknown messages. That is, the final returned content must be consistent with the parameters and return values of the message to be searched. If you want to be inconsistent, you need to go through other processes.

Fast message forwarding is implemented through assembly, and lookUpImpOrForward will return _objc_msgForward_impcache if dynamic parsing fails, according to the source code. Objc-msg-arm64.s objC-msg-arm64.s objC-msg-arm64.s objC-msG-arm64.s

STATIC_ENTRY __objc_msgForward_impcache

	// No stret specialization.
	b	__objc_msgForward

	END_ENTRY __objc_msgForward_impcache

	
	ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
Copy the code
  • aboutforwardInvocationCorresponds to slow message forwardingmethodSignatureForSelectorMethod signature:
  1. forwardInvocationMethod has two tasks: (1): lookup can respondinInvocationObject of the encoded message in. This object need not be the same for all messages. (2) : to useanInvocationThe message is sent to the object.anInvocationThe results are saved, and the run-time system extracts the results and passes them to the original sender.
  2. forwardInvocationMethod implementations can do more than forward messages.forwardInvocationIt can also be used to combine code that responds to a variety of different messages, avoiding the hassle of having to write separate methods for each selector.forwardInvocationMethods may also refer to several other objects in response to a given message, rather than forwarding it to just one object.
  3. NSObjectforwardInvocationImplementation: only callsdosNotRecognizeSelector: method, which does not forward any messages. Therefore, if you choose not to implement the forwardInvocation, sending an unrecognized message to the object will raise an exception.

To illustrate: Suppose we call the [dog Walk] method, it goes through the following process:

  1. The compiler will take[dog walk]intoObjc_msgSend (dog, SEL)SEL is @selector(walk).
  2. The Runtime looks for the SEL of the method in the method cache list of the dog class corresponding to the dog object
  3. If not, the SEL of the search method is published in the method section of the Dog class. (Classes are pointed to by object ISA Pointers, and methods are represented as methodLists.)
  4. If not, look for SEL of the method in the distribution table of its superClass.
  5. If it doesn’t, it goes down the inheritance hierarchy and ends up with the NSObject class.
  6. If found in one of the steps in 234, the entry to the method implementation is located and the concrete implementation is executed
  7. If finally still not found, will face two situations: ‘ ‘(1) if it is used[dog walk]The method called ““(2) is used[dog performSelector: @ the selector (walk)]Method ‘

If it is an undefined method, it undergoes a dynamic method resolution, message forwarding process

  • How does an object find a method to call
  1. According to the object’s ISA to find the corresponding class lookup method, ISA: determine which class to find the corresponding method refers to the class of the method call
  2. Number SEL according to the Method that is passed in. There is a hash list inside. Find the corresponding Method in the list.
  3. Find the function implementation based on the method name (function entry). The function implementation is in the method area

1.1.8 What is Method Swizzling

Simply put, it’s a method swap

  1. When you call a method in Objective-C, you’re actually sending a message to an object, and the only way to find the message is by the name of the selector. By taking advantage of the dynamic characteristics of Objective-C, the corresponding method implementation of selector can be swapped at run time to achieve the purpose of hook method
  2. Each class has a list of methods, storing the Method name and Method implementation mapping, selector is the essence of the Method name, IMP is a bit like a function pointer, pointing to a specific Method implementation, by selector you can find the corresponding IMP
  3. There are several ways to implement the exchange method: (1) method_exchangeImplementations implementations of two methods (2) implementation of class_replaceMethod replacement method (3) method_setImplementation To set the IMP of a method directly
  • Practical application of method exchange: There is a requirement: for example, I have a project that has been developed for 2 years. Before that, UIImage was used to load images. When the leader wanted to call imageNamed, he would prompt me whether the loading was successful.

There are three ways to solve this requirement problem:

  1. Custom UIImage class, disadvantage: each use to import their own class
  2. Xmg_imageNamed = xmg_imageNamed = xmg_imageNamed = xmg_imageNamed = xmg_imageNamed = xmg_imageNamed
  3. Interactive method implementation, steps: 1. Provide classification 2. Write a method with such a function 3. The system method interacts with this functional method, implemented in the +load method

If you use method 2, every time you call imageNamed, you have to change it to xmg_imageNamed: to have this function, which is a hassle. Solution: Use the Runtime swap method is better.

Note: in the classification must not rewrite the system method, it is directly the system method to kill, if really want to rewrite the system method in front of the prefix, method inside to call the system method

Idea: When you need to customize a class like this, if the system is not functional enough, to extend the class

Method exchange implementation code is as follows:

/ #import "UIImage+Image.h"
/#import <objc/message.h>
@implementation UIImage (Image)
// when the class is loaded, it must only be called once

 +(void)load
{
    // The interactive method implements xmg_imageNamed,imageNamed
    /** get the Class Method name @param Class CLS,#> get the description of the Class Method #> @param SEL name#> Method number description#> @return class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>) */
    /** get the object Method name @param Class CLS,#> get the object Method description#> @param SEL name#> Method number description#> @return class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>) */
    
   Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
    Method xmg_imageNameMethod = class_getClassMethod(self, @selector(xmg_imageNamed:));
    // Use runtime to swap the imageNameMethod and xmg_imageNameMethod methods
    method_exchangeImplementations(imageNameMethod, xmg_imageNameMethod);
}
Xmg_imageNamed: imageNamed: xmg_imageNamed: xmg_imageNamed: xmg_imageNamed: xmg_imageNamed: xmg_imageNamed: xmg_imageNamed: xmg_imageNamed: xmg_imageNamed
+ (UIImage *)xmg_imageNamed:(NSString *)name
{
    // xmg_imageNamed has been changed to imageNamed, so the following is actually the call to imageNamed:
   UIImage *image = [UIImage xmg_imageNamed:name];
    
    if (image == nil) {
        NSLog(@"Load failed");
    }
    return image;
}
@end
Copy the code

1.2 Structural Model

1.2.1 Class structure, message forwarding related

Above are some questions about the Runtime process, but a deeper understanding of objC4’s relevant source code will follow. The message forwarding mechanism is probably the most common question.

  • Questions you might be asked about class structures related to message forwarding:
  1. Introduce the Runtime memory model (ISA, object, class, metaclass, structure storage information, etc.)
  2. Why design Metaclass
  3. Class_copyIvarList and class_copyPropertyList
  4. Difference between class_rw_t and class_ro_t
  5. 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
  6. Category vs. Extension, can I add extension to NSObject? What happens
  7. Message forwarding mechanism, message forwarding mechanism and the advantages and disadvantages of other languages message mechanism comparison
  8. At method invocation time, what is done before method query -> dynamic resolution -> message forwarding
  9. IMP, SEL, Method differences and use scenarios
  10. What is the difference between the load and initialize methods? What is the difference between them in inheritance
  11. Talk about the pros and cons of the message forwarding mechanism

1.2.1.1 Isa pointer related issues

Where does the ISA pointer point to? What are the two types of ISA Pointers?
  • Isa is equivalent to is kind of

Instance object ISA points to class object Class object ISA points to metaclass object ISA of metaclass object points to the base class of metaclass

  • There are two types of ISA

A pure pointer to the memory address NON_POINTER_ISA, which holds some other information besides the memory address

  • Isa source analysis at Runtime source view ISA_T is common. The simplified structure is as follows:
union isa_t 
{
    Class cls;
    uintptr_t bits;
    # if __arm64__ / / arm64 architecture
#   define ISA_MASK        0x0000000ffffffff8ULL // Use the (&) operation to retrieve the 33-bit memory address
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1; //0: indicates a common pointer. 1: indicates an optimized pointer that can store more information.
        uintptr_t has_assoc         : 1; // Whether the associated object is set. If not, the release will be faster
        uintptr_t has_cxx_dtor      : 1; // is there a C++ destructor
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 Memory address value
        uintptr_t magic             : 6; // Used to tell if an object is not initialized during debugging
        uintptr_t weakly_referenced : 1; // If there is a weak reference to point to
        uintptr_t deallocating      : 1; // Whether is being released
        uintptr_t has_sidetable_rc  : 1; // Whether the reference counter is too large to be stored in ISA. If it is 1, the reference count is stored in an attribute of a class called SideTable
        uintptr_t extra_rc          : 19; // The value stored inside refers to the counter decrement by 1

#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)}; # elif __x86_64__// The simulator is arm86
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)}; #else
#   error unknown architecture for packed isa
# endif

}
Copy the code
  • Continue with the definition of the objc_class structure:
typedef struct objc_class *Class;

struct objc_class {

    Class isa  OBJC_ISA_AVAILABILITY;   //isa pointer to metaclass (metaclass of that class)

#if! __OBJC2__Class super_class   // Super_class (superclass) to objc_class

    const char *name    //objc_class (class) class name

    long version        // The version of the objc_class (class), initialized to 0, can be modified and read by the Runtime functions class_setVersion and class_getVersion

    long info           // Some identification information, such as CLS_CLASS indicating that objc_class (the class) is a common class. ClS_CLASS means objc_class is metaclass

    long instance_size  // The size of the instance variable of objc_class

    struct objc_ivar_list *ivars// The address used to store each member variablestruct objc_method_list **methodLists// List of methods, andinfoIdentifier associatedstruct objc_cache *cache// A pointer to the most recently used method to improve efficiencystruct objc_protocol_list *protocols/ / storeobjc_classSome protocol # for (class)endif

} OBJC2_UNAVAILABLE;

typedef struct objc_object *id;

struct objc_object {

    Class isa  OBJC_ISA_AVAILABILITY;

};


Copy the code

Struct objc_classs metadata contains a pointer to the parent class, class name, version, instance size, instance variable list, method list, cache, protocol list, etc. This information is enough to create an instance. The first member variable of this structure is also an ISA pointer, which shows that Class itself is also an object, which we call a Class object. Class objects are generated at compile time to create instance objects, which are singletons.

The isa pointer in objec_Object points to a class structure called objec_class (the object’s class), which holds ordinary member variables and object methods (methods starting with “-“).

The isa pointer in objec_class points to aclass structure called metaclass, which holds static type member variables and static type methods (methods starting with “+”).

Metaclass: In OC, each class is actually an object. There is also an ISA pointer. Because aclass is an object, it must also be an instance of another class, a metaclass. That is, the object of a metaclass is the class that holds the list of its methods

In OC, each class is actually an object. Each class also has an ISA pointer. Each class can also receive messages. Because aclass is also an object, it is also an instance of another class, called a metaclass. A metaclass is also an object, and all isa Pointers to metaclasses point to a root metaclass. The isa pointer to the root metaclass points back to itself, forming a closed loop.

The following diagram shows the relationship between class inheritance and ISA reference:

Isa pointer to:

  1. Of an objC objectisaThe isa pointer to the object of his class points to his metaclass, the ISA pointer to the metaclass points to the root metaclass, all the ISas of the metaclass point to the same root metaclass, and the ISA pointer to the root metaclass points to the root metaclass itself. A metaclasssuper classThe parent class toNSObjectClass objects. The rootmetaclass(metaclass)superClassThe pointer points to the root class because the rootmetaclassMetaclasses are created by inheriting the root class.
  2. Instance objectisaPointer to his class object, class object ofisaPointer to his metaclass. The system determines which class an object belongs to by that objectisaThe point of the pointer. Member variables in an object are stored in the object itself, and instance methods of the object are stored in itsisaThe object to which the pointer points.
  3. Object when the minus method is called, the system will call the objectisaFind the method in the class object to which the pointer pointskvoYou can see in the implementation principle of,kvoThe implementation principle is that the system dynamically generates a class object, which is a subclass of the listening object class, in the generated subclass overrides the listening attribute set method, then will listen to the objectisaThe pointer points to the class dynamically generated by the system when the listener calls the set method because the listener’sisaThe pointer points to the class that was just dynamically generated, so the set method executed is also the set method overridden in the subclass, which is how KVO works. And we can do the same thingruntimeMethod in sets an objectisaA pointer to a class object that calls methods that don’t belong to it.
  • Class object:
  1. Class objects are created by the compiler. Any direct or indirect inheritanceNSObjectClass, which has one of its instance objectsisaPointer to its class object. This class object stores everything about the definition of the class to which the instance object belongs: variables, methods, protocols to obey, and so on. Thus, the class object has access to all information about the class that can be used to create a new instance, but the class object does not have access to the contents of any instance object. Class objects have no instance variables of their own.

For example, we create a p object: Person * p = [Person new];

Before creating an object P, there is a class (Person) in the heap, which is automatically created for us when we compile it. Created when the class is first loaded into memory.

After an object is created, a P object is created in heap memory that contains the member variable (the first property) of an ISA pointer to the class object that exists in the heap. A p pointer to the class object is created in stack memory. The p pointer points to isa and isa points to Person.

  • Object method calls:

OC calls the method, and at runtime the compiler converts the code to objc_msgSend(obj, @selector (selector)). In the objc_msgSend function, the isa pointer to obj finds the corresponding class of obj. In the class (class), first go to the cache to find the corresponding method (method) through SEL (method number). If it is not found in cache, then go to methodLists. If methodists are not found, then go to superClass. Method is added to the cache for next lookup, and the function pointer in method is used to jump to the corresponding function for execution. If it still cannot be found, the search continues through super_class in the parent structure up to the root class

  • Class method calls:
  1. When we call a class method, it first passes its ownisaPointer pointedobjc_classIn theisaPointer to and from the metaclassmethodListsIf not, the super_class pointer to the metaclass is used to find the metaclass structure of the parent class, and then the super_class pointer is used to find the metaclass structure of the parent classmethodListsIf the method still cannot be found, continue to passsuper_classLook in the parent structure up to the root metaclass;
  2. C determines which function to call at compile time. OC is a dynamic language, and it will defer the code from compile link to runtime as much as possible. This is called OC runtime polymorphism. When sending a message to an object, it is not executed immediately, but when it is run, it is looking for its corresponding implementation. However, the function of OC belongs to the dynamic call process, and it cannot decide which function to call at compile time. Only when it is run, it will find the corresponding function to call according to the name of the function.

1.3 Memory Management

Reference: www.jianshu.com/p/8345a79fd… Juejin. Cn/post / 684490… Github.com/ChenYilong/… www.jianshu.com/p/0bf8787db…