15、使用前綴避免命名空間沖突
1、重命名符號錯誤
OC沒有其他語言內(nèi)置的命名空間(namespace),命名時要避免潛在的命名沖沖突(naming clash):

比如如下錯誤,就是重命名符號錯誤(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)未知錯誤。

比如實現(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)

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)名稱沖突錯誤:

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é)果

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í)行深拷貝的方法。