一、指针
1、普通指针
首先我们先看看下面代码
int a = 10;
int b = 10;
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);
输出的是
2019-12-21 10:18:35.828144+0800 BY_Class[58235:914046] 10 -- 0x7ffeef311164
2019-12-21 10:18:35.828362+0800 BY_Class[58235:914046] 10 -- 0x7ffeef311160
对于这些int、NSString这些基本数据类型的指针是普通指针属于值拷贝,像上面的int a = 10,是将10拷贝到a的内存地址上
2、对象指针
看看下面代码
BYPerson *p1 = [BYPerson alloc];
BYPerson *p2 = [BYPerson alloc];
BYPerson *p3 = p1;
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
NSLog(@"%@ -- %p",p3,&p3);
输出
2019-12-21 10:18:35.828517+0800 BY_Class[58235:914046] <BYPerson: 0x600002a7d880> -- 0x7ffeef311158
2019-12-21 10:18:35.828644+0800 BY_Class[58235:914046] <BYPerson: 0x600002a7d8e0> -- 0x7ffeef311150
2019-12-21 10:18:35.828753+0800 BY_Class[58235:914046] <BYPerson: 0x600002a7d880> -- 0x7ffeef311148
- 会发现p1和p2的地址内里面内容都不一样,是因为每次alloc时候系统都会给对象单独分配一个内存空间,
- p1指向这个对象时候就是把该对象的内存地址copy过来存起来,因为每个对象的内存地址都不一样,所以p1和p2里面的地址都不一样
- 但是p1、p2、p3确都是独立的指针,所以自己本身地址是不一样的
3、数组指针
现象
代码:
int c[4] = {1,2,3,4};
BYNSLog(@"%p -- %p -- %p",&c,&c[0],&c[1]);
打印
BY打印: 0x7ffee6716180 -- 0x7ffee6716180 -- 0x7ffee6716184
会发现c的地址和c[0]的地址是一样的,这是因为数组的地址就是数组第一个元素的地址,并且后面元素地址比前一个大4个字节,因为数组里面元素是4个字节
验证:
我们在输出代码的后面加一个断点,然后用lldb调试下看看
我们通过c的地址打印出来会发现,c的地址里面存了四个值:1、2、3、4,因为int占4个字节,并且内存是从低位开始读取,所以打印出来的内存值是:
0x7ffee8cf4180: 0x0000000200000001 0x0000000400000003
并且由于每块内存是8个字节,但是int是4个字节,所以每个内存上面可以存储2个int类型的值
4、指针偏移
代码:
int c[4] = {1,2,3,4};
int *d = c;
BYNSLog(@"%p -- %p -- %p",d,d+1,d+2);
for (int i = 0; i < 4; i++) {
int value = *(d + i);
BYNSLog(@"%d",value);
}
输出:
BY打印: 0x7ffee6d0c180 -- 0x7ffee6d0c184 -- 0x7ffee6d0c188
BY打印: 1
BY打印: 2
BY打印: 3
BY打印: 4
d是c的内存地址,因为c里面元素是int类型的,d+1相当于内存地址偏移1个元素大小,也就是4个字节,所以for循环中我们可以通过指针偏移来取到每个元素的值
上一个图更直观
二、类的结构
1、类的结构探索
代码:
LGPerson *person = [LGPerson alloc];
Class pclass = object_getClass(person);
NSLog(@"%@ -- %p",person,pclass);
输出
2019-12-21 12:47:34.893387+0800 LGTest[65861:1081426] <LGPerson: 0x102034580> -- 0x100001130
(lldb) x/4gx pclass
0x100001130: 0x001d800100001109 0x0000000100afd140
0x100001140: 0x00000001020329b0 0x000000040000000f
(lldb) po 0x0000000100afd140
NSObject
(lldb) po 0x00000001020329b0
4328729008
(lldb) p 0x00000001020329b0
(long) $3 = 4328729008
(lldb) po 0x001d800100001109
8303516107936009
(lldb) p 0x001d800100001109
(long) $5 = 8303516107936009
(lldb)
首先加个断点,然后通过x/4gx命令打印出pclass的内存,明确的是第一个0x001d800100001109是isa指针,第二个我们发现事NSObject,说明第二个是它的父类,打印其他的发现找不出来
那我们通过clang命令将文件转换成.cpp文件,查看编译后的结构 通过命令:
clang -rewrite-objc main.m -o main.cpp
我们将main.m文件转成main.cpp文件,然后打开拉到最下面
在里面我们搜索LGPerson
会发现下面有个objc_class,再搜索objc_class
Class其实就是objc_class类型,我们再到objc源码里面搜索一下objc_class
会发现objc_class继承自objc_object
所以Class说明也是一个对象,类也是一个对象,万物皆对象
objc_class里面的ISA是隐藏的,通过查找objc_object我们会发现objc_object里面有一个属性是isa,所以objc_class的isa是从objc_object继承过来的
通过点击objc_object跳转
会跳转到
会发现这个objc_class废弃了,所以所有类都源自objc_object,查看NSObject的结构
我们发现其实NSObject也就是OC对objc_object的封装,
所以我们确定类的结构其实就是一个objc_class,它集成于objc_object
为什么isa是class,因为早期isa返回的就是类,现在只是添加了nonpointter属性 我们通过+(Class)Class{}方法可以看到返回的是就是类
2、属性的存储
a、首先我们看一下类的结构体的构造
会发现里面有四个属性,
- 第一个是isa
- 第二个父类指针,
- 第三个是缓存,
- 第四个是bits
所以类的属性最可能是存在bits里面,并且我们点击class_rw_t会发现里面结构
里面有methods、properties、protocols,下面我们通过lldb将相应的内容取出来看看。
首先我们知道类的地址,并且属性是按照顺序依次排列的,只要我们知道isa、superclass、cache的内存大小,那我们就可以通过内存偏移来得到bits的内存地址,然后取出bits里面的内容:
- 1、ISA是指针,是8个字节,
- 2、superclass也是指针、8个字节
- 3、cache_t结构形式为:
b、lldb调试得到class的属性
那我们就就得到类的内存地址偏移32个字节就得到bits的内存地址,下面我们就用lldb操作一下:
1、我们通过lldb调出class里面的内容
2019-12-21 14:42:53.811392+0800 LGTest[71379:1223891] <LGPerson: 0x101d4a400> -- 0x100001200
(lldb) x/4gx pclass
0x100001200: 0x001d8001000011d9 0x0000000100afd140
0x100001210: 0x0000000101d48940 0x000000040000000f
(lldb) po 0x100001208
<NSObject: 0x100001208>
(lldb) po 0x100001220
objc[71379]: Attempt to use unknown class 0x101d48740.
4294971936
(lldb) p 0x100001220
(long) $3 = 4294971936
(lldb) p (class_data_bits_t *)0x100001220
(class_data_bits_t *) $4 = 0x0000000100001220
(lldb) po $4->data()
0x0000000101d48740
(lldb) p $4->data()
(class_rw_t *) $6 = 0x0000000101d48740
(lldb) p *$7
error: use of undeclared identifier '$7'
(lldb) p *$6
(class_rw_t) $7 = {
flags = 2148139008
version = 0
ro = 0x0000000100001178
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010c8
arrayAndFlag = 4294971592
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100001160
arrayAndFlag = 4294971744
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSDate
demangledName = 0x0000000100000f46 "LGPerson"
}
(lldb) po $7.properties
(lldb) p $7.properties
(property_array_t) $9 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100001160
arrayAndFlag = 4294971744
}
}
}
(lldb) p $9[0]
error: type 'property_array_t' does not provide a subscript operator
(lldb) p *$9
error: indirection requires pointer operand ('property_array_t' invalid)
(lldb) p $9.list
(property_list_t *) $10 = 0x0000000100001160
(lldb) p $10.first
(property_t) $11 = (name = "address1", attributes = "T@\"NSString\",C,N,V_address1")
Fix-it applied, fixed expression was:
$10->first
(lldb) p ¥7.ro
error: use of undeclared identifier '¥7'
(lldb) p $7.ro
(const class_ro_t *) $12 = 0x0000000100001178
(lldb) p *$12
(const class_ro_t) $13 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100000f4f "\x02"
name = 0x0000000100000f46 "LGPerson"
baseMethodList = 0x00000001000010c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100001118
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100001160
}
(lldb) p $13.baseProperties
(property_list_t *const) $14 = 0x0000000100001160
(lldb) p $13.weakIvarLayout
(const uint8_t *const) $15 = 0x0000000000000000
(lldb) p *$14
(property_list_t) $16 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "address1", attributes = "T@\"NSString\",C,N,V_address1")
}
}
(lldb) p *$15
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
(lldb) p $13.flags
(uint32_t) $18 = 388
(lldb) p $13.ivarLayout
(const uint8_t *const) $19 = 0x0000000100000f4f "\x02"
(lldb) p *$19
(uint8_t) $20 = '\x02'
(lldb) p 0x0000000100000f4f
(long) $21 = 4294971215
(lldb) p $13.ivars
(const ivar_list_t *const) $22 = 0x0000000100001118
(lldb) p *$22
(const ivar_list_t) $23 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000011c8
name = 0x0000000100000f75 "by_hobby"
type = 0x0000000100000fa3 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $23.get(1)
(ivar_t) $24 = {
offset = 0x00000001000011d0
name = 0x0000000100000f7e "_address1"
type = 0x0000000100000fa3 "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $13.baseMethodList
(method_list_t *const) $25 = 0x00000001000010c8
(lldb) p *$25
(method_list_t) $26 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 3
first = {
name = "address1"
types = 0x0000000100000f90 "@16@0:8"
imp = 0x0000000100000dd0 (LGTest`-[LGPerson address1] at LGPerson.h:17)
}
}
}
(lldb) p $26.get(1)
(method_t) $27 = {
name = "setAddress1:"
types = 0x0000000100000f98 "v24@0:8@16"
imp = 0x0000000100000e00 (LGTest`-[LGPerson setAddress1:] at LGPerson.h:17)
}
(lldb) p $26.get(2)
(method_t) $28 = {
name = ".cxx_destruct"
types = 0x0000000100000f88 "v16@0:8"
imp = 0x0000000100000e40 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $26.get(3)
Assertion failed: (i < count), function get, file /Users/zhaojing/Downloads/Logic iOS 大师班/20191220-大师班-第4节课-OC类原理/01--课堂代码/001-类&元类的创建时机/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
###总结:
通过上面调试我们可以得到:
- 1、属性是存储在类的bits的class_rw_t里面的class_ro_t的baseProperties里面
- 2、成员变量是在class_ro_t的ivars里面,
- 3、实例方法是在class_ro_t 的baseMethodList里面
3、方法的存储
首先我们创建一个类,写一个实例方法和一个类方法并实现以下,然后创建完对象后打好断点
###a、实例方法的存储
####lldb调试流程
(lldb) p/x pclass //打印出类的内存
(Class) $0 = 0x0000000100001238 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100001258 //根据上面分析bits前面的属性内存占了32个字节,且类的内存地址是首元素的内存地址,所以我们可以通过类的内存地址偏移32个字节可以得到bits的内存地址并进行强转一下
(class_data_bits_t *) $1 = 0x0000000100001258
(lldb) p *$1
(class_data_bits_t) $2 = (bits = 4313984164)
(lldb) p $2->data() //取出bits里面的class_rw_t
(class_rw_t *) $3 = 0x0000000101222ca0
Fix-it applied, fixed expression was:
$2.data()
(lldb) p $3.ro //取出class_rw_t 里面的class_ro_t
(const class_ro_t *) $4 = 0x00000001000011b0
Fix-it applied, fixed expression was:
$3->ro
(lldb) p *$4 //取出class_ro_t的内容
(const class_ro_t) $5 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100000f4f "\x02"
name = 0x0000000100000f46 "LGPerson"
baseMethodList = 0x00000001000010e8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100001150
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100001198
}
(lldb) p $5.baseMethodList //拿到class_ro_t里面的baseMethodList
(method_list_t *const) $6 = 0x00000001000010e8
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "address1"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000dd0 (LGTest`-[LGPerson address1] at LGPerson.h:17)
}
}
}
(lldb) p $7.get(1) //通过下标取出baseMethodList里面的元素打印出来
(method_t) $8 = {
name = "setAddress1:"
types = 0x0000000100000fa0 "v24@0:8@16"
imp = 0x0000000100000e00 (LGTest`-[LGPerson setAddress1:] at LGPerson.h:17)
}
(lldb) p $7.get(2)
(method_t) $9 = {
name = ".cxx_destruct"
types = 0x0000000100000f90 "v16@0:8"
imp = 0x0000000100000e40 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $7.get(3)
(method_t) $10 = {
name = "run"
types = 0x0000000100000f90 "v16@0:8"
imp = 0x0000000100000db0 (LGTest`-[LGPerson run] at LGPerson.m:12)
}
(lldb) p $7.get(4) //方法列表里面就四个方法
Assertion failed: (i < count), function get, file /Users/zhaojing/Downloads/Logic iOS 大师班/20191220-大师班-第4节课-OC类原理/01--课堂代码/001-类&元类的创建时机/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb)
####总结:
类的实例方法是存储在类的bits的class_rw_t的class_ro_t的baseMethodList里面,并且只有实例方法没有类方法
b、类方法的存储
####ldb调试流程
(lldb) x/4gx pclass //取到类的内存地址
0x1000023a0: 0x001d800100002379 0x0000000100afe140
0x1000023b0: 0x0000000101919d30 0x0000000200000003
(lldb) p/x 0x001d800100002379 & 0x00007ffffffffff8 //拿到元类的内存地址
(long) $1 = 0x0000000100002378
(lldb) x/4gx $1 //拿到元类的内存结构
0x100002378: 0x001d800100afe0f1 0x0000000100afe0f0
0x100002388: 0x0000000101919e10 0x0000000500000007
(lldb) po 0x0000000100afe0f0 //拿到元类的父类,确定拿到的元类是正确的
NSObject
(lldb) p (class_data_bits_t *)0x100002398 //拿到元类的bits内存地址,并强转成class_data_bits_t
(class_data_bits_t *) $3 = 0x0000000100002398
(lldb) p *$3 //取出元类的bits内存地址的内容
(class_data_bits_t) $4 = (bits = 4321287200)
(lldb) p $4->data() //拿到bits里面的class_rw_t
(class_rw_t *) $5 = 0x0000000101919c20
Fix-it applied, fixed expression was:
$4.data()
(lldb) p $5.ro //拿到class_rw_t里的class_ro_t
(const class_ro_t *) $6 = 0x00000001000021d0
Fix-it applied, fixed expression was:
$5->ro
(lldb) p *$6 //取出class_ro_t里面的值
(const class_ro_t) $7 = {
flags = 389
instanceStart = 40
instanceSize = 40
reserved = 0
ivarLayout = 0x0000000000000000
name = 0x0000000100001f84 "LGPerson"
baseMethodList = 0x00000001000021b0
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
}
(lldb) p $7.baseMethodList //拿到class_ro_t里面的方法列表
(method_list_t *const) $8 = 0x00000001000021b0
(lldb) p *$8
(method_list_t) $9 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "ear"
types = 0x0000000100001f8f "v16@0:8"
imp = 0x0000000100001be0 (LGTest`+[LGPerson ear] at LGPerson.m:16)
}
}
}
(lldb)
####总结:
然后我们确定发现类的类方法是存在元类里面
4、通过系统提供的api来验证属性、实例方法、类方法的存储位置
写一个函数:
void testObjc_Ivars_and_properise(Class class){
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(class, &count);
for (unsigned int i = 0; i < count; i++) {
Ivar const ivar = ivars[i];//如何知道Ivar是一个数组,且里面元素是ivar const类型
//获取成员变量名
const char *cName = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:cName];
NSLog(@"class_ivar:%@",ivarName);
}
unsigned const pCount = 0;
objc_property_t *properties = class_copyPropertyList(class, pCount);
for (unsigned int i = 0; i < pCount; i++) {
objc_property_t const property = properties[i];
//获取属性名
NSString *properityName = [NSString stringWithUTF8String:property_getName(property)];
NSLog(@"class_properity:%@",properityName);
}
}
//我们把LGPerson传进去
LGPerson *person = [LGPerson alloc];
Class pclass = object_getClass(person);
NSLog(@"%@ -- %p",person,pclass);
testObjc_Ivars_and_properise(pclass);
打印:
BY打印: class_ivar:by_hobby
BY打印: class_ivar:_address1
BY打印: class_properity:address1
方法:
再写一个函数
void testInstanceMethod(Class class){
unsigned int count = 0;
Method *methods = class_copyMethodList(class, &count);
for (unsigned int i = 0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
NSString *methodName1 = [NSString stringWithUTF8String:method_getTypeEncoding(method)];
NSLog(@"class_method:%@ --- %@",methodName,methodName1);
}
free(methods);
}
首先我们把LGPerson类传进去,然后将LGPerson的元类传进去,调用两次,调用代码:
LGPerson *person = [LGPerson alloc];
Class pclass = object_getClass(person);
testInstanceMethod(pclass);
BYNSLog(@"下面是元类方法");
const char *className = class_getName(pclass);
Class metaClass = objc_getMetaClass(className);
testInstanceMethod(metaClass);
调用结果:
BY打印: class_method:address1 --- @16@0:8
BY打印: class_method:setAddress1: --- v24@0:8@16
BY打印: class_method:.cxx_destruct --- v16@0:8
BY打印: class_method:run --- v16@0:8
BY打印: 下面是元类方法
BY打印: class_method:ear --- v16@0:8
我们会发现实例方法是在类对象中,但是类方法却在元类中,
另外,objc框架还提供了函数用来从类中查找方法的指针功能,如果能查到就说明该方法在类中,假如查不到就会返回0x0,这个功能可以用来验证该方法是否存在该类中。
函数:
//验证方法在类中是否存在
void testMethod_class(Class class){
const char *className = class_getName(class);
Class metaClass = objc_getMetaClass(className);
//得到类的实例方法,可以从类中找到实例方法,从元类中找到类方法,并且只声明,没实现的话,是找不到相应的方法的,
Method method1 = class_getInstanceMethod(class, @selector(run));
Method method2 = class_getInstanceMethod(metaClass, @selector(run));
Method method3 = class_getInstanceMethod(class, @selector(ear));
Method method4 = class_getInstanceMethod(metaClass, @selector(ear));
BYNSLog(@"instanceMethod:%p -- %p -- %p -- %p",method1,method2,method3,method4);
//得到类的类方法,可以从类中找到类方法,也可以从元类在找到类方法,但是找不到实例方法,没实现的话,是找不到相应的方法的,
Method method5 = class_getClassMethod(class, @selector(run));
Method method6 = class_getClassMethod(metaClass, @selector(run));
Method method7 = class_getClassMethod(class, @selector(ear));
Method method8 = class_getClassMethod(metaClass, @selector(ear));
BYNSLog(@"classMetod:%p -- %p -- %p -- %p",method5,method6,method7,method8);
//获取类中的方法实现,都可以获取到指针,,不知道啥玩意,c随便写都能获取到imp
IMP imp1 = class_getMethodImplementation(class, @selector(run));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(run));
IMP imp3 = class_getMethodImplementation(class, @selector(ear));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(ear));
IMP imp5 = class_getMethodImplementation(metaClass, @selector(ear1));
BYNSLog(@"methodIMP:%p -- %p -- %p -- %p -- %p",imp1,imp2,imp3,imp4,imp5);
}
调用结果:
BY打印: instanceMethod:0x100002268 -- 0x0 -- 0x0 -- 0x1000021b8
BY打印: classMetod:0x0 -- 0x0 -- 0x1000021b8 -- 0x1000021b8
BY打印: methodIMP:0x100001bd0 -- 0x1003449c0 -- 0x1003449c0 -- 0x100001be0 -- 0x1003449c0
我们根据得到的结果分析一下
class_getInstanceMethod
class_getClassMethod
class_getMethodImplementation
这三个方法的实现: ####(1)class_getInstanceMethod实现 #####结果: 得到类的实例方法,可以从类中找到实例方法,从元类中找到类方法,并且只声明,没实现的话,是找不到相应的方法的 #####分析: 拿到源码:
####(2)class_getClassMethod实现 #####结果: 得到类的类方法,可以从类中找到类方法,也可以从元类在找到类方法,但是找不到实例方法,没实现的话,是找不到相应的方法的 #####分析:
####(3)class_getMethodImplementation #####结果: 获取类中的方法实现指针,都可以获取到指针,并且从元类中获取实例方法、从类中获取类方法、从元类中获取不存在的方法返回的指针都是一样的 #####分析:
#总结:
bits的结构图:
#问题: 1、NSObject是对objc_object的封装,那NSObject(class)和objc_class是什么关系? 2、类的协议是存在哪的? 3、class_getInstanceMethod里面的具体实现? 4、为啥class_data_bits_t里面的method_array_t、property_array_t里面存的东西都很乱?