问题先行:
1、如下代码的打印结果是什么?
@implementation Son
- (instancetype)init {
if (self == [super init]) {
NSLog(@"self.class === %@",self.class);
NSLog(@"super.class === %@",super.class);
}
return self;
}
@end
2、如下代码的打印结果是什么?
BOOL result1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL result2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL result3 = [[Son class] isKindOfClass:[Son class]];
BOOL result4 = [[Son class] isMemberOfClass:[Son class]];
NSLog(@"result1 == %d result2 == %d result3 == %d result4 == %d",result1,result2,result3,result4);
BOOL result5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL result6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL result7 = [[Son alloc] isKindOfClass:[Son class]];
BOOL result8 = [[Son alloc] isMemberOfClass:[Son class]];
NSLog(@"result5 == %d result6 == %d result7 == %d result8 == %d",result5,result6,result7,result8);
3、如下代码class1和class2的打印结果一样吗?
Class class1 = [Son class];
Class class2 = [Son class];
NSLog(@"class1 === %p",class1);
NSLog(@"class2 === %p",class2);
4、属性、实例变量、成员变量的区别?
5、如下打印两行打印存在什么关系,&a和&a[0]一样吗?
int a[4] = {1,2,3};
int *b = a;
NSLog(@"%p ==== %p ==== %p ==== %p",&a,&a[0],&a[1],&a[2]);
NSLog(@"%p ==== %p ==== %p",b,b+1,b+2);
资料准备:
1、objc源码下载opensource.apple.com/
2、API官方文档developer.apple.com/documentati…
3、WWDC关于runtime的优化developer.apple.com/videos/play…
初步探索:
通过clang命令,生成相关代码的C++文件,查看底层实现
clang -rewrite-objc main.m -o main.cpp
实例如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
ASTest *test = [[ASTest alloc]init];
[test test];
return 0;
}
}
/// 相关 main.app 部分代码
#ifndef _REWRITER_typedef_ASTest
#define _REWRITER_typedef_ASTest
typedef struct objc_object ASTest;
typedef struct {} _objc_exc_ASTest;
#endif
struct ASTest_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
通过上面的探索,我们可以得出以下结论
1、对象的本质是个结构体
2、结构体内有一个objc_class类型的isa
objc_object、objc_class、NSObject、isa关系分析:
isa是什么?
objc_object、objc_class、NSObject是什么?
objc_object源码如下
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
......
}
objc_class源码如下
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
......
}
NSObject源码如下
/// NSObject.h
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
/// NSObject.mm
@implementation NSObject
+ (void)initialize {
}
+ (id)self {
return (id)self;
}
- (id)self {
return self;
}
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
......
}
总结
1、结构体类型objc_class
继承自objc_object
,其中objc_object
也是一个结构体,且有一个isa
属性,所以objc_class
也拥有了isa
属性
2、NSObject
中的isa
在底层是由Class
定义的,其中Class
的底层编码来自objc_class
类型,所以NSObject
也拥有了isa
属性
3、NSObject
是一个类,用它初始化一个实例对象objc
,objc
满足objc_object
的特性(即有isa
属性),主要是因为isa
是由 NSObject
从objc_class
继承过来的,而objc_class
继承自objc_object
,objc_object
有isa
属性,所以对象都有一个isa
,isa
表示指向来自于当前的objc_object
。因此会是如下情况对象 -> 类对象 -> 元类 -> 根元类 -> 自己
4、Class
本身其实也是一个对象,我们称之为类对象,类对象在编译期产生用于创建实例对象,是单例
5、NSObject
的实例方法self
是id
类型,其中id
是objc_object
的别名,因此可以得出万物的本质皆来源于objc_object
objc_class结构体解析
结构体主要包含如下
isa
:继承自objc_object
的isa
,占8字节superclass
:objc_class
类型的一个结构体指针,占8字节cache
:方法缓存、获取占16字节bits
:可以指向class_rw_t
和class_ro_t
,可以通过首地址平移32字节获取class_rw_t
:运行时生成,包含class_ro_t
。插入分类的方法、协议、属性,不能添加成员变量class_rw_t
->class_ro_t
:是编译期生成的,它存储了当前类在编译期就已经确定的属性、方法以及协议,它里面是没有分类中定义的方法和协议
isa(isa_t类型)
superclass(objc_class类型)
typedef struct objc_class *Class;
可以看出是是一个objc_class
类型的结构体指针。
cache(cache_t类型)
bits(class_data_bits_t类型)
/// 官方注释
// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
......
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
......
}
- 相当于
unsigned long bits
; 占64位 bits
实际上是一个地址(是一个指针,可以指向class_ro_t
,也可以指向class_rw_t
)- 通过官方注释可以得出:
class_data_bits_t
相当于class_rw_t
* 加上rr/alloc
标志 data()
这里将bits
与FAST_DATA_MASK
进行位运算,只取其中的3, 47位转换成class_rw_t
*返回- 通过前面的分析可知,想要获取
bits
的中的内容,只需通过类的首地址平移32字节
即可.
class_rw_t(class_data_bits_t中的data方法)
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t version;
uint16_t witness;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
......
}
1、objc_class
中的data()
方法调用了class_data_bits_t
结构体中的data()
方法,返回class_rw_t *
指针
2、class_rw_t
是在运行时生成的,它在realizeClass
中生成,它包含了class_ro_t
。它在_objc_init
方法中关于dyld
的回调的map_images
中最终将分类的方法
与协议
都插入到自己的方法列表
、协议列表
中。它不包含成员变量列表
,因为成员变量列表
是在编译期
就确定好的,它只保存在class_ro_t
中。不过class_rw_t
中包含了一个指向class_ro_t
的指针。
LLDB探索属性、方法:
x/4gx object打印当前类结构
bits是 (类的内存首地址 + isa、superclass、cache的内存长度) 0x100001240 + 32字节(0x20) = 0x100001260 打印bits的结构
根据
class_rw_t *data() { return bits.data(); }
打印bits.data()
从$21指针的打印结果中可以看出bits中存储的信息,其类型是class_rw_t
,也是一个结构体类型。
通过查看class_rw_t
定义的源码发现,结构体中有提供相应的方法去获取属性列表、方法列表等,如下所示
bits(class_rw_t)中的属性列表
由此我们知道class_rw_t
中的property_list
中只有属性,没有成员变量。
class_rw_t
有个属性class_ro_t
在控制台输出ro
,打印class_ro_t
的结构
ro(class_rw_t -> class_ro_t)中的成员变量列表
1、通过{}
定义的成员变量,会存储在类的bits
属性中,通过bits
-> data()
->ro()
-> ivars
获取成员变量列表,除了包括成员变量,还包括属性定义的成员变量
2、通过@property
定义的属性,也会存储在bits
属性中,通过bits
-> data()
-> properties()
-> list
获取属性列表,其中只包含属性
3、class_ro_t
存放的是编译期间就确定的;而class_rw_t
是在runtime时才确定,它会先将class_ro_t
的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t
是class_ro_t
的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t
中的内容\
bits(class_rw_t)中的方法列表
类中的方法列表
除了包括实例方法
,还包括属性的set方法
和 get方法
,还有系统在底层添加了一个c++的.cxx_destruct方法
上面发现类的类方法并没有在列表中,原因是实例方法存储在类对象中,方法的实现底层都是函数,因此类方法就不能再存储到类对象中了(重名等问题)。
类方法是存储在元类中,可以理解成元类对象的实例方法
WWDC20关于runtime的优化-数据结构的变化:
数据结构的变化
这个类对象包含了最常用的信息:指向元类、父类、以及方法的缓存。它还有一个指针指向更多的额外信息
class_ro_t
,其中ro
表示read only
这部分信息是只读的,其中包含了类名、方法、协议、实例变量和属性等信息。
clean memory 是指加载后不会发生更改的内存。例如:class_ro_t就是clean memory
dirty memory 是指加载后会发生更改的内存。例如:class_rw_t就是dirty memory
一旦类被使用,运行时会分配额外的空间来存储这部分数据,即class_rw_t
,其中rw
表示read write
。这个结构体中,我们只存储运行时产生的数据。
- First Subclass和Next Sibling Class指针让运行时可以遍历当前使用的所有类。
- Methods、Properties、Protocols这部分也是可以在运行时进行修改的。在实践中发现,其实只有大约10%类的方法会发生变化,所以这部分内存可以得到优化,滕出一些空间。
- Demangled Name只会被Swift类所使用,而且除非有需要获取它们的Objective-C名称,甚至都不会用到。
这样就把
class_rw_t
,拆成了2部分。如果确实有需要,我们才会这部分class_rw_ext_t
结构分配内存
问题先行解答:
1、打印结果为:
self.class === Son
super.class === Son
self
是类的隐藏参数,指向当前调用方法的这个类的实例
super
本质上是一个编译器标识符,和self
指向的是同一个消息接受者。不同的是super
调用方式的时候,会告诉编译器去调用父类的方法而不是本类的方法。
self
和super
发送消息方法分别是objc_msgSend
和objc_msgSendSuper
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
当在父类的方法列表中找到之后通过objc_super
中的receiver
去调用该方法的。
objc_super
-> receiver
就是此时的self
,因此最终都为self.class
,获取的都为类对象因此是一样的。
2、如下代码的打印结果是什么?
在进行分析之前,先明白几个知识点
class的实例方法,类方法
/// 类方法返回的就是在自己
+ (Class)class {
return self;
}
/// self为对象,结果是类对象
/// 非编译器优化方法
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj) {
if (obj) return obj->getIsa();
else return Nil;
}
isKindOfClass的实例方法,类方法
/// 比较调用者的元类/根元类->父类->父类。。。和目标类
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
/// 比较调用者的类对象—>父类->父类。。。和目标类
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
编译优化的原因,isKindOfClass实际调用的是objc_opt_isKindOfClass函数
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
isMemberOfClass的实例方法,类方法
/// 比较调用者的元类和目标类
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
/// 比较调用者的类对象和目标类
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
即结论如下
result1 == 1 result2 == 0 result3 == 0 result4 == 0
result5 == 1 result6 == 1 result7 == 1 result8 == 1
/// (传入值)NSObject类 -> 根元类 -> NSObject类 和 NSObject类 相等 1
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
/// (传入值)NSObject类 -> 根元类 和 NSObject类 不相等 0
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
/// (传入值)Son类 -> 元类 -> 根元类 -> NSObject类 和 Son类 不相等 0
BOOL re3 = [(id)[Son class] isKindOfClass:[Son class]];
/// (传入值)Son类 -> 元类 和 Son类 不相等 0
BOOL re4 = [(id)[Son class] isMemberOfClass:[Son class]];
/// (传入值)NSObject对象 -> NSObject类 和 NSObject类 相等 1
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
/// (传入值)NSObject对象 -> NSObject类 和 NSObject类 相等 1
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
/// (传入值)Son对象 -> Son类 和 Son类 相等 1
BOOL re7 = [(id)[Son alloc] isKindOfClass:[Son class]];
/// (传入值)Son对象 -> Son类 和 Son类 相等 1
BOOL re8 = [(id)[Son alloc] isMemberOfClass:[Son class]];
类方法class返回自己
类方法isKindOfClass比较的是调用者的元类\根元类 -> 父类 -> … 和目标类
类方法isMemberOfClass比较的是调用者的元类\根元类和目标类
实例方法class返回类对象
实例方法isKindOfClass比较的是调用者的类对象 -> 父类 -> … 和目标类
实例方法isMemberOfClass比较的是调用者的类对象 和目标类
3、如下代码class1
和class2
的打印结果一样吗?
一样,因为类的信息在内存中永远只存在一份,所以类对象是个单例。
4、属性、实例变量、成员变量的区别?
属性(property
):在OC中是通过@property
开头定义,编译阶段会自动生成带下划线的成员变量和setter、getter方法
实例变量(即成员变量中的对象变量就是实例变量):以实例对象实例化来的,是一种特殊的成员变量。
成员变量除去基本数据类型、NSString等其他都是实例变量(即可以添加属性的成员变量),实例变量主要是判断是不是对象。NSString是常量类型,因为不能添加属性,如果定义在类中的{}中,是成员变量
5、如下打印两行打印存在什么关系,&a和&a[0]一样吗?
结果如下
&a和&a[0]一样
b和&a和&a[0]一样
b+1和&a[1]一样
b+2和&a[2]一样
以上其实和通过对象的地址可以获取到对象类信息是一样的,都是通过内存平移实现
0x16da536d0 ==== 0x16da536d0 ==== 0x16da536d4 ==== 0x16da536d8
0x16da536d0 ==== 0x16da536d4 ==== 0x16da536d8