运行时绑定NSNumber类型传参Crash

一、问题描述

// 设置输入最大字数长度限制
- (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

  1. 32位iphone机器上inputLimitMaxLength传15时,调用getCurrentInputLimitMaxLength方法时直接崩溃EXC_BAD_ACCESS,报错:***\** -[CFNumber retain]: message sent to deallocated instance 0x7c3d9850**,可看出self绑定的@(inputLimitMaxLength)已经释放,野指针访问崩溃;但是inputLimitMaxLength传6时调用方法运行正常。
  2. 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就是正常的创建在堆上的对象,系统根据引用计数管理对象是否回收。截图如下

运行时绑定NSNumber类型传参Crash_第1张图片
32bit设备地址截图.png

(2) 64位机器上结果如下:

-16~16内存地址是一样的,创建的实力对象存储在内存共享区,永远不会被销毁。截图如下

运行时绑定NSNumber类型传参Crash_第2张图片
64bit设备地址截图.png

可见:ARC模式下是系统在32bit设备上对NSNumber类型的对象做的优化不够彻底,然后我们在使用关联对象时内存修饰符又使用不当,造成了崩溃的问题。猜测对于32bit的设备,如同时存在大量的共享内存会比较消耗资源,因此只对-1~12这少数的几个数做了优化,而出问题时候我们传入的参数刚好大于12,所以就掉进了坑里。而64bit设备存放在常量区的正常范围区间比32bit大,所以没有Crash。

三、解决办法以及结论

OBJC_ASSOCIATION_ASSIGN改为OBJC_ASSOCIATION_RETAIN,这样对被绑定的NSNumber实例对象有一个强引用,被关联绑定的对象就不会释放,在没解除绑定时生命周期和绑定源(self)对象相同了。因此,我认为既然被关联的都是对象,那么绝大部分时候都应该使用OBJC_ASSOCIATION_RETAIN,所以除了一些特殊情况外,运行时关联对象修饰符建议使用OBJC_ASSOCIATION_RETAIN

四、运行时绑定NSNumber类型使用示例

示例:UITextField输入字数限制实现

你可能感兴趣的:(运行时绑定NSNumber类型传参Crash)