Old Dog Swift Tricks
Over the past year and half I’ve spent as much time as possible learning Swift. I don’t care how senior of an iOS developer you are, Swift is a shift in language and paradigms. I hit the reset button in my development career and wanted to get up to speed with the new language changes in a short amount of time.
I’m Loving It
I’ve been reading, coding, cursing and embracing Swift since 1.0 (including Swift 3) and I love the language. Before I taught myself Objective-C I programmed in Java, PHP and occassionally Python. All of the later languages where extremely fun, but didn’t hold a candle to Objective-C or Swift.
Below are some examples of the new language features.
New Paradigms
- Map
- Reduce
- Filter
- Generics
- ErrorType
- Functional
- Protocols
- Value Types
Adjustments
Adjusting to these paradigms hasn’t been easy, but all been worth it. Swift is the future and the latest XCode is my Delorian.
Full Steam Ahead
Apple and the community have spoken…Swift is the future. With that said I’m not letting my Objective-C (fu) go to waste. I, like many others, have a TON of code written (and needs to be maintained) in Objective-C. The language and features are amazing and if you are starting out in iOS development then I would strongly suggest boning up on you Objective-C (fu).
Lessons Learned
Embrace the Swift change swiftly…and by change I mean high order functions map
and filter
.
Map
Definition
The map method solves the problem of transforming the elements of an array using a function. Let’s look at some examples.
Example
Let’s say you want to display a list of names from a Person
object in a UITableView
. In the “old” way I would have a list of Person
objects and then in my UITableViewCell
I would have grabbed the instance of the Person
object from the list and set the textLabel.text
to the name
property value.
In the Swift “way” map
would simplify this.
struct Person {
let name: String
let age: Int
}
let names: [String] = people.map { return $0.name }
You can also apply the same principles to dictionaries.
Uppercase the values
let payload: Dictionary <String, String> = ["hello": "world", "steve": "jobs"]
let result = payload.map { (key, value) in (key, value.uppercased()) }
/// Output
[("hello", "WORLD"), ("steve", "JOBS")]
Filter
Definition
The filter method solves the problem of selecting the elements of an array that pass a certain condition
Example (name filter)
Filter names from a pre-defined list
Extending our map
example from above let’s say you have a list of Person
objects that you want to filter from another list. Somewhat like a search filter.
struct Person {
let name: String
let age: Int
}
let people = [
Person(name: "Katie", age: 23),
Person(name: "Bob", age: 21),
Person(name: "Rachel", age: 33),
Person(name: "John", age: 27),
Person(name: "Megan", age: 15)
]
let matchingNames = ["Katie", "Rachel", "Megan"]
let names: [String] = people.map { return $0.name }
let filteredNames = names.filter({matchingNames.contains($0)})
/// Output
["Katie", "Rachel", "Megan"]
Example (character filter)
let badCharacters: [Character] = [".", "$", "#", "[", "]"]
let badChannelName: String = "thisis my string[.$#"
let cleanName = String(badChannelName.characters.filter{!badCharacters.contains($0)})
/// Output
"thisis my string"
Example (filter dictionary)
let data: [String: String] = ["hello": "world", "steve": "jobs"]
let filtered = data.filter { $0.1 == "world" }
// let filtered = data.filter { $1 == "jobs" }
Real World
I have a list of objects with three properties: name
, imageName
and selected
. You can toggle the selection from the list display. If user selected one then I would toggle the “selected” flag then I would add it to a local Array
of toggled items. Didn’t care there were dupes. When the user was finished with workflow then I need to pass the selected options to another view controller with only names.
Obvious question is “How do you filter an array of duplicates?”. With a a combination of map
, filter
and a computed property the answer is out there.
Let’s assume I have two lists of sports that can be selected or unselected and those datasource for those lists. What is the optimal solution for returning a final list of objects who has a selected value of true?
Let’s start with the lists
let SportPickerTypesRight: Array<SportPickerType> = [SportPickerType.create("NFL", imageName: "nfl_background"),
SportPickerType.create("NCAA Football", imageName: "football_background"),
SportPickerType.create("NCAA Basketball", imageName: "basketball_background"),
SportPickerType.create("NBA", imageName: "nfl_background")]
let SportPickerTypesLeft: Array<SportPickerType> = [SportPickerType.create("NHL", imageName: "nhl_background"),
SportPickerType.create("Soccer", imageName: "soccer_background"),
SportPickerType.create("MLB", imageName: "mlb_background"),
SportPickerType.create("Other", imageName: "other_background")]
var SportPickerTypes: Array<SportPickerType> {
let combined = [SportPickerTypesRight, SportPickerTypesLeft]
return combined.flatMap { $0 }
}
struct SportPickerType {
let name: String
let imageName: String
var selected: Bool
static func create(name: String, imageName: String) -> SportPickerType {
return SportPickerType(name: name, imageName: imageName, selected: false)
}
}
extension SportPickerType: Equatable {}
extension SportPickerType: Hashable {
var hashValue: Int {
return (name + imageName).hashValue
}
}
func ==(lhs: SportPickerType, rhs: SportPickerType) -> Bool {
return lhs.name == rhs.name
}
/// When you want to return a list of unique selected sports
///
/// What the property does is return a set (unique values)
/// and the filters that set for only values with selected == true
/// and returns that array of names
var selectedSportTypes: Array<String> {
let filteredSet: Set<SportPickerType> = Set(self.unfilteredSportTypes)
let mappedNames: Array<String> = filteredSet.filter({$0.selected == true}).map{return $0.name}
return mappedNames
}
Understanding these higher level function provide great functionality and clarity to your coding. Swift is the future and learning / understanding these higher level functions will provide much more elegance to your code.