RunTime簡(jiǎn)介
因?yàn)镺bjc是一門(mén)動(dòng)態(tài)語(yǔ)言掘殴,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí)舆乔。也就是說(shuō)只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來(lái)執(zhí)行編譯后的代碼。這就是 Objective-C Runtime 系統(tǒng)存在的意義茎杂,它是整個(gè)Objc運(yùn)行框架的一塊基石。
RunTime簡(jiǎn)稱(chēng)運(yùn)行時(shí)纫雁。OC就是運(yùn)行時(shí)機(jī)制煌往,其中最主要的是消息機(jī)制。對(duì)于C語(yǔ)言轧邪,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)刽脖。對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過(guò)程忌愚,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)曲管,只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱(chēng)找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
Runtime基本是用C和匯編寫(xiě)的硕糊,可見(jiàn)蘋(píng)果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力院水。你可以在這里下到蘋(píng)果維護(hù)的開(kāi)源代碼腊徙。蘋(píng)果和GNU各自維護(hù)一個(gè)開(kāi)源的runtime版本,這兩個(gè)版本之間都在努力的保持一致檬某。
RunTime中主要使用的函數(shù)定義在message.h和runtime.h
這兩個(gè)文件中撬腾。 在message.h中主要包含了一些向?qū)ο蟀l(fā)送消息的函數(shù),這是OC對(duì)象方法調(diào)用的底層實(shí)現(xiàn)恢恼。
使用時(shí)民傻,需要導(dǎo)入文件,導(dǎo)入如:
#import <objc/message.h>
#import <objc/runtime.h>
函數(shù)的定義
- 對(duì)
對(duì)象
進(jìn)行操作的方法一般以object_
開(kāi)頭 - 對(duì)
類(lèi)
進(jìn)行操作的方法一般以class_
開(kāi)頭 - 對(duì)
類(lèi)或?qū)ο蟮姆椒?/code>進(jìn)行操作的方法一般以
method_
開(kāi)頭 - 對(duì)
成員變量
進(jìn)行操作的方法一般以ivar_
開(kāi)頭 - 對(duì)
屬性
進(jìn)行操作的方法一般以property_
開(kāi)頭開(kāi)頭 - 對(duì)
協(xié)議
進(jìn)行操作的方法一般以protocol_
開(kāi)頭
根據(jù)以上的函數(shù)的前綴 可以大致了解到層級(jí)關(guān)系厅瞎。
對(duì)于以objc_開(kāi)頭的方法饰潜,則是runtime最終的管家,可以獲取內(nèi)存中類(lèi)的加載信息,類(lèi)的列表和簸,關(guān)聯(lián)對(duì)象和關(guān)聯(lián)屬性等操作彭雾。
RunTime使用
一、runtime本質(zhì)锁保,消息發(fā)送
方法調(diào)用的本質(zhì)薯酝,就是讓對(duì)象發(fā)送消息。
objc_msgSend,只有對(duì)象才能發(fā)送消息爽柒,因此以objc開(kāi)頭.
使用消息機(jī)制前提吴菠,必須導(dǎo)入#import <objc/message.h>
代碼例子:
// 創(chuàng)建person對(duì)象
Person *p = [[Person alloc] init];
// 調(diào)用對(duì)象方法
[p eat];
// 底層實(shí)現(xiàn):讓對(duì)象發(fā)送消息
objc_msgSend(p, @selector(eat)); // Xcode5之后這里需要把 targets -> bulid Settings 搜索 msg 把objc_msgSend Calls YES 修改為 NO
// 調(diào)用類(lèi)方法的方式:三種
// 第一種通過(guò)類(lèi)名調(diào)用
[Person eat];
// 第二種通過(guò)類(lèi)對(duì)象調(diào)用
[[Person class] eat];
// 第三種調(diào)用方法
[Person performSelector:@selector(eat)];
// 用類(lèi)名調(diào)用類(lèi)方法,底層會(huì)自動(dòng)把類(lèi)名轉(zhuǎn)換成類(lèi)對(duì)象調(diào)用
// 底層實(shí)現(xiàn) 讓類(lèi)對(duì)象發(fā)送消息
objc_msgSend([Person class], @selector(eat));
/**
還有浩村,我是怎么知道上面的方法的本質(zhì)demo的呢做葵,
我們可以通過(guò)clang 命令來(lái)查看代碼生成的CPP代碼。
最終代碼,需要把當(dāng)前代碼重新編譯,用xcode編譯器,clang
clang -rewrite-objc main.m 查看最終生成代碼
**/
二心墅、runtime方法交換
交換方法實(shí)現(xiàn)的需求場(chǎng)景:
1酿矢、自己創(chuàng)建了一個(gè)功能性的方法,在項(xiàng)目中多次被引用怎燥,當(dāng)項(xiàng)目的需求發(fā)生改變時(shí)瘫筐,要使用另一種功能代替這個(gè)功能,要求是不改變舊的項(xiàng)目(也就是不改變?cè)瓉?lái)方法的實(shí)現(xiàn))铐姚。
2策肝、可以做一些異常處理,防止APP崩潰處理隐绵,(數(shù)組越界等等)
可以在類(lèi)的分類(lèi)中之众,再寫(xiě)一個(gè)新的方法(是符合新的需求的),然后交換兩個(gè)方法的實(shí)現(xiàn)。這樣依许,在不改變項(xiàng)目的代碼棺禾,而只是增加了新的代碼 的情況下,就完成了項(xiàng)目的改進(jìn)悍手。
交換兩個(gè)方法的實(shí)現(xiàn)一般寫(xiě)在類(lèi)的load方法里面帘睦,因?yàn)閘oad方法會(huì)在程序運(yùn)行前加載一次袍患,而initialize方法會(huì)在類(lèi)或者子類(lèi)在 第一次使用的時(shí)候調(diào)用,當(dāng)有分類(lèi)的時(shí)候會(huì)調(diào)用多次竣付。
下面demo是從這里看到的
@implementation NSMutableArray (Safe)
+ (void)load
{
[NSClassFromString(@"__NSArrayM") swapMethod:@selector(objectAtIndex:)
currentMethod:@selector(ls_objectAtIndex:)];
}
// 實(shí)例方法的交換
+ (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
{
Method firstMethod = class_getInstanceMethod(self, originMethod);
Method secondMethod = class_getInstanceMethod(self, currentMethod);
method_exchangeImplementations(firstMethod, secondMethod);
}
- (id)ls_objectAtIndex:(NSUInteger)index
{
if (index >= self.count)
{
NSLog(@"數(shù)組越界诡延;;古胆;肆良;;逸绎;惹恃;;棺牧;巫糙;;颊乘;");
return nil;
}
return [self ls_objectAtIndex:index];
}
@end
三参淹、獲取相關(guān)參數(shù)
1、獲取成員變量乏悄,包括屬性生成的成員變量
+ (NSArray *)fetchIvarList
{
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
const char *ivarName = ivar_getName(ivarList[i]);
const char *ivarType = ivar_getTypeEncoding(ivarList[i]);
dic[@"type"] = [NSString stringWithUTF8String:ivarType];
dic[@"ivarName"] = [NSString stringWithUTF8String:ivarName];
[mutableList addObject:dic];
}
free(ivarList);
return [NSArray arrayWithArray:mutableList];
}
2浙值、獲取類(lèi)的屬性列表,包括私有和公有屬性檩小,也包括分類(lèi)中的屬性
+ (NSArray *)fetchPropertyList
{
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
const char *propertyName = property_getAttributes(propertyList[i]);
[mutableList addObject:[NSString stringWithUTF8String:propertyName]];
}
free(propertyList);
return [NSArray arrayWithArray:mutableList];
}
3开呐、 獲取對(duì)象方法列表:包括getter, setter, 分類(lèi)中的方法等
+ (NSArray *)fetchInstanceMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
4、獲取類(lèi)方法列表 包括分類(lèi)里面的
+ (NSArray *)fetchClassMethodList
{
unsigned int count = 0;
Method *methodList = class_copyMethodList(object_getClass(self), &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++)
{
Method method = methodList[i];
SEL methodName = method_getName(method);
[mutableList addObject:NSStringFromSelector(methodName)];
}
free(methodList);
return [NSArray arrayWithArray:mutableList];
}
5规求、獲取協(xié)議列表筐付,包括.h .m 和分類(lèi)里的
+ (NSArray *)fetchProtocolList
{
unsigned int count = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList(self, &count);
NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++ )
{
Protocol *protocol = protocolList[i];
const char *protocolName = protocol_getName(protocol);
[mutableList addObject:[NSString stringWithUTF8String:protocolName]];
}
return [NSArray arrayWithArray:mutableList];
}
四、關(guān)聯(lián)對(duì)象
關(guān)聯(lián)對(duì)象不是為類(lèi)\對(duì)象添加屬性或者成員變量(因?yàn)樵谠O(shè)置關(guān)聯(lián)后也無(wú)法通過(guò)ivarList或者propertyList取得) 颓哮,而是為類(lèi)添加一個(gè)相關(guān)的對(duì)象家妆,通常用于存儲(chǔ)類(lèi)信息鸵荠,例如存儲(chǔ)類(lèi)的屬性列表數(shù)組冕茅,為將來(lái)字典轉(zhuǎn)模型的方便。
1蛹找、對(duì)象關(guān)聯(lián)屬性
列如姨伤,oc里面,類(lèi)沒(méi)有name 這個(gè)參數(shù)庸疾,我們可以給他添加一個(gè)name
// 定義關(guān)聯(lián)的key
static const char *key = "name";
@implementation NSObject (RunTime)
- (NSString *)name
{
// 根據(jù)關(guān)聯(lián)的key乍楚,獲取關(guān)聯(lián)的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)
// 第二個(gè)參數(shù):關(guān)聯(lián)的key届慈,通過(guò)這個(gè)key獲取
// 第三個(gè)參數(shù):關(guān)聯(lián)的value
// 第四個(gè)參數(shù):關(guān)聯(lián)的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
// 調(diào)用
// NSObject對(duì)象添加屬性
NSObject * object = [[NSObject alloc] init];
object.name = @"gameover";
NSLog(@"object name =====%@",object.name);
// 打印 如下
//2018-06-28 14:52:54.302050+0800 Test_runTime[23578:9984095] object name ===== gameover
2徒溪、對(duì)象關(guān)聯(lián)對(duì)象
動(dòng)態(tài)添加方法會(huì)用到這兩個(gè)api
objc_setAssociatedObject
第一個(gè)參數(shù) id object, 當(dāng)前對(duì)象
第二個(gè)參數(shù) const void *key, 關(guān)聯(lián)的key忿偷,是c字符串
第三個(gè)參數(shù) id value, 被關(guān)聯(lián)的對(duì)象的值
第四個(gè)參數(shù) objc_AssociationPolicy policy關(guān)聯(lián)引用的規(guī)則
objc_getAssociatedObject
第一個(gè)參數(shù) id object, 當(dāng)前對(duì)象
第二個(gè)參數(shù) const void *key, 關(guān)聯(lián)的key,是c字符串
- (void)viewDidLoad {
// 對(duì)象添加關(guān)聯(lián)對(duì)象
// 列如btn 對(duì)象添加多個(gè)參數(shù)臊泌,
UIButton * btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 40)];
btn.backgroundColor = [UIColor redColor];
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
// 傳遞多參數(shù)
objc_setAssociatedObject(btn, "yt_text", @"你好", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(btn, "yt_size", @"15", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// 點(diǎn)擊按鈕觸發(fā)
- (void)btnClick:(UIButton*)btn
{
NSString *yt_text = objc_getAssociatedObject(btn, "yt_text");
NSString *yt_size = objc_getAssociatedObject(btn, "yt_size");
NSLog(@"yt_text ==%@ yt_size ==%@",yt_text,yt_size);
}
// 打印 如下
//2018-06-28 14:50:32.083111+0800 Test_runTime[23575:9981192] yt_text ==你好 yt_size ==15
五鲤桥、動(dòng)態(tài)添加方法
在開(kāi)發(fā)中:如果一個(gè)類(lèi)的方法非常多的時(shí)候,加載類(lèi)到內(nèi)存的時(shí)候也比較耗費(fèi)資源渠概,需要給每個(gè)方法生成映射表茶凳,可以使用動(dòng)態(tài)給某個(gè)類(lèi),添加方法解決
動(dòng)態(tài)添加方法會(huì)用到這個(gè)api
class_addMethod
第一個(gè)參數(shù):給哪個(gè)類(lèi)添加方法
第二個(gè)參數(shù):添加方法的方法編號(hào)
第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
第四個(gè)參數(shù):函數(shù)的類(lèi)型播揪,(返回值+參數(shù)類(lèi)型) v:void @:對(duì)象->self :表示SEL->_cmd
在使用[p performSelector:@selector(yt_sleep) withObject:nil];
或者
的時(shí)候贮喧,由于 yt_sleep
這個(gè)方法在Person.m里面是沒(méi)有實(shí)現(xiàn)
的
所以,會(huì)崩潰猪狈。
所以只要在下面兩個(gè)方法里面分別動(dòng)態(tài)添加方法即可避免該問(wèn)題
在調(diào)用沒(méi)有實(shí)現(xiàn)的實(shí)例方法
會(huì)觸發(fā)這個(gè)方法
+ (BOOL)resolveInstanceMethod:(SEL)sel;
在調(diào)用沒(méi)有實(shí)現(xiàn)的類(lèi)方法
會(huì)觸發(fā)這個(gè)方法
+(BOOL)resolveClassMethod:(SEL)sel;
#import <Foundation/Foundation.h>
@interface Person : NSObject
+ (void)eat;
- (void)eat;
+ (void)yt_sleep;
@end
#import "Person.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation Person
- (void)eat
{
NSLog(@"eat 方法調(diào)用");
}
+ (void)eat
{
NSLog(@"eat 類(lèi)方法調(diào)用");
}
//+ (void)yt_sleep
//{
// NSLog(@"sleep 類(lèi)方法調(diào)用");
//}
//當(dāng)類(lèi)調(diào)用一個(gè)沒(méi)有實(shí)現(xiàn)的類(lèi)方法就會(huì)到這里O渎佟!
+(BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"類(lèi)方法 %@",NSStringFromSelector(sel));
if (sel == @selector(yt_sleep)) {
/*
第一個(gè)參數(shù):給哪個(gè)類(lèi)添加方法
第二個(gè)參數(shù):添加方法的方法編號(hào)
第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
第四個(gè)參數(shù):函數(shù)的類(lèi)型雇庙,(返回值+參數(shù)類(lèi)型) v:void @:對(duì)象->self :表示SEL->_cmd
*/
//class_addMethod(self, @selector(yt_sleep), (IMP)yt_sleep, "v@:"); // 這樣會(huì)崩潰
class_addMethod(objc_getMetaClass("Person"), @selector(yt_sleep), (IMP)yt_sleep, "v@:");
}
return [super resolveClassMethod:sel];
}
//當(dāng)類(lèi)調(diào)用一個(gè)沒(méi)有實(shí)現(xiàn)的對(duì)象方法就會(huì)到這里1テ铡!
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"實(shí)例方法 %@",NSStringFromSelector(sel));
if (sel == @selector(yt_sleep)) {
// 動(dòng)態(tài)添加eat方法
/*
第一個(gè)參數(shù):給哪個(gè)類(lèi)添加方法
第二個(gè)參數(shù):添加方法的方法編號(hào)
第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
第四個(gè)參數(shù):函數(shù)的類(lèi)型状共,(返回值+參數(shù)類(lèi)型) v:void @:對(duì)象->self :表示SEL->_cmd
*/
class_addMethod(self, sel, (IMP)yt_sleep, "v@:");
}
return [super resolveInstanceMethod:sel];
}
// 默認(rèn)方法都有兩個(gè)隱式參數(shù)套耕,
void yt_sleep(id self,SEL sel)
{
NSLog(@"%@ %@ 睡覺(jué)了",self,NSStringFromSelector(sel));
}
@end
//其他地方調(diào)用
// 動(dòng)態(tài)添加實(shí)例方法
[p performSelector:@selector(yt_sleep) withObject:nil];
// 動(dòng)態(tài)添加類(lèi)方法
[Person performSelector:@selector(yt_sleep) withObject:nil];
六、字典轉(zhuǎn)模型
MJExtension 里面就使用runTime來(lái)完成這些相應(yīng)的操作的
下面是我的偽代碼(沒(méi)有mj那么精細(xì))
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic ,strong) NSString * id;
@property (nonatomic ,strong) NSString * name;
@property (nonatomic ,strong) NSString * sex;
@property (nonatomic ,strong) NSString * age;
@property (nonatomic ,strong) NSString * height;
@property (nonatomic ,strong) NSString * weight;
+ (instancetype)modelWithDict:(NSDictionary *)dict;
@end
#import "User.h"
#import <objc/runtime.h>
@implementation User
// Ivar:成員變量 以下劃線(xiàn)開(kāi)頭
// Property:屬性
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
id objc = [[self alloc] init];
// runtime:根據(jù)模型中屬性,去字典中取出對(duì)應(yīng)的value給模型屬性賦值
// 1.獲取模型中所有成員變量 key
// 獲取哪個(gè)類(lèi)的成員變量
// count:成員變量個(gè)數(shù)
unsigned int count = 0;
// 獲取成員變量數(shù)組
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍歷所有成員變量
for (int i = 0; i < count; i++) {
// 獲取成員變量
Ivar ivar = ivarList[i];
// 獲取成員變量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 獲取成員變量類(lèi)型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 獲取key
NSString *key = [ivarName substringFromIndex:1];
// 去字典中查找對(duì)應(yīng)value
// key:user value:NSDictionary
id value = dict[key];
// 二級(jí)轉(zhuǎn)換:判斷下value是否是字典,如果是,字典轉(zhuǎn)換層對(duì)應(yīng)的模型
// 并且是自定義對(duì)象才需要轉(zhuǎn)換
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
// 字典轉(zhuǎn)換成模型 userDict => User模型
// 轉(zhuǎn)換成哪個(gè)模型
// 獲取類(lèi)
Class modelClass = NSClassFromString(ivarType);
value = [modelClass modelWithDict:value];
}
// 給模型中屬性賦值
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
@end
// 其他地方調(diào)用
// 字典轉(zhuǎn)模型
NSDictionary * dict = @{ @"id" : @"1235",
@"name" : @"帥哥",
@"sex" : @"男",
@"age" : @"18",
@"height" : @"180cm",
@"weight" : @"70kg",
@"good" : @"eat",
@"money" : @"10000",
@"sport" : @"basketball"};
User * user = [User modelWithDict:dict];
NSLog(@"id =%@ name =%@ sex =%@ age =%@ height =%@ weight =%@",user.id,user.name,user.sex,user.age,user.height,user.weight);
打印如下
2018-06-28 17:00:07.724910+0800 Test_runTime[23654:10029472] id =1235 name =帥哥 sex =男 age =18 height =180cm weight =70kg
以上是本人的淺見(jiàn)峡继,如有錯(cuò)誤冯袍,望各位多多指正??