Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using 'self' in class extension functions in Swift

Tags:

swift

I'm looking to be able to pull out an instance of a UIView subclass from a Nib.

I'd like to be able to call MyCustomView.instantiateFromNib() and have an instance of MyCustomView. I'm almost ready to just port the working Objective-C code I have via the bridging header, but figured I'd try the idiomatic approach first. That was two hours ago.

extension UIView {
    class func instantiateFromNib() -> Self? {

        let topLevelObjects = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)

        for topLevelObject in topLevelObjects {
            if (topLevelObject is self) {
                return topLevelObject
            }
        }

        return nil
    }
}

Now if (topLevelObject is self) { is wrong because "Expected type after 'is'". What I've tried after that shows a lot about what I don't understand about the Swift type system.

  • if (topLevelObject is Self) {
  • if (topLevelObject is self.dynamicType) {
  • if (topLevelObject is self.self) {
  • A million other variations that are not even wrong.

Any insight is appreciated.

like image 451
rob5408 Avatar asked Jul 01 '15 15:07

rob5408


People also ask

How to add new functionality to an existing Swift class?

In Swift, we can add new functionality to existing types. We can achieve this using an extension. We use the extension keyword to declare an extension. For example, Here, we have created an extension of the Temperature class using the extension keyword. Now, inside the extension, we can add new functionality to Temperature.

How do you extend a type in Swift?

Creating extensions is similar to creating named types in Swift. When creating an extension, you add the word extension before the name. extension SomeNamedType { // Extending SomeNamedType, and adding new // functionality to it. } You can extend a particular named type, add a new computed instance, and type properties to it.

What are swift extensions?

Swift - Extensions. Functionality of an existing class, structure or enumeration type can be added with the help of extensions. Type functionality can be added with extensions but overriding the functionality is not possible with extensions.

How to add computed properties to an extension in Swift?

Note: Properties defined inside the class (like celsius) can be used inside the extension too. In Swift, we cannot add stored properties in extensions. For example, However, Swift lets us add computed properties to an extension. For example, extension Circle { // computed property var area: Double { ... } }


2 Answers

Using the approach from How can I create instances of managed object subclasses in a NSManagedObject Swift extension? you can define a generic helper method which infers the type of self from the calling context:

extension UIView {

    class func instantiateFromNib() -> Self? {
        return instantiateFromNibHelper()
    }

    private class func instantiateFromNibHelper<T>() -> T? {
        let topLevelObjects = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)

        for topLevelObject in topLevelObjects {
            if let object = topLevelObject as? T {
                return object
            }
        }
        return nil
    }
}

This compiles and works as expected in my quick test. If MyCustomView is your UIView subclass then

if let customView = MyCustomView.instantiateFromNib() {
    // `customView` is a `MyCustomView`
    // ...
} else {
    // Not found in Nib file
}

gives you an instance of MyCustomView, and the type is inferred automatically.


Update for Swift 3:

extension UIView {

    class func instantiateFromNib() -> Self? {
        return instantiateFromNibHelper()
    }

    private class func instantiateFromNibHelper<T>() -> T? {
        if let topLevelObjects = Bundle.main.loadNibNamed("CustomViews", owner: nil, options: nil) {
            for topLevelObject in topLevelObjects {
                if let object = topLevelObject as? T {
                    return object
                }
            }
        }
        return nil
    }
}
like image 72
Martin R Avatar answered Nov 15 '22 20:11

Martin R


I believe the conditional expression you're looking for is topLevelObject.dynamicType == self

Combining this with unsafeBitCast (which, by Apple's own documentation, "Breaks the guarantees of Swift's type system"), we can forcefully downcast topLevelObject to self's type. This should be safe because we already made sure that topLevelObject is the same type as self

This is one way to get around the helper method using generics that Martin R described.

extension UIView {
    class func instantiateFromNib() -> Self? {

        let bundle = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)

        for topLevelObject in topLevelObjects {
            if topLevelObject.dynamicType == self {
                return unsafeBitCast(topLevelObject, self)
            }
        }
        return nil
    }
}

Note that Apple also says in their documentation for unsafeBitCast:

There's almost always a better way to do anything.

So be careful!

like image 27
jperl Avatar answered Nov 15 '22 21:11

jperl