OC之Method_Swizling一些坑點(diǎn)壁熄、KVC原理分析
Method_Swizling
Method_Swizling我們并不陌生,通過交換兩個(gè)方法SEL的IMP指向闻坚,達(dá)到方法交換的目的货岭。一般來說鲜漩,我們通常寫在cateogry里苍息,在+load()方法里實(shí)現(xiàn)方法的交換廓奕。
常規(guī)的方法交換
// SSJPerson.h
@interface SSJPerson : NSObject
- (void)person_walk;
@end
// SSJPerson.m
import "SSJPerson.h"
@implementation SSJPerson
- (void)person_walk{
NSLog(@"SSJPerson ---> person_walk");
}
// SSJStudent.h
// SSJStudent 繼承自 SSJPerson
@interface SSJStudent : SSJPerson
- (void)student_sleep;
@end
// SSJStudent.m
import "SSJStudent.h"
@implementation SSJStudent
- (void)student_sleep{
NSLog(@"SSJStudent ---> student_sleep");
}
@end
//SSJStudent+category.m
import "SSJStudent+category.h"
import <objc/runtime.h>
@implementation SSJStudent (category)
- (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod([self class], @selector(student_sleep));
Method swizzlingMethod = class_getInstanceMethod([self class], @selector(student_sleepNew));
method_exchangeImplementations(originalMethod, swizzlingMethod);
});
}
- (void)student_sleepNew{
NSLog(@"替換過的方法 -- > student_sleepNew");
[self student_sleepNew];
}
@end
復(fù)制代碼
調(diào)用的時(shí)候:
SSJStudent *stu = [SSJStudent new];
[stu student_sleep];
復(fù)制代碼
運(yùn)行也沒問題:
image.png
父類實(shí)現(xiàn),子類沒有實(shí)現(xiàn)
那么替換一個(gè)父類的已經(jīng)實(shí)現(xiàn)了档叔,但當(dāng)前cateogry類沒實(shí)現(xiàn)的方法呢?
對(duì)代碼進(jìn)行修改:
// SSJStudent+category.m
import "SSJStudent+category.h"
import <objc/runtime.h>
@implementation SSJStudent (category)
- (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/// 替換父類方法:person_walk
/// 父類實(shí)現(xiàn)了person_walk蒸绩,子類并沒實(shí)現(xiàn)person_walk
Method originalMethod = class_getInstanceMethod([self class], @selector(person_walk));
Method swizzlingMethod = class_getInstanceMethod([self class], @selector(person_walkNew));
method_exchangeImplementations(originalMethod, swizzlingMethod);
});
}
//- (void)student_sleepNew{
// NSLog(@"替換過的方法 -- > student_sleepNew");
// [self student_sleepNew];
//}
- (void)person_walkNew{
NSLog(@"替換過的方法 -- > person_walkNew");
[self person_walkNew];
}
@end
復(fù)制代碼
// ViewController.m
@implementation ViewController
-
(void)viewDidLoad {
[super viewDidLoad];SSJStudent *student = [SSJStudent new];
/// 這里換成person_walk
[student person_walk];
}
@end
復(fù)制代碼
運(yùn)行:
image.png
看打印結(jié)果衙四,子類調(diào)用person_walk都沒問題。
這里對(duì)ViewController.m添加兩行代碼:
// ViewController.m
@implementation ViewController
-
(void)viewDidLoad {
[super viewDidLoad];SSJStudent *student = [SSJStudent new];
/// 這里換成person_walk
[student person_walk];
NSLog(@"\n");
/// 添加代碼患亿,父類也調(diào)用person_walk
SSJPerson *person = [SSJPerson new];
[person person_walk];
}
@end
復(fù)制代碼
再次運(yùn)行传蹈,就發(fā)現(xiàn)提示找不到方法:
image.png
這邊我畫了一張圖:
image.png
由于SSJStudent+category內(nèi)部實(shí)現(xiàn)了+load()方法,導(dǎo)致程序在load_images階段步藕,就調(diào)用了+load()方法惦界。
而+load()方法里對(duì)父類(SSJPerson)的person_walk方法進(jìn)行了替換,導(dǎo)致父類在調(diào)用自己方法person_walk的時(shí)候咙冗,提示找不到具體的person_walkNew實(shí)現(xiàn)沾歪,因?yàn)楦割惛揪蜎]這個(gè)方法。
在實(shí)際多人開發(fā)過程中雾消,提供父類的那個(gè)人他不一定知道你交換了父類的方法灾搏,當(dāng)他調(diào)用自己父類的方法時(shí),可能就一下子對(duì)這個(gè)報(bào)錯(cuò)感到莫名其妙:我明明沒有調(diào)用這個(gè)方法啊立润,為什么提示這個(gè)錯(cuò)誤狂窑?
如何避免這種子類替換了父類的方法,子類自己卻沒有實(shí)現(xiàn)父類方法的情況呢桑腮?
我們對(duì)SSJStudent+category.m的+load()方法進(jìn)行修改:
// SSJStudent+category.m
import "SSJStudent+category.h"
import <objc/runtime.h>
@implementation SSJStudent (category)
- (void)load{
/// 替換父類方法:person_walk
/// 父類實(shí)現(xiàn)了person_walk泉哈,子類并沒實(shí)現(xiàn)person_walk
Method originalMethod = class_getInstanceMethod([self class], @selector(person_walk));
Method swizzlingMethod = class_getInstanceMethod([self class], @selector(person_walkNew));
//添加一個(gè)Method(SEL - person_walk,IMP - person_walkNew)
BOOL isAdded = class_addMethod([self class], method_getName(originalMethod), method_getImplementation(swizzlingMethod), method_getTypeEncoding(originalMethod));
if(isAdded){
/// 添加成功 ,就說明子類沒有實(shí)現(xiàn)父類person_walk對(duì)應(yīng)的IMP方法.
/// 經(jīng)過class_addMethod這一步丛晦,子類已經(jīng)有了一個(gè)person_walk方法奕纫,并且IMP指向person_walkNew
/// 接下來,直接添加一個(gè)Method(SEL - person_walkNew采呐,IMP - person_walk)
/// 然后子類就實(shí)現(xiàn)了有了兩個(gè)IMP互相交換的Method,最終效果跟method_exchangeImplementations一樣
class_replaceMethod([self class], method_getName(swizzlingMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
///添加不成功若锁,說明子類本身就已經(jīng)實(shí)現(xiàn)了person_walk的IMP方法,那就直接交換兩個(gè)Method的IMP即可
method_exchangeImplementations(originalMethod, swizzlingMethod);
}
}
復(fù)制代碼
運(yùn)行效果:
image.png
簡單來說斧吐,就是:
class_addMethod添加Method(SEL - person_walk又固,IMP - person_walkNew)
成功 -》則調(diào)用class_replaceMethod添加Method(SEL - person_walkNew,IMP - person_walk)煤率。
失敗 -》則調(diào)用method_exchangeImplementations交換兩個(gè)Method的IMP指向仰冠。
為了便于理解,我畫了張圖
image.png
說明:
class_addMethod:只能在SEL沒有IMP指向時(shí)才可以添加成功蝶糯;
class_replaceMethod:不管SEL 有沒有IMP實(shí)現(xiàn)洋只,都可以添加成功;
父類沒有實(shí)現(xiàn)昼捍,子類也沒有實(shí)現(xiàn)
把父類實(shí)現(xiàn)部分注釋
image.png
然后再運(yùn)行:
image.png
提示找不到這個(gè)person_walk這個(gè)方法實(shí)現(xiàn)识虚,那就說明category那里出了問題。
我們?cè)赾lass_getInstanceMethod那一行打上斷點(diǎn):
image.png
由于父類和子類都沒有實(shí)現(xiàn)person_walk妒茬,導(dǎo)致這邊獲取的originalMethod為空担锤。
我們對(duì)category的+load()方法進(jìn)行修改,添加originalMethod空值處理:
-
(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{/// 替換父類方法:person_walk /// 父類實(shí)現(xiàn)了person_walk乍钻,子類并沒實(shí)現(xiàn)person_walk Method originalMethod = class_getInstanceMethod([self class], @selector(person_walk)); Method swizzlingMethod = class_getInstanceMethod([self class], @selector(person_walkNew)); if (!originalMethod) { /// 沒有肛循,那就添加一個(gè)person_walk方法,并且手動(dòng)添加一個(gè)臨時(shí)處理的IMP實(shí)現(xiàn) class_addMethod([self class], @selector(person_walk), method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod)); /// originalMethod需要重新獲取一邊银择,不然依舊是空的 originalMethod = class_getInstanceMethod([self class], @selector(person_walk)); method_setImplementation(originalMethod, imp_implementationWithBlock(^(id self,SEL _cmd){ NSLog(@"臨時(shí)方法"); })); } //添加一個(gè)Method(SEL - person_walk多糠,IMP - person_walkNew) BOOL isAdded = class_addMethod([self class], method_getName(originalMethod), method_getImplementation(swizzlingMethod), method_getTypeEncoding(originalMethod)); if(isAdded){ /// 添加成功 ,就說明子類沒有實(shí)現(xiàn)父類person_walk對(duì)應(yīng)的IMP方法. /// 經(jīng)過class_addMethod這一步浩考,子類已經(jīng)有了一個(gè)person_walk方法夹孔,并且IMP指向person_walkNew /// 接下來,直接添加一個(gè)Method(SEL - person_walkNew析孽,IMP - person_walk) /// 然后子類就實(shí)現(xiàn)了有了兩個(gè)IMP互相交換的Method,最終效果跟method_exchangeImplementations一樣 class_replaceMethod([self class], method_getName(swizzlingMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }else{ ///添加不成功析蝴,說明子類本身就已經(jīng)實(shí)現(xiàn)了person_walk的IMP方法,那就直接交換兩個(gè)Method的IMP即可 method_exchangeImplementations(originalMethod, swizzlingMethod); }
});
}
復(fù)制代碼
運(yùn)行:
image.png
KVC分析
在講解KVC之前绿淋,請(qǐng)先允許我演示一段騷操作闷畸,對(duì)SSJPerson類的未開放屬性進(jìn)行讀寫操作:
// SSJPerson.h
import <Foundation/Foundation.h>
@interface SSJPerson : NSObject
@end
// SSJPerson.m
import "SSJPerson.h"
@interface SSJPerson ()
/// 昵稱
@property (nonatomic , strong) NSString *nickName_private;
@end
@implementation SSJPerson
@end
復(fù)制代碼
image.png
如圖所示,對(duì)于一個(gè)未開放出來的屬性吞滞,我們無法通過對(duì)象.屬性名這種常規(guī)的方式進(jìn)行訪問佑菩。但是我們可以利用setValue:forKey:這方式進(jìn)行賦值盾沫。
這種通過setValue:forKey:方式進(jìn)行賦值的操作,我們稱之為KVC殿漠。
KVC是一種設(shè)計(jì)模式赴精,那么它的原理又是什么呢?為什么可以對(duì)未開放屬性進(jìn)行直接操作呢绞幌?
存在即是真理蕾哟。帶著探索的思維,我們決定去看一下setValue:forKey:的底層實(shí)現(xiàn)莲蜘。
進(jìn)入蘋果官方文檔:KVC部分
image.png
大概意思是:
NSObject提供的NSKeyValueCoding協(xié)議谭确,默認(rèn)實(shí)現(xiàn)使用一組明確定義的規(guī)則,將基于密鑰的訪問器調(diào)用映射到對(duì)象的底層屬性票渠。這些協(xié)議方法使用一個(gè)關(guān)鍵參數(shù)來搜索它們自己的對(duì)象實(shí)例逐哈,以查找訪問器、實(shí)例變量和遵循某些命名約定的相關(guān)方法问顷。
Setter
接下來看一下Setter搜索模式: image.png
按照?qǐng)D上所說昂秃,Setter搜索模式分為3步:
找set<Key>: 或 _set<Key>,找到了就調(diào)用它杜窄;
如果沒找到肠骆,就去依次查找_<key>, is<Key>, <key>, 或is<Key>,找到了就用輸入值設(shè)置變量(比如找到了查找<key>,那么后面的_is<Key>等就不需要找了)。
如果還是沒找到塞耕,就會(huì)調(diào)用setValue:forUndefinedKey:并引發(fā)異常哗戈。
我們來根據(jù)這3個(gè)步驟,實(shí)操一下:
// SSJPerson.h
@interface SSJPerson : NSObject{
@public
NSString *boddy;
NSString *_boddy;
NSString *isBoddy;
NSString *_isBoddy;
}
@end
// SSJPerson.m
//(根據(jù)第2步荷科,找個(gè)要設(shè)置為YES)
@implementation SSJPerson
- (BOOL)accessInstanceVariablesDirectly{
return true;
}
@end
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
/// 設(shè)置值
[personA setValue:@"足球" forKey:@"boddy"];
/// 打印內(nèi)容,我們要看一下具體賦值給哪個(gè)值
NSLog(@"_<key>---%@",personA->_boddy);
NSLog(@"_is<Key>---%@",personA ->_isBoddy);
NSLog(@"<key>---%@",personA ->boddy);
NSLog(@"is<Key>---%@",personA ->isBoddy);
}
復(fù)制代碼
運(yùn)行:
image.png
注釋_boddy:
image.png
注釋_isBoddy:
image.png
這也就驗(yàn)證了里第2點(diǎn):當(dāng)存在多個(gè)類似變量纱注,會(huì)依次查找_<key>, _is<Key>, <key>, 或is<Key>畏浆,找到了就給它賦值,后面的就賦值了狞贱。
思考:關(guān)于第一點(diǎn)set<Key>:刻获,是不是也有第2點(diǎn)類似的關(guān)系呢?
image.png
注釋setBoddy方法:
image.png
注釋_setBoddy方法:
image.png
注釋setIsBoddy方法:
image.png
經(jīng)過4次打印瞎嬉,我們發(fā)現(xiàn)蝎毡,在我們調(diào)用setValue:forKey:的時(shí)候,會(huì)依次查找: set<Key>: > _set<Key> > setIs<Key>氧枣。找到后就用輸入值賦值給變量
Getter
接下來看一下Getter:
IMG_0978.JPG
大概意思如下:
1沐兵、查找 get<Key>, <key>, is<Key>, or _<key>,找到了就進(jìn)入步驟5便监;找不到就進(jìn)入步驟2扎谎;
2碳想、在實(shí)例方法中搜索:countOf<Key>和objectIn<Key>AtIndex和<key>AtIndexes:,
countOf<Key>必須實(shí)現(xiàn)毁靶,另外兩個(gè)找到了其中一個(gè)胧奔,就創(chuàng)建集合代理對(duì)象,找不到就進(jìn)入步驟3预吆;
3龙填、改為搜索countOf<Key>、Enumeratorf<Key>和memberOf<Key>:拐叉,3個(gè)方法都存在才行岩遗,否則就進(jìn)入步驟4;
4巷嚣、當(dāng)確定AccessInstanceVariables方法返回YES(默認(rèn)也是YES)喘先,
順序搜索名為 _<key>, _is<Key>, <key>, 或is<Key>的實(shí)例變量。
如果找到廷粒,直接獲取實(shí)例變量的值并繼續(xù)執(zhí)行步驟5窘拯。否則,繼續(xù)執(zhí)行步驟6坝茎。
5涤姊、如果檢索到的屬性值是對(duì)象指針,只需返回結(jié)果嗤放。
如果該值是NSNumber支持的標(biāo)量類型思喊,請(qǐng)將其存儲(chǔ)在NSNumber實(shí)例中并返回該值。
如果結(jié)果是NSNumber不支持的標(biāo)量類型次酌,請(qǐng)轉(zhuǎn)換為NSValue對(duì)象并返回該對(duì)象恨课。
6、如果都找不到岳服,調(diào)用valueForUndefinedKey:并拋出異常剂公。
復(fù)制代碼
針對(duì)第1點(diǎn),我們來進(jìn)行實(shí)操:
// SSJPerson.m
(NSString *)getBoddy{
NSLog(@"%s -->Getter",func);
return boddy;
}(NSString *)boddy{
NSLog(@"%s -->Getter",func);
return boddy;
}(NSString *)isBoddy{
NSLog(@"%s -->Getter",func);
return isBoddy;
}(NSString *)_boddy{
NSLog(@"%s -->Getter",func);
return _boddy;
}
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
SSJPerson *personA = [SSJPerson new];
/// 設(shè)置值
[personA setValue:@"足球" forKey:@"boddy"];
NSLog(@"打印---%@",[personA valueForKey:@"boddy"]);
}
復(fù)制代碼
運(yùn)行:
image.png
注釋getBoddy:
image.png
注釋boddy:
image.png
注釋isBoddy: image.png
至于為什么只有boddy方法執(zhí)行之后吊宋,valueForKey才打印出內(nèi)容纲辽,那是因?yàn)槟J(rèn)情況下,setter和getter是一一對(duì)應(yīng)的關(guān)系璃搜,setter模式優(yōu)先執(zhí)行<key>方法拖吼,getter模式對(duì)應(yīng)著_boddy方法。
針對(duì)第2點(diǎn)这吻,我們來進(jìn)行實(shí)操
在實(shí)例方法中搜索:countOf<Key>和objectIn<Key>AtIndex和<key>AtIndexes:吊档,
countOf<Key>必須實(shí)現(xiàn),另外兩個(gè)找到了其中一個(gè)唾糯,就創(chuàng)建集合代理對(duì)象籍铁,找不到就進(jìn)入步驟
復(fù)制代碼
這里不能使用boddyArray涡上,不然會(huì)走第一步的Getter方法:
image.png
把key改為myBoddyArray
image.png
注釋掉objectInMyBoddyArrayAtIndex:方法:
image.png
針對(duì)第3點(diǎn),我們來進(jìn)行實(shí)操
改為搜索countOf<Key>拒名、Enumeratorf<Key>和memberOf<Key>:吩愧,3個(gè)方法都存在才行,否則就進(jìn)入步驟4增显;
復(fù)制代碼
image.png
針對(duì)第4點(diǎn)雁佳,我們來進(jìn)行實(shí)操
當(dāng)確定AccessInstanceVariables方法返回YES(默認(rèn)也是YES),
順序搜索名為 _<key>, _is<Key>, <key>, 或is<Key>的實(shí)例變量同云。
如果找到糖权,直接獲取實(shí)例變量的值并繼續(xù)執(zhí)行步驟5。否則炸站,繼續(xù)執(zhí)行步驟6星澳。
復(fù)制代碼
image.png 注釋_boddy:
image.png
注釋_isBoddy:
image.png
注釋boddy:
image.png
針對(duì)第6點(diǎn),我們來進(jìn)行實(shí)操
如果都找不到旱易,調(diào)用valueForUndefinedKey:并拋出異常
復(fù)制代碼
在不做處理的情況下禁偎,用一個(gè)不存在的key去訪問,會(huì)報(bào)錯(cuò):
image.png
我們實(shí)現(xiàn)一下valueForUndefinedKey:阀坏,然后:
image.png
總結(jié)一下KVC的Setter和Getter
Setter:
依次查找set<Key>: 或 _set<Key>如暖,找到了就調(diào)用方法;找不到就進(jìn)入步驟2忌堂;
先確定AccessInstanceVariables返回YES盒至,然后依次查找_<key>, _is<Key>, <key>, 或is<Key>,找到了就用輸入值設(shè)置變量。找不到就進(jìn)入步驟3士修;
(比如找到了查找_<key>,那么后面的_is<Key>等就不需要找了)枷遂。
調(diào)用setValue:forUndefinedKey:并引發(fā)異常。
Getter:
查找 get<Key>, <key>, is<Key>, or _<key>棋嘲,找到了就進(jìn)入步驟5酒唉;找不到就進(jìn)入步驟2;
在實(shí)例中搜索:countOf<Key>和objectIn<Key>AtIndex和<key>AtIndexes:封字, 其中countOf<Key>必須實(shí)現(xiàn),另外兩個(gè)找到了其中一個(gè)耍鬓,就創(chuàng)建集合代理對(duì)象阔籽,找不到就進(jìn)入步驟3;
改為搜索countOf<Key>牲蜀、enumeratorf<Key>和memberOf<Key>:笆制,3個(gè)方法都存在才行,否則就進(jìn)入步驟4涣达;
當(dāng)確定AccessInstanceVariables方法返回YES(默認(rèn)也是YES)在辆, 順序搜索名為 _<key>, _is<Key>, <key>, 或is<Key>的實(shí)例變量证薇。 如果找到,直接獲取實(shí)例變量的值并繼續(xù)執(zhí)行步驟5匆篓。否則浑度,繼續(xù)執(zhí)行步驟6。
如果檢索到的屬性值是對(duì)象指針鸦概,只需返回結(jié)果箩张。 如果該值是NSNumber支持的標(biāo)量類型,請(qǐng)將其存儲(chǔ)在NSNumber實(shí)例中并返回該值窗市。 如果結(jié)果是NSNumber不支持的標(biāo)量類型先慷,請(qǐng)轉(zhuǎn)換為NSValue對(duì)象并返回該對(duì)象。
如果都找不到咨察,調(diào)用valueForUndefinedKey:并拋出異常论熙。
至此,我們就完成了KVC的探索摄狱。