What Is 'Where Self' in Protocol Extension

what is 'where self' in protocol extension

That syntax is: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID521

Consider:

protocol Meh {
func doSomething()
}

// Extend protocol Meh, where `Self` is of type `UIViewController`
// func blah() will only exist for classes that inherit `UIViewController`.
// In fact, this entire extension only exists for `UIViewController` subclasses.

extension Meh where Self: UIViewController {
func blah() {
print("Blah")
}

func foo() {
print("Foo")
}
}

class Foo : UIViewController, Meh { //This compiles and since Foo is a `UIViewController` subclass, it has access to all of `Meh` extension functions and `Meh` itself. IE: `doSomething, blah, foo`.
func doSomething() {
print("Do Something")
}
}

class Obj : NSObject, Meh { //While this compiles, it won't have access to any of `Meh` extension functions. It only has access to `Meh.doSomething()`.
func doSomething() {
print("Do Something")
}
}

The below will give a compiler error because Obj doesn't have access to Meh extension functions.

let i = Obj()
i.blah()

But the below will work.

let j = Foo()
j.blah()

In other words, Meh.blah() is only available to classes that are of type UIViewController.

Swift protocol with where Self clause

The ability to put superclass constraints on protocols declarations (that is, being able to define protocol P where Self : C where C is the type of a class) was a premature consequence of
SE-0156, and the syntax should have been rejected in Swift 4.x until the feature was implemented. Attempting to use this feature in Swift 4.x can cause miscompilation and crashes, so I would avoid using it until Swift 5.

In Swift 5 (Xcode 10.2) the feature has now been implemented. From the release notes:

Protocols can now constrain their conforming types to those that
subclass a given class. Two equivalent forms are supported:

protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ }

Swift 4.2 accepted the second form, but it wasn’t fully implemented
and could sometimes crash at compile time or runtime. (SR-5581)
(38077232)

This syntax places a superclass constraint on MyView which restricts conforming types to those inheriting from (or being) UIView. In addition, the usage of MyView is semantically equivalent to a class existential (e.g UIView & MyView) in that you can access both members of the class and requirements of the protocol on the value.

For example, expanding upon the release notes' example:

protocol MyView : UIView {
var foo: Int { get }
}

class C : MyView {} // error: 'P' requires that 'C' inherit from 'UIView'

class CustomView : UIView, MyView {
var foo: Int = 0
}

// ...

let myView: MyView = CustomView(frame: .zero)

// We can access both `UIView` members on a `MyView` value
print(myView.backgroundColor as Any)

// ... and `MyView` members as usual.
print(myView.foo)

self as? within protocol extension

First, this is likely a Swift bug since the compiler should have provided an error if it can't handle this. You should open it at bugs.swift.com. This feels related to SR-544.

That said, as a rule it is very tricky to conform ObjC types to Swift-only protocols. Sometimes it works, sometimes it doesn't. This is a pretty complicated conformance, and clearly it doesn't always work. The solution is to expose the protocol to ObjC by adding @objc:

@objc protocol SomeProtocol: class {}

Swift protocol extension where Self: Equatable doesn't work

Let's start with your CustomEquatable protocol, without the extension:

protocol CustomEquatable {
func isEqualTo(_ other: CustomEquatable) -> Bool
}

Let's define some types to use for experiments:

struct A: Equatable {
let name: String
}

struct B: Equatable {
let id: Int
}

Suppose we then want A and B to conform to CustomEquatable. Then we have four cases to consider:

  • What does a1.isEqualTo(a2) mean (where a1 and a2 are both of type A)?
  • What does b1.isEqualTo(b2) mean (where b1 and b2 are both of type B)?
  • What does a.isEqualTo(b) mean (where a is an A and b is a B)?
  • What does b.isEqualTo(a) mean (where b is a B and a is an A)?

For the first two cases, possible answers are that a1.isEqualTo(a2) if and only if a1 == a2 and b1.isEqualTo(b2) if and only if b1 == b2.

For the second two cases, we have to decide if there's a way for an A to equal a B. The simplest solution (I think) is that an A can never equal a B.

So we can write the conformances like this:

extension A: CustomEquatable {
func isEqualTo(_ other: CustomEquatable) -> Bool {
return (other as? A) == self
}
}

extension B: CustomEquatable {
func isEqualTo(_ other: CustomEquatable) -> Bool {
return (other as? B) == self
}
}

The only difference in these two conformances is the cast-to type (on the right side of as?). So we can factor out the conformances into a protocol extension like this:

extension CustomEquatable where Self: Equatable {
func isEqualTo(_ other: CustomEquatable) -> Bool {
return (other as? Self) == self
}
}

With this protocol extension, we can make A and B conform to CustomEquatable without implementing isEqualTo for each:

extension A: CustomEquatable { }
extension B: CustomEquatable { }

To test the code:

let a1 = A(name: "a1")
let a2 = A(name: "a2")
let b1 = B(id: 1)
let b2 = B(id: 2)

a1.isEqualTo(a1) // true
a1.isEqualTo(a2) // false
b1.isEqualTo(b1) // true
b1.isEqualTo(b2) // false
a1.isEqualTo(b1) // false
b1.isEqualTo(a1) // false

How to properly use 'self' in a protocol extension to avoid having to use protocol stubs?

The procotol condition is wrong. You should use:

extension Camera where Self: UIViewController {

Why can't I change variables in a protocol extension where self is a class?

Your example doesn't compile because MyProtocol isn't class-bound, and as such can have mutating requirements and extension members. This includes property setters, which are by default mutating. Such members are free to re-assign a completely new value to self, meaning that the compiler needs to ensure they're called on mutable variables.

For example, consider:

public protocol MyProtocol {
init()
var i: Int { get set } // implicitly `{ get mutating set }`
}

extension MyProtocol {
var i: Int {
get { return 0 }
// implicitly `mutating set`
set { self = type(of: self).init() } // assign a completely new instance to `self`.
}
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
func a() {
i = 0 // error: Cannot assign to property: 'self' is immutable
}
}

final class C : MyProtocol2 {
init() {}
}

let c = C()
c.a()

If this were legal, calling c.a() would re-assign a completely new instance of C to the variable c. But c is immutable, therefore the code is not well formed.

Making MyProtocol class bound (i.e protocol MyProtocol : AnyObject or the deprecated spelling protocol MyProtocol : class) works because now the compiler knows that only classes can conform to MyProtocol. Therefore it imposes reference semantics by forbidding mutating requirements and extension members and therefore prevents any mutations of self.

Another option at your disposal is to mark the setter for the requirement i as being nonmutating – therefore meaning that it can only be satisfied by a non-mutating setter. This makes your code once again well-formed:

public protocol MyProtocol {
init()
var i: Int { get nonmutating set }
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
func a() {
i = 0 // legal
}
}

Swift protocol extension with specific Self type

Is there a way to specify a default value for the parameter for specific conforming structs when doing a protocol extension?

You have highlighted the problems with this approach in the question already.


How can I solve it in a different way?

UnsignedInteger inherits from BinaryInteger that can provide you bitWidth information (UInt8 => 8, UInt16 => 16 and so on).

extension UnsignedInteger {
func hex(uppercase: Bool = true) -> String {
let fieldWidth = self.bitWidth / 4
return String(format: "%0\(fieldWidth)\(uppercase ? "X" : "x")", self as! CVarArg)
}
}

Above makes it work for UInt, UInt8, UInt16, UInt32 & UInt64.


Taking it one step further, you can do this with FixedWidthInteger & now it will work for all signed and unsigned integers.
Sample Image

Self, protocol extension and non-final class

Here's my understanding of what you are seeing:

  1. You get the compilation error Method 'loadFromNib' in non-final class 'UIView' must return 'Self' to conform to protocol 'NibLoadable' at the point where you declare extension UIView: NibLoadable {}. Let's look at what this statement means to the compiler. It's saying "UIView (and all of its subclasses since it is a non-final class) are adopting the NibLoadable protocol. It means, for UIView, that there will be method with the signature static func loadFromNib(name: String?) -> UIView, because Self in this context is UIView."

    But what does this mean for subclasses of UIView? They inherit their conformance and might inherit the implementation of the method from UIView itself. So any subclass of UIView could have the method with the signature static func loadFromNib(name: String? = nil) -> UIView. However, the NibLoadable protocol that all subclasses also conform to says that the return type of that method must be Self. And in the case of any UIView subclass (for example, let's say "MyView"), the return type of the inherited method will be UIView and not MyView. So any subclass would then violate the contract of the protocol. I realize that your protocol extension uses Self and wouldn't create that issue, but technically, you could still also implement the method directly in a UIView extension, and it seems like the Swift compiler just won't allow it at all for that reason. A better implementation might find the Swift compiler verifying that a protocol extension exists which provides the implementation and there is no conflicting inherited implementation, but this appears to just not exist at this time. So for safety, my guess is that the compiler prevents ANY protocols that have methods with Self return types from being adopted by a non-final class. Thus the error you see.

    However, making UIView a final class makes that whole inheritance of a non-conforming method possibility and issue go away, which fixes the error.

  2. The reason why changing the return type in the protocol to UIView fixes everything is because not having 'Self' as the return type now relieves the compiler's concern about inherited versions of the method having a non-conforming return type. E.g., if UIView were to implement the method static func loadFromNib(name: String?) -> UIView, and subclasses inherited that method, the protocol contract would still hold for those subclasses, so there is no problem!

    On the other hand, the type inference works, because the subclasses of UIView are getting their method implementation from the protocol extension (since the method is not implemented directly in UIView). That implementation returns the type Self, which tells the compiler that the returned value has the same type as the type the method was called on, and the protocol is satisfied, because any subclass of UIView will have a Self type that is a subclass of the required type of UIView.

  3. Removing the where clause works only in this specific case because you changed the protocol method to return UIView, and the protocol extension defines a matching method implementation that returns Self, and then only UIView is getting that extension in your sample code. So the protocol requirement of the method returning UIView matches the implementation UIView is getting which returns Self (which happens to be UIView in this case). But, should you try to make any type other than UIView get the protocol extension method, e.g.

    class SomeClass : NibLoadable {}

    or even

    class MyView:UIView, NibLoadable {}

    the compiler wouldn't allow it, because the Self return type in the protocol extension method would not match the UIView required in the protocol. I feel like in the case of "MyView" or other UIView subclasses though, the compiler error might be a bug, since a method that returns MyView would satisfy the protocol requirement that a method return a UIView, if MyView did inherit from UIView.

To summarize some key points:

  • It doesn't look like the protocol extension has any role in the compiler error you noted. Just this will also create the error:

    protocol NibLoadable {
    static func loadFromNib(name: String?) -> Self
    }

    extension UIView: NibLoadable {}

    So it looks like the compiler doesn't allow non-final classes to adopt protocols by using default implementations of methods that have a return type of Self, period.

  • If you change the protocol method signature to return UIView instead of Self that particular compiler warning goes away because there is no longer the possibility of subclasses inheriting a superclass return type and breaking the protocol. And you can then add conformance to the protocol to UIView with your protocol extension. However, you will get a different error if you try to adopt the protocol for any type other than UIView, because the protocol return type of UIView will not match the protocol extension method's return type of Self except in the single case of UIView. This may be a bug, in my opinion, because Self for any subclass of UIView should meet the required UIView return type contract.

  • But strangely enough, if you adopt the protocol in UIView only, subclasses of UIView will inherit their conformance to the protocol (avoiding the triggering of any of the two above compiler errors) and get their generic implementations from the protocol extension as long as UIView doesn't explicitly implement the protocol method itself. So the subclasses will get the type inference of appropriate Self, and meet the protocol contract for having that method return a UIView.

I'm pretty sure there are one or more bugs mixed up in all this, but someone on the Swift team would have to confirm that to be sure.

UPDATE

Some clarification from the Swift team in this Twitter thread:

https://twitter.com/_danielhall/status/737782965116141568

As suspected, it's a compiler limitation (though apparently not considered an outright bug) that protocol matching does not consider subtypes, only exact type matches. Which is why extension UIView:NibLoadable {} will work when the protocol method defines a return type of UIView, but extension MyView:NibLoadable {} will not.

Objective C syntax corresponding to Swift extension where self: some protocol

There is no Objective-C syntax for that. (Not sure if there’s even a Swift syntax like this; we have a generic where clause, but not on self. And Objective-C doesn’t offer full generic implementation, only offering lightweight implementation.)



Related Topics



Leave a reply



Submit