之前遗留问题解决:
-
1.callAlloc中
if (fastpath(cls->canAllocFast()))
方法不走的原因
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
if (fastpath(cls->canAllocFast())) {
...
} else {
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
}
点进去canAllocFast的方法可以查看
bool canAllocFast() {
assert(!isFuture());
return bits.canAllocFast();
}
->
#if FAST_ALLOC
...
#else
bool canAllocFast() {
return false;
}
#endif
在canAllocFast
方法中可以看到有一个宏定义FAST_ALLOC
。
在全局搜索宏定义FAST_ALLOC
,发现FAST_ALLOC
也是在一个宏定义中
#if !__LP64__
...
#elif 1
...
#else
...
#define FAST_ALLOC (1UL<<2)
#endif
可以发现此宏定义只走中间#elif 1
的代码,回到上面判断FAST_ALLOC
方法会发现,FAST_ALLOC
不会走,只走bool canAllocFast() { return false; }
方法,继续往上查看,if (fastpath(cls->canAllocFast())) {} else {}
只走else{}方法。
流程图如下:
-
2.类 & 元类的创建时机
1.通过lldb进行调试,可查看
2.通过 ‘command + b’编译的可执行文件,通过 ‘machoView‘进行查看,发现已经存在相应的类。
-
3.指针 & 内存偏移
1.普通指针
int a = 10;
是值拷贝,原因是在常量区已经存在一个为10
的常量,int a = 10
,是将常量 10
拷贝给了 a
,是值拷贝,同理int b = 10
也一样是值拷贝。
打印,发现 a
和b
的值是一样的,但是地址不一样,如下代码:
int a = 10; //
int b = 10; //
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);
打印结果:
10 -- 0x7ffeefbff4fc
10 -- 0x7ffeefbff4f8
2. 指针拷贝
Person *p1 = [LGPerson alloc];
Person *p2 = [LGPerson alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
打印结果:
-- 0x7ffeefbff4f0(指向 “指针内存”的指针 -> 及 指针的指针)
-- 0x7ffeefbff4e8
如上代码,可以发现,p1
和p2
指向的内存空间、指针的指针都是不一样的,说明是两个全新的值。
普通指针 和 指针拷贝 如图:
3.内存偏移
数组指针:
创建一个数组,如下代码:
int a[4] = {1,2,3,4};
int *b = a;
NSLog(@"%p - %p - %p",&a,&a[0],&a[1]);
打印:
打印结果:
0x7ffeefbff510(&a 的指针) - 0x7ffeefbff510( &a[0]的指针) - 0x7ffeefbff514(&a[1]的指针)
打印内存空间:
(lldb) x/4gx 0x7ffeefbff510
0x7ffeefbff510: 0x0000000200000001 0x0000000400000003
0x7ffeefbff520: 0x00007ffeefbff540 0x8cd419990ace003a
从结果中可以看到:
&a
和&a[0]
的指针地址是相同的。
0x0000000200000001
值就是0x7ffeefbff510
所指向的空间,也就是 元素的首地址就是第一个元素的地址
结论:地址中的首地址是代表着这个对象的地址
第一个元素地址0x7ffeefbff510
和 第二个元素地址0x7ffeefbff514
相差4 也就是 int的所占的4字节。
那么打印如下代码会发现 可以通过地址偏移,打印出不同的元素:
NSLog(@"%p - %p - %p",b,b+1,b+2);
打印结果:
0x7ffeefbff510(&a[0]) - 0x7ffeefbff514(&a[1]) - 0x7ffeefbff518(&a[2])
还以可通过 对地址取值,来获取对应的值。
p *b+1
(int) $0 = 2
p *b+2
(int) $1 = 3
*b+1
,中b+1
是b
偏移地址,* + 地址
,是对地址所对应的内存空间进行取值。
类
问题:类的结构是什么?
探索结构
创建一个person对象,并且获取到person的类 pClass
Person *person = [Person alloc];
Class pClass = object_getClass(person);
`lldb调试`
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389(Class ISA;) 0x0000000100b37140(Class superclass;)
0x1000023c0: 0x00000001003da290(cache_t cache;) 0x0000000000000000
(lldb) po 0x0000000100b37140
NSObject
输出第二个内存空间发现是NSObject
,继续输出第三个发现输出不了。
(lldb) po 0x00000001003da290
4299006608
(lldb) p 0x00000001003da290
(long) $7 = 4299006608
下面通过对源码的探索,研究 类 的结构。
2.对源码探索,研究类的属性
通过源码对Class pClass = object_getClass(person);
中的Class
进行跟踪发下:
typedef struct objc_class *Class;
发现Class
是objc_class
的一个对象,继续对objc_class
点击查看发现如下代码:
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 不是8
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
到这发现objc_class
是继承 objc_object
的一种结构体类型
,也就是类也是继承与对象的,也验证了那句“万物皆对象”的说法。
注意:由于源码有 新 和 旧两种,
objc_class
的结构体类型也有种结构,上面所展示的是 新 的结构。
继续通过源码查找,发现objc_object
的结构如下:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
那么
NSObject
又是什么呢?
@interface NSObject {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
NSObject
也是对objc_object
结构的一种仿写。
问题1:
Class ISA
为什么isa是一个Class类型呢?
答:1.万物皆对象,isa是可以由Class接收的。
2.早期调用isa是用来返回类的,后面是通过 nonpointer区分 纯净isa和有内容的isa。
3.通过源码查找isa如下代码return (Class)(isa.bits & ISA_MASK);
进行class强转了。
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
3.研究objc_object
的内部结构,查找 实例方法
在上面知道了objc_object
的结构是这样的:
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 不是8
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
...
}
Class ISA
-
Class superclass
点击Class,发现结构是typedef struct objc_class *Class
,这里的是*
Class,也就是一个指针,也就是一个结构体指针,指针占8字节。 -
cache_t cache
是一个结构体,不是结构体指针
,是根据全部成员所占的内存。
struct bucket_t *_buckets;
是结构体,查表得是8字节
mask_t _mask;
查看typedef uint32_t mask_t;
是uint32_t
(int类型),占4字节。
同理mask_t _occupied;
也是4字节。
得出,cache_t占16字节。
struct cache_t {
struct bucket_t *_buckets; // 8
mask_t _mask; // 4
mask_t _occupied; // 4
...
}
类定义的一些 属性 和 方法:
@interface Person : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
Person中定义nickName属性,是在bits中,下面要读取bits中的数据。
Person *person = [Person alloc];
Class pClass = object_getClass(person);
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1280 0x0000000000000000
通过objc_class
中的结构得知0x001d800100002389
是isa,0x0000000100afe140
是superClass,0x00000001003a1280
是cache,则0x0000000000000000
是bits。
为什么bits中存有属性等数据呢?
查看源码发现,class_rw_t
中的method_array_t
、property_array_t
、protocol_array_t
是通过return bits.data();
返回的。
class_rw_t *data() {
return bits.data();
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
...
}
但是bits无法读出,只能通过内存偏移去查找。
从之前的了解,得知 isa
和superclass
都是8字节,cache_t cache
是16字节。
下面进行lldb调试
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1280 0x0000000000000000
(lldb) po 0x1000023d0
objc[3229]: Attempt to use unknown class 0x1018576c0.
4294976464
(lldb) p 0x1000023d0
(long) $2 = 4294976464
po 0x1000023d0
是内存0x1000023b0
偏移32,得到的bits的地址,但是不能输出对象,p 0x1000023d0
也不能输出地址。
因为bits的类型是class_data_bits_t
,进行强转,获得bits的指针
(lldb) p (class_data_bits_t *)0x1000023d0
(class_data_bits_t *) $3 = 0x00000001000023d0
上面提到class_rw_t
中存在的属性、方法、协议是通过return bits.data();
返回的。
(lldb) po $3->data()
0x00000001018576c0
(lldb) p $3->data()
(class_rw_t *) $5 = 0x00000001018576c0
得到class_rw_t *
的指针$5
,对class_rw_t
进行取值 p *$5
(lldb) p *$5
(class_rw_t) $6 = {
flags = 2148139008
version = 0
ro = 0x0000000100002308
methods = {
list_array_tt = {
= {
list = 0x0000000100002240
arrayAndFlag = 4294976064
}
}
}
properties = {
list_array_tt = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
protocols = {
list_array_tt = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSDate
demangledName = 0x0000000000000000
}
发现存在 properties
,属性应该会在这个里面,输出properties
(lldb) p $6.properties
(property_array_t) $7 = {
list_array_tt = {
= {
list = 0x00000001000022f0
arrayAndFlag = 4294976240
}
}
}
探究property_array_t
发现是一个 二位数组(entsize_list_tt),那么代表着是可以遍历输出。
struct property_list_t : entsize_list_tt {
};
但是输出 p $7(0)
等是没有输出
(lldb) p $7(0)
error: type 'property_array_t' does not provide a call operator
应为没有得到结果,只能查看list
。
(lldb) p $7.list
(property_list_t *) $8 = 0x00000001000022f0
查看struct entsize_list_tt
的源码,发现 有个Element first;
就是第一个元素,试着输出
struct entsize_list_tt
{
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
...
}
输出第一元素:
(lldb) p $8->first
(property_t) $9 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
发现是存在nickName
这个属性的,但是还有一个hobby
属性,是否也可以遍历?
(lldb) p $8.get[1]
error: member reference type 'property_list_t *' is a pointer; did you mean to use '->'?
error: reference to non-static member function must be called
遍历之后发现是不存在的。
留坑!! 为什么属性和会在 ro 中?等后面进行补齐!
其实具体的属性、方法、协议是存在 ro
中。
输出 ro
(lldb) p $6.ro
(const class_ro_t *) $10 = 0x0000000100002308
(lldb) p *$10
(const class_ro_t) $11 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001f89 "\x02"
name = 0x0000000100001f80 "LGPerson"
baseMethodList = 0x0000000100002240
baseProtocols = 0x0000000000000000
ivars = 0x00000001000022a8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000022f0
}
发现存在baseProperties
,试着查看。
(lldb) p $11.baseProperties
(property_list_t *const) $12 = 0x00000001000022f0
(lldb) p *$12
(property_list_t) $13 = {
entsize_list_tt = {
entsizeAndFlags = 16
count = 1
first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
}
}
结果是存在nickName
的,但是 count = 1
代表着,元素只有一个,那hobby
存在那?
为什么
hobby
不在baseProperties中呢?
结果是在 ivars
!
(lldb) p *$11.ivars
(const ivar_list_t) $15 = {
entsize_list_tt = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x0000000100002378
name = 0x0000000100001e64 "hobby"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $15.get(0)
(ivar_t) $16 = {
offset = 0x0000000100002378
name = 0x0000000100001e64 "hobby"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $15.get(1)
(ivar_t) $17 = {
offset = 0x0000000100002380
name = 0x0000000100001e6a "_nickName"
type = 0x0000000100001fa6 "@\"NSString\""
alignment_raw = 3
size = 8
}
ivars
中 count = 2
代表存在两个元素,遍历之后如上 成员变量hobby
和属性nickName
都存在。
有一条可以得知,属性 在底层会生成一个 带下划线"_"的成员变量,也就是上面的_nickName
成员变量。
3.对源码探索,研究类的方法
2中进行了 ro 内容的输出,可以发现存在一个 baseMethodList
,方法会存在这个里面吗?,继续探究
(lldb) p *$10
(const class_ro_t) $11 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100001f89 "\x02"
name = 0x0000000100001f80 "LGPerson"
baseMethodList = 0x0000000100002240
baseProtocols = 0x0000000000000000
ivars = 0x00000001000022a8
weakIvarLayout = 0x0000000000000000
baseProperties = 0x00000001000022f0
}
输出:baseMethodList
(lldb) p $10.baseMethodList
(method_list_t *const) $19 = 0x0000000100002240
Fix-it applied, fixed expression was:
$10->baseMethodList
(lldb) p *$19
(method_list_t) $20 = {
entsize_list_tt = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayHello"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
}
}
是存在"sayHello"
这个方法的,同时也可以查看method_t
的源码
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
...
};
发现验证了方法中有 name
(方法名)、types
(签名)、imp
(指针函数,指向方法的实现)。
继续打印剩余的方法
nickName
的get方法
(lldb) p $20.get(1)
(method_t) $22 = {
name = "nickName"
types = 0x0000000100001f93 "@16@0:8"
imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
nickName
的set方法
(lldb) p $20.get(2)
(method_t) $23 = {
name = "setNickName:"
types = 0x0000000100001f9b "v24@0:8@16"
imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}
".cxx_destruct"
是系统默认添加的 c++
的方法
(lldb) p $20.get(3)
(method_t) $24 = {
name = ".cxx_destruct"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
突然发现+ (void)sayHappy;
没有实现!!!!
在最开始探究类时候,创建一个一对象person,并通过Class pClass = object_getClass(person);
获取到类pClass
,后面研究的都是在pClass
中,并且获得了 实例方法。
也就是说,实例方法是存在 类
中,那么 类方法
是不是存在元类中呢?
查找类方法
按照上面的方法继续查找
获取pClass
的内存段
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100afe140
0x1000023c0: 0x00000001003a1280 0x0000000000000000
通过isa & mask
获取到 pClass的元类,并且打印内存段
(lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8
(long) $1 = 0x0000000100002388
(lldb) x/4gx 0x0000000100002388
0x100002388: 0x001d800100afe0f1 0x0000000100afe0f0
0x100002398: 0x0000000101076da0 0x0000000100000003
内存偏移,获取到class_data_bits_t
。0x100002388
->(偏移32字节)0x1000023a8
(lldb) p (class_data_bits_t *)0x1000023a8
(class_data_bits_t *) $2 = 0x00000001000023a8
调用函数data()
获取class_rw_t
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101076d00
读取ro
(lldb) p $3->ro
(const class_ro_t *) $4 = 0x00000001000021f8
(lldb) p *$4
(const class_ro_t) $5 = {
flags = 389
instanceStart = 40
instanceSize = 40
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100001f80 "LGPerson"
baseMethodList = 0x00000001000021d8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
}
继续读取baseMethodList
(lldb) p $5.baseMethodList
(method_list_t *const) $6 = 0x00000001000021d8
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt = {
entsizeAndFlags = 26
count = 1
first = {
name = "sayHappy"
types = 0x0000000100001f8b "v16@0:8"
imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
}
}
}
发现找到"sayHappy"
的方法。
也验证了 类方法 是存在 元类 中
总结
1.属性在
objc_class
->class_data_bits_t bits
->class_rw_t* data()
->class_ro_t * ro
->property_list_t *baseProperties
中,成员变量存在class_ro_t * ro
->ivar_list_t * ivars;
中,并且属性
会在ivars
中生成一个 带下划线“_”的 成员变量。即@property (nonatomic, copy) NSString *nickName;
会在ivars中生成_ nickName
。2.实例方法存在类中。
流程:objc_class
->class_data_bits_t bits
->class_rw_t* data()
->class_ro_t * ro
->method_list_t * baseMethodList;
3.类方法存在元类中。