All articles have been moved to my personal site: me.harley-xk.studio, please visit the comments

WWDC has been around for a while now, but I’ve had some time to take a closer look. For developers, the biggest news of WWDC this year was Swift 4.

Swift has finally stabilized at the API level after three years of development. Migrating code from Swift 3 to Swift 4 finally doesn’t have to be as painful as 2 to 3. This is a big plus for developers and should attract a lot of developers who are still on the fence about Swift.

In addition, Swift 4 introduced a number of new features, such as the fileprivate keyword limits are more precise; Declaring properties can finally restrict both types and protocols; The new KeyPath API and so on, we can see that the Swift ecosystem is getting better and better, and Swift itself is getting stronger and stronger.

The new features that Swift 4 brings to the table are most striking for me and are cidable for Codable protocols. Here’s what I learned about them in detail:

A simple introduction

Swift has long been a problem for weak types like JSON due to its type-safety features. Many of the best third-party libraries on the market have made this possible, but there are still some bugs that are hard to overcome. Codable is the solution to this problem. Second, it also provides us with new ideas to solve similar problems.

Codable is a combination protocol that is made up of Decodable and Encodable protocols:

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

/// A type that can encode itself to an external representation.
public protocol Encodable {
    public func encode(to encoder: Encoder) throws
}

/// A type that can decode itself from an external representation.
public protocol Decodable {
    public init(from decoder: Decoder) throws
}
Copy the code

Encodable and Decodable define encode(to:) and init(from:) protocol functions for archiving data models and parsing and instantiating external data, respectively. The most common scenarios are interface JSON data parsing and model creation. But Codable powers don’t stop there, more on that later.

Parsing JSON objects

Let’s take a look at Decodable’s parsing of JSON data objects. Swift does most of the work for us. Basic datatypes in Swift like String, Int, and Float are already Codable, so if your datatypes only contain attributes for these basic datatypes, All you need to do is add the Codable protocol to your type declaration, which is one of Codable’s greatest strengths: you don’t need to write any code to actually implement it.

For example, we have the following JSON string for student information:

Let jsonString = "" "{" name ":" xiao Ming ", "age" : 12, "weight" : 43.2} "" "Copy the code

In this case, we simply define a Student type that implements the Decodable protocol. Swift 4 already provides us with the default implementation:

struct Student: Decodable {   
    var name: String
    var age: Int
    var weight: Float
}
Copy the code

Then, all it takes is one line of code to parse Xiao Ming:

let xiaoming = try JSONDecoder().decode(Student.self, from: jsonString.data(using: .utf8)!)
Copy the code

It is important to note that the decode function requires the external Data type to be Data. If it is a string, it needs to be converted to Data before operation. However, network frameworks such as Alamofire return Data of Data type. In addition, the decode function is marked as throws. If parsing fails, an exception will be thrown. To ensure the robustness of the program, do catch is required to process the exception:

do {
    let xiaoming = try JSONDecoder().decode(Student.self, from: data)
} catch {
    // Exception handling
}
Copy the code

Special data type

There are times when basic data types alone won’t get the job done, and more often than not, special data types need to be used. Swift also provides a default Codable implementation for many specialized data types, but with some limitations.

The enumeration
{... "gender": "male" ... }Copy the code

Gender is a very common piece of information and we often define it as an enumeration:

enum Gender {
    case male
    case female
    case other
}
Copy the code

Enumerations also implement Codable by default, but if we declare Gender enumerations support Codable, the compiler will tell us that no implementation is provided:

There is a limitation: Enumeration types need to be declared to support Codable by default, and primitive values need to support Codable:

enum Gender: String.Decodable {
    case male
    case female
    case other
}
Copy the code

Because of the implicit assignment nature of the enumeration type raw value, parsing can be done without explicitly specifying the raw value if the enumeration value’s name is the same as the value in the corresponding JSON.

Bool

Our data model now has a new field to indicate whether a student is a Young Pioneer:

{... "isYoungPioneer": true ... }Copy the code

In this case, we can declare the corresponding attribute directly:

var isYoungPioneer: Bool
Copy the code

Bool parsing only supports true/false Bool parsing by default. For some backend frameworks that use the 0/1 format to represent Bool values, the conversion can only be done after Int parsing, or custom Codable protocols are available.

Date resolution strategy

Enumeration and Bool, another commonly used special type is Date, Date type particularity is that it has a variety of format standards and representation, from numbers to strings can be said to be multifaried, parsing Date type is any framework of the same type must face the topic.

Codable has a solution for this: Define a resolution strategy. The JSONDecoder class declares an attribute of type DateDecodingStrategy, which is used to define the parse strategy for type Date.

/// The strategy to use for decoding `Date` values.
public enum DateDecodingStrategy {
    
    /// Defer to `Date` for decoding. This is the default strategy.
    case deferredToDate
    
    /// Decode the `Date` as a UNIX timestamp from a JSON number.
    case secondsSince1970
    
    /// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
    case millisecondsSince1970
    
    /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
    case iso8601
    
    /// Decode the `Date` as a string parsed by the given formatter.
    case formatted(DateFormatter)
    
    /// Decode the `Date` as a custom value decoded by the given closure.
    case custom((Decoder) throws -> Date)}Copy the code

Codable supports several common formatting standards. The default policy is deferredToDate, which is the number of seconds since **UTC time January 1, 2001. In the corresponding Date type timeIntervalSinceReferenceDate this attribute. For example, 519751611.125429 results in 2017-06-21 15:26:51 +0000.

Zipgex, zipgex, zipgex, zipgex, zipgex, zipgex, zipgex, zipgex, zipgex, zipgex, zipgex

Codable offers two strategies for customizing Date formats:

  • formatted(DateFormatter)This policy is setDateFormatterTo specify theDateformat
  • custom((Decoder) throws -> Date) customThe strategy accepts a(Decoder) -> DateClosure, which basically leaves the parsing task entirely up to us to implement, has a high degree of freedom
Fractional parsing strategy

Decimal types (Float/Double) also implement the Codable protocol by default, but decimal types have many special values in Swift, such as PI (float.pi) and so on. Here are the other two attributes, starting with definitions:

/// Positive infinity.
///
/// Infinity compares greater than all finite numbers and equal to other
/// infinite values.
public static var infinity: Double { get }

/// A quiet NaN ("not a number").
///
/// A NaN compares not equal, not greater than, and not less than every
/// value, including itself. Passing a NaN to an operation generally results
/// in NaN.
public static var nan: Double { get }
Copy the code

Infinity means plus infinity (minus infinity written: -infinity), nan means no value, and these special values cannot be represented by numbers, but in Swift they are real values that can be evaluated, compared, etc. Different languages and frameworks have similar implementations of this, but the expression may not be exactly the same, so special transformations may be required if such values need to be parsed in certain scenarios.

Codable implementation is simple and crude, has an attribute nonConformingFloatDecodingStrategy JSONDecoder types, is used to specify inconsistent decimal conversion strategy, defaults to throw, namely direct throw an exception, parse failure. Another option is to specify the representation of infinity, -infinity, and nan:

let decoder = JSONDecoder()
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "infinity", negativeInfinity: "-infinity", nan: "nan")
// Another way to express it
/ / decoder. NonConformingFloatDecodingStrategy =. ConvertFromString (positiveInfinity: "up", negativeInfinity: "- up," nan: "n/a")
Copy the code

Currently, it seems that only these three special values can be converted, but the use of these special values should be very limited, at least in my own five or six years of development.

Custom data types

Pure basic data types still don’t work well, and the data structures of real projects are often complex, with one data type often containing attributes of another. For example, in our example, each student’s information also contains information about the school:

{
    "name": "Xiao Ming"."age": 12."weight": 43.2
    "school": {
      "name": "City No. 1 Middle School"."address": 66 Renmin Zhong Lu, XX City}}Copy the code

Student = School = Student = School

struct School: Decodable {
	var name: String
	var address: String
}
struct Student: Decodable {   
    var name: String
    var age: Int
    var weight: Float
    var school: School
}
Copy the code

Because all basic types implement the Codable protocol, School, like Student, does not need to manually provide any implementation to get the default Codable implementation if all attributes implement the Codable protocol. Because School supports the Codable protocol, which ensures that the Student still has the default Codable implementation, there is no additional code required to parse nested types.

Custom field

In many cases, the front and back ends may not be completely in step with each other. So often the data structure given by the back end will have some more personalized field names, of course sometimes we ourselves. While some frameworks (like the One I’m using, Laravel) tend to use snakelike nomenclature, the iOS code specification recommends camelback nomenclature. In order to maintain code style and platform characteristics, you must specify field names yourself.

Before we dive into custom fields, we need to dive into the basics of how Codable attributes are named and assigned by default. A look at the underlying C++ source code shows that CodingKeys automatically generates an enumeration type definition for CodingKeys based on type properties when Codable code is compiled. This is an enumeration type that takes String as its original value. Corresponds to the name of each attribute. The protocol is then automatically generated for init(from:) and encode(to:) for each type that is declared to implement the Codable protocol.

So we can implement the type definition of CodingKeys ourselves, and specify different raw values for the attributes to implement custom field resolution. Instead of regenerating a default, the compiler will simply adopt the scheme we already implemented.

For example, Student needs to add a date of birth attribute. The backend interface uses a snake name. JSON data is as follows:

{
    "name": "Xiao Ming"."age": 12."weight": 43.2
    "birth_date": "1992-12-25"
}
Copy the code

Add the CodingKeys definition to the Student type declaration and set birthday to birth_date:

struct Student: Codable {...var birthday: Date
	
	enum CodingKeys: String.CodingKey {
        case name
        case age
        case weight
        case birthday = "birth_date"}}Copy the code

Note that even if the property names are the same as the field names in JSON, these properties cannot be omitted if you have custom CodingKeys, Error: Type ‘Student’ does not conform to protocol ‘Codable’ However, it is theoretically possible to declare CodingKeys complete with other default properties at compile time. Expect further optimizations from Apple.

An optional value

Some fields may be null. For example, if the birth date of some students is not counted, the data returned by the background can either contain no birth_date field for data without birth date, or specify a null value: “birth_date”: null

In both cases, simply declare the birthday attribute as an optional value to resolve properly:

.var birthday: Date? .Copy the code

Parsing JSON arrays

Arrays are also supported for Codable, but only if the elements in the array implement Codable. Arrays automatically receive an implementation of the Codable protocol.

When using JSONDecoder to parse, you only need to specify the corresponding array type:

do {
    let students = try JSONDecoder().decode([Student].self, from: data)
} catch {
    // Exception handling
}
Copy the code

Archived data

Archived data uses the Encodable protocol, which is consistent with Decodable.

Export to JSON

Converting the data model to JSON is similar to the parsing process. Replace JSONDecoder with JSONEncoder:

let data = try JSONEncoder().encode(xiaomin)
let json = String(data: data, encoding: .utf8)
Copy the code

JSONEncoder has an outputFormatting property that specifies the formatting style of the output JSON.

public enum OutputFormatting {
    
    /// Produce JSON compacted by removing whitespace. This is the default formatting.
    case compact
    
    /// Produce human-readable JSON with indented output.
    case prettyPrinted
}
Copy the code
  • compact

    The default Compact style removes all formatting information from JSON data, such as line breaks, whitespace, and indentation, to reduce the amount of space taken up by JSON data. If the exported JSON data is used for communication between user programs and requires little reading, you are recommended to use this setting.

  • prettyPrinted

    If the output JSON data is intended for reading, select prettyPrinted, and the output is automatically formatted with newlines, Spaces, and indentation for easy reading. Similar to the JSON typesetting style used in this article.

PropertyList

The Codable protocol doesn’t just support JSON data. It also supports property lists, the plist file format commonly used on Macs. This is useful when we do things like system configuration.

Property lists are parsed and archived for the simplicity and ease of use of the Apple API, which uses JSON in a consistent way and requires no modifications to Codable protocols. Just replace JSONEncoder and JSONDecoder with the corresponding PropertyListEncoder and PropertyListDecoder.

Attribute list is a special format standard XML document in essence, so theoretically, we can refer to the Decoder/Encoder provided by the system to achieve their own arbitrary format of data serialization and deserialization scheme. Apple is also expected to extend the capabilities of other data formats by implementing new Decoder/Encoder classes. That’s what Codable does more than that. It has a lot of room to scale.

conclusion

That’s all for Codable core usage. Compared to several frameworks commonly used today:

ObjectMapper uses the generic mechanism to parse the model, but it needs to manually write the mapping relationship for each attribute, which is tedious. I also used this framework in my own project, and later made some optimization of it, using reflection mechanism to achieve automatic resolution of basic data types, but custom types still need to manually write mapping, and must inherit the Model base class that has automatic resolution, which is more limited.

SwiftyJSON is a simple way to parse JSON into dictionary data, but in practice, it still needs to use the subscript method to value, which is very complicated and error-prone, and difficult to read and maintain. Personally, I think this is a bad design.

HandyJSON is a framework for HandyJSON that is similar to Codable. HandyJSON has been used in the past for some time, but ObjectMapper was turned back to cidcidr for inadequate support for types like enumerations and dates. But it looks good enough to try again.

In general, Codable has its own strengths as a language support solution for model interpretation. However, there is a little lack of flexibility, and the support for custom fields is not humanized enough. We look forward to further improvement.

The launch of Codaable is both a challenge and an opportunity for third-party libraries that are dable, and framework authors will take inspiration from Codaable to improve their frameworks and make them more popular in the near future.