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

AssocitatedType and Extensions by Vishal Singh,...

AssocitatedType and Extensions by Vishal Singh, Swift Hyderabad

Abstract: associatedTypes is one of the most promising features in Swift. In this session, we will see that how along with extensions, we can write some really cool stuff. In the follow-up example, we will try to build a generic tableView data source which can be used to feed a simple table view, and all of the data source and common operations like adding and deleting rows can be placed at one common place.

Bio: Vishal Singh is an iOS developer at Mutual Mobile, Hyderabad. He has been writing apps in Swift for 2 years. In free time, he likes to contribute to open source community via Github(https://github.com/Vishal-Singh-Panwar), and also writes blogs on Swift (https://swiftales.github.io/)

Swift India

May 13, 2017
Tweet

More Decks by Swift India

Other Decks in Programming

Transcript

  1. What are associated types? • AssociatedType gives a placeholder name

    to a type that is used as part of a protocol. The actual type of that associated type is not concreted until the protocol is adapted. • Unlike classes, structs, and enums, protocols do not support generic type parameters; instead they support abstract type members. • Prior to Swift 2.2, the keyword used to define a placeholder type inside protocol was typealias. This was confusing as typealias meant something else when used in protocols.
  2. Motivation • It was not obvious that typealias meant something

    different when used in protocols. • The existence of associated type was hidden for novices; bad because it allowed them to write code they did not fully understand. • It was not clear that concrete typealiases are forbidden inside protocols.
  3. Without realising that Element is the new associatedtype with a

    default value of Container.Generator.Element instead of a typealias to Container.Generator.Element protocol Prot { typealias Container : Sequence typealias Element = Container.Iterator.Element } protocol Prot { typealias Container : Sequence } extension Prot { typealias Element = Container.Iterator.Element } The snippet above declares Element as a typealias to Container.Generator.Element
  4. Proposed Solution • typealias can only be used for declaring

    type aliases declaration. • An error message will be displayed while declaring type alias inside a protocol.
  5. protocol Prot { associatedtype Container : Sequence typealias Element =

    Container.Iterator.Element // error: cannot declare type alias inside protocol, use protocol extension instead }
  6. protocol Prot { associatedtype Container : Sequence typealias Element =

    Container.Iterator.Element // error: cannot declare type alias inside protocol, use protocol extension instead } protocol Prot { associatedtype Container : Sequence } extension Prot { typealias Element = Container.Iterator.Element }
  7. Generic Type Overview • Generics allow you to write flexible,

    reusable functions, and types that work with any other type (subject to requirements that you define). • Swift’s Array and Dictionary types are both generic collections. We can create an Array of Int or String.
  8. Creating Stack struct IntStack { var items: [Int] mutating func

    push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } } struct StringStack { var items: [String] mutating func push(_ item: String) { items.append(item) } mutating func pop() -> String { return items.removeLast() } }
  9. Generic Type Stack struct Stack<Element> { var items = [Element]()

    mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") stackOfStrings.push("cuatro") stackOfStrings.push(1) // Cannot convert type `Int` to expected argument type `String`
  10. Generic Type in function params func merge<T>(stacks firstStack: Stack<T>, secondStack:

    Stack<T>)-> Stack<T> { var mergedStack = firstStack mergedStack.items.append(contentsOf: secondStack.items) return mergedStack } var stackOfStrings = Stack<String>() var anotherStackOfStrings = Stack<String>() let mergedStack = merge(stacks: stackOfStrings, secondStack: anotherStackOfStrings) The above function ensures that stacks used as arguments are of the same type. Stacks of type Int and String cannot be used at the same time.
  11. Extension of Generic Type extension Stack where Element == Int

    { func sumOfAllItems() -> Int { let sum = items.reduce(0, +) return sum } } let stackOfString = Stack<String>() let sum = stackOfString.sumOfAllItems() // Stack<String> is not convertible to Stack<Int> let stackOfInt = Stack<Int>() let sum = stackOfInt.sumOfAllItems()
  12. Motivation • The repeating logic of datasource method resides at

    one common place. • It can be evolved and optimised so that you won’t have to remember it. You record the experience, instead of re-discovering it every time you try to solve a similar problem. • Can be used for simple tableviews that use a single section.
  13. Single Section UITableViews • We have an Array containing our

    models. • In tableView(_ tableView: UITableView, numberOfRowsInSection section: Int), we return count of modelArray. • In tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath), we get the model from our array at indexPath.row and and decorate our cell with that model.
  14. [Person] var persons: [Person] func numberOfSections(in tableView: UITableView) -> Int

    { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return persons.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath) cell.confgiure(forPerson person: persons[indexPath.row]) return cell }
  15. [Department] var departments: [Department] func numberOfSections(in tableView: UITableView) -> Int

    { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return departments.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath) cell.confgiure(forDepartment department: departments[indexPath.row]) return cell }
  16. Components • TableViewCellDataRepresentable: Models will conform to this protocol. Only

    a conforming type can be used with a UITableViewCell. protocol TableViewCellDataRepresentable {} • TableViewCellDecorator: The conforming type has the ability to configure a specific UITableViewCell with a specific TableViewCellDataRepresentable. protocol TableViewCellDecorator { associatedtype DataType: TableViewCellDataRepresentable associatedtype CellType: UITableViewCell func update(tableViewCell cell: CellType, withData data: DataType) }
  17. Implementation class TableViewHandler<Decorator: TableViewCellDecorator>: NSObject, UITableViewDataSource { fileprivate(set) var dataSet:

    [Decorator.DataType] fileprivate(set) var decorator: Decorator private(set) weak var tableView: UITableView! let reuseIdentifiers: (IndexPath) -> String init(withTableView tableView: UITableView, decorator: Decorator, reuseIdentifiers: @escaping (IndexPath) -> String) { self.tableView = tableView self.decorator = decorator self.reuseIdentifiers = reuseIdentifiers super.init() self.tableView.dataSource = self } func reloadWithData(data: [T.DataType]) { dataSet = data tableView.reloadData() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSet.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifiers(indexPath), for: indexPath) as? Decorator.CellType else { return tableView.dequeueReusableCell(withIdentifier: reuseIdentifiers(indexPath), for: indexPath) } decorator.update(tableViewCell: cell, withData: dataSet[indexPath.row]) return cell } }
  18. Adding data func addData(data: [T.DataType], animated: Bool = true) {

    let startIndex = dataSet.count var indicesToAdd: [IndexPath] = [] dataSet.append(contentsOf: data) if animated { for index in startIndex..<(data.count + startIndex) { indicesToAdd.append(IndexPath(row: index, section: 0)) } tableView.beginUpdates() tableView.insertRows(at: indicesToAdd, with: .fade) tableView.endUpdates() } else { tableView.reloadData() } }
  19. func removeData(data: [T.DataType], animated: Bool = true) { var indicesToRemove:

    [IndexPath] = [] let proxyDataSet = dataSet for index in 0..<data.count { guard let _indexToRemove = proxyDataSet.index(where: { return $0 == data[index] }), let currentIndex = dataSet.index(where: { return $0 == data[index] })else { continue } dataSet.remove(at: currentIndex) indicesToRemove.append(IndexPath(row: _indexToRemove, section: 0)) } if animated { tableView.beginUpdates() tableView.deleteRows(at: indicesToRemove, with: .fade) tableView.endUpdates() } else { tableView.reloadData() } } Removing data The above code has one problem.
  20. Removing data The above code has one problem. func removeData(data:

    [T.DataType], animated: Bool = true) { var indicesToRemove: [IndexPath] = [] let proxyDataSet = dataSet for index in 0..<data.count { guard let _indexToRemove = proxyDataSet.index(where: { return $0 == data[index] }), let currentIndex = dataSet.index(where: { return $0 == data[index] })else { continue } dataSet.remove(at: currentIndex) indicesToRemove.append(IndexPath(row: _indexToRemove, section: 0)) } if animated { tableView.beginUpdates() tableView.deleteRows(at: indicesToRemove, with: .fade) tableView.endUpdates() } else { tableView.reloadData() } }
  21. TableViewHandler+Extension extension TableViewHandler where T.DataType: Equatable { func removeData(data: [T.DataType],

    animated: Bool = true) { var indicesToRemove: [IndexPath] = [] for index in 0..<data.count { let indexToRemove = dataSet.index() { return $0 == data[index] } guard let _indexToRemove = indexToRemove else { continue } dataSet.remove(at: _indexToRemove) indicesToRemove.append(IndexPath(row: _indexToRemove, section: 0)) } if animated { tableView.beginUpdates() tableView.deleteRows(at: indicesToRemove, with: .fade) tableView.endUpdates() } else { tableView.reloadData() } } }