为什么OC对象至少需要16个字节以及对桥接关键字的理解。

背景

文字源自于读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操作。

欢迎大家指正以及补充。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享