4.類別和擴(kuò)展、協(xié)議和委托

題記:Each man is the architect of his own fate.——每個(gè)人都是自己命運(yùn)的建筑師。
今天要復(fù)習(xí)的是類別和擴(kuò)展、協(xié)議和委托,這些在我們編寫的應(yīng)用程序中會(huì)經(jīng)常用到,我想大家都會(huì)使用,在這里主要復(fù)習(xí)一下它們的原理,我們要不僅會(huì)用,還要理解它為什么這么用。

首先講一下類別和擴(kuò)展的聯(lián)系和區(qū)別

類別和擴(kuò)展


1. 類別

(1)為已有的類添加新的方法
(2)可以在類別中添加新屬性,但是不能為類別添加新的實(shí)例變量。

  • 這里需要注意的是:可以在類別中添加新屬性,但是類別中無法把實(shí)現(xiàn)屬性所需的實(shí)例變量及其存取方法合成出來,此時(shí)需要在類別中將屬性的存取方法聲明為@dynamic,即這些方法等到程序運(yùn)行時(shí)再提供,如果不添加@dynamic聲明屬性,編輯器將發(fā)出警告如下圖:

    不添加`@dynamic`聲明屬性時(shí)的警告

  • 解決
    將接口文件中聲明的屬性,在實(shí)現(xiàn)文件中聲明為@dynamic,即@dynamic myFriend;,此時(shí)意味著在程序運(yùn)行時(shí)再為為myFriend屬性添加存取方法,代碼如下:

// 示例代碼
// 接口文件代碼
@interface NSObject (Friend)

@property(nonatomic, copy) NSString *myFriend;
-(void)testMyFriend;
@end

// 實(shí)現(xiàn)文件的代碼
#import "NSObject+Friend.h"
@implementation NSObject (Friend)
@dynamic myFriend;
-(void)testMyFriend{
    self.myFriend = @"Lynn";
    NSLog(@"%@",self.myFriend);
}
@end

還有一種解決策略就是直接在類的實(shí)現(xiàn)文件中添加該屬性的存取方法,但是始終無法合成實(shí)例變量

#import "NSObject+Friend.h"
#import <objc/message.h>

@implementation NSObject (Friend)
-(void)setMyFriend:(NSString *)myFriend{
  objc_setAssociatedObject(self,"abc",myFriend,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSString *)myFriend{
    return objc_getAssociatedObject(self,"abc");
}
-(void)testMyFriend{
    self.myFriend = @"Lynn";
    NSLog(@"%@",self.myFriend);
}
@end

(3)缺陷:類別有兩個(gè)局限
-1. 無法向類中添加實(shí)例變量,類別中沒有空間容納實(shí)例變量。
-2. 存在名稱沖突問題,即類別中的實(shí)力方法與現(xiàn)有方法重名,類別具有更高優(yōu)先級(jí),造成方法覆蓋。

(4) 優(yōu)勢:
-1.可以將類的實(shí)現(xiàn)代碼放到不同的文件或框架中,需要的時(shí)候引用特定的文件即可。
-2.可以創(chuàng)建對私有方法的前行引用。

這里想要解釋一下什么是前向引用。
我們總說在OC中并沒用真正的私有方法,那在OC中又是如何實(shí)現(xiàn)對私有方法的調(diào)用的呢?
這里有兩種方法實(shí)現(xiàn)對私有方法的調(diào)用:
-(1) 通過performSelector方法在程序運(yùn)行時(shí)動(dòng)態(tài)調(diào)用,避開編譯器的檢查(在文章的最后講到一些performSelector方法的一些特性)
-(2) 通過類別定義前向引用,即類A中定義的私有方法是無法直接訪問的,但是可以通過創(chuàng)建類A的一個(gè)類別B,在類別B的接口文件中聲明這個(gè)私有方法,當(dāng)需要調(diào)用這個(gè)私有方法是,在那個(gè)實(shí)現(xiàn)文件中引入該類別的頭文件即可。


2. 擴(kuò)展
  1. 擴(kuò)展與類別類似,其實(shí)就相當(dāng)與匿名的類別
  2. 擴(kuò)展可用于臨時(shí)對某個(gè)類的借口進(jìn)行擴(kuò)展,在類的實(shí)現(xiàn)文件中定義,擴(kuò)展的定義如下:
// 在類的實(shí)現(xiàn)文件中
@interface XUCenterViewController ()
// 定義私有示例變量、屬性和方法
@end

類的實(shí)現(xiàn)文件同時(shí)實(shí)現(xiàn)類接口中聲明的方法和擴(kuò)展中定義的方法。


接下來講一下協(xié)議和委托,但還需要指出的是協(xié)議還可以進(jìn)一步劃分,分成正式協(xié)議和非正式協(xié)議

協(xié)議與委托


1. 協(xié)議
  1. 什么是協(xié)議
    協(xié)議的作用與類別類似,都是用于對類的擴(kuò)展。
    一提到協(xié)議,總會(huì)出現(xiàn)另一個(gè)詞與之緊密相連,就是規(guī)范。
    協(xié)議是用于定義多個(gè)類應(yīng)該遵守的規(guī)范,并不提供實(shí)現(xiàn)的細(xì)節(jié),協(xié)議中的方法的實(shí)現(xiàn)留給遵守它的類實(shí)現(xiàn),巧妙的將規(guī)范和實(shí)現(xiàn)分離,實(shí)現(xiàn)松耦合。

  2. 非正式協(xié)議
    什么是非正式協(xié)議呢?
    非正式協(xié)議的實(shí)現(xiàn)離不開類別,可以使用類別實(shí)現(xiàn)非正式協(xié)議,實(shí)現(xiàn)非正式協(xié)議的類別需要滿足是NSObject的類別,在這個(gè)類別的接口文件中聲明方法,但不需要實(shí)現(xiàn),當(dāng)某個(gè)類實(shí)現(xiàn)該NSObject的類別時(shí),可以在實(shí)現(xiàn)文件中實(shí)現(xiàn)該類別中聲明的方法,此時(shí)該NSObject的類別就可以視為非正式協(xié)議。
    示例代碼如下:

// 分類的接口文件
@interface NSObject (Friend)
// 未實(shí)現(xiàn)
-(void)testUnformal;
@end

// 繼承分類的TestUnform類的接口
#import "NSObject+Friend.h"
@interface TestUnform : NSObject
@end

// TestUnform類的實(shí)現(xiàn)
#import "TestUnform.h"
@implementation TestUnform
-(void)testUnformal{
    NSLog(@"這是非正式協(xié)議");
}
@end

此時(shí)Friend分類就是非正式協(xié)議

3. 正式協(xié)議

(1)正式協(xié)議具有繼承性,可以繼承其他正式協(xié)議,不能繼承類,支持多繼承,協(xié)議中的方法均是共有的方法,只有方法的聲明,沒有實(shí)現(xiàn),實(shí)現(xiàn)部分交給遵守它的類來完成。

@protocol TestUnformProtocal <NSObject>

// 聲明方法

@end

(2)協(xié)議的實(shí)現(xiàn)
如果自定義的類希望遵守某個(gè)協(xié)議,此時(shí)需要在自定義類的接口部分或擴(kuò)展部分遵守該協(xié)議,支持同時(shí)遵守多個(gè)協(xié)議

// 接口文件中
#import "NSObject+Friend.h"
@protocol TestUnformProtocal <NSObject>
// 聲明方法
@end

@interface TestUnform : NSObject<TestUnformProtocal>
@end
// 實(shí)現(xiàn)文件中實(shí)現(xiàn)協(xié)議中的聲明的方法

// 或在實(shí)現(xiàn)文件中的擴(kuò)展中遵守該協(xié)議
#import "TestUnform.h"
@interface TestUnform ()<TestUnformProtocal>

@end

@implementation TestUnform
// 實(shí)現(xiàn)協(xié)議中的聲明的方法
@end

(3)協(xié)議中的關(guān)鍵字 @require@optioanal
關(guān)鍵字 @require:自定義類遵守該協(xié)議后,必須實(shí)現(xiàn)
關(guān)鍵字 @optioanal:自定義類遵守該協(xié)議后,非必須實(shí)現(xiàn),根據(jù)需要有選擇的實(shí)現(xiàn)

4. 委托與代理

使用委托與代理的方法,可以實(shí)現(xiàn)視圖、控制器間的數(shù)據(jù)傳輸。實(shí)現(xiàn)的思想就是:
(1)定義一個(gè)正式協(xié)議
(2)自定義類ClassA的接口文件中聲明一個(gè)遵守協(xié)議的變量
@property(nonatomic, weak) id<TestUnformProtocal> delegate;
(3)在另一個(gè)自定義ClassB類中定義一個(gè)ClassA的屬性objA,在ClassB類中設(shè)置ClassA類的代理為ClassB,即objA.delegate = self;
(4)讓ClassB類遵守TestUnformProtocal協(xié)議,并實(shí)現(xiàn)TestUnformProtocal協(xié)議中定義的方法
(5)這時(shí)就可以在ClassA類的實(shí)現(xiàn)文件中調(diào)用這個(gè)在ClassB類中實(shí)現(xiàn)的協(xié)議的方法,ClassA類中

if([self.delegate respondsToSelector:@selector(/*代理方法*/)]){
        [self.delegate /*代理方法*/];//調(diào)用代理方法
    }

performSelector方法

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

這里的aSelector參數(shù)是一個(gè)選擇子,就是一個(gè)方法名,這個(gè)選擇子在運(yùn)行時(shí)決定,在ARC機(jī)制下使用performSelector方法可能存在內(nèi)存泄漏,是因?yàn)榫幾g器并不知道將要調(diào)用的選擇子是什么,無法運(yùn)用ARC的內(nèi)存管理規(guī)則來判斷,所以在編譯時(shí)不添加釋放的操作。
對于返回值的類型只能是void或?qū)ο箢愋?,允許傳遞的參數(shù)的個(gè)數(shù)也存在局限性(0到2個(gè))


總結(jié)

類別和擴(kuò)展、協(xié)議和委托,實(shí)現(xiàn)都不是很難,放在一起要梳理好各部分的知識(shí),搞清楚弄明白才能運(yùn)用自如。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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