Objective-C Runtime入院第一天
2017-05-10 17:56
316 查看
(一)[self class] 与 [super class]
下面代码输出什么?@implementation Son : Father - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
self和super的区别:
self是类的一个隐藏参数,每个方法的实现的第一个参数即为self。
super并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。
在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) /// Specifies the superclass of an instance. struct objc_super { /// Specifies an instance of a class. __unsafe_unretained id receiver; /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus) && !__OBJC2__ /* For compatibility with old objc-runtime.h header */ __unsafe_unretained Class class; #else __unsafe_unretained Class super_class; #endif /* super_class is the first class to search */ };
在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。
入院考试第一题错误的原因就在这里,误认为[super class]是调用的[super_class class]。
objc_msgSendSuper的工作原理应该是这样的:
从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver,而不是super_class!
那么objc_msgSendSuper最后就转变成
// 注意这里是从父类开始msgSend,而不是从本类开始,谢谢@Josscii 和他同事共同指点出此处描述的不妥。 objc_msgSend(objc_super->receiver, @selector(class)) + (Class)class { return self; }
由于找到了父类NSObject里面的class方法的IMP,又因为传入的入参objc_super->receiver = self。self就是son,调用class,所以父类的方法class执行IMP之后,输出还是son,最后输出两个都一样,都是输出son。
(二)isKindOfClass 与 isMemberOfClass
下面代码输出什么?@interface Sark : NSObject @end @implementation Sark @end int main(int argc, const char * argv[]) { @autoreleasepool { BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]]; BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]]; NSLog(@"%d %d %d %d", res1, res2, res3, res4); } return 0; }
先来分析一下源码这两个函数的对象实现
+ (Class)class { return self; } - (Class)class { return object_getClass(self); } Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } inline Class objc_object::getIsa() { if (isTaggedPointer()) { uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK; return objc_tag_classes[slot]; } return ISA(); } inline Class objc_object::ISA() { assert(!isTaggedPointer()); return (Class)(isa.bits & ISA_MASK); } + (BOOL)isKindOfClass:(Class)cls { for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } - (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; } + (BOOL)isMemberOfClass:(Class)cls { return object_getClass((id)self) == cls; } - (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; }
首先题目中NSObject 和 Sark分别调用了class方法。
+ (BOOL)isKindOfClass:(Class)cls方法内部,会先去获得object_getClass的类,而object_getClass的源码实现是去调用当前类的obj->getIsa(),最后在ISA()方法中获得meta class的指针。
接着在isKindOfClass中有一个循环,先判断class是否等于meta class,不等就继续循环判断是否等于super class,不等再继续取super class,如此循环下去。
[NSObject class]执行完之后调用isKindOfClass,第一次判断先判断NSObject 和 NSObject的meta class是否相等,之前讲到meta class的时候放了一张很详细的图,从图上我们也可以看出,NSObject的meta class与本身不等。接着第二次循环判断NSObject与meta class的superclass是否相等。还是从那张图上面我们可以看到:Root class(meta) 的superclass 就是 Root class(class),也就是NSObject本身。所以第二次循环相等,于是第一行res1输出应该为YES。
同理,[Sark class]执行完之后调用isKindOfClass,第一次for循环,Sark的Meta Class与[Sark class]不等,第二次for循环,Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等。第三次for循环,NSObject Meta Class的super class指向的是NSObject Class,和 Sark Class 不相等。第四次循环,NSObject Class 的super
class 指向 nil, 和 Sark Class不相等。第四次循环之后,退出循环,所以第三行的res3输出为NO。
如果把这里的Sark改成它的实例对象,[sark isKindOfClass:[Sark class],那么此时就应该输出YES了。因为在isKindOfClass函数中,判断sark的meta class是自己的元类Sark,第一次for循环就能输出YES了。
isMemberOfClass的源码实现是拿到自己的isa指针和自己比较,是否相等。
第二行isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等。第四行,isa指向Sark的Meta Class,和Sark Class也不等,所以第二行res2和第四行res4都输出NO。
(三)Class与内存地址
下面的代码会?Compile Error / Runtime Crash / NSLog…?@interface Sark : NSObject @property (nonatomic, copy) NSString *name; - (void)speak; @end @implementation Sark - (void)speak { NSLog(@"my name's %@", self.name); } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; id cls = [Sark class]; void *obj = &cls; [(__bridge id)obj speak]; } @end
这道题有两个难点。难点一,obj调用speak方法,到底会不会崩溃。难点二,如果speak方法不崩溃,应该输出什么?
首先需要谈谈隐藏参数self和_cmd的问题。
当[receiver message]调用方法时,系统会在运行时偷偷地动态传入两个隐藏参数self和_cmd,之所以称它们为隐藏参数,是因为在源代码中没有声明和定义这两个参数。self在上面已经讲解明白了,接下来就来说说_cmd。_cmd表示当前调用方法,其实它就是一个方法选择器SEL。
难点一,能不能调用speak方法?
id cls = [Sark class]; void *obj = &cls;
答案是可以的。obj被转换成了一个指向Sark Class的指针,然后使用id转换成了objc_object类型。obj现在已经是一个Sark类型的实例对象了。当然接下来可以调用speak的方法。
难点二,如果能调用speak,会输出什么呢?
很多人可能会认为会输出sark相关的信息。这样答案就错误了。
正确的答案会输出
my name is <ViewController: 0x7ff6d9f31c50>
内存地址每次运行都不同,但是前面一定是ViewController。why?
我们把代码改变一下,打印更多的信息出来。
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"ViewController = %@ , 地址 = %p", self, &self); id cls = [Sark class]; NSLog(@"Sark class = %@ 地址 = %p", cls, &cls); void *obj = &cls; NSLog(@"Void *obj = %@ 地址 = %p", obj,&obj); [(__bridge id)obj speak]; Sark *sark = [[Sark alloc]init]; NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark); [sark speak]; }
我们把对象的指针地址都打印出来。输出结果:
ViewController = <ViewController: 0x7fb570e2ad00> , 地址 = 0x7fff543f5aa8 Sark class = Sark 地址 = 0x7fff543f5a88 Void *obj = <Sark: 0x7fff543f5a88> 地址 = 0x7fff543f5a80 my name is <ViewController: 0x7fb570e2ad00> Sark instance = <Sark: 0x7fb570d20b10> 地址 = 0x7fff543f5a78 my name is (null)
// objc_msgSendSuper2() takes the current search class, not its superclass. OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
objc_msgSendSuper2方法入参是一个objc_super *super。
/// Specifies the superclass of an instance. struct objc_super { /// Specifies an instance of a class. __unsafe_unretained id receiver; /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus) && !__OBJC2__ /* For compatibility with old objc-runtime.h header */ __unsafe_unretained Class class; #else __unsafe_unretained Class super_class; #endif /* super_class is the first class to search */ }; #endif
所以按viewDidLoad执行时各个变量入栈顺序从高到底为self, _cmd, self.class, self, obj。
第一个self和第二个_cmd是隐藏参数。第三个self.class和第四个self是[super viewDidLoad]方法执行时候的参数。
在调用self.name的时候,本质上是self指针在内存向高位地址偏移一个指针。在32位下面,一个指针是4字节=4*8bit=32bit。
从打印结果我们可以看到,obj就是cls的地址。在obj向上偏移32bit就到了0x7fff543f5aa8,这正好是ViewController的地址。
所以输出为my name is <ViewController: 0x7fb570e2ad00>。
至此,Objc中的对象到底是什么呢?
实质:Objc中的对象是一个指向ClassObject地址的变量,即 id obj = &ClassObject , 而对象的实例变量 void *ivar = &obj + offset(N)
加深一下对上面这句话的理解,下面这段代码会输出什么?
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"ViewController = %@ , 地址 = %p", self, &self); NSString *myName = @"halfrost"; id cls = [Sark class]; NSLog(@"Sark class = %@ 地址 = %p", cls, &cls); void *obj = &cls; NSLog(@"Void *obj = %@ 地址 = %p", obj,&obj); [(__bridge id)obj speak]; Sark *sark = [[Sark alloc]init]; NSLog(@"Sark instance = %@ 地址 = %p",sark,&sark); [sark speak]; }
ViewController = <ViewController: 0x7fff44404ab0> , 地址 = 0x7fff56a48a78 Sark class = Sark 地址 = 0x7fff56a48a50 Void *obj = <Sark: 0x7fff56a48a50> 地址 = 0x7fff56a48a48 my name is halfrost Sark instance = <Sark: 0x6080000233e0> 地址 = 0x7fff56a48a40 my name is (null)
由于加了一个字符串,结果输出就完全变了,[(__bridge id)obj speak];这句话会输出“my name is halfrost”
原因还是和上面的类似。按viewDidLoad执行时各个变量入栈顺序从高到底为self,_cmd,self.class,self,myName,obj。obj往上偏移32位,就是myName字符串,所以输出变成了输出myName了。
相关文章推荐
- 神经病院Objective-C Runtime入院第一天——isa和Class
- Objective-C Runtime 运行时之二:成员变量与属性
- Objective-C Runtime 一小时入门教程(中)
- Objective-C Runtime 运行时之五:协议与分类
- Objective-C Runtime 运行时之四:Method Swizzling
- 刨根问底Objective-C Runtime(2)- Object & Class & Meta Class
- Objective-C Runtime
- iOS开发:详解Objective-C runtime
- Objective-C特性:Runtime
- Objective-C Runtime 运行时:Method Swizzling
- Objective-C runtime之运行时的基本特点(一)
- Objective-C runtime 机制
- ArcGIS Runtime SDK for iOS开发系列教程(3)——Objective-C语法基础
- Objective-C 运行时编程指南 之 Interacting with the Runtime
- Objective - C基础: 第一天 - 3.BOOL基本认识
- Objective-C与Runtime的那点事儿消息机制
- iOS开发:详解Objective-C runtime
- iOS进阶:Objective-C runtime(一)
- Objective-C Runtime
- Objective-C Runtime 运行时之二:成员变量与属性