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:
Your declaration of the subscript in the
SearchTree
protocol needs to have{ get }
after it.Collection
requires a subscript that returns itsElement
. You have two subscripts, one of which returns aString?
and one of which returns a(key: Int, value: String)
, but neither of these isElement
, which the compiler needs; therefore the type does not conform toCollection
. If you defineElement
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
Waiting for Alamofire in Unit Tests
Xcode 6, Swift - Read Standard Input (Console) to String
Wkwebview Auto Fill Login Form Swift 2
Preventing a Coredata Crash for Upgrading Users
How Are Hash Collisions Handled
Find Multiple Quoted Words in a String with Regex
How to Transfer the User's Score to Another Scene in Swift and Spritekit
How to Do an If-Else Comparison on Enums with Arguments
Pagination with Firebase Firestore - Swift 4
Swift Add Badge to Navigation Barbuttonitem and Uibutton
Swift & Firebase - How to Store More User Data Other Than Email and Password
What Is Trailing Closure Syntax in Swift
Swiftui Coordinator Not Updating the Containing View's Property
Swift Switch Statement Considered All Cases of Int, But Compiler Still Display Error
Create an Outlet in Storyboard to an Inherited Property