Swift Minimum Implementation for Types Conforming to Protocols with Default Implementations

Default implementation of protocol extension in Swift not working

We'll use a more scaled-down example (below) to shed light on what goes wrong here. The key "error", however, is that Case cannot make use of the default implementation of objectWithId() for ... where T: NSManagedObject, I: AnyObject; since type Int does not conform to the type constraint AnyObject. The latter is used to represent instances of class types, whereas Int is a value type.

AnyObject can represent an instance of any class type.

Any can represent an instance of any type at all, including function types.

From the Language Guide - Type casting.

Subsequently, Case does not have access to any implementation of the blueprinted objectWithId() method, and does hence not conform to protocol ObjectByIdFetchable.


Default extension of Foo to T:s conforming to Any works, since Int conforms to Any:

protocol Foo {
typealias T
static func bar()
static func baz()
}

extension Foo where T: Any {
static func bar() { print ("bar") }
}

class Case : Foo {
typealias T = Int

class func baz() {
print("baz")
}
}

The same is, however, not true for extending Foo to T:s conforming to AnyObject, as Int does not conform to the class-type general AnyObject:

protocol Foo {
typealias T
static func bar()
static func baz()
}

/* This will not be usable by Case below */
extension Foo where T: AnyObject {
static func bar() { print ("bar") }
}

/* Hence, Case does not conform to Foo, as it contains no
implementation for the blueprinted method bar() */
class Case : Foo {
typealias T = Int

class func baz() {
print("baz")
}
}

Edit addition: note that if you change (as you've posted in you own answer)

typealias T = Int

into

typealias T = NSNumber

then naturally Case has access to the default implementation of objectWithId() for ... where T: NSManagedObject, I: AnyObject, as NSNumber is class type, which conforms to AnyObject.


Finally, note from the examples above that the keyword override is not needed for implementing methods blueprinted in a protocol (e.g., entityName() method in your example above). The extension of Case is an protocol extension (conforming to ObjectByIdFetchable by implementing blueprinted types and methods), and not really comparable to subclassing Case by a superclass (in which case you might want to override superclass methods).

Providing a default implementation for Collection conformance prevents additional subscript requirements

Two problems:

  1. Your declaration of the subscript in the SearchTree protocol needs to have { get } after it.

  2. Collection requires a subscript that returns its Element. You have two subscripts, one of which returns a String? and one of which returns a (key: Int, value: String), but neither of these is Element, which the compiler needs; therefore the type does not conform to Collection. If you define Element in either your protocol or in the extension, it should compile.

In the protocol:

associatedtype Element = (key: Int, value: String)

or:

associatedtype Element = String?

Or in the extension:

typealias Element = (key: Int, value: String)

or:

typealias Element = String?

EDIT:

The above is true for Swift 4; however, for Swift 3, you also need to define _Element in addition to Element. Copying and pasting your code into a project, the following declaration of the protocol causes everything to compile in Swift 3:

protocol SearchTree: Collection {
associatedtype Element = (key: Int, value: String)
associatedtype _Element = (key: Int, value: String)

subscript(key: Int) -> String? { get }
}

Swift - implement protocol with generic method for different T types

The way you have it written, by conforming to the protocol Client, you have to implement a function get, with a generic, unconstrained argument T. In the example implementations provided, you added a type constraint to the generic parameter T, which does not match the function in the protocol.

There's more than one way you can approach a solution to this problem. Keeping in mind that you said all entities will conform to Mockable, the solution that requires the least change to the code you provided is to use protocol composition to enforce that all parameters T conform to both Decodable and Mockable.

protocol Client {
func get<T: Decodable & Mockable>(_ url: String) -> Promise<T>
}

In your clients, you would implement that function exactly as written.

Now, in your MockClient, you can call T.mock(), and in your real implementations, you can treat T as Decodable as required. Of course, this now requires that even your mock arguments conform to Decodable, but I would assume your mock arguments will be fairly lightweight and thus this wouldn't be a problem.

Make a protocol conform to another protocol

Protocols can inherit each other:

Protocol Inheritance


A protocol can inherit one or more other protocols and can add further requirements on top of the requirements it inherits. The syntax for protocol inheritance is similar to the syntax for class inheritance, but with the option to list multiple inherited protocols, separated by commas:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}

So, you basically need to do this:

protocol InstrumentForProfessional {
var title: String {get}
}

protocol Pen: InstrumentForProfessional {
var title: String {get} // You can even drop this requirement, because it's already required by `InstrumentForProfessional`
var color: UIColor {get}
}

Now everything that conforms to Pen conforms to InstrumentForProfessional too.

Swift Protocol Optional conformance via Non-Optional

If the protocol provides a default implementation that returns an optional:

protocol SomeProtocol {
var foo: Int? { get }
}

extension SomeProtocol {
var foo: Int? { return nil }
}

protocol-conforming types can then provide an overriding non-optional version of the variable/function:

struct StructB: SomeProtocol {
let foo: Int
}

I found this discussed on the Swift Evolution forum:

At the first glance I thought there is a rule that allows us to satisfy protocol requirements with non-optional types, but this resulted in an error. Only after further investigation I noticed that a default implementation must exist in order to 'kind of override' the requirement with a non-optional version.

https://forums.swift.org/t/how-does-this-rule-work-in-regard-of-ambiguity/19448

This Swift team also discusses allowing non-optional types to satisfy optional-value protocols:

Would it make any sense to allow protocol requirement satisfaction with non-optional types, like with failable init's? (Probably with some implicit optional promotion.)

Yep, totally! Except for the part where this changes the behavior of existing code, so we'd have to be very careful about it. This is considered part of [SR-522] Protocol funcs cannot have covariant returns

which is tracked on Stack Overflow here:

Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?

Swift protocol extension in Objective-C class

Objective-C protocols cannot have default implementations.

Objective-C protocols can have real optional methods/properties, unlike Swift protocols, which only have required methods/properties. The workaround for this in Swift is the use of a default implementation, however, sadly those cannot be seen in Objective-C.

I would suggest creating a pure Swift protocol and for all Objective-C classes that want to extend this, write the conformance in Swift, then create @objc wrapper functions in Swift that call the default protocol implementations - if it needs to be called, if it doesn't need to be called, simply ignore it.

Something along the lines of:

protocol SwiftProtocol {
func requiredFunc()
func optionalFunc()
}

extension SwiftProtocol {
func optionalFunc() {}
}

@objc extension ObjcClass: SwiftProtocol {
@objc func requiredFunc() {
print("do something")
}

// This will call the default implementation - can be omitted if you don't need to call the default implementation from Objective-C
@objc func objc_optionalFunc() {
optionalFunc()
}
}

Implementing a function with a default parameter defined in a protocol

This is due to the fact that the call

castedCar.move(to: CGPoint(x: 20, y: 10))

is able to be resolved to the protocol requirement func move(to point: CGPoint) – therefore the call will be dynamically dispatched to via the protocol witness table (the mechanism by which protocol-typed values achieve polymorphism), allowing Car's implementation to be called.

However, the call

castedCar.move()

does not match the protocol requirement func move(to point: CGPoint). It therefore won't be dispatched to via the protocol witness table (which only contains method entries for protocol requirements). Instead, as castedCar is typed as Movable, the compiler will have to rely on static dispatch. Therefore the implementation in the protocol extension will be called.

Default parameter values are merely a static feature of functions – only a single overload of the function will actually be emitted by the compiler (one with all the parameters). Attempting to apply a function by excluding one of its parameters which has a default value will trigger the compiler to insert an evaluation of that default parameter value (as it may not be constant), and then insert that value at the call site.

For that reason, functions with default parameter values simply do not play well with dynamic dispatch. You can also get unexpected results with classes overriding methods with default parameter values – see for example this bug report.


One way to get the dynamic dispatch you want for the default parameter value would be to define a static property requirement in your protocol, along with a move() overload in a protocol extension which simply applies move(to:) with it.

protocol Moveable {
static var defaultMoveToPoint: CGPoint { get }
func move(to point: CGPoint)
}

extension Moveable {

static var defaultMoveToPoint: CGPoint {
return .zero
}

// Apply move(to:) with our given defined default. Because defaultMoveToPoint is a
// protocol requirement, it can be dynamically dispatched to.
func move() {
move(to: type(of: self).defaultMoveToPoint)
}

func move(to point: CGPoint) {
print("Moving to origin: \(point)")
}
}

class Car: Moveable {

static let defaultMoveToPoint = CGPoint(x: 1, y: 2)

func move(to point: CGPoint) {
print("Moving to point: \(point)")
}

}

let castedCar: Moveable = Car()
castedCar.move(to: CGPoint(x: 20, y: 10)) // Moving to point: (20.0, 10.0)
castedCar.move() // Moving to point: (1.0, 2.0)

Because defaultMoveToPoint is now a protocol requirement – it can be dynamically dispatched to, thus giving you your desired behaviour.

As an addendum, note that we're calling defaultMoveToPoint on type(of: self) rather than Self. This will give us the dynamic metatype value for the instance, rather than the static metatype value of what the method is called on, ensuring defaultMoveToPoint is dispatched correctly. If however, the static type of whatever move() is called on (with the exception of Moveable itself) is sufficient, you can use Self.

I go into the differences between the dynamic and static metatype values available in protocol extensions in more detail in this Q&A.

Why does swift hide default implementation for restricted protocols?

What you're saying is that this doesn't compile:

protocol RefreshableView where Self: UIView {
func reload()
}
extension RefreshableView {
func reload() {
print("Default implementation")
}
}
extension UIView: RefreshableView {
}

As Rob Napier points out in a comment, that's a very odd thing to say, because if UIView itself is going to adopt RefreshableView, then what's the protocol for? The original declaration of the protocol means that only a UIView subclass can adopt RefreshableView, so what the compiler expects is that that's what will happen:

protocol RefreshableView where Self: UIView {
func reload()
}
extension RefreshableView {
func reload() {
print("Default implementation")
}
}
class MyView: UIView {}
extension MyView: RefreshableView {
}

That is a useful real-world case, and it compiles just fine.

So you could file a bug against your original code, but you have to admit it
is a very peculiar edge case to start with; you are saying something that no one would in fact ever say.



Related Topics



Leave a reply



Submit