前篇說到我們通過ObjC的Category特性給日常工作增加便捷的實現(xiàn)活尊,這一篇則要從語言設(shè)計角度隶校,跟大家分享一些思考。
不要忽視interface
ObjC的@interface設(shè)計蛹锰,跟Java和C#真的很像深胳,但又略有不同,相比之下Java和C#則像是一個模子刻出來的铜犬。ObjC的特點十分明顯舞终,首先是一般不用寫@private
和@public
來區(qū)分私有變量轻庆,大部分ObjC開發(fā)者甚至都不知道還有這兩個關(guān)鍵字,其實Cocoa源代碼中也基本沒有使用過這種設(shè)計敛劝,即使ObjC是支持的余爆。
在@interface 中使用 @private和@public
@interface Student : NSObject
{
@private
NSString* _name;
@public
NSNumber* _age;
int _height;
}
@end
如上代碼中,Student
有一個私有變量_name
夸盟,和兩個共有變量_age
蛾方、_height
,但在@interface
中聲明變量满俗,一定不是Cocoa設(shè)計者的初衷转捕,這里有兩個方面的考慮。
其一唆垃,把內(nèi)部變量直接暴露在外五芝,會降低整個框架的穩(wěn)定性,因為增加不同模塊之間的耦合辕万,降低了每個類的內(nèi)聚性枢步。
其二,內(nèi)部變量的變量名渐尿,很容易跟局部變量變量名產(chǎn)生沖突醉途。上例中我給每一個變量名前加了下劃線,就是為了防止這個問題發(fā)生砖茸。
所以縱觀Cocoa框架的頭文件設(shè)計隘擎,基本沒有這樣的代碼,因為設(shè)計者提供了更好的實現(xiàn)方式凉夯,就是大家用的更多的@property
關(guān)鍵字货葬。
如果用@property聲明上面的類,大家都很熟悉
@interface Student : NSObject
@property (nonatomic, strong) NSString* name;
@property (nonatomic, strong) NSNumber* age;
@property (nonatomic, assign) NSInteger height;
@end
@property這個設(shè)計真的很有意思劲够,首先我們不再區(qū)分私有公有屬性震桶,因為只要寫在.h
里面的@property,我們都默認(rèn)是共有的征绎,私有的@property可以寫在.m
文件里蹲姐。
其次,配合寫在@implementation里面的@synthesize關(guān)鍵字人柿,可以自動生成setter和getter方法柴墩,而現(xiàn)在@synthesize關(guān)鍵字都可以省略,除了個別情況有修改內(nèi)部變量名稱的需求凫岖。
@implementation Student
@synthesize name = _name;
@synthesize age = __age;
@end
上面的@synthesize拐邪,第一個是可以省略的,在不寫的情況下隘截,編譯預(yù)處理會自動給添加@synthesize代碼扎阶,所以即使沒有合成(synthesize)height屬性汹胃,我們依然實現(xiàn)了它的setter和getter方法
//這兩個方法可以重寫
- (void)setHeight:(NSInteger)height
{
_height = height;
}
- (NSInteger)height
{
return _height;
}
在setter和getter方法均重寫的情況下,@synthesize需要手動添加东臀。
@synthesize height = _height;
為什么要使用 setter 和 getter
setter
和getter
的設(shè)計的確值得琢磨着饥,我們主要從以下幾點分析:
setter 和 getter 包裝了內(nèi)部變量,整個類對外可以只暴露接口惰赋,增強(qiáng)類的內(nèi)聚性宰掉。
例如上例中的內(nèi)部變量_name
,外部類是無法操作的赁濒,只能通過set和get接口來發(fā)消息:
Student* s = [[Student alloc] init];
[s setName:@"Tom"];
[s name];
通過實現(xiàn) getter方法轨奄,可以避開初始化變量的時機(jī)問題
這也是很實用的一個點,因為ObjC的消息設(shè)計機(jī)制拒炎,導(dǎo)致ObjC很難在初始化(init)方法中傳入過多參數(shù)(題外話挪拟,我給ObjC擴(kuò)展過依賴注入,詳見iOS實現(xiàn)依賴注入)击你。因此新實例的默認(rèn)屬性玉组,放在什么位置實現(xiàn)合適,是大家一定遇到過的問題丁侄。
例如最常見的UIViewController
惯雳,代碼初始化走init
方法,而通過storyboard實力化則走initWithCoder
方法鸿摇,一些容器屬性石景,通過getter方法初始化,則可避免第一次調(diào)用尚未初始化造成的問題拙吉。
早期的Cocoa在如果給nil
發(fā)消息潮孽,是會引起異常的,現(xiàn)在的版本給沒有alloc的對象發(fā)消息不再拋異常庐镐,以至于某些時候?qū)傩詻]有初始化造成的問題變得更隱蔽恩商,然而重寫getter方法可以有效避免這個問題变逃,例如:
//班級類
@interface XXClass : NSObject
@property (nonatomic, strong) NSMutableArray* students; //學(xué)生
@end
@implementation
//實現(xiàn)getter方法必逆,在內(nèi)部變量_students沒初始化的情況下將其初始化
- (NSMutableArray *)students
{
if (!_students) {
_students = [NSMutableArray array];
}
return _students;
}
@end
如此一來,無論在任何時候揽乱,第一次發(fā)送[self students]
消息的時候名眉,內(nèi)部變量_students
都會初始化。
在這里要另外注明一點凰棉,在類的內(nèi)部损拢,不要在setter和getter方法外,直接使用內(nèi)部變量撒犀,遵守這一條會收益很多福压。
setter 和 getter 可以單獨使用掏秩,也可以脫離內(nèi)部變量使用
這里要說的就是@property的靈活性了,大家知道@property擁有一系列的修飾詞荆姆,除了常用的nonatomic(非原子化蒙幻,線程安全)
,strong(強(qiáng)引用類型)
胆筒,weak(弱引用類型)
邮破,assign(賦值,用于非對象屬性)
以外仆救,還有readonly(只讀)
和readwrite(可讀寫)
兩個影響setter和getter方法的屬性抒和,readonly
修飾的屬性,只有g(shù)etter方法而沒有setter方法彤蔽。
readwrite
則是一個看起來可有可無的修飾詞摧莽,因為默認(rèn)就是可讀寫。然而它其實有個專門設(shè)計的用法铆惑,就是在.h中的interface中被readonly
修飾的屬性范嘱,可以在這個類的其他類別(category)或者匿名類別中重新聲明這個屬性時,修改其讀寫限制员魏,例如
//班級類
@interface XXClass : NSObject
@property (nonatomic, strong, readonly) NSMutableArray* students; //學(xué)生
@end
//匿名類別
@interface XXClass()
@property (nonatomic, strong, readwrite) NSMutableArray* students;
@end
這樣一來丑蛤,因為匿名類別一般寫在.m文件里(基本沒見過寫在.h文件里的),所以外部是不能調(diào)用students
屬性的setter方法撕阎,而XXClass
類內(nèi)部則可以使用受裹。
還有一種常見情況是用setter和getter來模擬屬性(@property),例如:
//班級類
@interface XXClass : NSObject
@property (nonatomic, strong, readonly) NSMutableArray* students; //學(xué)生
@property (nonatomic, assign, readonly) NSUInteger studentsCount; //學(xué)生數(shù)量
@end
- (NSUInteger)studentsCount
{
return self.students.count;
}
這里的studentsCount
是沒有內(nèi)部變量的虏束,通過getter方法偽造成屬性接口棉饶。
小結(jié)
這一篇是ObjC的接口設(shè)計模式的一部分,寫的比較詳細(xì)是幫助新手入門镇匀,給有經(jīng)驗的朋友帶來一些思考照藻,并引出接下來的內(nèi)容。