4

I want to create a Convertible-like protocol and extend NSObject subclasses to implement it. In particular:

protocol DataConvertible {

    class func convertFromData(data:NSData) -> Self?

    func data() -> NSData

}

I thought implementing would be as simple as:

extension UIImage : DataConvertible {

    class func convertFromData(data:NSData) -> Self? {
        let image : UIImage? = UIImage(data: data)
        return image
    }

    func data() -> NSData {
        return UIImagePNGRepresentation(self)
    }

}

But this fails to compile with error 'UIImage' is not identical to 'Self'. Am I missing something?

Is there another way to implement a protocol like this?

hpique
  • 119,096
  • 131
  • 338
  • 476

3 Answers3

4

Disclaimer: This is a workaround, not an ideal solution.


Using a typealias in the protocol instead of Self works:

protocol DataConvertible {
    typealias Result

    class func convertFromData(data:NSData) -> Result?

    func data() -> NSData

}

Then return UIImage? instead of Self? from the implementation of convertFromData:

extension UIImage : DataConvertible {

    class func convertFromData(data:NSData) -> UIImage? {
        let image : UIImage? = UIImage(data: data)
        return image
    }

    func data() -> NSData {
        return UIImagePNGRepresentation(self)
    }

}

Update: While this doesn't strictly enforce that UIImage.convertFromData(data) will return a UIImage? by itself, it does make it enforceable with additional generics in some situations (as pointed out by @hpique here).

For instance:

class Cache<T: DataConvertible where T.Result == T> { /* ... */ }

Then, if you try to define UIImage.convertFromData to return an Int?:

class func convertFromData(data:NSData) -> Int? {
    return 1
}

With just the protocol in place, it will compile. However, if you try to create a Cache<UIImage>, it will fail because Int (T.Result) is not equal to UIImage (T).

Mike S
  • 41,895
  • 11
  • 89
  • 84
  • -1 because while this *lets* your protocol implementations return `UIImage` (or their own type, whatever it is), it doesn't *enforce* it. For example, try changing your implementation of `convertFromData()` to return an `Int` instead, and just return `2` instead of `image`. It won't complain - unfortunately, this method isn't better than simply returning `AnyObject` in your protocol definition. – Craig Otis Sep 18 '14 at 21:40
  • @CraigOtis Fair enough (and thanks for the explanation of the down vote). I was mainly looking for an alternative method of specifying the protocol. But you're right, you lose all the enforcement with it. – Mike S Sep 18 '14 at 21:43
  • Yea, I'm pondering a solution myself. Rob Napier has a pretty excellent explanation here of why OP's protocol and implementation can't quite work the way he wants: http://stackoverflow.com/a/25645689/88111 – Craig Otis Sep 18 '14 at 21:44
  • @CraigOtis I believe your -1 is not warranted. You can enforce the type by using generic requirements, as shown here: https://speakerdeck.com/hpique/learning-swift-by-developing-haneke?slide=12. Yes, it's not perfect but we're talking about a workaround for a Swift bug/limitation. – hpique Sep 22 '14 at 01:02
  • @hpique Fair enough, downvote removed. The answer has been improved since I originally came across it. – Craig Otis Sep 22 '14 at 10:03
  • 1
    @CraigOtis This was a nice example of Stack Overflow collaboration. I love this site. :) – hpique Sep 22 '14 at 10:17
1

The answer "should" be:

class func convertFromData(data:NSData) -> Self? {
    return self(data:data)
}

Unfortunately, there seems to be bug that crashes the playground repl. Although it crashes the playground/REPL, it does seem to work in an application.

Although it's of limited utility since the code for the invocation also crashes the compiler:

let image = UIImage.convertFromData(data)       <--- works

let converter = UIImage.self as DataConvertible
let image = converter.convertFromData(data)     <--- crashes

Since this works just fine, I assume it's a playground bug:

class Fred {
    var fred:Int

    required init(value:Int) {
        fred = value
    }
}

extension Fred {
    class func makeOne(value:Int) -> Self {
        return self(value:value)
    }
}

I suspect the problem lies in the fact that UIImage is a class cluster and UIImage(data:) isn't really an initializer, but is a class method [UIImage imageWithData:]

David Berry
  • 40,941
  • 12
  • 84
  • 95
  • 2
    Yeah, swift is definitely not ready for primetime yet. Still way too many ways to crash or otherwise confuse the compiler, not to mention the REPL/Playground. – David Berry Sep 18 '14 at 22:29
0

The best solution here might be to define this as an init() method. That's really the only way for you, in a protocol, to ensure that your callers are going to get back a fully-initialized type in cases where your subclasses don't override the method, while at the same time giving them the opportunity to do so.

Here's a good explanation of why the "promise" your protocol and implementation make may not hold up in all cases:

https://stackoverflow.com/a/25645689/88111

Particularly if you have a subclass of UIImage, say AnimatedImage that does not override the protocol method, leaving the superclass to return UIImage instead, which is not Self.

Note: This doesn't work, but it seems like it should. Bug?

protocol DataConvertible {

    init?(usingData:NSData)

    func data() -> NSData

}

extension UIImage : DataConvertible {

    convenience init?(usingData: NSData) {
        self.init(data: usingData)
    }

    func data() -> NSData {
        return UIImagePNGRepresentation(self)
    }

}
Community
  • 1
  • 1
Craig Otis
  • 31,257
  • 32
  • 136
  • 234