Identifier property in NSObjectProtocol


(Fahid Attique) #1
extension NSObjectProtocol {

    static var identifier: String { return String(describing: self) }
}

We can use this identifier property in multiple scenarios. Check the following table view dequeue cell example,

    func registerNib(from cellClass: UITableViewCell.Type) {
        let identifier = cellClass.identifier
        register(UINib(nibName: identifier, bundle: nil), forCellReuseIdentifier: identifier)
    }
    
    func registerCell(from cellClass: UITableViewCell.Type) {
        register(cellClass, forCellReuseIdentifier: cellClass.identifier)
    }
    
    func dequeue<T: Any>(cell: UITableViewCell.Type) -> T? where T : UITableViewCell {
        return dequeueReusableCell(withIdentifier: cell.identifier) as? T
    }

In this way you don't have to specify the reusable identifiers for cells, custom views or controller's identifier etc.


(Rod Brown) #2

Hi Fahid,

I actually use a very similar technique to this in codebases I’ve worked in. I definitely use it, and like it. That said, I’m not sure of the value for an identifier of a class beyond these cases. Even then, all this identifier does is convenience out having to write “String(describing:)” at each callsite.

I think the bar for inclusion into the Swift language would be somewhat higher:

  • What value does this extension have for general use, or is it limited to a few specific cases?
  • What value would this feature have over just using String(describing:) or NSStringFromClass() in these cases?

(Fahid Attique) #3

Hi Rod_Brown,

Thanks for sharing your thoughts on it. I've been using this approach for the following purposes and It can be use anywhere you need the class related identifiers to make it reusable but I think it will be considered as specific cases.

  1. UITableView/UICollectionView cells & header/footer register
func registerNib(from cellClass: UITableViewCell.Type) {
        let identifier = cellClass.identifier
        register(UINib(nibName: identifier, bundle: nil), forCellReuseIdentifier: identifier)
    }
func registerNibHeaderFooter(from viewClass: UITableViewHeaderFooterView.Type) {
        let identifier = viewClass.identifier
        register(UINib(nibName: identifier, bundle: nil), forHeaderFooterViewReuseIdentifier: identifier)
    }
  1. UITableView/UICollectionView dequeuing cells or header/footer
    func dequeue<T: Any>(cell: UITableViewCell.Type) -> T? {
        return dequeueReusableCell(withIdentifier: cell.identifier) as? T
    }

    func dequeue<T: Any>(headerFooter: UITableViewHeaderFooterView.Type) -> T? {
        return dequeueReusableHeaderFooterView(withIdentifier: headerFooter.identifier) as? T
    }
  1. In prepare for segue, you can compare the segue identifier with className.identifier

In this way, you don't have to write & manage the re-useable identifiers in code.

What value would this feature have over just using String(describing:) or NSStringFromClass() in these cases?

I think, I found it vie more time saving approach instead of writing & managing multiple identifiers in code. This way is also more retroactive in terms of implementation also gives you better readability for code space.


#4

What you suggest is more problematic than you think:

  • There is no guarantee that the description property is going to return the same value every time it's called, or that it's unique across different objects, or that it contains only characters suitable for whatever purpose you need identifier for. If you really want a String version of a class name, NSStringFromClass() is probably a better choice.

  • It's extremely common — the norm, I'd say — to use multiple table cells of one class — usually UITableViewCell itself – in the same table view. Such uses wouldn't have unique identifiers.


(Rod Brown) #5

I think there are 2 sides to this.

Firstly, is this a good idea:

I’d agree NSStringFromClass is a safer way to get the canonical name. Additionally, you’ve got the problem that class names give you very little (aka no) collision avoidance. There’s a risk someone on a multi-dev codebase who doesn’t use your reuse style may use the class name directly in code. Best to use some form of obfuscated name instead that at least has very low risk of collision (you can’t fix these classes to make the risk zero).

Secondly, is this an SDK issue, or a language issue?

From what has been shown here, it seems this is an SDK issue, not a language issue. Indeed, this stuff is still a problem if we use this with Objective-C. Updating Swift as a language to enable such an isolated use case seems a bit like using a sledgehammer to scratch your back. Even in Obj-C, iOS is only one platform. UIKit, despite being common, is again only one framework on that platform.

At this stage it might just be a better idea for us to house this extension in our codebases, rather than try to “fix” such an isolated problem in the language itself. Especially since, as I’ve mentioned, even the fix is somewhat flaky.

It is an interesting concept to add an identifier to each class for String-based identification, but I’m trying to work out why were wouldn’t just use NSStringFromClass, which whilst not being quite as clean as a class property, would be just as valid. Also, when investigating the risk of collisions, it seems this may not be what you’re after anyway.

@fahidattique55 I have a slightly different implementation: I have a protocol called DefaultReusable which vends defaultReuseIdentifiers for each class, and prepends a meaningful word to the reuse identifier that prints in logs and debugging.

protocol DefaultReusable: AnyObject {
     
    static var defaultReuseIdentifier: String { get }

}

extension DefaultReusable {
     
    static var defaultReuseIdentifier: String {
        return “DefaultReusable_” + NSStringFromClass(self)
    }

}

This has a few wins:

  • It means I can tag classes that are default reusable. I actually extend UICollectionReusableView and UITableViewCell without overloading every class, and without writing it multiple times
  • It provides clarity for users as to intent behind it, rather than a global string extension everyone can access that no one else will use.
  • It allows overriding in all implementing the extension to do something different if it makes sense.
  • It has built in obfuscation with the prepended String which also aids in logging and debugging.

(John Scott) #6

This general approach for instantiating UITableView Cells from a Nib is problematic as the resuableIdentifier must be manually entered into the XIB or Storyboard file.

I think a better approach is to parse the IB files to generate Swift constants which can then be used. I implemented this in https://github.com/jjrscott/Sidekick/blob/master/registernibs_generator It's a pretty simple implementation so you can port it to any language you like. It also allows you to register all UITableView Nibs in one go as the generator can build the function for you.


(Kiel Gillard) #7

Yes! It’d be much less error prone and cumbersome to manage xibs, storyboards and custom cells etc if Xcode generated cell (and other kinds of) identifiers for your source code just like NSManagedObject subclasses, Siri intents etc.


(Rod Brown) #8

While I don’t want to turn this into a UIKit discussion, Xcode-configured identifiers and names for these functions may limit a lot of the flexibility of these UIKit components. Dev-defined identifiers and loading has a lot more flexibility to mix and match different xibs and storyboards with the same classes, use different identifiers to use different cells of the same class and/or nib, etc.

Nevertheless, in this context it would seem these are more SDK issues rather than language issues.