2014年1月7日 星期二

[CS193P] 1. Class Logistics, Overview of iOS, MVC, Objective-C

MVC (Model / View / Controller)

MVC一直是這五、六年來我非常推崇的開發觀念,早期從Java的Spring MVC開始著手,就深深愛上這種UI與程式邏輯獨立的開發方式。在iOS的開發裡,更是非常徹底的實踐了MVC。當然MVC有多種不同的變型,在此處的MVC我們可以簡單的來說明:

掌管UI背後運作的程式碼為Controller(在iOS開發,會稱為ViewController),是MVC中的運作核心。Controller掌控了最主要App流程的運作,除了接收使用者透過UI(View)傳遞而來的操作應如何有對應的動作,以及向Model端取得資料與商業邏輯的運算,再將運算結果反饋回UI做畫面上的改變。

而在iOS中的View,若開發的是Universal(可用於iPhone與iPad,但可依其不同特性而有不同的UI展現方式,但背後邏輯是相同一套Controller和Model),則可搭配兩個storyboard(一個iPhone用,一個iPad用,每個storyboard中由多個View所組成)呈現不同的UI。

這個設計方法說實話,有其優點也有其缺點,優點是真正落實了MVC的彈性,針對不同的顯示方式只要分開設計,可簡單搭配同一組背後的邏輯運算,而且可產生成相同的一套App,使用者不論是iPhone或是iPad,安裝後會以對應的UI來呈現。但缺點是兩種View若是有絕大部份都是相同的設計(90%),但卻要分兩個storyboard來設計,雖然可用copy/paste將大部份的UI元件複製再調整,但若當App愈來愈複雜時,若要調整某個既有的View的元件時,也得兩邊都更動,就UI模組化的觀念來看,改一處就要改多處(若有設計多國語言的storyboard,會更惱人),對UI人員是一種不會太開心的作業方式。

不過在iOS的View的獨立性,的確是相當的出色。若寫過.Net的話,應該會了解要讓Controller與View溝通,要在View上對每個元件都指定ID,好讓Controller可以針對ID來做事。但在Xcode優異的IDE介面,View上的元件與Controller之間是以「連線」的概念將兩者關聯起來,若View元件需有操作事件要指派delegate給Controller,只需要用肉眼直覺將該元件拖曳至Controller程式碼中建立IBAction關聯;而若有到時Controller需要修改UI元件內容屬性,也只需要將該元件拖曳至程式碼建立IBOutlet關聯。但在View當中,仍然沒有ID的觀念,只有從Xcode GUI上才看得出到底是哪個元件連到哪個Controller的屬性或方法上,而且沒有要用的就不必做連線。這點和其他開發環境上是有蠻大的差異,但做法相當直覺且先進。

總之,在iOS的開發中,就是一堆的MVC與MVC的互動。例如行事曆點選了Year View的某月後,會進入Month View,而點選其中某一天,會進入Day View…。Year View其實就是由一組Year的MVC組成,而Month View也是由一組Month MVC組成,以此類推會有Day的MVC,甚至填寫新事件頁面的MVC。因此以上的流程轉換,就是前面所稱的「一堆的MVC與MVC的互動」

在iOS 5之前並無Storyboard設計方式,需在獨立的.xib檔中設計View的UI,不同組MVC之間的流程,很難直接看的出來,只能從code中去了解。但自從有了Storyboard後,MVC之間的流程化被具象化出來,就像是分鏡圖一樣,可以從Storyboard中一眼就把整個流程架構看完,無論是拿來與相關人員討論或事後維護都非常的有幫助。更別說再更早期UI的開發要在另一套Interface Builder (IB)設計UI,再和程式碼整合的困難了。

基本上,iOS的MVC中,和其他程式相比差異最小的就是Model的部份,但在程式碼上許多初學者會一不小心,就把程式邏輯直接寫在Controller裡面了,造成了MVC只剩下VC。(其他語言亦然)

因此在程式碼的結構上,要養成好的習慣將Model的部份另外抽離出來,如此在被Controller呼叫時才可做到較有彈性與擴充性的設計。


Objective-C初探

我們常說.h檔是定義檔,.m檔是實作檔。Hegarty教授給了一個更實務的說法,.h檔其實就是你的API (開放出來讓人看的規格),而.m檔就是你不讓別人看見,以及其他所有要實作的程式碼。

所以呢,和Java或.Net這類語言不同之處是,我們並沒有針對各個屬性或方法設定所謂的「存取範圍」,也就是沒有什麼private、public…這種東西,反正你放在.h的,就是public,其他的就放.m就是了。

在View去拉IBOutlet和IBAction時,我們常有一種認知,覺得從UI元件按cmd接出的那條連接線就是要拉到.h檔的慣性,其實若你不是要開放出來的方法或屬性,其實拉到.m檔就好了,我之前真的是沒想過呢。

小地方注意:

  • @interface其實指的是在.h檔的class,並非其他語言的interface(在iOS反而叫protocol)
  • 在.h檔要寫繼承的類別,在.m檔則不要
  • 以@property來存取變數

Strong vs Weak Pointer

ex.
@property (strong, nonatomic) NSString *content;

在設定@property時,有個屬性要嘛就strong,要嘛就weak,但到底什麼是strong和weak pointer?

strong pointer是一種reference counting技術,只要所有被設定strong property所指到的物件(置於heap中),有至少一個以上還指著,那該物件就不會被回收。直到最後一個strong指標刪除,則iOS會自動釋放掉該物件的資源。但是這種自動的reference counting技術,在iOS 5後出現,正確名稱為ARC(Automatic Reference Counting),並不是我們在其他語言所說的GC(Garbage Collector)。

而weak pointer指的是,若此指標指到在heap中的那個物件,只要有超過一個strong pointer指向他,則幫我連接著;但若沒有任何的strong pointer指向該物件了(該物件會被釋放),則此weak pointer會一併被設定成nil。

strong和weak指標往往在很多書和文件上都說的不清不楚,大部份的人也都一知半解,說穿了就是亂用一通,反正好像run起來沒有很明顯的說出哪裡有問題,就這樣隨心所欲使用,哪一天出現了 memory的錯誤時,會非常難抓bug。


@property與@synthesize的改變(變得更省事了)

在iOS 5以前(換種說法,是在Xcode 4.4之前的版本),在.h檔設定了@property後,需要在.m檔加上對應的@synthesize,在.m檔中才可以使用self.的方式取用或設定property的值,或者重新覆寫getter或setter。


Emp.h中
@interface Emp : NSObject

@property (strong, nonatomic) NSString *name;

@end

Emp.m中
#import "Emp.h"

@implementation Emp

@synthesize name = _name; //這行

@end


但在iOS 6開始,在.h檔宣告了@property後,在.m檔已不需要再自行加入@synthesize,這些會由compiler幫我們自動加入(除了@synthesize語句外,也會幫我們自動加入預設的setter與getter內容)。

甚至我們要針對setter或getter重新改寫,也可以直接寫上改寫部份,也不會有問題。


@property的setter或getter的名稱變更

在Objective-C的property的setter與getter,和Java體系不一樣的一點是在於getter會與property名稱一致,前置詞不加上「get」字樣。setter則與Java相同。

也就是說,若你的property為name,則setter會叫"setName",而getter會叫"name"。

當然,若我們使用self.方式取用或設值,是不受影響直接都用self.name。但若是透過message傳遞時,就會使用原本message名稱。若有時候,我們有需要改變getter(或setter)的名稱時,讓程式閱讀上更清楚意思,我們則可在宣告@property時,直接做指定。

這種情況通常會發生在BOOL的property上,例如有個BOOL的property名稱為active。照預設,其setter會叫setActive,getter會叫active。但在Java中,我們通常會以較為符合英文文意的方式來看待布林值的結果,通常會以「is」作為前置詞,例如「isActive」。既清楚又好懂。

所以我們在Objective-C中也想要這樣使用的話,就是直接在@property宣告時指定名稱變更。

@property (nonatomic, getter = isActive) BOOL active;

這樣我們就輕鬆改變了一個較容易理解的getter名稱了!這種作法一般實務上,較常運用在BOOL的getter上,當然有時候也有其他的時機,是為了解決一些奇奇怪怪的問題時,也可以使用這個方法來調開名稱的衝突。(我之前開發的專案有遇到過,但我現在想不起來時機是什麼了)

沒有留言:

張貼留言