因?yàn)樽罱趯W(xué)習(xí)runtime,學(xué)習(xí)到關(guān)聯(lián)對(duì)象的時(shí)候用到分類(lèi),所以順便把分類(lèi)復(fù)習(xí)了一下。我平時(shí)用繼承多于分類(lèi),然后就很困惑的是,分類(lèi)做的事情繼承也能做,為什么要用分類(lèi)呢?所以繼續(xù)復(fù)習(xí)了一下繼承。答案在后面,開(kāi)始吧!
一.分類(lèi)
詳解:深入淺出理解分類(lèi)(category)和類(lèi)擴(kuò)展(extension)
http://www.itdecent.cn/p/9e827a1708c6
1.分類(lèi)的特點(diǎn)
- 運(yùn)行時(shí)決議;
- 可以為系統(tǒng)類(lèi)添加分類(lèi);
2.分類(lèi)中可以添加什么內(nèi)容?
(面試考點(diǎn))

- 實(shí)例方法
- 類(lèi)方法
- 協(xié)議
- 屬性(@proterty修飾的)(未生成get、set方法,但是可以通過(guò)runtime添加get、set方法)。
注意:分類(lèi)不可以添加成員變量;
分類(lèi)可以訪問(wèn)原有類(lèi)中.h的屬性

- 每個(gè)分類(lèi)在編譯的時(shí)候都是生成一個(gè)名叫catrgort_t的結(jié)構(gòu)體;
3. Category的實(shí)現(xiàn)原理
- 每一個(gè)分類(lèi)編譯之后生成一個(gè) struct category_t。里面存儲(chǔ)著分類(lèi)的對(duì)象方法、類(lèi)方法、屬性、協(xié)議信息;
- 把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中;后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面。
- 將合并后的分類(lèi)數(shù)據(jù)(方法、屬性、協(xié)議),插入到類(lèi)原來(lái)數(shù)據(jù)的前面
或者說(shuō):
- Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲(chǔ)著分類(lèi)的對(duì)象方法、類(lèi)方法、屬性、協(xié)議信息
- 在程序運(yùn)行的時(shí)候,runtime會(huì)將Category的數(shù)據(jù),合并到類(lèi)信息中(類(lèi)對(duì)象、元類(lèi)對(duì)象中)
4. 分類(lèi)的對(duì)象方法添加到類(lèi)對(duì)象過(guò)程如下:
因?yàn)閷?duì)象方法存儲(chǔ)在類(lèi)對(duì)象里面,所以分類(lèi)的對(duì)象方法要添加到類(lèi)對(duì)象里面。
- 有NSString+other2、NSString+other、NSString+other1三個(gè)分類(lèi),添加順序如下圖;
- 合并的時(shí)候:A數(shù)組里面放著NSString+other2、NSString+other、NSString+other1的對(duì)象方法。
-
NSString的類(lèi)對(duì)象里面,存放對(duì)象方法的list里面,先把NSString的對(duì)象方法挪到最后一個(gè)位置。再把A數(shù)組里面的方法,從后往前添加到對(duì)象方法列表里面。所以相同的方法名,最后一個(gè)分類(lèi)里面的方法先被調(diào)用。
添加分類(lèi)

4.分類(lèi)執(zhí)行順序
如果方法名和原有類(lèi)里面的方法相同的話(huà),會(huì)覆蓋系統(tǒng)的方法;執(zhí)行順序:分類(lèi)>本類(lèi)>父類(lèi)。所以盡量不要和原有類(lèi)的方法重名。
如果不同的分類(lèi)里面有相同的方法,調(diào)用的方法由編譯器:targets->Build Phases->Complie Source里面添加的文件順序決定,從上到下編譯。調(diào)用這個(gè)方法時(shí)會(huì)執(zhí)行最后一個(gè)參與編譯的分類(lèi)里面的方法;
如下:有NSString的三個(gè)分類(lèi),NSString+Other、NSString+Other1、NSString+Other2,假如三個(gè)分類(lèi)里面都有方法A,調(diào)用方法A的時(shí)候。從上到下進(jìn)行編譯,會(huì)調(diào)用最后參與編譯的NSString+Other1里面的方法A。

- 名字相同的分類(lèi)會(huì)引起報(bào)錯(cuò)
5.分類(lèi)的使用
1.擴(kuò)展現(xiàn)有類(lèi)/添加非正式協(xié)議
2.分解體積龐大的類(lèi)文件
分類(lèi)可以將類(lèi)的實(shí)現(xiàn)分散到不同文件或多個(gè)不同的框架當(dāng)中。
1)減少單個(gè)文件的體積
2)把相同的功能放到同一個(gè)category中
3)按需加載想要的category
4)多個(gè)開(kāi)發(fā)者可以一起完成一個(gè)類(lèi)
3.聲明私有方法/創(chuàng)建對(duì)私有方法的前向引用
定義在類(lèi)名.h文件中的方法/屬性一定是公開(kāi)的;
而在類(lèi)名.m中的類(lèi)延展(Extension)中定義的方法/屬性是私有的;
不在任何地方申明,只在類(lèi).m中寫(xiě)實(shí)現(xiàn)代碼的方法都是私有的。
但是通過(guò)分類(lèi)聲明私有方法,就可以實(shí)現(xiàn)對(duì)私有方法的調(diào)用。
4.實(shí)現(xiàn)多繼承
5.把Framework的私有方法公開(kāi)
5.1擴(kuò)展現(xiàn)有類(lèi)
為現(xiàn)有類(lèi)添加分類(lèi)很普遍,不再展示;
補(bǔ)充的是非正式協(xié)議:
凡是NSObject或其子類(lèi)的分類(lèi),都是非正式協(xié)議。
5.2分解體積龐大的類(lèi)文件
分解體積龐大的類(lèi)文件,就是把類(lèi)的.h文件聲明方法,類(lèi)的實(shí)現(xiàn)放到分類(lèi)里面。這樣我們就可以按照需求把不同的實(shí)現(xiàn)放到不同分類(lèi)里面。
實(shí)例如下:
- Teacher類(lèi)的.h進(jìn)行方法聲明:read、teach方法
Teacher.h:
@interface Teacher : NSObject
@property(nonatomic,strong)NSString * teahcerName;
-(void)read;
-(void)teach:(NSString *)teachClass;
@end
Teacher.m:
@implementation Teacher
@end
- Teacher+Read實(shí)現(xiàn)Teacher的read方法:
Teacher+Read.h:
@interface Teacher (Read)
@end
Teacher+Read.m:
@implementation Teacher (Read)
//實(shí)現(xiàn)Teacher類(lèi)的read方法
-(void)read{
NSLog(@"read");
}
@end
- Teacher+Teach實(shí)現(xiàn)Teacher的teach方法:
Teacher+Teach.h:
@interface Teacher (Teach)
@end
Teacher+Teach.m:
@implementation Teacher (Teach)
//實(shí)現(xiàn)Teacher類(lèi)的teach方法;
-(void)teach:(NSString *)teachClass{
NSLog(@"%@ teach:%@",self.teahcerName,teachClass);
}
@end
4.ViewController調(diào)用Teacher的read、teach方法。成功調(diào)用
#import "Teacher.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//分解體積較大的類(lèi)文件
Teacher * teacher = [[Teacher alloc] init];
teacher.teahcerName = @"xp";
[teacher read];
[teacher teach:@"English"];
}
@end
5.3 聲明私有方法/創(chuàng)建對(duì)私有方法的前向引用
類(lèi)的.m文件進(jìn)行私有方法實(shí)現(xiàn),分類(lèi).h進(jìn)行私有聲明。實(shí)現(xiàn)外部對(duì)私有方法的使用。這也叫做對(duì)私有方法向前引用。
實(shí)例:假如Student類(lèi)中有私有方法study,怎么在ViewController中調(diào)用study方法呢?
Student.h:
#import <Foundation/Foundation.h>
@interface Student : NSObject
@end
Student.m:
@implementation Student
-(void)study{
NSLog(@"study");
}
@end
方法1:
使用performSelector在Viewcontroller中調(diào)用:
Student * stu = [[Student alloc] init];
[stu performSelector:@selector(study) withObject:nil];
方法2(私有方法前向引用):
添加Student的分類(lèi)。如下,可以把分類(lèi)的方法聲明直接添加到Studnt的.h文件中,分類(lèi)不需要實(shí)現(xiàn)方法。
注意分類(lèi)可以添加到Student.h中,也可以新建分類(lèi)文件。但是把分類(lèi)添加到Student.m中仍舊調(diào)用不到Student的私有方法。
Student.h文件:
#import <Foundation/Foundation.h>
@interface Student : NSObject
@end
//添加分類(lèi)
@interface Student(Study)
//聲明私有方法
-(void)study;
@end
ViewController調(diào)用:
//分類(lèi)的前向引用
Student * stu = [[Student alloc] init];
[stu study];
總結(jié):要運(yùn)用類(lèi)的私有方法,可以運(yùn)用分類(lèi)進(jìn)行私有方法聲明既可。
相當(dāng)于類(lèi).m進(jìn)行方法實(shí)現(xiàn)。分類(lèi).h進(jìn)行方法聲明。
5.4 實(shí)現(xiàn)多繼承
在oc里面是沒(méi)有多繼承的。我們現(xiàn)在要間接實(shí)現(xiàn)多繼承的功能。
繼承,就是可以使用父類(lèi)的內(nèi)容;
多繼承,就是可以使用多個(gè)父類(lèi)的內(nèi)容,比如調(diào)用多個(gè)類(lèi)的方法。
現(xiàn)在,我們需要讓ClassA調(diào)用ClassB的方法,也可以調(diào)用ClassC的方法。我們需要使用分類(lèi)和消息轉(zhuǎn)發(fā)。
如下:
- 有ClassA類(lèi)進(jìn)行消息轉(zhuǎn)發(fā),接收到方法后,ClassB和ClassC誰(shuí)能執(zhí)行對(duì)應(yīng)的方法就讓誰(shuí)執(zhí)行:
ClassA.h:
@interface ClassA : NSObject
@end
ClassA.m:
@implementation ClassA
//消息轉(zhuǎn)發(fā),ClassB和ClassC誰(shuí)能執(zhí)行對(duì)應(yīng)的方法就返回誰(shuí)
-(id)forwardingTargetForSelector:(SEL)aSelector{
ClassB * b = [ClassB new];
ClassC * c = [ClassC new];
if ([b respondsToSelector:aSelector]) {
return b;
}else if ([c respondsToSelector:aSelector]){
return c;
}
return nil;
}
@end
2.有ClassB類(lèi)聲明并實(shí)現(xiàn)methodB方法:
ClassB.h:
@interface ClassB : NSObject
-(void)methodB;
@end
ClassB.m:
@implementation ClassB
-(void)methodB{
NSLog(@"methodB");
}
@end
3.有ClassC類(lèi)聲明并實(shí)現(xiàn)methodC方法:
ClassC.h:
@interface ClassC : NSObject
-(void)methodC;
@end
ClassC.m:
@implementation ClassC
-(void)methodC{
NSLog(@"methodC");
}
@end
4.在Viewcontroller里面,讓ClassA調(diào)用ClassB、ClassC的方法:
方法1:使用performSelector調(diào)用
ClassA * a = [ClassA new];
[a performSelector:@selector(methodB)];
方法2:把ClassA的對(duì)象強(qiáng)轉(zhuǎn)為ClassB的
ClassA * a = [ClassA new];
[(ClassB *)a methodB];
方法3: ClassA.h中增加ClassA的分類(lèi),聲明ClassB和ClassC中的方法
ClassA.h文件:
//增加ClassA的分類(lèi);聲明ClassB和ClassC中的方法
@interface ClassA(SuperForOtherClass)
-(void)methodB;
-(void)methodC;
@end
ViewController里面:
ClassA * a = [ClassA new];
[a methodB];
[a methodC];
總結(jié):運(yùn)用消息轉(zhuǎn)發(fā)確定執(zhí)行的對(duì)象、分類(lèi)引用方法。從而達(dá)到ClassA調(diào)用其他對(duì)象的方法。
5.5 把Framework的私有方法公開(kāi)
6. load、initialize
6.1 load方法:
load的調(diào)用時(shí)機(jī)
- +load方法會(huì)在runtime加載類(lèi)、分類(lèi)時(shí)調(diào)用
- 每個(gè)類(lèi)、分類(lèi)的+load,在程序運(yùn)行過(guò)程中只調(diào)用一次
load的調(diào)用順序
先調(diào)用類(lèi)的+load
按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
調(diào)用子類(lèi)的+load之前會(huì)先調(diào)用父類(lèi)的+load再調(diào)用分類(lèi)的+load
按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
如果分類(lèi)寫(xiě)了和原本類(lèi)相同的方法,調(diào)用的時(shí)候只調(diào)用分類(lèi)的方法。但是load方法不是。
load方法的調(diào)用,直接是去找這個(gè)方法的地址,進(jìn)行調(diào)用;不是消息機(jī)制調(diào)用(消息機(jī)制調(diào)用:通過(guò)isa指針,找到類(lèi)對(duì)象的對(duì)象方法、元類(lèi)對(duì)象的類(lèi)方法),所以不是只調(diào)用分類(lèi)的方法。
如下:Student:Person。有分類(lèi)Person+Test1、Person+Test2、Student+Test1、Student+Test2。加載的時(shí)候:
1)先調(diào)用類(lèi)的load方法、類(lèi)里面先調(diào)用父類(lèi)的load方法:所以先調(diào)用Person的load方法,再調(diào)用Student的load方法。
2)再調(diào)用分類(lèi)的load方法。先編譯先調(diào)用。

Category中有l(wèi)oad方法嗎?load方法是什么時(shí)候調(diào)用的?load 方法能繼承嗎?
- 有l(wèi)oad方法
- load方法在runtime加載類(lèi)、分類(lèi)的時(shí)候調(diào)用
- load方法可以繼承,但是一般情況下不會(huì)主動(dòng)去調(diào)用load方法,都是讓系統(tǒng)自動(dòng)調(diào)用
6.2 initialize方法:
initialize調(diào)用時(shí)機(jī)
+initialize方法會(huì)在類(lèi)第一次接收到消息時(shí)調(diào)用
(通過(guò)消息機(jī)制調(diào)用,+initialize放在元類(lèi)對(duì)象里面)
調(diào)用順序
- 先調(diào)用父類(lèi)的+initialize,再調(diào)用子類(lèi)的+initialize
(先初始化父類(lèi),再初始化子類(lèi),每個(gè)類(lèi)只會(huì)初始化1次)
+load和+initialize的區(qū)別:
1.調(diào)用方式:
- +load是根據(jù)函數(shù)地址直接調(diào)用;
- +initialize是通過(guò)消息機(jī)制調(diào)用;
2.調(diào)用時(shí)刻:
- +load是在加載類(lèi)、分類(lèi)的時(shí)候調(diào)用,之后加載一次
- +initialize是類(lèi)第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類(lèi)之后+ initialize一次。但是父類(lèi)的+ initialize會(huì)被調(diào)用多次。
3.調(diào)用順序:
load
1>load是先調(diào)用類(lèi)的load
先編譯的先調(diào)用;
如果有父類(lèi),先調(diào)用父類(lèi)的load;
2>再調(diào)用分類(lèi)的load
先編譯的先調(diào)用;initaialize:
1>先初始化父類(lèi)
2>再初始化子類(lèi)(可能最終調(diào)用的是父類(lèi)的initaialize方法)
3> 如果分類(lèi)實(shí)現(xiàn)了+initialize,就覆蓋類(lèi)本身的+initialize調(diào)用
+initialize和+load的很大區(qū)別是,+initialize是通過(guò)objc_msgSend進(jìn)行調(diào)用的,所以有以下特點(diǎn)
如果子類(lèi)沒(méi)有實(shí)現(xiàn)+initialize,會(huì)調(diào)用父類(lèi)的+initialize(所以父類(lèi)的+initialize可能會(huì)被調(diào)用多次)
如果分類(lèi)實(shí)現(xiàn)了+initialize,就覆蓋類(lèi)本身的+initialize調(diào)用
二.類(lèi)擴(kuò)展
類(lèi)擴(kuò)展的形式(其實(shí)平時(shí)我們?cè)?m文件里面經(jīng)常寫(xiě)):
@interface XXX ()
//私有屬性
//私有方法(如果不實(shí)現(xiàn),編譯時(shí)會(huì)報(bào)警,Method definition for 'XXX' not found)
@end
總結(jié):
- 可以添加屬性、成員變量,都是私有的。(只能自身類(lèi)里面使用,子類(lèi)或者其他方法不可用)
- 可以添加方法,但是如果方法不實(shí)現(xiàn)的話(huà)會(huì)報(bào)警告;
- 分類(lèi)里面如果有方法未實(shí)現(xiàn)的話(huà)不會(huì)報(bào)警告。因?yàn)轭?lèi)擴(kuò)展是在編譯時(shí)期添加到類(lèi)里面,編譯時(shí)期發(fā)現(xiàn)未實(shí)現(xiàn)就可以警告了。分類(lèi)的方法是在運(yùn)行的時(shí)候添加到類(lèi)里面的;
- 類(lèi)擴(kuò)展沒(méi)有自己的實(shí)現(xiàn)部分(@implementation),必須依靠對(duì)應(yīng)類(lèi)的實(shí)現(xiàn)部分來(lái)實(shí)現(xiàn);
- 定義在.m里面的類(lèi)擴(kuò)展的方法是私有的,定義在.h文件中的類(lèi)擴(kuò)展的方法為共有的。
| 分類(lèi) | 擴(kuò)展 |
|---|---|
| 運(yùn)行時(shí)決議 | 編譯時(shí)決議 |
| 可以有聲明、實(shí)現(xiàn) | 只以聲明的形式存在,多數(shù)情況下寄生于宿主類(lèi)的.m中 |
| 可以為系統(tǒng)類(lèi)添加分類(lèi) | 不可以為系統(tǒng)類(lèi)添加擴(kuò)展 |
是在運(yùn)行時(shí),才會(huì)將數(shù)據(jù)合并到類(lèi)信息中 |
在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類(lèi)信息中 |
三.繼承
詳解:https://casatwy.com/tiao-chu-mian-xiang-dui-xiang-si-xiang-yi-ji-cheng.html
使用繼承應(yīng)該滿(mǎn)足以下3點(diǎn):
- 父類(lèi)只是提供了服務(wù)。父類(lèi)和子類(lèi)沒(méi)有業(yè)務(wù)關(guān)系;(Object提供了基礎(chǔ)服務(wù),比如內(nèi)存計(jì)數(shù)功能)
- 層級(jí)關(guān)系明顯,子類(lèi)和父類(lèi)各做各的。如果一件事情在父類(lèi)做了,子類(lèi)又做了,那他們就是并列關(guān)系了。
- 父類(lèi)進(jìn)行了修改子類(lèi)要有所體現(xiàn),這時(shí)候父類(lèi)和子類(lèi)是耦合了的,要有耦合需求才行。(ApiManager里面判斷了網(wǎng)絡(luò)狀態(tài),所有的派生類(lèi)都是需要的。此時(shí),牽一發(fā)而動(dòng)全身,是適合繼承的)。
回答開(kāi)頭的問(wèn)題:如果繼承就可以實(shí)現(xiàn)的,為什么要用分類(lèi)呢?
因?yàn)槔^承容易造成代碼耦合,再抽取出某一個(gè)功能的時(shí)候難。特別是第三層繼承的時(shí)候就已經(jīng)屬于繼承濫用了。
繼承可以實(shí)現(xiàn)代碼復(fù)用,但是要滿(mǎn)足了以上三條才可使用。
四.繼承和分類(lèi)的區(qū)別
| 繼承 | 分類(lèi) |
|---|---|
| 繼承可以添加屬性,并且自動(dòng)生成屬性的get、set方法 | 分類(lèi)添加屬性后,需要使用runtime的關(guān)聯(lián)對(duì)象生成get、set方法 |
| 繼承的方法名如果和父類(lèi)的一樣,可以把父類(lèi)的實(shí)現(xiàn)也添加上(super) | 分類(lèi)的方法名和系統(tǒng)的一樣,會(huì)把系統(tǒng)原的方法覆蓋,無(wú)法添加上系統(tǒng)的實(shí)現(xiàn); |
- 繼承要滿(mǎn)足三大要求后使用,否則會(huì)發(fā)生高耦合;
- 如果只是增加某一個(gè)方法,可以使用分類(lèi)。(eg:針對(duì)系統(tǒng)的一些類(lèi)進(jìn)行擴(kuò)展(例如,NSString, NSArray, NSNumber))
- 大型而復(fù)雜的類(lèi),可以把利用分類(lèi),把相關(guān)的方法放到多個(gè)單獨(dú)的文件中,提高維護(hù)下和可讀性;