Swift3.0朝圣之路-使用Runtime在分类Extension中添加属性

96
溪枫狼
2016.10.12 13:03* 字数 790

我最近自学Swift3.0,由于之前没学过Swift,只能将OC的代码“翻译”成Swift,在此过程慢慢学习Swift,Swift3.0的资料少,遇到了不少坑,今天就介绍一个。

在OC里面,咱给分类添加属性是这么写的,即使用Runtime中的objc_setAssociatedObjectobjc_getAssociatedObject

- (void)setQuickTapEnable:(BOOL)quickTapEnable{
    objc_setAssociatedObject(self, JKSecurityButtonQuickTapEnableKey, @(quickTapEnable), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)quickTapEnable{
    return [objc_getAssociatedObject(self, JKSecurityButtonQuickTapEnableKey) boolValue];
}

这次咱用Swift3.0给ViewController的Extension(相当于OC里面的Category)添加一个属性JKPro,赋值后再取出来打印,练练手。
ViewControll类文件的代码:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.jkPro = "通过类别拓展的属性"
        NSLog(self.jkPro!)
        
        print("标记")
    }
}

ViewControll Extension文件的代码

extension ViewController {
    // 平常写法[不推荐]
    var jkPro: String? {
        set {
            objc_setAssociatedObject(self, "key", newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        
        get {
            return objc_getAssociatedObject(self, "key") as? String
        }
    }
}

你觉得有问题吗? 我告诉你,有
运行多次后会出现随机性的崩溃,也就是说基本上能正常运行和打印结果,但是偶尔会出现崩溃,我也一直没找到根本原因,不过找到了解决方案。
崩溃后会进入下面的界面:

常规写法崩溃

运行10次左右会随机出现崩溃,提示fatal error: unexpectedly found nil while unwrapping an Optional value,也就是中间出现了nil
再看看崩溃点的代码: 解包出错!
常规写法崩溃信息

那就从源头找问题,后面改成下面写法来测试崩溃,运行多次后发现是objc_getAssociatedObject返回的值为nil,导致解包崩溃。
有时正常,有时nil,什么鬼❓❓❓❓❓❓
常规写法崩溃信息

再看看objc_setAssociatedObject函数方法,用到了UnsafeRawPointer类型的参数,没接触过,那就从UnsafeRawPointer入手

public func objc_setAssociatedObject(_ object: Any!, _ key: UnsafeRawPointer!, _ value: Any!, _ policy: objc_AssociationPolicy)

通常咱用字符串来命名以及区分Key值,然而UnsafeRawPointer并没有String参数的init方法,倒是有个Int参数的init方法,但是咱不能用数字做Key吧,队友一看代码能知道啥意思吗?庆幸的是StringhashValue (哈希值)可以返回Int值,而字符串的哈希值和字符串是一一对应的,测试多次都没出现崩溃,完美。
下面就是一种解决方案,应该还有其他的,只是等着大家去发现

    ///  推荐写法
    var jkPro: String? {
        set {
            let key: UnsafeRawPointer! = UnsafeRawPointer.init(bitPattern: "key".hashValue)
            objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        
        get {
            let key: UnsafeRawPointer! = UnsafeRawPointer.init(bitPattern: "key".hashValue)
            let obj: String? = objc_getAssociatedObject(self, key) as? String
            return obj
        }
    }

-------------------------继续改进-------------------------

之前崩溃原因已找到,之所以出现nil,是因为2次使用的Key内存地址不一样导致的,即取值和设值的Key内存地址不一样导致取出nil,解包nil崩溃。
同一字符串的哈希值是一样的,所以方向是对的,于是下午继续优化了下。使用结构体struct作为容器声明不同的Key,以后改也只要一个改地方就行,便于管理,而且相比上午的写法代码量更少,更简洁。
如果有其他的思路,欢迎一起讨论。

    // 改进写法【推荐】
    struct RuntimeKey {
        static let jkKey = UnsafeRawPointer.init(bitPattern: "JKKey".hashValue)
        /// ...其他Key声明
    }
    
    var jkPro: String? {
        set {
            objc_setAssociatedObject(self, ViewController.RuntimeKey.jkKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        
        get {
            return  objc_getAssociatedObject(self, ViewController.RuntimeKey.jkKey) as? String
        }
    }


我所有Swift3.0练习Demo都放到了Github上,并且在不断更新。
Swift3.0朝圣之路-全集地址

  1. Swift3.0闭包的使用详解,简单封装GET/POST网络请求
  2. WKWebView的使用详解,包括JS交互
  3. 原来MapKit的简单使用,包括定位+地图+地理编码
  4. OC+Swift混编,介绍高德地图SDK的简单使用,包括定位+地图+POI搜索+导航+UISearchController使用
  5. 协议代理的基础用法
  6. 分类/类别的使用和封装
  7. 【Then协议库】-眼前一亮的初始化方式
  8. 使用Runtime在分类Extension中添加属性
  9. 封装UIAlertController
  10. 自定义相册【尚未完成】
  11. 用原生框架扫描、识别二维码图片,生成黑白色、彩色二维码图片
Swift3.0朝圣之路
Web note ad 1