什么是runtime
runtime是底層的純C語言的API,它包含的很多底層的語法。
我們平時編寫的OC代碼,在程序運行過程中,最終都是轉(zhuǎn)化成了runtime的C語言代碼,
runtime也稱為運行時,它是OC的幕后工作者。
runtime的作用
runtime主要就是做一些底層的操作,如:
1. 動態(tài)的添加對象的成員變量和方法
2.動態(tài)交換兩個方法的實現(xiàn)(可以替換系統(tǒng)的方法)
3.獲得某個類的所有成員方法、所有成員變量
4. 實現(xiàn)分類也可以添加屬性
5.實現(xiàn)NSCoding的自動歸檔和解檔
6.實現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換
替換系統(tǒng)方法,可以通過攔截系統(tǒng)的方法探究底層,比如block 的實現(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.交換兩個方法的實現(xiàn)
void method_exchangeImplementations(Method m1,Method m2)
如
Person *p =[[Person alloc] init];
[p study];
[p run];
//交換實現(xiàn)
//instance method :實例方法,
//class_getInstanceMethod得到實例的方法(即對象方法)
//兩個參數(shù) 1:類名 2.方法名
//class_getClassMethod :得到實例化的方法
Method m1 = class_getInstanceMethod([Person class], @selector(study));
Method m2 = class_getInstanceMethod([Person class], @selector(run));
method_exchangeImplementations(m2, m1);
[p study];
[p run];
具體操作

051B23EE-AFC6-4C95-9297-1E58708D5B96.png
4.獲取成員變量
Ivar *ivars = class_getCopyIvarList(Ivar ivar);
實現(xiàn)分類中添加屬性
為所有的NSObject對象添加屬性
1.首先創(chuàng)建一個NSObject分類NSObject+Extension
2.在.h中使用@property添加屬性
此時使用@property添加數(shù)屬性,并非真正的屬性,如果此時調(diào)用查看屬性,將會崩潰,
因為分類并未實現(xiàn)添加添加屬性的功能,想要添加屬性,需要使用runtime,動態(tài)的添加
3.在.m文件中實現(xiàn)getter和setter方法
如果想要添加多個屬性,就需要在每個對象中抽出一塊空間用于存放屬性,
使用objc_setAssociatedObject方法進行關(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,實現(xiàn)屬性的自動歸檔與解檔
需求分析:
當(dāng)想要對象自動進行歸檔解檔的時候,如果屬性非常的多,一個一個天添加[encoder encodeObject:@(xxx) forKey:@"_xxx"];將會非常的繁瑣。
既然能夠獲取所有的屬性,我們就可以通過循環(huá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)存管理機制 只適合OC語法,對于C語言的內(nèi)存還是需要手動的釋放,當(dāng)使用runtime的時候,
如果包含了copy、create、retain、new等詞語,那么在最后就需要釋放內(nèi)存
使用free(對象)進行釋放如:free(ivars);
利用runtime實現(xiàn)字典轉(zhuǎn)模型
描述:
KVC的字典轉(zhuǎn)模型具有一個缺陷,就是屬性的數(shù)量與名稱都必須保持一致,如果字典中的屬性多,
而模型中沒有使用KVC賦值的時候就會崩潰,需要實現(xiàn)另一個方法
setValue:forUndefinedKey:方法,并如果對象中包含了另一個對象作為屬性,
也將不能自動將其轉(zhuǎn)化為模型
而使用runtime實現(xiàn)的字典轉(zhuǎn)模型,可以實現(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;
}
//如果類中包含另一個類為對象,也要將該對象進行字典轉(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