The essence of function execution is message mechanism, which includes three stages: message sending, message dynamic parsing and message forwarding. So today we’re going to look at some synthesis problems and some applications of the Runtime.
The keyword super
The keyword super is converted to the __rw_objc_super structure when [super init] is called
struct __rw_objc_super { struct objc_object *object; Struct objc_object *superClass; // parent class __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}};Copy the code
[super init] Convert to CPP using xcrun-sdk iphoneos clang-arch arm64-rewrite-objc-fobjc-arc-fobjc-Runtime =ios-8.0.0 student-m Open CPP is found roughly at the bottom
(Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))
Copy the code
So this simplifies to
(void *)objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))
Copy the code
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, … */) struct objc_super *super, SEL op… We found the implementation of this function in objc-msG-arm64.s source code
ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, Struct __rw_objc_super {//struct objc_object *object; //struct objc_object *superClass; // The parent class} takes up 16 bytes of space, objc_msgSendSuper is __rw_objc_super, x0 is offset by 16 bytes, that is, the space of two Pointers, P0, p16, [x0] // p0 = self, p16 = superclass CacheLookup NORMAL // calls imp or objc_msgSend_uncached END_ENTRY _objc_msgSendSuperCopy the code
Assign self and superclass to p0, and p16 calls CacheLookup NORMAL
P1 = SEL, p16 = isa LDP p10, p11, [x16,#CACHE] // p10 = buckets, p11 = occupied|mask
#if ! __LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if(bucket->sel ! = _cmd) b.ne 2f // scan more CacheHit$0 // call or returnImp hit call or RETURN IMP 2: // not hit: P12 = not-hit bucket failed to hit CheckMiss$0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if(bucket->sel ! = _cmd) b.ne 2f // scan more CacheHit$0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
Copy the code
P16 = isa (superclass); p16 = isa (superclass); For the most part, it’s clear that super essentially calls objc_msgSendSuper, which looks up methods starting from the parent class.
[super init] is self calling the parent init method directly, but objc_msgSend is self. [self init] creates an infinite loop. [super test] is the test that executes the parent class. Use Debug Workflow->Always Show Disassemdly to find that super actually calls objc_msgSendSuper2 of the assembly. Objc_msgSendSuper2 objc-msG-arm64.s 422 is the same as objc_msgSendSuper
//_objc_msgSendSuper start ENTRY _objc_msgSendSuper UNWIND _objc_msgSendSuper, NoFrame x0 offset 16 bytes, which is the space for two Pointers, P0, p16, [x0] // p0 = self, p16 = superclass CacheLookup NORMAL // calls imp or objc_msgSend_uncached END_ENTRY _objc_msgSendSuper //_objc_msgSendSuper End //objc_msgLookupSuper2 Start ENTRY _objc_msgSendSuper2 UNWIND _objc_msgSendSuper2, NoFrame LDP P0, P16, [x0] // p0 = real receiver, p16 = class ldr p16, [x16,#SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
Copy the code
You can also use LLVM converted to intermediate code to view the key functions, clang-emit – LLVM -s fycat. m
define internal void @"\01-[FYCat forwardInvocation:]"(%1*, i8*, %2*) # 1 {call void bitcast (i8* (%struct._objc_super*, i8*, ...) * @objc_msgSendSuper2 to void (%struct._objc_super*, i8*, %2*)*)(%struct._objc_super* %7, i8* %18, %2* %12) }Copy the code
Objc_msgSendSuper2 (self,struct._objc_super i8*,%2*); Is the objc_msgSendSuper2 (self, superclass, @ the selector (forwardInvocation), anInvocation).
validation
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
- (int)age;
-(void)test;
@end
@implementation FYPerson
- (void)test{; NSLog(@"%s",__func__);
}
- (int)age{
NSLog(@"%s",__func__);
return 10;
}
- (NSString *)name{
return [_name stringByAppendingString:@" eat apple"];
}
@end
@interface FYStudent : FYPerson
@end
@implementation FYStudent
- (void)test{
[super test]; // Execute the parent classtestint age = [super age]; // Get the parent's method return value NSLog(@"age is %d",age); NSString * name = [self name]; // Start from the parent class to find the value of name, but return the value of self.name NSLog(@)"% @",name);
}
-(int)age{
return12. } @end // output -[FYPersontest[FYPerson age] age is 10 little plum eat appleCopy the code
Test is a method that executes the parent class. [super age] retrieves the fixed age in the parent class. [self name] starts from the parent class to find the value of name, but returns the value of self.name.
isMemberOfClass & isKindOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
printf("%s %s\n",class_getName(tcls),class_getName(cls));
if (tcls == cls)
{returnYES; }else{
printf("%s",class_getName(tcls)); }}return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
printf(" %s %s\n",class_getName(tcls),class_getName(cls));
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isSubclassOfClass:(Class)cls {
for (Class tcls = self; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
Copy the code
– (BOOL)isMemberOfClass and – (BOOL)isKindOfClass:(Class) CLS + (BOOL)isMemberOfClass:(Class) CLS = self. Class ->isa = CLS + (BOOL)isKindOfClass:(Class) is it possible for CLS ->isa and CLS ->isa to be equal to CLS? Only the base class is, and nothing else is.
Validate instance method
Class cls = NSObject.class;
Class pcls = FYPerson.class;
FYPerson *p=[FYPerson new];
NSObject *obj=[NSObject new];
BOOL res11 =[p isKindOfClass:pcls];
BOOL res12 =[p isMemberOfClass:pcls];
BOOL res13 =[obj isKindOfClass:cls];
BOOL res14 =[obj isMemberOfClass:cls];
NSLog(@"instance:%d %d %d %d",res11,res12,res13,res14);
//log
//instance:1 1 1 1
Copy the code
P is a subclass of PCLS, obj is a subclass of CLS, which is obvious enough.
Validation class method
//isKindOfClass is CLS ->isa equal to CLS/CLS ->superclass? // The metaclass object is not equal to the class object, but the last metaclass isa->superclass is the class that points to NSObject, so res1 = YES; BOOL res1 =[CLS isKindOfClass: CLS]; // CLS -> ISA ->superclass: NSObject BOOL res1 =[CLS isKindOfClass: CLS]; // are isa and CLS equal? Unequal CLS ->isa isa metaclass object, CLS isa class object and cannot be equal. BOOL res2 =[cls isMemberOfClass:cls]; CLS ->isa->superclass: NSObject Class object ->superclass:NSObject ->superclass:nil // PCLS :person BOOL res3 =[PCLS isKindOfClass: PCLS]; BOOL res4 =[PCLS isMemberOfClass: PCLS]; NSLog(@"%d %d %d %d",res1,res2,res3,res4); Result: 1 0 0 0Copy the code
Stack object nature class nature combat
Online saw a more interesting interview question, today we will take this opportunity to analyze, although a lot of online blog has spoken, but it seems not quite right, or did not talk about the fundamental things, so today to discuss what it is. In fact, this problem looks at the layout of objects in memory, the relationship between classes and objects, and the layout of memory on the heap. For those of you who don’t have a solid foundation, take a look at my history blog obj_MSgsend Fundamentals, Nature of Classes, Nature of Objects.
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
- (void)print;
@end
@implementation FYPerson
- (void)print{
NSLog(@"my name is %@",self.name); } @end - (void)viewDidLoad { [super viewDidLoad]; NSObject *fix =[NSObject new]; // 16 bytes 0x60000219B030 ID CLS = [FYPerson Class]; Void * obj = &cls; [(__bridge id)objprint];
}
Copy the code
Question one: Can you compile successfully?
When you see the second question, if you are not stupid, you will answer that it will compile successfully, otherwise why ask the result. SEL. Now CLS and obj are both on the stack. Obj points to the memory address of CLS. Accessing OBj is equivalent to accessing CLS memory directly. CLS stores person. class,[obj print] = objc_msgSend(CLS,@selector(print)), CLS has print method, so it will compile successfully.
Output what?
Fix/CLS /obj are all stored on the stack. The fix/ CLS /obj addresses are sequential from high to low, and they are 8 bytes apart. A pointer size is 8 bytes. Their three addresses are as follows:
Use diagrams to represent fix and obj:
object | address | Address of high and low |
---|---|---|
fix | 0x7ffeec3df920 | high |
cls | 0x7ffeec3df918 | In the |
obj | 0x7ffeec3df910 | low |
CLS = 0x7ffeec3df918; CLS = 0x7ffeec3df918; CLS = 0x7ffeec3df918; Plus 8 bytes is exactly the address 0x7FFeEC3DF920 of the fix. Since they are both Pointers, they are all 8 bytes, so the final output is the address of the fix object.
To make things a little more complicated, let’s change the FYPerson structure
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *name2;
@property (nonatomic,copy) NSString *name3;
- (void)print;
@end
Copy the code
8*1=8/8*2=16/8*3=24 bytes; that is, the pointer to the first, second, and third object is searched up.
Test code:
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *name2;
@property (nonatomic,copy) NSString *name3;
- (void)print;
@end
@implementation FYPerson
- (void)print{
NSLog(@"name1:%@ name2:%@ name3:%@",self.name1,self.name2,self.name3); } @end // main function NSObject *fix =[NSObject new]; FYPerson *fix2 =[FYPerson new]; id cls = [FYPerson class]; void * obj = &cls; [(__bridge id)objprint]; //objc_msgSend(self,sel); NSLog(@"fix:%p fix2:%p cls:%p obj:%p",&fix,&fix2,&cls,&obj);
//log
name1:<FYPerson: 0x6000033a38a0>
name2:<NSObject: 0x6000031f5380>
name3:<ViewController: 0x7f8307505580>
fix: 0x7ffeec3d f9 28
fix2:0x7ffeec3d f9 20
cls: 0x7ffeec3d f9 18
obj: 0x7ffeec3d f9 10
Copy the code
Deformation again:
- (void)viewDidLoad {
[super viewDidLoad];
/*
objc_msgSuperSend(self,ViewController,sel)
*/
NSLog(@"self:%p ViewController.class:%p SEL:%p",self,ViewController.class,@selector(viewDidLoad)); id cls = [FYPerson class]; // CLS is a class pointer void * obj = &cls; //obj [(__bridge id)objprint]; //objc_msgSend(self,sel); NSLog(@"cls:%p obj:%p",&cls,&obj);
//log
name1:<ViewController: 0x7fad03e04ea0>
name2:ViewController
self: 0x7fad03e04ea0
ViewController.class: 0x10d0edf00
SEL: 0x1117d5687
cls:0x7ffee2b11908
obj:0x7ffee2b11900
}
Copy the code
_name1 is CLS address up + 8 bytes, _name2 is moving up 16 bytes, [super viewDidLoad] is essentially objc_msgSuperSend (self, ViewController. Class, sel), Self, viewController.class, SEL are the same contiguous block of memory, arranged from low to high. If you look at the memory layout below, you will realize that the structure is as follows:
object | Address of high and low |
---|---|
self | low |
ViewController.class | In the |
SEL | high |
The commonly used runtimeAPI
method | desc |
---|---|
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) | Create a class dynamically (parameters: parent class, class name, extra memory |
void objc_registerClassPair(Class cls)) | Registering a class |
void objc_disposeClassPair(Class cls) | Destroying a class |
Class objcect_getClass(id obj) | Gets the class to which ISA points |
Class object_setClass (id obj,Class cls) | Set the class to which ISA points |
BOOL object_isClass(id class) | Check whether the OC object is Class |
BOOL class_isMetaClass(Class cls) | Whether metaclass |
Class class_getSuperclass(Class cls) | Access to the parent class |
Ivar class_getInstanceVariable(Class cls ,const char * name | Gets an instance variable information |
Ivar * class_copyIvarList(Class cls,unsigned int * outCount) | Copy the list of instance variables, requiring free |
void object_setIvar(id obj,Ivar ivar,id value | Set to get the value of the instance variable |
id object_getIvar(id obj,Ivar ivar) | Gets the value of the instance variable |
BOOL class_addIvar(Class cls,const cahr * name ,size_t size,uint_t alignment,const char * types) | Dynamically add member variables (registered classes cannot dynamically add member variables) |
Const char * ivar_getName (Ivar v) | Get variable name |
const char * ivar_getTypeEncoding(Ivar v) | Variables encode |
objc_property_t class_getProperty(Class cls,const char* name) | Get a property |
objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount) | Copy property list |
objc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name) | Get property list |
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount) | Add attributes |
void class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount) | Replace the attribute |
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,unsigned int attributeCount) | Dynamic substitution property |
const char * _Nonnull property_getName(objc_property_t _Nonnull property) | Get the name |
const char * _Nullable property_getAttributes(objc_property_t _Nonnull property) | Gets the property of the property |
IMP imp_implementationWithBlock(id block) | Get the IMP of the block |
id imp_getBlock(IMP anIMP) | Get blocks from IMP |
BOOL imp_removeBlock(IMP anIMP) | Whether IMP is deleted |
. | . |
In the business sometimes need to assign a value to the system control of a property, but the system does not provide a method, can only rely on their own, so we get all the member variables of class, can obtain Ivar to check whether there is this variable, and then can be assigned through KVC.
@interface FYCat : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,assign) int age;
@end
FYCat *cat=[FYCat new];
unsigned int count = 0;
Ivar *vars= class_copyIvarList(cat.class, &count);
for (int i = 0; i < count; i ++) {
Ivar item = vars[i];
const char *name = ivar_getName(item);
NSLog(@"%s",name);
}
free(vars);
Method *m1= class_copyMethodList(cat.class, &count);
for (int i = 0; i < count; i ++) {
Method item = m1[i];
SEL name = method_getName(item);
printf("method:%s \n",NSStringFromSelector(name).UTF8String);
}
free(m1);
//log
_age
_name
method:.cxx_destruct
method:name
method:setName:
method:methodSignatureForSelector:
method:forwardInvocation:
method:age
method:setAge:
Copy the code
One of the most commonly used functions is JsonToModel, so now that we’ve seen the basics of runtime, we can now do our own JsonToModel.
@interface NSObject (Json)
+ (instancetype)fy_objectWithJson:(NSDictionary *)json;
@end
@implementation NSObject (Json)
+ (instancetype)fy_objectWithJson:(NSDictionary *)json{
id obj = [[self alloc]init];
unsigned int count = 0;
Ivar *vars= class_copyIvarList(self, &count);
for (int i = 0; i < count; i ++) {
Ivar item = vars[i];
const char *name = ivar_getName(item);
NSString * nameOC= [NSString stringWithUTF8String:name];
if (nameOC.length>1) {
nameOC = [nameOC substringFromIndex:1];
NSString * value = json[nameOC];
if ([value isKindOfClass:NSString.class] && value.length) {
[obj setValue:value forKey:nameOC];
}else if ([value isKindOfClass:NSArray.class]){
[obj setValue:value forKey:nameOC];
}else if ([value isKindOfClass:NSDictionary.class]){
[obj setValue:value forKey:nameOC];
}else if ([value isKindOfClass:[NSNull class]] || [value isEqual:nil])
{
printf("%s value is nil or null \n",name);
}else if ([value integerValue] > 0){
[obj setValue:value forKey:nameOC];
}else{
printf("Unknown error \n");
}
}
}
free(vars);
return obj;
}
@end
Copy the code
Then test the code by defining a dictionary of your own
@interface FYCat : NSObject
@property (nonatomic,copy) NSString * name;
@property (nonatomic,assign) int age;
- (void)run;
@end
NSDictionary * info = @{@"age": @"10"The @"value": @ @ 10"name": @"Xiao Ming"};
FYCat *cat=[FYCat fy_objectWithJson:info];
//logAge: 10 name: xiao MingCopy the code
Hook hook (method_exchangeImplementations)
Since the business needs to log certain button click events, we can use hooks to intercept all button click events.
@implementation UIButton (add)
+ (void)load{
Method m1= class_getInstanceMethod(self.class, @selector(sendAction:to:forEvent:));
Method m2= class_getInstanceMethod(self.class, @selector(fy_sendAction:to:forEvent:));
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(m1, m2);
});
}
- (void)fy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
NSLog(@"%@ ",NSStringFromSelector(action)); /* code here */ /sel IMP is already swapped, so it doesn't loop [self fy_sendAction: Action to:target]forEvent:event];
}
@end
Copy the code
You can add code that needs to be handled in code here, generally logging and delayed triggering can be handled. [self fy_sendAction:action to:target forEvent:event]; There is no dead loop because m1 and M2 have been swapped through IMP in +load. We’re going into method_exchangeImplementations inside:
void method_exchangeImplementations(Method m1, Method m2)
{
if(! m1 || ! m2)return; mutex_locker_t lock(runtimeLock); M1_imp = m1-> IMP; m1->imp = m2->imp; m2->imp = m1_imp; // Flushhcaches (nil); updateCustomRR_AWZ(nil, m1); updateCustomRR_AWZ(nil, m2); } struct method_t { SEL name; const char *types; MethodListIMP imp; }; using MethodListIMP = IMP;Copy the code
Method_t -> method_t-> method_t-> method_t-> method_t-> data->method
Array out of bounds and nil handling
@implementation NSMutableArray (add)
+ (void)load{
Class cls= NSClassFromString(@"__NSArrayM");
Method m1= class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
SEL sel = @selector(fy_insertObject:atIndex:);
Method m2= class_getInstanceMethod(cls, sel);
Method m3= class_getInstanceMethod(cls, @selector(objectAtIndexedSubscript:));
Method m4= class_getInstanceMethod(cls, @selector(fy_objectAtIndexedSubscript:));
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(m1, m2);
method_exchangeImplementations(m3, m4);
});
}
- (void)fy_insertObject:(id)anObject atIndex:(NSUInteger)index{
if(anObject ! = nil) { [self fy_insertObject:anObject atIndex:index]; }else{
printf(" anObject is nil \n");
}
}
- (id)fy_objectAtIndexedSubscript:(NSUInteger)idx{
if (self.count > idx) {
return [self fy_objectAtIndexedSubscript:idx];
}else{
printf(" %ld is outof rang \n",(long)idx);
return nil;
}
}
@end
NSMutableArray *array=[NSMutableArray array];
id obj = nil;
[array addObject:obj];
array[1];
//log
anObject is nil
1 is outof rang
Copy the code
NSMutableArray is a class cluster, using the factory pattern. NSMutableArray is not an array instance, but a factory that produces array objects. The real array object is __NSArrayM, and then gives __NSArrayM the hook, Exchange objectAtIndexedSubscript (NSUInteger) independence idx and insertObject: (id) anObject atIndex (NSUInteger) index method, to avoid collapse.
Dictionary nil handling
@interface NSMutableDictionary (add)
@end
@implementation NSMutableDictionary (add)
+ (void)load{
Class cls= NSClassFromString(@"__NSDictionaryM");
Method m1= class_getInstanceMethod(cls, @selector(setObject:forKey:));
// __NSDictionaryM
SEL sel = @selector(fy_setObject:forKey:);
Method m2= class_getInstanceMethod(cls, sel);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(m1, m2);
});
}
- (void)fy_setObject:(id)anObject forKey:(id<NSCopying>)aKey{
if (anObject) {
[self fy_setObject:anObject forKey:aKey];
}else{
NSString * key = (NSString *)aKey;
printf("key:%s anobj is nil \n",key.UTF8String);
}
}
@end
Copy the code
NSMutableDictionary setObject:Key: = NSMutableDictionary setObject: = NSMutableDictionary setObject: = NSMutableDictionary setObject: = NSMutableDictionary setObject: = NSMutableDictionary setObject: = NSMutableDictionary setObject: = NSMutableDictionary But to prevent the IMP and SEL from being confused by the developer calling again, use dispatch_once for a single run.
conclusion
super
Essentially,self
Call the function, but find the function fromsueprclass
Start the search+isKandOfClass
Is a judgmentself
Whether it iscls
The subclass,+isMemberOfClass:
Is a judgmentself
Whether andcls
The same.- To understand
+load
inCategory
Is compiled using runtime at startup, and is only loaded once and then exploitedobjc/runtime.h
In themethod_exchangeImplementations
Implement the exchange of two functionsIMP
, can achieve interceptionnil
To reduce the crash rate. NSMutableDictionary
,NSMutableArray
Classes that find their classes and then swap their functionsIMP
.
Data reference
- Little brother video
Data download
- Download of learning materials
- demo code
- Runtime runnable source
The most afraid of a mediocre life, but also comfort their ordinary valuable.
AD time