文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. OC对象和CF对象的关系
  4. 4. 正确获取引用计数
  5. 5. 进一步理解TFB技术
  6. 6. 扩展到Release, Retain方法
    1. 6.1. Retain操作也是相同的原理
    2. 6.2. Release操作也是相同的原理
  7. 7. 一些疑惑
  8. 8. 推荐阅读

文档更新说明

  • 最后更新 2020年04月19日
  • 首次更新 2020年04月19日

前言

  平时的开发里, CoreFoundtion框架虽然说用的不多, 不过还是会经常接触到, 由于CoreFoundtion框架不支持ARC, 涉及到的相关代码都需要手动管理引用计数, 所以还是有必要了解一下CF对象的引用计数和OC对象的引用计数是如何正确获取到的.   

OC对象和CF对象的关系

苹果实现了一套技术, Toll-Free Bridging, 允许让部分OC对象和CF对象互相转换, 这部分技术不是本文讨论的重点就不说了. 其中OC对象和CF对象都有各自的引用计数存储方法, 下面写上对应的源码简单了解一下

OC对象的引用计数存储方式

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对象引用计数存储方法

CFIndex CFGetRetainCount(CFTypeRef cf) {
if (NULL == cf) { CRSetCrashLogMessage("*** CFGetRetainCount() called with NULL ***"); HALT; }
uint32_t cfinfo = *(uint32_t *)&(((CFRuntimeBase *)cf)->_cfinfo);
if (cfinfo & 0x800000) { // custom ref counting for object
CFTypeID typeID = (cfinfo >> 8) & 0x03FF; // mask up to 0x0FFF
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; // bogus object
}
#if __LP64__
if (((CFRuntimeBase *)cf)->_rc != 0xFFFFFFFFU) {
HALT; // bogus object
}
#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方法获取正确的引用计数.

// 需要声明方法后才能使用, 毕竟没有引入头文件的
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)); // retainCount:6
NSLog(@"retainCount:%lu", _objc_rootRetainCount(arr)); //retainCount:1

OC层对象, 可以通过_objc_rootRetainCount这个方法得到正确的引用计数, 也可以通过CFGetRetainCount方法.

NSArray *arr = @[@11111];
NSArray *arr2 = arr;
NSArray *arr3 = arr;
CFArrayRef cfArr = (__bridge CFTypeRef)(arr);
NSLog(@"retainCount:%ld", (long)CFGetRetainCount((__bridge CFTypeRef)(arr))); // retainCount:3
NSLog(@"retainCount:%lu", _objc_rootRetainCount(arr)); // retainCount:3

进一步理解TFB技术

上面的结论看起来很神奇, 为什么OC对象能通过CFGetRetainCount方法获取引用计数呢? 原来CFGetRetainCount方法内部会判断当前传入的对象是不是OC对象, 如果是的话会直接调用OC对象的retainCount方法, 最终objc_object::rootRetainCount方法会被调用, 所以就有了上面的结论.

为了进一步证明这个说法, 我在对应方法上都打了断点, 证明了上面的推测.

(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运行时那套逻辑

retaincount

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

nsobjc-retaincount

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

objc_rootRetainCount

扩展到Release, Retain方法

Retain操作也是相同的原理

调用CFRetain方法, 传入一个NSArray对象, (提前打好打断点如下)

(lldb) breakpoint set --method rootRetain
Breakpoint 4: 7 locations.

objc_object-rootRetain

可以看到执行CFRetain方法, 会跳到objc_object::rootRetain方法上, 说明CFRetain内部同样是判断了类型, 如果属于OC对象的, 则还是走OC运行时这一套

如果对象真的是CF类型的话, 则不会跳到objc_object::rootRetain上, 而是走CF自己的一套计数逻辑

Release操作也是相同的原理

(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

objc_object-release

这里就不多说了, 情况和Retain是一模一样的

一些疑惑

上面写出的CFGetRetainCount纯C源码, 其实我看了很久也没看出来他哪里判断了对象类型然后确认是OC对象就调用msgSend函数发送OC消息, 但是实际上运行确实是如此, 包括下文的CFRetain, Release方法都有同样的疑惑.

也有可能是我拿的CF源码不是目前系统运行的版本, 知道的朋友可以指点一下我.

推荐阅读

深入理解Toll-Free Bridging

CoreFoundation源码

OC Runtime源码

文章目录
  1. 1. 文档更新说明
  2. 2. 前言
  3. 3. OC对象和CF对象的关系
  4. 4. 正确获取引用计数
  5. 5. 进一步理解TFB技术
  6. 6. 扩展到Release, Retain方法
    1. 6.1. Retain操作也是相同的原理
    2. 6.2. Release操作也是相同的原理
  7. 7. 一些疑惑
  8. 8. 推荐阅读