從前端到iOS
引子
我從去年3月份開始學習 iOS,大約1個多月之后進入正式開發,截止目前已經完成了4個 App(兩個公司的中型 App,兩個私人的小型 App)
陸續有一些做前端的同事向我咨詢該如何入門 iOS 開發,所以最近回顧了一下學習的歷程,寫成本文。
這篇文章不是 Step by Step 的介紹,也不會深入討論太多技術細節,而主要是提煉關鍵的知識點,并將 iOS 與前端做一些類比,希望幫助前端開發者「觸類旁通」「舉一反三」地快速了解整個 iOS 體系,以更針對和有效地學習。
另外我推薦在入門階段,不要去深究一些過于細節和深入的知識或者概念,否則很容易打消學習熱情(就像我不會推薦在前端入門階段就去研究派生繼承一樣),這在文章中會陸續有提到。
在閱讀之前,我希望你至少擁有以下技能:
- 非常熟悉前端的知識體系,JS,HTML,CSS,DOM,BOM 這些概念應該了然于胸
- 使用過 Backbone,Angular, React 等框架中的任一,也就是說至少做過 SPA,思考過 MV*
修改歷史
由于我也是初學 iOS 不久,為了校正文章中的錯漏,預留一小節
- 2016-01-15 First Blood
準備工作
首先,做好心理準備。iOS的體系非常龐大,所以需要投入大量時間和精力。你最好能找到一個非常堅實的理由來做支撐。
比如「 iOS 開發的薪水好高丫我一定要學會然后跳槽掙更多錢」就非常不錯,當然這種理由可能會受到 外部環境 影響,所以找一個內因會更好。
例如我學習 iOS 的理由是「 僅靠前端技術難以滿足移動端的用戶需求或體驗要求 」。去年女兒剛出生,為了記錄喂奶的時間,我嘗試過觸屏頁面,可惜體驗上確實難以與 App 相比,那么作為一名以創造更好用戶體驗為工作目的的程序員,我實在找不到不學習一門移動端開發技術的理由。
其次,做好硬件準備。買一臺 Mac 設備,窮人可以選最低配的 Air (5K+) 或者 Mac mini (3K+),機器性能親測夠用(對的我就是窮人T_T),別折騰黑蘋果或者虛擬機,把時間用在刀刃上。iPhone 或者 iTouch 可以晚點再買,因為如果只是出于學習目的,前期使用模擬器就夠了。
最后,準備好學習資料,比如書籍、視頻、PPT 等等。不要驚訝,我前面說過,這篇文章只是給你「指路」用的,請配合其他詳細的資料一起學習。
鑒于市面上這些資料相當多,我時間有限難以一一辨別,出于不誤人子弟(以及沒廣告費)的考慮,就只推薦兩個吧:Google 和 iOS官方文檔庫
知識點介紹
如果是第一次打開 iOS 的官方文檔,估計會被這密密麻麻的鏈接直接打消掉學習熱情。請堅持住!下面會逐一介紹基礎的概念,幫助你先建立模糊(不一定準確)但整體的認識。
開發語言
iOS 的開發語言有兩種:Objective C 和 Swift,應該學習哪個呢?在當前(2016.01),我依然推薦先學習 Objective C,主要理由是:
- 各種輪子、可參考的示例、問題的答案,OC 更多一些
- 只會 Swift 不一定能立即參與公司的項目,或者找到一份 iOS 開發工作,因為正式使用 Swfit 的公司或者產品比例不高
- 與前端學習一樣,語言只占小部分(ECMAScript 標準不包含 DOM/BOM 等大部頭),所以別在這里太糾結了,而且對較有經驗的開發者,換一種語言并不是難事。
我推薦入門時別在這個部分停留太多時間,盡快進入到界面開發去尋找成就感(語言癖請無視)。Code School 的 Try Objective-C 還不錯,花一晚上時間就可以做完,再配合經常查詢 Foundation 框架的文檔 ,基本夠用。
下面羅列一下 OC 語法的大致內容:
- OC 是 C 的超集,開發中會使用到 C 的控制語法(if/for/while)、變量類型(如數值 int/float/double )、指針等,建議翻出大學的 C 語言課本稍微回憶下
- OC 是面向對象的,剛才提到的 Foundation 框架提供了基本的類型,比如 NSObject/NSString/NSArray/NSDictionary,建議常去文檔翻翻后3個類的 API,比如 stringByReplacingOccurrencesOfString:withString: (@_@)
- Foundation 有 NSNumber 這樣的「數值」類,但卻不能直接使用+-*/等運算符,所以在計算部分還是會用 C 的數值類型,或者 NSInteger/CGFloat 等(推薦后者),但是注意 NSInteger 不是類,而只是 int 或 long 的宏定義(根據 CPU 是32位或64位)
- 別覺得 OC 古老,它有 Block 能玩函數式,還有 Category 語法能玩元編程,入門階段可以了解下,后面再深究
- OC 沒有垃圾回收,內存管理有 MRC 與 ARC (默認)兩種方式,實際上原理基本一樣,不過 ARC 可以省略很多代碼(半自動),使用 ARC 時簡單的代碼(比如入門時寫的練習)都不用特意去關注內存問題,建議在入門時了解下引用計數的基本原理和 strong/weak 等 property 修飾符,入門之后有時間再慢慢細讀「iOS與OS X多線程和內存管理 」一書(看睡著過逃……)
在習慣了話嘮式的 API (componentWillReceiveProps 算什么)以及滿眼奇怪的 @ 和 [] 之后,趕緊進入下一節
界面構建方式
在前端,我們通常使用 HTML+CSS 這種聲明式的語言來構建界面,而在 iOS 里有兩種方式
- 純代碼手寫,類似于使用 JS 創建元素、appendChild 這樣來一步步構建界面
- 使用 Storyboard/Xib 這種可視化方案,類似于 VB/C# 里的拖拽控件,比 HTML+CSS 還要方便
關于到底哪種方式好,一直爭論不休,比如可以看看 唐巧的這篇文章
我的觀點是:
- 目前不推薦 Storyboard , 這種技術可以將整個 App 的界面全用一個文件來描述,雖然很方便也很直觀(可以一眼看明白 App 的界面結構和關系),但是多人維護時文件非常容易沖突而且難以解決(Apple 挖的坑),而且再加上這個技術誕生的晚,使用它的公司和產品也不多
- 推薦 Xib,雖然也容易沖突,但是因為是一個界面一個 Xib 文件,所以概率不高,通過 Xib 可以直觀的看出單個界面的元素組成,拖拽的方式也很方便
- 同樣推薦使用代碼手寫,首先是采用這樣方式的公司不少,其次手寫也有不少好處,比如不會沖突、易于復用代碼等等
注意,目前蘋果官方的文檔和示例里,基本都是用 Storyboard(畢竟是主推的新技術),建議先跳過這些部分。另外一個思考,為啥前端沒人爭論類似問題呢?
界面元素
我們先粗略地看一下前端界面是如何組成的:
- 基本的界面單元是元素(Element),它有很多種類型,如 div/span,有些還自帶交互如 a/input/select
- HTML 聲明元素(Element)及元素的層次關系
- CSS 負責元素的樣式與布局
- 要構造新的控件,需要組合已有的元素(比如日期選擇器),但是在 WebComponents 等技術出現之前復用比較麻煩(組件化是前端目前的熱點)
下面來對比看一下 iOS 的情況
- 基本的元素是 UIView,有很多子類(是相當多),如 UIButton/UILabel/UIImageView 等等,這些都定義在 UIKit 框架中,建議多翻翻 UIKit 文檔
- 使用 Xib 聲明 UIView 的結構或者直接用代碼來組合(addSubview等方法)
- 沒有 CSS,可以使用控件屬性控制樣式,比如背景、字號、顏色等,但遠不如 CSS 強大,布局后面再提
- 構造新的控件,可以通過繼承已有控件的方式來實現,復用方便
可以看到不少概念還是相通的,可惜沒有 CSS(做 iOS 開發時真挺想念它的)
MVC 與界面顯示
下面要提一些 iOS 中特有的概念和知識,先看一張圖

圖中提到了兩個概念:UIWindow 和 ViewController
- UIWindow 代表窗口,通常 App 里只有一個窗口(跟前端很像)
- 要顯示界面,首先要指定 UIWindow 的 rootViewController 屬性,也就是一個 ViewController 對象,ViewController 的主要作用是管理和展示 UIView 以及響應用戶交互
- 然后指定 ViewController 的 view 屬性,也就是 UIView 對象
- 最后這個 UIView 會顯示在屏幕上
如果你用過一些 MV* 類的框架,應該對 Controller 的概念不陌生,而比前端更進一步的是, Apple 直接在開發庫中內置了 MVC,建議閱讀 Apple 官方的 MVC 介紹
這里要提一個 iOS 中特有的重要概念: delegate ,ViewController 就是 UIView 的 delegate,以 UITableView 為例,它的數據來源(有多少 Cell、每個 Cell 長啥樣),用戶交互(選擇一個 Cell)以及生命周期都是委托給對應的 ViewController 處理,建議好好研究下 UITableViewController 的用法,畢竟這是 iOS 開發中使用頻率最高的類,而且完整展示了 MVC 的協作方式。
理解了上面的概念,可以再看看稍微復雜的 App 結構
通常 App 會有多個頁面,每個頁面都會由一個 Controller 進行管理
這些 Controller 可以形成一個類似堆棧的結構,棧頂 Controller 的 view 才會顯示
怎么壓棧呢?所有的 Controller 都有 presentViewController 方法,可以用來顯示一個 Modal 頁面;UINavigationController (帶導航欄的 Controller)的 pushViewController 方法也可以
好了,現在再按從大到小,由根到葉的順序總結一下關于界面的知識
- iOS 內置了 MVC 的支持,提供了 ViewController 的概念,我們溝通中常說一個 App 頁面,通常指的就是一個 ViewController 對應的界面,這是原生前端所沒有的更抽象的概念
- 頁面的切換,可以通過修改 ViewController 的堆棧來實現(不準確,比如 Tab 不是,但不影響理解)
- App 顯示的「根」是 UIWindow 的 rootViewController 的 view
- 根 View 下可以有多個層級、各個種類的子 UIView,他們共同組成了當前界面
事件響應
iOS 的事件響應原理比前端略復雜一些,我先說入門,再細談原理
入門階段,你只要學會 Target-Action 即可,基本能滿足簡單的交互要求
- 如果使用 Xib/Storyboard,直接拖拽添加 IBAction 方法即可
- 如果純代碼,那么需要調用控件的 addTarget:action:forControlEvents: 方法
這兩種方式是等價的,與前端 addEventListenter 的方式也很類似
不過你會發現,很多控件是沒有 addTarget:action:forControlEvents: 方法的(比如 UIImageView),因為這個方法源自 UIControl,真正的繼承結構其實是這樣的: UIButton < UIControl < UIView
那如何為 UIImageView 之類的控件添加觸摸事件呢?可以使用 UIGestureRecognizer,請看最常用的 UITapGestureRecognizer 子類
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imgTapped:)]; [imgView addGestureRecognizer:tapRecognizer]; imgView.userInteractionEnabled = YES; // 對于 UIImageView 默認是 NO
可以從 UIGestureRecognizer 的文檔中發現更多的子類,涵蓋了各種常見手勢,比如 Tap/Pinch/Swipe 等等。相比于前端還需要自己通過組合 TouchUp/TouchMove 之類的原始事件來實現手勢,iOS 顯得方便的多。
學會以上兩種方式就能解決很多問題了,不過內在的原理是怎樣的呢?有沒有類似事件冒泡的機制呢?UITapGestureRecognizer 與 UIControlEventTouchUpInside 有沒有關系呢?
想要搞懂這些問題,我推薦仔細閱讀這幾篇文檔: Event Delivery: The Responder Chain , Gesture Recognizers
下面我對原理的簡單總結:
- 用戶觸摸開始/移動/結束,都會產生 UITouch 事件
- 系統會首先尋找事件對應的 UIView,方法是從外向里通過 hit-test 鏈 (類似捕獲)尋找
- 找到對應的 UIView 之后,會根據事件的階段調用相應方法,如 touchesBegan:withEvent:,方法內如果調用了 super,那么系統會尋找 nextResponder,通常是父 View 或者其對應的 ViewController,再調用相應的 touches 處理方法,這就是從內向外的 Responder 鏈 (類似冒泡)
- 如果 UIView 沒有調用 super,那么事件不再繼續傳遞(類似 stopPropagation)
- UIGestureRecognizer 與 UIControl 實際上是對上述機制的更高級封裝(手勢事件),例如從 UIControlEventTouchUpInside 的名字就可以大概推測出,他其實通過組合 TouchBegin 和 TouchEnd 來封裝出的事件(像不像 Zepto 里的 tap 事件封裝?不過 iOS 這是內置的)
- 為 UIView 添加了 UIGestureRecognizer 之后,UITouch 事件會交給 UIGestureRecognizer,而不再交給 UIView,因此添加了 UITapGestureRecognizer 之后,UIButton 的 UIControlEventTouchUpInside 就不會觸發了
布局技術
CSS 的布局規則其實非常復雜,這里不贅述了,iOS 是什么情況呢?我按照歷史的順序來梳理下先明確下,iOS 坐標系的概念與前端一致,都是左上角為原點,而且基本單位 pt 與前端 px 一樣,都是指邏輯像素而非物理像素(頁面無縮放時)
- 很早很早之前,由于屏幕只有一種規格,所以直接采用絕對定位就行了,比如常見的 initWithFrame: 其實就是指定了元素的大小和絕對位置
- 后來 iPhone 支持了橫屏以及 iPad 出現,蘋果推出了 Autoresizing,可以根據設置的規則自動縮放定位元素,舉個例子,通過設置簡單的規則,可以讓一個控件在 iPhone4s 和 iPhone6P 下占據屏幕的比例一樣(按屏幕大小縮放),不少產品依然在使用這種技術
- 后來蘋果又推出了更強大的 Auto Layout,通過指定元素自身或之間的約束,來控制布局與元素大小(CSS 里似乎完全沒有這樣的布局方式),舉個例子,通過 Auto Layout,可以讓兩個控件之間的水平間距在任何屏幕尺寸下都一致,而使用 Autoresizing 就做不到
- iOS8 又引入了 Size Classes ,它與 Auto Layout 并不沖突,而是在更抽象更高層的角度考慮布局布局問題,如果你的應用需要考慮橫豎屏或者跨 iPad+iPhone,那么一定要好好研究它
由于現在 iPhone 的尺寸已經多達4種,因此 Auto Layout 非常重要,建議重點學習
除了看 官方的文檔 之外,我也簡單梳理下 AutoLayout 的原理:
- 使用約束(Constraint)來描述元素與元素,或者元素自身的位置大小
- 一個約束其實可以簡寫成這么一個方程:A.屬性值 = B.屬性值 * multiplier + constant,例如我們期望一個元素 A 左側距離父元素左側邊界 10 pt,那么約束是這樣的 A.left = SuperView.left * 1 + 0
- 屬性值包括:left,top,right,bottom,centerX,centerY,width,height,為了照顧文字從右往左寫的阿拉伯人,還有兩個屬性 leading 和 trailing,根據文字書寫方向來決定究竟是左還是右(多語言方案)
- 布局時,系統就是在求解多元一次方程組(約束就是方程),如果能正確解出所有的元,那么就能正確布局
舉個例子,我們期望一個矩形左右各距離屏幕邊緣10pt,高度固定為100pt,頂部距離屏幕 20pt,那么我們需要添加這么幾條約束:
- 矩形.top = SuperView.top * 1 + 20
- 矩形.left = SuperView.left * 1 + 10
- 矩形.centerX = SuperView.centerX * 1 + 0
- 矩形.height = 100
根據這4個方程,加上初始條件(SuperView 的 top,left 都是0,centerX 為屏幕寬度一半),可以求解出矩形的 left,top,width,height,那么矩形的布局就確定了
再一個稍微復雜點的例子,我們期望矩形 A 和 B 能夠左右等分屏幕,那么約束是這樣的
- A.left = SuperView.left * 0 + 0
- B.left = A.right * 1 + 0
- A.top = SuperView.top * 1 + 0
- B.top = SuperView.top * 1 + 0
- A.width = B.width * 1 + 0
- A.height = SuperView.height * 1 + 0
- B.height = SuperView.height * 1 + 0
- B.right = SuperView.right * 1 + 0
根據以上8個方程,可以求解出矩形 A 和 B 的一共 8 個屬性。當然,這樣的結果還有其他等價的約束方程組
AutoLayout 的約束,可以在 Xib/Storyboard 中添加(添加后還可以拖出 IBOutlet 哦),也可以用代碼手寫,如果你選擇后者的話,可以試試 Masonry 這個庫
包管理
OC 像早期 JS 一樣,沒有內置包管理,所以有人用 Ruby 開發了一個第三方的包管理系統 CocoaPods ,使用方式請看文檔,我列一下國內鏡像的安裝命令
# 從國內 Rubygems 鏡像安裝 Cocoapods gem sources -r https://rubygems.org/ gem sources -a https://ruby.taobao.org/ sudo gem install cocoapods # 指定 Pod 國內鏡像 pod repo add master https://gitcafe.com/akuandev/Specs.git
指定 Pod 國內鏡像可能會很慢,可以去~/.cocoapods 目錄下執行 du -sh * 查看目錄大小來判斷進度,目前執行完畢是400多M
深入 iOS
入門之后,該繼續學習和深入哪些方面呢?我覺得以下幾個方面是非常重要的,不過既然本文不會探討深入的技術細節,那么我就主要列舉下關鍵詞
- 內存管理:這可能是用慣了帶 GC 語言的人最討厭的,不過這個坎是繞不過去的,前面提到的「iOS與OS X多線程和內存管理 」前半部分請好好閱讀
- 多線程:用慣了單線程 JS 的同學又要哭了,好吧,這也是繞不過去的,關鍵詞 GCD/NSOperationQueue,好好研究吧,上面提到那本書的后半部分,也可以好好讀一讀
- 本地存儲,簡單的 NSUserDefaults(可以類比于 LocalStorage)真的很簡單,復雜的如 CoreData 真的很復雜
- 兼容性,iOS 開發也有兼容性問題,比如一些技術或者 API 是 iOS 新版本引入的,那么如果你要支持舊版本就不能使用,另外內置框架比如 UIKit 的內部實現可能在某個版本做了調整,那么某些代碼也許就會有問題,但是比之前端還是簡單很多
- 交互,iOS 的交互可是有非常完備體系的,建議閱讀 iOS Human Interface Guidelines 中文譯本 ,讀完你會有收獲的,而且我覺得做移動前端的開發者,也應該好好讀讀
- 與 Apple 打交道,例如證書、簽名、打包、提交審核、發布等等雜事,這塊麻煩的讓人抓狂,幸虧有人正在整理了 iOS 開發流程
結尾
這是我寫的最長的一篇文章,如果你堅持看到了這里……祝閱讀愉快,學習順利,以及世界和平……