Workarounds for Self in classes


(Mox) #1

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?


Allow `self = x` in class convenience initializers
(Roy Hsu) #2

If I understand it correctly, you can simply pass the generic cell type as the parameter to achieve your goal.

No Self and additional protocols required.

extension UITableView {

    func dequeueReusableCell<Cell: UITableViewCell>(
        _ cellType: Cell.Type,
        for indexPath: IndexPath
    )
    -> Cell {

        let identifier = String(describing: cellType)

        guard 
            let cell = dequeueReusableCell(
                withIdentifier: identifier,
                for: indexPath
            ) as? Cell 
        else { fatalError("Could not dequeue a cell with identifier: \(identifier)") }
        
        return cell

    }

}

Then dequeue a cell like the following.

// The type of the dequeued cell should be MyCell.
let cell = tableView.dequeueReusableCell(
    MyCell.self,
    for: indexPath
)

(Mox) #3

Yes, that's true. I used that approach at some point too. I still like the Self approach more, but it's a matter of taste :)


(Mox) #5

Thanks @ahti , this is cool :slight_smile:
So for static methods, the self property is same as Self.Type. Awesome, I didn't realize that.

This means the SelfType in my protocol is completely unnecessary and I can simply use self in those places instead.

However, it's not possible to do coercion with that (as? self) so for that I think I need still the asSelf() method in the protocol.


(Lukas Stabe 🙃) #6

Yeah, that's why I withdrew the post, it doesn't help you at all with returning self :confused: