Objective-C是一门比较古老的语言了,每一个iOSer都会使用。不知不觉参加工作已然三年,前一年半使用纯Objective-C开发,后面换工作后一直使用纯Swift。对于Objective-C,我谈不上喜欢,也不讨厌,就是觉得它设计的很巧妙。
Objective-C是C语言的超集。C(.m)和C++(.mm)可以直接写在Objective-C代码中。Objective-C就像是盖在C语言上一个薄薄的兼容层,为这门面向过程的语言带来了面向对象的能力。Runtime这个用C、汇编编写的运行时库,提供了对象模型、内存管理、消息调用、协议等一系列面向对象的概念。
由于是在C语言上拓展的面向对象,Objective-C对象的概念在C语言的基础上封装出来的。其底层实现可以简单的概括为:结构体、数组、哈希表、函数指针,以及在这些基础上的修改操作。
对象模型-类的结构
每当我们谈论到对象&类时,总少不了类、元类、属性(成员变量)、函数(消息)、生命周期这些概念。
先看一下Runtime源码中的定义。
typedef struct objc_object* id;
struct objc_object {
private:
isa_t isa; // isa
// 省略了一些 isa操作、内存管理、关联对象、弱引用相关的方法
};
C++中结构体struct是可以继承的,于是乎在objc_object的基础上,可以定义出类Class。
typedef struct objc_class* Class;
struct objc_class : objc_object {
Class superclass; // 指向父类
cache_t cache; // 方法缓存
class_data_bits_t bits; // 类的具体信息
class_rw_t *data() {
return bits.data();
}
};
Objective-C的基本对象类型是NSObject,它包含了一个Class类型成员变量isa,以及NSObject协议的实现。
@protocol NSObject
// 协议方法省略
@end
@interface NSObject <NSObject> {
Class isa;
}
// 方法省略
@end
为了探究NSObject的底层实现,我们先简单定义一个类,代码如下:
// AObject.h
@interface AObject : NSObject
@property (nonatomic, copy) NSString* publicValue;
+ (void)staticMethod;
- (instancetype)initWithValue:(NSString *)value;
@end
// AObject.m
@interface AObject ()
@property (nonatomic, copy) NSString* privateValue;
@end
@implementation AObject
@synthesize privateValue = _privateValue;
+ (void)staticMethod {
NSLog(@"%@", @"staticMethod");
}
- (instancetype)initWithValue:(NSString *)value {
if (self = [super init]) {
// do some custom actions
_publicValue = value;
}
return self;
}
- (void)setPrivateValue:(NSString *)privateValue {
_privateValue = privateValue;
}
- (NSString *)privateValue {
if (!_privateValue) {
_privateValue = @"privateValue";
}
return _privateValue;
}
@end
使用下面的命令编译成cpp。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc /文件路径/AObject.m -o AObject.cpp
编译后的文件比较大,我们捡一些重点的看看。
// 实锤 NSObject 为 objc_object
typedef struct objc_object NSObject;
// 编译后的实现,可知NSObject其实是一个含有Class(objc_class)成员变量的结构体
struct NSObject_IMPL {
Class isa;
};
再看一下Aobject的编译后的代码。
struct AObject_IMPL {
// 父类实现
struct NSObject_IMPL NSObject_IVARS;
// 成员变量
// PS: 这也是为什么可以在代码中使用 `_变量名` 取值。
NSString *_privateValue;
NSString *_publicValue;
};
AObject中定义的方法去哪里了呢?
// + (void)staticMethod;
static void _C_AObject_staticMethod(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_th_nyrzwfk9591fq0n11vss4v_r0000gn_T_AObject_4b2570_mi_0, (NSString *)&__NSConstantStringImpl__var_folders_th_nyrzwfk9591fq0n11vss4v_r0000gn_T_AObject_4b2570_mi_1);
}
// - (instancetype)initWithValue:(NSString *)value;
static instancetype _I_AObject_initWithValue_(AObject * self, SEL _cmd, NSString *value) {
if (self = ((AObject *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("AObject"))}, sel_registerName("init"))) {
(*(NSString **)((char *)self + OBJC_IVAR_$_AObject$_publicValue)) = value;
}
return self;
}
// - (void)setPrivateValue:(NSString *)privateValue;
static void _I_AObject_setPrivateValue_(AObject * self, SEL _cmd, NSString *privateValue) {
(*(NSString **)((char *)self + OBJC_IVAR_$_AObject$_privateValue)) = privateValue;
}
// - (NSString *)privateValue
static NSString * _I_AObject_privateValue(AObject * self, SEL _cmd) {
if (!(*(NSString **)((char *)self + OBJC_IVAR_$_AObject$_privateValue))) {
(*(NSString **)((char *)self + OBJC_IVAR_$_AObject$_privateValue)) = (NSString *)&__NSConstantStringImpl__var_folders_th_nyrzwfk9591fq0n11vss4v_r0000gn_T_AObject_4b2570_mi_2;
}
return (*(NSString **)((char *)self + OBJC_IVAR_$_AObject$_privateValue));
}
// 自动生成的 publicValue 的 gettter
static NSString * _I_AObject_publicValue(AObject * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_AObject$_publicValue)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// 自动生成的 publicValue 的 settter
static void _I_AObject_setPublicValue_(AObject * self, SEL _cmd, NSString *publicValue) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct AObject, _publicValue), (id)publicValue, 0, 1); }
基于此,我们可以简单的了解到OC中类的设计。类 = 父类实现 + 变量 + 一组方法实现
。
由于C语言的限制,结构体中无法定义函数,但可以定义函数指针,所以OC类中的方法的真实实现其实都是包含了静态函数指针的结构体。静态方法和实例方法区别在于函数的第一个参数是Class还是结构体指针。
下面是一张老生常谈的图,iOSer估计都看过。
![](https://img.haomeiwen.com/i12417244/24c25442f7029b4d.png)
这种类、元类以及类之间继承的机制是怎么实现的呢?
我们先回顾objc_class的实现,它继承自objc_object,包含了指针isa、父类superClass、方法缓存cache、类信息bits。毫无疑问,示例代码中定义的AObject自然是有这些的。那么这些变量肯定也是要初始化的。
在继续讲类的设计之前,我们先花一点时间,了解一下Objective-C中函数的设计。
typedef struct method_t* Method;
struct method_t {
SEL name; // 方法名
const char *types; // 使用Type Encodings 编码技术,将函数编码成字符串
IMP imp; // 函数指针,指向具体的实现
};
// method_t 编译成 cpp的形式,method_t和_objc_method实际是一样的
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
// objc_selector没有找到具体实现,推测只是函数名称,作为identifier使用
typedef struct objc_selector* SEL;
// IMP
typedef void (*IMP)(void /* id, SEL, ... */ );
值得一提的是,SEL维护在一个全局的表中,全局唯一。不同类中相同名字的SEL是相同的,IMP不同,所以需要使用Method来做映射。
我们以AObject中的静态方法为例staticMethod
为例,其编译之后的结构体如下:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_AObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"staticMethod", "v16@0:8", (void *)_C_AObject_staticMethod}}
};
接着根据_OBJC_$_CLASS_METHODS_AObject
去初始化元类的class_ro_t
(bits的一部分,我们这个类比较简单,编译后没有用到的部分去除了)
static struct _class_ro_t _OBJC_METACLASS_RO_$_AObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1, sizeof(struct _class_t), sizeof(struct _class_t),
0,
"AObject",
(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_AObject,
0,
0,
0,
0,
};
再然后去初始化元类的信息。
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_AObject __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_NSObject,
0, // &OBJC_METACLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_AObject,
};
同理,使用实例方法去初始化类的信息。
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[7];
} _OBJC_$_INSTANCE_METHODS_AObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
7,
{{(struct objc_selector *)"initWithValue:", "@24@0:8@16", (void *)_I_AObject_initWithValue_},
{(struct objc_selector *)"setPrivateValue:", "v24@0:8@16", (void *)_I_AObject_setPrivateValue_},
{(struct objc_selector *)"privateValue", "@16@0:8", (void *)_I_AObject_privateValue},
{(struct objc_selector *)"publicValue", "@16@0:8", (void *)_I_AObject_publicValue},
{(struct objc_selector *)"setPublicValue:", "v24@0:8@16", (void *)_I_AObject_setPublicValue_},
{(struct objc_selector *)"publicValue", "@16@0:8", (void *)_I_AObject_publicValue},
{(struct objc_selector *)"setPublicValue:", "v24@0:8@16", (void *)_I_AObject_setPublicValue_}}
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_AObject __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_AObject,
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_AObject,
};
搞到这里,类的设计基本就清晰了:类是结构体,类的每一部分也都是结构体。
最后在setup
函数中对类进行复制,设置其他的变量。
// 可以根据这个体会下上面那张老生常谈的图:)
static void OBJC_CLASS_SETUP_$_AObject(void ) {
OBJC_METACLASS_$_AObject.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_AObject.superclass = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_AObject.cache = &_objc_empty_cache;
OBJC_CLASS_$_AObject.isa = &OBJC_METACLASS_$_AObject;
OBJC_CLASS_$_AObject.superclass = &OBJC_CLASS_$_NSObject;
OBJC_CLASS_$_AObject.cache = &_objc_empty_cache;
}
将函数指针注册,初始化Objc的时候进行统一调用。
PS:减少OC无用类,即可减少数组中的函数指针个数,进而优化启动时间。
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CLASS_SETUP[] = {
(void *)&OBJC_CLASS_SETUP_$_AObject,
};
行文至此,我们可以简单总结一下:AObject* obj = [AObject new];
实际上是创建了一个OBJC_CLASS_$_AObject的结构体指针。
类Class中存储了实例方法(的地址),元类MetaClass中存储了静态方法(的地址)。类和元类、类的继承结构其实是在Objc初始化时调用setup函数进行赋值的。
属性property本质上就是成员变量ivar,但在其上做了一些字符串编码,用于表示属性的一些特性。eg: copy, nonatomic。
talk is cheap,看下下面的代码就明白啦,以AObject为例。
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
struct _prop_t {
const char *name;
const char *attributes;
};
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_AObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_AObject$_privateValue, "_privateValue", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_AObject$_publicValue, "_publicValue", "@\"NSString\"", 3, 8}}
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_AObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"publicValue","T@\"NSString\",C,N,V_publicValue"}}
};
消息(函数)则是以method_t结构体的形式存储在class_ro_t结构体中的method_list中。
关于对象的生命周期,我们知道,C语言需要手动管理内存的开辟与释放,所以OC需要一套内存管理机制。以前是手动管理,MRC(Manual Reference Count),现在是自动管理,ARC(Automatic Reference Count)。这部分主要是介绍对象模型,后面会单开一节介绍内存管理。
对象模型-isa
上文中,我们简单了解了下class_ro_t
和isa
,下面我们详细介绍下这部分。
class_data_bits
Class_data_bits是对class_rw_t的封装,通过bits & FAST_DATA_MASK取到。
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
class_rw_t是对class_ro_t的封装,存储了类的一些可读写的信息。方法列表、属性列表、协议列表都是二维数组(基于二维数组实现了分类的机制)。
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; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
class_ro_t表示类的只读信息,包含成员变量列表,类名等,是类最初始的内容。baseMethods、baseProtocols、ivars、properties都是一维数组。
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
当程序运行时,会在Objc初始化的方法中经过一系列的函数,在realizeClass()方法中将class_ro_t和分类的内容合并,存储在class_rw_t里。
isa
isa在64位系统上是一个联合体。其中33位用于存储class/meta-class的内存地址信息。其余位置存储一些特定的信息。
struct objc_object {
Class isa; // 在 arm64 架构之前
};
struct objc_object {
private:
isa_t isa; // 在 arm64 架构开始
};
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL // 用来取出 Class、Meta-Class 对象的内存地址
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 0:代表普通的指针,存储着 Class、Meta-Class 对象的内存地址
// 1:代表优化过,使用位域存储更多的信息
uintptr_t has_assoc : 1; // 是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1; // 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
uintptr_t shiftcls : 33; // 存储着 Class、Meta-Class 对象的内存地址信息
uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否有被弱引用指向过,如果没有,释放时会更快
uintptr_t deallocating : 1; // 对象是否正在释放
uintptr_t has_sidetable_rc : 1; // 如果为1,代表引用计数过大无法存储在 isa 中,那么超出的引用计数会存储在一个叫 SideTable 结构体的 RefCountMap(引用计数表)哈希表中
uintptr_t extra_rc : 19; // 里面存储的值是引用计数 retainCount - 1
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
};
对象模型-分类
这部分介绍下分类的实现原理。我们现在为AObject写一个分类,代码如下所示。
@interface AObject (extension)
@property (nonatomic, strong) NSObject* bObject;
- (void)extensionMethod;
@end
const static void* bObjectKey = @"bObjectKey";
@implementation AObject (extension)
- (void)setBObject:(NSObject *)bObject {
objc_setAssociatedObject(self, bObjectKey, bObject, OBJC_ASSOCIATION_RETAIN);
}
- (NSObject *)bObject {
return objc_getAssociatedObject(self, bObjectKey);
}
- (void)extensionMethod {
// 分类方法实现
}
@end
接着同样编译成C代码,相较之前多了如下部分。
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_AObject_$_extension __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"setBObject:", "v24@0:8@16", (void *)_I_AObject_extension_setBObject_},
{(struct objc_selector *)"bObject", "@16@0:8", (void *)_I_AObject_extension_bObject},
{(struct objc_selector *)"extensionMethod", "v16@0:8", (void *)_I_AObject_extension_extensionMethod}}
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_AObject_$_extension __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"bObject","T@\"NSObject\",&,N"}}
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_AObject;
static struct _category_t _OBJC_$_CATEGORY_AObject_$_extension __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"AObject",
0, // &OBJC_CLASS_$_AObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_AObject_$_extension,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_AObject_$_extension,
};
static void OBJC_CATEGORY_SETUP_$_AObject_$_extension(void ) {
_OBJC_$_CATEGORY_AObject_$_extension.cls = &OBJC_CLASS_$_AObject;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_AObject_$_extension,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_AObject,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_AObject_$_extension,
};
和class_t类似,编译后生成了分类的方法列表、属性列表(但是没有ivar),也在OBJC_CATEGORY_SETUP
注册了回调。会在Objective-C初始化时进行类和分类的合并。类的基本信息class_ro_t和_category_t进行合并,最后组装成了class_rw_t。合并的代码里,分类的method_list会在本类的前面,这样在查找的时候就会优先被找到,相当于"覆盖"了原来的实现。
消息机制
在上文中,可以知道OC对象的方法,在C语言层面都是静态函数。C静态函数参数为结构体指针、SEL、真实参数(如有)。
Objective-C中的方法调用,使用的是message_send。
// 参数一:对象或者叫消息接受者
// 参数二:SEL
// 可变长参数:SEL参数
void objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
函数参数了列表和OC方法编译后的格式一样。message_send调用过程分为消息调用
、动态消息解析
、消息转发
三个阶段。
大致流程如下图所示。图来自师大小海腾,本文参考了他很多的内容。
![](https://img.haomeiwen.com/i12417244/6fadd350b5c998a4.png)
消息调用
源码分析的网上有很多,这里只是简单说下实现。
1、先根据消息接受者的类型是instance还是class,决定是从Class还是MetaClass中查找。
2、先从cache中找,找到了直接调用。没有就从class_rw_t的方法列表查找。
2、class_rw_t的方法列表未排序就遍历查找、已排序就二分查找。找到了就调用,并将方法缓存在cache中。
3、未找到就从superClass继续查找,查找流程同2、3。
4、superClass遍历到头了还是找不到,进行动态消息解析阶段。
动态消息解析
这一阶段会根据实例方法、类方法来调用如下方法(按需重写)。
+(BOOL)resolveInstanceMethod:(SEL)sel;
+(BOOL)resolveClassMethod:(SEL)sel;
一般在方法内部搭配addMethod来动态添加方法实现。
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
resolveInstanceMethod返回YES后,会重新进行一次查找,从cache开始查。查完没找到方法,并且也经过一次动态消息解析了,那么开始进行消息转发阶段。
消息转发
这一步分为快速转发Fast forwarding
和正常转发 Normal forwarding
。
Fast forwarding需要返回一个不同于自身的备用接受者。
+/- (id)forwardingTargetForSelector:(SEL)sel {
return 备用接收者;
}
Normal forwarding需要重写两个方法。methodSignatureForSelector
需要返回一个具体的方法签名,Runtime会根据方法签名来生成一个具体的NSInvocation对象。forwardInvocation
中可以使用生成的NSInvocation对象来做一些自定义操作。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return 方法签名;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 一些自定义操作
}
看一下系统默认实现,经典的unrecognized selector sent to instance
错误~
// NSObject.mm
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
+ (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
对象生命周期
简单来讲,创建一个实例对象,以及实例对象的析构操作,本质上对应的是内存的开辟malloc
与回收free
。
引用计数
Objective-C中的内存管理是基于引用计数来实现的,将对象的引用计数保存起来,当引用计数为0时,就将其释放。引用计数保存在isa中。前文提过,isa是一个64位的联合体,其中33位存储着 Class、Meta-Class 对象的内存地址信息,引用计数使用19位进行存储,如果19位不够实用,则存储在SideTable
中。
早期的引用计数需要开发者手动控制,称为MRC,Manual Reference Counting。
现在则是编译器处理了,称为ARC,Automatic Reference Counting。
ARC几乎让开发者对于内存管理无感知,但少数情况下,处理与CoreFoundation桥接时(Toll-Free Bridging),会处理到一些手动内存管理的情况。
这里不做过多介绍,想要了解可以搜Toll-Free Bridging,相关文章很多。
自动释放
引用计数为0的时候,对象就会立即释放,可以使用@autoreleasepool来推后释放时机。MRC中使用[object autorelease],并不会立即使得object的引用计数-1,而是加入到autoreleasepool中。在runloop快要结束时,autoreleasepool会将其内部对象统一调用[object release]。
还有一种方式是使用@autoreleasepool{}包裹代码,这样当代码出了@autoreleasepool作用域时,就会release。假设一个循环中创建了大量临时对象,通过这种手段可以避免内存使用量快速增高,达到内存削峰的目的。
__weak
引用计数无法处理双向持有的问题,这个时候需要使用弱引用来打破内存的持有环。
TaggedPointer
对于一些量比较小的数据,可以直接存在指针里,不需要在堆上开辟内存。
一些没有提及的部分
文章写到这里,基本上也就是蜻蜓点水的过了一遍Objective-C的一些设计吧,比较宏观,没有做深入细致的讲解。
有关self和super、属性关键字&修饰符、KVO&KVC、方法缓存查找&添加、对象析构过程、messageSend的具体代码过程、initialize&load、关联对象、weak原理、AutoReleasePool原理等,本来想写,字太多就跳过去了(不面试我记不住)。
如果有需要,大家可以拿着关键字去搜一下,很多博客。
网友评论