Dynamic dispatching protocol extension doesn't work multiple targets
Your Project
module declares MyClass
's conformance to ProtocolA
.
Swift implements that conformance using a data structure called a “protocol witness table”. For each method declared by the protocol, the witness table contains a function that calls the actual implementation of the method for the conforming type.
To be concrete, there is a witness table for the conformance of MyClass
to ProtocolA
. That witness table contains a function for the dontCrash
method declared by ProtocolA
. That function in the witness table calls the MyClass
dontCrash
method.
You can see the function from the protocol witness table in the stack trace when your test case hits fatalError
:
#8 0x00000001003ab9d9 in _assertionFailure(_:_:file:line:flags:) ()
#9 0x00000001000016fc in ProtocolA.dontCrash() at /Users/rmayoff/TestProjects/Project/Project/AppDelegate.swift:11
#10 0x0000000100001868 in protocol witness for ProtocolA.dontCrash() in conformance MyClass ()
#11 0x000000010000171e in ProtocolA.tryCrash() at /Users/rmayoff/TestProjects/Project/Project/AppDelegate.swift:15
#12 0x00000001030f1987 in ProjectTests.testExample() at /Users/rmayoff/TestProjects/Project/ProjectTests/ProjectTests.swift:12
#13 0x00000001030f19c4 in @objc ProjectTests.testExample() ()
Frame #10 is the call from tryCrash
to the function in the protocol witness table. Frame #9 is the call from the protocol witness table function to the actual implementation of dontCrash
.
Swift emits the protocol witness table in the module that declares the conformance. So, in your case, the witness table is part of the Project
module.
Your override of dontCrash
in your test bundle cannot change the contents of the witness table. It's too late for that. The witness table was fully defined when Swift generated the Project
module.
Here's why it has to be this way:
Suppose I'm the author of the Project
module and you're just a user of it. When I wrote the Project
module, I knew calling MyClass().dontCrash()
would call fatalError
, and I relied on this behavior. In many places inside Project
, I called MyClass().dontCrash()
specifically because I knew it would call fatalError
. You, as a user of Project
, don't know how much Project
depends on that behavior.
Now you use the Project
module in your app, but you retroactively change MyClass().dontCrash()
to not call fatalError
. Now all those places where Project
calls MyClass().dontCrash()
don't behave in the way that I expected when I wrote the Project
module. You have broken the Project
module, even though you didn't change the source code of the Project
module or any of the modules that Project
imports.
It's critical to the correct operation of the Project
module that this not happen. So the only way to change what MyClass().dontCrash()
means (when called from inside the Project
module) is to change the source code of the Project
module itself (or change the source code of something that Project
imports).
Redeclaring members in an extension hides the original member *sometimes*. Why?
This works because you are declaring this extension in a separate module from the original variable declaration.
Across modules a variable name can be overloaded but in my mind this has been a shortcoming of Swift as there is currently no way to explicitly state which module declaration it is that you want.
Always the default implementation of protocol gets called even after implementing the method in class extension in an XCTest file
Found it. I was using @testable import MYProject.
The above method wont work if you are using this. If you are adding all of your project files instead of using the import, then the above method works.
ref: https://medium.com/@hartwellalex/protocol-extensions-and-shared-dependency-injection-in-swift-an-alternative-to-singletons-68934dee6998
Swift 3 protocol extension using selector error
This is a Swift protocol extension. Swift protocol extensions are invisible to Objective-C, no matter what; it knows nothing of them. But #selector
is about Objective-C seeing and calling your function. That is not going to happen because your on(tap:)
function is defined only in the protocol extension. Thus the compiler rightly stops you.
This question is one of a large class of questions where people think they are going to be clever with protocol extensions in dealing with Cocoa by trying to inject Objective-C-callable functionality (selector, delegate method, whatever) into a class via a protocol extension. It's an appealing notion but it's just not going to work.
How to provide a localized description with an Error type in Swift?
As described in the Xcode 8 beta 6 release notes,
Swift-defined error types can provide localized error descriptions by adopting the new LocalizedError protocol.
In your case:
public enum MyError: Error {
case customError
}
extension MyError: LocalizedError {
public var errorDescription: String? {
switch self {
case .customError:
return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
}
}
}
let error: Error = MyError.customError
print(error.localizedDescription) // A user-friendly description of the error.
You can provide even more information if the error is converted
to NSError
(which is always possible):
extension MyError : LocalizedError {
public var errorDescription: String? {
switch self {
case .customError:
return NSLocalizedString("I failed.", comment: "")
}
}
public var failureReason: String? {
switch self {
case .customError:
return NSLocalizedString("I don't know why.", comment: "")
}
}
public var recoverySuggestion: String? {
switch self {
case .customError:
return NSLocalizedString("Switch it off and on again.", comment: "")
}
}
}
let error = MyError.customError as NSError
print(error.localizedDescription) // I failed.
print(error.localizedFailureReason) // Optional("I don\'t know why.")
print(error.localizedRecoverySuggestion) // Optional("Switch it off and on again.")
By adopting the CustomNSError
protocol the error can provide
a userInfo
dictionary (and also a domain
and code
). Example:
extension MyError: CustomNSError {
public static var errorDomain: String {
return "myDomain"
}
public var errorCode: Int {
switch self {
case .customError:
return 999
}
}
public var errorUserInfo: [String : Any] {
switch self {
case .customError:
return [ "line": 13]
}
}
}
let error = MyError.customError as NSError
if let line = error.userInfo["line"] as? Int {
print("Error in line", line) // Error in line 13
}
print(error.code) // 999
print(error.domain) // myDomain
How to override a protocol extension's default implementation of a func?
The default implementations in the protocols are only called if the class that conforms to these protocols do not implement that method itself. The classes' methods override the default implementations of the protocols, not the other way around.
you could do like this:
import UIKit
protocol MyProtocol : class {
func someFuncWithDefaultImplementation()
func someFunc()
var someInt:Int { get set }
}
extension MyProtocol {
func someFuncWithDefaultImplementation() {
someInt = 5
}
func someFunc() {
someFuncWithDefaultImplementation()
}
}
class MyClass : MyProtocol {
var someInt = 6
}
class MyClass2 : MyProtocol
{
var someInt: Int = 4
func someFuncWithDefaultImplementation()
{
someInt = 7
}
}
let class2 = MyClass2()
class2.someFunc()
or like this:
class MyClass : MyProtocol {
var someInt = 6
func someFuncWithDefaultImplementation() {
someInt = 8
}
}
class MyClass2 : MyClass
{
override func someFuncWithDefaultImplementation()
{
someInt = 7
}
}
these were what i got with my tests, but you may find some better solution
Overriding methods in Swift extensions
Extensions cannot/should not override.
It is not possible to override functionality (like properties or methods) in extensions as documented in Apple's Swift Guide.
Extensions can add new functionality to a type, but they cannot override existing functionality.
Swift Developer Guide
The compiler is allowing you to override in the extension for compatibility with Objective-C. But it's actually violating the language directive.
That just reminded me of Isaac Asimov's "Three Laws of Robotics" /p>
Extensions (syntactic sugar) define independent methods that receive their own arguments. The function that is called for i.e. layoutSubviews
depends on the context the compiler knows about when the code is compiled. UIView inherits from UIResponder which inherits from NSObject so the override in the extension is permitted but should not be.
So there's nothing wrong with grouping but you should override in the class not in the extension.
Directive Notes
You can only override
a superclass method i.e. load()
initialize()
in an extension of a subclass if the method is Objective-C compatible.
Therefore we can take a look at why it is allowing you to compile using layoutSubviews
.
All Swift apps execute inside the Objective-C runtime except for when using pure Swift-only frameworks which allow for a Swift-only runtime.
As we found out the Objective-C runtime generally calls two class main methods load()
and initialize()
automatically when initializing classes in your app’s processes.
Regarding the dynamic
modifier
From the Apple Developer Library (archive.org)
You can use the dynamic
modifier to require that access to members be dynamically dispatched through the Objective-C runtime.
When Swift APIs are imported by the Objective-C runtime, there are no guarantees of dynamic dispatch for properties, methods, subscripts, or initializers. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime. /p>
So dynamic
can be applied to your layoutSubviews
-> UIView Class
since it’s represented by Objective-C and access to that member is always used using the Objective-C runtime.
That's why the compiler allowing you to use override
and dynamic
.
Related Topics
Avaudioconverter with Avaudioconverterinputblock Stutters Audio After Processing
Why How to Make Same-Type Requirement in Swift with Generics? Is There Any Way
No Exact Matches in Call to Instance Method Error Message in Swift
Wait for Download Task to Finish in Nsurlsession
Using an Avplayer Returns a "Non-Multipath Connection" Error
Debug View Hierarchy Does Not Render UI
How to Find The Nth Root of a Value
Nstextfield Non-System-Font Content Clipped When Usessinglelinemode Is True
How to Know If a Swiftui Button Is Enabled/Disabled
iOS 16 Swiftui List Background
How to Convert Unicode Character to Int in Swift
Location Access Request in iOS 11
Can't Upload .Ipa from Xcode 8, "The Info.Plist Indicates a iOS App, But Submitting a Pkg or Mpkg."
Close UIdatepicker After Selection When Style Is .Compact
Xcode Error: Ar Reference Image Must Have Non Zero Positive Width
Ibdesignable View Not Rendering