iOS 開發(fā)中的爭議
打算分享一些有爭議的話題,并且表達(dá)一下我的看法。這是該系列的***篇,我想討論的是:類的成員變量應(yīng)該如何定義?
在 Objective-C 的語言的早期,類的私有成員變量是只能定義在 .h 的頭文件里面的。像如下這樣:
- @interface ViewController : UIViewController {
- @private
- NSInteger _value;
- }
之后,蘋果改進了 Objective-C,允許在 .m 里面添加一個特殊的匿名 Category,即沒有名字的 Category,來實現(xiàn)增加類的成員變量。像如下這樣:
- @interface ViewController ()
- @property (nonatomic) NSInteger value;
- @end
這樣的好處是,這些變量在頭文件中被徹底隱藏起來了,不用暴露給使用者。
接著,在 2013 年的 WWDC 中,蘋果進一步改進了 Objective-C,允許在 .m 的 @implementation 中直接添加類的私有成員變量。像如下這樣:
- @implementation ViewController {
- NSInteger _value;
- }
于是,大家對于如何定義私有的成員變量上就產(chǎn)生的分歧。許多人喜歡用匿名的 Category 的方式來定義私有成員變量。但是,我個人更推薦在 @implementation 中直接添加類的私有成員變量。下面我做一些解釋。
歷史原因
首先早期的 iOS 程序員一定知道,在 2011 年 ARC 被推出之前,Objective-C 是需要手工地管理引用計數(shù)的。而對類的所有私有成員使用 self.property 的形式,就可以使編譯器為我們自動生成管理引用計數(shù)的代碼。在 2012 年前,這個 feature 還需要使用 @synthesize 關(guān)鍵字來啟用的。于是,蘋果通過在代碼規(guī)范中推薦和強調(diào)使用 self.property 的編程習(xí)慣,來讓大家避免在內(nèi)存管理中遇到問題。而在 ARC 時代,這個編程習(xí)慣帶來的優(yōu)勢不再存在了,因為編譯器會自動為我們管理引用計數(shù),我們只需要關(guān)心不要造成循環(huán)引用問題就行了。
省心省事
剛剛說到,在類中完全使用 _property 的方式來訪問私有成員變量,是不會有內(nèi)存管理上的問題的。但是使用 self.property 的方式來訪問私有變量是不是也是一樣不會有內(nèi)存管理上的問題呢?確實也是,但是有一點需要注意:我們***不要在 init 和 dealloc 中使用 self.property 的方式來訪問成員變量,這一點是寫在蘋果的官方文檔里的,我在以前的文章里也介紹過。(見:《不要在init和dealloc函數(shù)中使用accessor》)
所以,如果你用 self.property 來訪問私有成員變量。那么你需要注意,在 init 和 dealloc 中不使用這種方式。這其實對程序員來說是一個負(fù)擔(dān),你需要不停提醒自己有沒有犯錯。如果你使用完全的 _property 的方式來訪問私有成員變量,就不用想這一類問題了。
關(guān)于隱藏
大家知道,self.property 其實是調(diào)用了類的 [self property] 方法,所以這其實是有一層方法調(diào)用的隱藏,很多時候,我們需要延遲初使化一個類成員的時候,就會把這個成員的初使化方法寫在這個 [self property] 方法的實現(xiàn)中。
那么問題來了,當(dāng)你在閱讀別人代碼時,看到 self.property 的時候,你會想:這里會不會有一些隱藏的函數(shù)實現(xiàn)?于是你需要跳轉(zhuǎn)到其方法實現(xiàn)中去查找。但是在實際開發(fā)中,大部分的 property 其實是使用編譯器自動生成的 Getter 和 Setter 方法,于是你會找不到實現(xiàn),這個時候,你才知道:“哦,原來這段代碼并沒有做自定義的成員初使化工作”。
這種默認(rèn)的隱藏在代碼中多了,會影響代碼的閱讀和維護。其實大部分的類成員變量都需要在類初使化方法中賦值,大部分的 UIViewController 的成員變量,都需要在 viewDidLoad 方法中賦值。那既然這樣,不如直接在相應(yīng)的方法中用一個名為 setupProperty 方法直接進行初使化。這樣的好處是,代碼的可讀性更好了,self.property 只有需要延遲初使化的情況下才被使用。
關(guān)于這個還有一個小故事,我之前 Review 過一個同事的 iOS 端代碼,那個同事喜歡把 table view 的數(shù)據(jù)另外封裝成一個類,而我覺得這些數(shù)據(jù)其實就是一個數(shù)組,沒必要進行這一層封裝,最終我們爭論了比較久。我的觀點是,一切隱藏都是對代碼復(fù)雜性的增加,除非它帶來了好處,例如達(dá)到了代碼復(fù)用,提高了代碼的可維護性等,否則,沒有好處的封裝只會給代碼閱讀理解帶來成本。就我現(xiàn)在的經(jīng)歷中,大部分的 table view 的數(shù)據(jù)都可以放在一個數(shù)組中,沒必要把這個數(shù)組封裝起來,另外提供一套操作這個數(shù)組數(shù)據(jù)的方法。
簡短的代碼更易讀
_property 的寫法比 self.property 更短,也更簡單。我認(rèn)為這樣寫出來的代碼更方便閱讀。
執(zhí)行速度更快,IPA體積更小
我之前從來沒想到過這兩者之間的速度和應(yīng)用體積會有很大差別。不過一個同行(來自國外著名的社交網(wǎng)絡(luò)公司)告訴我,他們公司發(fā)現(xiàn)二者還是有不小的差距,如果你們的應(yīng)用需要做一些深度優(yōu)化,可以考慮一下把 self.property 換成 _property。但我覺得,大部分應(yīng)用都應(yīng)該是不需要做這種深度優(yōu)化的。
KVO 和 KVC
是的,如果用 _property 這種寫法,就不能使用 KVO 和 KVC 了。但是我得反問一下,在一個類的內(nèi)部,KVO 自己的私有成員變量算是一個好設(shè)計嗎?我們講類要”高內(nèi)聚,低耦合”,KVO 是為了實現(xiàn)觀察者模式,讓對象之間相互解耦的。如果把 KVO 用在類的內(nèi)部,KVO 自己的私有成員,我認(rèn)為其實這不是一個很好的設(shè)計。
Computed Properties
在 Swift 中,引入了 Computed Properties 的概念,其實這在 Objective-C 中也有,只是沒有專門給它名字。如果一個 property 我們提供了對應(yīng)的 setter 和 getter,并且沒有直接使用其對應(yīng)的 _property 變量,那么這個 property 就是所謂的 Computed Properties。
是的,在類的內(nèi)部如果直接使用 _property 形式,也無法使用 Computed Properties 了,但我認(rèn)為這影響不大。其實 Computed Properties 也就是一層對數(shù)據(jù)存取的封裝,我們另外實現(xiàn)兩個函數(shù),分別對應(yīng)數(shù)據(jù)的 setter 和 getter 功能,就可以達(dá)到同樣的效果。
寫在***
其實我上面提到的這些問題都是小問題,影響不大。但是代碼風(fēng)格的統(tǒng)一卻是大問題。所以不管你們項目中使用的是 self.property 風(fēng)格還是 _property 風(fēng)格,問題都不大,但是如果你們同時使用這兩種風(fēng)格,那么就非常不好了。
希望我的這篇文章能讓大家了解到在這方面的爭論,也希望大家能夠在這一點上,在公司內(nèi)部達(dá)成統(tǒng)一。