級別: ★★☆☆☆
標(biāo)簽:「iOS」「OC」「Objective-C」
作者: MrLiuQ
審校: QiShare團(tuán)隊(duì)
前言:
這幾篇文章是小編在鉆研《Effective Objective-C 2.0》的知識產(chǎn)出撰茎,其中包含作者和小編的觀點(diǎn)嵌牺,以及小編整理的一些demo。希望能幫助大家以簡潔的文字快速領(lǐng)悟原作者的精華。
在這里逆粹,QiShare團(tuán)隊(duì)向原作者M(jìn)att Galloway表達(dá)誠摯的敬意募疮。
文章目錄如下:
iOS 編寫高質(zhì)量Objective-C代碼(一)
iOS 編寫高質(zhì)量Objective-C代碼(二)
iOS 編寫高質(zhì)量Objective-C代碼(三)
iOS 編寫高質(zhì)量Objective-C代碼(四)
iOS 編寫高質(zhì)量Objective-C代碼(五)
iOS 編寫高質(zhì)量Objective-C代碼(六)
iOS 編寫高質(zhì)量Objective-C代碼(七)
iOS 編寫高質(zhì)量Objective-C代碼(八)
這一篇,將通過介紹OC的接口和API設(shè)計(jì)
來提高Objective-C的代碼質(zhì)量
一僻弹、用前綴避免命名空間沖突
OC里沒有命名空間的概念(
namespace
)阿浓。于是,我們需要給類加前綴蹋绽,避免重名芭毙,避免發(fā)生命名沖突。當(dāng)然卸耘,不僅是類名退敦,一些全局變量和方法也需要加上適當(dāng)?shù)那熬Y加以區(qū)分。
所以蚣抗,我們要:
選擇與公司侈百、工程相關(guān)的前綴作為類名的前綴。
為了避免重復(fù)引用第三方庫帶來的沖突忠聚,必要時也要為他們加上前綴區(qū)分设哗。
二、提供“全能初始化方法”
- 在類中提供一個
全能初始化方法
两蟀,并在文檔中寫明注釋网梢。其他的初始化方法全調(diào)用此全能初始化方法。 - 好處:當(dāng)類的結(jié)構(gòu)發(fā)生改變或初始化邏輯發(fā)生改變時赂毯,只需要改動全能初始化方法即可战虏。
舉個例子來說:可以看一下NSDate類中定義了一個全能初始化方法:
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
其余的初始化方法 定義在NSDate (NSDateCreation) 分類中
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
在官方文檔中,關(guān)于NSDate有如下說明
If you want to subclass NSDate to obtain behavior different than that provided by the private or public subclasses, you must:
- Override
initWithTimeIntervalSinceReferenceDate:
, one of the designated initializer methods
解釋:選定一個方法作為全能初始化方法党涕,剩下的其余的初始化方法都調(diào)用這這個方法初始化烦感,這樣做的好處是以后如果初始化的邏輯更改了只需更改全能初始化方法,或者即使子類覆寫的時候也只覆寫全能初始化方法~
三膛堤、實(shí)現(xiàn) description 方法
本條寫的是通過覆寫
description
(或者debugDescription
)方法來在NSLog打印
(或者LLDB打印
時)輸出更多的自定義信息手趣。
下面舉個例子:
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, %@>",
[self class],
self,
@{
@"qi": _qi,
@"share" : _share}
];
}
四、盡量使用不可變對象
- 聲明對外屬性時肥荔,盡量使用不可變對象绿渣,同時,對外屬性聲明里盡量加上
readonly
修飾(默認(rèn)是readwrite修飾)燕耿。這樣外部只能讀取數(shù)據(jù)而不能修改數(shù)據(jù)中符,保證了這個類的實(shí)例所持有的數(shù)據(jù)更加安全。尤其是不要把可變的collection作為屬性公開誉帅,而是應(yīng)該提供相應(yīng)的方法修改可變的collection淀散。 - 若外部想修改修改對象的值有兩種途徑:
- 提供
接口方法
修改 - 使用
KVC(Key-Value Coding)
技術(shù)
這種技術(shù)允許對象的數(shù)據(jù)或?qū)傩钥梢栽谶\(yùn)行時通過其鍵名進(jìn)行查找右莱,其中,屬性的名稱即為其值的鍵名档插。在靜態(tài)語言中慢蜓,這樣的做法是不可能的。KVC大大的增加了設(shè)計(jì)的自由度:通過KVC阀捅,無需知道對象的類型即可訪問其屬性或數(shù)據(jù)胀瞪。
- 提供
例如:
不推薦寫法:
//Animals.h
@property (nonatomic, strong) NSSet *animals;
應(yīng)改為:
//Animals.h
@interface Animals : NSObject
@property (nonatomic, strong, readonly) NSSet *animals;
- (void)addAnimal:(NSString *)animal;
- (void)removeAnimal:(NSString *)animal;
@end
//Animals.m
@implementation Animals {
NSMutableSet *_mutableAnimals;
}
- (NSSet *)animals {
return [_mutableAnimals copy];
}
- (void)addAnimal:(NSString *)animal {
[_mutableAnimals addObject:animal];
}
- (void)removeAnimal:(NSString *)animal {
[_mutableAnimals removeObject:animal];
}
但是针余,小編認(rèn)為這樣寫固然有好處:保證了數(shù)據(jù)的安全性饲鄙,但代碼量也會提升不少。所以推薦大家可以有選擇的使用圆雁,對一些重要的類才有使用必要忍级。
另外,如果某屬性僅可以在對象內(nèi)部修改伪朽,則可以在.h
文件中聲明為readonly
轴咱。然后 在.m的類擴(kuò)展
中將屬性擴(kuò)展為readwrite
屬性。
當(dāng)然烈涮,師父說了:也可以在.h
文件中把屬性聲明為readonly
朴肺,在.m
文件中通過實(shí)例變量
修改值,當(dāng)block內(nèi)部修改值時坚洽,可以用self->實(shí)例變量
的方法訪問修改戈稿。(小編測試過,確實(shí)有效讶舰。歡迎路過的大神繼續(xù)討論)
五鞍盗、使用清晰而協(xié)調(diào)的命名方式
師父語錄:“寫OC代碼像是在講故事,而讀OC代碼更像是在聽故事跳昼“慵祝”
這句話要?dú)w功于OC清晰而協(xié)調(diào)的命名方式。
- 首先鹅颊,是駝峰式命名方法:這個和大部分編程語言都一樣敷存。
- 其次,也是最關(guān)鍵的方法命名堪伍。從左至右讀起來就像日常用語中的句子锚烦。
例如:我們想給初始化一個矩形,并給他的寬和高賦值杠娱。
// C++:
Rectangle *aRectangle = new Rectangle(5.0, 10.0);
// Objective-C:
Rectangle *aRectangle = [[Rectangle alloc] initWithWidth:5.0 andHeight:10.0];
很顯然挽牢,OC的方法可以很直接的看出所要傳遞的參數(shù)的具體含義,而C++的傳參就并沒有這么直觀摊求。
六禽拔、為私有方法名加前綴
這一條:給大家參考一下我們QiShare團(tuán)隊(duì)制定的 iOS 代碼規(guī)范
QiShare更喜歡通過#pragma mark -
來區(qū)分 公私有等方法
。
例如:
#pragma mark - Private Functions
// code...
#pragma mark - Action functions
// code...
#pragma mark - Request functions
// code...
#pragma mark - xxxDataSource
// code...
#pragma mark - xxxDelegate
// code...
當(dāng)然,大家也可以根據(jù)團(tuán)隊(duì)自己定制規(guī)范睹栖。
七硫惕、理解 Objective-C 錯誤模型
很多語言都有異常處理機(jī)制,Objective-C也不例外野来。@throw
但是恼除,
注意:OC里拋異常很可能會導(dǎo)致內(nèi)存泄漏
注意:OC里拋異常很可能會導(dǎo)致內(nèi)存泄漏
注意:OC里拋異常很可能會導(dǎo)致內(nèi)存泄漏
解釋:OC里的ARC機(jī)制(Automatic Reference Counting)在默認(rèn)情況下是“無異常安全”。簡單來說曼氛,一旦拋出異常豁辉,對象很可能就無法正常自動釋放了。
所以舀患,
- 異常只用于處理嚴(yán)重的錯誤(fatal error徽级,致命錯誤)
- 對于一些不那么嚴(yán)重的錯誤(nonfatal error,非致命錯誤)聊浅,有兩種解決方案:
- 讓對象返回
nil
或者0
(例如:初始化的參數(shù)不合法餐抢,方法返回nil或0) - 使用
NSError
- 讓對象返回
八、理解 NSCopying協(xié)議
在iOS開發(fā)中低匙,使用對象時經(jīng)常需要拷貝它旷痕,這時我們會通過copy/mutableCopy
方法完成。
如果我們想讓自己的類支持拷貝顽冶,那就必須要實(shí)現(xiàn)NSCopying
協(xié)議欺抗,該協(xié)議只有一個方法:
- (id)copyWithZone:(nullable NSZone *)zone;
當(dāng)然,如果要求返回對象是可變的那就要實(shí)現(xiàn)NSMutableCopying
協(xié)議渗稍,對應(yīng)方法:
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
同時佩迟,在拷貝對象時,要注意是執(zhí)行淺拷貝還是深拷貝
那么引出了一個概念:什么是深拷貝竿屹?什么是淺拷貝报强?
- 深拷貝:內(nèi)容拷貝(既拷貝新的
指針
又拷貝出新的Object
) - 淺拷貝:指針拷貝(僅拷貝新的
指針
指向原來的Object
)
這里有張很經(jīng)典的圖解:
深拷貝在拷貝對象時拱燃,會將指針?biāo)傅牡讓訑?shù)據(jù)也拷貝一份秉溉。而淺拷貝只是創(chuàng)建了一個新的指針指向要拷貝的內(nèi)容。一般情況下碗誉,盡量使用淺拷貝召嘶。
此外,還有一個注意點(diǎn):
[NSMutableArray copy]
拷貝出 => NSArray
(不可變)[NSArray mutableCopy]
拷貝出 => NSMutableArray
(可變)這種操作可以在可變版本和不可變版本間切換哮缺。
說太多弄跌,不如給一個Demo~
- 下面請看小編準(zhǔn)備的NSCopying協(xié)議相關(guān)的小Demo:
QiShareMember.h:
@interface QiShareMember : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *name; //!< 姓名
@property (nonatomic, copy, readonly) NSString *sex; //!< 性別
@property (nonatomic, assign, readonly) NSUInteger age; //!< 年齡
//! 初始化方法
- (instancetype)initWithName:(NSString *)name andSex:(NSString *)sex andAge:(NSUInteger)age;
- (void)addFriend:(QiShareMember *)friend;
- (void)removeFriend:(QiShareMember *)friend;
@end
QiShareMember.m:
@implementation QiShareMember {
NSMutableSet *_friends;
}
- (instancetype)initWithName:(NSString *)name andSex:(NSString *)sex andAge:(NSUInteger)age {
if (self = [super init]) {
_name = [name copy];
_sex = [sex copy];
_age = age;
_friends = [NSMutableSet new];
}
return self;
}
- (void)addFriend:(QiShareMember *)friend {
[_friends addObject:friend];
}
- (void)removeFriend:(QiShareMember *)friend {
[_friends removeObject:friend];
}
- (id)copyWithZone:(NSZone *)zone {
QiShareMember *copy = [[[self class] allocWithZone:zone] initWithName:_name andSex:_sex andAge:_age];
copy->_friends = [_friends mutableCopy]; //!< 注意friends只是一個實(shí)例變量不是一個屬性,所以不能用點(diǎn)語法
return copy;
}
@end
最后尝苇,特別致謝《Effective Objective-C 2.0》第三章
推薦文章:
代碼快不快铛只?跑個分就知道
iOS UIButton之改變有效點(diǎn)擊區(qū)域(改變熱區(qū))