這意味着全部方法、變量、類之間的link,都會推遲到應用實際運行的最後一刻纔會創建。
這將給開發人員極高的靈活性,由於咱們能夠修改這些link。git
所以在 Swift 當中,靈活性受到了限制,不過您會所以獲得更多的安全性。github
咱們一般所說的 Objective-C 「動態性」,每每都是指 KVO。雖然還有其他的函數,可是這些是最多見、最經常使用的。這也就是人們所說的,Swift 缺失的部分。編程
而 KVO是Foundation框架基於運行時實現的一個特性。所以本文先從Objective-C 的運行時 開始描述。安全
本質上是一個庫。它負責了 「Objective」 這個部分,app
#import <objc/runtime.h>
它主要由 C 和彙編編寫而成,其實現了諸如類、對象、方法調度、協議等等這些東西。它是徹底開源的,而且開源了很長一段時間了。框架
typedef struct objc_class *Class; struct objc_object { Class isa; };
對象只與一個類創建引用關聯,也就是這個 isa 的意思所在。編程語言
struct objc_class { Class isa; Class super_class; const char *name; long version; long info; long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols; };
類當中一樣有 isa 這個值。除了 NSObject 這個類以外,super_class 的值永遠不會爲 nil,由於 Objective-C 當中的其他類都是以某種方式繼承自 NSObject 的.ide
更多的應該是關注變量列表 (ivars)、方法列表 (methodLists) 和這個協議列表 (protocols)。函數
這些就是咱們能在運行時修改和讀取的。能夠看到,對象其實本質上是一個很是簡單的結構體,類一樣也是。咱們能夠藉助運行時函數,從而在運行時動態建立類。單元測試
BJC_EXPORT Class _Nullable objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
Class myClass = objc_allocateClassPair([NSObject class], "MyClass", 0); // 在這裏添加變量、方法和協議 objc_registerClassPair(myClass); // 當類註冊以後,變量列表將會被鎖定 [[myClass alloc] init];
所建立的這個類和其他的 Objective-C 類毫無區別.
利用 Objective-C 運行時函數:allocateClassPair 建立Class。咱們爲其提供一個 isa,在本例當中咱們提供了 NSObject,而後爲其命名。第三個參數則是額外字節的定義,一般咱們都直接賦值 0 便可。隨後咱們就能夠添加變量、方法以及協議了,以後就註冊這個 ClassPair。註冊以後,咱們就沒法修改變量列表了,其他的內容仍然能夠修改。
[myObject isMemberOfClass:NSObject.class]; [myObject respondsToSelector:@selector(doStuff:)]; // isa == class class_respondsToSelector(myObject.class, @selector(doStuff:));
respondsToSelector,其接受 Selector 和類爲參數。
BJC_EXPORT BOOL class_respondsToSelector(Class _Nullable cls, SEL _Nonnull sel) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
在編寫 XCTestCase 的時候,須要完成 setUp 和 tearDown 的設定,隨後才能編寫相關的 test 函數。
當測試運行的時候,系統會自行遍歷全部的測試函數,並自動運行。
unsigned int count; Method *methods = class_copyMethodList(myObject.class, &count); //Ivar *list = class_copyIvarList(myObject.class,&count); for(unsigned i = 0; i < count; i++) { SEL selector = method_getName(methods[i]); NSString *selectorString = NSStringFromSelector(selector); if ([selectorString containsString:@"test"]) { [myObject performSelector:selector]; } } free(methods);
struct objc_ivar { char *ivar_name; char *ivar_type; int ivar_offset; } struct objc_method { SEL method_name; char *method_types; IMP method_imp; }
Method doStuff = class_getInstanceMethod(self.class, @selector(doStuff)); IMP doStuffImplementation = method_getImplementation(doStuff); const char *types = method_getTypeEncoding(doStuff); //「v@:@" class_addMethod(myClass.class, @selector(doStuff:), doStuffImplementation, types);
這個例子具體的方法實現部分咱們取了個巧,由於咱們使用了既有的 doStuff 方法,
所以可以很簡單地獲取其方法實現和方法類型。
不過咱們還能夠用其餘方法來完成:交換方法的實現。可使用運行時當中最著名的動態特性:方法混淆 (swizzling)。
咱們可使用 [self doStuff] 或者 [self performSelector:@selector(doStuff)] 來進行調用,實際上在運行時級別,它們都是藉助 objc_msgSend 向對象發送了一個消息。
[self doStuff]; [self performSelector:@selector(doStuff)]; objc_msgSend(self, @selector(message));
擴展一個不是本身建立的類,想要向其中添加函數。Swift 的擴展與之很是類似。
類別的一個問題便在於,它沒法添加存儲屬性。您能夠添加一個計算屬性,可是存儲屬性是沒法添加的。
運行時的另外一個特性即是:
咱們能夠藉助 setAssociatedObject 和 getAssociatedObject 這兩個函數,向既有的類當中添加存儲屬性。
@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)); }
可是若是調用方法所在的對象爲 nil 的時候,咱們就會獲得一個異常,應用便會崩潰。但事實證實,在崩潰以前會預留幾個步驟,從而容許咱們對某個不存在的函數進行一些操做。
// 1 +(BOOL)resolveInstanceMethod:(SEL)sel{ // 添加實例方法並返回 YES 的一次機會,它隨後會再次嘗試發送消息 } // 2 - (id)forwardingTargetForSelector:(SEL)aSelector{ // 返回能夠處理 Selector 的對象 } // 3 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ // 您須要實現它來建立 NSInvocation } - (void)forwardInvocation:(NSInvocation *)invocation { // 在您所選擇的目標上調用 Selector [invocation invokeWithTarget:target]; }
ps:試圖橋接兩個不一樣的框架的時候,這個功能便很是有用
當調用了某個不存在的方法時,運行時首先會調用一個名爲 resolveInstanceMethod 的類方法,
若是所調用的方法是類方法的話,則爲調用 resolveClassMethod。
這時候咱們便有機會來添加方法了,即上面提到的利用運行時動態添加方法
若是不想建立新方法的話,第一步返回了NO,
還有 forwardingTargetForSelector。能夠直接返回須要調用方法的目標對象便可,以後這個對象就會調用 Selector。
第三步驟:forwardInvocation
略爲複雜的 forwardInvocation。
若是您須要這麼作,那麼還須要實現 methodSignatureForSelector。
全部的調用過程都被封裝到 NSInvocation 對象當中,以後你即可以使用特定的對象進行調用了。
交換方法的實現
+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(doSomething); SEL swizzledSelector = @selector(mo_doSomething); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); }
2種實現方式:
一、class_addMethod originalSelector 的時候使用swizzledMethod的IMP、TYP
以後進行class_replaceMethod,來達到交換的目的。
即先換掉originalSelector的IMP,再換掉swizzledMethod的IMP,達到exchange 的目的
二、直接使用exchange 方法
當類加載以後,會調用一個名爲 load 的類函數。因爲咱們只打算混淆一次,所以咱們須要使用 dispatch_once。接着咱們即可以獲得該方法,而後使用 class_replaceMethod 或者 method_exchangeImplementations 來替換方法。
之因此想要混淆,是由於它能夠用於日誌記錄和 Mock 測試。例如上報用戶打開的界面所在VC的名稱,就可使用swizzling 統一處理
Foundation 框架實現了基於運行時的一個特性:
鍵值編碼 (key-value-coding, KVC) 以及鍵值觀察 (key-value observing, KVO)。
KVC 和 KVO 容許咱們將 UI 和數據進行綁定。這也是 Rx 以及其餘響應式框架實現的基礎。
ps:並且MVVM的實現又能夠藉助「V-VM」第三方綁定框架進行實現
@property (nonatomic, strong) NSNumber *number; [myClass valueForKey:@"number"]; [myClass setValue:@(4) forKey:@"number"];
能夠將屬性名稱做爲鍵,來獲取屬性值或者設置屬性值.
[myClass addObserver:self forKeyPath:@"number" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ // Respond to observation. }
在觀察的值發生變動以後,KVO 會調用此方法當即通知觀察者。經過這個方法,咱們即可以按需更新 UI。
咱們一般所說的 Objective-C 「動態性」,每每都是指 KVO。雖然還有其他的函數,可是這些是最多見、最經常使用的。這也就是人們所說的,Swift 缺失的部分。
Swift 是一種強類型語言。類型靜態,也就是說 Swift 的默認類型是很是安全的。
若是須要的話,不安全類型也是存在的,Swift 中的動態性能夠經過 Objective-C 運行時來得到。
可是 Swift 開源並遷移到 Linux 以後,因爲 Linux 上的 Swift 並不提供 Objective-C 運行時,事情就。。
Swift 當中存在有這兩個修飾符 @objc 和 @dynamic,此外咱們一樣還能夠訪問 NSObject。@objc 將您的 Swift API 暴露給 Objective-C 運行時,可是它仍然不能保證編譯器會嘗試對其進行優化。
若是您真的想使用動態功能的話,就須要使用 @dynamic。(一旦您使用了 @dynamic 修飾符以後,就不須要添加 @objc 了,由於它已經隱含在其中。)
// 1 override class func resolveInstanceMethod(_ sel: Selector!) -> Bool { // 添加實例方法並返回 true 的一次機會,它隨後會再次嘗試發送消息 } // 2 override func forwardingTarget(for aSelector: Selector!) -> Any? { // 返回能夠處理 Selector 的對象 } // 3 - Swift 不支持 NSInvocation
load 在 Swift 再也不會被調用,所以咱們須要在 initialize 中進行混淆。
在 Objective-C 當中,咱們使用 dispatch_once,可是自 Swift 3 以後,dispatch_once 便不復存在於 Swift 當中了。事情變得略爲複雜。
if self is MyClass { // YAY } let myString = "myString"; let mirror = Mirror(reflecting: myString) print(mirror.subjectType) // 「String" let string = String(reflecting: type(of: myString)) // Swift.String // No native method introspection
is 替代了 isMemberOfClass
若是打算爲 Linux 編寫單元測試的時候,就沒法自動遍歷全部的函數。您必須實現 static var allTests,而後手動列出全部的測試函數。這很。。。。
KVO 的魅力在於,您能夠在不是本身所建立的類當中使用它,也能夠只對您想要監聽變化的類使用。所觀察的對象必需要繼承自 NSObject,而且使用一個 Objective-C 類型。所觀察的變量必需要生命爲 dynamic。致使你必需要對想要觀察的事務瞭如指掌。
只能使用基於協議來觀察對象,語言自身是沒有原生的解決方案的。或者使用一些符合 Swift 風格的方法來暴露一些運行時函數的 ObjectiveKit 的開源庫