real world generics in swift
TRANSCRIPT
REAL WORLD GENERICS IN SWIFT
@VADYMMARKOV
ABOUT ME
let me = Developer( name: "Vadym Markov", specialisation: "iOS", company: "Hyper Interaktiv AS", interests: [ "Technology", "Open Source", "Music" ] )
GENERICS, HUH?
DEFINITION
▸Generic programming is a way to write functions and data types without being specific about the types they use
▸Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define
GENERICS AND SWIFT
▸Generics are one of the most powerful features of Swift
▸Much of the Swift standard library is built with generic code
▸ You’ve already been using generics, even if you didn’t realise it
SWIFT STANDARD LIBRARY
// Array var array = [String]()
// Dictionary var dictionary = [String: String]()
// Enum var string1 = Optional<String>.Some("Swift") var string2 = Optional<String>.None == nil
THE PROBLEMstruct IntQueue {
private var items = [Int]()
mutating func enqueue(item: Int) { items.append(item) }
mutating func dequeue() -> Int? { return items.removeFirst() }
func peek() -> Int? { return items.first }
func isEmpty() -> Bool { return items.isEmpty } }
SOLUTION?struct AnyQueue {
private var items = [Any]()
mutating func enqueue(item: Any) { items.append(item) }
mutating func dequeue() -> Any? { return items.removeFirst() }
func peek() -> Any? { return items.first }
func isEmpty() -> Bool { return items.isEmpty } }
SO NOW WE ARE ABLE TO PUSH STRINGS TO THE STACK, RIGHT?
YES BUT…
▸We are losing type safety
▸We also need to do a lot of casting
SOLUTION!struct Queue<T> {
private var items = [T]()
mutating func enqueue(item: T) { items.append(item) }
mutating func dequeue() -> T? { return items.removeFirst() }
func peek() -> T? { return items.first }
func isEmpty() -> Bool { return items.isEmpty } }
SOLUTION!
var stringQueue = Queue<String>() stringQueue.enqueue("String") stringQueue.dequeue() // "String"
var intQueue = Queue<Int>() intQueue.enqueue(1) intQueue.dequeue() // 1
WELCOME TO REALITY
GENERIC TYPES
class DataSource<Model, Cell: UITableViewCell> : NSObject, UITableViewDataSource, UITableViewDelegate {
let cellIdentifier: String let configureCell: (Model, Cell) -> Void var cellHeight: CGFloat = 64 var items = [Model]() var action: (Model -> Void)?
init(cellIdentifier: String, configureCell: (Model, Cell) -> Void) { self.cellIdentifier = cellIdentifier self.configureCell = configureCell }
// ...
// MARK: - UITableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 }
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count }
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier( cellIdentifier, forIndexPath: indexPath)
let item = items[indexPath.item]
if let cell = cell as? Cell { configureCell(item, cell) } return cell }
// MARK: - UITableViewDelegate
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return cellHeight }
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
guard let action = action else { return }
let item = items[indexPath.row] action(item) }
class TeamController: UITableViewController {
static let reusableIdentifier = "TeamCellIdentifier"
var developers = [Developer]()
lazy var dataSource: DataSource<Developer, TableViewCell> = { [unowned self] in let dataSource = DataSource( cellIdentifier: TeamController.reusableIdentifier, configureCell: self.configureCell)
dataSource.action = self.selectCell
return dataSource }()
// ...
func configureCell(developer: Developer, cell: TableViewCell) { cell.textLabel?.text = developer.name cell.imageView?.image = UIImage(named: "placeholder")) }
func selectCell(developer: Developer) { UIApplication.sharedApplication().openURL(developer.githubURL) } }
protocol CacheAware { func object<T: Cachable>(key: String, completion: (object: T?) -> Void) }
class DiskStorage: CacheAware {
func object<T: Cachable>(key: String, completion: (object: T?) -> Void) { // ... } }
// String must conform Cachable
let storage = DiskStorage(name: "Unknown") storage.add("string", object: "My string”) storage.object("string") { (object: String?) in }
GENERIC FUNCTIONS
struct ViewModel: Mappable {
// ...
var meta = [String : AnyObject]()
func meta<T>(key: String, _ defaultValue: T) -> T { return meta[key] as? T ?? defaultValue } }
let modelFoo = ViewModel(title: "foo", meta: ["id" : 1])
modelFoo.meta("id", 0) // 1 modelFoo.meta("foo", "bar") // bar
GENERIC FUNCTIONS
WHAT ABOUT PROTOCOLS?
▸Protocols in Swift cannot be defined generically using type parameters.
▸ Instead, protocols can define what are known as associated types.
ASSOCIATED TYPES protocol Cachable { typealias CacheType
static func decode(data: NSData) -> CacheType? func encode() -> NSData? }
extension String: Cachable {
typealias CacheType = String
static func decode(data: NSData) -> CacheType? { // ... }
func encode() -> NSData? { // ... } }
ASSOCIATED TYPES
let list: [Cachable] = []
▸ Cachable represents a set of types rather than a single type
▸ In an array of Cachable, you are not be able to say anything about the return type of the decode() method
HOW COULD IT LOOK LIKE?
let list : [Cachable<String>] = []
let’s imagine Swift supporting generic parameterised protocols
MORE EXAMPLES: OPERATORS infix operator ?= { associativity right precedence 90 }
public func ?=<T>(inout left: T, right: T?) { guard let value = right else { return } left = value }
public func ?=<T>(inout left: T?, right: T?) { guard let value = right else { return } left = value }
// ...
func testIfLetAssignment() { let hyper = NSURL(string: "hyper.no")! let faultyURL = NSURL(string: "\\/http")
var testURL: NSURL? testURL ?= hyper // "hyper.no" testURL ?= faultyURL // "hyper.no" }
MORE EXAMPLES: PROTOCOL EXTENSIONS
protocol Queueable { func process() -> Bool }
extension Array where Element : Queueable {
mutating func processQueue(from: Int, to: Int, process: ((element: Element) -> Void)) { // ... } }
var testQueue = [Object(), Object()] testQueue.processQueue()
MORE EXAMPLES: ENUMS enum Result<T, ErrorType> { case Success(T) case Failure(ErrorType) }
struct UFO { enum Error: ErrorType { case NotFound } }
func recognizeUFO(completion: Result<UFO, UFO.Error> -> Void) { var ufo: UFO?
// Some async complex calculations
if let result = ufo { completion(Result.Success(result)) } else { completion(Result.Failure(UFO.Error.NotFound)) } }
WHY GENERICS?
▸ Type safety
▸ Less code duplication
▸No type casting hell
▸ Public API flexibility
▸Abstraction is fun
RESOURCES
▸ https://developer.apple.com/swift/
▸ https://github.com/hyperoslo/Orchestra
▸ https://github.com/hyperoslo/Cache
▸ https://github.com/hyperoslo/Spots
▸ https://github.com/hyperoslo/Sugar