runtime 的運(yùn)行時(shí)機(jī)制
runtime 又叫運(yùn)行時(shí)歧蒋,是一套底層的 C 語言 API醉锄,其為 iOS 內(nèi)部的核心之一,我們平時(shí)編寫的 OC 代碼龙亲,底層都是基于它來實(shí)現(xiàn)的虑椎。Objective-C語言是一門動態(tài)語言。
runtime 的作用
- 獲取某個(gè)類的所有成員變量俱笛、屬性捆姜、方法、協(xié)議
- 添加一個(gè)成員變量迎膜、屬性泥技、方法
- 過濾
- 重定向
- 交換方法
- 轉(zhuǎn)發(fā)
- 字典轉(zhuǎn)模型(重難點(diǎn))
demo演示
1.runtime獲取變量、屬性磕仅、方法珊豹、協(xié)議
- (void)testRunTimeGetValue
{
unsigned int count;
//獲取成員變量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Ivar myivar = ivarList[i];
const char *ivarname = ivar_getName(myivar);
const char *ivarType = ivar_getTypeEncoding(myivar); // 獲取變量編碼類型
NSString *ivarNameStr = [NSString stringWithUTF8String:ivarname];
NSLog(@"ivar----="">%@ %@", ivarNameStr, [NSString stringWithUTF8String:ivarType]);
if ([ivarNameStr isEqualToString:@"_property2"]) {
[self setValue:@"這是屬性值" forKey:@"property2"];
NSLog(@"self.property2:%@", self.property2);
}
}
//獲取屬性列表
objc_property_t * propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char * propertyname = property_getName(propertyList[i]);
NSLog(@"property----="">%@", [NSString stringWithUTF8String:propertyname]);
}
//獲取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method----=>%@", NSStringFromSelector(method_getName(method)));
}
//獲取協(xié)議列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Protocol *myprotocal = protocolList[i];
const char *protocolname = protocol_getName(myprotocal); NSLog(@"protocol----="">%@", [NSString stringWithUTF8String:protocolname]);
}
}
2.runtime添加屬性簸呈,還可以添加任何對象
- (void)testRunTimeAddValue
{
//添加屬性
objc_setAssociatedObject(self, &associatedObjectKey, @"addProperty", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//添加對象
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(10, 100, 300, 60)];
label.numberOfLines = 2;
label.textColor = [UIColor blueColor];
label.backgroundColor = [UIColor redColor];
objc_setAssociatedObject(self, &associatedObjectKey2,label, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self getValueOfRunTime];
}
- (void)getValueOfRunTime
{
//獲取關(guān)聯(lián)對象string與label
NSString *string = objc_getAssociatedObject(self, &associatedObjectKey);
UILabel *label = objc_getAssociatedObject(self, &associatedObjectKey2);
label.text = [NSString stringWithFormat:@"動態(tài)添加的屬性名稱為:%@\\\\n此label也是動態(tài)添加的對象", string];
[self.view addSubview:label];
}
3. runtime過濾
調(diào)用一個(gè)不存在的實(shí)例方法的時(shí)候,會調(diào)用resolveInstanceMethod:方法店茶;調(diào)用一個(gè)不存在的類方法的時(shí)候蜕便,會調(diào)用resolveClassMethod:方法;
如果resolveInstanceMethod:返回NO贩幻,則會再掉用forwardingTargetForSelector方法實(shí)現(xiàn)轉(zhuǎn)發(fā)轿腺。如果返回Yes,反之不掉用丛楚。從而實(shí)現(xiàn)過濾的作用族壳。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
#if 1
return NO;
#else
if (sel == @selector(testCategory)) {
class_addMethod([self class], sel, (IMP)defualtFunc, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
#endif
}
4. runtime重定向
將你調(diào)用的不存在的方法重定向到一個(gè)其他聲明了這個(gè)方法的類,只需要返回一個(gè)有這個(gè)方法的target趣些。
- (id)forwardingTargetForSelector:(SEL)sel
{
if(sel == @selector(testCategory)) {
return appDelegate;
}
return [super forwardingTargetForSelector:sel];
}
5. runtime交換方法
交換實(shí)例方法使用class_getInstanceMethod仿荆。交換類方法使用class_getClassMethod。
- (void)testRunTimeChangeFunction
{
// 獲取 testFunction1方法
Method testFunction1 = class_getInstanceMethod([self class], @selector(testFunction1));
// 獲取 testFunction2方法
Method testFunction2 = class_getInstanceMethod([self class], @selector(testFunction2));
// 交換方法地址, 相當(dāng)于交換實(shí)現(xiàn)
method_exchangeImplementations(testFunction1, testFunction2);
[self testFunction1];
//實(shí)際上會掉用testFunction2方法
}
- (void)testFunction1
{
NSLog(@"runtime交換方法-testFunction1");
}
- (void)testFunction2
{
NSLog(@"runtime交換方法-testFunction2");
}
6. runtime轉(zhuǎn)發(fā)
是將調(diào)用不存在的方法打包成了NSInvocation傳遞來坏平。做完你自己的處理后拢操,調(diào)用invokeWithTarget:方法讓某個(gè)target觸發(fā)這個(gè)方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([appDelegate respondsToSelector:
[anInvocation selector]]) {
[anInvocation invokeWithTarget:appDelegate];
} else {
[super forwardInvocation:anInvocation];
}
}
7. runtime字典轉(zhuǎn)模型(第三方MJExtension庫也是按照如下方法實(shí)現(xiàn)的字典轉(zhuǎn)模型)
- 用法:通過方法modelWithDictionary:傳入了一個(gè)字典,返回一個(gè)對應(yīng)的model舶替,用法簡單令境。
- (void)testRunTimeDictionaryToModel
{
//獲取字典數(shù)據(jù)
NSDictionary *dictionary = [self getMainBundleResource:@"jsonData.json"];
LXResultModel *model = [LXResultModel modelWithDictionary:dictionary];
NSLog(@"model.foods[0].food: %@", model.foods[0].food);
}
- 必要條件:根據(jù)字典結(jié)構(gòu)(在jsonData.json文件中)創(chuàng)建對應(yīng)的model層級結(jié)構(gòu)(在RuntimeDicToModel文件夾中),并將字典的key聲明為model的屬性名稱坎穿。字典結(jié)構(gòu)如下,因此對應(yīng)的model可以這樣取到值返劲,正如上面的NSLog輸出為:model.foods[0].food: rice玲昧。
{
"address": {
"city": "遵義市",
"likePlaces": {
"place": "HongKong"
},
"province": "貴州省"
},
"age": 25,
"foods" :[
{
"food": "rice",
"fruit": "apple",
},
{
"food": "noodle",
"fruit": "watermelon",
}
],
"name": "lixu"
}
- 核心代碼:使用到runtime的邏輯處理,可以將字典里的arr子元素?cái)?shù)據(jù)轉(zhuǎn)化為model篮绿,也可以將字典里包含的字典轉(zhuǎn)化為model(用遞歸算法)孵延。
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary
{
id objc = [[self alloc] init];
unsigned int count;
// 獲取類中的所有成員屬性
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 根據(jù)角標(biāo),從數(shù)組取出對應(yīng)的成員屬性
Ivar ivar = ivarList[i];
// 獲取成員屬性名
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 從第一個(gè)角標(biāo)開始截取亲配,因?yàn)閷傩宰兞康牡谝粋€(gè)字符為“_”,
if (![[name substringToIndex:1] isEqualToString:@"_"]) {
continue;
}
NSString *key = [name substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對應(yīng)的value
id value = dictionary[key];
// 二級轉(zhuǎn)換:如果字典中還有字典尘应,也需要把對應(yīng)的字典轉(zhuǎn)換成模型
// 判斷下value是否是字典
if ([value isKindOfClass:[NSDictionary class]]) {
// 獲取成員屬性類型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 根據(jù)字符串類名生成類對象
Class modelClass = NSClassFromString([self cutClassString:type]);
if (modelClass) { // 有對應(yīng)的模型才需要轉(zhuǎn)
// 把字典轉(zhuǎn)模型,采用遞歸
value = [modelClass modelWithDictionary:value];
}
}
// 三級轉(zhuǎn)換:NSArray中也是字典吼虎,把數(shù)組中的字典轉(zhuǎn)換成模型.
// 判斷值是否是數(shù)組
if ([value isKindOfClass:[NSArray class]]) {
// 判斷對應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// 獲取數(shù)組中字典對應(yīng)的模型
Class classModel = [self arrayContainModelClass][key];
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典數(shù)組犬钢,生成模型數(shù)組
for (NSDictionary *dict in value) {
// 字典轉(zhuǎn)模型
id model = [classModel modelWithDictionary:dict];
[arrM addObject:model];
}
// 把模型數(shù)組賦值給value
value = arrM;
}
}
if (value) { // 有值,才需要給模型的屬性賦值
// 利用KVC給模型中的屬性賦值
[objc setValue:value forKey:key];
}
}
return objc;
}
討論
為了瘦身ViewController或者AppDelegate代碼思灰,常常用到繼承玷犹、封裝、分類等方案洒疚,這里為了結(jié)合runtime只討論分類歹颓。
現(xiàn)在的需求是將AppDelegate里的代碼部分隔離到AppDelegate+RunTime中坯屿,其中需要引用AppDelegate的一個(gè)屬性testStr。
好巍扛,重點(diǎn)來了领跛,下面我采用了三種方案可以在AppDelegate+RunTime中引用屬性testStr。分別用testStr1撤奸、testStr2 吠昭、testStr3表示。
方案一:在本類AppDelegate的.h聲明屬性寂呛,即可在分類中導(dǎo)入本類頭文件怎诫,引用其屬性了
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, copy) NSString *testStr1;
- (void)testRedirect;
@end
將testStr2與testStr3都聲明在了分類中
@interface AppDelegate (RunTime)
@property (nonatomic, copy) NSString *testStr2;
@property (nonatomic, copy) NSString *testStr3;
- (void)testCategory;
@end
方案二:在AppDelegate+RunTime.m中重寫testStr2 的set和get方法,使用runtime實(shí)現(xiàn)給分類添加屬性贷痪。
-(NSString *)testStr2
{
return objc_getAssociatedObject(self, _cmd);//_cmd當(dāng)前方法的一個(gè)SEL指針幻妓,與@selector(str)對應(yīng),這樣可以避免定義一個(gè)靜態(tài)全局變量
}
-(void)setTestStr2:(NSString *)testStr2
{
objc_setAssociatedObject(self, @selector(testStr2), testStr2, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
方案三:在AppDelegate+RunTime.m中重寫testStr3 的set和get方法劫拢,使用全局靜態(tài)變量switchStr去引用testStr3的值實(shí)現(xiàn)給分類添加屬性肉津。
static NSString *switchStr;
-(NSString *)testStr3
{
return switchStr;
}
-(void)setTestStr3:(NSString *)testStr3
{
switchStr = [testStr3 copy];
}
通過調(diào)用testCategory方法可以看到三種方案設(shè)置的屬性都可以正常獲取值,那么平時(shí)你使用的是哪種方案呢舱沧?為什么呢妹沙?
源碼請點(diǎn)擊github地址下載。
QQ:2239344645 我的github