關(guān)鍵詞:#import、#class、class-continuation 分類、點(diǎn)語(yǔ)法(直接訪問還是通過屬性訪問)、協(xié)議等
文章是參考書籍與博客的總結(jié),自己寫下來(lái)也算是自我總結(jié),加深印象~
一、前言
在 OOP 編程中有兩個(gè)技術(shù)用于描述類或?qū)ο笈c對(duì)象之間的關(guān)系:一個(gè)是繼承;另一個(gè)是復(fù)合。在 Object-C 中 ,當(dāng)一個(gè)類需要引用另一個(gè)類,即建立復(fù)合關(guān)系時(shí),需要在類的頭文件 (h) 中,通過 #import 修飾符來(lái)建立被引用的指針。
通常情況下我們都使用 "#import" 修飾符引用類,但從代碼質(zhì)量與安全角度來(lái)看,使用 "#import" 建立復(fù)合關(guān)系時(shí),也暴露了所引用類的實(shí)體變量與方法。很多時(shí)候我們并不需要知道關(guān)于這個(gè)類的更多信息,那么只需要了解它是通過指針引用即可,同時(shí)也減少了依賴關(guān)系,也減少了重新編譯所產(chǎn)生的影響。
二者在編譯效率上也存在很大差異。對(duì)于已經(jīng)產(chǎn)生的引用,若被引用的頭文件有變化時(shí),那么引用它的類都需要重新編譯,這將耗費(fèi)大量的時(shí)間,而使用 @class 則不會(huì)。
對(duì)于初學(xué)者,使用 #import 還容易犯 "類循環(huán)依賴" 錯(cuò)誤。即兩個(gè)類互相引用對(duì)方,使用 @class 互相引用雖然不會(huì)出現(xiàn)編譯錯(cuò)誤,但還是盡量避免這種 "類循環(huán)依賴" ,因?yàn)槿菀自斐筛唏詈隙嘁蕾?,不便于維護(hù)。
二、何時(shí)使用 #import 何時(shí)使用 @class
既然 #import 有很多不足之處,但是很多情況下不得不用 #import,如在一個(gè)頭文件 (.h) 中包含多個(gè)類的聲明定義時(shí),要與該頭文件聲明的多個(gè)類建立復(fù)合關(guān)系,即所引用的類所處的文件有多個(gè)類或者多個(gè)其他的定義,使用 #import 比較好。如下:
#import <Foundation/Foundation.h>
//B 類
@interface BClass : NSObject
@end
//C 類
@interface CClass : NSObject
@end
//D 類
@interface DClass : NSObject
@end
一般來(lái)說,使用 @class 只是為了在頭文件中引用這個(gè)類,把之當(dāng)做類型來(lái)用。同時(shí),在實(shí)現(xiàn)類 .m 中 ,如果需要引用這個(gè)類的實(shí)體變量或方法等,還需要通過 #import 把在 @class 中聲明的類引用進(jìn)來(lái)。下面舉例說明一下:
#import <Foundation/Foundation.h>
@interface ClassA : NSObject
@property (nonatomic, copy) NSString *title;
@end
@import UIKit.UIViewController;
@class ClassA;
@interface ViewController : UIViewController
@property (nonatomic, strong) ClassA *aClass;
@end
#import "ViewController.h"
#import "ClassA.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"%@", self.aClass.title);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (ClassA *)aClass {
if (!_aClass) {
_aClass = [[ClassA alloc] init];
}
return _aClass;
}
@end
四、class-continuation 分類
綜上,在設(shè)計(jì)類的時(shí)候,要分清何時(shí)使用 #import 何時(shí)使用 @class,是否有必要引入頭文件等。若因?yàn)橐獙?shí)現(xiàn)屬性、實(shí)例變量或者要遵循協(xié)議而必須引入頭文件,則應(yīng)盡量將其移至“class-continuation 分類”中,這樣不僅可以縮減編譯時(shí)間,而且還能降低彼此依賴程度。下面就講講 “class-continuation 分類”。
類中經(jīng)常會(huì)包含一些無(wú)需對(duì)外公布的方法以及實(shí)例變量(當(dāng)然也可以對(duì)外公布,并寫明其為私有)。然后,我們最好還是只把確實(shí)需要對(duì)外公布的部分公開。那么不公開的怎么寫呢,其實(shí)就是用“class-continuation 分類”。
“class-continuation 分類” 和普通分類不同,它必須定義在其類的實(shí)現(xiàn)文件中,與其他分類不同,“class-continuation 分類”沒有名字。比如有個(gè)類叫 KPerson,其“class-continuation 分類” 寫法如下:
@interface KPerson()
@end
看到這里,可能突然明白,哦,原來(lái)這就是“class-continuation 分類”~
我們平時(shí)也一直這么用...
@interface ViewController ()
@end
在分類其中可以定義方法和實(shí)例變量,放在里面的相當(dāng)于隱藏起來(lái),僅供本類使用。
當(dāng)然的確可以寫在頭文件中,哪怕是將其標(biāo)注為 private,也還是會(huì)泄露實(shí)現(xiàn)細(xì)節(jié)。別人知道有個(gè)視圖寬度的屬性了?;蛘吣憧梢詫⑦@個(gè)值改為強(qiáng)類型 id,但是 id 類型不夠友好,在實(shí)現(xiàn)類中無(wú)法獲得編譯器的幫助,即輔助檢查功能,且自己也難以理解。
#import <Foundation/Foundation.h>
@interface ClassA : NSObject{
@private
NSInteger viewWidth;
}
@end
編寫 C++ 代碼時(shí)“class-continuation 分類” 尤為有用。由于游戲大多用 C++ 來(lái)寫,若把 C++ 文件的引用放在頭文件中(實(shí)現(xiàn)文件必須是 .mm 擴(kuò)展名表示編譯器應(yīng)該將此文件按 Object-C++ 編譯)。后果就是所有引入了這個(gè)引入了 C++ 文件的類,都要改為 .mm,這可能導(dǎo)致整個(gè)應(yīng)用程序都改為 .mm 了(互相引用是有可能的)。使用“class-continuation 分類”即將 C++ 的引入放到了其實(shí)現(xiàn)文件中,頭文件沒有 C++ 代碼了,使用這個(gè)頭文件的人甚至不知道其內(nèi)部混有 C++ 代碼,對(duì)外暴露的是一套純 Object-C 接口。
“class-continuation 分類”還有一種合理用法,就是將 public 接口中標(biāo)為只讀的屬性擴(kuò)展成可讀寫,以便在內(nèi)部設(shè)置其值。通過觸發(fā)鍵值觀測(cè)通知,其他對(duì)象有可能正監(jiān)聽此事件。只需要像下面的幾行代碼就行。實(shí)現(xiàn)類既可以隨意調(diào)用 setFirstName: 或 setLastName: 這兩個(gè)設(shè)置方法,也可以用點(diǎn)語(yǔ)法來(lái)設(shè)置屬性,這樣做既可以讓外界無(wú)法修改對(duì)象,又能在其內(nèi)部按照需要管理其數(shù)據(jù)。
.h 文件
#import <Foundation/Foundation.h>
@interface ClassA : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@end
.m文件
#import "ClassA.h"
@interface ClassA()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation ClassA
@end
五、關(guān)于降低類與類的耦合度
在我看來(lái),提高效率倒是其次,畢竟硬件發(fā)展這么快,這種優(yōu)化提升不大(話說回來(lái),精益求精還是需要的)。其實(shí)不管采取哪種方式都可以,首要目的還是為了降低耦合度,降低文件代碼之間的粘合性,依賴關(guān)系過于復(fù)雜會(huì)失去代碼重用性,給維護(hù)增加難度。
如果建立的復(fù)合關(guān)系過于復(fù)雜時(shí)(無(wú)論使用 #import 或 @class),這時(shí)可以考慮 2 種方式來(lái)解決: