如果说移动时代的前身是什么,我想一个可能的答案就是网络时代。网络的兴起,让所有设备相连成为了可能,也催生了电商、社交、搜索等多个领域的商业巨头。而移动时代,则是网络时代的必然延伸,它代表着更便捷、更广阔、更深入的连接。

iOS 面试策略之系统框架-网络、推送与数据处理 - 图1

在这个背景之下,我们所开发的 App 或多或少会与网络相连。或是拉取服务器端数据来更新 UI,或是通过网络推送自己的消息,或是在手机端删除自己曾经的照片,或是打开音乐播放应用下载自己喜欢的歌曲。如何请求、接收、处理、发送数据,就是我们这节要讨论的内容。

计算机理论

1.谈谈 HTTP 中 GET 与 POST 的区别

关键词:#方向 #类型 #参数位置


  • 从方向上来看,GET 是从服务器端获取信息,POST 是向服务器端发送信息。

  • 从类型上来看,GET 处理静态和动态内容,POST 只处理动态内容。

  • 从参数位置来看,GET 的参数在其 URI 里,POST 的参数在它的包体里:从这个角度来看,POST 比 GET 更加安全隐秘。

  • GET 可以被缓存,可以被储存在浏览器历史中,其内容理论上有长度限制;POST 在这 3 点上恰恰相反。

2.谈谈 Session,Token,Cookie 的概念

关键词:#用户认证 #客户端 #服务器端


  • Session 是服务器端用来认证、追踪用户的数据结构。它通过判断客户端传来的信息确定用户,确定用户的唯一标识是客户端传来的 Session ID。

  • Token 是服务器端生成的一串字符串,是客户端进行请求的令牌、服务器端用以确定用户的唯一标识。Session ID 就经常被用作 Token 来使用。Token的出现避免了服务器频繁的查询用户名和密码,降低了数据库的查询压力。

  • Cookie 是客户端保存用户信息的机制。初次会话 HTTP 协议会在 Cookie 里记录一个 Session ID ,之后每次把 Session ID 发给服务器端。

  • Session 一般用于用户验证。它默认存在服务器的一个文件里,当然内存、数据库里也可以存储。

  • 若是客户端禁用了 Cookie,客户端会用 URL 重写技术,即会话时在 URL 的末尾加上 Session ID,并发送给服务器端。

3.在一个 HTTPS 连接的网站里,输入账号密码点击登录后,到服务器返回这个请求前,中间经历了什么

关键词:#锁 #客户端 #服务器端

1) 客户端打包请求。包括 url,端口啊,你的账号密码等等。账号密码登陆应该用的是 Post 方式,所以相关的用户信息会被加载到 body 里面。这个请求应该包含三个方面:网络地址,协议,资源路径。注意,这里是 HTTPS,就是 HTTP + SSL / TLS,在 HTTP 上又加了一层处理加密信息的模块(相当于是个锁)。这个过程相当于是客户端请求钥匙。

2) 服务器接受请求。一般客户端的请求会先发送到 DNS 服务器。 DNS 服务器负责将你的网络地址解析成 IP 地址,这个 IP 地址对应网上一台机器。这其中可能发生 Hosts Hijack 和 ISP failure 的问题。过了 DNS 这一关,信息就到了服务器端,此时客户端会和服务器的端口之间建立一个 socket 连接,socket 一般都是以 file descriptor 的方式解析请求。这个过程相当于是服务器端分析是否要向客户端发送钥匙模板。

3) 服务器端返回数字证书。服务器端会有一套数字证书(相当于是个钥匙模板),这个证书会先发送给客户端。这个过程相当于是服务器端向客户端发送钥匙模板。

4) 客户端生成加密信息。根据收到的数字证书(钥匙模板),客户端会生成钥匙,并把内容锁上,此时信息已经加密。这个过程相当于客户端生成钥匙并锁上请求。

5) 客户端发送加密信息。服务器端会收到由自己发送出去的数字证书加锁的信息。 这个时候生成的钥匙也一并被发送到服务器端。这个过程是相当于客户端发送请求。

6) 服务器端解锁加密信息。服务器端收到加密信息后,会根据得到的钥匙进行解密,并把要返回的数据进行对称加密。这个过程相当于服务器端解锁请求、生成、加锁回应信息。

7) 服务器端向客户端返回信息。客户端会收到相应的加密信息。这个过程相当于服务器端向客户端发送回应。

8) 客户端解锁返回信息。客户端会用刚刚生成的钥匙进行解密,将内容显示在浏览器上。

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群931 542 608来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

整个过程的流程图如下:

iOS 面试策略之系统框架-网络、推送与数据处理 - 图2

iOS 网络请求

4.请说明并比较以下类:URLSessionTask,URLSessionDataTask,URLSessionUploadTask,URLSessionDownloadTask

关键词:#URLSession


  • URLSessionTask 是个抽象类。通过实现它可以实例化任意网络传输任务,诸如请求、上传、下载任务。它的暂停(cancel)、继续(resume)、终止(suspend)方法有默认实现

  • URLSessionDataTask 负责 HTTP GET 请求。它是 URLSessionTask 的具体实现。一般用于从服务器端获取数据,并存放在内存中。

  • URLSessionUploadTask 负责 HTTP Post/Put 请求。它继承了 URLSessionDataTask。一般用于上传数据。

  • URLSessionDownloadTask 负责下载数据。它是 URLSessionTask 的具体实现。它一般将下载的数据保存在一个临时的文件中;在 cancel 后可将数据保存,并之后继续下载。

它们之间的关系如下图:

iOS 面试策略之系统框架-网络、推送与数据处理 - 图3

5. 什么是 Completion Handler?

关键词:#闭包

Completion Handler 一般用于处理 API 请求之后的返回数据。

当URLSessionTask 结束之后,无论成功或是报错,Completion Handler 一般都会接受 3 个参数:Data, URLResponse,Error,注意这 3 个参数都是 Optional。

在 Swift 中,Completion Handler 必须标明 @escaping。因为它总是在 API 请求之后才执行,也就是说方法已经返回才会涉及 Completion Handler,是个经典的逃逸闭包情况。

6. 代码实战:设计一个方法,给定 API 的网址,返回用户数据

关键词:#URLSessionDataTask

这道题目考察的是 URLSessionDataTask 的基本用法。下面是一种最简单粗暴的写法:

  1. func queryUser(url: String, completion: @escaping (_ user: User?, _ error : Error?) -> Void)) {
  2. guard let url = URL(string: url) else {
  3. return
  4. }
  5. URLSession.shared.dataTask(with: url) { data, response, error in
  6. if let error = error {
  7. DispatchQueue.main.async {
  8. completion(nil, error)
  9. }
  10. return
  11. } else if let data = data, let response = response as? HTTPURLResponse {
  12. DispatchQueue.main.async {
  13. completion(convertDataToUser(data), nil)
  14. }
  15. } else {
  16. DispatchQueue.main.async {
  17. completion(nil, NSError(“invalid response”))
  18. }
  19. }
  20. }.resume()
  21. }

上面的写法有很多问题,其中最主要的是:


  • url 出错处理不当。应该返回错误信息以方便日后调试,而不是应该 return

  • 用 URLSession 的单例不妥。这样每次请求创建一个 dataTask 是一种浪费,同时短时间内多次请求会不必要的造成服务器压力。正确的处理方法应该是每次请求都取消上一次请求(无论有无完成)。

  • 代码重复冗余。代码中多次用到了切换至主线程并调用闭包的过程。实际上我们可以将整个方法扩展为一个类,然后将返回值与成员变量结合起来使用。

除了以上 3 点,我们还可以进一步修正代码,增强其可读性,并完善其逻辑。修改后的代码如下:

  1. enum QueryError: String {
  2. case InvaldURL = Invalid URL”,
  3. case InvalidResponse = Invalid response
  4. }
  5. class QueryService {
  6. typealias QueryResult = (User?, String?) -> Void
  7. var user: User?
  8. var errorMessage: String?
  9. let defaultSession = URLSession(configuration: .default)
  10. var dataTask: URLSessionDataTask?
  11. func queryUsers(url: String, completion: @escaping QueryResult) {
  12. dataTask?.cancel()
  13. guard let url = URL(string: url) else {
  14. DispatchQueue.main.async {
  15. completion(user, QueryError.InvalidURL)
  16. }
  17. return
  18. }
  19. dataTask = defaultSession.dataTask(with: url) { [weak self] data, response, error in
  20. defer {
  21. self?.dataTask = nil
  22. }
  23. if let error = error {
  24. self?.errorMessage = error.localizedDescription
  25. } else if let data = data,
  26. let response = response as? HTTPURLResponse,
  27. response.statusCode == 200 {
  28. self?.user = convertDataToUser(data)
  29. } else {
  30. self?.errorMessage = QueryError.InvalidResponse
  31. }
  32. DispatchQueue.main.async {
  33. completion(self?.user,self?.errorMessage)
  34. }
  35. }.resume()
  36. }
  37. }

上面的修改方法主要针对一些硬伤。如果配合 Swift 的面向协议的编程来实现该 API,整个代码会更加灵活。

信息推送

7. iOS 开发中本地消息通知的流程是怎样的?

关键词:#UserNotifications

UserNotifications 框架是苹果针对远程和本地消息通知的框架。其流程主要分 4 步:

1) 注册。通过调用 requestAuthorization 这个方法,通知中心会向用户发送通知许可请求。在弹出的 Alert 中点击同意,即可完成注册。

2) 创建。首先设置信息内容 UNMutableNotificationContent 和触发机制 UNNotificationTrigger ;然后用这两个值来创建 UNNotificationRequest;最后将 request 加入到当前通知中心 UNUserNotificationCenter.current() 中。

3) 推送。这一步就是系统或者远程服务器推送通知。伴随着一声清脆的响声(或自定义的声音),通知对应的 UI 显示到手机界面的过程。

4) 响应。当用户看到通知后,点击进去会有相应的响应选项。设置响应选项是 UNNotificationAction 和 UNNotificationCategory。

如果你正在跳槽或者正准备跳槽不妨动动小手,添加一下咱们的交流群931 542 608来获取一份详细的大厂面试资料为你的跳槽多添一份保障。

加分回答:

远程推送的流程与本地推送大同小异,不同的是第 2 步创建,参数内容和消息创建都在服务器端完成,而不是在本地完成。

8.iOS 开发中远程消息推送的原理是怎样的?

关键词: #APNs Server

回答这道题目的关键在于理清 iOS 系统,App,APNs 服务器,以及 App 对应的客户端之间的关系。具体来说就是:

  1. App 向 iOS 系统申请远程消息推送权限。这与本地消息推送的注册是一样的;
  2. iOS 系统向 APNs(Apple Push Notification Service) 服务器请求手机的 device token,并告诉 App,允许接受推送的通知;
  3. App 将手机的 device token 传给 App 对应的服务器端;
  4. 远程消息由 App 对应的服务器端产生,它会先经过 APNs;
  5. APNs 将远程通知推送给响应手机。

具体的流程图如下:

iOS 面试策略之系统框架-网络、推送与数据处理 - 图4

数据处理

9.iOS 开发中如何实现编码和解码?

关键词: #Encodable #Decodable

编码和解码在 Swift 4 中引入了 Encodable 和 Decodable 这两个协议,而 Codable 是 Encodable 和 Decodable 的合集。在 Swift 中,Enum,Struct,Class 都支持 Codable。一个最简单的使用如下:

  1. enum Gender: String, Codable {
  2. case Male = Male
  3. case Female = Female
  4. }
  5. class User: Codable {
  6. let name: String
  7. let age: Int
  8. let gender: Gender
  9. init(name: String, age: Int, gender: Gender) {
  10. (self.name, self.age, self.gender) = (name, age, gender)
  11. }
  12. }

这样定义完成之后,我们就可以轻易的在 User 及其对应 JSON 数据进行编码和解码,示范代码如下:

  1. let userJsonString = """
  2. {
  3. "name": "Cook",
  4. "age": 58,
  5. "gender": "Male"
  6. }
  7. """
  8. // 从JSON解码到实例
  9. if let userJSONData = userJsonString.data(using: .utf8) {
  10. let userDecode = try? JSONDecoder().decode(User.self, from: userJSONData)
  11. }
  12. //从实例编码到JSON
  13. let userEncode = User(name: "Cook", age: 58, gender: Gender.Male)
  14. let userEncodedData = try? JSONEncoder().encode(userEncode)

追问:假如 JSON 的键值和对象的属性名不匹配该怎么办?

可以在对象中定义一个枚举(enum CodingKeys: String, CodingKey),然后将属性和 JSON 中的键值进行关联。

追问:假如 class 中某些属性不支持 Codable 该怎么办?

将支持 Codable 的属性抽离出来定义在父类中,然后在子类中配合枚举(enum CodingKeys),将不支持的 Codable 的属性单独处理。

10.谈谈 iOS 开发中数据持久化的方案

关键词: #plist #Preference #NSKeyedArchiver #CoreData

数据持久化就是将数据保存在硬盘中,这样无论是断网还是重启,我们都可以访问到之前保存的数据。iOS 开发中有以下几种方案:


  • plist。它是一个 XML 文件,会将某些固定类型的数据存放于其中,读写分别通过 contentsOfFile 和 writeToFile 来完成。一般用于保存 App 的基本参数。

  • Preference。它通过 UserDefaults 来完成 key-value 配对保存。如果需要立刻保存,需要调用 synchronize 方法。它会将相关数据保存在同一个 plist 文件下,同样是用于保存 App 的基本参数信息。

  • NSKeyedArchiver。遵循 NSCoding 协议的对象就就可以实现序列化。NSCoding 有两个必须要实现的方法,即父类的归档 initWithCoder 和解档 encodeWithCoder 方法。存储数据通过 NSKeyedArchiver 的工厂方法 archiveRootObject:toFile: 来实现;读取数据通过 NSKeyedUnarchiver 的工厂方法 unarchiveObjectwithFile:来实现。相比于前两者, NSKeyedArchiver 可以任意指定存储的位置和文件名。

  • CoreData。前面几种方法,都是覆盖存储。修改数据要读取整个文件,修改后再覆盖写入,十分不适合大量数据存储。CoreData 就是苹果官方推出的大规模数据持久化的方案。它的基本逻辑类似于 SQL 数据库,每个表为 Entity,然后我们可以添加、读取、修改、删除对象实例。它可以像 SQL 一样提供模糊搜索、过滤搜索、表关联等各种复杂操作。尽管功能强大,它的缺点是学习曲线高,操作复杂。

以上几种方法是 iOS 开发中最为常见的数据持久化方案。除了这些以外,针对大规模数据持久化,我们还可以用 SQLite3、FMDB、Realm 等方法。相比于 CoreData 和其他方案,Realm 以其简便的操作和丰富的功能广受很多开发者青睐。同时大公司诸如 Google 的 Firebase 也有离线数据库功能。其实没有最佳的方案,只有最合适的方案,应该根据实际开发的 App 来挑选合适的持久化方案。

小编推荐阅读