文档更新说明
- 最后更新 2020年04月19日
- 首次更新 2020年04月19日
前言
平时的开发里, CoreFoundtion框架虽然说用的不多, 不过还是会经常接触到, 由于CoreFoundtion框架不支持ARC, 涉及到的相关代码都需要手动管理引用计数, 所以还是有必要了解一下CF对象的引用计数和OC对象的引用计数是如何正确获取到的.
OC对象和CF对象的关系
苹果实现了一套技术, Toll-Free Bridging, 允许让部分OC对象和CF对象互相转换, 这部分技术不是本文讨论的重点就不说了. 其中OC对象和CF对象都有各自的引用计数存储方法, 下面写上对应的源码简单了解一下
OC对象的引用计数存储方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| inline uintptr_t objc_object::rootRetainCount() { if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock(); isa_t bits = LoadExclusive(&isa.bits); ClearExclusive(&isa.bits); if (bits.nonpointer) { uintptr_t rc = 1 + bits.extra_rc; if (bits.has_sidetable_rc) { rc += sidetable_getExtraRC_nolock(); } sidetable_unlock(); return rc; }
sidetable_unlock(); return sidetable_retainCount(); }
|
CF对象引用计数存储方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| CFIndex CFGetRetainCount(CFTypeRef cf) { if (NULL == cf) { CRSetCrashLogMessage("*** CFGetRetainCount() called with NULL ***"); HALT; } uint32_t cfinfo = *(uint32_t *)&(((CFRuntimeBase *)cf)->_cfinfo); if (cfinfo & 0x800000) { CFTypeID typeID = (cfinfo >> 8) & 0x03FF; CFRuntimeClass *cfClass = __CFRuntimeClassTable[typeID]; uint32_t (*refcount)(intptr_t, CFTypeRef) = cfClass->refcount; if (!refcount || !(cfClass->version & _kCFRuntimeCustomRefCount) || (((CFRuntimeBase *)cf)->_cfinfo[CF_RC_BITS] != 0xFF)) { HALT; } #if __LP64__ if (((CFRuntimeBase *)cf)->_rc != 0xFFFFFFFFU) { HALT; } #endif uint32_t rc = refcount(0, cf); #if __LP64__ return (CFIndex)rc; #else return (rc < LONG_MAX) ? (CFIndex)rc : (CFIndex)LONG_MAX; #endif } uint64_t rc = __CFGetFullRetainCount(cf); return (rc < (uint64_t)LONG_MAX) ? (CFIndex)rc : (CFIndex)LONG_MAX; }
|
正确获取引用计数
上面两处代码展示了OC对象和CF对象完全不同的引用技术存储方式, 那么如何正确获取两种类型的对象的引用计数呢?
实际测试可以得到下面的结论
CF层的对象, 只能通过CFGetRetainCount方法获取正确的引用计数.
1 2 3 4 5 6 7 8 9 10 11 12 13
| extern uintptr_t _objc_rootRetainCount(id obj);
CFStringRef strs[] = {CFSTR("1"), CFSTR("2")}; CFArrayRef cfArr = CFArrayCreate(kCFAllocatorDefault, (void *)strs, 2, NULL); CFRetain(cfArr); CFRetain(cfArr); CFRetain(cfArr); NSArray *arr = (__bridge NSArray *)(cfArr); NSArray *arr2 = arr;
NSLog(@"retainCount:%ld", (long)CFGetRetainCount(cfArr)); NSLog(@"retainCount:%lu", _objc_rootRetainCount(arr));
|
OC层对象, 可以通过_objc_rootRetainCount这个方法得到正确的引用计数, 也可以通过CFGetRetainCount方法.
1 2 3 4 5 6
| NSArray *arr = @[@11111]; NSArray *arr2 = arr; NSArray *arr3 = arr; CFArrayRef cfArr = (__bridge CFTypeRef)(arr); NSLog(@"retainCount:%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(arr))); NSLog(@"retainCount:%lu", _objc_rootRetainCount(arr));
|
进一步理解TFB技术
上面的结论看起来很神奇, 为什么OC对象能通过CFGetRetainCount方法获取引用计数呢? 原来CFGetRetainCount
方法内部会判断当前传入的对象是不是OC对象, 如果是的话会直接调用OC对象的retainCount方法, 最终objc_object::rootRetainCount
方法会被调用, 所以就有了上面的结论.
为了进一步证明这个说法, 我在对应方法上都打了断点, 证明了上面的推测.
1 2 3 4
| (lldb) breakpoint set --name CFGetRetainCount Breakpoint 4: where = CoreFoundation`CFGetRetainCount, address = 0x00007fff35eca184 (lldb) breakpoint set --method rootRetainCount Breakpoint 5: where = libobjc.A.dylib`objc_object::rootRetainCount() + 20 at objc-object.h:713:9, address = 0x00000001003921a4
|
调用CFGetRetainCount方法, 内部会判断当前对象的类型是不是真正的CF类型, 如果不是则会调用向对象发送retainCount消息, 走OC运行时那套逻辑

下面是objc_object::rootRetainCount()
断点处的调用栈

下面是直接调用全局函数_objc_rootRetainCount
的调用栈

扩展到Release, Retain方法
Retain操作也是相同的原理
调用CFRetain方法, 传入一个NSArray对象, (提前打好打断点如下)
1 2
| (lldb) breakpoint set --method rootRetain Breakpoint 4: 7 locations.
|

可以看到执行CFRetain方法, 会跳到objc_object::rootRetain方法上, 说明CFRetain内部同样是判断了类型, 如果属于OC对象的, 则还是走OC运行时这一套
如果对象真的是CF类型的话, 则不会跳到objc_object::rootRetain上, 而是走CF自己的一套计数逻辑
Release操作也是相同的原理
1 2
| (lldb) breakpoint set --method objc_object::release Breakpoint 4: where = libobjc.A.dylib`objc_object::release() + 32 at objc-object.h:530:5, address = 0x00000001003912f0
|

这里就不多说了, 情况和Retain是一模一样的
一些疑惑
上面写出的CFGetRetainCount
纯C源码, 其实我看了很久也没看出来他哪里判断了对象类型然后确认是OC对象就调用msgSend函数发送OC消息, 但是实际上运行确实是如此, 包括下文的CFRetain, Release方法都有同样的疑惑.
也有可能是我拿的CF源码不是目前系统运行的版本, 知道的朋友可以指点一下我.
推荐阅读
深入理解Toll-Free Bridging
CoreFoundation源码
OC Runtime源码