我最近自学Swift3.0,由于之前没学过Swift,只能将OC的代码“翻译”成Swift,在此过程慢慢学习Swift,Swift3.0的资料少,遇到了不少坑,今天就介绍一个。
在OC里面,咱给分类添加属性是这么写的,即使用Runtime
中的objc_setAssociatedObject
和objc_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吧,队友一看代码能知道啥意思吗?庆幸的是String
的hashValue (哈希值)
可以返回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朝圣之路-全集地址