Dynamic Dispatching Protocol Extension Doesn't Work Multiple Targets

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



Leave a reply



Submit