前言:x/4gx 查看内存,p/x 打印对象所在类的内存地址
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= {
} } firstSubclass = nil ///<< 这里为什么是nil,不是EMTeacher呢??? nextSiblingClass = NSUUID }Value = 4295000504
- 类的加载形式是懒加载firstSubClass为nil,执行p/x EMTeacher.class后再执行p *$2,可以看到firstSubClass为EMTeacher,因为类是懒加载的,所以开始为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
通过源码,我们可以看到`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
这里可以查看`ivars`结构,$9是一个数组,C++底层数组有get方法<br />tips:- 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~~~~~ ^
(lldb) p $9.get(0)
(ivar_t) $10 = {
offset = 0x00000001000085d0
name = 0x0000000100003f13 “_age”
type = 0x0000000100003f6c “i”
alignment_raw = 2
size = 4
}
(lldb)
通过`get(i)`即看到了我们定义的成员变量```shell(lldb) p $9.get(0)(ivar_t) $10 = {offset = 0x00000001000085d0name = 0x0000000100003f13 "_age"type = 0x0000000100003f6c "i"alignment_raw = 2size = 4}(lldb) p $9.get(1)(ivar_t) $11 = {offset = 0x00000001000085d8name = 0x0000000100003f18 "_name"type = 0x0000000100003f45 "@\"NSString\""alignment_raw = 3size = 8}
变量种类
- 成员变量 - NSString * _name;
- 属性 - @property nickName;
- 实例变量 - NSObject *_obj;
.cpp文件分析
建立person类,通过clang -rewrite-objc main.m -o main.cpp编译为cpp文件
在cpp文件中,属性被编译为带_的变量struct LGPerson_IMPL {struct NSObject_IMPL NSObject_IVARS;NSString *hobby;int a;NSObject *objc;NSString *_nickName;NSString *_acnickName;NSString *_nnickName;NSString *_anickName;NSString *_name;NSString *_aname;};
_method_list_t中的字符串@16@0:8定义如下:
- @, 代表返回类型是id
- 16,代表所占用的内存大小
- @,代表隐藏参数(self,_cmd)中的id self
- 0, 代表从0号位置开始(这里指上面的隐藏参数)
- :,代表隐藏参数(self,_cmd)中SEL
- 8, 从8号位置开始(这里指上面的SEL)
static struct /*_method_list_t*/ {unsigned int entsize; // sizeof(struct _objc_method)unsigned int method_count;struct _objc_method method_list[24];} _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),24,{{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},{(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},{(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},{(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},{(struct objc_selector *)"anickName", "@16@0:8", (void *)_I_LGPerson_anickName},{(struct objc_selector *)"setAnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAnickName_},{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},{(struct objc_selector *)"aname", "@16@0:8", (void *)_I_LGPerson_aname},{(struct objc_selector *)"setAname:", "v24@0:8@16", (void *)_I_LGPerson_setAname_},{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},{(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},{(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},{(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},{(struct objc_selector *)"anickName", "@16@0:8", (void *)_I_LGPerson_anickName},{(struct objc_selector *)"setAnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAnickName_},{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},{(struct objc_selector *)"aname", "@16@0:8", (void *)_I_LGPerson_aname},{(struct objc_selector *)"setAname:", "v24@0:8@16", (void *)_I_LGPerson_setAname_}}};
setter底层原理
- 使用strong修饰符时,在底层被编译为ivars+地址偏移
- 使用copy修饰时,在底层被编译为setProperty
我们观察到不同属性的**set**方法也有区别,其中**nikeName**调用了**setProperty**方法,而**nnikeName**还是普地址平移赋值
- 在编译器,LLVM帮助我们对copy修饰的属性做了处理
- 为什么该处理在LLVM库?setName相当于根据符号找imp,如果在运行时处理,压力很大
- copy方法会有深浅拷贝,与strong修饰不同
方法存储
对象方法存储在类中,类方法存储在元类中
获取类方法,本质就是获取类的元类的对象方法 ```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(); }
<a name="PUxs4"></a>## 面试题```cppvoid lgKindofDemo(void){BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);}运行结果re1 :1re2 :0re3 :0re4 :0re5 :1re6 :1re7 :1re8 :1
分析
isKindOfClass源码调用如下
///<< self的isa即元类,循环查找父类比较+ (BOOL)isKindOfClass:(Class)cls {for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {if (tcls == cls) return YES;}return NO;}///<< 类与循环查找父类比较- (BOOL)isKindOfClass:(Class)cls {for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {if (tcls == cls) return YES;}return NO;}
isMemberOfClass源码如下
///<< self类的元类与cls比较+ (BOOL)isMemberOfClass:(Class)cls {return self->ISA() == cls;}///<< self的类与cls比较- (BOOL)isMemberOfClass:(Class)cls {return [self class] == cls;}
补充
- 对象的isa与类的isa不一样,因为对象isa经常存储除类信息以外的数据。
- 而类的isa经常与元类的isa一样,类的isa和元类的isa很少存储其它信息,基本只有其类信息。

