背景
文字源自于读runtime里的一段源码中的一条注释,在计算对象所需内存的大小时,有一段特殊的逻辑,如果对象所需的内存大小不足16个字节,也依然会分配16个字节。并且给了一条注释说明了原因,是因为CF层需要所有的object都至少需要16个字节。
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
复制代码
CF即CoreFoundation层,是c语言的API,可以跟Foundation框架桥接。
这里注释提示了我们,这里的处理跟两层之间的桥接有关系。
CF层需要至少16个字节
上面给到线索说CF要求至少16个字节,所以我们可以看一下CF对象的创建过程。发现很多的CF对象都是通过
_CFRuntimeCreateInstance
函数来分配内存的。在这个函数声明的header文件中,跟着一段注释,找到其中一句话:
A CFRuntimeBase structure is initialized at the beginning of the returned instance.
是说每一个实例开始的地方都会有一个CFRuntimeBase的结构体。搜一下这个结构体的具体定义:
/* All CF "instances" start with this structure. Never refer to
* these fields directly -- they are for CF's use and may be added
* to or removed or change format without warning. Binary
* compatibility for uses of this struct is not guaranteed from
* release to release.
*/
typedef struct __CFRuntimeBase {
uintptr_t _cfisa; // 指针 64位系统8个字节
uint8_t _cfinfo[4]; // 4个字节
#if __LP64__
uint32_t _rc; // 64位系统的话,再加4个字节 没啥用 是为了按指针长度对齐的
#endif
} CFRuntimeBase;
复制代码
发现64位系统中,CFRuntimeBase
正好占用16个字节。每个CF对象的开始都是一个CFRuntimeBase
结构体,可以认为CFRuntimeBase
是所有CF层对象的基类。基类都需要占用16个字节,所以CF层的对象至少需要16个字节。
原因
将OC对象桥接为一个CF对象后,对这个CF对象之后的很多操作都是类型强转为CFRuntimeBase来处理的,比如可以通过CFGetRetainCount
函数获取对象当前的引用计数,在这些操作中,会访问CFRuntimeBase
的_cfinfo
或者_rc
字段,如果OC层分配的对象不够16个字节,在一些极端的情况下回导致访问到无效内存,引发段错误。
桥接中的内存管理
桥接关键字有
标题 | 使用 |
---|---|
__bridge | 既可以OC到CF,也可以CF到OC |
__bridge_retained | OC到CF |
__bridge_transfer | CF到OC |
__bridge可以做到双向桥接,其余两个反方向使用会报错。
OC为ARC环境下的内存管理
__bridge的语义是不会对ARC的管理语义做额外操作。
即:obj持有对象,如果是通过new/copy/init/alloc等开头的方法获取,则直接持有,如果是通过别的方法获取,则发送retain消息。在超出作用域之后,发送release消息。
使用__bridge
从OC到CF,obj通过new获取对象,引用计数为1,超出作用域调用release引用计数-1。
NSObject *obj = [NSObject new];
CFTypeRef ref = (__bridge CFTypeRef)(obj);
复制代码
使用__bridge
从CF到OC,obj通过其他方法持有对象,对对象发送retain消息,作用域结束,发送release消息。
NSObject *obj = (__bridge NSObject *)(ref);
复制代码
剩下的两个是对__bridge的语义的补充,处理特殊情况,也就是会跟正常__bridge的情况有点不一样。
使用__bridge_retained
从OC到CF(也可以使用CFBridgingRetain),在转换时,会对OC对象额外做一个retain操作,是引用计数加1,这样CF层的ref也就持有了对象,当ref不再使用对象时,通过调用CFRelease操作来释放对象。命名中的retained也很说明问题,ARC会对对象多做一次retain操作,ARC中也不能手动调用release方法,所以只能在CF层调用CFRelease使引用计数平衡。
NSObject *obj = [NSObject new];
CFTypeRef ref = (__bridge_retained CFTypeRef)(obj);
复制代码
使用__bridge_transfer
从CF到OC(也可以使用CFBridgingRelease),在转换时,obj持有对象,但是不会做retain操作,超出作用域,会向obj发送release消息。命名中的transfer也很形象的说明了问题,obj持有对象不是自己通过增加引用计数得到的,而是从CF层转移过来的,在超出作用域的时候,ARC直接调用release释放。
CFBridgingRetain及CFBridgingRelease中,retain及release的语义都是对转换之前的对象而言的。
CFBridgingRetain是将OC对象转为CF对象,会对OC的对象多一次retain,增加引用计数。
CFBridgingRelease是将CF对象转为OC对象,相当于对CF层的对象做了一次CFRealse操作。虽然从汇编中,并没有调用CFRealse函数来真正release,但编译器的操作是在OC对象被OC指针持有的时候,没有调用retain操作,在OC指针超出作用域时,调用release方法对引用计数-1,也维持了引用计数的平衡。将CF层持有的引用计数转移给了OC指针持有。这样做的目的是可以少调用一次CFRealse以及一次OC的retain操作。
欢迎大家指正以及补充。