<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                [toc] ![](https://img.kancloud.cn/6b/32/6b32cffc4212471fcbf4943c2347bdfc_2783x1147.png) ## 一、簡介 ### 1.1 什么是Runtime >Runtime是一套底層純`C語言API`,我們編寫的OC代碼最終都會被編譯器轉化為`運行時代碼`,通過`消息機制`決定函數調用方式,這也是OC作為`動態語言`使用的基礎。 ### 1.2 消息機制的基本原理 在Object-C的語言中,對象方法調用都是類似`[receiver selector]` 的形式,其本質:`就是讓對象在運行時發送消息的過程。` 而方法調用`[receiver selector] `分為兩個過程: - `編譯階段` [receiver selector] 方法被編譯器轉化,分為兩種情況: >1.不帶參數的方法被編譯為:objc_msgSend(receiver,selector) 2.帶參數的方法被編譯為:objc_msgSend(recevier,selector,org1,org2,…) - `運行時階段` 消息接收者`recever`尋找對應的`selector`,也分為兩種情況: >1.接收者能找到對應的selector,直接執行接收receiver對象的selector方法。 2.接收者找不到對應的selector,`消息被轉發`或者臨時向接收者添加這個selector對應的實現內容,否則崩潰 **總而言之:** >OC調用方法`[receiver selector]`,`編譯階段`確定了要`向哪個接收者`發送message消息,但是`接收者`如何響應決定于`運行時的判斷` ### 1.3 Runtime中的概念解析 #### 1.3.1 objc_msgSend >`所有` Objective-C 方法調用在`編譯時`都會轉化為對 `C` 函數 `objc_msgSend `的調用。`objc_msgSend(receiver,selector); 是 `[receiver selector]; `對應的 C 函數 #### 1.3.2 Object(對象) 在 `objc/runtime.h` 中,`Object(對象)` 被定義為指向 `objc_object` **結構體的指針**,`objc_object`結構體 的數據結構如下: ``` //runtime對objc_object結構體的定義 struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; //id是一個指向objc_object結構體的指針,即在Runtime中: typedef struct objc_object *id; //OC中的對象雖然沒有明顯的使用指針,但是在OC代碼被編譯轉化為C之后,每個OC對象其實都是擁有一個isa的指針的 ``` #### 1.3.3 Class(類) 在 `objc/runtime.h` 中,`Class(類)` 被定義為指向 `objc_class` **結構體 的指針**,`objc_class`結構體 的數據結構如下: ``` //runtime對objc_class結構體的定義 struct objc_class { Class _Nonnull isa; // objc_class 結構體的實例指針 #if !__OBJC2__ Class _Nullable super_class; // 指向父類的指針 const char * _Nonnull name; // 類的名字 long version; // 類的版本信息,默認為 0 long info; // 類的信息,供運行期使用的一些位標識 long instance_size; // 該類的實例變量大小; struct objc_ivar_list * _Nullable ivars; // 該類的實例變量列表 struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定義的列表 struct objc_cache * _Nonnull cache; // 方法緩存 struct objc_protocol_list * _Nullable protocols; // 遵守的協議列表 #endif }; //class是一個指向objc_class結構體的指針,即在Runtime中: typedef struct objc_class *Class; ``` #### 1.3.4 SEL (方法選擇器) 在 `objc/runtime.h `中,`SEL (方法選擇器)` 被定義為指向 `objc_selector` **結構體 的指針**: ``` typedef struct objc_selector *SEL; //Objective-C在編譯時,會依據每一個方法的名字、參數序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL ``` **注意:** >1.不同類中相同名字的方法對應的方法選擇器是相同的。 2.即使是同一個類中,方法名相同而變量類型不同也會導致它們具有相同的方法選擇器。 **通常獲取SEL有三種方法:** >1.OC中,使用`@selector("方法名字符串")` 2.OC中,使用`NSSelectorFromString("方法名字符串")` 3.`Runtime`方法,使用`sel_registerName("方法名字符串")` #### 1.3.5 Ivar 在 `objc/runtime.h` 中,`Ivar` 被定義為指向 `objc_ivar` 結構體 的指針,`objc_ivar`結構體 的數據結構如下: ``` struct objc_ivar { char * Nullable ivar_name OBJC2UNAVAILABLE; char * Nullable ivar_type OBJC2UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef LP64 int space OBJC2_UNAVAILABLE; #endif } //Ivar代表類中實例變量的類型,是一個指向ojbcet_ivar的結構體的指針 typedef struct objc_ivar *Ivar; ``` 在`objc_class`中看到的`ivars`成員列表,其中的元素就是`Ivar`,可以通過實例查找其在類中的名字,這個過程被稱為反射,下面的`class_copyIvarList`獲取的不僅有實例變量還有屬性: ``` Ivar *ivarList = class_copyIvarList([self class], &count); for (int i= 0; i<count; i++) { Ivar ivar = ivarList[i]; const char *ivarName = ivar_getName(ivar); NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]); } free(ivarList); ``` #### 1.3.5 Method(方法) 在 `objc/runtime.h` 中,`Method(方法)` 被定義為指向 `objc_method` 結構體 的指針,在`objct_class`定義中看到`methodLists`,其中的元素就是`Method,objc_method`結構體 的數據結構如下: ``` struct objc_method { SEL _Nonnull method_name; // 方法名 char * _Nullable method_types; // 方法類型 IMP _Nonnull method_imp; // 方法實現 }; //Method表示某個方法的類型 typedef struct objc_method *Method; ``` ## 二、和Runtime交互的三種方式 ### 2.1 OC源代碼 >OC代碼會在`編譯階段`被編譯器轉化。OC中的類、方法和協議等在`Runtime`中都由一些`數據結構`來定義。 所以在日常的項目開發過程中,使用OC語言進行編碼時,這已經是在和Runtime進行交互了,只是這個過程對于開發者而言是無感的 ### 2.2 NSObject方法 >Runtime的最大特征就是實現了OC語言的`動態特性`。 作為大部分`Objective-C`類繼承體系的根類的`NSObject`,其本身就具有了一些非常具有運行時動態特性的方法, 比如: >1. `-respondsToSelector:`方法可以檢查在代碼運行階段當前對象是否能響應指定的消息 >2. `-description:`返回當前類的描述信息 >3. `-isKindOfClass: `和 `-isMemberOfClass:` 檢查對象是否存在于指定的類的繼承體系中 >4. `-conformsToProtocol:` 檢查對象是否實現了指定協議類的方法; >5. `-methodForSelector:` 返回指定方法實現的地址。 ### 2.3 使用Runtime函數 >`Runtime`系統是一個由`一系列函數`和`數據結構`組成,具有`公共接口`的`動態共享庫`。頭文件存放于`/usr/include/objc`目錄下。 在項目工程代碼里引用Runtime的頭文件,同樣能夠實現類似OC代碼的效果: ``` //相當于:Class class = [UIView class]; Class viewClass = objc_getClass("UIView"); //相當于:UIView *view = [UIView alloc]; UIView *view = ((id (*)(id, SEL))(void *)objc_msgSend)((id)viewClass, sel_registerName("alloc")); //相當于:UIView *view = [view init]; ((id (*)(id, SEL))(void *)objc_msgSend)((id)view, sel_registerName("init")); ``` ## 三、Runtime消息轉發 ### 3.1 動態方法解析與消息轉發 #### 3.1.1 動態方法解析:動態添加方法 Runtime足夠強大,能夠在`運行時`動態添加一個`未實現的方法`,這個功能主要有兩個應用場景: >1.動態添加未實現方法,解決代碼中因為方法未找到而報錯的問題; >2.利用懶加載思路,若一個類有很多個方法,同時加載到內存中會耗費資源,可以使用動態解析添加方法 方法動態解析主要用到的方法如下: ``` //OC方法: //類方法未找到時調起,可于此添加類方法實現 + (BOOL)resolveClassMethod:(SEL)sel //實例方法未找到時調起,可于此添加實例方法實現 + (BOOL)resolveInstanceMethod:(SEL)sel //Runtime方法: /** 運行時方法:向指定類中添加特定方法實現的操作 @param cls 被添加方法的類 @param name selector方法名 @param imp 指向實現方法的函數指針 @param types imp函數實現的返回值與參數類型 @return 添加方法是否成功 */ BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) ``` #### 3.1.2 解決方法無響應崩潰問題 執行OC方法其實就是一個發送消息的過程,若方法未實現,可以利用`方法動態解析`與`消息轉發`來避免程序崩潰,這主要涉及下面一個處理未實現消息的過程: 在這個過程中,可能還會使用到的方法有: ![](https://img.kancloud.cn/96/a6/96a634389fccb2ae5e93123fa08dc13a_785x425.png) **例子:** ``` #import "ViewController.h" #import <objc/runtime.h> @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 執行 fun 函數 [self performSelector:@selector(fun)]; } // 重寫 resolveInstanceMethod: 添加對象方法實現 + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(fun)) { // 如果是執行 fun 函數,就動態解析,指定新的 IMP class_addMethod([self class], sel, (IMP)funMethod, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } void funMethod(id obj, SEL _cmd) { NSLog(@"funMethod"); //新的 fun 函數 } @end //日志輸出: 2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] funMethod ``` 從執行任務的輸出日志中,可以看到: >雖然沒有實現 `fun `方法,但是通過重寫 `resolveInstanceMethod:` ,利用 `class_addMethod `方法添加對象方法實現 `funMethod` 方法,并執行。從打印結果來看,成功調起了`funMethod` 方法。 ### 3.2 消息`接收者`重定向: 如果上一步中 `+resolveInstanceMethod:`或者 `+resolveClassMethod: `沒有添加其他函數實現,運行時就會進行下一步:消息接受者重定向。 如果當前對象實現了` -forwardingTargetForSelector:`,Runtime 就會調用這個方法,允許將消息的接受者轉發給其他對象,其主要方法如下: ``` //重定向類方法的消息接收者,返回一個類 - (id)forwardingTargetForSelector:(SEL)aSelector //重定向實例方法的消息接受者,返回一個實例對象 - (id)forwardingTargetForSelector:(SEL)aSelector ``` **例子:** ``` #import "ViewController.h" #import <objc/runtime.h> @interface Person : NSObject - (void)fun; @end @implementation Person - (void)fun { NSLog(@"fun"); } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 執行 fun 方法 [self performSelector:@selector(fun)]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return YES; // 為了進行下一步 消息接受者重定向 } // 消息接受者重定向 - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(fun)) { return [[Person alloc] init]; // 返回 Person 對象,讓 Person 對象接收這個消息 } return [super forwardingTargetForSelector:aSelector]; } //日志輸出: 2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] fun ``` 從執行任務的輸出日志中,可以看到: >雖然當前 ViewController 沒有實現 `fun` 方法,`+resolveInstanceMethod: `也沒有添加其他函數實現。 但是我們通過 `forwardingTargetForSelector `把當前 ViewController 的方法轉發給了 `Person 對象`去執行了 通過`forwardingTargetForSelector` 可以`修改消息的接收者`,該方法返回參數是一個對象,如果這個對象是不是 `nil`,也不是 `self`,系統會將運行的消息轉發給這個對象執行。否則,繼續進行下一步:消息重定向流程 ### 3.3 `消息`重定向: 如果經過消息`動態解析`、`消息接受者重定向`,Runtime 系統還是找不到相應的方法實現而無法響應消息,Runtime 系統會利用 `-methodSignatureForSelector:` 方法獲取函數的參數和返回值類型。 **其過程:** >1.如果 `-methodSignatureForSelector:` 返回了一個 `NSMethodSignature` 對象(函數簽名),Runtime 系統就會創建一個 `NSInvocation `對象, 并通過 -forwardInvocation: 消息通知當前對象,給予此次消息發送最后一次尋找 IMP 的機會。 2.如果 `-methodSignatureForSelector:` 返回 nil。則 Runtime 系統會發出 `-doesNotRecognizeSelector: `消息,程序也就崩潰了 所以可以在`-forwardInvocation:`方法中對`消息進行轉發`。 其主要方法: ``` // 消息重定向 - (void)forwardInvocation:(NSInvocation *)anInvocation; // 獲取函數的參數和返回值類型,返回簽名 - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector; ``` **例子:** ``` #import "ViewController.h" #import <objc/runtime.h> @interface Person : NSObject - (void)fun; @end @implementation Person - (void)fun { NSLog(@"fun"); } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 執行 fun 函數 [self performSelector:@selector(fun)]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return YES; // 為了進行下一步 消息接受者重定向 } - (id)forwardingTargetForSelector:(SEL)aSelector { return nil; // 為了進行下一步 消息重定向 } // 獲取函數的參數和返回值類型,返回簽名 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; } // 消息重定向 - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = anInvocation.selector; // 從 anInvocation 中獲取消息 Person *p = [[Person alloc] init]; if([p respondsToSelector:sel]) { // 判斷 Person 對象方法是否可以響應 sel [anInvocation invokeWithTarget:p]; // 若可以響應,則將消息轉發給其他對象處理 } else { [self doesNotRecognizeSelector:sel]; // 若仍然無法響應,則報錯:找不到響應方法 } } @end //日志輸出: 2019-09-01 23:24:34.911774+0800 XKRuntimeKit[30032:8724248] fun ``` 從執行任務的輸出日志中,可以看到: >在` -forwardInvocation: `方法里面讓 Person 對象去執行了 fun 函數 既然 `-forwardingTargetForSelector:` 和 `-forwardInvocation: `都可以將消息轉發給其他對象處理,那么兩者的**區別在哪?** >區別就在于` -forwardingTargetForSelector: `只能將消息轉發給`一個對象`。而 `-forwardInvocation: `可以將消息轉發給`多個對象`。 ## 四、Runtime的應用 ### 4.1 動態方法交換 實現動態方法交換(Method Swizzling )是Runtime中最具盛名的應用場景,其原理是: >通過`Runtime`獲取到方法實現的地址,進而動態交換兩個方法的功能 關鍵方法: ``` //獲取類方法的Mthod Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name) //獲取實例對象方法的Mthod Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) //交換兩個方法的實現 void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) ``` #### 4.1.1 動態方法交換 ``` #import "RuntimeKit.h" #import <objc/runtime.h> @implementation RuntimeKit - (instancetype)init { self = [super init]; if (self) { //交換方法的實現,并測試打印 Method methodA = class_getInstanceMethod([self class], @selector(testA)); Method methodB = class_getInstanceMethod([self class], @selector(testB)); method_exchangeImplementations(methodA, methodB); [self testA]; [self testB]; } return self; } - (void)testA{ NSLog(@"我是A方法"); } - (void)testB{ NSLog(@"我是B方法"); } @end 日志輸出: 2019-09-01 21:25:32.858860+0800 XKRuntimeKit[1662:280727] 我是B方法 2019-09-01 21:25:32.859059+0800 XKRuntimeKit[1662:280727] 我是A方法 ``` #### 4.1.2攔截并替換系統方法 ``` #import "UIViewController+xk.h" #import <objc/runtime.h> @implementation UIViewController (xk) + (void)load{ //獲取系統方法地址 Method sytemMethod = class_getInstanceMethod([self class], @selector(viewWillAppear:)); //獲取自定義方法地址 Method customMethod = class_getInstanceMethod([self class], @selector(run_viewWillAppear:)); //判斷存在與否 if (!class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(customMethod), method_getTypeEncoding(customMethod))) { method_exchangeImplementations(sytemMethod, customMethod); } else{ class_replaceMethod([self class], @selector(run_viewWillAppear:), method_getImplementation(sytemMethod), method_getTypeEncoding(sytemMethod)); } } - (void)run_viewWillAppear:(BOOL)animated{ [self run_viewWillAppear:animated]; NSLog(@"我是運行時替換的方法-viewWillAppear"); } - (void)run_viewWillDisappear:(BOOL)animated{ [self run_viewWillDisappear:animated]; NSLog(@"我是運行時替換的方法-viewWillDisappear"); } @end 日志輸出: 2019-09-01 21:36:55.610385+0800 XKRuntimeKit[1921:310118] 我是運行時替換的方法-viewWillAppear ``` 將該分類引入,從執行結果可以看到,但系統的控制器執行`viewWillAppear`時,則會進入已經替換的方法`run_viewWillAppear`之中。 ### 4.2 類目添加新的屬性 在日常開發過程中,常常會使用類目`Category`為一些已有的類擴展功能。雖然繼承也能夠為已有類增加新的方法,而且相比類目更是具有增加屬性的優勢,但是繼承畢竟是一個重量級的操作,添加不必要的繼承關系無疑增加了代碼的復雜度。 >遺憾的是,OC的類目并不支持直接添加屬性 為了實現給分類添加屬性,還需借助 `Runtime`的`關聯對象(Associated Objects)`特性,它能夠幫助我們在運行階段將任意的屬性關聯到一個對象上: ``` /** 1.給對象設置關聯屬性 @param object 需要設置關聯屬性的對象,即給哪個對象關聯屬性 @param key 關聯屬性對應的key,可通過key獲取這個屬性, @param value 給關聯屬性設置的值 @param policy 關聯屬性的存儲策略(對應Property屬性中的assign,copy,retain等) OBJC_ASSOCIATION_ASSIGN @property(assign)。 OBJC_ASSOCIATION_RETAIN_NONATOMIC @property(strong, nonatomic)。 OBJC_ASSOCIATION_COPY_NONATOMIC @property(copy, nonatomic)。 OBJC_ASSOCIATION_RETAIN @property(strong,atomic)。 OBJC_ASSOCIATION_COPY @property(copy, atomic)。 */ void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) /** 2.通過key獲取關聯的屬性 @param object 從哪個對象中獲取關聯屬性 @param key 關聯屬性對應的key @return 返回關聯屬性的值 */ id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) /** 3.移除對象所關聯的屬性 @param object 移除某個對象的所有關聯屬性 */ void objc_removeAssociatedObjects(id _Nonnull object) ``` **注意:** >key與關聯屬性一一對應,我們必須確保其全局唯一性,常用我們使用@selector(methodName)作為key **例子:** 在`UIViewController+xk.h`中新增一個`name`屬性: ``` @interface UIViewController (xk) //新增屬性:名稱 @property(nonatomic,copy)NSString * name; - (void)clearAssociatedObjcet; @end ``` 在`UIViewController+xk.m`中補充對應的實現: ``` #import "UIViewController+xk.h" #import <objc/runtime.h> @implementation UIViewController (xk) //set方法 - (void)setName:(NSString *)name{ objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } //get方法 - (NSString *)name{ return objc_getAssociatedObject(self, @selector(name)); } //添加一個自定義方法,用于清除所有關聯屬性 - (void)clearAssociatedObjcet{ objc_removeAssociatedObjects(self); } @end ``` **執行任務:** ``` ViewController * vc = [ViewController new]; vc.name = @"我是根控制器"; NSLog(@"獲取關聯屬性name:%@",vc.name); [vc clearAssociatedObjcet]; NSLog(@"獲取關聯屬性name:%@",vc.name); 日志輸出: 2019-09-01 21:50:05.162915+0800 XKRuntimeKit[2066:335327] 獲取關聯屬性name:我是根控制器 2019-09-01 21:50:05.163080+0800 XKRuntimeKit[2066:335327] 獲取關聯屬性name:(null) ``` 同樣的,使用運行時還可以為類目新增一些自身沒有的方法,比如給`UIView`新增點擊事件: ``` #import <objc/runtime.h> static char onTapGestureKey; static char onTapGestureBlockKey; @implementation UIView (Gesture) //添加輕拍手勢 - (void)addTapGestureActionWithBlock:(onGestureActionBlock)block{ UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &onTapGestureKey); self.userInteractionEnabled = YES; if (!gesture){ gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(xk_handleActionForTapGesture:)]; [self addGestureRecognizer:gesture]; objc_setAssociatedObject(self, &onTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN); } //添加點擊手勢響應代碼塊屬性 objc_setAssociatedObject(self, &onTapGestureBlockKey, block, OBJC_ASSOCIATION_COPY); } //點擊回調 - (void)xk_handleActionForTapGesture:(UITapGestureRecognizer*)sender{ onGestureActionBlock block = objc_getAssociatedObject(self, &onTapGestureBlockKey); if (block) block(sender); } @end ``` 但是使用運行時給類目新增代理屬性時,需要注意`循環引用`問題,由于運行時執行添加的屬性都是`retain`操作,所以往往在執行過程會導致對應的 `delegate` 得不到釋放,因而會導致崩潰,對此,可以進行以下修改操作: **場景: 給`UIView`新增`emptyDataDelegate`空頁面代理,以處理一些異常情況的顯示** 在`UIView+EmptyDataSet.h`中新增一個`emptyDataDelegate`屬性: ``` //頁面無數據代理 @protocol XKEmptyDataSetDelegate <NSObject> @optional //占位文字 - (NSString*)placeholderForEmptyDataSet:(UIScrollView*)scrollView; @end //空頁面設置 @interface UIView (EmptyDataSet) @property (nonatomic,weak) id<XKEmptyDataSetDelegate>emptyDataDelegate; @end ``` 在`UIView+EmptyDataSet.m`中借助`XKEmptyDataWeakObjectContainer`實現其方法: ``` //弱引用代理 @interface XKEmptyDataWeakObjectContainer : NSObject @property (nonatomic,weak,readonly)id weakObject; - (instancetype)initWithWeakObject:(id)object; @end @implementation XKEmptyDataWeakObjectContainer - (instancetype)initWithWeakObject:(id)object{ self = [super init]; if (self) { _weakObject = object; } return self; } @end static char xk_EmptyDataSetDelegateKey; //空視圖設置 @implementation UIView (EmptyDataSet) - (void)setEmptyDataDelegate:(id<XKEmptyDataSetDelegate>)emptyDataDelegate{ objc_setAssociatedObject(self, &xk_EmptyDataSetDelegateKey, [[XKEmptyDataWeakObjectContainer alloc] initWithWeakObject:emptyDataDelegate], OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id<XKEmptyDataSetDelegate>)emptyDataDelegate{ XKEmptyDataWeakObjectContainer * container = objc_getAssociatedObject(self, &xk_EmptyDataSetDelegateKey); return container.weakObject; } @end ``` ### 4.3 獲取類詳細屬性 #### 4.3.1獲取屬性列表 獲取類屬性列表用到runtime的 `class_copyPropertyList`方法,該方法接收一個類對象及返回屬性數量的地址引用 ``` unsigned int count; objc_property_t *propertyList = class_copyPropertyList([self class], &count); for (unsigned int i = 0; i<count; i++) { const char *propertyName = property_getName(propertyList[i]); NSLog(@"PropertyName(%d): %@",i,[NSString stringWithUTF8String:propertyName]); } free(propertyList); ``` #### 4.3.2獲取所有成員變量 獲取類中所有的成員變量,使用的是runtime的`class_copyIvarList`方法。 ``` Ivar *ivarList = class_copyIvarList([self class], &count); for (int i= 0; i<count; i++) { Ivar ivar = ivarList[i]; const char *ivarName = ivar_getName(ivar); NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]); } free(ivarList); ``` #### 4.3.3.獲取所有方法 獲取類中所有的方法列表,使用runtime的`class_copyMethodList`方法。 ``` Method *methodList = class_copyMethodList([self class], &count); for (unsigned int i = 0; i<count; i++) { Method method = methodList[i]; SEL mthodName = method_getName(method); NSLog(@"MethodName(%d): %@",i,NSStringFromSelector(mthodName)); } free(methodList); ``` #### 4.3.4獲取當前遵循的所有協議 獲取當前遵循的所有協議,使用`class_copyProtocolList`方法。 ``` __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count); for (int i=0; i<count; i++) { Protocol *protocal = protocolList[i]; const char *protocolName = protocol_getName(protocal); NSLog(@"protocol(%d): %@",i, [NSString stringWithUTF8String:protocolName]); } free(propertyList); //C語言中使用Copy操作的方法,要注意釋放指針,防止內存泄漏 ``` 上面幾組獲取類的屬性列表,成員列表,方法列表及遵循的協議列表的方法最后都調用了`free`函數。 這是因為`C語言`中使用`Copy`操作的方法,要注意`釋放指針`,`防止內存泄漏` ### 4.4 解決同一方法高頻率調用的效率問題 Runtime源碼中的`IMP`作為`函數指針`,`指向方法的實現`。通過它,可以`繞開發送消息的過程`來提高函數調用的效率。當需要持續大量重復調用某個方法的時候,會十分有用,如下: ``` void (*setter)(id, SEL, BOOL); int i; setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)]; for ( i = 0 ; i < 1000 ; i++ ) setter(targetList[i], @selector(setFilled:), YES); ``` ### 4.5 動態操作屬性 #### 4.5.1修改私有屬性 **場景:** 我們使用第三方框架里的Person類,在特殊需求下想要更改其私有屬性nickName,這樣的操作我們就可以使用Runtime可以動態修改對象屬性。 ``` Person *ps = [[Person alloc] init]; NSLog(@"nickName: %@",[ps valueForKey:@"nickName"]); //null //第一步:遍歷對象的所有屬性 unsigned int count; Ivar *ivarList = class_copyIvarList([ps class], &count); for (int i= 0; i<count; i++) { //第二步:獲取每個屬性名 Ivar ivar = ivarList[i]; const char *ivarName = ivar_getName(ivar); NSString *propertyName = [NSString stringWithUTF8String:ivarName]; if ([propertyName isEqualToString:@"_nickName"]) { //第三步:匹配到對應的屬性,然后修改;注意屬性帶有下劃線 object_setIvar(ps, ivar, @"allenlas"); } } NSLog(@"nickName: %@",[ps valueForKey:@"nickName"]); //allenlas ``` #### 4.5.2改進iOS歸檔和解檔 `歸檔`是一種常用的`輕量型`文件存儲方式,但是它有個弊端: >在歸檔過程中,若一個`Model`有多個屬性,我們不得不對每個屬性進行處理,非常繁瑣 歸檔操作主要涉及兩個方法: `encodeObject` 和 `decodeObjectForKey` ,對于這兩個方法,可以利用Runtime 來進行改進: ``` //原理:使用Runtime動態獲取所有屬性 //解檔操作 - (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super init]; if (self) { unsigned int count = 0; Ivar *ivarList = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { Ivar ivar = ivarList[i]; const char *ivarName = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:ivarName]; id value = [aDecoder decodeObjectForKey:key]; [self setValue:value forKey:key]; } free(ivarList); //釋放指針 } return self; } //歸檔操作 - (void)encodeWithCoder:(NSCoder *)aCoder{ unsigned int count = 0; Ivar *ivarList = class_copyIvarList([self class], &count); for (NSInteger i = 0; i < count; i++) { Ivar ivar = ivarList[i]; NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; id value = [self valueForKey:key]; [aCoder encodeObject:value forKey:key]; } free(ivarList); //釋放指針 } ``` **測試:** ``` //--測試歸檔 Person *ps = [[Person alloc] init]; ps.name = @"allenlas"; ps.age = 20; NSString *temp = NSTemporaryDirectory(); NSString *fileTemp = [temp stringByAppendingString:@"person.archive"]; [NSKeyedArchiver archiveRootObject:ps toFile:fileTemp]; //--測試解檔 NSString *temp = NSTemporaryDirectory(); NSString *fileTemp = [temp stringByAppendingString:@"person.henry"]; Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:fileTemp]; NSLog(@"person-name:%@,person-age:%ld",person.name,person.age); //person-name:allenlas,person-age:20 ``` #### 4.5.3實現字典與模型的轉換 在日常項目開發中,經常會使用`YYModel `或 `MJExtension`等對接口返回的數據對象實現轉模型操作。對于此,可以利用`KVC`和`Runtime `來進行類似的功能實現,在這個過程中需要解決的問題有: ![](https://img.kancloud.cn/e6/03/e603492fc5539473162411d9698094ad_696x214.png) 利用Runtime實現的思路大體如下: >借助Runtime可以`動態獲取`成員列表的特性,遍歷模型中所有屬性,然后以獲取到的屬性名為key,在JSON字典中尋找對應的值value;再將每一個對應Value賦值給模型,就完成了字典轉模型的目的。 **json數據:** ``` { "id":"10089", "name": "Allen", "age":"20", "position":"iOS開發工程師", "address":{ "country":"中國", "province": "廣州" }, "tasks":[{ "name":"Home", "desc":"app首頁開發" },{ "name":"Train", "desc":"app培訓模塊開發" },{ "name":"Me", "desc":"完成個人頁面" } ] } ``` **1.創建NSObject的類目 NSObject+model,用于實現字典轉模型** ``` //在NSObject+model.h中 NS_ASSUME_NONNULL_BEGIN //AAModel協議,協議方法可以返回一個字典,表明特殊字段的處理規則 @protocol AAModel<NSObject> @optional + (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass; @end; @interface NSObject (model) + (instancetype)xk_modelWithDictionary:(NSDictionary *)dictionary; @end NS_ASSUME_NONNULL_END ``` ``` #import "NSObject+model.h" #import <objc/runtime.h> @implementation NSObject (model) + (instancetype)xk_modelWithDictionary:(NSDictionary *)dictionary{ //創建當前模型對象 id object = [[self alloc] init]; //1.獲取當前對象的成員變量列表 unsigned int count = 0; Ivar *ivarList = class_copyIvarList([self class], &count); //2.遍歷ivarList中所有成員變量,以其屬性名為key,在字典中查找Value for (int i= 0; i<count; i++) { //2.1獲取成員屬性 Ivar ivar = ivarList[i]; NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)] ; //2.2截取成員變量名:去掉成員變量前面的"_"號 NSString *propertyName = [ivarName substringFromIndex:1]; //2.3以屬性名為key,在字典中查找value id value = dictionary[propertyName]; //3.獲取成員變量類型, 因為ivar_getTypeEncoding獲取的類型是"@\"NSString\""的形式 //所以我們要做以下的替換 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];// 替換: //3.1去除轉義字符:@\"name\" -> @"name" ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""]; //3.2去除@符號 ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""]; //4.對特殊成員變量進行處理: //判斷當前類是否實現了協議方法,獲取協議方法中規定的特殊變量的處理方式 NSDictionary *perpertyTypeDic; if([self respondsToSelector:@selector(modelContainerPropertyGenericClass)]){ perpertyTypeDic = [self performSelector:@selector(modelContainerPropertyGenericClass) withObject:nil]; } //4.1處理:字典的key與模型屬性不匹配的問題,如id->uid id anotherName = perpertyTypeDic[propertyName]; if(anotherName && [anotherName isKindOfClass:[NSString class]]){ value = dictionary[anotherName]; } //4.2.處理:模型嵌套模型 if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) { Class modelClass = NSClassFromString(ivarType); if (modelClass != nil) { //將被嵌套字典數據也轉化成Model value = [modelClass xk_modelWithDictionary:value]; } } //4.3處理:模型嵌套模型數組 //判斷當前Vaue是一個數組,而且存在協議方法返回了perpertyTypeDic if ([value isKindOfClass:[NSArray class]] && perpertyTypeDic) { Class itemModelClass = perpertyTypeDic[propertyName]; //封裝數組:將每一個子數據轉化為Model NSMutableArray *itemArray = @[].mutableCopy; for (NSDictionary *itemDic in value) { id model = [itemModelClass xk_modelWithDictionary:itemDic]; [itemArray addObject:model]; } value = itemArray; } //5.使用KVC方法將Vlue更新到object中 if (value != nil) { [object setValue:value forKey:propertyName]; } } free(ivarList); //釋放C指針 return object; } @end ``` **2.分別新建`UserModel、AddressModel、TasksModel`對json處理進行處理:** UserModel類 ``` #import "NSObject+model.h" #import "AddressModel.h" #import "TasksModel.h" NS_ASSUME_NONNULL_BEGIN @interface UserModel : NSObject<AAModel> //普通屬性 @property (nonatomic, copy) NSString * uid; @property (nonatomic, copy) NSString * name; @property (nonatomic, copy) NSString * position; @property (nonatomic, assign) NSInteger age; //嵌套模型 @property (nonatomic, strong) AddressModel *address; //嵌套模型數組 @property (nonatomic, strong) NSArray *tasks; @end NS_ASSUME_NONNULL_END @implementation UserModel + (NSDictionary<NSString *,id> *)modelContainerPropertyGenericClass{ //需要特別處理的屬性 return @{@"tasks" : [TasksModel class],@"uid":@"id"}; } @end ``` AddressModel類 ``` #import "NSObject+model.h" NS_ASSUME_NONNULL_BEGIN @interface AddressModel : NSObject @property (nonatomic, copy) NSString * country; @property (nonatomic, copy) NSString * province; @end NS_ASSUME_NONNULL_END @implementation AddressModel @end ``` TasksModel類 ``` #import "NSObject+model.h" NS_ASSUME_NONNULL_BEGIN @interface TasksModel : NSObject @property (nonatomic, copy) NSString * name; @property (nonatomic, copy) NSString * desc; @end NS_ASSUME_NONNULL_END @implementation TasksModel @end ``` **3.代碼測試** ``` - (void)viewDidLoad { [super viewDidLoad]; //讀取JSON數據 NSDictionary * jsonData = @{ @"id":@"10089", @"name": @"Allen", @"age":@"20", @"position":@"iOS開發工程師", @"address":@{ @"country":@"中國", @"province":@"廣州" }, @"tasks":@[@{ @"name":@"Home", @"desc":@"app首頁開發" },@{ @"name":@"Train", @"desc":@"app培訓模塊開發" },@{ @"name":@"Me", @"desc":@"完成個人頁面" } ] }; //字典轉模型 UserModel * user = [UserModel xk_modelWithDictionary:jsonData]; TasksModel * task = user.tasks[0]; NSLog(@"%@",task.name); } ``` 其執行結果,數據結構如下: ![](https://img.kancloud.cn/42/57/425752265b260a5fb58fa4adb4edb978_1692x428.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看