Objective-C的本质
OC是C语言的超集,我们平时写得OC,底层的实现都是C\C++代码,OC的对象、类是基于C\C++的结构体实现的。
- 将Objective-C代码转换为C\C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc-o <输出的.cpp文件>
如果需要链接其他框架,使用-framework参数。比如-framework UIKit
OC对象的本质
- NSObject的底层实现
struct NSObject_IMPL{
Class *isa
}
//Class是objc_class结构体指针
typedef objc_class *Class
//-----------obj指针的地址值就是isa的地址值------------------
NSObject *obj = [[NSObject alloc] init];
//obj == obj结构体->isa
从上面可以看得到,NSObject对象的本质就是NSObject_IMPL结构体,OC中的类本质就是结构体,NSObject类里只有Class isa指针,obj对象的地址值就是isa指针的地址值。表面上看NSObect类只占用了8个字节(指针变量占8个字节),不过实际上是使用了16个字节,追踪alloc方法可以直到instanceSize函数可以知道,内存分配至少是16个字节。
//一个实例对象至少需要多少字节
class_getInstanceSize([NSObject class]);
//一个实例对象实际占用多少字节
malloc_size((__brige const void *)obj);
一个继承NSObject的类的结构体
//例如
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end
struct Person_IMPL{
stuct NSObject_IMPL NSObject_IVARS;
int _age;
}
//NSObject_IMPL的成员变量只有isa指针一个,所以实际结构体等价如下,person实例对象的地址值还是isa指针的地址值,再继承也同理
struct Person_IMPL{
Class *isa;
int _age;
}
OC对象的分类
Objective-C中的对象,简称OC对象,主要分3种
- instance对象(实例对象)
instance对象就是通过类alloc出来的对象,每次alloc都会创建新的对象
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
//obj1、obj2都是instance对象,它们是不同的两个对象,分别占据着不同的内存
instance对象在内存中存储的信息有isa指针和其他成员变量。
Person *p1 = [[Person alloc] init];
p1->_age = 3;
Person *p2 = [[Person alloc] init];
p1->_age = 4;
- class对象(类对象)
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
objectClass1 ~ objectClass5都是NSObject的class对象(类对象);
它们都是同一个对象,每一个类在内存中有且只有一个class对象;
class对象在内存中存储的信息主要包括:
isa指针
superclass指针
类的属性信息(@property)、类的对象方法信息(instance method)
类的协议信息(protocol)、类的成员变量信息(ivar)
- meta-class(元类对象)
//获取元类对象的方法
Class objMetaClass = object_getClass([NSObject class]);
/*
注意:这个方法获取到的还是类对象
Class objMetaClass = [[NSObject class] class];
*/
objectMetaClass是NSObject的meta-class对象(元类对象)
每个类在内存中有且只有一个meta-class对象
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:
isa指针
superClass指针
类的类方法信息
isa指针和supclass指针
- instance的isa指针指向class
调用对象方法时,通过isa指针找到class,找出对应的对象方法进行调用 - class的isa指针指向meta-class
调用类方法时,通过class的isa找到meta-class,找出对应的类方法进行调用
从Arm64开始,优化了isa指针,isa需要进行位运算(&ISA_MASK)才是真正的地址值
-
class对象的superclass指针指向父类class对象,当student调用父类Person对象方法时,会先通过isa找到student的class对象,再通过class的superclass指针知道到父类Person的class对象,然后找出对应的对象方法进行调用。
-
meta-class对象的superclass指针指向父类的meta-class对象,当student调用父类的类方法时,通过class对象的isa找到meta-class对象,再通过meta-class对象的superclass指针找到父类的meta-class对象,找出对应的类方法进行调用。
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基类的meta-class
- class的superclass指向父类的class
如果没有父类,则superclass置nil - meta-class的superclass指向父类的meta-class
如果没有父类,则指向基类的class - instance调用对象方法的轨迹
isa找到class,方法不存在,通过superclass找父类 - class调用类方法的轨迹
isa找到meta-class,方法不存在,通过superclass找父类
Class的结构
class和meta-class的本质结构都是struct objc_class,也可以理解为meta-class是特殊的class对象,只是用途不一样。
//struct objc_class 结构
struct objc_class{
Class isa;
Class superclass;
Cache_t cache;//方法缓存
class_data_bit_t bit;//用于获取类的具体信息
}
// bit & FAST_DATA_MASK 得到类的具体信息
struct class_rw_t{
unit32_t flags;
unit32_t version;
const class_ro_t *ro;
method_list_t * methods;//方法列表,是一个二维数组
property_list_t * properties;//属性列表
const protocol_list_t * protocols;//协议列表
Class firtSubslass;
Class nextSiblingClass;
char *demanglegName;
}
//struct class_ro_t 的结构
struct class_ro_t{
unit32_t flags;
unit32_t instanceStart;
unit32_t instanceSize;//instance对象占用的内存空间
unit32_t flag;
#ifdef __LP64__
unit32_t reserved;
#endif
const unit8_t * ivarLayout;
const char * name;//类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成员变量列表
const unit8_t * weakIvarLayout;
property_list_t * baseProperties;
}
KVO
-
KVO 全称Key-Value Observing,俗称"键值监听",可以用于某个对象属性值得改变
- KVO的原理解释
利用Rumtime动态生成一个子类,并让instance对象的isa指向这个全新的类,当instance对象的属性修改时,会调用Foundation的_NSSetXXXValueAndNotify函数,该函数内部的调用流程是,先调用willChangeForKey:,父类原来的setter,didChangeForKey。然后内部会触发监听器(Observer)的监听方法(observeValueForKeyPath:ofObject:change:context:)
KVC
- KVC全称是Key-Value Coding,键值编码,可以通过一个key来访问访问某个属性
常见API
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
accessInstanceVariablesDirectly的默认返回值是YES
Category
//category的底层结构
struct category_t {
const char *name;//类名
classref_t cls;
struct method_list_t *instanceMethods;//对象方法列表
struct method_list_t *classMethods;//类方法列表
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
- category的加载处理过程
1.通过runtime加载某个类的category数据
2.把所有的category的方法、属性、协议数据合并到一个大数组中
后面参与编译的category数据,会在数组的前面
3.将合并后的分类数据(方法、属性、协议),插到类原来数据的前面
+load方法
- load方法会在runtime加载类、分类时调用
- 每个类、分类在程序运行过程中只调用 一次load方法
- 调用顺序
1.先调用类的+load
按照编译顺序调用,先编译先调用
调用子类的+load之前会先调用父类的+load
2.再调用分类的+load
按照编译顺序调用,先编译先调用 - load方法是根据方法地址(*load_method)(cls, SEL_load)
直接调用的,并不是经过objc_msgSend函数调用
+initialize方法
- +initialize方法会在类第一次接收到消息时调用
- 调用顺序
先调用父类的+initialize,再调用子类的+initialize
(相处初始化父类,再初始化子类,每个类只会初始化一次)
+initialize和+load的区别
+initialize是通过objc_msgSend调用的,所以有以下特点:如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+intialize方法可以会被调用多次),如果分类实现的+initialize,就会覆盖类的+initialize调用。而+load是通过方法地址直接调用的,所以不会有这些特点。
关联对象
Block
block的本质
- block本质也是个OC对象,它的内部有个isa指针
-
block是封装了函数调用以及函数调用环境的OC对象
//OC代码
int age = 20;
void (^block)(void) = ^{
NSLog(@"age is %d",age);
};
//block的本质源码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int age;//变量捕获
}
//__block_impl结构
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//__test_block_desc_0结构
struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
}
block的变量捕获
为了能够保证block内部访问到外部变量,block有个变量捕获机制
block的类型
block有3种类型,可以通过class方法或者isa指针查看,它们都继承自NSBlock
__NSGlobalBlock__(全局block)
__NSStackBlock__(栈block)
__NSMallocBlock__(堆block)
block的copy
在ARC环境下,编译器会根据情况将栈block copy为堆block,比如以下情况
- block作为函数返回值时
- 将block赋值给__Strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的参数时
//MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
//ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
block访问对象类型的auto变量
- 如果block是在栈上,将不会对auto变量产生强引用
- 如果block被copy到堆上
block会调用内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 -
如果block从堆上被移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
__block修饰符
- __block可以用于解决block内部无法修改外部auto变量值的问题
- __block不能用于修改全局变量,静态变量(static变量)
- 编译器会将__block修饰的变量包装成一个对象
__block int age = 10;
^{
NSLog(@"age is %d",age);
};
//age包装成对象的结构体代码
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;//该指针指向自身
int __flags;
int __size;
int age;
};
//block捕获age变量,实际捕获ae对象
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *p;
__Block_byref_age_0 *age; // 变量捕获
}
__block的内存管理
- 当block在栈时,不会对__block变量产生强引用
- 当block被copy到堆时
会调用block的copy函数
copy函数会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain) - 当block从堆中被移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量(release)
__block的_forwarding指针
- 当__block变量在栈上时,不会对指向的对象产生强引用
- 当__block变量被copy到堆时
会调用__block变量内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain) - 如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)
block循环引用问题
解决方法
//用__weak
__weak typeof(self) weakSelf = self;
self.block = ^{
print("%p",weakSelf);
};
//用__unsafe_unretained
__unsafe_unretained id weakSelf = self;
self.block = ^{
print("%p",weakSelf);
}