在我们前面的几章中,分析了OC的runtime一些底层的数据结构以及实现机制。今天,我们就从一个OC对象的生命周期的角度,来解析在runtime底层是如何实现的。
我们创建一个对象(或对象引用)有几种方式?
Student *student = [[Student alloc] init];
Student *student2 = [Student new];
__weak Student *weakStudent = [Student new];
NSDictionary *dict = [[NSDictionary alloc] init];
NSDictionary *autoreleaseDict = [NSDictionary dictionary];
有很多种方式,我们就来依次看一下这些方式的背后实现。
alloc
要创建一个对象,第一步就是需要为对象分配内存。在创建内存时,我们会调用alloc
方法。查看runtime的NSObject +alloc
方法实现:
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
alloc方法会将self作为参数传入_objc_rootAlloc(Class cls) 方法中
,注意,因为alloc是一个类方法,因此此时的self是一个Class类型。
最终该方法会落脚到callAlloc
方法。
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) { // 如果可以fast alloc,走这里
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize()); // 直接调用 calloc方法,申请1块大小为bits.fastInstanceSize()的内存
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else { // 如果不可以fast alloc,走这里,
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0); // (1)需要读取cls 的class_ro_t 中的instanceSize,并使之大于16 byte, Because : CF requires all objects be at least 16 bytes. (2)initInstanceIsa
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
在callAlloc方法里面,做了三件事:
- 调用
calloc
方法,为类实例分配内存 - 调用obj->initInstanceIsa(cls, dtor)方法,初始化obj的isa
- 返回obj
在第一件事中,调用calloc
方法,你需要提供需要申请内存的大小。在OC中有两条分支:
(1)can alloc fast
(2)can’t alloc fast
对于可以alloc fast的类,应该是经过编译器优化的类。这种类的实例大小直接被放到了bits
中
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
...
}
而不需要通过bits找到class_rw_t->class_ro_t->instanceSize
。省略了这一条查找路径,而是直接读取位值,其创建实例的速度自然比不能alloc fast的类要快。
而对于不能alloc fast的类,则会进入第二条路径,代码会通过上面所说的通过bits找到class_rw_t->class_ro_t->instanceSize
来确定需要申请内存的大小。
当申请了对象的内存后,还需要初始化类实例对象的isa成员变量:
obj->initInstanceIsa(cls, hasCxxDtor);
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
assert(!cls->instancesRequireRawIsa());
assert(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) { // 如果没有启用isa 优化,则直接将cls赋值给isa.cls,来表明当前object 是哪个类的实例
isa.cls = cls;
} else { // 如果启用了isa 优化,则初始化isa的三个内容(1) isa基本的内容,包括nonpointer置1以及设置OC magic vaule (2)置位has_cxx_dtor (3) 记录对象所属类的信息。 通过 newisa.shiftcls = (uintptr_t)cls >> 3;
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
结合代码注释,以及我们在Objective-C runtime机制(5)——iOS 内存管理中提到的关于isa的描述,应该可以理解isa初始化的逻辑。
init
我们再来看一下init
方法:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
实现很简单,就是将自身返回,没有做任何其他操作。
__strong
Student *student = [[Student alloc] init];
Student *student2 = [Student new];
在等号的左边,我们通过alloc
和new
的方式创建了两个OC对象。而在右面,我们通过Student *
的方式来引用这些对象。
在OC中,对对象所有的引用都是有所有权修饰符
的,所有权修饰符会告诉编译器,该如何处理对象的引用关系。如果代码中没有显示指明所有权修饰符,则默认为__strong
所有权。
因此上面代码实际是:
__strong Student *student = [[Student alloc] init];
__strong Student *student2 = [Student new];
对于new方法,苹果的文档解释为:
Allocates a new instance of the receiving class, sends it an initmessage, and returns the initialized object.
其实就是alloc + init 方法的简写。因此,这里的两种创建实例对象的方式可以理解是一个。
那么,当所有权修饰符是__strong时,runtime是如何管理对象引用的呢?
runtime会通过 void objc_storeStrong(id *location, id obj)
方法来处理__strong 引用。 这里的location就是引用指针,即Student *student
,而obj就是被引用的对象,即Student实例
。
void objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj); //1. retain obj
*location = obj; //2. 将location 指向 obj
objc_release(prev); //3. release location之前指向的obj
}
代码逻辑很简单,主要是调用了objc_retain和objc_release两个方法。
我们分别来看一下它们的实现。
objc_retain
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *,