Category擴(kuò)展唧领,它是對(duì)一個(gè)類進(jìn)行功能的擴(kuò)展处嫌。
在項(xiàng)目的開發(fā)過(guò)程中绒尊,在不斷的迭代開發(fā)過(guò)程中够傍,我們的類也不可避免的要根據(jù)需求來(lái)增加新的功能甫菠,而這個(gè)時(shí)候很多的人可能會(huì)新建一個(gè)子類,然后在子類中去增加我們的新功能冕屯,這確實(shí)能夠?qū)崿F(xiàn)我們的目的寂诱,但是久而久之,我們會(huì)因?yàn)樾陆ǖ念愒絹?lái)越多安聘,導(dǎo)致項(xiàng)目也越來(lái)越龐大痰洒,而且也很難管理,這個(gè)時(shí)候Category就派上用場(chǎng)了浴韭,我們可以將一組具有相似的功能的擴(kuò)展放在一個(gè)Category里面丘喻,這樣就可以進(jìn)行模塊化劃分功能。
Category的調(diào)用
首先我們來(lái)了解一下類擴(kuò)展念颈,涉及到多個(gè)擴(kuò)展重寫類的同一個(gè)方法的調(diào)用順序
1泉粉、一個(gè)類的擴(kuò)展的情況
首先創(chuàng)建一個(gè)Person類,里面有一個(gè)addPerson:方法榴芳,然后建立一個(gè)Person類的擴(kuò)展Person+Extend嗡靡,里面可以擴(kuò)展你的其他方法,在這里我們實(shí)驗(yàn)一下繼續(xù)擴(kuò)展addPerson:方法窟感,看看調(diào)用的順序枕荞。
Person類
@implementation Person
- (void)addPerson:(NSString *)a {
NSLog(@"Person類里面~~~~~~%@", self);
}
@end
擴(kuò)展Extend里面
@implementation Person (Extend)
- (void)addPerson:(NSString *)a {
NSLog(@"Extend~~~~~~%@", self);
}
@end
調(diào)用
- (void)test3 {
Person *person = [[Person alloc] init];
[person addPerson:@"a"];
}
發(fā)現(xiàn)只打印了擴(kuò)展的輸出:
2017-03-24 22:37:49.135 RunTimeDemo[84566:15956638] Extend~~~~~~<Person: 0x608000004cd0>
并沒(méi)有打印Person原來(lái)方法的addPerson:方法粟害,所以擴(kuò)展有更高的優(yōu)先級(jí)千元,如果擴(kuò)展里出現(xiàn)了和原類里面相同的方法暖眼,那么會(huì)直接調(diào)用擴(kuò)展里的方法哩至,而不會(huì)調(diào)用類里面原來(lái)的方法。這里擴(kuò)展的方法不是覆蓋了類里面的方法蜜自,類里面的方法和擴(kuò)展里的方法都存在類的結(jié)構(gòu)空間菩貌,但是最先尋找到的是擴(kuò)展里的方法,所以先調(diào)用該方法的實(shí)現(xiàn)袁辈。更具體的原因會(huì)在之后用runtime進(jìn)行解釋菜谣。
2、兩個(gè)類擴(kuò)展的情況
在上文的基礎(chǔ)上晚缩,我們?cè)趧?chuàng)建一個(gè)Person的擴(kuò)展Person+A,然后同樣實(shí)現(xiàn)了addPerson:的方法媳危,然后在調(diào)用這個(gè)方法荞彼,看一下編譯器會(huì)調(diào)用哪個(gè)方法,還是兩個(gè)方法都會(huì)調(diào)用待笑。
@implementation Person (A)
- (void)addPerson:(NSString *)a {
NSLog(@"A~~~~~~%@", self);
}
@end
調(diào)用
- (void)test3 {
Person *person = [[Person alloc] init];
[person addPerson:@"a"];
}
發(fā)現(xiàn)文件打印的是:
2017-03-24 22:45:39.342 RunTimeDemo[84658:16064399] A~~~~~~<Person: 0x600000009be0>
那么原來(lái)Extend擴(kuò)展里的addPerson:方法呢鸣皂,為什么沒(méi)有調(diào)用呢?
上面的編譯的順序是
首先我們修改一下編譯的順序暮蹂,將Person+A和Person+Extend的編譯順序換一下
我們看到打印出來(lái)的結(jié)果又變成了
2017-03-24 22:51:58.767 RunTimeDemo[84770:16152019] Extend~~~~~~<Person: 0x6000000187d0>
這個(gè)實(shí)驗(yàn)說(shuō)明當(dāng)有兩個(gè)擴(kuò)展里面有同一個(gè)方法時(shí)寞缝,會(huì)調(diào)用后編譯的那個(gè)擴(kuò)展里的方法,為什么會(huì)這樣呢仰泻,原因是當(dāng)類有擴(kuò)展方法的時(shí)候荆陆,擴(kuò)展方法的鏈表就放在原來(lái)類的方法鏈表的前面,那么在尋找方法的時(shí)候集侯,會(huì)從前面開始尋找方法被啼,當(dāng)尋找到第一個(gè)這個(gè)方法的時(shí)候,返回方法的實(shí)現(xiàn)調(diào)用棠枉,所以根據(jù)上面兩個(gè)編譯順序浓体,我用下面的圖來(lái)解釋一下:
所以當(dāng)類有多個(gè)擴(kuò)展的時(shí)候,擴(kuò)展里有相同的方法的時(shí)候辈讶,會(huì)調(diào)用最后面編譯的那個(gè)擴(kuò)展里的方法命浴。
3、在類擴(kuò)展里進(jìn)行方法交換
現(xiàn)在暫時(shí)的屏蔽掉Person+Extend的方法贱除,而新加一個(gè)Person+B擴(kuò)展生闲,現(xiàn)在對(duì)Person+A和Person+B進(jìn)行方法交換。
Person+A
@implementation Person (A)
+ (void)load
{
Method originalMethod = class_getInstanceMethod(self, @selector(addPerson:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(a_addPerson:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)a_addPerson:(NSString *)a {
NSLog(@"A類里面~~~~~~%@", self);
[self a_addPerson:a];
}
@end
Person+B
@implementation Person (B)
+ (void)load
{
Method originalMethod = class_getInstanceMethod(self, @selector(addPerson:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(b_addPerson:));
method_exchangeImplementations(originalMethod, swizzledMethod);
}
- (void)b_addPerson:(NSString *)a {
NSLog(@"B類里面~~~~~~%@", self);
[self b_addPerson:a];
}
@end
調(diào)用
- (void)test3 {
Person *person = [[Person alloc] init];
[person addPerson:@"a"];
}
打印出來(lái)的結(jié)果
發(fā)現(xiàn)A擴(kuò)展和B擴(kuò)展的方法都調(diào)用了勘伺,這里擴(kuò)展的不是addPerson:方法跪腹,其實(shí)是+load方法,+load方法是一個(gè)類所在的文件引用就會(huì)調(diào)用飞醉,而+load方法是所有的擴(kuò)展文件都會(huì)調(diào)用的冲茸,所以在這里看看編譯的順序
所以這里看當(dāng)Person+B擴(kuò)展里的+load方法被調(diào)用的時(shí)候屯阀,將Person類里面本來(lái)的方法的實(shí)現(xiàn)和B擴(kuò)展里的方法的實(shí)現(xiàn)進(jìn)行了交換,然后編譯到Person+A方法的時(shí)候轴术,將Person的方法和A的方法交換难衰,此時(shí)Person里面方法的實(shí)現(xiàn)是B,所以就相當(dāng)于將B的實(shí)現(xiàn)和A的實(shí)現(xiàn)進(jìn)行了交換逗栽,所以就有了最后的一個(gè)圖盖袭,所以三個(gè)方法都交換了方法的實(shí)現(xiàn),當(dāng)調(diào)用Person的方法的時(shí)候彼宠,實(shí)際上是調(diào)用A的方法鳄虱,然后調(diào)用A方法的時(shí)候調(diào)用的是B方法,最后再是真正意義上的Person的方法凭峡。
當(dāng)改變編譯的順序的時(shí)候拙已,可以看到打印順序也換了
打印出結(jié)果
4、對(duì)類擴(kuò)展進(jìn)行方法交換和單獨(dú)的類擴(kuò)展
現(xiàn)在將Person+Extend文件打開摧冀,同時(shí)Person+A和Person+B進(jìn)行方法交換
打印出來(lái)的結(jié)果
此時(shí)沒(méi)有打印Person里面的方法倍踪,說(shuō)明方法交換的時(shí)候交換的是Extend擴(kuò)展里的方法。
5索昂、對(duì)類擴(kuò)展添加一個(gè)屬性的時(shí)候
在 Person+Extend.h 的擴(kuò)展里加一個(gè)屬性建车,然而 Person+Extend.m里并沒(méi)有實(shí)現(xiàn)setter和getter方法
Person+Extend
@interface Person (Extend)
@property (nonatomic, copy) NSString *name;
@end
調(diào)用
- (void)test3 {
Person *person = [[Person alloc] init];
person.name = @"ffff";
}
會(huì)直接報(bào)錯(cuò)
當(dāng)我們?cè)赑erson的init方法里打印self.name的時(shí)候,會(huì)發(fā)現(xiàn)并沒(méi)有這個(gè)name屬性
說(shuō)明在類的擴(kuò)展中添加屬性的時(shí)候椒惨,編譯器并沒(méi)有自動(dòng)為我們添加setter和getter的方法缤至,而擴(kuò)展里添加屬性也并不是添加了成員變量,而我們?cè)L問(wèn)這個(gè)屬性的時(shí)候框产,其實(shí)是訪問(wèn)setter和getter方法凄杯。