編寫高質(zhì)量iOS與OSX代碼的52個有效方法-第三章:接口與API協(xié)議

15、使用前綴避免命名空間沖突

1、重命名符號錯誤

OC沒有其他語言內(nèi)置的命名空間(namespace),命名時要避免潛在的命名沖沖突(naming clash):


15-1.png

比如如下錯誤,就是重命名符號錯誤(duplicate symbol error)。

duplicate symbol _OBJC_CLASS_$_DogObject in:
    xxx/DogObject-ED8631F460AAA56A.o
    xxx/DogObject-917EE703FAC7406E.o
duplicate symbol _OBJC_METACLASS_$_DogObject in:
    xxx/x86_64/DogObject-ED8631F460AAA56A.o
    xxx/DogObject-917EE703FAC7406E.o
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
  • 解決方法:把錯誤中提到的duplicate symbol _OBJC_CLASS_$_DogObject類名檢查一遍,重新配置。

  • 避免方法(尤其是在引入很多三方庫,或者項目工程文件較多的時候,應(yīng)該說在所有的項目中都要如此):變相實現(xiàn)命名空間--為所有名稱都加上是當(dāng)前綴。

另,蘋果宣稱保留使用所有兩個字母的前綴(two-letter Prefix)的權(quán)利。所以自己選用前綴最好是三個字母。

2、給新增分類和分類方法加上前綴(第25條)

分類機(jī)制通常用于向無源碼的既有類中新增功能。

分類的方法是直接加到類中的,就好比是類中固有的方法,將分類方法加入類中這一操作時在運行期系統(tǒng)加載分類時完成的。運行期系統(tǒng)會把分類中所實現(xiàn)的每個方法都加入類的方法列表中。

如果類中本來就有這個方法,分類中又實現(xiàn)了一次,那么分類中方法會覆蓋原來的實現(xiàn)代碼。有可能會發(fā)生多次覆蓋。

如果多個分類名稱相同,在運行期,是不會報錯的。但是加載的分類有可能不是你所期望的。

如果有相同的方法,那么運行時調(diào)用的不一定是你想要的方法。

運行期不會報錯,但是在實現(xiàn)結(jié)果的時候,就會出現(xiàn)未知錯誤。

15-2.png

比如實現(xiàn)了NSString的兩個分類,同時都有一個分類方法"- (NSString *)urlEncordedString;"那么在調(diào)用過程中,就不知道是調(diào)用哪個分類中實現(xiàn)的方法。同樣能夠正常編譯通過。

NSString *urlString = @"http://www.baidu.com";
NSLog(@"%@",[urlString urlEncordedString]);

因為不會報錯,這種問題比較難發(fā)現(xiàn),所以在寫之前就避免這種情況就顯得非常重要。

當(dāng)然如果分類名相同,但是方法名不同時,有可能出現(xiàn)的問題是:No visible @interface for 'NSString' declares the selector 'seconString',你無法調(diào)用自己實現(xiàn)的方法。系統(tǒng)只是提供最后加載到的分類,如此而已。

  • 如何避免:添加分類時,給分類名稱加上專用前綴,同時給分類方法名加上專用前綴。

3、類的實現(xiàn)文件中所用的純C函數(shù)及全局變量

在類的實現(xiàn)文件中所用的純C函數(shù)及全局變量,在編譯好的目標(biāo)文件中,這些名稱要算作“頂級符號”(top-level symbol)。

如果在不同的類文件中實現(xiàn)同樣的C函數(shù),就會報重命名符號錯誤(duplicate symbol error)

15-3.png
duplicate symbol _completion in:
    xxx/ViewController.o
    xxx/NSString+Http.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

duplicate symbol _completion會指出錯誤方法名completion,另外會在下面的描述中指明在那些文件中出現(xiàn)沖突。

  • 解決方法:找到錯誤的類和方法名,修改。

  • 避免方法:C函數(shù)名加前綴,同時加上類名信息,在回溯查找問題是就能很快確定位置。

同樣的,即使在實現(xiàn)文件中聲明全局靜態(tài)變量,在不同文件中聲明相同名稱的變量,也會出現(xiàn)名稱沖突錯誤:

15-4.png
duplicate symbol _NameString in:
    xxx/ViewController.o
    xxx/NSString+Http.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
  • 解決方法:找到錯誤的類和聲明的變量名,修改。

  • 避免方法:聲明全局變量,變量名前加前綴。

4、所開發(fā)的程序庫中用到第三方庫,給第三方庫加前綴

這個問題很簡單,如果要把自己封裝的程序庫給別人用,同時使用了不同的第三方庫。那么在別人引用的時候,如果他工程中也使用相同的第三方庫,就會出現(xiàn)重命名符號錯誤。

另外考慮到所使用第三方庫版本不同,那么,在封裝自己的程序庫時就要將所用到的第三方庫中的文件添加前綴,避免此類問題。

16、提供全能初始化方法

全能初始化方法(designated initializer):為對象提供必要信息以便其能完成工作的初始化方法。

可以通過警告或者設(shè)置默認(rèn)值調(diào)用全能初始化方法的方式,實現(xiàn)初始化。

全能初始化方法的調(diào)用鏈一定要維系,也即是,集成關(guān)系中,初始化方法的維護(hù)調(diào)用。

Mac OS X 的APPKit會iOS的UIKit兩個UI框架都廣泛運用序列化機(jī)制(serialization mechanism),將對象序列化,保存至XML格式的XIB文件中農(nóng)。這些XIB文件通常用來存放視圖控制器機(jī)器視圖布局。加載NIB文件時,系統(tǒng)會在解壓縮的過程中解碼視圖控制器。

  • 在類中提供一個全能初始化方法,并于文檔里指明。其他初始化方法均應(yīng)調(diào)用此方法。

  • 若全能初始化方法與超類不同,則需要覆寫超類中的對應(yīng)方法。

  • 若超類初始化方法不適用于子類,那么應(yīng)該覆寫這個超類方法,并在其中拋出異常。

17、實現(xiàn)description方法

實現(xiàn)description方法返回一個有意義的字符串,用以描述該實例。

- (NSString *)description {
    return [NSString stringWithFormat:@"%@ %zd",_dogName,_dogAge];
}

若想在調(diào)試時(LLDB)打印出更詳盡的對象描述信息,則應(yīng)實現(xiàn)debugDescription方法。

- (NSString *)debugDescription {
    return [NSString stringWithFormat:@"<%@: %p \"%@ %zd \">",[self class],self,_dogName,_dogAge];
}

打印結(jié)果

17-1.png

18、盡量使用不可變對象 --

關(guān)聯(lián)第6條-屬性

盡量減少對象中的可變內(nèi)容,應(yīng)該盡量把對外公布出來的屬性設(shè)為只讀,并且只在必要時才將屬性對外公布。

如果想要修改封裝在對象內(nèi)部的數(shù)據(jù),同時不將哲學(xué)數(shù)據(jù)為外人所動,可以在對象內(nèi)部將readonly屬性重新聲明為readwrite。

在定義類的公共API時,對象里表示各種collection的那些屬性究竟應(yīng)該設(shè)成可變的,還是不可變的。

  • 盡量創(chuàng)建不可變的對象
  • 若某屬性僅可于對象內(nèi)部修改,則在class-continuation分類中將其由readonly擴(kuò)展為readwrite。
  • 不要把可變的collection作為屬性公開,而應(yīng)提供相關(guān)方法,以此修改對象中的可變collection。

19、使用清晰協(xié)調(diào)的命名方式

OC中一般采用駝峰式大小寫命名法。

1)方法命名

  • 如果方法的返回值是新創(chuàng)建的,那么方法名的首個詞應(yīng)是返回值的類型,除非前面還有修飾語,如localizedString。屬性的存取方法不遵循這種命名方式,一邊惹味這種方法不會創(chuàng)建新對象,即使又是返回內(nèi)部對象的一份拷貝,也認(rèn)為那相當(dāng)于原有的對象。這些存取方法應(yīng)按照其所對應(yīng)的屬性來命名。
- (NSString *)stringOfDogInfomation;

- (NSDictionary *)dictionaryOfDogInfomation;
  • 應(yīng)該把表示參數(shù)類型的名詞放在參數(shù)前面。

  • 如果方法要在對象上執(zhí)行操作,就應(yīng)包含動詞,若執(zhí)行操作還需要參數(shù),應(yīng)在動詞后面加上一個或多個名詞。

  • 不要使用str這樣的簡稱,而用string這樣的全稱。

[string lowercaseString];
  • Boolean屬性應(yīng)加is前綴,如果方法返回非屬性的Boolean值,那么應(yīng)該根據(jù)其功能,選用has或is前綴。

[string hasSuffix:@"this"];
[string isEqualToString:@"xxxx"];

  • get前綴留給那些借由輸出參數(shù)來保存返回值的方法,

清晰明了,統(tǒng)一規(guī)范

2)類與協(xié)議的命名

應(yīng)該為類和協(xié)議加上前綴,避免命名空間沖突。

命名方式要協(xié)調(diào)一致,如果要從其他框架中繼承子類,務(wù)必遵循其命名慣例。

若要自定義委托協(xié)議,則名稱中應(yīng)包含委托發(fā)起方的名字,再加上Delegate

  • 起名時,遵從標(biāo)準(zhǔn)的OC命名規(guī)范
  • 方法名要言簡意賅,從左只有讀起來想個日常用語中的句子。
  • 方法名里不要使用縮略后的類型名稱
  • 方法起名,確保風(fēng)格與自己的代碼或所要集成的框架相符。

20、為私有方法名加前綴

為在內(nèi)部使用的私有方法加前綴,區(qū)分公共方法和私有方法,便于修改方法名和方法簽名。

依據(jù)個人習(xí)慣,p_method。

  • 在私有方法名稱前加上前綴,區(qū)分私有和公共方法。
  • 不要單用一個下劃線做私有方法的前綴,因為這種是蘋果公司預(yù)留的。

21、理解OC錯誤模型

1)異常 exception --fatal error致命錯誤

OC中,在激起罕見的情況下拋出異常,異常拋出之后不再考慮恢復(fù)問題,應(yīng)用程序此時應(yīng)該退出。不需要再辨析復(fù)雜的“異常安全”代碼。

異常一般只用于處理嚴(yán)重錯誤(fatal error 致命錯誤)。

@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:string userInfo:nil];

比如編寫某個抽象基類,正確用法是先從中集成一個子類,再用這個子類。這種情況下,如果直接使用了這個抽象基類的,那么可以拋出異常。

OC沒有辦法將某個類標(biāo)記為“抽象類”。要想達(dá)成效果,最好的辦法是在那些子類必須覆寫的超類方法里拋出異常。

2)其他錯誤 --nonfatal error 非致命錯誤

OC語言所用編程范式為:令方法返回nil/0,或是使用NSError,表明有錯誤發(fā)生。

NSError對象封裝了三條消息:

  • Error domain(錯誤范圍,類型為字符串)
    錯誤發(fā)生的范圍,也就是產(chǎn)生錯誤的根源,通常用一個特有的全局變量來定義。如NSURLErrorDomain-處理URL的子系統(tǒng),在從URL中解析或取得數(shù)據(jù)出錯。

  • Error code 錯誤碼,其類型為整數(shù)
    獨有的錯誤代碼,用以指明在某個范圍內(nèi)具體發(fā)生了何種錯誤。某個特定范圍內(nèi)可能會發(fā)生一些列相關(guān)錯誤,這些錯誤情況通常采用enum來定義。如,HTTP請求出錯時,會把HTTP狀態(tài)碼設(shè)為錯誤碼。

  • User info 用戶信息,其類型是字典
    有關(guān)次錯誤的額外信息,其中或許包含一段本地化的描述(localized description),或許還包含導(dǎo)致該錯誤發(fā)生的另一個錯誤,經(jīng)由此種信息,可將相關(guān)錯誤串成一條錯誤連(chain of errors)

NSError的用法:

  • 通過委托協(xié)議來傳遞錯誤。
    當(dāng)有錯誤發(fā)生時,當(dāng)前對象會把錯誤信息經(jīng)由協(xié)議的某個方法傳遞給其委托對象(delegate)。如NSURLConnection在其委托協(xié)議NSURLConnectionDelegate中定義代理方法- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

  • 經(jīng)由方法的輸出參數(shù)返回給調(diào)用者。

- (BOOL)doSomething:(NSString *)thing error:(NSError **)error {
    if ([thing isEqualToString:@"1"]) {
        return YES;
    }
    *error = [NSError errorWithDomain:NSURLErrorDomain code:100 userInfo:@{@"key":@"something wrong"}];
    return NO ;
}
NSError *error;
BOOL ret  = [littleDog doSomething:@"0" error:&error];
if (!ret) {
    NSLog(@"error : %@",[error debugDescription]);
}

另外,定義自己的指定的專用錯誤范圍字符串,使用這個字符串創(chuàng)建NSError對象,就能確定錯誤來源。

extern NSString *const ZYDErrorDomain;

typedef NS_ENUM(NSUInteger,ZYDError) {
    ZYDErrorUnknown                 = -1, //未知錯誤
    ZYDErrorBadInput                = 500,
};

3)

  • 只有發(fā)生了可使整個應(yīng)用程序崩潰的嚴(yán)重錯誤時,才應(yīng)使用異常。
  • 一般錯誤,可使用指派委托方法來處理錯誤,也可以把錯誤信息放在NSError對象中,經(jīng)由輸出參數(shù)返回給調(diào)用者。

22、理解NSCopying協(xié)議

1)不可變拷貝 NSCopying

OC 中如果需要拷貝對象,需要通過copy方法完成。如果希望自己的類支持拷貝操作,就要實現(xiàn)NSCopying協(xié)議。該協(xié)議只要一個方法:

- (id)copyWithZone:(NSZone *)zone;

在以前開發(fā)程序時,會據(jù)此吧內(nèi)存分成不同的區(qū)(zone),而對象會創(chuàng)建在某個區(qū)里面?,F(xiàn)在不用,每個程序只有一個區(qū):默認(rèn)區(qū)(default zone)。所以需要實現(xiàn)這個方法,但是不用擔(dān)心zone參數(shù)。

若要某個類支持拷貝功能,需要改類聲明遵從NSCoping協(xié)議,并實現(xiàn)其中的方法就可以。

.h
@interface DogObject : NSObject <NSCopying>

@end
.m

@interface DogObject()
{
    NSMutableArray *_familys;//內(nèi)部成員變量,并非屬性
}
@end

@implementation DogObject

- (instancetype)initWithDogName:(NSString *)dogName age:(NSInteger)age {
    self = [super init];
    
    if (self) {
        _dogName = [dogName copy];
        _dogAge = age;
    }
    return self;
}

#pragma mark -- NSCopying
- (id)copyWithZone:(NSZone *)zone {
    DogObject *copy = [[[self class] allocWithZone:zone] initWithDogName:_dogName age:_dogAge];
    copy -> _familys = [_familys mutableCopy]; //有
    return copy;
}

2)可變拷貝 NSMutableCopying

定義一個方法:
- (id)mutableCopyWithZone:(NSZone *)zone,與copy類似,也用默認(rèn)的zone參數(shù)來調(diào)mutableCopyWithZone:。如果類分為可變版本,可不可變版本,需要實現(xiàn)NSMutableCopying。

3)深拷貝

深拷貝:在拷貝對象自身是,將其底層數(shù)據(jù)也一并復(fù)制過去。

淺拷貝:之拷貝容器對象本身,而不復(fù)制漆黑中的數(shù)據(jù)。

容易內(nèi)的對象并不都能拷貝,而且調(diào)用者也未必要在拷貝容器的同時一并拷貝其中的每個對象。

一般NSCopying大多數(shù)情況下執(zhí)行的是淺拷貝,如需要在對象上執(zhí)行深拷貝,那么除非該類的文檔說它是用深拷貝來實現(xiàn)NSCopying協(xié)議的,否則,要么尋找能夠執(zhí)行深拷貝的方法,要么自己編寫方法來實現(xiàn)。

比如,NSArray中的方法:- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag如果flag為YES,該方法會向數(shù)組中每個元素發(fā)送copy信息,用拷貝好的創(chuàng)建新的Array,并返回給調(diào)用者。

可以給對象創(chuàng)建自定義深拷貝方法:

- (id)deepCopy {
    DogObject *copy = [[[self class] alloc] initWithDogName:_dogName age:_dogAge];
    copy -> _familys = [[NSMutableArray alloc] initWithArray:_familys copyItems:YES];
    return copy;
}

4)

  • 令自己所寫的對象具有拷貝功能,需要實現(xiàn)NSCopying協(xié)議
  • 如果自定義對象分為可變和不可變版本,需要同時實現(xiàn)NSCopying與NSMutableCopying協(xié)議。
  • 復(fù)制對象時需決定采用淺拷貝還是深拷貝,一般情況下盡量執(zhí)行淺拷貝
  • 如果對象需要深拷貝,那么新增一個專門執(zhí)行深拷貝的方法。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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