本文从2018年12月15日周末开始,至2018年末,历经3个周末,6天时间。
引用计数原理?
- 引用计数存储在isa中和sidetable中。
- 弱引用变量的数量存储在sidetable中。
先来看arm64架构下isa的定义
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
//isa变量是否为非指针
uintptr_t nonpointer : 1;
//是否有关联对象
uintptr_t has_assoc : 1;
//是否有析构函数
uintptr_t has_cxx_dtor : 1;
//当前对象对应的类指针右移3位后的地址
uintptr_t shiftcls : 33;
//用于在调试时分辨对象是否未完成初始化
uintptr_t magic : 6;
//是否有弱引用变量指向本实例
uintptr_t weakly_referenced : 1;
//是否正在释放
uintptr_t deallocating : 1;
//是否需要散列表存储引用计数。当extra_rc存储不下时,该值为1
uintptr_t has_sidetable_rc : 1;
//引用计数数量,实际的引用计数减一
uintptr_t extra_rc : 19;
};
};
再看SideTable结构
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; //引用计数
weak_table_t weak_table; //弱引用表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
验证isa存储的引用计数
建一个iOS项目,新建一个空类Person继承NSObject.
看测试代码
//数值转二进制字符串
NSString* binaryFromInteger(NSUInteger integer){
NSString *ret = @"";
NSUInteger x = integer;
while ( x > 0 ) {
// 若i=14,即二进制1110,则i&1为 1110 & 0001 结果为0000
ret = [[NSString stringWithFormat:@"%ld",x&1] stringByAppendingString:ret];
x = x >> 1;
}
return ret;
}
//不直接传递NSObject对象,是为了防止引用计数+1
void printIsaValueWithObject(struct objc_object* obj){
NSObject *oj = (__bridge NSObject*)obj;
Class cls = [oj valueForKey:@"isa"];
//isa 的值,二进制形式
NSString *bin =
binaryFromInteger((NSUInteger)(cls));
NSLog(@"isa:%@",bin);
//obj的引用计数
NSNumber *retainCount = [oj valueForKey:@"retainCount"];
NSLog(@"retainCount:%@",retainCount.stringValue);
}
void test(){
XWPerson *per = [XWPerson new];
printIsaValueWithObject(per);
NSMutableArray *arr1 = [NSMutableArray new]; //①
for( NSUInteger i=0; i<524286; i++ ){
XWPerson *per1 = per;
[arr1 addObject:per1];
if( i==0 ){
printIsaValueWithObject(per); //②
}
}
XWPerson *per2 = per;
printIsaValueWithObject(per);
XWPerson *per3 = per; //③
printIsaValueWithObject(per);
NSLog(@"%@",per); //④
}
分别在①②③④处打断点,然后打印per所指内存中的二进制值,前8个字节即为per实例的isa的值。按照isa_t的结构,解析isa。
运行结果:
isa:11010000100000001000110111101110001100001
retainCount:1
(lldb) x per
0x2826c4170: 61 dc 1b 01 a1 01 00 00 00 00 00 00 00 00 00 00 a...............
isa:10000011010000100000001000110111101110001100001
retainCount:3
(lldb) x per
0x2826c4170: 61 dc 1b 01 a1 41 00 00 00 00 00 00 00 00 00 00 a....A..........
isa:1111111111111111111000011010000100000001000110111101110001100001
retainCount:524288
(lldb) x per
0x2826c4170: 61 dc 1b 01 a1 e1 ff ff 00 00 00 00 00 00 00 00 a...............
isa:1000000000000000000100011010000100000001000110111101110001100001
retainCount:524289
(lldb) x per
0x2826c4170: 61 dc 1b 01 a1 11 00 80 00 00 00 00 00 00 00 00 a...............
根据isa的结构体定义,可知其长度为64位,引用计数占19位,所以打印出的isa二进制的高19位,即为引用计数的值减一。分别看下引用计数为1和3时,isa的值。
引用计数为1时,isa内存中的值61 dc 1b 01 a1 01 00 00
对应的二级制:11010000100000001000110111101110001100001
补全64位:
0000 0000 0000 0000 0000 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
前19位,为引用计数的值-1. 那么看一下前19位的值:
0000 0000 0000 0000 000 结果为0,刚好符合引用计数减一.
引用计数为3时,isa内存中的值61 dc 1b 01 a1 41 00 00
对应的二进制:
0000 0000 0000 0000 0100 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
前19位,值为 2. 符合引用计数减一.
在这两种情况下,isa的对应的第20位的值都为0,表示未使用sidetable
既然isa中引用计数值占19位,那么当19位都占满时和占满之后isa值的情况如何呢?下面来看下这两种情况。
#######当isa的前19位都为1时,isa指针的值:61 dc 1b 01 a1 e1 ff ff
对应的二进制:
1111 1111 1111 1111 1110 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
此时引用计数为2的19次方加1,也就是524288.
注意此时的isa的第20位,依旧为0.
接下来,我们再增加一个对per对象的引用。则引用计数为524289.
此时,isa的值为:61 dc 1b 01 a1 11 00 80
对应的二进制:
1000 0000 0000 0000 0001 0001 1010 0001
0000 0001 0001 1011 1101 1100 0110 0001
此时isa的高19位的值为 1000 0000 0000 0000 000
第20位值为1.说明启用了SideTabel存储引用计数。
如果,我们再次增加引用计数的话,isa的高19位的值依然会增加。
可以得出结论:当isa的存储引用计数的19位占满之后,其最高位变为1,其他位变0. 若继续增加引用计数,这19位会依旧继续增加值。
注意问题
1.只有自定义的继承NSObject类,对应实例的isa,才能查看到其存储的引用计数。
2.使用的时候,要用真机调试。模拟器和真机有区别。
思考问题?
1.strong、retain如何增加引用计数?
2.weak 实现原理?
3.assign 与weak 区别?
4.引用计数为0的对象,何时释放?
5.autorelasepool原理以及何时释放?