1. load方法的调用时机?

load_images函数中

  1. add_class_to_loadable_list:将所有主类的load方法,收集到一张主类load方法表
  2. add_category_to_loadable_list:将所有分类的load方法,收集到一张分类load方法表
  3. call_class_loads:先循环调用主类的load方法
  4. call_category_loads:再循环调用分类的load方法

image.png

1.1 add_class_to_loadable_list

  1. void add_class_to_loadable_list(Class cls)
  2. {
  3. IMP method;
  4. loadMethodLock.assertLocked();
  5. method = cls->getLoadMethod();
  6. if (!method) return; // Don't bother if cls has no +load method
  7. if (PrintLoading) {
  8. _objc_inform("LOAD: class '%s' scheduled for +load",
  9. cls->nameForLogging());
  10. }
  11. if (loadable_classes_used == loadable_classes_allocated) {
  12. loadable_classes_allocated = loadable_classes_allocated*2 + 16;
  13. loadable_classes = (struct loadable_class *)
  14. realloc(loadable_classes,
  15. loadable_classes_allocated *
  16. sizeof(struct loadable_class));
  17. }
  18. loadable_classes[loadable_classes_used].cls = cls;
  19. loadable_classes[loadable_classes_used].method = method;
  20. loadable_classes_used++;
  21. }
  • 将所有主类的load方法,收集到一张主类load方法表

1.2 add_category_to_loadable_list

  1. void add_category_to_loadable_list(Category cat)
  2. {
  3. IMP method;
  4. loadMethodLock.assertLocked();
  5. method = _category_getLoadMethod(cat);
  6. // Don't bother if cat has no +load method
  7. if (!method) return;
  8. if (PrintLoading) {
  9. _objc_inform("LOAD: category '%s(%s)' scheduled for +load",
  10. _category_getClassName(cat), _category_getName(cat));
  11. }
  12. if (loadable_categories_used == loadable_categories_allocated) {
  13. loadable_categories_allocated = loadable_categories_allocated*2 + 16;
  14. loadable_categories = (struct loadable_category *)
  15. realloc(loadable_categories,
  16. loadable_categories_allocated *
  17. sizeof(struct loadable_category));
  18. }
  19. loadable_categories[loadable_categories_used].cat = cat;
  20. loadable_categories[loadable_categories_used].method = method;
  21. loadable_categories_used++;
  22. }
  • add_category_to_loadable_list:将所有分类的load方法,收集到一张分类load方法表

1.3 call_class_loads

  1. static void call_class_loads(void)
  2. {
  3. int i;
  4. // Detach current loadable list.
  5. struct loadable_class *classes = loadable_classes;
  6. int used = loadable_classes_used;
  7. loadable_classes = nil;
  8. loadable_classes_allocated = 0;
  9. loadable_classes_used = 0;
  10. // Call all +loads for the detached list.
  11. for (i = 0; i < used; i++) {
  12. Class cls = classes[i].cls;
  13. load_method_t load_method = (load_method_t)classes[i].method;
  14. if (!cls) continue;
  15. if (PrintLoading) {
  16. _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
  17. }
  18. (*load_method)(cls, @selector(load));
  19. }
  20. // Destroy the detached list.
  21. if (classes) free(classes);
  22. }
  • call_class_loads:先循环调用主类的load方法

1.4 call_category_loads

  1. static bool call_category_loads(void)
  2. {
  3. int i, shift;
  4. bool new_categories_added = NO;
  5. // Detach current loadable list.
  6. struct loadable_category *cats = loadable_categories;
  7. int used = loadable_categories_used;
  8. int allocated = loadable_categories_allocated;
  9. loadable_categories = nil;
  10. loadable_categories_allocated = 0;
  11. loadable_categories_used = 0;
  12. // Call all +loads for the detached list.
  13. for (i = 0; i < used; i++) {
  14. Category cat = cats[i].cat;
  15. load_method_t load_method = (load_method_t)cats[i].method;
  16. Class cls;
  17. if (!cat) continue;
  18. cls = _category_getClass(cat);
  19. if (cls && cls->isLoadable()) {
  20. if (PrintLoading) {
  21. _objc_inform("LOAD: +[%s(%s) load]\n",
  22. cls->nameForLogging(),
  23. _category_getName(cat));
  24. }
  25. (*load_method)(cls, @selector(load));
  26. cats[i].cat = nil;
  27. }
  28. }
  29. // Compact detached list (order-preserving)
  30. shift = 0;
  31. for (i = 0; i < used; i++) {
  32. if (cats[i].cat) {
  33. cats[i-shift] = cats[i];
  34. } else {
  35. shift++;
  36. }
  37. }
  38. used -= shift;
  39. // Copy any new +load candidates from the new list to the detached list.
  40. new_categories_added = (loadable_categories_used > 0);
  41. for (i = 0; i < loadable_categories_used; i++) {
  42. if (used == allocated) {
  43. allocated = allocated*2 + 16;
  44. cats = (struct loadable_category *)
  45. realloc(cats, allocated *
  46. sizeof(struct loadable_category));
  47. }
  48. cats[used++] = loadable_categories[i];
  49. }
  50. // Destroy the new list.
  51. if (loadable_categories) free(loadable_categories);
  52. // Reattach the (now augmented) detached list.
  53. // But if there's nothing left to load, destroy the list.
  54. if (used) {
  55. loadable_categories = cats;
  56. loadable_categories_used = used;
  57. loadable_categories_allocated = allocated;
  58. } else {
  59. if (cats) free(cats);
  60. loadable_categories = nil;
  61. loadable_categories_used = 0;
  62. loadable_categories_allocated = 0;
  63. }
  64. if (PrintLoading) {
  65. if (loadable_categories_used != 0) {
  66. _objc_inform("LOAD: %d categories still waiting for +load\n",
  67. loadable_categories_used);
  68. }
  69. }
  70. return new_categories_added;
  71. }
  • call_category_loads:再循环调用分类的load方法

2. 主类方法和分类方法的调用顺序?

2.1 普通方法

普通方法,包括initialize,优先分类中的方法调用

因为分类的方法是在类realize之后attach进去的,所以插在前面

2.2 load方法

load方法,优先主类,然后分类

因为类的初始化,优先主类,读取ro。然后分类初始化,读取rwe

2.3 多分类

多个分类之间,看文件的编译顺序。load方法,先编译的分类先执行。同名方法,最后编译的分类中的方法会被执行

3. load、initialize、cxx的调用顺序?

load方法和cxx函数,在程序启动时自动调用

调用顺序:loadcxxmain

对于相同镜像文件,load方法一定在cxx函数之前

不同镜像文件的调用顺序:系统库优先→动态库→主程序

initialize方法,属于懒加载方法,在对象首次消息发送时调用

objc中的cxx函数,它会在_objc_init函数中,调用static_init函数,执行C++静态构造函数
image.png

4. Runtime是什么?

Runtime是由CC++、汇编实现的一套API,为OC语言增加面向对象及运行时的功能

Runtime是指将数据类型的确定从编译时推迟到运行时

例如:类扩展(Extension)和分类(Category)的区别

Runtime机制对于AOP面向切面编程提供良好的支持

平时编写的OC代码,最终都会转换成RuntimeC语言代码,RuntimeObject-C的幕后工作者

5. 方法的本质是什么?

方法的本质是消息发送,消息发送的流程:

  1. 消息快速查找,由汇编代码实现的objc_msgSend
  2. 消息慢速查找,遍历当前类和父类,由C++代码实现的lookUpImpOrForward函数
  3. 方法动态决议,resolveInstanceMethod
  4. 消息快速转发,forwardingTargetForSelector
  5. 消息慢速转发,methodSignatureForSelectorforwardInvocation
  6. 挽救失败,由doesNotRecognizeSelector:报出异常

5.1 selimp

sel:方法编号,在read_images时就读取到了内存
imp:函数地址,消息发送就是selimp的过程

5.2 二者的关系

sel为方法编号,impsel对应的函数地址,通过imp函数地址才能找到真正的函数实现

例如

sel相当于一本书的目录
imp相当于页码,通过imp才能找到函数的实现代码

查找某个函数的过程,相当于找到这本书中某个章节的具体内存

  1. 首先知道要找的是什么,sel
  2. 找到对应的页面,imp
  3. 将书翻到指定页,找到具体内容

6. 能否在运行时对编译后的类添加实例变量?

不能,因为编译后的实例变量存储在ro中,⼀旦编译完成,内存结构就完全确定了,⽆法修改。可以对编译后的类添加属性和⽅法

7、能否对运⾏时创建的类添加实例变量?

可以,使用objc_allocateClassPair创建类,只要在objc_registerClassPair注册之前,可添加实例变量。一旦注册后,无法添加实例变量

使用class_addIvar添加实例变量,添加前进行flags & RW_CONSTRUCTING的条件判断

  1. BOOL
  2. class_addIvar(Class cls, const char *name, size_t size,
  3. uint8_t alignment, const char *type)
  4. {
  5. ...
  6. // No class variables
  7. if (cls->isMetaClass()) {
  8. return NO;
  9. }
  10. // Can only add ivars to in-construction classes.
  11. if (!(cls->data()->flags & RW_CONSTRUCTING)) {
  12. return NO;
  13. }
  14. ...
  15. }

调用objc_registerClassPair函数,会对flags进行标记

  1. void objc_registerClassPair(Class cls)
  2. {
  3. ...
  4. // Clear "under construction" bit, set "done constructing" bit
  5. cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
  6. cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
  7. ...
  8. }

8. [self class]和[super class]的区别?

[self class]:其中self为参数名,本质调用objc_msgSend,消息接收者为self,方法编号为class

[super class]:其中super为关键字,本质调用objc_msgSendSuper,消息接收者为self,方法编号为class。运行时,查看汇编代码,实际上调用的objc_msgSendSuper2

8.1 案例分析

  1. #import <Foundation/Foundation.h>
  2. @interface LGPerson : NSObject
  3. @end
  4. @implementation LGPerson
  5. - (instancetype)init {
  6. self = [super init];
  7. if (self) {
  8. NSLog(@"init:%@ - %@",[self class],[super class]);
  9. }
  10. return self;
  11. }
  12. @end
  13. -------------------------
  14. initLGPerson - LGPerson
  • [self class][super class],二者的打印结果都是LGPerson

因为二者的消息接收者都是当前self,而方法编号都是class

而二者的区别,self调用objc_msgSendsuper调用objc_msgSendSuper

它们最终调用的都是NSObject中的class方法,消息接收者都是self当前实例对象,只是objc_msgSendSuper直接查找父类方法,比使用objc_msgSend更快

8.2 [self class]的本质

  1. (Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))

8.3 [super class]的本质

  1. ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("class"))

8.4 __rw_objc_super结构体

  1. struct __rw_objc_super {
  2. struct objc_object *object;
  3. struct objc_object *superClass;
  4. __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
  5. };

8.5 objc_msgSendSuper2

objc源码中,打开objc-msg-arm64.s文件

  1. ENTRY _objc_msgSendSuper
  2. UNWIND _objc_msgSendSuper, NoFrame
  3. ldp p0, p16, [x0] // p0 = real receiver, p16 = class
  4. b L_objc_msgSendSuper2_body
  5. END_ENTRY _objc_msgSendSuper

运行时,实际调用的是objc_msgSendSuper2,目的是objc_msgSendSuper升级后的过度

9. 实例方法的调用?

9.1 实例方法的两种调用方式

创建LGPerson

  1. #import <Foundation/Foundation.h>
  2. @interface LGPerson : NSObject
  3. @property (nonatomic, copy) NSString *lgName;
  4. - (void)say1;
  5. @end
  6. @implementation LGPerson
  7. - (void)say1{
  8. NSLog(@"%@ : %s",self,__func__);
  9. }
  10. @end

viewDidLoad方法中,使用两种不同方式,对其进行调用

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. //方式一
  4. LGPerson *p = [[LGPerson alloc] init];
  5. [p say1];
  6. //方式二
  7. Class cls = [LGPerson class];
  8. void *ptr = &cls;
  9. [(__bridge id)ptr say1];
  10. }
  11. -------------------------
  12. <LGPerson: 0x281e546d0> : -[LGPerson say1]
  13. <LGPerson: 0x16b141aa0> : -[LGPerson say1]

两种方式全部调用成功

方式一,常规写法,调用实例对象的say1方法,进入objc_msgSend流程,拿到实例对象所属isa,然后进行方法查找

方式二,将类对象地址,赋值给ptr指针,然后将其桥接成OCid类型,进行say1方法的调用。ptr指针等同于实例对象所属isa,然后进行方法查找,所以也能调用成功

9.2 实例方法中打印属性

修改say1方法

  1. - (void)say1{
  2. NSLog(@"%@ : %s : %@",self,__func__,self.lgName);
  3. }

main函数中,使用两种不同方式,对其进行调用

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. //方式一
  4. LGPerson *p = [[LGPerson alloc] init];
  5. [p say1];
  6. //方式二
  7. Class cls = [LGPerson class];
  8. void *ptr = &cls;
  9. [(__bridge id)ptr say1];
  10. }
  11. return 0;
  12. }
  13. -------------------------
  14. <LGPerson: 0x281e546d0> : -[LGPerson say1] : (null)
  15. <LGPerson: 0x16b141aa0> : -[LGPerson say1] : <LGPerson: 0x281e546d0>

两种方式,打印出lgName的属性值各不相同。实例对象与类对象最大的区别,实例对象使用malloc开辟内存空间,结构中包含isa + 成员变量。所以实例对象使用首地址 + 偏移值的方式,在自己开辟的堆空间中,偏移isa指针的8字节,即可找到lgName属性并对其打印

方式一,由于lgName没有赋值,打印结果为null

方式二,ptr为类对象的指针,在viewDidLoad的函数调用栈中,所以ptr使用首地址 + 偏移值的方式,只能找到函数调用栈中ptr的上一个元素,即:LGPerson的实例对象p

9.3 为什么上一个元素是LGPerson的实例对象?

栈:是一种具有特殊的访问方式的存储空间,具有后进先出的特性(Last In Out Firt,LIFO

ARM64中,栈的开口方向是向下的,由高地址到低地址。对栈的操作是16字节对齐

打印函数调用栈中的全部元素

  1. void *fp = (void *)&self;
  2. void *sp = ptr;
  3. long count = (fp - sp) / 0x8;
  4. for (long i = 0; i <= count; i++) {
  5. void *address = fp - i * 0x8;
  6. if(i==1){
  7. NSLog(@"%p : %s",address, *(char **)address);
  8. continue;
  9. }
  10. NSLog(@"%p : %@",address, *(void **)address);
  11. }
  12. -------------------------
  13. 0x16dd5dac8 : <ViewController: 0x102607340>
  14. 0x16dd5dac0 : viewDidLoad
  15. 0x16dd5dab8 : ViewController
  16. 0x16dd5dab0 : <ViewController: 0x102607340>
  17. 0x16dd5daa8 : <LGPerson: 0x282808ca0>
  18. 0x16dd5daa0 : LGPerson
  • 允许压栈进来的对象,包含方法的参数,viewDidLoad方法的两个隐式参数self_cmd

  • 调用[super viewDidLoad]方法,需要在当前函数调用栈中,创建__rw_objc_super结构体的临时变量,然后传入super方法。结构体的成员变量,分别包含objectsuperClass

  • 方法中定义的局部变量,例如:LGPerson的实例变量pLGPerson类对象的ptr指针

从栈底到栈顶,每8字节打印一个元素。最后一个元素ptr指针,使用ptr + 8字节偏移,找到的是LGPerson的实例对象p

9.4 为什么superClass传入的是ViewController

查看[super viewDidLoad]方法的汇编代码
image.png

  • x0寄存器中存储结构体,其中super_class成员变量,存储的不是父类UIViewController,而是当前类对象ViewController

objc源码中,搜索objc_msgSendSuper2

  1. #if __OBJC2__
  2. // objc_msgSendSuper2() takes the current search class, not its superclass.
  3. OBJC_EXPORT id _Nullable
  4. objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
  5. OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);
  • 注释:接受当前的搜索类,而不是它的父类

查看汇编的源码
image.png

  • cls为要搜索的类的子类

10. Runtime是如何实现weak的,为什么可以⾃动置nil?

  1. 通过SideTable找到我们的weak_table
  2. weak_table根据referent找到或者创建weak_entry_t
  3. 然后append_referrer(entry, referrer)将我的新弱引⽤的对象加进去entry
  4. 最后weak_entry_insertentry加⼊到我们的weak_table

image.png