Abhinav Arora
Abhinav Arora

Reputation: 11

How to create a protocol extension that returns the confirming type?

I am trying to implement a protocol extension JsonProcess which has a function that takes NSData as the argument and i want to create different protocol extensions using self requirement something like this

extension JSONResource where Self: Login {
    func proccessJSON(data: NSData) -> Self{
        return Login()
    }
}

but the compiler shows an error

Cannot convert return expression of type 'Login' to return type 'Self'

the Login class is defined like this

class Login: NSObject {
    var username: String?
    var firstName: String?
}

what should I return here if i serialize the NSData and create an isntanse of the Login class populate it and try to return it?

Upvotes: 1

Views: 651

Answers (2)

Hamish
Hamish

Reputation: 80781

The problem here is that Self is potentially more type-specific than Login. Let's say you create a subclass of Login:

class TopSecretLogin : Login {
    var topSecret : String?
}

Now when you come to use your protocol extension on an instance of TopSecretLogin (assuming Login conforms to JSONResource in the first place) – your processJSON method says that it returns Self, which is TopSecretLogin in this case. However, you're trying to return a Login. Therefore the compiler isn't happy, as you cannot pass a superclass to something that expects its subclass.

The solution depends on whether you intend for Login to be subclassed. If you don't intend for it to be subclassed, then simply change the method signature to returning a Login, and make Login a final class.

If you do intend for this, you need to add a required initialiser to Login, allowing you to construct an arbitrary instance of Self in your protocol extension. An example setup could look like this:

protocol JSONResource {}

class Login : NSObject, JSONResource {

    var username: String?
    var firstName: String?

    required override init() {
        super.init()
    }
}

class TopSecretLogin : Login {

    var topSecret : String?

    required init() {
        super.init()
    }
}

extension JSONResource where Self : Login {
    func processJSON(data: NSData) -> Self {

        // do some processing...

        return Self()
    }
}

let someData = // ...

let l = Login()
l.processJSON(someData) // returns Login

let t = TopSecretLogin()
t.processJSON(someData) // returns TopSecretLogin

Although that all being said, wouldn't it make more sense if processJSON was a static method instead of an instance method? To me it doesn't look like it needs to operate on an instance, it just creates a new instance from a given set of NSData.

You also may want to consider making processJSON a protocol requirement, as surely anything that's a JSONResource should be able to processJSON? In that case you'll want to extend Login rather than the protocol itself in order to implement the method. You can then create a new instance through self.init() at the static scope, or self.dynamicType.init() at instance scope.

You may also want to consider doing this with an initialiser:

protocol JSONResource {
    init(jsonData:NSData)
}

class Login : NSObject, JSONResource {

    var username: String?
    var firstName: String?

    required init(jsonData:NSData) {

        // do some processing

        super.init()
    }
}

class TopSecretLogin : Login {

    var topSecret : String?

    required init(jsonData:NSData) {

        // do some processing

        super.init(jsonData:jsonData)
    }
}

let someData = // ...

let l = Login(jsonData: someData) // returns Login

let t = TopSecretLogin(jsonData: someData) // returns TopSecretLogin

Upvotes: 1

Said Sikira
Said Sikira

Reputation: 4533

Since you specified that the extension only applies if conforming object Self is class Login you should write extension like this:

extension JSONResource where Self: Login {
    func proccessJSON(data: NSData) -> Login {
        return Login()
    }
}

Upvotes: 1

Related Questions