preface

In my previous blog on the nature of objects and ISA, we found that the underlying implementation of a member variable just adds a variable and doesn’t implement get and set methods, whereas the underlying implementation of an attribute variable becomes an _ variable and implements get and set methods, but some of the underlying set methods are setting property values through objc_setProperty, Some of them are by starting address + offset, and we were left in suspense. Similarly, in the structural analysis of the blog class, we find that the instance methods of the class are in the class, but where are the class methods of the class? I’m going to talk a little bit more about those two questions today.

Class 1.0 memory optimization

Before exploring the structure of classes, let’s summarize WWDC2020’s comments about runtime memory optimization for classes. Let’s start with two concepts

  • Clean Memory: “clean memory”, after being loadedMemory that will not changeThe first time a class is loaded from disk into Memory is Clean Memory, class_ro_t is Clean Memory because it’s read-only,Ro means read only.
  • Dirty Memory“Dirty Memory”, which is Memory that changes while the process is running. Classes that are used are Dirty Memory because new data is written, methods are added, and so on.class_rw_tisDirty MemoryRw isread write.
  • The difference between:clean MemoryCan be doneremoveSave more memory space because if youNeed to beClean memory system can be retrieved from the diskReload the;Dirty MemoryAs long as the process is running, it must exist alone, so it is specialMemory consumption, then needAs far as possible,Convert Dirty Memory to clean Memory.

Ios14 before

Class objects contain the most commonly used information: caches pointing to metaclasses, superclasses, and methods. It also has a pointer to more additional information class_ro_t, where ro means read only. This section of information is read-only and containsClass names, methods, protocols, instance variables, and propertiesAnd other information. Both the Swift and Objective-C classes use this structure. But once a class is used, something changes. This is what happened to memory before ios14.

After ios14

  • First SubclassNext Sibling ClassPointers allow the runtime to traverse all classes currently in use
  • Methods, Properties, ProtocolsThis section can also be found inModified at runtime. In practice, it turns out that only about 10% of classes actually change their methods, soThis part of memoryIt can be optimized to give you some space
  • Demangled Name will only be used by the Swift class and will not even be used unless you need to get their Objective-C names.

So the last two parts that we don’t use very often, we can break them out again. So we split class_rw_t into two parts, and we only allocate memory for that part of the class_rw_ext_t structure if we really need it, and about 90% of the classes don’t need that extra data. This is an optimization made by the Runtime to the class structure to minimize Dirty Memory.

2.0 objc_setProperty

We follow up with the nature of the object with the example main.cpp in ISA.

The main. M is as follows:

@interface LGWPerson:NSObject{
    NSString* sex;
}
@property(nonatomic,copy)NSString* name;
@property(nonatomic,strong)NSString* nickname;
@property(nonatomic,assign)int age;
@end
@implementation LGWPerson
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
    return 0;
}
Copy the code

The main. CPP is as follows:

typedef struct objc_object LGWPerson;
typedef struct {} _objc_exc_LGWPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGWPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LGWPerson$_nickname;
extern "C" unsigned long OBJC_IVAR_$_LGWPerson$_age;
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
typedef struct objc_class *Class;

struct NSObject_IMPL {
  Class isa;
};

struct LGWPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *sex;
	int _age;
	NSString *_name;
	NSString *_nickname;
};


// @implementation LGWPerson

static NSString * _I_LGWPerson_name(LGWPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGWPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGWPerson_setName_(LGWPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGWPerson, _name), (id)name, 0.1); }

static NSString * _I_LGWPerson_nickname(LGWPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGWPerson$_nickname)); }
static void _I_LGWPerson_setNickname_(LGWPerson * self, SEL _cmd, NSString *nickname) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGWPerson$_nickname)) = nickname; }

static int _I_LGWPerson_age(LGWPerson * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_LGWPerson$_age)); }
static void _I_LGWPerson_setAge_(LGWPerson * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_LGWPerson$_age)) = age; }
// @end

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
    }
    return 0;
}
Copy the code

The set method for name is set by objc_setProperty. The set method for nickname and age is set by the first address and the offset. The code is compiled to determine how to set the value of the property.

1.0.1 LLVM explores set_Property

Now that the attribute variable set method is defined at compile time, explore set_Property in the LLVM source code

Analysis:CGM.CreateRuntimeFunction(FTy, "objc_setProperty"), to create aobjc_setPropertyMethods. To create the objc_setProperty method LLVM, call the getSetPropertyFn() function. Source lookup where the getSetPropertyFn() function is called, looks up and finally locates toGetPropertySetFunction()Function, which generates the objc_setProperty method.

Strategy.getkind () equalsGetSetPropertyThe GetPropertySetFunction() method is called. So when strategy.getkind() is equal to GetSetProperty?Analysis: When the property iscopyModification iskind=GetSetPropertyThat is, the property iscopyIs called when the value of the set property is decoratedset_PropertyOtherwise, the value of the property is set to the first address + offset.

1.0.2 ObjC source explore objc_setProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) __attribute__((always_inline));

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
    / / the new value to retain
    if(! atomic) { oldValue = *slot; *slot = newValue; }else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
    / / release the old value
    objc_release(oldValue);
}
Copy the code

The process of setting the property value of objc_setProperty is to release the old value, retain the new value, copyWithZone opens up new memory space.

1.0.3 Copy Property modifier expansion

NSString immutable object instance variables are directly assigned

@interface ViewController()
@property(nonatomic,strong)NSString* strongstr;
@property(nonatomic,copy)NSString* copystr;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString* originstr=@"hello";
    _strongstr=originstr;
    _copystr=originstr;
    NSLog(@"Origin: %p, %p, %@",originstr,&originstr,originstr);
    NSLog(@"Strongstr: %p, %p, %@",_strongstr,&_strongstr,_strongstr);
    NSLog(@"Copystr: %p, %p, %@",_copystr,&_copystr,_copystr);
}
Copy the code

Output:

Origin:0x1049ee010.0x7ffeeb214c58, hello strongstr:0x1049ee010.0x600003a708e8, hello copystr:0x1049ee010.0x600003a708f0, hello!Copy the code

Analysis: When Origin is an immutable object NSString, origin, StrongSTR, and copystr are three different Pointers pointing to the same memory space 0x1049EE010

NSMutableString Specifies a variable object instance variable

@interface ViewController()
@property(nonatomic,strong)NSString* strongstr;
@property(nonatomic,copy)NSString* copystr;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableString * originstr=[NSMutableString stringWithFormat:@"hello"];
    _strongstr=originstr;
    _copystr=originstr;
    [originstr setString:@"hello word"];
    NSLog(@"Origin: %p, %p, %@",originstr,&originstr,originstr);
    NSLog(@"Strongstr: %p, %p, %@",_strongstr,&_strongstr,_strongstr);
    NSLog(@"Copystr: %p, %p, %@",_copystr,&_copystr,_copystr);
}
Copy the code

Output:

Origin:0x600000d0cc60.0x7ffeee772c58, hello strongstr:0x600000d0cc60.0x600003174fa8, hello copystr:0x600000d0cc60.0x600003174fb0, hello!Copy the code

Analysis: When origin is NSMutableString, origin, StrongSTR, and Copystr are three different Pointers pointing to the same memory space 0x600000D0CC60. Copy and strong modifiers have no effect, right? Let’s explore

NSString immutable object point syntax assignment

@interface ViewController()
@property(nonatomic,strong)NSString* strongstr;
@property(nonatomic,copy)NSString* copystr;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString * originstr=@"hello";
    self.strongstr=originstr;
    self.copystr=originstr;
    NSLog(@"Origin: %p, %p, %@",originstr,&originstr,originstr);
    NSLog(@"Strongstr: %p, %p, %@",_strongstr,&_strongstr,_strongstr);
    NSLog(@"Copystr: %p, %p, %@",_copystr,&_copystr,_copystr);
}
Copy the code

Output:

Origin:0x10f19c018.0x7ffee0a66c48, hello strongstr:0x10f19c018.0x6000011e07c8, hello copystr:0x10f19c018.0x6000011e07d0, hello!Copy the code

Analysis: When origin is an immutable object NSString, origin, StrongSTR, and copystr are three different Pointers pointing to the same memory space 0x10F19C018

NSMutableString Mutable object point syntax assignment

#import "ViewController.h"
@interface ViewController()
@property(nonatomic,strong)NSString* strongstr;
@property(nonatomic,copy)NSString* copystr;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableString * originstr=[NSMutableString stringWithFormat:@"hello"];
    self.strongstr=originstr;
    self.copystr=originstr;
    [originstr setString:@"hello word"];
    NSLog(@"Origin: %p, %p, %@",originstr,&originstr,originstr);
    NSLog(@"Strongstr: %p, %p, %@",_strongstr,&_strongstr,_strongstr);
    NSLog(@"Copystr: %p, %p, %@",_copystr,&_copystr,_copystr);
}
Copy the code

Output:

Origin:0x6000015d0f60.0x7ffeea73fc48, hello word strongstr:0x6000015d0f60.0x6000029a4bb8, hello word copystr:0xec3c9c9f9ae973a1.0x6000029a4bc0, hello!Copy the code

Strongstr and Origin point to the same memory space 0x6000015D0f60 and copystr points to 0xEC3C9c9F9AE973A1. NSArray and NSMutableArray are also tested, and the result is the same. The copy modifier is used to make a memory copy of the mutable container object. Strong simply increments the reference count by one.

Conclusion:

  • When theCopy to modifyWhen,Dot syntax assignmentWill callobjc_setPropertyAnd underlineThe way a variable is assignedIs only for member variablesDirect assignment.
  • When theCopy to modify, if the value ofObjects are immutable objects, then just copy the pointer, i.eShallow copy, memory copy will not be performed. If you assign a value ofObjects are mutable objects, then the memory copy is madeDeep copy.
  • When theThe strong modificationWhen, no matter whether the assigned object is mutable or immutable, only a pointer copy is madeShallow copy, just put the source stringThe reference count increases by 1.
  • It’s generally declared that an NSString or an NSArray object isI don't want it to change, so usecopyEmbellish, so as to beThe variable objectNSMutableString or NSMutableArrayThe assignmentWhen toDeep copyThe assigned object is a new memory, mutable object changesNew memory space will not be affected.
  • Assign modifier objectCan causeWild pointerBecause assign sets a new valueIt's not going to release the old value.assignUsed generally to modify.Basic data types, basic data type variables inThe stack, the stack memory is allocated and released by the system management, no wild pointer.
  • By the wayweakWeak, the pointer is set to nil when the object is released, soavoidtheWild pointerThe emergence of

1.0.4 Copy and mutableCopy methods

Copy and mutableCopy are not open source in Foundation. Copy and mutableCopy are not open source in Foundation. We’ll have a chance to decompile the framework later and dig into it.

Immutable objects NSString and NSArray

    NSLog(@"---- non-container immutable objects -----");
    NSString * originstr=@"hello";
    NSString * copystr=[originstr copy];
    NSString* mutablestr=[originstr mutableCopy];
    NSLog(@"Originstr: % p - % @",originstr,originstr);
    NSLog(@"Copystr: % p - % @",copystr,copystr);
    NSLog(@"Mutablestr: % p - % @",mutablestr,mutablestr);
    
    NSLog(@"---- Container immutable object -----");
    NSArray* originArray=@[@"hello"The @"word"];
    NSArray* copyArray=[originArray copy];
    NSArray* mytableArray=[originArray mutableCopy];
    NSLog(@"OriginArray: % p - % @",originArray,originArray);
    NSLog(@"CopyArray: % p - % @",copyArray,copyArray);
    NSLog(@"MytableArray: % p - % @",mytableArray,mytableArray);
Copy the code

Output:

---- non-container immutable objects ----- Originstr:0x10dcc2040- hello copystr:0x10dcc2040- hello mutablestr:0x600000f900c0--hello ---- Immutable container objects ----- originArray:0x6000001ca480--(hello, word) copyArray:0x6000001ca480MytableArray --(hello, word)0x600000f90060--(
    hello,
    word
)
Copy the code

Copystr and copyArray pointer can be seen, for immutable objects, copy is only a copy of the pointer, and did not copy memory that is a shallow copy; As you can see from mutableStr and mytableArray, for immutable objects, mutableCopy makes a deep copy of memory.

Mutable objects NSMutableString and NSMutableArray

    NSLog(@"---- non-container mutable objects -----");
    NSMutableString * originstr=[NSMutableString stringWithFormat:@"hello"];
    NSString * copystr=[originstr copy];
    NSString* mutablestr=[originstr mutableCopy];
    [originstr setString:@"word"];
    NSLog(@"Originstr: % p - % @",originstr,originstr);
    NSLog(@"Copystr: % p - % @",copystr,copystr);
    NSLog(@"Mutablestr: % p - % @",mutablestr,mutablestr);
    
    NSLog(@"---- container mutable object -----");
    NSMutableArray* originArray=[NSMutableArray arrayWithObjects:@"hello", nil];
    NSArray* copyArray=[originArray copy];
    NSArray* mytableArray=[originArray mutableCopy];
    [originArray addObject:@"word"];
    NSLog(@"OriginArray: % p - % @",originArray,originArray);
    NSLog(@"CopyArray: % p - % @",copyArray,copyArray);
    NSLog(@"MytableArray: % p - % @",mytableArray,mytableArray);
    
     NSLog(@"OriginArray first element address: %p",originArray[0]);
    NSLog(@"CopyArray first element address: %p",copyArray[0]);
    NSLog(@"MytableArray first element address: %p",mytableArray[0]);
Copy the code

Output:

---- non-container mutable objects ----- Originstr:0x600000196b50- word copystr:0xf28896a3aa0c427f- hello mutablestr:0x6000001f0000--hello ---- container mutable objects ----- originArray:0x6000001d70c0--(hello, word) copyArray:0x600000d98380--(hello) mytableArray:0x600000180000OriginArray address of the first element:0x107438038CopyArray first element address:0x107438038MytableArray first element address:0x107438038
Copy the code

Analysis:

  • forThe variable objectOriginstr and originArray, bothcopyormutableCopyThey’re all copies of memoryDeep copy, mutable object changes do not affect the value of the deep copy.
  • The containerobjectDeep copyBut the insideThe elementisShallow copy

conclusion

  • Immutable object:copyisShallow copy.mutableCopyisDeep copy
  • The variable object: copy and mutableCopy are both trueDeep copy
  • The containerThe object’sDeep copyBut the insideElement is a shallow copy

2.0 class method attribution analysis

In the last article on class structure analysis, we know that an object is an instance of a class, and a class is an instance of a metaclass, so the instance method of an object is in a class, so is the class method in a metaclass? Let’s explore this with runtime

@interface GyPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation GyPerson
- (void)sayHello{
    
}
+ (void)sayHappy{
    
}
@end

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        // Get the method name
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        const char * className = class_getName(GyPerson.class);
        NSLog(@"---- class method -----");
        Class gyperson=objc_getClass(className);
        lgObjc_copyMethodList(gyperson);
         NSLog(@"---- metaclass method -----");
        Class meteperson=objc_getMetaClass(className);
        lgObjc_copyMethodList(meteperson);
    }
   return 0;
}

Copy the code

The output is as follows:

---- Method ----- Method,name: sayHello ---- metaclass Method ----- Methodname: sayHappy
Copy the code

Analysis: Using the Runtime API class_copyMethodList, we get the list of class and metaclass methods and print them out. We find that the sayHello() instance method is in the GyPerson class and the sayHappy() class method is in the GyPerson metaclass. We can also use the Runtime API class_getInstanceMethod to determine whether the sayHello() or sayHappy() methods exist.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        const char * className = class_getName(GyPerson.class);
        / / class
        Class gyperson=objc_getClass(className);
        / / the metaclass
        Class meteperson=objc_getMetaClass(className);
        // Check whether the class has sayHello
        Method method1=class_getInstanceMethod(gyperson, @selector(sayHello));
        // Check if there is sayHappy in the class
        Method method2=class_getInstanceMethod(gyperson, @selector(sayHappy));
        // Check whether there is sayHello in the metaclass
        Method method3=class_getInstanceMethod(meteperson, @selector(sayHello));
        // Determine if there is sayHappy in the metaclass
        Method method4=class_getInstanceMethod(meteperson, @selector(sayHappy));
        NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    }
   return 0;
}
Copy the code

The output is as follows:

0x100008110-0x0-0x0-0x1000080a8
Copy the code

0x100008110 is the address of method sayHello() pointer, 0x1000080a8 is the address of method sayHappy() pointer, 0x0 means none.

Let’s look at the structure of Method from source code:


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

Copy the code

Method_name (SEL), method_types(char*), method_IMP (IMP); The relationship between SEL and IMP is just like the directory and page number of a book, SEL is the directory name, IMP is the page number, that is, SEL corresponds to the method name, IMP corresponds to the pointer address of the method implementation.

2.1 Inheritance of class methods

Class methods in metaclasses, metaclasses are instances of classes, so all methods are object methods, everything is object, remember isa pointing diagram, let’s explore with ISA diagram.

This isa alignment and inheritance diagram also shows that,Method of instanceThe lookup process doesn’t go into the metaclass, it just follows the class inheritance all the way to NSObject’s parent class nil, andClass methodThe search process will enterThe metaclassNSObject is the root of any object, and you find NSObject all the way down the metaclass inheritance to nil.

Create say666(), say666(), say666(), say666(), say666()

@interface Gteacher: NSObject
@end
@implementation Gteacher
@end

@interface Gperson: NSObject
-(void)say666:(NSString*)str;
@end
@implementation Gperson
-(void)say666:(NSString*)str{
    NSLog(@"Parent instance method :%@",str);
}
@end

//NSObject class method+ (void)say666:(NSString*)str{
    NSLog(@"NSObject metaclass: % @",str); } - (void)say666:(NSString*)str{
    NSLog(@"NSObject root class: % @",str);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
     [LGTeacher say666:@"ok666"];
     }
     return 0;
  }
Copy the code

Output:

NSObject metaclass: ok666Copy the code

Analysis: Say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666: say666 So you go all the way to the root metaclass NSObject based on metaclass inheritance, and you find the say666 method in the root metaclass. We also found that if we removed the class NSObject method +(void)say666, we would still find the object method -(void)say666, because the parent of the root metaclass NSObject is NSObject.

Conclusion:

  • Instance methodsinclass,Class methodinThe metaclassIn the
  • objectisclassSoObject method in class,classisThe metaclassAn instance of theClass methods are in metaclasses.
  • The compilerAutomatically generate metaclassesWith the aim ofHolding class methods
  • Instance methodsLookup processDon'ttoThe metaclass, will onlyInheritance relationships along classesAll the way to NSObject’s parent class nil, andClass methodSearch flow ofIt goes into the metaclass.Inheritance relationships along metaclassAll the way to NSObject’s parent class nil, which isNSObjectisThe root of any object, will find NSObject.