前言:x/4gx 查看内存,p/x 打印对象所在类的内存地址IMG_5465.JPG

isa走位图(分解)

  • 对象(isa) -> 类(isa)-> 元类
  • 继承链:EMPerson p,EMTeacher t; p和t仅仅是两个指针,没有关联。EMTeacher和EMPerson是类的继承
  • EMTeacher(superclass)-> EMPerson(superclass)-> NSObject(superclass)-> nil 无中生有-_-
  • 元类(isa)-> 根源类(isa)-> 根源类(NSObject的元类)
  • EMPerson元类继承于根源类,根源类继承于NSObject,NSObject继承于nil

    ro与rw

  • ro -> cleanMemory,在编译即确定的内存空间,只读,加载后不会改变内容的空间

  • rw -> dirtyMemory,rw是运行时结构,可读可写,可以向类中添加属性、方法等,在运行时会改变的内存。
  • rwe,相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正的改变他们的内容,所以为避免资源的消耗就有了rwe
  • 运行时,如果需要动态向类中添加方法协议等,会创建rwe,并将ro的数据优先attach到rwe中,在读取时会优先返回rwe的数据,如果rwe没有被初始化,则返回ro的数据
  • rw中包括ro和rwe,rw是dirty memory,ro是clean memory;为了让dirty memory占用更少的空间,把rw中可变的部分抽取出来为rwe
  • clean memory越多越好,dirty memory越少越好,因为iOS系统底层虚拟内存的原因,内存不足时会把一部分内存回收掉,后面需要再次使用时从硬盘中加载出来即swap机制,clean memory是可以从硬盘中重新加载的内存,iOS中的mach-o文件动态库都属于此类型。dirty memory是运行时产生的数据,这部分数据不能从硬盘中重新加载所以一直占据内存,当系统物理内存紧张的时候,会回收掉clean memory内存。

    内存ro属性

    首先建立两个类,EMPerson、EMTeacher,其中EMTeacher继承于EMPerson。断点调试,lldb isa -> class_data_bits_t -> data -> class_rw_t ``` (lldb) p/x EMPerson.class (Class) $0 = 0x0000000100008670 EMPerson (lldb) p (class_data_bits_t )0x0000000100008690 (class_data_bits_t ) $1 = 0x0000000100008690 (lldb) p $1->data() (class_rw_t ) $2 = 0x0000000108dc5fe0 (lldb) p $2 (class_rw_t) $3 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std::__1::atomic = {
    1. Value = 4295000504
    } } firstSubclass = nil ///<< 这里为什么是nil,不是EMTeacher呢??? nextSiblingClass = NSUUID }
  1. - 类的加载形式是懒加载
  2. firstSubClassnil,执行p/x EMTeacher.class后再执行p *$2,可以看到firstSubClassEMTeacher,因为类是懒加载的,所以开始为nil

(lldb) p/x EMTeacher.class (Class) $4 = 0x00000001000086e8 EMTeacher (lldb) p *$2 (class_rw_t) $5 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std::__1::atomic = { Value = 4295000504 } } firstSubclass = EMTeacher nextSiblingClass = NSUUID }

  1. 通过源码,我们可以看到`class_rw_t`会进行`ro`相关的`get``set`方法,所以可以对$5进一步查看得到一个`class_ro_t`结构

(lldb) p $5.ro() (const class_ro_t ) $6 = 0x00000001000081b8 (lldb) p $6 (const class_ro_t) $7 = { flags = 0 instanceStart = 8 instanceSize = 24 reserved = 0 = { ivarLayout = 0x0000000000000000 nonMetaclass = nil } name = { std::__1::atomic = “EMPerson” { Value = 0x0000000100003e94 “EMPerson” } } baseMethodList = 0x0000000100008200 baseProtocols = nil ivars = 0x0000000100008280 weakIvarLayout = 0x0000000000000000 baseProperties = 0x00000001000082c8 _swiftMetadataInitializer_NEVER_USE = {} }

  1. 这里可以查看`ivars`结构,$9是一个数组,C++底层数组有get方法<br />tips
  2. - M1电脑执行`$9.get(0`)即可,不需要加big,而x86架构的电脑需要加big,所以big应该代表大端模式

(lldb) p $7.ivars (const ivar_list_t const) $8 = 0x0000000100008280 (lldb) p $8 (const ivar_list_t) $9 = { entsize_list_tt = (entsizeAndFlags = 32, count = 2) } (lldb) p $9.get(0).big error: :1:11: no member named ‘big’ in ‘ivar_t’ $9.get(0).big ~~~~~ ^ (lldb) p $9.get(0) (ivar_t) $10 = { offset = 0x00000001000085d0 name = 0x0000000100003f13 “_age” type = 0x0000000100003f6c “i” alignment_raw = 2 size = 4 } (lldb)

  1. 通过`get(i)`即看到了我们定义的成员变量
  2. ```shell
  3. (lldb) p $9.get(0)
  4. (ivar_t) $10 = {
  5. offset = 0x00000001000085d0
  6. name = 0x0000000100003f13 "_age"
  7. type = 0x0000000100003f6c "i"
  8. alignment_raw = 2
  9. size = 4
  10. }
  11. (lldb) p $9.get(1)
  12. (ivar_t) $11 = {
  13. offset = 0x00000001000085d8
  14. name = 0x0000000100003f18 "_name"
  15. type = 0x0000000100003f45 "@\"NSString\""
  16. alignment_raw = 3
  17. size = 8
  18. }

变量种类

  • 成员变量 - NSString * _name;
  • 属性 - @property nickName;
  • 实例变量 - NSObject *_obj;

    .cpp文件分析

    建立person类,通过clang -rewrite-objc main.m -o main.cpp编译为cpp文件
    cpp文件中,属性被编译为带_的变量
    1. struct LGPerson_IMPL {
    2. struct NSObject_IMPL NSObject_IVARS;
    3. NSString *hobby;
    4. int a;
    5. NSObject *objc;
    6. NSString *_nickName;
    7. NSString *_acnickName;
    8. NSString *_nnickName;
    9. NSString *_anickName;
    10. NSString *_name;
    11. NSString *_aname;
    12. };
    _method_list_t中的字符串@16@0:8定义如下:
  1. @, 代表返回类型是id
  2. 16,代表所占用的内存大小
  3. @,代表隐藏参数(self,_cmd)中的id self
  4. 0, 代表从0号位置开始(这里指上面的隐藏参数)
  5. :,代表隐藏参数(self,_cmd)中SEL
  6. 8, 从8号位置开始(这里指上面的SEL)
    1. static struct /*_method_list_t*/ {
    2. unsigned int entsize; // sizeof(struct _objc_method)
    3. unsigned int method_count;
    4. struct _objc_method method_list[24];
    5. } _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    6. sizeof(_objc_method),
    7. 24,
    8. {{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
    9. {(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
    10. {(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},
    11. {(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},
    12. {(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},
    13. {(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},
    14. {(struct objc_selector *)"anickName", "@16@0:8", (void *)_I_LGPerson_anickName},
    15. {(struct objc_selector *)"setAnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAnickName_},
    16. {(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
    17. {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
    18. {(struct objc_selector *)"aname", "@16@0:8", (void *)_I_LGPerson_aname},
    19. {(struct objc_selector *)"setAname:", "v24@0:8@16", (void *)_I_LGPerson_setAname_},
    20. {(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
    21. {(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
    22. {(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},
    23. {(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},
    24. {(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},
    25. {(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},
    26. {(struct objc_selector *)"anickName", "@16@0:8", (void *)_I_LGPerson_anickName},
    27. {(struct objc_selector *)"setAnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAnickName_},
    28. {(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
    29. {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
    30. {(struct objc_selector *)"aname", "@16@0:8", (void *)_I_LGPerson_aname},
    31. {(struct objc_selector *)"setAname:", "v24@0:8@16", (void *)_I_LGPerson_setAname_}}
    32. };

setter底层原理

  • 使用strong修饰符时,在底层被编译为ivars+地址偏移
  • 使用copy修饰时,在底层被编译为setProperty

我们观察到不同属性的**set**方法也有区别,其中**nikeName**调用了**setProperty**方法,而**nnikeName**还是普地址平移赋值
image.png

  • 在编译器,LLVM帮助我们对copy修饰的属性做了处理
    • 为什么该处理在LLVM库?setName相当于根据符号找imp,如果在运行时处理,压力很大
    • copy方法会有深浅拷贝,与strong修饰不同

未命名文件 (3).jpg

方法存储

  • 对象方法存储在类中,类方法存储在元类中

    • 获取类方法,本质就是获取类的元类的对象方法 ```objectivec Method class_getClassMethod(Class cls, SEL sel) { if (!cls || !sel) return nil;

      return class_getInstanceMethod(cls->getMeta(), sel); }

Class getMeta() { if (isMetaClassMaybeUnrealized()) return (Class)this; else return this->ISA(); }

  1. <a name="PUxs4"></a>
  2. ## 面试题
  3. ```cpp
  4. void lgKindofDemo(void){
  5. BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
  6. BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
  7. BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
  8. BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
  9. NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
  10. BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
  11. BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
  12. BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
  13. BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
  14. NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
  15. }
  16. 运行结果
  17. re1 :1
  18. re2 :0
  19. re3 :0
  20. re4 :0
  21. re5 :1
  22. re6 :1
  23. re7 :1
  24. re8 :1

分析

isKindOfClass源码调用如下

  1. ///<< self的isa即元类,循环查找父类比较
  2. + (BOOL)isKindOfClass:(Class)cls {
  3. for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
  4. if (tcls == cls) return YES;
  5. }
  6. return NO;
  7. }
  8. ///<< 类与循环查找父类比较
  9. - (BOOL)isKindOfClass:(Class)cls {
  10. for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
  11. if (tcls == cls) return YES;
  12. }
  13. return NO;
  14. }

isMemberOfClass源码如下

  1. ///<< self类的元类与cls比较
  2. + (BOOL)isMemberOfClass:(Class)cls {
  3. return self->ISA() == cls;
  4. }
  5. ///<< self的类与cls比较
  6. - (BOOL)isMemberOfClass:(Class)cls {
  7. return [self class] == cls;
  8. }

补充

  • 对象的isa与类的isa不一样,因为对象isa经常存储除类信息以外的数据。
  • 而类的isa经常与元类的isa一样,类的isa和元类的isa很少存储其它信息,基本只有其类信息。

IMG_7531.PNG