如果说移动时代的前身是什么,我想一个可能的答案就是网络时代。网络的兴起,让所有设备相连成为了可能,也催生了电商、社交、搜索等多个领域的商业巨头。而移动时代,则是网络时代的必然延伸,它代表着更便捷、更广阔、更深入的连接。
在这个背景之下,我们所开发的 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 网络请求
4.请说明并比较以下类:URLSessionTask,URLSessionDataTask,URLSessionUploadTask,URLSessionDownloadTask
关键词:#URLSession
URLSessionTask 是个抽象类。通过实现它可以实例化任意网络传输任务,诸如请求、上传、下载任务。它的暂停(cancel)、继续(resume)、终止(suspend)方法有默认实现
URLSessionDataTask 负责 HTTP GET 请求。它是 URLSessionTask 的具体实现。一般用于从服务器端获取数据,并存放在内存中。
URLSessionUploadTask 负责 HTTP Post/Put 请求。它继承了 URLSessionDataTask。一般用于上传数据。
URLSessionDownloadTask 负责下载数据。它是 URLSessionTask 的具体实现。它一般将下载的数据保存在一个临时的文件中;在 cancel 后可将数据保存,并之后继续下载。
它们之间的关系如下图:
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 的基本用法。下面是一种最简单粗暴的写法:
func queryUser(url: String, completion: @escaping (_ user: User?, _ error : Error?) -> Void)) {
guard let url = URL(string: url) else {
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
DispatchQueue.main.async {
completion(nil, error)
}
return
} else if let data = data, let response = response as? HTTPURLResponse {
DispatchQueue.main.async {
completion(convertDataToUser(data), nil)
}
} else {
DispatchQueue.main.async {
completion(nil, NSError(“invalid response”))
}
}
}.resume()
}
上面的写法有很多问题,其中最主要的是:
url 出错处理不当。应该返回错误信息以方便日后调试,而不是应该 return
用 URLSession 的单例不妥。这样每次请求创建一个 dataTask 是一种浪费,同时短时间内多次请求会不必要的造成服务器压力。正确的处理方法应该是每次请求都取消上一次请求(无论有无完成)。
代码重复冗余。代码中多次用到了切换至主线程并调用闭包的过程。实际上我们可以将整个方法扩展为一个类,然后将返回值与成员变量结合起来使用。
除了以上 3 点,我们还可以进一步修正代码,增强其可读性,并完善其逻辑。修改后的代码如下:
enum QueryError: String {
case InvaldURL = “Invalid URL”,
case InvalidResponse = “Invalid response”
}
class QueryService {
typealias QueryResult = (User?, String?) -> Void
var user: User?
var errorMessage: String?
let defaultSession = URLSession(configuration: .default)
var dataTask: URLSessionDataTask?
func queryUsers(url: String, completion: @escaping QueryResult) {
dataTask?.cancel()
guard let url = URL(string: url) else {
DispatchQueue.main.async {
completion(user, QueryError.InvalidURL)
}
return
}
dataTask = defaultSession.dataTask(with: url) { [weak self] data, response, error in
defer {
self?.dataTask = nil
}
if let error = error {
self?.errorMessage = error.localizedDescription
} else if let data = data,
let response = response as? HTTPURLResponse,
response.statusCode == 200 {
self?.user = convertDataToUser(data)
} else {
self?.errorMessage = QueryError.InvalidResponse
}
DispatchQueue.main.async {
completion(self?.user,self?.errorMessage)
}
}.resume()
}
}
上面的修改方法主要针对一些硬伤。如果配合 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 对应的客户端之间的关系。具体来说就是:
- App 向 iOS 系统申请远程消息推送权限。这与本地消息推送的注册是一样的;
- iOS 系统向 APNs(Apple Push Notification Service) 服务器请求手机的 device token,并告诉 App,允许接受推送的通知;
- App 将手机的 device token 传给 App 对应的服务器端;
- 远程消息由 App 对应的服务器端产生,它会先经过 APNs;
- APNs 将远程通知推送给响应手机。
具体的流程图如下:
数据处理
9.iOS 开发中如何实现编码和解码?
关键词: #Encodable #Decodable
编码和解码在 Swift 4 中引入了 Encodable 和 Decodable 这两个协议,而 Codable 是 Encodable 和 Decodable 的合集。在 Swift 中,Enum,Struct,Class 都支持 Codable。一个最简单的使用如下:
enum Gender: String, Codable {
case Male = “Male”
case Female = “Female”
}
class User: Codable {
let name: String
let age: Int
let gender: Gender
init(name: String, age: Int, gender: Gender) {
(self.name, self.age, self.gender) = (name, age, gender)
}
}
这样定义完成之后,我们就可以轻易的在 User 及其对应 JSON 数据进行编码和解码,示范代码如下:
let userJsonString = """
{
"name": "Cook",
"age": 58,
"gender": "Male"
}
"""
// 从JSON解码到实例
if let userJSONData = userJsonString.data(using: .utf8) {
let userDecode = try? JSONDecoder().decode(User.self, from: userJSONData)
}
//从实例编码到JSON
let userEncode = User(name: "Cook", age: 58, gender: Gender.Male)
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 来挑选合适的持久化方案。