前言: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 = 0x00000001000085d0
name = 0x0000000100003f13 "_age"
type = 0x0000000100003f6c "i"
alignment_raw = 2
size = 4
}
(lldb) p $9.get(1)
(ivar_t) $11 = {
offset = 0x00000001000085d8
name = 0x0000000100003f18 "_name"
type = 0x0000000100003f45 "@\"NSString\""
alignment_raw = 3
size = 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>
## 面试题
```cpp
void 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 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :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很少存储其它信息,基本只有其类信息。