前篇說到咱們經過ObjC的Category特性給平常工做增長便捷的實現,這一篇則要從語言設計角度,跟你們分享一些思考。segmentfault
ObjC的@interface設計,跟Java和C#真的很像,但又略有不一樣,相比之下Java和C#則像是一個模子刻出來的。ObjC的特色十分明顯,首先是通常不用寫@private
和@public
來區分私有變量,大部分ObjC開發者甚至都不知道還有這兩個關鍵字,其實Cocoa源代碼中也基本沒有使用過這種設計,即便ObjC是支持的。設計模式
<!--more-->安全
在@interface 中使用 @private和@public框架
@interface Student : NSObject { @private NSString* _name; @public NSNumber* _age; int _height; } @end
如上代碼中,Student
有一個私有變量_name
,和兩個共有變量_age
、_height
,但在@interface
中聲明變量,必定不是Cocoa設計者的初衷,這裏有兩個方面的考慮。atom
其一,把內部變量直接暴露在外,會下降整個框架的穩定性,由於增長不一樣模塊之間的耦合,下降了每一個類的內聚性。
其二,內部變量的變量名,很容易跟局部變量變量名產生衝突。上例中我給每個變量名前加了下劃線,就是爲了防止這個問題發生。線程
因此縱觀Cocoa框架的頭文件設計,基本沒有這樣的代碼,由於設計者提供了更好的實現方式,就是你們用的更多的@property
關鍵字。設計
若是用@property聲明上面的類,你們都很熟悉code
@interface Student : NSObject @property (nonatomic, strong) NSString* name; @property (nonatomic, strong) NSNumber* age; @property (nonatomic, assign) NSInteger height; @end
@property這個設計真的頗有意思,首先咱們再也不區分私有公有屬性,由於只要寫在.h
裏面的@property,咱們都默認是共有的,私有的@property能夠寫在.m
文件裏。對象
其次,配合寫在@implementation裏面的@synthesize關鍵字,能夠自動生成setter和getter方法,而如今@synthesize關鍵字均可以省略,除了個別狀況有修改內部變量名稱的需求。blog
@implementation Student @synthesize name = _name; @synthesize age = __age; @end
上面的@synthesize,第一個是能夠省略的,在不寫的狀況下,編譯預處理會自動給添加@synthesize代碼,因此即便沒有合成(synthesize)height屬性,咱們依然實現了它的setter和getter方法
//這兩個方法能夠重寫 - (void)setHeight:(NSInteger)height { _height = height; } - (NSInteger)height { return _height; }
在setter和getter方法均重寫的狀況下,@synthesize須要手動添加。
@synthesize height = _height;
setter
和getter
的設計的確值得琢磨,咱們主要從如下幾點分析:
例如上例中的內部變量_name
,外部類是沒法操做的,只能經過set和get接口來發消息:
Student* s = [[Student alloc] init]; [s setName:@"Tom"]; [s name];
這也是很實用的一個點,由於ObjC的消息設計機制,致使ObjC很難在初始化(init)方法中傳入過多參數(題外話,我給ObjC擴展過依賴注入,詳見iOS實現依賴注入)。所以新實例的默認屬性,放在什麼位置實現合適,是你們必定遇到過的問題。
例如最多見的UIViewController
,代碼初始化走init
方法,而經過storyboard實力化則走initWithCoder
方法,一些容器屬性,經過getter方法初始化,則可避免第一次調用還沒有初始化形成的問題。
早期的Cocoa在若是給nil
發消息,是會引發異常的,如今的版本給沒有alloc的對象發消息再也不拋異常,以致於某些時候屬性沒有初始化形成的問題變得更隱蔽,然而重寫getter方法能夠有效避免這個問題,例如:
//班級類 @interface XXClass : NSObject @property (nonatomic, strong) NSMutableArray* students; //學生 @end @implementation //實現getter方法,在內部變量_students沒初始化的狀況下將其初始化 - (NSMutableArray *)students { if (!_students) { _students = [NSMutableArray array]; } return _students; } @end
如此一來,不管在任什麼時候候,第一次發送[self students]
消息的時候,內部變量_students
都會初始化。
在這裏要另外註明一點,在類的內部,不要在setter和getter方法外,直接使用內部變量,遵照這一條會收益不少。
這裏要說的就是@property的靈活性了,你們知道@property擁有一系列的修飾詞,除了經常使用的nonatomic(非原子化,線程安全)
,strong(強引用類型)
,weak(弱引用類型)
,assign(賦值,用於非對象屬性)
之外,還有readonly(只讀)
和readwrite(可讀寫)
兩個影響setter和getter方法的屬性,readonly
修飾的屬性,只有getter方法而沒有setter方法。
readwrite
則是一個看起來無關緊要的修飾詞,由於默認就是可讀寫。然而它其實有個專門設計的用法,就是在.h中的interface中被readonly
修飾的屬性,能夠在這個類的其餘類別(category)或者匿名類別中從新聲明這個屬性時,修改其讀寫限制,例如
//班級類 @interface XXClass : NSObject @property (nonatomic, strong, readonly) NSMutableArray* students; //學生 @end
//匿名類別 @interface XXClass() @property (nonatomic, strong, readwrite) NSMutableArray* students; @end
這樣一來,由於匿名類別通常寫在.m文件裏(基本沒見過寫在.h文件裏的),因此外部是不能調用students
屬性的setter方法,而XXClass
類內部則可使用。
還有一種常見狀況是用setter和getter來模擬屬性(@property),例如:
//班級類 @interface XXClass : NSObject @property (nonatomic, strong, readonly) NSMutableArray* students; //學生 @property (nonatomic, assign, readonly) NSUInteger studentsCount; //學生數量 @end
- (NSUInteger)studentsCount { return self.students.count; }
這裏的studentsCount
是沒有內部變量的,經過getter方法僞形成屬性接口。
這一篇是ObjC的接口設計模式的一部分,寫的比較詳細是幫助新手入門,給有經驗的朋友帶來一些思考,並引出接下來的內容。