WWDC Day 2: Importing Frameworks into Playground

My first WWDC is going awesome so far. It’s cool to be able to talk to the Apple engineers on the products they worked on, and hear how they work under the hood. I went to a couple of the lab sessions, for Swift, Xcode, and autolayout.

I was curious to get help on importing frameworks into Xcode playgrounds, and from the people at the Xcode labs I learned a couple interesting things.

  • Xcode 7: Framework search path only searches the currently selected scheme
  • Xcode 8: Undocumented feature: searches all schemes in your workspace

This answers why I was having problems importing frameworks into my playgrounds even when I added the playground into my workspace. You need to have a scheme selected which has the framework in its framework search path. If you’re using CocoaPods, they create a scheme for each pod, so you just need to select the scheme for the pod that you want to import into the playground. This won’t be an issue in Xcode 8 though, you can just import frameworks built by any scheme into your playground.
I also learned that you can create a Sources folder and Resources resources folder within a playground, allowing you to create or copy any appropriate Swift source files, or resource files.

Also another random thought: It seems like dlopen() is getting mentioned everywhere I go. At the Swift lab, when I asked about importing framworks into the Swift REPL, one idea the engineer gave me was dlopen (you can pass the -F flag to swift to import framworks) In the Optimizing App Startup Time session, they mentioned that you shouldn’t use dlopen for Dylib loading because it’s more work overall. Also there was a mention in this tweet that dlopen works in the new iPad Swift Playground. Also found an interesting read about dlopen by Erica Sadun

ReactiveCocoa4 Adventure Time

I started playing around with ReactiveCocoa at probably not an ideal time to start learning it. This was two years ago when Swift was not yet announced, and I was reading Ash Furrow’s book Functional Reactive Programming on iOS which was based on ReactiveCocoa2 in Objective-C. Not too much after I finished the book, Swift was announced! I jumped on the bandwagon, excited to make an app with Swift, and with ReactiveCocoa, and quickly realized that the two were incompatible at the time.

Fast forward over a year later, after the great Swiftening with RAC3, RAC4 stable version has been released and I’ve been using it every day for the past couple months. RAC has been such a joy to use, and I can’t imagine going back to using boolean flag state and nested closures anymore. The ReactiveCocoa BasicOperator doc and RACMarbles interactive page have plenty of awesome examples of how to use the operators defined in RAC, but I was a little lost on how to use them in practical situations. I’m hoping that I can help anyone getting started with some examples of common network operations that can be simplified with RAC.

I came up with some (contrived) examples and threw them together into this ReactivePokemon app. These examples make use of the networking abstraction library Moya, which conveniently has RAC and RxSwift extensions to bring network requests into the reactive land of streams! I also used Argo for JSON parsing.

Simple mapping of JSON to model

As simple as it gets - taking a String, making a network request and returning a decoded Pokemon struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   func getPokemonByID(id: String) -> SignalProducer<Pokemon?, NoError> {
return provider.request(.Pokemon(id: id)) // 1. Request to endpoint, get back JSON
.map(Pokemon.decode) // 2. Take JSON, return Pokemon struct
.map { $0.value } // 3. Unwrap Pokemon struct
.flatMapError { _ in return SignalProducer<Pokemon?, NoError>.empty } // 4. Ignore network errors (Not usually necessary, but needed in my example app to dynamically show results in the UI)
}

// Another simple example, which will be used in the next example
private func getTypeByID(id: String) -> SignalProducer<Type?, NoError> {
return provider.request(.Pokemon(id: id))
.map(Type.decode)
.map { $0.value }
.flatMapError { _ in return SignalProducer<Type?, NoError>.empty }
}

Nested network requests

Take the value of one netork request to make another network request. You can flatmap as many requests after requests as you want without any nested closures. Network request-ception!

1
2
3
4
5
6
7
8
9
10
func getPokemonMainTypeByPokemonID(id: String) -> SignalProducer<Type?, NoError> {
return getPokemonByID(id) // 1. Take the 1st request...
.map { pokemon in
pokemon?.types.first?.type.id // 2. Get the pokemon type id...
}.flatMap(.Latest) {(id: String?) -> SignalProducer<Type?, NoError> in
// 3. This flatMap is where the magic happens. Use the resulting pokemon type id to make a different network request
guard let id = id else { return SignalProducer.empty }
return self.getTypeByID(id)
}
}

Multiple network requests merged

Take the value of one netork request to make multiple network request. The same as above, but instead of only getting the Pokemon’s main type, we get all of the types. Shoutout to Charizard for being Fire and Flying type (and sometimes Dragon).

1
2
3
4
5
6
7
8
9
func getPokemonTypesByPokemonID(id: String) -> SignalProducer<[Type], NoError> {
return getPokemonByID(id)
.map { pokemon in
pokemon?.types.map{$0.type.id} ?? []
}.flatMap(.Latest) { (ids: [String]) -> SignalProducer<[Type?], NoError> in
let signals = ids.map { self.getTypeByID($0) } // Array of `getTypeID` requests
return SignalProducer(values: signals).flatten(.Merge).collect() // Merges all results into one array when all the requests complete
}.map{$0.flatMap{$0}} // Removes any `getTypeByID` nil results
}

Multiple network requests zipped

Take multiple netork requests and combine them to make one model. Without RAC I’d usually have a dispatch_group that gets entered into multiple times and then gets combined when all the network responses have returned. RAC makes it easier to see how the requests all fit together.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func getTheUltimatePokemonTeam(trainerID: String, pokemonID: String) -> SignalProducer<PokemonTeam?, NoError> {
return getPokemonByID(pokemonID)
.zipWith(getTrainerByID(trainerID)) // 1. Joins result of a parallel network request into a tuple with the result from the previous network request
.map { pokemon, trainerName in // 2. Now we have the result of two parallel network requests and can construct a PokemonTeam
guard let pokemon = pokemon, trainerName = trainerName else { assertionFailure("Couldn't find the pokemon and trainer for this ultimate team"); return nil }
return PokemonTeam(trainerName: trainerName, pokemon: pokemon)
}
}

// Stubbed Trainer network request
private func getTrainerByID(id: String) -> SignalProducer<String?, NoError> {
return SignalProducer { observer, disposable in
observer.sendNext("Ash Ketchum")
observer.sendCompleted()
}
}

Sequential network requests

This is kind of a rare case that I would usually avoid, as it’s a sign of other side effects. Do something with the result of one network request, and then do something else (not dependant on a value from the first request, but dependant on the side effect from the first result).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func getPokemonByIDAndFightTeamRocket(id: String) -> SignalProducer<Bool, NoError> {
return getPokemonByID(id)
.map { pokemon in
guard let pokemon = pokemon else { return }
print("Consulting with Professor Oak about my new \(pokemon.name)") // 1. Side effects! Very important to talk with Professor Oak before fighting Team Rocket
}.then(fightTeamRocket) // 2. Now we've finished talking with Oak and we're ready to fight Team Rocket
}

private var fightTeamRocket: SignalProducer<Bool, NoError> {
return SignalProducer<Bool, NoError> { observer, disposable in
print("Fighting Team Rocket")
observer.sendNext(true)
print("Defeated Team Rocket!")
observer.sendCompleted()
}
}

What I Code About

The long awaited blog! Title inspired by Haruki Murakami’s book on running, What I Talk About When I Talk About Running.
This is what I code about when I talk about coding.