0%

Swift 4 的改变

测试均在playground中运行,playground文件下载地址:GitHub

基本语法方面的改变

Extension 中可以访问 Class 或者 Structprivate 属性

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct PersonStruct {
enum Gender: Int {
case male
case female
}

var name: String?
var gender: Gender?
private var age: Int?

init(name: String, gender: Gender, age: Int) {
self.name = name
self.gender = gender
self.age = age
}
}

extension PersonStruct {
func getPersonAge() -> Int? {
return age
}

mutating func setPersonAge(_ age: Int?) {
self.age = age
}
}


var p1 = PersonStruct.init(name: "周星驰", gender: .male, age: 60)
p1.getPersonAge()
p1.setPersonAge(55)
p1.getPersonAge()

Protocol 可以与 具体的类型 使用 & 符号进行结合

  • 在ObjC中,如果使用代理可以使用 id<SomeProtocol>类型,也可以使用 SomeClass<SomeProtocol> * 这种比较具体的类型,但是Swift3中没有对应的语法。

  • 现在Swift4中可以使用 SomeClass & SomeProtocol 进行连接成为一个类型。

  • 需要这种语法的原因:一个变量需要是SomeClass 的子类,用以调用 SomeClass 的方法,同时要符合 SomeProtocol 协议,已提供对应的方法

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protocol SomeProtocol {
func shouldGiveMeThisFunc()
}

class SomeClass {
func objectFunction() {
print("run object function")
}
}

class SomeChildClass: SomeClass, SomeProtocol {
func shouldGiveMeThisFunc() {
print("realize protocol function")
}
}

class TestClassAndProtocol {
// in swift3
var oldDelegate: SomeClass?
var oldDelegate_P: SomeProtocol? {
if oldDelegate == nil {
return nil
}
let delegate_P = oldDelegate as? SomeProtocol
assert(delegate_P != nil, "oldDelegate must is nil or a SomeClass's object and conforming to SomeProtocol")
return delegate_P
}

// in swift4
var delegate: (SomeClass & SomeProtocol)?
}

KeyPath语法的改变

Swift4 中语法形式

  • set: obj[keyPath: \Type.valueName] = someValue
  • get: someValue = obj[keyPath: \Type.valueName]

Swift 4 与 Swift 3 对比

  • 无需再使用@objc, dynamic等关键字
  • 支持struct和class
  • 具备类型推断

测试代码:Swift 3

1
2
3
4
@objc class PersonClass: NSObject {
@objc var name: String?
@objc var job: String?
}

测试代码:Swift 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let steven = PersonClass()
steven.setValue("Steven Jobs", forKey: #keyPath(PersonClass.name))
steven.setValue("Artist, Engineer", forKey: #keyPath(PersonClass.job))

let stevenName = steven.value(forKeyPath: #keyPath(PersonClass.name))
type(of: stevenName)

steven.name
steven.job


var personStruct = PersonStruct.init(name: "", gender: .male, age: 28)
personStruct[keyPath: \PersonStruct.name] = "Taylor Swift"
personStruct[keyPath: \PersonStruct.gender] = PersonStruct.Gender.female

// 不可以访问私有属性
//personStruct[keyPath: \PersonStruct.age] = 19

let personStructName = personStruct[keyPath: \PersonStruct.name]
type(of: personStructName)
personStruct.name
personStruct.gender

关于字符串

characters

之前String中需要使用characters属性调用的一些属性和函数,现在可以直接使用String直接调用,返回类型与以前相同,例如count

1
2
3
4
5
6
7
8
9
let str = "Hello,world"

// swift3
let count3 = str.characters.count
type(of: count3)

// swift4
let count4 = str.count
type(of: count4)

swift 4 中的 String.CharacterView.IndexDistance

1
2
3
4
5
6
7
8
9
10
11
extension String : StringProtocol, RangeReplaceableCollection {
........
public typealias IndexDistance = String.CharacterView.IndexDistance
........
}

extension String.CharacterView : BidirectionalCollection {
.....
public typealias IndexDistance = Int
.....
}

Collection 方法

for in, reversed, map, filter, reduce 等可以直接使用

Swift 1 中,String 遵循了 CollectionTypeSwift3+ 中的 Collection)协议,这意味着您可以对它们执行各种收集操作(比如 forEach()filter() 等)。 您仍然可以在 Swift 2 & 3 中进行此操作,通过访问 characters 属性,但这很快会导致阅读代码更难。

Swift 4 中字符串再次遵循了 Collection 协议,这意味着你可以简单地将它们视为字符集合

1
2
3
4
5
str.forEach { (c) in
print(c)
}

let reversedStr = String.init(str.reversed())

子串和语法糖 .....<

swift3 中取子串的方法在swift4中已经废弃(但是也可以用)

1
2
3
str.substring(to: `String.Index`)
str.substring(from: `String.Index`)
str.substring(with: `Range<String.Index>`)

swift4中截取子串的方法

1
2
3
4
5
6
7
8
if let range = str.range(of: ",") {
let subString1 = str[..<range.lowerBound]
type(of: subString1)
let subString2 = str[range.upperBound...]
}

let index = String.Index.init(encodedOffset: 3)
let substr = str[...index]

关于swift 4 中的 SubSequenceSubstring , 部分定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extension String : StringProtocol, RangeReplaceableCollection {
......
public typealias SubSequence = Substring
......
}

public struct Substring : StringProtocol {
.....
public typealias SubSequence = Substring
.....
}

extension String.UnicodeScalarView {
......
public typealias SubSequence = Substring.UnicodeScalarView
......
}

extension Substring.UTF8View : BidirectionalCollection {
.....
public typealias SubSequence = Substring.UTF8View
.....
}
  1. 多行字符串

类似于Python中的多行字符串一样。

内部也可以使用 \( someValue ) 调用变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let multiLineString =
"""
// Some Code
extension String : StringProtocol, RangeReplaceableCollection {

/// A type that represents the number of steps between two `String.Index`
/// values, where one value is reachable from the other.
///
/// In Swift, *reachability* refers to the ability to produce one value from
/// the other through zero or more applications of `index(after:)`.
public typealias IndexDistance = String.CharacterView.IndexDistance

/// A sequence that represents a contiguous subrange of the collection's
/// elements.
public typealias SubSequence = Substring
.....
}
===========
Get Value:
\(str)
===========
"""
print(multiLineString)

Codable

Codabel定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// A type that can encode itself to an external representation.
public protocol Encodable {

/// Encodes this value into the given encoder.
///
/// If the value fails to encode anything, `encoder` will encode an empty
/// keyed container in its place.
///
/// This function throws an error if any values are invalid for the given
/// encoder's format.
///
/// - Parameter encoder: The encoder to write data to.
public func encode(to encoder: Encoder) throws
}

/// A type that can decode itself from an external representation.
public protocol Decodable {

/// Creates a new instance by decoding from the given decoder.
///
/// This initializer throws an error if reading from the decoder fails, or
/// if the data read is corrupted or otherwise invalid.
///
/// - Parameter decoder: The decoder to read data from.
public init(from decoder: Decoder) throws
}

/// A type that can convert itself into and out of an external representation.
public typealias Codable = Decodable & Encodable

下面测试代码的辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum JSONDataError: Error {
case pathError
}

func getJSONData(forResource resource: String?, ofType type: String?) throws -> Data {
guard let jsonFile = Bundle.main.path(forResource: resource, ofType: type) else {
throw JSONDataError.pathError
}
let jsonURL = URL.init(fileURLWithPath: jsonFile)
return try Data.init(contentsOf: jsonURL)
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .iso8601

辅助文件dog.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
{
"name": "Dog_1",
"age": 5,
"gender": 0,
"nikeName": "Nikee"
},
{
"name": "Dog_2",
"age": 3,
"gender": 1
},
{
"name": "Dog_3",
"age": 6,
"gender": 2
}
]

辅助文件human.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"userName": "Steven Jobs",
"userAge": 50,
"birthday": "1967-06-21T15:29:32Z",
"dogs": [
{
"name": "HDog_1",
"age": 15,
"gender": 0,
"nikeName": "Nikee"
},
{
"name": "HDog_2",
"age": 13,
"gender": 1
},
{
"name": "HDog_3",
"age": 16,
"gender": 2
}
]
}

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct Dog: Codable {
enum Gender: Int, Codable {
case unknow = 0
case male = 1
case female = 2
}
var name: String
var age: Int
var gender: Gender

// 可能存在也可能不存在的值必须使用Optional
var nikeName: String? = "default"

// 解析json
static func decodeDogArray(forResource resource: String) -> [Dog]? {
guard let dogJsonData = try? getJSONData(forResource: resource, ofType: "json"),
let dogArr = try? decoder.decode([Dog].self, from: dogJsonData) else {
print("Get JSON Data or Decoder Error")
return nil
}
return dogArr
}

// 序列化为json
static func encodeDogArray(_ dogArr: [Dog]) throws -> Data {
return try encoder.encode(dogArr)
}
}

// 示例
if let dogArr = Dog.decodeDogArray(forResource: "dog") {
for dog in dogArr {
print("\(dog.name) \t \(dog.age) \t \(dog.gender) \t \(dog.nikeName ?? "nil")")
}

if let data = try? Dog.encodeDogArray(dogArr) {
print(String(data: data, encoding: .utf8) ?? "Error")
}
}

嵌套的Codable自定义Key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Human: Codable {
var name: String
var age: Int
var dogs: [Dog]
var birthday: Date

enum CodingKeys: String, CodingKey {
case name = "userName"
case age = "userAge"
case dogs
case birthday
}

// 解析 json
class func decode(forResource resource: String) -> Human? {
guard let humanJsonData = try? getJSONData(forResource: resource, ofType: "json"),
let human = try? decoder.decode(Human.self, from: humanJsonData) else {
print("Get JSON Data or Decoder Error")
return nil
}
return human
}

// 序列化为json
func encode() throws -> Data {
return try encoder.encode(self)
}
}

// 示例
if let human = Human.decode(forResource: "human") {
print(human.name)
print(human.age)
print(human.birthday)
for dog in (human.dogs) {
print("\(dog.name) \t \(dog.age) \t \(dog.gender) \t \(dog.nikeName ?? "nil")")
}

if let data = try? human.encode() {
print(String(data: data, encoding: .utf8) ?? "Error")
}
}

特殊数据类型的处理

可以去配置encoder和decoder对应的属性用来解析这些数据类型,下面是对应的属性,皆为枚举类型,按自己需要进行配置

Date
  • encoder.dateEncodingStrategy

JSONEncoder.DateEncodingStrategy

1
2
3
4
5
6
7
8
9
10
/// The strategy to use for encoding `Date` values.
public enum DateEncodingStrategy {
case deferredToDate /// Defer to `Date` for choosing an encoding. This is the default strategy.
case secondsSince1970 /// Encode the `Date` as a UNIX timestamp (as a JSON number).
case millisecondsSince1970 /// Encode the `Date` as UNIX millisecond timestamp (as a JSON number).
case iso8601 /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
case formatted(DateFormatter) /// Encode the `Date` as a string formatted by the given formatter.
case custom((Date, Encoder) throws -> Swift.Void) /// Encode the `Date` as a custom value encoded by the given closure.
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
}
  • decoder.dateDecodingStrategy

JSONDecoder.DateDecodingStrategy

1
2
3
4
5
6
7
8
9
/// The strategy to use for decoding `Date` values.
public enum DateDecodingStrategy {
case deferredToDate /// Defer to `Date` for decoding. This is the default strategy.
case secondsSince1970 /// Decode the `Date` as a UNIX timestamp from a JSON number.
case millisecondsSince1970 /// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
case iso8601 /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
case formatted(DateFormatter) /// Decode the `Date` as a string parsed by the given formatter.
case custom((Decoder) throws -> Date) /// Decode the `Date` as a custom value decoded by the given closure.
}
Data
  • encoder.dataEncodingStrategy

JSONEncoder.DataEncodingStrategy

1
2
3
4
5
6
7
/// The strategy to use for encoding `Data` values.
public enum DataEncodingStrategy {
case deferredToData /// Defer to `Data` for choosing an encoding.
case base64 /// Encoded the `Data` as a Base64-encoded string. This is the default strategy.
case custom((Data, Encoder) throws -> Swift.Void) /// Encode the `Data` as a custom value encoded by the given closure.
/// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place.
}
  • decoder.dataDecodingStrategy

JSONDecoder.DataDecodingStrategy

1
2
3
4
5
6
/// The strategy to use for decoding `Data` values.
public enum DataDecodingStrategy {
case deferredToData /// Defer to `Data` for decoding.
case base64 /// Decode the `Data` from a Base64-encoded string. This is the default strategy.
case custom((Decoder) throws -> Data) /// Decode the `Data` as a custom value decoded by the given closure.
}
Float

encoder.nonConformingFloatEncodingStrategy

JSONEncoder.NonConformingFloatEncodingStrategy

1
2
3
4
5
6
7
/// The strategy to use for non-JSON-conforming floating-point values (IEEE 754 infinity and NaN).
public enum NonConformingFloatEncodingStrategy {
case `throw` /// Throw upon encountering non-conforming values. This is the default strategy.
case convertToString(positiveInfinity: String,
negativeInfinity: String,
nan: String) /// Encode the values using the given representation strings.
}

decoder.nonConformingFloatDecodingStrategy

JSONDecoder.NonConformingFloatDecodingStrategy

1
2
3
4
5
6
7
/// The strategy to use for non-JSON-conforming floating-point values (IEEE 754 infinity and NaN).
public enum NonConformingFloatDecodingStrategy {
case `throw` /// Throw upon encountering non-conforming values. This is the default strategy.
case convertFromString(positiveInfinity: String,
negativeInfinity: String,
nan: String) /// Decode the values from the given representation strings.
}