1.什么是Runtime?
我所理解的runtime是一個(gè)使用C編寫(xiě)的庫(kù),為C添加了面向?qū)ο蟮奶匦?它是一個(gè)庫(kù)(Runtime Library中文:運(yùn)行時(shí)庫(kù)).在這個(gè)庫(kù)中可以用C函數(shù)來(lái)實(shí)現(xiàn)方法,對(duì)象也可以用C語(yǔ)言的結(jié)構(gòu)體來(lái)表示…所有oc的方法的背后都是通過(guò)runtime來(lái)運(yùn)行的.
2.runtime的使用
(1)利用runtime的消息發(fā)送機(jī)制調(diào)用方法
首先新建一個(gè)類(lèi)RuntimeModel,并實(shí)現(xiàn)對(duì)象方法eat,在RuntimeViewController中調(diào)用eat方法,使用oc來(lái)語(yǔ)言來(lái)實(shí)現(xiàn)很簡(jiǎn)單了
RuntimeModel * model=[[RuntimeModel alloc]init];
[model eat];
接下來(lái)一點(diǎn)點(diǎn)用runtime實(shí)現(xiàn)上面的代碼,導(dǎo)入runtime的頭文件#import <objc/message.h>,由于xcode5.0開(kāi)始蘋(píng)果不建議我們使用底層的代碼,所以target->build setting->搜索msg->將YES改為NO,這樣接下來(lái)我們用runtime的時(shí)候才會(huì)出現(xiàn)提示。
我們使用objc_msgSend(<#id self#>, <#SEL op, ...#>)這個(gè)方法,可以看到需要兩個(gè)參數(shù),第一個(gè)參數(shù)是id類(lèi)型,代表誰(shuí)要發(fā)送消息,第二個(gè)參數(shù)是要把消息發(fā)送給誰(shuí),我們用runtime來(lái)實(shí)現(xiàn)[model eat];這個(gè)方法。
objc_msgSend(model, sel_registerName("eat"));
而初始化對(duì)象同樣是調(diào)用了alloc init這兩個(gè)方法。將導(dǎo)入的頭文件RuntimeModel去掉,用純c語(yǔ)言的代碼實(shí)現(xiàn)上面的功能。
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("RuntimeModel"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("eat"));
運(yùn)行,我們可以看到在eat方法中的nslog被調(diào)用了,雖然我們實(shí)現(xiàn)了功能,但是怎么才能知道我們的oc語(yǔ)言在運(yùn)行時(shí)確實(shí)是被轉(zhuǎn)換成了c語(yǔ)言的代碼呢?
新建工程,創(chuàng)建命令行工具。

新建一個(gè)person類(lèi),然后進(jìn)入main.m中。
調(diào)用頭文件,在main中實(shí)例化person。
person * p=[[person alloc]init];關(guān)閉項(xiàng)目,打開(kāi)終端,進(jìn)入剛才建的文件夾下,ls打開(kāi)可以看到剛才我們新建的類(lèi)和main.m文件,接下來(lái)執(zhí)行命令行clang -rewrite-objc main.m這時(shí)我們可以看到,在剛才的工程中出現(xiàn)一個(gè)
main.cpp的文件,打開(kāi)并且拖到最下面。


(2)交換方法
做為oc的程序員最悲慘的就是,運(yùn)行--崩潰在main里面,我尼瑪?。?! 例如:
NSURL * url=[NSURL URLWithString:@"www.baidu.com.啦啦"];
當(dāng)我們沒(méi)有進(jìn)行編碼的時(shí)候,這個(gè)url在編譯的時(shí)候是有效的,但是一旦運(yùn)行起來(lái),這個(gè)url就會(huì)變?yōu)閚il。因?yàn)闄z測(cè)不到這是一個(gè)無(wú)效的url,會(huì)繼續(xù)發(fā)送網(wǎng)絡(luò)請(qǐng)求。
怎么辦呢?在下面用if做一個(gè)判斷?可是要在每一個(gè)url下面都做判斷。這時(shí)候最先想到的一定是重寫(xiě)。
創(chuàng)建一個(gè)url的分類(lèi),重寫(xiě)URLWithString:<#(nonnull NSString *)#>但是,我們看:
+(instancetype)URLWithString:(NSString *)URLString
{
// 首先創(chuàng)建一個(gè)URL
NSURL * url= ????????
if (url==nil) {
NSLog(@"有問(wèn)題");
}
return url;
}
我們?cè)撛趺磩?chuàng)建呢,死循環(huán)了是不是,所以runtime就起作用了。
在分類(lèi)中自定義一個(gè)方法
+(instancetype)TY_URLWithStr:(NSString *)Str;
{
NSURL * url =[NSURL URLWithString:Str];
if (url == nil) {
NSLog(@"是空?。。?!");
}
return url;
}
但是我們并不是要每一次創(chuàng)建url都調(diào)用這個(gè)方法,因?yàn)槌绦蚶镉泻芏鄤?chuàng)建url的地方,我們還繼續(xù)使用系統(tǒng)自帶的方法。在分類(lèi)中實(shí)現(xiàn)load方法,當(dāng)程序加載這個(gè)類(lèi)的時(shí)候最先調(diào)用這個(gè)方法。導(dǎo)入頭文件,開(kāi)始進(jìn)行方法交換。
+(void)load
{
// Method : 成員方法
//class_getClassMethod 拿到類(lèi)方法
//class_getInstanceMethod 拿到對(duì)象方法
Method URLWithStr = class_getClassMethod([NSURL class], @selector(URLWithString:));
Method TYURLWithStr = class_getClassMethod([NSURL class], @selector(TY_URLWithStr:));
// 開(kāi)始交換方法
method_exchangeImplementations(URLWithStr, TYURLWithStr);
}
記得將上面我們自定義的方法中創(chuàng)建url的方法改變回去,不然再次死循環(huán),改為
+(instancetype)TY_URLWithStr:(NSString *)Str;
{
NSURL * url =[NSURL TY_URLWithStr:Str];
if (url == nil) {
NSLog(@"是空?。。。?);
}
return url;
}
是不是有種一葉落而知天下秋的感覺(jué)。

(3)遍歷屬性列表簡(jiǎn)化序列化
oc的序列化在這里就不多說(shuō)了,讓我們來(lái)說(shuō)一種常見(jiàn)的情況,當(dāng)需要?dú)w檔的屬性過(guò)多時(shí),我們需要一條條的寫(xiě)出來(lái),十分繁瑣,有沒(méi)有可能簡(jiǎn)化一些呢,如果單純的用for循環(huán)去做,那么不同的類(lèi)型該怎么處理呢,這時(shí)候我們的runtime又來(lái)了。首先創(chuàng)建一個(gè)Person類(lèi),多弄一些虛擬屬性。
// .h
@property(nonatomic,strong) NSString * name;
@property(nonatomic,strong) NSString * name1;
@property(nonatomic,assign) int age;
@property(nonatomic,assign) int age1;
@property(nonatomic,assign) double age2;
// .m
-(void)encodeWithCoder:(NSCoder *)Coder
{
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self=[super init];
if (self) {
}
return self;
}
實(shí)現(xiàn)思路:
-(void)encodeWithCoder:(NSCoder *)Coder
{
for (int i = 0; i < 屬性數(shù)量; i++) {
[Coder encodeObject:屬性值 forKey:屬性名稱(chēng)];
}
}
那么 回到controller中,導(dǎo)入runtime頭文件,使用一個(gè)方法class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)獲取屬性列表,第一個(gè)參數(shù),傳入一個(gè)類(lèi)[Person class],第二個(gè)參數(shù),傳入一個(gè)指針,在上面定義unsigned int count = 0;,然后傳入&count。這個(gè)count就是獲取的屬性的數(shù)量。同時(shí)在c語(yǔ)言中是不分.h.m的,所以無(wú)論是在哪個(gè)文件中定義的屬性,都可以取到。
unsigned int count = 0;
class_copyIvarList([Person class], &count);
然后我們需要定義一個(gè)Ivar類(lèi)型的指針,這個(gè)指針會(huì)指向每一個(gè)屬性,下面這個(gè)圖說(shuō)明一下,他并不是同時(shí)指向每一個(gè)屬性,而是一個(gè)一個(gè)分別指向來(lái)獲取屬性。

我們使用一個(gè)方法通過(guò)這個(gè)ivars去獲取屬性名稱(chēng)。
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([Person class], &count);
Ivar ivar = ivars[0];
const char * name = ivar_getName(ivar);
NSLog(@"%s",name);
打印看到第一個(gè)屬性名,可以改變ivars[第幾個(gè)],去獲取第幾個(gè)。而且即使角標(biāo)越界,依然不會(huì)崩潰。
那么我們回到person類(lèi)中,直接用剛才的代碼實(shí)現(xiàn)我們最開(kāi)始提出的問(wèn)題。
// 歸檔
-(void)encodeWithCoder:(NSCoder *)Coder
{
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar= ivars[i];
const char * name = ivar_getName(ivar);
NSString * key = [[NSString alloc]initWithUTF8String:name];
// 使用KVC 拿出屬性的值
[Coder encodeObject:[self valueForKey:key] forKey:key];
}
}
// 解檔
-(instancetype)initWithCoder:(NSCoder *)Decoder
{
self=[super init];
if (self) {
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar= ivars[i];
const char * name = ivar_getName(ivar);
NSString * key = [[NSString alloc]initWithUTF8String:name];
// 使用KVC 拿出屬性的值
id value = [Decoder decodeObjectForKey:key];
// 設(shè)置屬性
[self setValue:value forKey:key];
}
}
return self;
}
通過(guò)上面的講述,這段代碼就很容易理解了。我們用的是kvc的賦值和取值,所以任何類(lèi)型的歸檔解檔都是沒(méi)有問(wèn)題的。

(4)刨析KVO底層實(shí)現(xiàn)
我們先用oc實(shí)現(xiàn)一個(gè)簡(jiǎn)單的KVO監(jiān)聽(tīng)。
// controller.m
self.c=[[Cat alloc]init];
self.d=[[Dog alloc]init];
// 注冊(cè)監(jiān)聽(tīng)
[self.d addObserver:self.c forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
// cat.m
// 監(jiān)聽(tīng)到了object的對(duì)象keyPath屬性變化為樂(lè)change
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"監(jiān)聽(tīng)到了%@的對(duì)象%@屬性變化為樂(lè)%@",object,keyPath,change);
}
這是KVO最簡(jiǎn)單的應(yīng)用,那么接下來(lái)我們看一下KVO底層到底是怎么實(shí)現(xiàn)的呢?
首先在KVO運(yùn)行的時(shí)候會(huì)動(dòng)態(tài)的添加一個(gè)類(lèi),繼承與被觀察者的類(lèi)。名字叫做NSKVONotifying_Dog這個(gè)類(lèi)。類(lèi)名可不是瞎編的哦。然后在.m文件中,調(diào)用父類(lèi)的set方法:
-(void)setAge:(int)age
{
[super setAge:age];
// 在子類(lèi)中調(diào)用這兩個(gè)方法
// 這個(gè)是 將要被改變的值是什么
[self willChangeValueForKey:@"age"];
// 這個(gè)是 改變之后的新值是什么
[self didChangeValueForKey:@"age"];
}
這樣就會(huì)監(jiān)聽(tīng)到改變并且傳值,但是為什么說(shuō)KVO是這樣實(shí)現(xiàn)的呢?
將剛才新建的NSKVONotifying_Dog類(lèi)刪掉,在controller中實(shí)現(xiàn)點(diǎn)擊改變值的方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.d.age=99;
}
在賦值的地方打斷點(diǎn),如果程序運(yùn)行到這里,d的類(lèi)型變?yōu)閯偛拍莻€(gè)方法的類(lèi)型,那么就說(shuō)明KVO就是這樣實(shí)現(xiàn)的。


(5)動(dòng)態(tài)添加方法
首先創(chuàng)建一個(gè)Person類(lèi),然后在controller中實(shí)例化person直接可以這樣直接調(diào)用一個(gè)不存在的方法
Person * p=[[Person alloc]init];
[p performSelector:@selector(eat)];
這樣雖然編譯可以過(guò),但是運(yùn)行起來(lái)就會(huì)崩潰
這時(shí)候我沒(méi)回到person.m中,來(lái)看兩個(gè)方法
// 當(dāng)這個(gè)類(lèi)被調(diào)用沒(méi)有實(shí)現(xiàn)的類(lèi)方法 就會(huì)來(lái)到這里
+(BOOL)resolveClassMethod:(SEL)sel
{
return [super resolveClassMethod:sel];
}
// 當(dāng)這個(gè)類(lèi)被調(diào)用沒(méi)有實(shí)現(xiàn)的對(duì)象方法 就會(huì)來(lái)到這里
+(BOOL)resolveInstanceMethod:(SEL)sel
{
return [super resolveInstanceMethod:sel];
}
方法中的參數(shù)就是被調(diào)用的方法名,然后我們需要實(shí)現(xiàn)一個(gè)名為eat的函數(shù)
void eat(){
NSLog(@"lalal");
}
這時(shí)候,我們將要用到一個(gè)方法class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)第一個(gè)參數(shù):類(lèi)類(lèi)型,第二個(gè)參數(shù):方法編號(hào),第三個(gè)參數(shù):方法實(shí)現(xiàn)(函數(shù)指針),第四個(gè)參數(shù):返回值類(lèi)型。關(guān)于這第四個(gè)參數(shù),這是c語(yǔ)音,我們?cè)撛趺磳?xiě)返回類(lèi)型呢?去查一下官方文檔關(guān)于第四個(gè)參數(shù)的描述。

那么我們來(lái)實(shí)現(xiàn)代碼
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
class_addMethod([Person class], sel, (IMP)eat, "v");
}
return [super resolveClassMethod:sel];
return [super resolveInstanceMethod:sel];
}
void eat(){
NSLog(@"lalal");
}
這樣就完成了一個(gè)動(dòng)態(tài)添加方法,然后我們接著看文檔,文檔中有一段代碼示例

]
我們可以看到,當(dāng)動(dòng)態(tài)添加方法是會(huì)傳入兩個(gè)參數(shù),實(shí)際上每一個(gè)函數(shù)被調(diào)用時(shí)都會(huì)傳入這兩個(gè)參數(shù),叫做隱式參數(shù)。參數(shù)一:調(diào)用了哪個(gè)類(lèi)的,參數(shù)二:調(diào)用了哪個(gè)方法,我們用nslog打印一下這兩個(gè)參數(shù),在這之前需要改一下上面的第四個(gè)參數(shù)為"v@:",因?yàn)槲覀兎祷刂殿?lèi)型改變了。打印一下:

接下來(lái),就是如何傳遞參數(shù),我們?cè)谡{(diào)用方法的時(shí)候傳入一個(gè)參數(shù)
Person * p=[[Person alloc]init];
[p performSelector:@selector(eat:) withObject:@"6666"];
回去Person類(lèi),將判斷的方法名改為eat:,并將eat函數(shù)增加一個(gè)參數(shù):
+(BOOL)resolveInstanceMethod:(SEL)sel
{
// 方法名的判斷
if (sel == @selector(eat:)) {
class_addMethod([Person class], sel, (IMP)eat, "v@:");
}
return [super resolveClassMethod:sel];
return [super resolveInstanceMethod:sel];
}
void eat(id self, SEL _cmd ,id obj){
NSLog(@"%@ ",obj);
}
控制臺(tái):

參數(shù)完美傳遞過(guò)來(lái)。
