題記: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ò)展
- 擴(kuò)展與類別類似,其實(shí)就相當(dāng)與匿名的類別
- 擴(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é)議
什么是協(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)松耦合。非正式協(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)用自如。
