美文网首页iOS 开发 iOS开发Swift3语法学习
Swift3.0朝圣之路-使用Runtime在分类Extensi

Swift3.0朝圣之路-使用Runtime在分类Extensi

作者: 溪枫狼 | 来源:发表于2016-10-12 13:03 被阅读5111次

    我最近自学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. 用原生框架扫描、识别二维码图片,生成黑白色、彩色二维码图片

    相关文章

      网友评论

      • 汾酒iOSer:看了下面的评论,也没细研究了,解决问题了
      • 汾酒iOSer:太赞了,我正好用这个,及时解决问题!大神,膜拜了
      • SmallflyBlog:更加深入一点的奔溃解释,是不是因为 String 是值类型,相等的值类型地址并不一样,而引用类型的地址是相同的。
      • 大灰灰iOS:用常量的地址就行了。。OC你应该&JKSecurityButtonQuickTapEnableKey,swift同理也可以。
      • 806349745123:利用字符串常量,在extension前用 private var jkKey = "jkKey",这样的方法可行吗?
      • 33707cfde13b: struct runtimeKey {

        static var vc = "HGJAlertController"

        }
        用上面这个办法更简单
        33707cfde13b:@溪枫狼 全swift了,还有一些老代码是oc,算是混编吧
        溪枫狼:@侃1992 你们的项目全用Swift了?
        33707cfde13b:下面给指针的时候用 “&”取结构体变量的地址作为参数
      • 33707cfde13b:objc_setAssociatedObject(self, UIAlertController.runtimeKey.vc, newValue, .OBJC_ASSOCIATION_ASSIGN)
        这个方法创建出来的都是强引用,即时用OBJC_ASSOCIATION_ASSIGN也是的,在使用过程中我遇到了循环引用问题,有解决办法吗
        33707cfde13b:我弄错了,是弱引用,问题出在我代码别处的block处
        溪枫狼:@侃1992 身边没对电脑,没发给你测试
        溪枫狼:@侃1992 self是什么类对象?你是要绑定UIAlertController对象?弹窗消失置空看看,self.alertCcontroller = nil
      • GA_:你好 我这想动态添加闭包 运行就报错
        1. While emitting IR SIL function @_TFE13GA_KingfisherCSo6UIView7ga_showfT5toastGSqS0__8durationGSqSd_8positionGSqOS_16ToastPostionType_10completionFSbT__T_ for 'ga_show' at /Users/houjianan/Desktop/GA_Kingfisher/GA_Kingfisher/GA_Toast.swift:35:5
        如果遇到过 希望帮我个忙解答一下 谢谢 swift3.0 xcode8.1
        溪枫狼:@GA_ 最近忙别的,没注意看简书。有详细的代码吗?私发给我
      • GA_:func ga_show(toast: UIView?, duration: TimeInterval? = 0, position: ToastPostionType? = .center, completion: (_ didTap: Bool) -> ()) {
        guard toast != nil else {
        return
        }

        objc_setAssociatedObject(toast, kToastCompletionKey, completion, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }

      本文标题:Swift3.0朝圣之路-使用Runtime在分类Extensi

      本文链接:https://www.haomeiwen.com/subject/olkiyttx.html