一、问题描述
// 设置输入最大字数长度限制
- (void)setInputLimitMaxLength:(NSUInteger)inputLimitMaxLength
{
objc_setAssociatedObject(self,
@selector(checkAndFilterBeyondLimitsCharacters),
@(inputLimitMaxLength),
OBJC_ASSOCIATION_ASSIGN);
}
// 获取输入最大字数长度限制
- (NSUInteger)getInputLimitMaxLength
{
return [objc_getAssociatedObject(self, @selector(checkAndFilterBeyondLimitsCharacters)) unsignedIntegerValue];
}
ARC模式下,在上述设置输入字数长度限制方法setInputLimitMaxLength:
方法中,objc_AssociationPolicy类型我设置的是OBJC_ASSOCIATION_ASSIGN
。
- 32位iphone机器上inputLimitMaxLength传15时,调用
getCurrentInputLimitMaxLength
方法时直接崩溃EXC_BAD_ACCESS
,报错:***\** -[CFNumber retain]: message sent to deallocated instance 0x7c3d9850**
,可看出self绑定的@(inputLimitMaxLength)
已经释放,野指针访问崩溃;但是inputLimitMaxLength传6时调用方法运行正常。 - 64位iphone机器上inputLimitMaxLength传15或6时调用
getCurrentInputLimitMaxLength
方法运行正常,这是啥情况,懵逼了是不是?
二、寻找原因
创建取值-16~16的NSNumber实例对象,在不同的视图控制器(UIViewController)上运行如下代码,打印内存地址发现:
for (int i = -16; i <= 16; i++) {
for (int j = 0; j < 2; j++) {
NSLog(@"%d:%p", i, [NSNumber numberWithInteger:i]);
}
}
(1) 32位机器上结果如下:
-1~12内存地址是一样的,创建的实力对象存储在内存共享区,永远不会被销毁。而只要大于12或小于-1就是正常的创建在堆上的对象,系统根据引用计数管理对象是否回收。截图如下
(2) 64位机器上结果如下:
-16~16内存地址是一样的,创建的实力对象存储在内存共享区,永远不会被销毁。截图如下
可见:ARC模式下是系统在32bit设备上对NSNumber
类型的对象做的优化不够彻底,然后我们在使用关联对象时内存修饰符又使用不当,造成了崩溃的问题。猜测对于32bit的设备,如同时存在大量的共享内存会比较消耗资源,因此只对-1~12这少数的几个数做了优化,而出问题时候我们传入的参数刚好大于12,所以就掉进了坑里。而64bit设备存放在常量区的正常范围区间比32bit大,所以没有Crash。
三、解决办法以及结论
将OBJC_ASSOCIATION_ASSIGN
改为OBJC_ASSOCIATION_RETAIN
,这样对被绑定的NSNumber实例对象有一个强引用,被关联绑定的对象就不会释放,在没解除绑定时生命周期和绑定源(self)对象相同了。因此,我认为既然被关联的都是对象,那么绝大部分时候都应该使用OBJC_ASSOCIATION_RETAIN
,所以除了一些特殊情况外,运行时关联对象修饰符建议使用OBJC_ASSOCIATION_RETAIN
。