Search code examples
swiftcastingswift-extensions

In Swift, can you write an extension which returns a typed instance of the class the extension is on?


This is one of those things that seems simple enough, but doesn't work as you'd expect.

I'm working on a 'fluent/chaining'-style API for my classes to allow you to set properties via functions which can be chained together so you don't have to go crazy with initializers. Plus, it makes it more convenient when working with functions like map, filter and reduce which share the same kind of API.

Consider this RowManager extension...

extension RowManager
{
    @discardableResult
    public func isVisible(_ isVisible:Bool) -> RowManager
    {
        self.isVisible = isVisible

        return self
    }
}

This works exactly as one would expect. But there's a problem here... if you're working with a subclass of RowManager, this downcasts the object back to RowManager, losing all of the subclass-specific details.

"No worries!" I thought. "I'll just use Self and self to handle the type!" so I changed it to this...

extension RowManager
{
    @discardableResult
    public func isVisible(_ isVisible:Bool) -> Self // Note the capitalization representing the type, not instance
    {
        self.isVisible = isVisible

        return self // Note the lowercase representing the instance, not type
    }
}

...but that for some reason won't even compile giving the following error...

Command failed due to signal: Segmentation fault: 11

UPDATE

Doing more research, this seems to be because our code both is in, and also uses, dynamic libraries. Other questions here on SO also talk about that specific error in those cases. Perhaps this is a bug with the compiler because as others have correctly pointed out, this code works fine in a stand-alone test but as soon as the change is made in our code, the segmentation fault shows up.

Remembering something similar with class functions that return an instance of that type, I recalled how you had to use a private generic function to do the actual cast, so I tried to match that pattern with the following...

extension RowManager
{
    @discardableResult
    public func isVisible(_ isVisible:Bool) -> Self // Note the capitalization
    {
        self.isVisible = isVisible

        return getTypedSelf()
    }
}

private func getTypedSelf<T:RowManager>() -> T
{
    guard let typedSelfInstance = self as? T
    else
    {
        fatalError() // Technically this should never be reachable.
    }

    return typedSelfInstance
}

Unfortunately, that didn't work either.

For reference, here's the class-based code I attempted to base that off of (className is another extension that simply returns the string-representation of the name of the class you called it on)...

extension UITableViewCell
{
    /// Attempts to dequeue a UITableViewCell from a table, implicitly using the class name as the reuse identifier
    /// Returns a strongly-typed optional
    class func dequeue(from tableView:UITableView) -> Self?
    {
        return self.dequeue(from:tableView, withReuseIdentifier:className)
    }

    /// Attempts to dequeue a UITableViewCell from a table based on the specified reuse identifier
    /// Returns a strongly-typed optional
    class func dequeue(from tableView:UITableView, withReuseIdentifier reuseIdentifier:String) -> Self?
    {
        return self.dequeue_Worker(tableView:tableView, reuseIdentifier:reuseIdentifier)
    }

    // Private implementation
    private class func dequeue_Worker<T:UITableViewCell>(tableView:UITableView, reuseIdentifier:String) -> T?
    {
        return tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) as? T
    }
}

Solution

  • At WWDC Apple confirmed this was a Swift compiler issue that something else In our codebase was triggering, adding there should never be a case where you get a Seg11 fault in the compiler under any circumstances, so this question is actually invalid. Closing it now, but I will report back if they ever address it.