runtime 運行時機制 + 應用場景

 

 

 

  ——————注:(非海騰原創)    git


 

1、runtime 運行時機制

  • Objective-C語言是一門動態語言,它將不少靜態語言在編譯和連接時期作的事放到了運行時來處理。這種動態語言的優點在於:咱們寫代碼時更具靈活性,如咱們能夠把消息轉發給咱們想要的對象,或者隨意交換一個方法的實現等。
  • 這種特性意味着Objective-C不只須要一個編譯器,還須要一個運行時系統來執行編譯的代碼。對於Objective-C來講,這個運行時系統就像一個操做系統同樣:它讓全部的工做能夠正常的運行。這個運行時系統即Objc Runtime。Objc Runtime實際上是一個Runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了面向對象的能力。
  • 對於C語言,函數的調用在編譯的時候會決定調用哪一個函數。對於OC的函數,屬於動態調用過程,在編譯的時候並不能決定真正調用哪一個函數,只有在真正運行的時候纔會根據函數的名稱找到對應的函數來調用。
  • 在編譯階段,OC能夠調用任何函數,即便這個函數並未實現,只要聲明過就不會報錯。
    在編譯階段,C語言調用未實現的函數就會報錯。

2、運行時的做用

  • 能得到某個類的全部成員變量
  • 能得到某個類的全部屬性
  • 能得到某個類的全部方法
  • 交換方法實現
  • 能動態添加一個成員變量
  • 能動態添加一個屬性
  • 字典轉模型
  • runtime歸檔/反歸檔

常見的函數,頭文件github

#import<objc/runtime.h> : //成員變量,類,方法 class_copyIvarList : 得到某個類內部的全部成員變量 class_copyMethodList : 得到某個類內部的全部方法 class_getInstanceMethod : 得到某個具體的實例方法 (對象方法,減號-開頭) class_getClassMethod : 得到某個具體的類方法 (加號+開頭) method_exchangeImplementations : 交換兩個方法的實現 #import<objc/message.h> : //消息機制 objc_msgSend(...)

3、應用場景

場景1 ----------------------- runtime 發送消息 ----------------------面試

方法的調用本質是,對象發送消息objective-c

objc/msgSend 只有對象才能發送消息,所以以objc開頭
  導入 #import <objc/message.h> 或者直接導入 #import <objc/runtime.h> 注意 Xcode 6 以後代碼檢查 單獨使用<objc/message.h>會報錯 builtSeting 修改 Enable Strict Checking of objc_msgSend Calls -> NO 才能調用 objc_msgSend

咱們建立一個對象Dog 自定義一個實例方法和類方法,並實現方法swift

#import <Foundation/Foundation.h> @interface Dog : NSObject - (void)run; + (void)eat; - (void)run { NSLog(@"一隻狗正在奔跑。。。。"); } + (void)eat { NSLog(@"一隻狗正在吃。。。。"); } @end

而後咱們在vc裏面使用數組

// 建立對象 -> 調用方法數據結構

Dog *d = [[Dog alloc] init]; // 調用方法 -> 實例方法

// [d run];app

// 系統底層本質 -> 讓對象發消息 objc_msgSend(d, @selector(run)); // 等同於 [d run]; // 調用方法 -> 類方法

// [Dog eat];函數

objc_msgSend([Dog class], @selector(eat)); // 等同於 [Dog eat];

消息機制原理
對象根據方法編號SEL去映射表查找對應方法的實現,即咱們在調用實例方法的時候,實際上是實例對象d,在發送消息,消息的實現,實際上是SEL,根據方法編號,去映射表查找對應方法的實現.類方法本質是[Dog class],發什麼消息.學習


場景2 ------------------- runtime 交換方法 ----------------------

使用場景,系統自帶方法功能不夠用,給系統自帶的方法擴展一些功能,並保存原有功能.

  • 實現方法 1 -> 繼承系統的類, 重寫方法.
  • 實現方法 2 -> runtime 交換方法

案例:這裏咱們寫一個UIImage的類目,來保證UIImage,不會被渲染,同時,若是圖片爲空,會打印提示.

#import <UIKit/UIKit.h> @interface UIImage (Image) // 建立一個類方法 // 傳入 一個字符串 -> 返回 不被 渲染的原始圖片 + (id)ImageOriginalWithStrName:(NSString *)name; @end

在.m進行實現, 使用method_exchangeImplementations(method1, method2)方法交換,詳情看代碼註釋.

#import "UIImage+Image.h" #import <objc/runtime.h> @implementation UIImage (Image) // 加載內存時調用 + (void)load { // 交換方法 // 獲取 ImageOriginalWithStrName: 方法 Method imageWithName = class_getClassMethod(self, @selector(ImageOriginalWithStrName:)); // 獲取 imageName 方法 Method imageName = class_getClassMethod(self, @selector(imageNamed:)); // 交換方法地址, 至關於交換實現 method_exchangeImplementations(imageWithName, imageName); } // 注意 // 這裏 返回值是一個函數結果類型 使用instancetype 會產生類型不匹配, 因此使用id // 不能在改分類UIImage中重寫 imageNamed:由於系統會把imageNamed:原來的功能覆蓋掉 // 分類中不能調用super自己 + (id)ImageOriginalWithStrName:(NSString *)name { UIImage *image = [[self ImageOriginalWithStrName:name] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; if (image == nil) { NSLog(@"加載圖片爲空..."); } return image; } @end

在vc中使用

UIImage *image = [UIImage imageNamed:@"123"]; // 這裏經過runtime 交換自定義方法 和 UIImage 自身的imageNamed: 來實現圖片加載 不被渲染 // 代碼在 #import "UIImage+Image.h" 類目中

// 傳入了一個空123


log日誌

場景3-------------------- runtime 動態添加方法 ---------------------

開發場景:
若是一個類方法很是多,加載類到內存中的時候,會比較耗費資源,須要給給個方法生成映射表,這裏可使用動態添加方法給某個類.
經典面試題 有沒有使用過 performSelector 其實主要是想問你有沒有動態添加過方法.

// 下面是簡單使用,繼續以Dog 對象爲例 [d performSelector:@selector(jump)]; // 默認的狗 沒有jump 這個方法實現, 直接調用會出錯,可使用 performSelector 調用就不會出錯. // --> 動態添加方法 不會報錯

而後咱們能夠到Dog對象中進行添加動態方法

// void(*)() // 默認方法 都有兩個隱式參數 // 定義添加的方法 void jump (id self, SEL sel) { NSLog(@" eat ...... %@ --- %@ ", self, NSStringFromSelector(sel)); } // 當一個對象調用未實現的方法,會調用(+ (BOOL)resolveInstanceMethod:(SEL)sel )這個方法處理,而且會把這個對應方法列表傳過來 // 因此動態添加方法, 咱們能夠在這裏作判斷, 爲咱們未實現的方法動態添加本身的方法. + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(jump)) { // <#__unsafe_unretained Class cls#> 參數1 給哪一個類添加方法 // <#SEL name#> 參數2 添加放啊編號 // <#IMP imp#> 參數3 添加方法函數實現 (函數地址) // <#const char *types#> 參數4 函數的類型 (返回值 + 參數類型) v:void @:對象-> self :表示SEL -> _cmd class_addMethod(self, @selector(jump), jump, "v@:"); } return [super resolveInstanceMethod:sel]; }

// 實現動態添加方法後,在vc中,使用不會出錯.

場景4 -------------------- runtime 給分類添加屬性 ---------------

原理給一個類聲明屬性,實際上是本質就是給這個類添加關聯,並非直接把這個值的內存空間添加到內存空間
案例 : 這裏在類目中對NSObject擴展name 屬性 能夠直接給屬性賦值使用

#import <Foundation/Foundation.h> @interface NSObject (Property) @property (nonatomic, strong) NSString *name; // 添加一個name屬性 @end

在.m 中使用objc_setAssociatedObject動態添加方法

import "NSObject+Property.h" #import <objc/runtime.h> static const char *key = "name"; @implementation NSObject (Property) - (NSString *)name { // 根據關聯的key,獲取關聯的值 return objc_getAssociatedObject(self, key); } - (void)setName:(NSString *)name { // 參數1 <#id object#> 給那個對象添加關聯 // 參數2 <#const void *key#> 關聯的key 值,經過這個key 值獲取 // 參數3 <#id value#> 關聯的value // 參數4 <#objc_AssociationPolicy policy#> 關聯的策略 // typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { // OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ // OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. // * The association is not made atomically. */ // OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. // * The association is not made atomically. */ // OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. // * The association is made atomically. */ // OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. // * The association is made atomically. */ // }; objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end

VC 使用.能正常獲取該屬性,並能操做使用

NSObject *objc = [[NSObject alloc] init]; objc.name = @"xxx"; NSLog(@"%@", objc.name);

場景5 -------------------- runtime 字典轉模型 ---------------

設計模型 : 字典轉模型的第一步
模型屬性, 一般須要和字典中的key一一對應
經過建立一個分類,專門根據字典生成對應屬性的字符串 (高效率的字典轉模型)

咱們能夠正常經過寫類目來實現 NSObject+DictionaryToModel

// 自動打印屬性字符串 // 寫一個類方法 + (void)transformToModelByDictionary:(NSDictionary *)dict; // 實現 + (void)transformToModelByDictionary:(NSDictionary *)dict { // 根據類別拼接屬性字符串代碼 NSMutableString *str = [NSMutableString string]; // 遍歷字典,把字典中的全部key取出來;生成對應的屬性代碼 [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { // 對各種新進行分類, 抽取出來 NSString *type; // 須要 理解 系統底層 數據結構類型 // 能夠自行斷點查看 各種型底層類型 if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) { type = @"NSString"; } else if ([obj isKindOfClass:NSClassFromString(@"__NSArrayI")]) { type = @"NSArray"; } else if ([obj isKindOfClass:NSClassFromString(@"__NSArrayM")]) { type = @"NSMutableArray"; } else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]) { type = @"NSNumber"; } else if ([obj isKindOfClass:NSClassFromString(@"__NSDictionaryI")]) { type = @"NSDictionary"; } else if ([obj isKindOfClass:NSClassFromString(@"__NSDictionaryM")]) { type = @"NSMutableDictionary"; } // 屬性字符串 NSString *property; if ([type containsString:@"NS"]) { property = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@", type, key]; } else { property = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@", type, key]; } // 每生成一對屬性字符串 就自動換行 [str appendFormat:@"\n%@\n", property]; }]; // 打印出拼接的字符串 NSLog(@"對應屬性 -> %@", str); }

vc中測試

NSArray *array = [NSArray arrayWithObjects:@1, @2, @3, @4, nil]; NSMutableArray *arr = [NSMutableArray arrayWithArray:array]; NSDictionary *dic = [NSDictionary dictionaryWithObject:@"dfdf" forKey:@"dfsdf"]; [NSObject transformToModelByDictionary:@{@"name" : @"str", @"num" : array, @"count" : @0, @"hah": arr, @"dic" : dic}];

打印結果

// 字典轉模型KVC方式
dic setValuesForKeysWithDictionary:<#(nonnull NSDictionary<NSString *,id> *)#>
必須保證屬性和字典中key值一一對應, 若是不一致 會調用 setValue:forUndefinedKey: 重寫該方法能夠覆蓋系統方法 能夠繼續KVC 字典轉模型

字典轉模型 -> runtime
-> MJEXtension

#import "NSObject+Model.h" #import <objc/runtime.h> @implementation NSObject (Model) + (instancetype)modelWithDictionary:(NSDictionary *)dictionary { // 思路:遍歷模型中全部屬性-》使用運行時 // 0.建立對應的對象 id objc = [[self alloc] init]; // 1.利用runtime給對象中的成員屬性賦值 // class_copyIvarList:獲取類中的全部成員屬性 // Ivar:成員屬性的意思 // 第一個參數:表示獲取哪一個類中的成員屬性 // 第二個參數:表示這個類有多少成員屬性,傳入一個Int變量地址,會自動給這個變量賦值 // 返回值Ivar *:指的是一個ivar數組,會把全部成員屬性放在一個數組中,經過返回的數組就能所有獲取到。 /* 相似下面這種寫法 Ivar ivar; Ivar ivar1; Ivar ivar2; // 定義一個ivar的數組a Ivar a[] = {ivar,ivar1,ivar2}; // 用一個Ivar *指針指向數組第一個元素 Ivar *ivarList = a; // 根據指針訪問數組第一個元素 ivarList[0]; */ unsigned int count; // 獲取類中的全部成員屬性 Ivar *ivarList = class_copyIvarList(self, &count); for (int i = 0; i < count; i++) { // 根據角標,從數組取出對應的成員屬性 Ivar ivar = ivarList[i]; // 獲取成員屬性名 NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 處理成員屬性名->字典中的key // 從第一個角標開始截取 NSString *key = [name substringFromIndex:1]; // 根據成員屬性名去字典中查找對應的value id value = dictionary[key]; // 二級轉換:若是字典中還有字典,也須要把對應的字典轉換成模型 // 判斷下value是不是字典 if ([value isKindOfClass:[NSDictionary class]]) { // 字典轉模型 // 獲取模型的類對象,調用modelWithDict // 模型的類名已知,就是成員屬性的類型 // 獲取成員屬性類型 NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // 生成的是這種@"@\"User\"" 類型 -》 @"User" 在OC字符串中 \" -> ",\是轉義的意思,不佔用字符 // 裁剪類型字符串 NSRange range = [type rangeOfString:@"\""]; type = [type substringFromIndex:range.location + range.length]; range = [type rangeOfString:@"\""]; // 裁剪到哪一個角標,不包括當前角標 type = [type substringToIndex:range.location]; // 根據字符串類名生成類對象 Class modelClass = NSClassFromString(type); if (modelClass) { // 有對應的模型才須要轉 // 把字典轉模型 value = [modelClass modelWithDictionary:value]; } } // 三級轉換:NSArray中也是字典,把數組中的字典轉換成模型. // 判斷值是不是數組 if ([value isKindOfClass:[NSArray class]]) { // 判斷對應類有沒有實現字典數組轉模型數組的協議 if ([self respondsToSelector:@selector(arrayContainModelClass)]) { // 轉換成id類型,就能調用任何對象的方法 id idSelf = self; // 獲取數組中字典對應的模型 NSString *type = [idSelf arrayContainModelClass][key]; // 生成模型 Class classModel = NSClassFromString(type); NSMutableArray *arrM = [NSMutableArray array]; // 遍歷字典數組,生成模型數組 for (NSDictionary *dict in value) { // 字典轉模型 id model = [classModel modelWithDictionary:dict]; [arrM addObject:model]; } // 把模型數組賦值給value value = arrM; } } if (value) { // 有值,才須要給模型的屬性賦值 // 利用KVC給模型中的屬性賦值 [objc setValue:value forKey:key]; } } return objc; } @end
// 測試 NSMutableDictionary *di = [NSMutableDictionary dictionary]; NSMutableArray *a = [NSMutableArray array]; [a addObject:dic]; [di setValue:a forKey:@"dic"]; id model = [NSObject modelWithDictionary:dic]; NSLog(@"%@", model); id dmodel = [NSObject modelWithDictionary:di]; NSLog(@"%@", dmodel);

場景6 -------------------- runtime 快速歸檔 ---------------

主要仍是經過class_copyIvarList,遍歷對象屬性,來作事情.

#import <Foundation/Foundation.h> @interface NSObject (Extension) - (NSArray *)ignoredNames; - (void)encode:(NSCoder *)aCoder; - (void)decode:(NSCoder *)aDecoder; @end
#import "NSObject+Extension.h" #import <objc/runtime.h> @implementation NSObject (Extension) - (void)decode:(NSCoder *)aDecoder { // 一層層父類往上查找,對父類的屬性執行歸解檔方法 Class c = self.class; while (c &&c != [NSObject class]) { unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(c, &outCount); for (int i = 0; i < outCount; i++) { Ivar ivar = ivars[i]; NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 若是有實現該方法再去調用 if ([self respondsToSelector:@selector(ignoredNames)]) { if ([[self ignoredNames] containsObject:key]) continue; } id value = [aDecoder decodeObjectForKey:key]; [self setValue:value forKey:key]; } free(ivars); c = [c superclass]; } } - (void)encode:(NSCoder *)aCoder { // 一層層父類往上查找,對父類的屬性執行歸解檔方法 Class c = self.class; while (c &&c != [NSObject class]) { unsigned int outCount = 0; Ivar *ivars = class_copyIvarList([self class], &outCount); for (int i = 0; i < outCount; i++) { Ivar ivar = ivars[i]; NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 若是有實現該方法再去調用 if ([self respondsToSelector:@selector(ignoredNames)]) { if ([[self ignoredNames] containsObject:key]) continue; } id value = [self valueForKeyPath:key]; [aCoder encodeObject:value forKey:key]; } free(ivars); c = [c superclass]; } } // 設置須要忽略的屬性 - (NSArray *)ignoredNames { return @[@"bone"]; } // 在須要歸解檔的對象中實現下面方法便可: //// 在系統方法內來調用咱們的方法 //- (instancetype)initWithCoder:(NSCoder *)aDecoder { // if (self = [super init]) { // [self decode:aDecoder]; // } // return self; //} // //- (void)encodeWithCoder:(NSCoder *)aCoder { // [self encode:aCoder]; //} @end

----- 最後使用runtime 來寫了經過 block回調 直接調用手勢識別的action ---------------

#import <UIKit/UIKit.h> typedef void(^XXWGestureBlock)(id gestureRecognizer); @interface UIGestureRecognizer (Block) /** * 使用類方法 初始化 添加手勢 * * @param block 手勢回調 * * @return block 內部 action * * * 使用 __unsafe_unretained __typeof(self) weakSelf = self; * 防止循環引用 * */ + (instancetype)xxw_gestureRecognizerWithActionBlock:(XXWGestureBlock)block; @end
#import "UIGestureRecognizer+Block.h" #import <objc/runtime.h> static const int target_key; @implementation UIGestureRecognizer (Block) + (instancetype)xxw_gestureRecognizerWithActionBlock:(XXWGestureBlock)block { return [[self alloc]initWithActionBlock:block]; } - (instancetype)initWithActionBlock:(XXWGestureBlock)block { self = [self init]; [self addActionBlock:block]; [self addTarget:self action:@selector(invoke:)]; return self; } /** * Returns the value associated with a given object for a given key. * * @param object The source object for the association. * @param key The key for the association. * * @return The value associated with the key \e key for \e object. * * @see objc_setAssociatedObject */ //OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) //__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1); - (void)addActionBlock:(XXWGestureBlock)block { if (block) { objc_setAssociatedObject(self, &target_key, block, OBJC_ASSOCIATION_COPY_NONATOMIC); } } - (void)invoke:(id)sender { XXWGestureBlock block = objc_getAssociatedObject(self, &target_key); if (block) { block(sender); } } @end

VC 中使用,咱們能夠直接調用手勢對象,來實現action,是否是很方便.

[self.view addGestureRecognizer:[UITapGestureRecognizer xxw_gestureRecognizerWithActionBlock:^(id gestureRecognizer) { NSLog(@"點擊-------"); }]]; [self.view addGestureRecognizer:[UILongPressGestureRecognizer xxw_gestureRecognizerWithActionBlock:^(id gestureRecognizer) { NSLog(@"長按-------"); }]];

總結:經過學習使用 runtime,咱們能更好來了解體會oc底層的運行機制,同時,咱們可使用runtime,來獲取系統底層私有方法和屬性來使用,動態的添加屬性和方法,在有些場景下使用很是的高效,同時咱們可使用runtime,開完成不少好的Category,來高效開發.本文章只是對runtime的一些基礎知識的概括,可以讓初學者,更好更快的理解runtime,力圖起個拋磚引玉的做用。還有許多關於runtime有意思東西還須要讀者本身去探索發現。

runtime 深刻學習 推薦 ->
顧鵬:http://tech.glowing.com/cn/objective-c-runtime/
楊瀟玉:http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
南峯子:http://southpeak.github.io/blog/2014/10/25/objective-c-runtime-yun-xing-shi-zhi-lei-yu-dui-xiang/
葉純俊http://chun.tips/blog/2014/11/05/bao-gen-wen-di-objective[nil]c-runtime(1)[nil]-self-and-super/



文/Civel_Xu(簡書做者) 原文連接:http://www.jianshu.com/p/f87b220f48ae 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。
相關文章
相關標籤/搜索