Runtime總結筆記`AssociatedObject`&`Method Swizzling`

本文爲原創,轉載請註明出處

Associated Objects

參考文章 NSHipster

主要方法

  1. objc_setAssociatedObjcet:
  2. objc_getAssociatedObject:
  3. 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:)方法中執行,這也會保證該方法被實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章