Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Real World Generics In Swift

Vadym Markov
February 12, 2016

Real World Generics In Swift

Short overview and examples of practical usage of generics in Swift.

Vadym Markov

February 12, 2016
Tweet

More Decks by Vadym Markov

Other Decks in Programming

Transcript

  1. ABOUT ME let me = Developer( name: "Vadym Markov", specialisation:

    "iOS", company: "Hyper Interaktiv AS", interests: [ "Technology", "Open Source", "Music" ] )
  2. 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
  3. 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
  4. 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
  5. THE PROBLEM struct 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 } }
  6. 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 } }
  7. 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
  8. 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 } }
  9. 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 } // ...
  10. // 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 }
  11. // 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) }
  12. 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) } }
  13. 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
  14. 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
  15. WHAT ABOUT PROTOCOLS? ▸Protocols in Swift cannot be defined generically

    using type parameters.
 ▸Instead, protocols can define what are known as associated types.
  16. 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? { // ... } }
  17. 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
  18. HOW COULD IT LOOK LIKE? let list : [Cachable<String>] =

    [] let’s imagine Swift supporting generic parameterised protocols
  19. 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" }
  20. 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()
  21. 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)) } }
  22. WHY GENERICS? ▸ Type safety ▸ Less code duplication ▸

    No type casting hell ▸ Public API flexibility ▸ Abstraction is fun