Search code examples
swiftprotocolsfoundation

extending Foundation classes to conform to Swift protocol


I am trying to define a protocol for which I would like to add conformance to several Foundation classes as well as some custom types of my own. I first tried doing this with a convenience initializer in the protocol, but that does not seem possible. I read in the linked thread on the Apple dev forums where they talked about using a class method that returns type Self, but I am unable to figure out how to go about this.

typealias JSONObject = AnyObject
protocol JSONParseable {
    static func fromJSONObject(jsonObject: JSONObject) throws -> Self
}

extension NSURL: JSONParseable {
    class func fromJSONObject(jsonObject: JSONObject) throws -> Self {
        guard let jsonString = jsonObject as? String else {
            throw JSONParseError.ConversionError(message: "blah")
        }
        guard let result = NSURL(string: jsonString) else {
            throw JSONParseError.ConversionError(message: "blah")
        }
        return result // Error: cannot convert return expression of type 'NSURL' to return type 'Self'
    }
}

I found a similar question but the answer there was to mark the class as final -- I obviously can't do that on a Foundation class.

Could someone explain how to fix my approach above? Or suggest a different approach for adding protocol conformance to Foundation classes?


Solution

  • Use self.init(....) instead of NSURL(....) since this also needs to work for NSURL subclasses.

    protocol JSONParseable {
        static func fromJSONObject(jsonObject: JSONObject) throws -> Self
    }
    
    extension NSURL : JSONParseable {
    
        class func fromJSONObject(jsonObject: JSONObject) throws -> Self {
    
            guard let jsonString = jsonObject as? String else {
                throw JSONParseError.ConversionError(message: "blah")
            }
            guard let result = self.init(string: jsonString) else {
                throw JSONParseError.ConversionError(message: "blah")
            }
            return result
        }
    }
    

    Hack : define a typealias T, this will return an NSURL even for subclasses.

    protocol JSONParseable {
        typealias T = Self
        static func fromJSONObject(jsonObject: JSONObject) throws -> T
    }
    
    extension NSURL : JSONParseable {
    
        typealias T = NSURL
        class func fromJSONObject(jsonObject: JSONObject) throws -> T {
    
            guard let jsonString = jsonObject as? String else {
                throw JSONParseError.ConversionError(message: "blah")
            }
            guard let result = NSURL(string: jsonString) else {
                throw JSONParseError.ConversionError(message: "blah")
            }
            return result
        }
    }