一赁炎、Objective-C Runtime到底是什么東西撞牢?
簡而言之慨默,Objective-C Runtime是一個將C語言轉(zhuǎn)化為面向?qū)ο笳Z言的擴展。
我們將C++和Objective進行對比弄跌,雖然C++和Objective-C都是在C的基礎(chǔ)上加入面向?qū)ο蟮奶匦詳U充而成的程序設(shè)計語言署浩,但二者實現(xiàn)的機制差異很大扮休。C++是基于靜態(tài)類型糯景,而Objective-C是基于動態(tài)運行時類型。也就是說用C++編寫的程序編譯時就直接編譯成了可令機器讀懂的機器語言殉农;用Objective-C編寫的程序不能直接編譯成可令機器讀懂的機器語言刀脏,而是在程序運行的時候,通過Runtime把程序轉(zhuǎn)為可令機器讀懂的機器語言超凳。也就是說用C++編寫的程序通過編譯器直接把函數(shù)地址硬編碼進入可執(zhí)行文件愈污;而Objective-C無法通過編譯器直接把函數(shù)地址硬編碼進入可執(zhí)行文件,而是在程序運行的時候轮傍,利用Runtime根據(jù)條件判斷作出決定暂雹。函數(shù)標識與函數(shù)過程的真正內(nèi)容之間的關(guān)聯(lián)可以動態(tài)修改。Runtime是Objective不可缺少的重要一部分创夜。
二 杭跪、Objective-C的元素認知
2.1 id和Class
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
Class是一個指向objc_class結(jié)構(gòu)體的指針,而id是一個指向objc_object結(jié)構(gòu)體的指針驰吓,其中的isa是一個指向objc_class結(jié)構(gòu)體的指針涧尿。其中的id就是我們所說的對象,Class就是我們所說的類檬贰。
objc_class的定義如下:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息姑廉,默認為0,可以通過runtime函數(shù)class_setVersion或者class_getVersion進行修改翁涤、讀取
long info OBJC2_UNAVAILABLE; // 類信息桥言,供運行時期使用的一些位標識萌踱,如CLS_CLASS (0x1L) 表示該類為普通 class,其中包含實例方法和變量;CLS_META (0x2L) 表示該類為 metaclass号阿,其中包含類方法;
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大胁⑼摇(包括從父類繼承下來的實例變量)
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量地址列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法地址列表,與 info 的一些標志位有關(guān)扔涧,如CLS_CLASS (0x1L)园担,則存儲實例方法,如CLS_META (0x2L)扰柠,則存儲類方法;
struct objc_cache *cache OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址粉铐,用于提升效率疼约;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 存儲該類聲明遵守的協(xié)議的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */
由以上代碼可見卤档,類與對象的區(qū)別就是類比對象多了很多特征成員,?類也可以當做一個objc_object來對待程剥,也就是說類和對象都是對象劝枣,分別稱作類對象(class object)和實例對象(instance object),這樣我們就可以區(qū)別對象和類了织鲸。
isa:objc_object(實例對象)中isa指針指向的類結(jié)構(gòu)稱為class(也就是該對象所屬的類)其中存放著普通成員變量與動態(tài)方法(“-”開頭的方法)舔腾;此處isa指針指向的類結(jié)構(gòu)稱為metaclass,其中存放著static類型的成員變量與static類型的方法(“+”開頭的方法)搂擦。
super_class: 指向該類的父類的指針稳诚,如果該類是根類(如NSObject或NSProxy),那么super_class就為nil瀑踢。
類與對象的繼承層次關(guān)系如圖(圖片源自網(wǎng)絡(luò)):
objective-runtime-1
所有的metaclass中isa指針都是指向根metaclass扳还,而根metaclass則指向自身。根metaclass是通過繼承根類產(chǎn)生的橱夭,與根class結(jié)構(gòu)體成員一致氨距,不同的是根metaclass的isa指針指向自身。
2.2 SEL
SEL是selector在Objective-C中的表示類型棘劣。selector可以理解為區(qū)別方法的ID俏让。
typedef struct objc_selector *SEL;
objc_selector的定義如下:
struct objc_selector {
char *name; OBJC2_UNAVAILABLE;// 名稱
char *types; OBJC2_UNAVAILABLE;// 類型
};
name和types都是char類型。
2.3 IMP
終于到IMP了茬暇,它在objc.h中得定義如下:
typedef id (*IMP)(id, SEL, ...);
IMP是“implementation”的縮寫首昔,它是由編譯器生成的一個函數(shù)指針。當你發(fā)起一個消息后(下文介紹)糙俗,這個函數(shù)指針決定了最終執(zhí)行哪段代碼勒奇。
2.4 Method
Method代表類中的某個方法的類型。
typedef struct objc_method *Method;
objc_method的定義如下:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE; // 方法類型
IMP method_imp OBJC2_UNAVAILABLE; // 方法實現(xiàn)
}
方法名method_name類型為SEL臼节,上文提到過撬陵。
方法類型method_types是一個char指針珊皿,存儲著方法的參數(shù)類型和返回值類型。
方法實現(xiàn)method_imp的類型為IMP巨税,上文提到過蟋定。
2.5 Ivar
Ivar代表類中實例變量的類型
typedef struct objc_ivar *Ivar;
objc_ivar的定義如下:
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 變量名
char *ivar_type OBJC2_UNAVAILABLE; // 變量類型
int ivar_offset OBJC2_UNAVAILABLE; // ?基地址偏移字節(jié)
#ifdef __LP64__
int space OBJC2_UNAVAILABLE; // 占用空間
#endif
}
2.6 objc_property_t
objc_property_t是屬性,它的定義如下:
typedef struct objc_property *objc_property_t;
objc_property是內(nèi)置的類型草添,與之關(guān)聯(lián)的還有一個objc_property_attribute_t驶兜,它是屬性的attribute,也就是其實是對屬性的詳細描述远寸,包括屬性名稱抄淑、屬性編碼類型、原子類型/非原子類型等驰后。它的定義如下:
typedef struct {
const char *name; // 名稱
const char *value; // 值(通常是空的)
} objc_property_attribute_t;
2.7 Cache
Catch的定義如下:
typedef struct objc_cache *Cache
objc_cache的定義如下:
struct objc_cache {
unsigned int mask OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
mask: 指定分配cache buckets的總數(shù)肆资。在方法查找中,Runtime使用這個字段確定數(shù)組的索引位置灶芝。
occupied: 實際占用cache buckets的總數(shù)郑原。
buckets: 指定Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個數(shù)組可能包含不超過mask+1個元素夜涕。需要注意的是犯犁,指針可能是NULL,表示這個緩存bucket沒有被占用女器,另外被占用的bucket可能是不連續(xù)的酸役。這個數(shù)組可能會隨著時間而增長。
objc_msgSend(下文講解)每調(diào)用一次方法后驾胆,就會把該方法緩存到cache列表中涣澡,下次的時候,就直接優(yōu)先從cache列表中尋找俏拱,如果cache沒有暑塑,才從methodLists中查找方法。
2.8 Catagory
這個就是我們平時所說的類別了锅必,很熟悉吧事格。它可以動態(tài)的為已存在的類添加新的方法。
它的定義如下:
typedef struct objc_category *Category;
objc_category的定義如下:
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 類別名稱
char *class_name OBJC2_UNAVAILABLE; // 類名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 實例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 類方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議列表
}
因為是入門搞隐,以上就列舉這些吧驹愚!
三、Objective-C的消息傳遞
3.1 基本消息傳遞
在面向?qū)ο缶幊讨辛痈伲瑢ο笳{(diào)用方法叫做發(fā)送消息逢捺。在編譯時,程序的源代碼就會從對象發(fā)送消息轉(zhuǎn)換成Runtime的objc_msgSend函數(shù)調(diào)用癞季。
例如某實例變量receiver實現(xiàn)某一個方法oneMethod
[receiver oneMethod];
Runtime會將其轉(zhuǎn)成類似這樣的代碼
objc_msgSend(receiver, selector);
具體會轉(zhuǎn)換成什么代碼呢劫瞳?
Runtime會根據(jù)類型自動轉(zhuǎn)換成下列某一個函數(shù):
objc_msgSend:普通的消息都會通過該函數(shù)發(fā)送
objc_msgSend_stret:消息中有數(shù)據(jù)結(jié)構(gòu)作為返回值(不是簡單值)時倘潜,通過此函數(shù)發(fā)送和接收返回值
objc_msgSendSuper:和objc_msgSend類似,這里把消息發(fā)送給父類的實例
objc_msgSendSuper_stret:和objc_msgSend_stret類似志于,這里把消息發(fā)送給父類的實例并接收返回值
當消息被發(fā)送到實例對象時涮因,是如圖所示處理的(圖片源自網(wǎng)絡(luò)):
objective-runtime-2
objc_msgSend函數(shù)的調(diào)用過程:
第一步:檢測這個selector是不是要忽略的。
第二步:檢測這個target是不是nil對象伺绽。nil對象發(fā)送任何一個消息都會被忽略掉养泡。
第三步:
1.調(diào)用實例方法時,它會首先在自身isa指針指向的類(class)methodLists中查找該方法奈应,如果找不到則會通過class的super_class指針找到父類的類對象結(jié)構(gòu)體澜掩,然后從methodLists中查找該方法,如果仍然找不到杖挣,則繼續(xù)通過super_class向上一級父類結(jié)構(gòu)體中查找肩榕,直至根class;
2.當我們調(diào)用某個某個類方法時程梦,它會首先通過自己的isa指針找到metaclass点把,并從其中methodLists中查找該類方法橘荠,如果找不到則會通過metaclass的super_class指針找到父類的metaclass對象結(jié)構(gòu)體屿附,然后從methodLists中查找該方法,如果仍然找不到哥童,則繼續(xù)通過super_class向上一級父類結(jié)構(gòu)體中查找挺份,直至根metaclass;
第四部:前三部都找不到就會進入動態(tài)方法解析(看下文)贮懈。
3.2 消息動態(tài)解析
動態(tài)解析流程圖(圖片來自網(wǎng)絡(luò)):
objective-runtime-6
第一步:通過resolveInstanceMethod:方法決定是否動態(tài)添加方法匀泊。如果返回Yes則通過class_addMethod動態(tài)添加方法,消息得到處理朵你,結(jié)束各聘;如果返回No,則進入下一步抡医;
第二步:這步會進入forwardingTargetForSelector:方法躲因,用于指定備選對象響應(yīng)這個selector,不能指定為self忌傻。如果返回某個對象則會調(diào)用對象的方法大脉,結(jié)束。如果返回nil水孩,則進入第三部镰矿;
第三部:這步我們要通過methodSignatureForSelector:方法簽名,如果返回nil俘种,則消息無法處理秤标。如果返回methodSignature绝淡,則進入下一步;
第四部:這步調(diào)用forwardInvocation:方法苍姜,我們可以通過anInvocation對象做很多處理够委,比如修改實現(xiàn)方法,修改響應(yīng)對象等怖现,如果方法調(diào)用成功茁帽,則結(jié)束。如果失敗屈嗤,則進入doesNotRecognizeSelector方法潘拨,若我們沒有實現(xiàn)這個方法,那么就會crash饶号。
到這里大家可能暈乎乎的铁追,下面看實戰(zhàn)篇吧!蒼老師必須讓你懂茫船!
四琅束、?Runtime實戰(zhàn)
4.1 蒼老師問好篇
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
// 自定義一個方法
void sayFunction(id self, SEL _cmd, id some) {
NSLog(@"%@歲的%@說:%@", object_getIvar(self, class_getInstanceVariable([self class], "_age")),[self valueForKey:@"name"],some);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 動態(tài)創(chuàng)建對象 創(chuàng)建一個Person 繼承自 NSObject類
Class People = objc_allocateClassPair([NSObject class], "Person", 0);
// 為該類添加NSString *_name成員變量
class_addIvar(People, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
// 為該類添加int _age成員變量
class_addIvar(People, "_age", sizeof(int), sizeof(int), @encode(int));
// 注冊方法名為say的方法
SEL s = sel_registerName("say:");
// 為該類增加名為say的方法
class_addMethod(People, s, (IMP)sayFunction, "v@:@");
// 注冊該類
objc_registerClassPair(People);
// 創(chuàng)建一個類的實例
id peopleInstance = [[People alloc] init];
// KVC 動態(tài)改變 對象peopleInstance 中的實例變量
[peopleInstance setValue:@"蒼老師" forKey:@"name"];
// 從類中獲取成員變量Ivar
Ivar ageIvar = class_getInstanceVariable(People, "_age");
// 為peopleInstance的成員變量賦值
object_setIvar(peopleInstance, ageIvar, @18);
// 調(diào)用 peopleInstance 對象中的 s 方法選擇器對于的方法
// objc_msgSend(peopleInstance, s, @"大家好!"); // 這樣寫也可以,請看我博客說明
((void (*)(id, SEL, id))objc_msgSend)(peopleInstance, s, @"大家好");
peopleInstance = nil; //當People類或者它的子類的實例還存在算谈,則不能調(diào)用objc_disposeClassPair這個方法涩禀;因此這里要先銷毀實例對象后才能銷毀類;
// 銷毀類
objc_disposeClassPair(People);
}
return 0;
}
最后的結(jié)果是:18歲的蒼老師說:大家好然眼!
在使用
objc_msgSend(peopleInstance, s, @"大家好!");
默認會出現(xiàn)以下錯誤:
objc_msgSend()報錯Too many arguments to function call ,expected 0,have3
直接通過objc_msgSend(self, setter, value)是報錯艾船,說參數(shù)過多。
請這樣解決:
Build Setting–> Apple LLVM 7.0 – Preprocessing–> Enable Strict Checking of objc_msgSend Calls 改為 NO
當然你也可以這樣寫(推薦):
((void (*)(id, SEL, id))objc_msgSend)(peopleInstance, s, @"大家好");
強制轉(zhuǎn)換objc_msgSend函數(shù)類型為帶三個參數(shù)且返回值為void函數(shù)高每,然后才能傳三個參數(shù)屿岂。
此實戰(zhàn)內(nèi)容是,動態(tài)創(chuàng)建一個類鲸匿,并創(chuàng)建成員變量和方法爷怀,最后賦值成員變量并發(fā)送消息。其中成員變量的賦值使用了KVC和object_setIvar函數(shù)兩種方式带欢,這些東西大家舉一反三就可以了运授。
4.2 蒼老師的特征篇
People.h文件
@interface People : NSObject
{
NSString *_occupation;
NSString *_nationality;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
- (NSDictionary *)allProperties;
- (NSDictionary *)allIvars;
- (NSDictionary *)allMethods;
@end
People.m文件
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People
- (NSDictionary *)allProperties
{
unsigned int count = 0;
// 獲取類的所有屬性,如果沒有屬性count就為0
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableDictionary *resultDict = [@{} mutableCopy];
for (NSUInteger i = 0; i < count; i ++) {
// 獲取屬性的名稱和值
const char *propertyName = property_getName(properties[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
id propertyValue = [self valueForKey:name];
if (propertyValue) {
resultDict[name] = propertyValue;
} else {
resultDict[name] = @"字典的key對應(yīng)的value不能為nil哦洪囤!";
}
}
// 這里properties是一個數(shù)組指針徒坡,我們需要使用free函數(shù)來釋放內(nèi)存。
free(properties);
return resultDict;
}
- (NSDictionary *)allIvars
{
unsigned int count = 0;
NSMutableDictionary *resultDict = [@{} mutableCopy];
Ivar *ivars = class_copyIvarList([self class], &count);
for (NSUInteger i = 0; i < count; i ++) {
const char *varName = ivar_getName(ivars[i]);
NSString *name = [NSString stringWithUTF8String:varName];
id varValue = [self valueForKey:name];
if (varValue) {
resultDict[name] = varValue;
} else {
resultDict[name] = @"字典的key對應(yīng)的value不能為nil哦瘤缩!";
}
}
free(ivars);
return resultDict;
}
- (NSDictionary *)allMethods
{
unsigned int count = 0;
NSMutableDictionary *resultDict = [@{} mutableCopy];
// 獲取類的所有方法喇完,如果沒有方法count就為0
Method *methods = class_copyMethodList([self class], &count);
for (NSUInteger i = 0; i < count; i ++) {
// 獲取方法名稱
SEL methodSEL = method_getName(methods[i]);
const char *methodName = sel_getName(methodSEL);
NSString *name = [NSString stringWithUTF8String:methodName];
// 獲取方法的參數(shù)列表
int arguments = method_getNumberOfArguments(methods[i]);
resultDict[name] = @(arguments-2);
}
free(methods);
return resultDict;
}
@end
在main.m中運行以下代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *cangTeacher = [[People alloc] init];
cangTeacher.name = @"蒼井空";
cangTeacher.age = 18;
[cangTeacher setValue:@"老師" forKey:@"occupation"];
NSDictionary *propertyResultDic = [cangTeacher allProperties];
for (NSString *propertyName in propertyResultDic.allKeys) {
NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyResultDic[propertyName]);
}
NSDictionary *ivarResultDic = [cangTeacher allIvars];
for (NSString *ivarName in ivarResultDic.allKeys) {
NSLog(@"ivarName:%@, ivarValue:%@",ivarName, ivarResultDic[ivarName]);
}
NSDictionary *methodResultDic = [cangTeacher allMethods];
for (NSString *methodName in methodResultDic.allKeys) {
NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodResultDic[methodName]);
}
}
return 0;
}
最后的輸出結(jié)果如下:
objective-runtime-3
是不是有點失望,我沒有加一些特殊的技能剥啤,留給下文了锦溪。此實戰(zhàn)內(nèi)容是通過蒼老師的一些特征學(xué)習(xí):如何獲取對象所有的屬性名稱和屬性值不脯、獲取對象所有成員變量名稱和變量值、獲取對象所有的方法名和方法參數(shù)數(shù)量刻诊。
4.3 蒼老師增加新技能篇
要通過Category和Associated Objects增加技能了防楷,快看!
創(chuàng)建People+Associated.h文件如下:
#import "People.h"
typedef void (^CodingCallBack)();
@interface People (Associated)
@property (nonatomic, strong) NSNumber *associatedBust; // 胸圍
@property (nonatomic, copy) CodingCallBack associatedCallBack; // 寫代碼
@end
People+Associated.m如下:
#import "People+Associated.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People (Associated)
- (void)setAssociatedBust:(NSNumber *)bust
{
// 設(shè)置關(guān)聯(lián)對象
objc_setAssociatedObject(self, @selector(associatedBust), bust, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSNumber *)associatedBust
{
// 得到關(guān)聯(lián)對象
return objc_getAssociatedObject(self, @selector(associatedBust));
}
- (void)setAssociatedCallBack:(CodingCallBack)callback {
objc_setAssociatedObject(self, @selector(associatedCallBack), callback, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CodingCallBack)associatedCallBack {
return objc_getAssociatedObject(self, @selector(associatedCallBack));
}
@end
在main.m中運行以下代碼
#import "People.h"
#import "People+Associated.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *cangTeacher = [[People alloc] init];
cangTeacher.name = @"蒼井空";
cangTeacher.age = 18;
[cangTeacher setValue:@"老師" forKey:@"occupation"];
cangTeacher.associatedBust = @(90);
cangTeacher.associatedCallBack = ^(){
NSLog(@"蒼老師要寫代碼了则涯!");
};
cangTeacher.associatedCallBack();
NSDictionary *propertyResultDic = [cangTeacher allProperties];
for (NSString *propertyName in propertyResultDic.allKeys) {
NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyResultDic[propertyName]);
}
NSDictionary *methodResultDic = [cangTeacher allMethods];
for (NSString *methodName in methodResultDic.allKeys) {
NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodResultDic[methodName]);
}
}
return 0;
}
這次運行結(jié)果多出了一個associatedBust(胸圍)和一個associatedCallBack(寫代碼)屬性复局。
如圖:
objective-runtime-4
我們成功的給蒼老師添加個一個胸圍的屬性和一個寫代碼的回調(diào),但是添加屬性沒有什么意義粟判,我們平時在開發(fā)過成功中用的比較多的就是添加回調(diào)了亿昏。
4.4 蒼老師的資料歸檔篇
蒼老師的資料總要整理一下吧!
創(chuàng)建People.h
#import <Foundation/Foundation.h>
@interface People : NSObject <NSCoding>
@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, strong) NSNumber *age; // 年齡
@property (nonatomic, copy) NSString *occupation; // 職業(yè)
@property (nonatomic, copy) NSString *nationality; // 國籍
@end
People.m
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([People class], &count);
for (NSUInteger i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([People class], &count);
for (NSUInteger i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end
4.5 蒼老師的資料轉(zhuǎn)換篇
服務(wù)器返回了大量蒼老師的數(shù)據(jù)档礁,手機端這邊接收后如何去轉(zhuǎn)換呢角钩?當然是要將JSON轉(zhuǎn)換為Model啦!
相信平時你們的項目中也用到過這些三方庫呻澜,下面我們?nèi)チ私庀聄untime實現(xiàn)JSON和Model互轉(zhuǎn)递礼。
創(chuàng)建People.h
#import <Foundation/Foundation.h>
@interface People : NSObject
@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, strong) NSNumber *age; // 年齡
@property (nonatomic, copy) NSString *occupation; // 職業(yè)
@property (nonatomic, copy) NSString *nationality; // 國籍
// 生成model
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
// 轉(zhuǎn)換成字典
- (NSDictionary *)covertToDictionary;
@end
People.m的代碼如下:
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
for (NSString *key in dictionary.allKeys) {
id value = dictionary[key];
SEL setter = [self propertySetterByKey:key];
if (setter) {
// 這里還可以使用NSInvocation或者method_invoke,不再繼續(xù)深究了羹幸,有興趣google脊髓。
((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);
}
}
}
return self;
}
- (NSDictionary *)covertToDictionary
{
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
if (count != 0) {
NSMutableDictionary *resultDict = [@{} mutableCopy];
for (NSUInteger i = 0; i < count; i ++) {
const void *propertyName = property_getName(properties[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
SEL getter = [self propertyGetterByKey:name];
if (getter) {
id value = ((id (*)(id, SEL))objc_msgSend)(self, getter);
if (value) {
resultDict[name] = value;
} else {
resultDict[name] = @"字典的key對應(yīng)的value不能為nil哦!";
}
}
}
free(properties);
return resultDict;
}
free(properties);
return nil;
}
#pragma mark - private methods
// 生成setter方法
- (SEL)propertySetterByKey:(NSString *)key
{
// 首字母大寫睹欲,你懂得
NSString *propertySetterName = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
SEL setter = NSSelectorFromString(propertySetterName);
if ([self respondsToSelector:setter]) {
return setter;
}
return nil;
}
// 生成getter方法
- (SEL)propertyGetterByKey:(NSString *)key
{
SEL getter = NSSelectorFromString(key);
if ([self respondsToSelector:getter]) {
return getter;
}
return nil;
}
@end
main.m中運行以下代碼:
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSDictionary *dict = @{
@"name" : @"蒼井空",
@"age" : @18,
@"occupation" : @"老師",
@"nationality" : @"日本"
};
// 字典轉(zhuǎn)模型
People *cangTeacher = [[People alloc] initWithDictionary:dict];
NSLog(@"熱烈歡迎供炼,從%@遠道而來的%@歲的%@%@",cangTeacher.nationality,cangTeacher.age,cangTeacher.name,cangTeacher.occupation);
// 模型轉(zhuǎn)字典
NSDictionary *covertedDict = [cangTeacher covertToDictionary];
NSLog(@"%@",covertedDict);
}
return 0;
}
objective-runtime-5
相信通過前面的學(xué)習(xí),這些代碼不用寫過多的注釋你也可以看懂了窘疮,我把假設(shè)是網(wǎng)絡(luò)返回的蒼老師的資料轉(zhuǎn)化為了model,然后又將model轉(zhuǎn)回字典冀墨。這是一個JSON轉(zhuǎn)Model相互轉(zhuǎn)換的一個思路闸衫,大家稍后運行Demo細細品味。
4.6 蒼老師的唱歌篇
這個實例主要是驗證一下上文《5.2 消息動態(tài)解析》
第一首:
添加sing實例方法诽嘉,但是不提供方法的實現(xiàn)蔚出。驗證當找不到方法的實現(xiàn)時,動態(tài)添加方法虫腋。
創(chuàng)建People.h
#import <Foundation/Foundation.h>
@interface People : NSObject
@property (nonatomic, copy) NSString *name;
- (void)sing;
@end
創(chuàng)建People.m
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 我們沒有給People類聲明sing方法骄酗,我們這里動態(tài)添加方法
if ([NSStringFromSelector(sel) isEqualToString:@"sing"]) {
class_addMethod(self, sel, (IMP)otherSing, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void otherSing(id self, SEL cmd)
{
NSLog(@"%@ 唱歌啦!",((People *)self).name);
}
在main.m中運行以下代碼:
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *cangTeacher = [[People alloc] init];
cangTeacher.name = @"蒼老師";
[cangTeacher sing];
}
return 0;
}
objective-runtime-7
我們沒有提供蒼老師唱歌的方法實現(xiàn)悦冀,因此在調(diào)用此方法的時候趋翻,會調(diào)用resolveInstanceMethod方法,我們動態(tài)添加了方法盒蟆。我們也可以返回No踏烙,繼續(xù)向下傳遞师骗。(此處請返回《5.2 消息動態(tài)解析》第一步品味下)
第二首
外面的小鳥在唱歌,但是蒼老師的歌聲蓋過了小鳥讨惩,我們只能聽到蒼老師唱歌了辟癌。
這里我們不聲明sing方法,將調(diào)用途中動態(tài)更換調(diào)用對象荐捻。
在第一首代碼的基礎(chǔ)上黍少,創(chuàng)建Bird的model
Bird.h
#import <Foundation/Foundation.h>
@interface Bird : NSObject
@property (nonatomic, copy) NSString *name;
@end
Bird.m
#import "Bird.h"
#import "People.h"
@implementation Bird
// 第一步:我們不動態(tài)添加方法,返回NO处面,進入第二步仍侥;
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
return NO;
}
// 第二部:我們不指定備選對象響應(yīng)aSelector,進入第三步鸳君;
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return nil;
}
// 第三步:返回方法選擇器农渊,然后進入第四部;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
// 第四部:這步我們修改調(diào)用對象
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 我們改變調(diào)用對象為People
People *cangTeacher = [[People alloc] init];
cangTeacher.name = @"蒼老師";
[anInvocation invokeWithTarget:cangTeacher];
}
@end
main.m運行代碼如下:
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
#import "Bird.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Bird *bird = [[Bird alloc] init];
bird.name = @"小小鳥";
((void (*)(id, SEL))objc_msgSend)((id)bird, @selector(sing));
}
return 0;
}
objective-runtime-8
成功更換了對象或颊,把對象更換為蒼老師了砸紊。(此處請返回《5.2 消息動態(tài)解析》品味)
第三首
蒼老師不想唱歌想跳舞了。
這里我是實現(xiàn)不提供聲明囱挑,不修改調(diào)用對象醉顽,但是將sing方法修改為dance方法。
創(chuàng)建People.h
#import <Foundation/Foundation.h>
@interface People : NSObject
@end
People.m
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People
// 第一步:我們不動態(tài)添加方法平挑,返回NO游添,進入第二步;
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
return NO;
}
// 第二部:我們不指定備選對象響應(yīng)aSelector通熄,進入第三步唆涝;
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return nil;
}
// 第三步:返回方法選擇器,然后進入第四部唇辨;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
// 第四部:這步我們修改調(diào)用方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setSelector:@selector(dance)];
// 這還要指定是哪個對象的方法
[anInvocation invokeWithTarget:self];
}
// 若forwardInvocation沒有實現(xiàn)廊酣,則會調(diào)用此方法
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"消息無法處理:%@", NSStringFromSelector(aSelector));
}
- (void)dance
{
NSLog(@"跳舞!I兔丁亡驰!come on!");
}
@end
在main.m中運行如下代碼:
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *cangTeacher = [[People alloc] init];
((void(*)(id, SEL)) objc_msgSend)((id)cangTeacher, @selector(sing));
}
return 0;
}
objective-runtime-9
成功更換了方法饿幅,蒼老師由唱歌改為跳舞了(此處請返回《5.2 消息動態(tài)解析》品味)
消息轉(zhuǎn)發(fā)與method swizzleDemo
轉(zhuǎn)載自:Objective-C Runtime 1小時入門教程