Last active
November 26, 2018 21:04
-
-
Save Moximillian/f666a6ffd481b01be04c797950bed3b8 to your computer and use it in GitHub Desktop.
Journey into PAT-land
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A journey in trying to provide unified interface to UITableView and UICollection | |
*/ | |
protocol UnifiedCell { /* specific features omitted, as they are not relevant for this journey */ } | |
extension UITableViewCell: UnifiedCell {} | |
extension UICollectionViewCell: UnifiedCell {} | |
// the idea of the deque() below is that a unified code can be built | |
// that uses single codepath to call collection.deque() on both UITableViews and UICollections | |
// | |
// see https://github.com/Moximillian/ProtoKit/blob/4.1.0/Sources/UnifiedCollection.swift | |
// for context where this would be used | |
/** | |
* Attempt 1: simple protocols ------------------------------------ | |
*/ | |
protocol UnifiedCollection { | |
func deque(for indexPath: IndexPath) -> UnifiedCell | |
} | |
extension UITableView: UnifiedCollection { | |
func deque(for indexPath: IndexPath) -> UnifiedCell { | |
return dequeueReusableCell(withIdentifier: String(describing: UnifiedCell.self), for: indexPath) | |
} | |
} | |
// well, doesn't work at all. UnifiedCell doesn't keep the underlying type | |
// | |
// So, you can't use UnifiedCell directly, but you could use associate type... oh, doh! PAT wall. | |
// | |
// So Crusty says to use generics, let's use generics. | |
/** | |
* Attempt 2: Generics ------------------------------------ | |
*/ | |
protocol GenericCollection { | |
func deque<T: UnifiedCell>(for indexPath: IndexPath) -> T | |
} | |
extension UITableView: GenericCollection { | |
func deque<T: UnifiedCell>(for indexPath: IndexPath) -> T { | |
guard let cell = dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T else { | |
fatalError("Cell Type \(T.self) is not supported") | |
} | |
return cell | |
} | |
} | |
// works fine, except compiler won't complain about trying to instantiate UICollectionViewCell on a UITableView | |
// it will only bug out at runtime :( That's not Swifty my friend! | |
/** | |
* Attempt 2b: Generics / Parametrized extension ------------------------------------ | |
*/ | |
extension<T> UITableView: GenericCollection where T: UITableViewCell & UnifiedCell { | |
func deque(for indexPath: IndexPath) -> T { | |
guard let cell = dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T else { | |
fatalError("Cell Type \(T.self) is not supported") | |
} | |
return cell | |
} | |
} | |
// Maybe it would work, maybe not. Don't know, because Swift doesn't support this (yet). | |
// Also, would this keep deque function signature intact (in extension), so that protocol conformance | |
// to GenericCollection is still valid? | |
/** | |
* Attempt 3: protocols with associated type ------------------------------------ | |
*/ | |
protocol UnifiedCollection { | |
associatedtype Cell: UnifiedCell | |
func deque(for indexPath: IndexPath) -> Cell | |
} | |
extension UnifiedCollection where Self: UITableView, Cell: UITableViewCell { | |
func deque(for indexPath: IndexPath) -> Cell { | |
guard let cell = dequeueReusableCell(withIdentifier: String(describing: Cell.self), for: indexPath) as? Cell else { | |
fatalError("Cell Type \(Cell.self) is not supported") | |
} | |
return cell | |
} | |
} | |
class MyCell: UITableViewCell {} | |
class MyTable: UITableView, UnifiedCollection { | |
typealias Cell = MyCell | |
} | |
// this works, but you'd have to use subclassed table always, and can't make UITableView conform directly, | |
// unless you use only single type for cell (need to define typealias) | |
// is there any better way? Well, one can give up trying to follow UIKit API | |
// and do deque() on cell instead of table. | |
/** | |
* Attempt 4 (give up on generics/existentials): classes with Self ------------------------------------ | |
*/ | |
extension UITableViewCell { | |
public static func deque(in table: UITableView, for indexPath: IndexPath) -> Self { | |
guard let cell = table.dequeueReusableCell(withIdentifier: String(describing: self), for: indexPath) as? Self else { | |
fatalError("Could not dequeue tableview cell with identifier: " + String(describing: self)) | |
} | |
} | |
} | |
// ooops, Self works as return type, but Self cannot be casted to :( | |
// but protocol Self can be used in casts. More elbow grease needed... | |
/** | |
* Final solution 5: multiprotocol with associated type and Self ------------------------------------ | |
*/ | |
protocol UnifiedCollection {} | |
extension UITableView: UnifiedCollection {} | |
// have to introduce a parent protocol so that the associated type does not to blow up | |
protocol HasCollection { | |
associatedtype Collection: UnifiedCollection | |
} | |
extension UITableViewCell: HasCollection { | |
typealias Collection = UITableView | |
} | |
// main protocol, uses associated type from parent | |
protocol UnifiedCell: HasCollection { | |
static func deque(in collection: Collection, for indexPath: IndexPath) -> Self | |
} | |
extension UnifiedCell where Self: UITableViewCell { | |
static func deque(in collection: Collection, for indexPath: IndexPath) -> Self { | |
guard let cell = collection.dequeueReusableCell(withIdentifier: String(describing: self), for: indexPath) as? Self else { | |
fatalError("Could not dequeue tableview cell with identifier: " + String(describing: self)) | |
} | |
return cell | |
} | |
} | |
// can only use final classes, but that's ok. | |
final class MyCell: UITableViewCell, UnifiedCell {} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment