什么是runtime
runtime是底層的純C語言的API鞠苟,它包含的很多底層的語法乞榨。
我們平時編寫的OC代碼,在程序運(yùn)行過程中当娱,最終都是轉(zhuǎn)化成了runtime的C語言代碼吃既,
runtime也稱為運(yùn)行時,它是OC的幕后工作者跨细。
runtime的作用
runtime主要就是做一些底層的操作鹦倚,如:
1. 動態(tài)的添加對象的成員變量和方法
2.動態(tài)交換兩個方法的實(shí)現(xiàn)(可以替換系統(tǒng)的方法)
3.獲得某個類的所有成員方法、所有成員變量
4. 實(shí)現(xiàn)分類也可以添加屬性
5.實(shí)現(xiàn)NSCoding的自動歸檔和解檔
6.實(shí)現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換
替換系統(tǒng)方法冀惭,可以通過攔截系統(tǒng)的方法探究底層震叙,比如block 的實(shí)現(xiàn)原理
常用方法
1.獲取類中的方法
Method class_getClassMethod(Method cls , SEL name)
如:
Method m = class_getClassMethod([Person class],@selector(setName:));
2.獲取對象中的方法
Method class_getInstanceMethod(Method cls, SEL name)
如:
Person *person = [[Person alloc] init];
Method m = get_InstanceMethod([person class],@selector(setName:));
3.交換兩個方法的實(shí)現(xiàn)
void method_exchangeImplementations(Method m1,Method m2)
如
Person *p =[[Person alloc] init];
[p study];
[p run];
//交換實(shí)現(xiàn)
//instance method :實(shí)例方法掀鹅,
//class_getInstanceMethod得到實(shí)例的方法(即對象方法)
//兩個參數(shù) 1:類名 2.方法名
//class_getClassMethod :得到實(shí)例化的方法
Method m1 = class_getInstanceMethod([Person class], @selector(study));
Method m2 = class_getInstanceMethod([Person class], @selector(run));
method_exchangeImplementations(m2, m1);
[p study];
[p run];
具體操作
4.獲取成員變量
Ivar *ivars = class_getCopyIvarList(Ivar ivar);
實(shí)現(xiàn)分類中添加屬性
為所有的NSObject對象添加屬性
1.首先創(chuàng)建一個NSObject分類NSObject+Extension
2.在.h中使用@property添加屬性
此時使用@property添加數(shù)屬性,并非真正的屬性媒楼,如果此時調(diào)用查看屬性乐尊,將會崩潰,
因?yàn)榉诸惒⑽磳?shí)現(xiàn)添加添加屬性的功能划址,想要添加屬性扔嵌,需要使用runtime,動態(tài)的添加
3.在.m文件中實(shí)現(xiàn)getter和setter方法
如果想要添加多個屬性夺颤,就需要在每個對象中抽出一塊空間用于存放屬性痢缎,
使用objc_setAssociatedObject方法進(jìn)行關(guān)聯(lián)
#import "NSObject+Extension.h"
#import <objc/runtime.h>
@implementation NSObject (Extension)
//用于存放屬性的變量,多個屬性世澜,需要創(chuàng)建不同的變量
char BookKey;
-(void)setBooks:(NSArray *)books{
objc_setAssociatedObject(self, &BookKey, books, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSArray *)books{
return objc_getAssociatedObject(self, &BookKey);
}
@end
遵守協(xié)議NSCoding独旷,實(shí)現(xiàn)屬性的自動歸檔與解檔
需求分析:
當(dāng)想要對象自動進(jìn)行歸檔解檔的時候,如果屬性非常的多寥裂,一個一個天添加[encoder encodeObject:@(xxx) forKey:@"_xxx"];將會非常的繁瑣势告。
既然能夠獲取所有的屬性,我們就可以通過循環(huán)遍歷屬性的方式進(jìn)行統(tǒng)一的歸檔和解檔
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
// 用來存儲成員變量的數(shù)量
unsigned int outCount = 0;
// 獲得Dog類的所有成員變量
Ivar *ivars = class_copyIvarList([self class], &outCount);
// 遍歷所有的成員變量
for (int i = 0; i<outCount; i++) {
// 取出i位置對應(yīng)的成員變量
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 獲得key對應(yīng)的值
id value = [decoder decodeObjectForKey:key];
// 設(shè)置到成員變量上
[self setValue:value forKeyPath:key];
}
free(ivars);
}
return self;
}
/**
* 將對象寫入文件時會調(diào)用這個方法(開發(fā)者需要在這個方法中說明需要存儲哪些屬性)
*/
- (void)encodeWithCoder:(NSCoder *)encoder
{
// 用來存儲成員變量的數(shù)量
unsigned int outCount = 0;
// 獲得Dog類的所有成員變量
Ivar *ivars = class_copyIvarList([self class], &outCount);
// 遍歷所有的成員變量
for (int i = 0; i<outCount; i++) {
// 取出i位置對應(yīng)的成員變量
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 通過key獲得對應(yīng)成員變量的值
id value = [self valueForKeyPath:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
注意:
ARC的內(nèi)存管理機(jī)制 只適合OC語法抚恒,對于C語言的內(nèi)存還是需要手動的釋放,當(dāng)使用runtime的時候络拌,
如果包含了copy俭驮、create、retain春贸、new等詞語混萝,那么在最后就需要釋放內(nèi)存
使用free(對象)進(jìn)行釋放如:free(ivars);
利用runtime實(shí)現(xiàn)字典轉(zhuǎn)模型
描述:
KVC的字典轉(zhuǎn)模型具有一個缺陷,就是屬性的數(shù)量與名稱都必須保持一致萍恕,如果字典中的屬性多逸嘀,
而模型中沒有使用KVC賦值的時候就會崩潰,需要實(shí)現(xiàn)另一個方法
setValue:forUndefinedKey:方法允粤,并如果對象中包含了另一個對象作為屬性崭倘,
也將不能自動將其轉(zhuǎn)化為模型
而使用runtime實(shí)現(xiàn)的字典轉(zhuǎn)模型,可以實(shí)現(xiàn)將所有的對象都轉(zhuǎn)化為對應(yīng)的模型类垫,
并且不會出現(xiàn)屬性找不到司光,而奔潰的現(xiàn)象
NSObject+Extension.h
#import <Foundation/Foundation.h>
@interface NSObject (Extension)
-(void)setDiction:(NSDictionary *)dict;
+(instancetype)objectWithDiction:(NSDictionary *)dict;
@end
NSObject+Extension.m
#import "NSObject+Extension.h"
#import <objc/runtime.h>
@implementation NSObject (Extension)
-(void)setDiction:(NSDictionary *)dict{
//獲取類
Class c = self.class;
//循環(huán)遍歷 類 (本類 和所有的父類)
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)];
//去掉key中的 _
key = [key substringFromIndex:1];
//通過key 獲取屬性的值
id value = dict[key];
//如果key是一個空值 退出本輪的循環(huán)
//原因:如果字典中沒有這個key,那么value將會是一個空值悉患,kvc 不能賦值空值
if (value == nil) {
continue;
}
//如果類中包含另一個類為對象残家,也要將該對象進(jìn)行字典轉(zhuǎn)模型
//獲取對象的屬性的類名
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 對象名會以@“名字”的形式 出現(xiàn),但是同時字符串也是以這種形式表示售躁,因此可以先判斷type中是否包含 @ 符號
NSRange range = [type rangeOfString:@"@"];
//如果range.location 不等于NSNotFound說明 找到了@
if (range.location != NSNotFound) {
//截取type中的名字 去除@“ ”
type = [type substringWithRange:NSMakeRange(2, type.length -3)];
if (![type hasPrefix:@"NS"]) {
//將type轉(zhuǎn)化為類名
Class class = NSClassFromString(type);
value = [class objectWithDiction:value];
}
}
//賦值
[self setValue:value forKey:key];
}
//ARC 只適用于OC語法坞淮,C語言中的內(nèi)存 需要手動釋放
free(ivars);
c = [c superclass];
NSLog(@"1");
}
}
+(instancetype)objectWithDiction:(NSDictionary *)dict{
NSObject *obj = [[self alloc] init];
[obj setDiction:dict];
return obj;
}
@end