分類(lèi)、類(lèi)擴(kuò)展、繼承的總結(jié)

因?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)

  1. 運(yùn)行時(shí)決議;
  2. 可以為系統(tǒng)類(lèi)添加分類(lèi);

2.分類(lèi)中可以添加什么內(nèi)容?

(面試考點(diǎn))


分類(lèi)的結(jié)構(gòu)體
  1. 實(shí)例方法
  2. 類(lèi)方法
  3. 協(xié)議
  4. 屬性(@proterty修飾的)(未生成get、set方法,但是可以通過(guò)runtime添加get、set方法)。
    注意:分類(lèi)不可以添加成員變量;
    分類(lèi)可以訪問(wèn)原有類(lèi)中.h的屬性
效果
  1. 每個(gè)分類(lèi)在編譯的時(shí)候都是生成一個(gè)名叫catrgort_t的結(jié)構(gòu)體;

3. Category的實(shí)現(xiàn)原理

  1. 每一個(gè)分類(lèi)編譯之后生成一個(gè) struct category_t。里面存儲(chǔ)著分類(lèi)的對(duì)象方法、類(lèi)方法、屬性、協(xié)議信息;
  2. 把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中;后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面。
  3. 將合并后的分類(lèi)數(shù)據(jù)(方法、屬性、協(xié)議),插入到類(lèi)原來(lái)數(shù)據(jù)的前面

或者說(shuō):

  1. Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲(chǔ)著分類(lèi)的對(duì)象方法、類(lèi)方法、屬性、協(xié)議信息
  2. 在程序運(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ì)象里面。

  1. 有NSString+other2、NSString+other、NSString+other1三個(gè)分類(lèi),添加順序如下圖;
  2. 合并的時(shí)候:A數(shù)組里面放著NSString+other2、NSString+other、NSString+other1的對(duì)象方法。
  3. NSString的類(lèi)對(duì)象里面,存放對(duì)象方法的list里面,先把NSString的對(duì)象方法挪到最后一個(gè)位置。再把A數(shù)組里面的方法,從后往前添加到對(duì)象方法列表里面。所以相同的方法名,最后一個(gè)分類(lèi)里面的方法先被調(diào)用。


    添加分類(lèi)
分類(lèi)添加的順序

4.分類(lèi)執(zhí)行順序

  1. 如果方法名和原有類(lèi)里面的方法相同的話(huà),會(huì)覆蓋系統(tǒng)的方法;執(zhí)行順序:分類(lèi)>本類(lèi)>父類(lèi)。所以盡量不要和原有類(lèi)的方法重名。

  2. 如果不同的分類(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)
  1. 名字相同的分類(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í)例如下:

  1. 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
  1. 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
  1. 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ā)。

如下:

  1. 有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é):

  1. 可以添加屬性、成員變量,都是私有的。(只能自身類(lèi)里面使用,子類(lèi)或者其他方法不可用)
  2. 可以添加方法,但是如果方法不實(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)里面的;
  1. 類(lèi)擴(kuò)展沒(méi)有自己的實(shí)現(xiàn)部分(@implementation),必須依靠對(duì)應(yīng)類(lèi)的實(shí)現(xiàn)部分來(lái)實(shí)現(xiàn);
  2. 定義在.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):

  1. 父類(lèi)只是提供了服務(wù)。父類(lèi)和子類(lèi)沒(méi)有業(yè)務(wù)關(guān)系;(Object提供了基礎(chǔ)服務(wù),比如內(nèi)存計(jì)數(shù)功能)
  2. 層級(jí)關(guān)系明顯,子類(lèi)和父類(lèi)各做各的。如果一件事情在父類(lèi)做了,子類(lèi)又做了,那他們就是并列關(guān)系了。
  3. 父類(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ù)下和可讀性;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容