RunTime簡介##
剛入行的時候,經常聽到某些自稱大神的人說runtime怎么怎么強大怎么怎么牛逼,我總是被忽悠的一愣一愣。但是當你問他runtime到底是什么的時候?他就只會含含糊糊告訴你三個字:運行時。。。聽到這我只能說:你說的好有道理,我竟然無言以對?。?!但這個東西到底是什么?到底能用在哪里呢?下面簡單講一下自己的理解。為了不把大家搞懵逼,下面我先通過幾個實例講一下runtime到底用在什么地方。
RunTime的應用場景##
- 1.給分類添加一個屬性(本質上只是添加了關聯(lián),類似于屬性的作用)
當我們看一些第三方框架的時候我們可能看到這樣的代碼,例如在SDWebImage中
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(self, &imageURLKey);
SDWebImage是UIImageView的一個分類。我們可以使用方法sd_setImageWithURL根據(jù)URL去下載圖片,但是如果我們的URL地址在后期要用到,我們如何把URL保存在下來呢?我們都知道通過分類可以添加方法,但是添加屬性就無能為力了。這個時候我們的runtime就派上用場了,通過上邊的兩個函數(shù),我們就能動態(tài)的為我們的分類添加屬性了。通過objc_setAssociatedObject就能把URL 保存下來,在需要用到的時候通過objc_getAssociatedObject取出URL的值。
- 2.動態(tài)的交換方法的實現(xiàn)
大家可能看到過runtime中有這么一個函數(shù)method_exchangeImplementations,它能在運行時動態(tài)的交換兩個方法的具體實現(xiàn)。
+ (void)load {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
但是具體可以用在哪里呢?之前看到一種非常有趣的做法:在我們做項目的后期,我們可能需要在各個頁面加上統(tǒng)計事件,大部分時候是通過在每個控制器的viewDidAppear和viewDidDisappear等方法中加入統(tǒng)計。如果我們的項目比較大,加起來就比較麻煩,但是通過runtime我們可以輕松解決這個問題。首先我們需要寫一個方法,在此方法中加入統(tǒng)計事件,然后在UIViewController的load方法中通過method_exchangeImplementations交換viewDidAppear的實現(xiàn),這樣我們就能輕松解決這個問題。添加頁面的統(tǒng)計以及點擊事件的統(tǒng)計可以參考下邊這篇文章,作者寫的非常棒:http://www.cocoachina.com/ios/20160421/15912.html
- 3.通過runtime自動實現(xiàn)歸檔
在開發(fā)中,我們很多時候需要對數(shù)據(jù)進行歸檔,但是如果一個Model中的屬性比較多的時候,我們可能會被搞得頭昏腦漲,十分影響我們的開發(fā)效率。這個時候我們可以通過runtime來幫我們解決這個問題。大致實現(xiàn)如下:
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = 0;
Ivar *vars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
const char *name = ivar_getName(vars[i]);
NSString *strName = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:strName];
[self setValue:value forKey:strName];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *vars = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
const char *name = ivar_getName(vars[i]);
NSString *strName = [NSString stringWithUTF8String:name];
id value = [self valueForKey:strName];
[aCoder encodeObject:value forKey:strName];
}
}
```
* 4.通過runtime實現(xiàn)字典轉模型
在iOS開發(fā)中,我們可能會用到各種字典轉模型的框架,如:JSONModel/MJExtension等。其實這些字典轉模型的框架正是利用了runtime的特性。首先通過class_copyIvarList和ivar_getName獲取屬性名稱,然后在字典中查找到對應的值,最后通過KVC為model的屬性賦值。大致過程如下:
```
+ (void)modelWithDict:(NSDictionary *)dict {
id objc = [[self alloc] init];
unsigned int count;
// 獲取model的所有屬性
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 獲取成員屬性名
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
// 去掉name里的第一個下滑線字符
NSString *key = [name substringFromIndex:1];
// 從字典中查找對應屬性的值
id value = dict[key];
[objc setValue:value forKey:key];
}
}
```
總結:runtime的使用場景還有動態(tài)的給類添加方法、發(fā)送消息等等,但個人覺得比較實用的有以上幾種,當然更多的使用的場合和技巧還要靠大家來發(fā)掘。
##RunTime能做什么?
runtime能做什么呢?其實我們只要在runtime.h這個頭文件中查看它所提供的API就知道了,下面只是列出一些我們常用的runtime API。
```
//獲取cls類對象所有成員ivar結構體
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//獲取cls類對象name對應的實例方法結構體
Method class_getInstanceMethod(Class cls, SEL name)
//獲取cls類對象name對應類方法結構體
Method class_getClassMethod(Class cls, SEL name)
//獲取cls類對象name對應方法imp實現(xiàn)
IMP class_getMethodImplementation(Class cls, SEL name)
//測試cls對應的實例是否響應sel對應的方法
BOOL class_respondsToSelector(Class cls, SEL sel)
//獲取cls對應方法列表
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//測試cls是否遵守protocol協(xié)議
BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
//為cls類對象添加新方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//替換cls類對象中name對應方法的實現(xiàn)
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//為cls添加新成員
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
//為cls添加新屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
//獲取m對應的選擇器
SEL method_getName(Method m)
//獲取m對應的方法實現(xiàn)的imp指針
IMP method_getImplementation(Method m)
//獲取m方法的對應編碼
const char *method_getTypeEncoding(Method m)
//獲取m方法參數(shù)的個數(shù)
unsigned int method_getNumberOfArguments(Method m)
//copy方法返回值類型
char *method_copyReturnType(Method m)
//獲取m方法index索引參數(shù)的類型
char *method_copyArgumentType(Method m, unsigned int index)
//獲取m方法返回值類型
void method_getReturnType(Method m, char *dst, size_t dst_len)
//獲取方法的參數(shù)類型
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len)
//設置m方法的具體實現(xiàn)指針
IMP method_setImplementation(Method m, IMP imp)
//交換m1,m2方法對應具體實現(xiàn)的函數(shù)指針
void method_exchangeImplementations(Method m1, Method m2)
//獲取v的名稱
const char *ivar_getName(Ivar v)
//獲取v的類型編碼
const char *ivar_getTypeEncoding(Ivar v)
//設置object對象關聯(lián)的對象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取object關聯(lián)的對象
id objc_getAssociatedObject(id object, const void *key)
//移除object關聯(lián)的對象
void objc_removeAssociatedObjects(id object)
```
##到底什么是RunTime?
就自己的理解而言,runtime就是一套底層的C語言API,我們的程序在運行的時候會將我們編寫的OC代碼轉為底層的C語言函數(shù)來執(zhí)行。于是我們可以利用runtime直接調用這些C語言的函數(shù),這樣我們可以在運行時動態(tài)的修改類的具體實現(xiàn)等。