1、[self class]和[super class]

查看如下代码打印的结果是?

  1. @interface Father : NSObject
  2. @end
  3. @implementation Father
  4. @end
  5. @interface Son : Father
  6. @end
  7. @implementation Son
  8. - (instancetype)init {
  9. self = [super init];
  10. if (self) {
  11. NSLog(@"%@",NSStringFromClass([self class]));
  12. NSLog(@"%@",NSStringFromClass([super class]));
  13. }
  14. return self;
  15. }
  16. @end

通过编译运行,打印的结果
image.png
为什么出现这样的结果呢?
首先看下self和super的区别:

  • self是类的一个隐藏参数,每个方法的实现的第一个参数都是self
  • super并不是隐藏参数,它实际上是一个”编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。

在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend。

  1. OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
  2. struct objc_super {
  3. /// Specifies an instance of a class.
  4. __unsafe_unretained _Nonnull id receiver;
  5. /// Specifies the particular superclass of the instance to message.
  6. #if !defined(__cplusplus) && !__OBJC2__
  7. /* For compatibility with old objc-runtime.h header */
  8. __unsafe_unretained _Nonnull Class class;
  9. #else
  10. __unsafe_unretained _Nonnull Class super_class;
  11. #endif
  12. /* super_class is the first class to search */
  13. };

在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。
objc_msgSendSuper的工作原理:
从objc_super结构体指向的superClass父类的方法列表开始查找SEL,找到后以objc->receiver去调用父类的这个SEL。注意,最后的调用者是objc->receiver,而不是super_class。
所以objc_msgSendSuper将转化为如下:

  1. // 注意这里是从父类开始msgSend,而不是从本类开始
  2. objc_msgSend(objc_super->receiver, @selector(class))

因为objc_super->receiver = self,所以两个打印的结果都是Son了。

2、isKindOfClass 与 isMemberOfClass

如下代码,将输出什么?Father类使用问题1中的类

  1. BOOL q1 = [[NSObject class] isKindOfClass:[NSObject class]];
  2. BOOL q2 = [[NSObject class] isMemberOfClass:[NSObject class]];
  3. BOOL q3 = [[Father class] isKindOfClass:[Father class]];
  4. BOOL q4 = [[Father class] isMemberOfClass:[Father class]];
  5. NSLog(@"q1=%d,q2=%d,q3=%d,q4=%d",q1,q2,q3,q4);

打印输出结果是
image.png
原因是什么呢?
首先从源码进行分析

  1. + (Class)class {
  2. return self;
  3. }
  4. - (Class)class {
  5. return object_getClass(self);
  6. }
  7. + (BOOL)isMemberOfClass:(Class)cls {
  8. return self->ISA() == cls;
  9. }
  10. - (BOOL)isMemberOfClass:(Class)cls {
  11. return [self class] == cls;
  12. }
  13. + (BOOL)isKindOfClass:(Class)cls {
  14. for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
  15. if (tcls == cls) return YES;
  16. }
  17. return NO;
  18. }
  19. - (BOOL)isKindOfClass:(Class)cls {
  20. for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
  21. if (tcls == cls) return YES;
  22. }
  23. return NO;
  24. }

object_getClass的源码如下

  1. Class object_getClass(id obj)
  2. {
  3. if (obj) return obj->getIsa();
  4. else return Nil;
  5. }

从以上代码中可以看出isKindOfClass方法,会先去获得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本身。
  • 所以第二次循环相等,于是q1=1(YES)。

同理,[Father class]执行完之后调用isKindOfClass,

  • 第一次for循环,Father的Meta Class与[Sark class]不等,
  • 第二次for循环,Father Meta Class的super class 指向的是 NSObject Meta Class, 和 Father Class不相等。
  • 第三次for循环,NSObject Meta Class的super class指向的是NSObject Class,和 Father Class 不相等。
  • 第四次循环,NSObject Class 的super class 指向 nil, 和 Father Class不相等。
  • 第四次循环之后,退出循环,所以第三行的q3=0(NO)。

isMemberOfClass的源码实现是拿到自己的isa指针和自己比较,是否相等。
q2的isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等。
q4的isa指向Father的Meta Class,和Father Class也不等,
所以q2=0(NO),q4=0(NO)。

如果是类的示例对象调用isKindOfClass是相等的。

  1. Father *f = [Father new];
  2. BOOL q5 = [f isKindOfClass:[Father class]];

输出结果q5=1(YES)

3.Class与内存地址

如下代码会发生什么?Compile Error/Runtime Crash/ NSLog..?

  1. @interface Father : NSObject
  2. @property(nonatomic, copy) NSString *name;
  3. - (void)speak;
  4. @end
  5. @implementation Father
  6. - (void)speak {
  7. NSLog(@"my name's %@",self.name);
  8. }
  9. @end
  10. @implementation ViewController
  11. - (void)viewDidLoad {
  12. [super viewDidLoad];
  13. // Do any additional setup after loading the view.
  14. // 第三题
  15. id cls = [Father class];
  16. void *obj = &cls;
  17. [(__bridge id)obj speak];
  18. }

运行结果:
image.png
这个问题考察的的主要是两个点?

1、程序是否正常运行,obj能调用speak吗? 2、程序正常运行,那输出打印是什么?

回到这个问题,首先需要知道,在Objective-C中存在类对象和实例对象,通过查看底层源码objc_object和objc_class实质都是对象看待。所以代码能正常运行。
其次,对于输出的结果,需要联系Objctive-C中方法的隐含参数self和_cmd,参数在栈空间的内存存储位有关系。

obj已经满足了构成一个objc对象的全部要求(首地址指向ClassObject),遂能够正常走消息机制;
由于这个人造的对象在栈上,而取self.name的操作本质上是self指针在内存向高位地址偏移(64位下一个指针是8字节),按viewDidLoad执行时各个变量入栈顺序从高到底为(self, _cmd, self.class, self, obj)(前两个是方法隐含入参,随后两个为super调用的两个压栈参数),遂栈低地址的obj+8取到了self。

以下做深入的探讨:
将ViewController中的self,_cmd,cls,obj的地址进行打印出来是

  1. NSLog(@"VC=%@,地址=%p",self,&self);
  2. id cls = [Father class];
  3. NSLog(@"cls=%@,地址=%p",cls,&cls);
  4. void *obj = &cls;
  5. NSLog(@"*obj=%@,地址=%p",obj,&obj);
  6. [(__bridge id)obj speak];

VC=,地址=0x7ffee13d2208 cls=Father,地址=0x7ffee13d21e8 *obj=,地址=0x7ffee13d21e0

4.方法调用[NSObject foo]

如下源码将会发生什么?Compile Error/Runtime Crash/ NSLog..?

  1. @interface NSObject (Sark)
  2. + (void)foo;
  3. @end
  4. @implementation NSObject (Sark)
  5. - (void)foo {
  6. NSLog(@"IMP: -[NSObject (Sark) foo]");
  7. }
  8. @end
  9. @interface ViewController ()
  10. @end
  11. @implementation ViewController
  12. - (void)viewDidLoad {
  13. [super viewDidLoad];
  14. [NSObject foo];
  15. [[NSObject new] foo];
  16. }
  17. @end

这段代码能直接运行并打印结果:
image.png
如果以分类文件的方式定义NSObject (Sark),则[[NSObject new] foob];将会报错

No visible @interface for ‘NSObject’ declares the selector ‘foo’

那么在同一个文件下,为什么会出现这样的问题呢?
首先将这两句代码转换成C++代码

  1. // [NSObject foob];
  2. ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("foo"));
  3. // [[NSObject new] foob];
  4. ((void (*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")), sel_registerName("foo"));

继续查看方法调用sel_registerName函数

  1. SEL sel_registerName(const char *name) {
  2. return __sel_registerName(name, 1, 1); // YES lock, YES copy
  3. }
  4. static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
  5. {
  6. SEL result = 0;
  7. if (shouldLock) selLock.assertUnlocked();
  8. else selLock.assertLocked();
  9. if (!name) return (SEL)0;
  10. result = _objc_search_builtins(name);
  11. if (result) return result;
  12. conditional_mutex_locker_t lock(selLock, shouldLock);
  13. if (_objc_selectors) {
  14. result = __objc_sel_set_get(_objc_selectors, (SEL)name);
  15. }
  16. if (result) return result;
  17. // No match. Insert.
  18. if (!_objc_selectors) {
  19. _objc_selectors = __objc_sel_set_create(SelrefCount);
  20. }
  21. if (!result) {
  22. result = (SEL)(copy ? strdup(name) : name);
  23. __objc_sel_set_add(_objc_selectors, result);
  24. #if defined(DUMP_UNKNOWN_SELECTORS)
  25. printf("\t\"%s\",\n", name);
  26. #endif
  27. }
  28. return result;
  29. }

clang之后,识别两个方法都是通过sel_registerName("foo")返回的SEL指针。类方法和实例方法只是存储的位置不一样,一个是在class中,一个是在mateclass中。NSObject的mateclass是他自己本身,所以他能找到foo的方法实现,所以就能正常打印。
如果修改成下面的代码:

  1. @interface Father : NSObject
  2. @end
  3. @implementation Father
  4. @end
  5. @interface Father (Sark)
  6. + (void)foo;
  7. @end
  8. @implementation Father (Sark)
  9. - (void)foo {
  10. NSLog(@"IMP: -[Father (Sark) foo]");
  11. }
  12. @end

调用方法代码:

  1. [Father foo];
  2. [[Father new] foo];

这里编译能通过,但是运行的时候[Father foo];将运行报错。这是因为Father的mateclass是NSObject mateclass,在这些类中都没找到foo方法,所以run报错。
所以这种情况只会在**NSObject**才会出现,在其他类如果有这种情况,必定会产生崩溃。

5、分类的底层本质

Category 分类底层实现

6、怎么获取某个类的所有继承者?

怎么获取某个类的所有子类/父类?
1、如果是获取父类的情况,只需要循环遍历获取get_superClass();

7、怎么给一个小白用户去解释objective-C中runtime?

A:runtime是xxx

8、Runloop是如何进入休眠的,如何从休眠唤醒的?

9、如何理解RunLoop本质是一个循环?

10、Source0与Source1的有什么区别和使用场景?