2

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?

Community
  • 1
  • 1
SG1
  • 2,871
  • 1
  • 29
  • 41

2 Answers2

4

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.

Community
  • 1
  • 1
ABakerSmith
  • 22,759
  • 9
  • 68
  • 78
  • The magic here is indeed in the `self()` and `self.init()` - I wouldn't have ever guessed that and couldn't find this tip using google search. Thank you. – SG1 Sep 08 '15 at 20:59
4

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()
Price Ringo
  • 3,424
  • 1
  • 19
  • 33
  • Personally I liked your answer better and found it to be more helpful (except I couldn't copypaste the code) - and yes, from an understanding perspective, not just a get're done perspective. I have respected your request nonetheless. Thanks! – SG1 Sep 08 '15 at 20:57