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 change
The 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_t
isDirty Memory
Rw isread write
.- The difference between:
clean Memory
Can be doneremove
Save more memory space because if youNeed to be
Clean memory system can be retrieved from the diskReload the
;Dirty Memory
As 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 properties
And 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 Subclass
和Next Sibling Class
Pointers allow the runtime to traverse all classes currently in useMethods, Properties, Protocols
This 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 memory
It 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_setProperty
Methods. 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 () equalsGetSetProperty
The GetPropertySetFunction() method is called. So when strategy.getkind() is equal to GetSetProperty?Analysis: When the property iscopy
Modification iskind=GetSetProperty
That is, the property iscopy
Is called when the value of the set property is decoratedset_Property
Otherwise, 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 the
Copy to modify
When,Dot syntax assignment
Will callobjc_setProperty
And underlineThe way a variable is assigned
Is only for member variablesDirect assignment
. - When the
Copy 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 the
The strong modification
When, 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 is
I don't want it to change
, so usecopy
Embellish, so as to beThe variable object
NSMutableString or NSMutableArrayThe assignment
When toDeep copy
The assigned object is a new memory, mutable object changesNew memory space will not be affected
. Assign modifier object
Can causeWild pointer
Because assign sets a new valueIt's not going to release the old value
.assign
Used 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 way
weak
Weak, the pointer is set to nil when the object is released, soavoid
theWild pointer
The 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:
- for
The variable object
Originstr and originArray, bothcopy
ormutableCopy
They’re all copies of memoryDeep copy
, mutable object changes do not affect the value of the deep copy. The container
objectDeep copy
But the insideThe element
isShallow copy
conclusion
Immutable object
:copy
isShallow copy
.mutableCopy
isDeep copy
The variable object
: copy and mutableCopy are both trueDeep copy
The container
The object’sDeep copy
But 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 instance
The lookup process doesn’t go into the metaclass, it just follows the class inheritance all the way to NSObject’s parent class nil, andClass method
The search process will enterThe metaclass
NSObject 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 methods
inclass
,Class method
inThe metaclass
In theobject
isclass
SoObject method in class
,class
isThe metaclass
An instance of theClass methods are in metaclasses
.- The compiler
Automatically generate metaclasses
With the aim ofHolding class methods
Instance methods
Lookup processDon't
toThe metaclass
, will onlyInheritance relationships along classes
All the way to NSObject’s parent class nil, andClass method
Search flow ofIt goes into the metaclass
.Inheritance relationships along metaclass
All the way to NSObject’s parent class nil, which isNSObject
isThe root of any object
, will find NSObject.