SG1
SG1

Reputation: 2891

Swift: Conform to protocol declaring class function returning instance

I'd like to have a common protocol for returning a new "random"-ly configured instance of a given class.

In ObjC:

@protocol Random
+ (instancetype)random;
@end

@interface UIColor (Random)
<Random>
@end

@implementation
+ (instancetype)random {
    return [UIColor colorWith...];
}
@end

It works in ObjC, but I can't get it to work in Swift.

In Swift:

protocol Random {
    static func randomExample() -> Self
}

extension UIColor: Random {
    final class func randomExample() -> UIColor {
        return UIColor(red: ...)
    }
}

But this throws errors no matter how I configure it.

How can I properly craft a protocol for class methods returning instances of conforming classes?

Upvotes: 2

Views: 1212

Answers (2)

Price Ringo
Price Ringo

Reputation: 3440

ABakerSmith deserves the credit for answering your question but I would like to extend his answer to show how the same protocol applies to the value types of Struct and Enum. Since the value types can not be derived from, their protocol implementation simply uses the type name and not Self.

enter image description here

EDIT: Adding code as requested.

protocol Random {
    static func random() -> Self
}

extension Float: Random {
    static func random() -> Float {
        return Float(arc4random()) / Float(UInt32.max)
    }
}

extension CGFloat: Random {
    static func random() -> CGFloat {
        return CGFloat(Float.random())
    }
}

extension UIColor: Random {
    static func random() -> Self {
        return self.init(
            red:   CGFloat.random(),
            green: CGFloat.random(),
            blue:  CGFloat.random(),
            alpha: 1.0)
    }
}

let c1 = UIColor.random()
let c2 = UIColor.random()
let c3 = UIColor.random()

Upvotes: 4

ABakerSmith
ABakerSmith

Reputation: 22939

Your problem arises from trying to return a UIColor from randomExample, because randomExample wants you to return Self.

Take the following incorrect example:

// Here `Self` is `UIColor`.
extension UIColor: Random {
    class func randomExample() -> Self { 
        return UIColor(...) // Here's the problem...
    }
}

// Here `Self` is `MyColor`.
class MyColor: UIColor {}

Since randomExample isn't overriden by MyColor, randomExample called by MyColor would try to return a UIColor. However, randomExample is expecting to return MyColor instance.

To solve this you can do:

extension UIColor: Random {
    class func randomExample() -> Self {
        // `self` refers to the current class.
        // If this wasn't a class method you would use `self.dynamicType`
        return self(red: 0, green: 0, blue: 0, alpha: 0)
    }
}

let color1 = UIColor.randomExample() // color1 is a `UIColor`.
let color2 = MyColor.randomExample() // color2 is a `MyColor`.

If you're using Swift 2 you need to use:

self.init(red: 0, green: 0, blue: 0, alpha: 0)

You may also be interested in: Protocol func returning Self and Implementing NSCopying in Swift with subclasses.

Upvotes: 4

Related Questions