If you go with the approach that identifiers are derived directly from class names, you can do a lot of convenience functions in UIKit and AppKit. For example:
extension UITableView { // NOTE: this extends table
public func dequeueReusableCell<T: UITableViewCell>() -> T {
guard let cell = dequeueReusableCell(withIdentifier: String(describing: T.self)) as? T else {
fatalError("Could not dequeue tableview cell with identifier: \(T.self)")
}
return cell
}
public func register<T: UITableViewCell>(cell: T.Type) {
register(T.self, forCellReuseIdentifier: "\(T.self)")
}
}
However, using generics for this is not appealing because the users of these functions have to rely on coercion to get the types right:
let tableView = UITableView()
class MyCell: UITableViewCell {}
tableView.register(cell: MyCell.self)
let cell = tableView.dequeueReusableCell() as MyCell
I find using Self in protocols better:
public protocol Dequeuable {}
extension Dequeuable where Self: UITableViewCell {
public static func dequeueReusable(in table: UITableView) -> Self {
guard let cell = table.dequeueReusableCell(withIdentifier: String(describing: Self.self)) as? Self else {
fatalError("Could not dequeue tableview cell with identifier: \(Self.self)")
}
return cell
}
public static func register(to table: UITableView) {
table.register(Self.self, forCellReuseIdentifier: String(describing: Self.self))
}
}
extension UITableViewCell: Dequeuable {} // this has to be done separately
That you can then use like this:
MyCell.register(to: tableView)
let cell = MyCell.dequeueReusable(in: tableView)
The protocol really shouldn't be necessary here, though – Just extending UITableViewCell directly should be enough. Unfortunately Self cannot be used there (except as a return type). So the whole dance with "... where Self: ...." is needed.
Ultimately I ended up implementing Self support for classes like this:
EDIT: revised based on tips from @ahti (=> removed SelfType)
public protocol SelfPresentable {}
extension SelfPresentable {
/// Identifier derived from the name of the class
public static var identifier: String { return String(describing: self) }
/// Cast an object to Self or show error message
public static func asSelf(object: Any?, _ errorMessage: String) -> Self {
guard let object = object as? Self else { fatalError(errorMessage) }
return object
}
}
And with that it's possible to use Self in classes, like this:
extension UITableViewCell: SelfPresentable {
public static func dequeueReusable(in table: UITableView) -> Self {
return asSelf(object: table.dequeueReusableCell(withIdentifier: identifier),
"Could not dequeue tableview cell with identifier: " + identifier)
}
public static func register(to table: UITableView) {
table.register(self, forCellReuseIdentifier: identifier)
}
}
I do wonder though, is there something more elegant as a solution that I might have missed? Are there other ways of working around the Self limitations?