从前端到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 开发流程
结尾
这是我写的最长的一篇文章,如果你坚持看到了这里……祝阅读愉快,学习顺利,以及世界和平……
</div>