本文爲原創,轉載請註明出處
Associated Objects
參考文章 NSHipster
主要方法
- objc_setAssociatedObjcet:
- objc_getAssociatedObject:
- objc_removeAssociatedObjects:
爲什麼使用
允許開發者在已存在的類裏添加自定義屬性
如何使用(Objective - C)
- NSObject + AssociatedObject .h文件
@interface NSObject(AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
- NSObject + AssociatedObject .m文件
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject: (id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
@end
推薦用法
添加屬性最好是 static char的,更推薦指針類型的,也可以是@selector類型的,通常來說要求該屬性應該是常量,唯一的,在getter和setter訪問範圍內的
static char LNAssociatedObjectKey;
objc_getAssociatedObjec(self, &LNAssociatedObjectKey);
使用注意
OBJC_ASSOCIATION_ASSIGN的行爲不代表 0 retain的weak,更像unsafe_unretained
刪除屬性
不使用objc_removeAssociatedObjects,因爲這會刪除該對象上面所有的關聯類型,好的方法是在objc_setAssociatedObject上面將其設置爲nil
Swift中的關聯類型使用
extension UIViewController {
private struct LNAssociatedKeys {
static let ln_descriptiveName = "ln_descriptiveName"
}
var descriptiveName: String {
get {
return objc_getAssociatedObject(self, &LNAssociatedKeys.ln_descriptiveName) as? String
}
set {
if let newValue = newValue {
objc_setAssociatedObject(self, &LNAssociatedKeys.ln_descriptiveName, newValue as? String, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
Method Swizzling (Objective-C)
Method Swizzling用於改變一個已存在Selector的實現
@implementation UIViewController (Tracking)
+ load() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzlingSelector = @selector(ln_viewWillAppear:);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzlingMethod), class_getTypeEncoding(swizzlingMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzlingSelector, method_getImplementation(originalMethod), getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzlingMethod);
}
});
}
- (void) ln_viewWillAppear: (BOOL)animated {
[self ln_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self];
}
@end
使用場景
主要用於調試和響應事件
+load 和 initialize
swizzling應該只在+load中完成,在Objective-C運行時中,每個類有兩個方法都會調用。+load是在初始化裝載時調用,+initialize是在應用第一次該類的實例方法或類方法調用前調用的,兩個方法都是可選的,只有方法被實現纔會調用
dispatch_once
Method Swizzling 應該只在dispatch_once中完成
由於Method Swizzling改變了全局的狀態,所以要確保每個預防措施在運行時都是可用的,原子操作就是確保代碼只執行一次的操作,在不同線程也能保證只執行一次
Selectors & IMP & Methods
- Selector(typedef struct objc_selector *SEL):在運行時用來表示方法名,是一個C的字符串,Selector由編譯器產生,並在當類被加載進內存時自動進行方法名和實現的映射
- Method(typedef struct objc_method *Method):一個不透明的代表一個方法的類型
- IMP(typedef id (*IMP)(id, SEL, …)):指向一個方法實現最開始的地方,該方法由CPU架構使用標準的C實現,第一個參數指向元類對象(metaClass),第二個對象是方法的方法名,真正參數緊隨其後
最好的描述:在運行時類維護了一個消息分發列表來解決消息的正確發送,每個消息的入口都是一個方法(Method),這個方法映射了一對鍵值對,其中鍵是SEL(selectors),值是指向方法的實現(IMP)。Method Swizzling使一個存在的方法Selector映射到一個另一個實現的IMP上面,同時重命名了原生方法的實現爲一個新的 selector。
不會產生遞歸的原因
[self ln_viewWillAppear]看似會出現遞歸,但此時self的實現指針實際指向原方法的實現,所以不會產生遞歸
Method Swizzling(Swift)
當前Swift中禁用了load和initialize方法
要使用 Method Swizzling 要在application(_: didFinishLaunchingWithOptions:)方法中執行,這也會保證該方法被實現