当然我只是一只半吊子半桶水二把刀三脚猫会写两行代码的家伙。不过只要上过 App Store 或者靠 iOS 拿过工资就可以腆着脸自称 iOS Developer 了吧 wwwww
# Developer's perspective
看了这两个页面:
Apple Developer Documentation apple/swift-evolution<img src="https://pic4.zhimg.com/v2-0d937cbb1b53f8aad7469ed303d9465f_b.png" data-rawwidth="1436" data-rawheight="877" class="origin_image zh-lightbox-thumb" width="1436" data-original="https://pic4.zhimg.com/v2-0d937cbb1b53f8aad7469ed303d9465f_r.png">
变化不小,所有的 Package (Library?) 都有修改。不过根据经验就知道水果的 API 变化很多都是瞎折腾。说几个我觉得有用的东西:
## Swift 4
把这个提出来说是因为对于普通开发者来说用处最大。说实在的各路的新 A-Kit B-Kit Core-C 看起来酷炫实际上并没有公司敢于用在核心功能上的,毕竟还有那么多不愿意升级 iOS 版本的用户。但是 Swift 只要用了就兼容所有 iOS 版本,不仅让人说一句 666. 只不过是 App 大了几个 MB,who cares.
### 最喜欢的功能:`Codable`
辣鸡知乎编辑器什么时候支持 Markdown? 就算写不出来能不能加个 inline code 的按钮? 不能什么功能微信不做你就不做啊。 @知乎小管家 @周源 @黄继新 @any-zhihu-guys
#### 背景:data type serialization
现在世界上所有认真的 API 都是 JSON 的了。在语言/标准库层面提供 JSON 基础设施也成为了标配,而 Swift 之前一直都只是 bridge 了 Foundation 的 JSON 解析,在 Swift 内非常水土不服,很难用。比如我们有这样一个 API:
POST xxx.com/user?gender=female&loveRuiyang=yes
{
"user": [
{
"name": "Aragaki Yui",
"age": 28
},
{
"name": "Miao",
"age": 20
}
]
}
为了获得首个用户的名字,在纯粹的 Swift 里面我们只能这么写:
if let statusesArray = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]],
let user = statusesArray[0]["user"] as? [String: Any],
let username = user["name"] as? String {
// Finally we got the username
print("\(username) loves Ruiyang!")
}
以及什么?知乎编辑器指定语言居然没有 Swift? 说好的都是果粉呢?
就非常恶心。这个恶心是为了绕开 Swift 的强大的静态类型检查和 null check. 稍微好一点的写法是:
if let JSONObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]],
let username = (JSONObject[0]["user"] as? [String: Any])?["name"] as? String {
// There's our username
}
但是第一,这个还是很恶心。第二,这个 optional chaining 只能拿到一个字段,既不能 deserialize 成为数据类也不能处理 array 中的多个 instance。
#### 用库
为了解决这个问题,我们一般会去用 SwiftyJSON:
let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string {
//Now you got your value
}
SwiftyJSON 定义了一个 enum 类型,非常自如地平衡了类型检查和 JSON 本质动态的问题。但是如果我们想 deserialize/deserialize 还是要写一大堆 boilerplate:
struct User {
let name:String
let age:Int
}
extension User {
init(_ json:JSON) {
self.name = json["name"]
self.age = json["age"]
//what if there are 100 fields?
}
func toJSON() -> JSON {
//make a dict and make a JSON
//oh ugly
}
}
这就很烦。一般来说,如果一个语言设计之初有一个目标,那么随着语言的进化,任何偏离目标的改动都会完蛋,比如 C++ 的██,██和███;C# 的██。Swift 也是如此。作为一种想要多加利用静态分析,并在工程上 elegant and simple 的语言,反复写 boilerplate 肯定是要被拍死在沙滩上的。
#### 反射
之前我们想要利用 reflection 解决问题:evermeer/EVReflection, 但是很可惜 Swift 就没有好好设计 reflection 的东西,可以说 Mirror 这一套东西完全是给 playground 准备的,很残。因而这个库也不幸地失败了,官方说明如下:
It's not possible in Swift to use .setObjectForKey for:
- nullable type fields like Int?
- properties based on an enum
- an Array of nullable objects like [MyObject?]
- a Set like Set
- generic properties like var myVal:T = T()
- structs like CGRect or CGPoint
尽管有这样那样的 workground, 这样严重的限制使得本库成为了工程上不实用的东西。
可以想见,尽管不能反射了,我们仍然可以使用 code generation 来解决问题。这样的难度在于每次改动数据结构都要 regenerate 一遍,而且有种种可能的 hidden bugs, 比如 data type declaration 和 generated code 版本不一致这样的,很难发现。最好的还是让 Swift 自己去 generate 这东西。于是——
#### Codable
终于,在所有程序员都在哭着写 boilerplate 之后,水果终于认识到第三方库不能很好地解决这个问题。他们推出了自己的 (hidden) code generation solution.
proposal 见 apple/swift-evolution 和 apple/swift-evolution.
首先,我们不需要写任何 encode 和 decode 的代码了,只要继承了 Codable, 编译器会自动根据 field name 来为我们进行序列化。官方代码示例:
// Codable implies Encodable and Decodable
// If all properties are Codable, protocol implementation is automatically generated by the compiler:
public struct Location : Codable {
public let latitude: Double
public let longitude: Double
}
public enum Animal : Int, Codable {
case chicken = 1
case dog
case turkey
case cow
}
public struct Farm : Codable {
public let name: String
public let location: Location
public let animals: [Animal]
}
只要一个 class/struct/enum 的所有 field 都遵循 Codable, 它自己就可以无痛遵循 Codable. 简直无比感人。尤其是所有有意义的内置类型(String, Int, Double, ..., Date, Array, Dictionary) 都已经写好了 Codable(我要哭出来惹)
现在想要 Encode 只需要一行:
let payload: Data = try JSONEncoder().encode(farm)
而 Decode 只需要另一行:
let farm = try JSONDecoder().decode(Farm.self, from: payload)
(水果爸爸好)
你还可以自定义 Serialization 函数,并且可以定义只能 Encode 和只能 Decode 的东西。还可以定义序列时的 number limit 等等细节。反正工程上的东西水果都帮你想好了。
讲道理这个东西在理论上没什么了不得的地方,但是工程上简直让人泪流满面。太感人了。
不过我不喜欢 Swift 的异常处理。实际上……我不喜欢任何异常处理。以前在自己的代码里不会有 try, 但是现在看来代码中即将充满了 try. Sad.
<img src="https://pic1.zhimg.com/v2-82665133c539a0459c1fd68d8e3529f0_b.png" data-rawwidth="84" data-rawheight="95" class="content_image" width="84">
### Protocols, Associated Types and Constraints
直到 Swift 3, 水果号称的 Protocol-oriented Programming 也没成为完全体。一个真正基于 Protocol 的代码中应该可以有这样的代码:
protocol JSONEncodable {
func toJSON() -> String
}
extension Int : JSONEncodable {
func toJSON() -> String {
return "\(self)"
}
}
//some implementations for string, float, date
extension Array : JSONEncodable where Element : JSONEncodable {
func toJSON() -> String {
let content = self.map { $0.toJSON() }.joined(separator: ",")
return "[\(content)]"
}
}
然鹅……事实是残酷的。
<img src="https://pic2.zhimg.com/v2-7fd25f65c5e0efb0ef833468a6cdc211_b.png" data-rawwidth="745" data-rawheight="124" class="origin_image zh-lightbox-thumb" width="745" data-original="https://pic2.zhimg.com/v2-7fd25f65c5e0efb0ef833468a6cdc211_r.png">extension 中是不能加 where 的。这就很难受了。之前一直有小道消息说本来会有这个功能只是没做完。现在终于有了。请看:
extension 中是不能加 where 的。这就很难受了。之前一直有小道消息说本来会有这个功能只是没做完。现在终于有了。请看:
apple/swift-evolutionapple/swift-evolution
<img src="https://pic3.zhimg.com/v2-fc3e969910a617f463400acd0b425b46_b.png" data-rawwidth="73" data-rawheight="78" class="content_image" width="73">
