Blog

Latest Post

Swift Tip: String to Data and Back

When converting a string into data, the return type is optional. For example:

let cafe: Data? = "Café".data(using: .utf8) // non-nil

Is it safe to force-unwrap this value? Can it ever return nil? It depends. When you use a Unicode encoding (such as .utf8 or .utf16), you can safely force-unwrap. Strings in Swift use Unicode internally, so encoding a string using a Unicode encoding will always succeed.

When you use other encodings, it is not safe to force-unwrap (unless you explicitly know that a string is guaranteed to be representable). It is easy to see this with an example:

let upsideDown = "🙃".data(using: .ascii)

ASCII can’t represent emoji, and the conversion will fail. This isn’t limited to emoji, Unicode can represent vastly more characters than ASCII.

How about the other way around? When we want to convert data into a string, can we be sure it’s safe to force-unwrap? In this case, the answer is an unqualified no. Converting data into a string can always fail.

For example, here are the bytes for the cafe variable above:

var bytes: Data = cafe! // [67, 97, 102, 195, 169]

When you read some bytes, you never know if it’s valid Unicode. The last two bytes are the mark on top of the “é” and the “e”. We can try to remove the “e” from the array, so that the mark has no character left to modify:

bytes.removeLast()
String(data: bytes, encoding: .utf8) // nil

As we can see, decoding fails. Unless you know exactly where the data comes from, you shouldn’t rely on decoding to succeed, and you shouldn’t force-unwrap the string. Sometimes, however, you might want to display what is in the string, even if there are decoding errors. There is a special initializer for this, which will try to repair the data, and inserts the “�” character (a replacement character) if it can’t decode the data.

String(decoding: bytes, as: UTF8.self) // "Caf�"

TLDR

  • It’s safe to force-unwrap the result of string-to-data transformation only when you use a Unicode encoding.
  • It’s never safe to force-unwrap a data-to-string transformation.

To learn more about Swift’s strings in depth, check out our book, Advanced Swift.

Previous Posts

Swift Tip: Networking with Codable

In the very first Swift Talk we built a tiny networking library, leveraging Swift’s structs and generics for type-safety and testability. This was in June 2016, and we were still working with Swift 2.2. A lot has changed since then — so let’s revisit the code and make use of Swift’s latest tricks!

The basic idea was to separate the description of an endpoint from the code executing the network request. The description includes not only the URL, but also the function that can turn data from the network into the desired result:

struct Resource<A> {
    let url: URL
    let parse: (Data) -> A?
}

The parse function takes Data and turns it into an optional A. The advantage of using Data is that we can deal with any endpoint, irrespective of the data format. But let’s face it, more often than not we’re dealing with JSON. So we wrote the following initializer to make it easier to create JSON resources:

extension Resource {
    init(url: URL, parseJSON: @escaping (Any) -> A?) {
        self.url = url
        self.parse = { data in
            let json = try? JSONSerialization.jsonObject(with: data, options: [])
            return json.flatMap(parseJSON)
        }
    }
}

In the meantime Swift gained the Codable protocol that makes it super easy to encode and decode values conforming to it. As Aurélien Noce pointed out on Twitter, this makes a very nice addition to this tiny networking library. We can write another initializer on Resource for types that conform to the Decodable protocol:

extension Resource where A: Decodable {
    init(url: URL) {
        self.url = url
        self.parse = { try? JSONDecoder().decode(A.self, from: $0) }
    }
}

This makes it super easy to create resources for Decodable types. For example:

struct Episode: Codable {
    let id: String
    let title: String
}

let allEpisodes = Resouce<[Episode]>(url: url)
Webservice().load(allEpisodes) { print($0) }

Since arrays are also decodable if their elements are, we can create a resource for all episodes — including the function that parses data from the network — in a single line. In fact, we didn’t have to write any parsing code ourselves.

Of course this only works if you can determine how your data is being represented in JSON. In real projects you’ll often not have this possibility. However, it’s still a great technique for quick experiments and prototyping!

Swift Tip: OpaquePointer vs. UnsafePointer

When you’re working with a C library, some pointers are imported as OpaquePointer, but other pointers are imported as UnsafePointer<struct_type>. The difference is found in the C header file.

When a struct person is completely defined in the header file, any pointers to it will be imported by Swift as UnsafePointer<person>. This means we can also dereference the pointers, and look at the contents by calling .pointee on the pointer. For example, in the following C header file, we have full information about the person struct:

// sample.h
typedef struct person person;
struct person {
    int age;
    char *first_name;
};
void person_print(person*);

When we use person_print from Swift, it gets imported as func person_print(_: UnsafeMutablePointer<person>!). Because person is in the header file, we also get a constructor for person, and we get accessors for its properties:

let p = person(age: 33, first_name: strdup("hello"))
print("person: \(p.age)")
free(p.first_name)

Another common practice in C is to keep the struct definition incomplete. For example, consider a header file looks like this:

// sample.h
typedef struct account account;
void account_print(account*);

The header file above doesn’t provide the definition of the account struct, only a typedef. The definition is found in the implementation file, and not visible outside of sample.m:

// sample.m
struct account {
    int account_number;
    char *first_name;
};

This practice is common in many libraries, such as libgit which we discussed in last week’s Swift Talk episode. Because account is only in the header file, it’s called an opaque (or sometimes: incomplete) type: from the outside, we don’t know anything about it.

This means that we don’t get an account initializer in Swift, we can’t access an account’s properties, and so on. Even in account_print, there’s no mention of the account type. It gets imported as func account_print(_: OpaquePointer!). An OpaquePointer is Swift’s way of letting you know that you’re dealing with, well, an opaque pointer.

In last week’s Swift Talk episode we go into detail about how to work with both kinds of pointers in libgit2, including memory management and making the APIs feel at home in Swift. In our new book, Advanced Swift, we look at all the pointer types in great detail.

Swift Tip: Codable Enums

If we have an enum wih associated types, the compiler can’t automatically generate Codable conformance yet. For example, let’s create an enum that can be one of two values:

enum Either<A,B> {
    case left(A)
    case right(B)
}

If we use the latest Swift toolchain, we can conditionally make it conform to Codable (see this gist), but even in the current Swift release we can make it work by adding Codable constraints to our generic parameters:

enum Either<A: Codable, B: Codable> {
    case left(A)
    case right(B)
} 

To encode, we create two CodingKeys, and use those keys to tag the value before encoding it:

extension Either: Encodable {
    enum CodingKeys: CodingKey {
        case left
        case right
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .left(let value):
            try container.encode(value, forKey: .left)
        case .right(let value):
            try container.encode(value, forKey: .right)
        }
    }
}

To decode, we mirror the process. First, we try to decode using the .left key. If that doesn’t work, we catch the error, and try to decode using the .right key. If that doesn’t work either, the error will be propagated (just like a regular decoding error):

extension Either: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            let leftValue =  try container.decode(A.self, forKey: .left)
            self = .left(leftValue)
        } catch {
            let rightValue =  try container.decode(B.self, forKey: .right)
            self = .right(rightValue)
        }
    }
}

Now we can make use of our new Codable conformance:

import Foundation

let values: [Either<String,Int>] = [
    .left("Hi"),
    .right(42)
]


let encoder = JSONEncoder()
let data = try! encoder.encode(values)
let str = String(data: data, encoding: .utf8)!
// [{"left":"Hi"},{"right":42}]

And we can see that we also can read it back:

let decoder = JSONDecoder()
let result = try! decoder.decode([Either<String,Int>].self, from: data)
// [.left("Hi"), .right(42)]

The technique above doesn’t just work for the Either type, but can be made to work for any enum with associated values, as long as all associated values are Codable themselves. Just add a case to CodingKeys for each case in your enum.

For more information on Codable, see our book Advanced Swift, which has a full chapter on encoding and decoding.

Our New Book: App Architecture (Early Access)

“Most writing about app architecture tries to sell you on a specific solution. We believe almost any architecture can work well, if applied correctly.” — Chris Eidhof

Today, we’re pleased to announce early access to our new book:

📗 App Architecture, by Chris Eidhof, Matt Gallagher, and Florian Kugler.

Born from a “hallway track” conversation with Matt Gallagher, at a conference in early 2017, the book started life with the working title “A Hundred Different Ways to Write an iOS App”. Most discussions of app architecture begin with an attempt to sell you on a specific pattern, but we agreed with Matt that any architecture can work well, if applied correctly. What’s more, “correctly” is a relative term: it depends on your team, your code base, your experience, and your preferences.

Between Melbourne and Berlin, we set to work.

Instead of advocating for particular patterns, this book lays out the problems all architectures are trying to address: constructing the app’s components, communicating between the view and the model, and handling non-model state.

Using a single example app, we implement the same specification in six different architectural patterns, including three commonly used and three experimental patterns (full source code included). We show high-level solutions to these problems, breaking each pattern down to understand how it is implemented at the level of code.

Our three common architectures are Model-View-Controller (MVC), Online-Only MVC, and Model-View-ViewModel + Coordinator (MVVM-C). Common architectures have commonly encountered problems, and we discuss techniques and solutions specific to each pattern.

On the experimental side, we explain View-State-Driven MVC, ModelAdaptor-ViewBinder, and the Elm architecture. By examining these experimental patterns, we extract valuable lessons that can be applied to other patterns, and to existing code bases.

After reading about new architectures, it’s tempting to start over, to build with a clean slate. This isn’t necessary. Many lessons from our book can be applied to existing applications, in both Swift and Objective-C. You can improve the handling of view-state, use reducers to simplify code, refactor large view controllers, achieve unidirectional data flow in MVC, and much more with the code you already have.

Our book helps you make well-informed decisions about all of these options by showing how different architectures function, and how you can combine different elements to build applications that suit the context of your work.

By the end, you should have a thorough understanding of the commonalities, differences, and trade-offs between architectures, and how you can best implement them to fit your specific needs.

Watch us discuss the book in our introduction video.

Early Access

We are releasing the book in chapters. The first chapter is available now, with updates and new content added weekly until publication is complete.

Early access also includes:

  • A private GitHub repository with markdown and PDF versions
  • Complete source code for all six architectures
  • The opportunity to file issues and ask questions
  • An exclusive weekly video Q&A

And our gratitude for your support 🙇‍

With Video

New this year, readers have the option to purchase the book with video, which includes over 8 hours of in-depth discussion covering all six architectural patterns, and additional live-coding of new features. The videos are exclusive to the Ebook + Video edition, and will remain separate from our Swift Talk series.

Early access is digital only, with videos and complete book released in April 2018. A separate paperback edition will follow shortly after.

How to Get Access

Readers can purchase early access directly from our website. Sales and checkout are managed by Gumroad, if you need assistance their help team is on Twitter.

We look forward to hearing your questions!

Best from Berlin, and Melbourne. 🐨

Chris, Matt, and Florian.