Issue with the call hierarchy of a swift protocol extension - swift

I've implemented a little wrapper for Alamofire's reachability. Now I ran into an issue with a subclass not receiving notifications.
As dfri added in a comment, the problem can be described as a superclass method invoked by a subclass instance does not see an overridden subclass method if it is placed in an extension.
Thanks for his excellent gist showing the reproducible problem with the usual foo/bar: https://gist.github.com/dfrib/01e4bf1020e2e39dbe9cfb14f4165656
The protocol & extension logic:
protocol ReachabilityProtocol: class {
func reachabilityDidChange(online: Bool)
}
extension ReachabilityProtocol {
func configureReachabilityManager() {
// triggered externally:
rechabilityManager?.listener = { [weak self] status in
self?.reachabilityDidChange(online)
}
}
func reachabilityDidChange(online: Bool) {
// abstract, implement in subclasses
}
}
Now ViewController A adopts this protocol and implements the method:
class A: UIViewController, ReachabilityProtocol {
func reachabilityDidChange(online: Bool) {
debugPrint("123")
}
}
Working until here, prints 123.
ViewController B is a subclass and overrides the method from A:
class B: A {
override func reachabilityDidChange(online: Bool) {
super.reachabilityDidChange(online)
debugPrint("234")
}
}
Working, too. Prints now 234 and 123 upon notification.
Now the tricky part, in terms of structuring the code, the overriden method got moved into an own extension inside of B (same swift file as class B):
class B: A {
...
}
// MARK: - Reachability Notification
extension B {
override func reachabilityDidChange(online: Bool) {
super.reachabilityDidChange(online)
debugPrint("234")
}
}
Now B's reachabilityDidChange() isn't called anymore. Output is just 123.
To check the logic I even tried to remove the override keyword: the compiler immediately complains about needing it.
So from what I can say the call hierarchy is correct. Visibility too.
Subsumption: If the method overridden is placed into it's own extension, it's not visible/invoked anymore but the compiler still requires the override keyword.
That's something I didn't came across yet, or it's me working to long on the same project today..
Any hints?

Overriding superclass methods in extensions: only allowed for Objective-C compatible methods
First of all we note that you can only override a superclass method in an extension of a subclass if the method is Objective-C compatible. For classes deriving from NSObject, this is true for all instance methods (which is true here, as UIViewController derives from NSObject). This is covered in e.g. the following Q&A:
Can you override between extensions in Swift or not? (Compiler seems confused!)
Enforcing dynamic dispatch via Objective-C runtime
Now, from Interoperability - Interacting with Objective-C APIs - Requiring Dynamic Dispatch, we note the following
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.
You can use the dynamic modifier to require that access to members be
dynamically dispatched through the Objective-C runtime.
Moreover, from The Language Ref. - Declarations - Declaration Modifyers we read
dynamic (modifier)
Apply this modifier to any member of a class that can be represented
by Objective-C. When you mark a member declaration with the dynamic
modifier, access to that member is always dynamically dispatched using
the Objective-C runtime. Access to that member is never inlined or
devirtualized by the compiler.
Because declarations marked with the dynamic modifier are dispatched
using the Objective-C runtime, they’re implicitly marked with the objc
attribute.
Enforcing dynamic dispatch for reachabilityDidChange(...) in A
Hence, if you add the dynamic modifier to method reachabilityDidChange(...) in your superclass A, then access to reachabilityDidChange(...) will always be dynamically dispatched using the Objective-C runtime, and hence find and make use of the correct overridden reachabilityDidChange(...) method in the class B extension, for instances of B. Hence,
dynamic func reachabilityDidChange(online: Bool) { ... }
in A will fix the above.
Below follows a more minimal example of your issue above, redeemed by demanding dynamic dispatch via obj-c runtime for method foo() in class A (equivalent to your method reachabilityDidChange(...) in class A).
import UIKit
protocol Foo: class {
func foo()
}
extension Foo {
func foo() { print("default") }
}
class A: UIViewController, Foo {
dynamic func foo() { print("A") } // <-- note dynamic here
func bar() { self.foo() }
/* \
hence, foo() is dynamically dispatched here, and for
instances where "self === B()", this is correctly
resolved as the foo() in the extension to B */
}
class B : A { }
extension B {
override func foo() {
super.foo()
print("B")
}
}
let a = A()
a.bar() // A
let b = B()
b.bar() // A B
/* or, only "A" if not using dynamic dispatch */

Related

Accessing optional method in protocol returns scope error

I've exhausted every SO thread about this topic and haven't gotten anywhere with this error.
Code Setup:
#objc protocol MyProtocol: NSObjectProtocl{
#objc optional func myFunction()
}
class MyClass: NSObject, MyProtocol {
func doSomething() {
myFunction?() //Error: cannot find myFunction in scope.
self.myFunction() //Error: Value of type 'MyClass' has no member 'myFunction'
}
}
Things I've tried:
Using responds(to:) to check selector. The same error pops up when calling the function afterward.
if self.responds(to: #selector(MyProtocol.myFunction)){
self.myFunction?() //Error: Value of type 'MyClass' has no member 'myFunction'
}
Using if let to check if the function is implemented runs into the same error above.
Giving the optional function a return type #objc optional func myFunction() -> NSError?
Using a default implementation works, but doesn't allow me to use the #objc tag.
extension MyProtocol {
#objc func myFunction(){ //Error: #objc can only be used with members of classes, #objc protocols, and concrete extensions of classes.
}
}
Other Objective-C implementations that use MyClass need myFunction, so I need to expose myFunction with #objc.
Do I have to implement myFunction inside MyClass if I want to use it. This problem is when converting a file from Obj-C to Swift, where Obj-C allows calling optional methods without having to check their implementation.
class MyClass: NSObject, MyProtocol { ... }
This says that MyClass implements MyProtocol, which allows, but does not require myFunction. The compiler can clearly see that MyClass does not implement myFunction, so self.myFunction() isn't a thing. It could be a thing, but it's clearly not a thing. The protocol doesn't play into this.
You could "work around" this by saying "I don't mean MyClass; I mean MyProtocol":
func doSomething() {
(self as MyProtocol).myFunction?()
}
But this is kind of silly IMO. self doesn't implement myFunction and this is known at compile time.
If the point is to implement this via subclassing, the correct way to do that is to just implement it as empty in the superclass:
class MyClass: NSObject, MyProtocol {
func myFunction() {}
func doSomething() {
myFunction()
}
}
This is precisely equivalent to "do nothing" while allowing overrides in subclasses.

swift subclasses used in generics don't get called when inheriting from NSObject

Partial Solution Update at the end!
Attached is code that produces odd behavior. I copied it out of a swift playground so it should run in one fine.
I created a subclass in my project and passed it to my generic class as the concrete type. However, I quickly noticed that only the base class methods are called. This is shown with myBase and mySub below. Despite the generic class being instantiated as <mySub>, only the base methods are called. The print lines for the subclass are never shown.
Well, I found a simple way around that and that is to not inherit from NSObject. When I used swift native classes, the subclass methods are in fact called. These are secondBase and secondSub.
How do I pass a subclass into a generic class and get the actual subclass to receive calls when inheriting from NSObject?
And why would behavior be different?
import Foundation
// The Protocol
protocol P {
init ()
func doWork() -> String
}
// Generic Class
class G<T: P> {
func doThing() -> String {
let thing = T()
return thing.doWork()
}
}
// NSObject Base Class with Protocol
class A1: NSObject, P {
override required init() {
super.init()
}
func doWork() -> String {
return "A1"
}
}
// NSObject Sub Class
class B1: A1 {
required init() {
super.init()
}
override func doWork() -> String {
return "B1"
}
}
// Swift Base Class
class A2: P {
required init() {
}
func doWork() -> String {
return "A2"
}
}
// Swift Sub Class
class B2: A2 {
required init() {
super.init()
}
override func doWork() -> String {
return "B2"
}
}
print ("Sub class failure with NSObject")
print ("Recieved: " + G<B1>().doThing() + " Expected: B1 - NSObject Sub Class Generic (FAILS)")
print ("\nSub class success with Swift Native")
print ("Recieved: " + G<B2>().doThing() + " Expected: B2 - Swift Sub Class Generic (SUCCEEDS)")
print("")
#if swift(>=5.0)
print("Hello, Swift 5.0")
#elseif swift(>=4.1)
print("Hello, Swift 4.1")
#elseif swift(>=4.0)
print("Hello, Swift 4.0")
#elseif swift(>=3.0)
print("Hello, Swift 3.x")
#else
print("Hello, Swift 2.2")
#endif
Output:
Sub class failure with NSObject
Recieved: A1 Expected: B1 - NSObject Sub Class Generic (FAILS)
Sub class success with Swift Native
Recieved: B2 Expected: B2 - Swift Sub Class Generic (SUCCEEDS)
Hello, Swift 5.0
Partial solution update:
Moving the protocol conformance from the base class to the sub class allows the sub class to behave correctly. Definitions become:
class A1: NSObject
class B1: A1, P
The problem is the base class can no longer be used directly at all when no functionality beyond it is needed. This is mostly an issue if the protocol being conformed to has an associated type. When this is true, you must have a concrete class that conforms to the protocol for use in generics.
One use case here is expecting a base class in the generics (with a protocol involving an associated type) which allows something to function without caring what actual sub class was passed in. This actually ends up being a poor man's form of type erasure in some cases. And you can still use the same generic with the subclass.
G<A1>()
G<B1>()
This was derived from a similar question here: Generic Class does not forward delegate calls to concrete subclass
Partial options are:
remove NSObject and use swift native classes only
when NSObject is required, try to separate protocol conformance from inheritance of NSObject
UPDATE ON THE BELOW IDEA: Doesn't Work
I am going to test if providing an additional layer changes the behavior. Basically have 3 layers, Base class inheriting from NSObject, base Protocol class adding the protocol but inheriting from base and then specific classes. If it can distinguish between the base protocol class and the specific subclass in that case, that would be a functional workaround in all use cases. (and may explain why Apple's NSManagedObject works fine)
Still seems like a bug though.
I was able to confirm your results and submitted it as a bug, https://bugs.swift.org/browse/SR-10617. Turns out this is a known issue! I was informed (by good old Hamish) that I was duplicating https://bugs.swift.org/browse/SR-10285.
In my bug submission, I created a clean compact reduction of your example, suitable for sending to Apple:
protocol P {
init()
func doThing()
}
class Wrapper<T:P> {
func go() {
T().doThing()
}
}
class A : NSObject, P {
required override init() {}
func doThing() {
print("A")
}
}
class B : A {
required override init() {}
override func doThing() {
print("B")
}
}
Wrapper<B>().go()
On Xcode 9.2, we get "B". On Xcode 10.2, we get "A". That alone is enough to warrant a bug report.
In my report I listed three ways to work around the issue, all of which confirm that this is a bug (because none of them should make any difference):
make the generic parameterized type's constraint be A instead of P
or, mark the protocol P as #objc
or, don't have A inherit from NSObject
UPDATE: And it turns out (from Apple's own release notes) there's yet another way:
mark A's init as #nonobjc
This is not an answer so much as a way to avoid the problem.
In most of my code, I did not have to conform to NSObjectProtocol only Equatable and/or Hashable. I have implemented those protocols on the objects that needed it.
I then went through my code, removed all NSObject inheritance except on those classes which inherit from an Apple protocol or object that requires it (Like UITableViewDataSource).
The classes that are required to inherit from NSObject are Generic but they are not typically handed into other Generic classes. Therefore the inheritance works fine. In my MVVM pattern these tend to be the intermediate classes that work with view controllers to make logic like table views reusable. I have a tableController class that conforms to the UITableView protocols and accepts 3 generic viewModel types allowing it to provide the table logic for 95% of my views with no modifications. And when it needs it, subclasses easily provide alternate logic.
This is a better strategy as I am no longer randomly using NSObject for no reason.
This is a second way to avoid the problem.
#matt originally suggested this but then deleted his answer. It is a good way to avoid the problem. His answer was simple. Mark the protocol with objc like this:
// The Protocol
#objc protocol P {
init ()
func doWork() -> String
}
This solves the above sample code and you now get the expected results. But doing this has side effects for swift. At least one of those is here:
How to use #objc protocol with optional and extensions at the same time?
For me, it began a chain of having to make all my protocols objc compatible. That made the change not worth it for my code base. I was also using extensions.
I decided to stay with my original answer at least until Apple fixes this bug or there is a less invasive solution.
I thought this one should be documented in case it helps someone else facing this problem.

In swift, how to access methods of class A inside class B if class B inherits from class A?

Examples for creating mocked classes, methods and protocols would be helpful.
It was answered dozen of times. Here's the extract from one of the answers I gave on equivalent topic, slightly adjusted.
When you have class B derived from class A...
class ClassA: UIViewController {
func myFunc() { print("...") }
}
class ClassB: ClassA {
}
In class B, you can call a method from class A by typing
super.myFunc() **OR** self.myFunc()
...depending on whether or not you have re-implemented that method in a subclass with override keyword. If you did not override it (and you don't necessarily have to), then those two calls are equivalent.
On the other hand, if you added your own implementation in B:
override func myFunc() {
print("My implementation of myFunc")
}
calling super.myFunc() will call implementation from A. And calling self.myFunc() will call your local implementation, both being acceptable from your subclass. And when your subclass calls super as well:
override func myFunc() {
super.myFunc()
print("My implementation of myFunc")
}
calling self.myFunc() from subclass will call implementation from both. Keep in mind, that if you don't call superclass implementation in your override method, you replace this implementation.
In most UI-classes, it's highly recommended to call superclass method upon overriding, in order to avoid any abnormal behavior, unless you provide sufficient implementation on your own (e.g. reposition or draw elements).
P.S. Mocks and unit-testing is the entire solid topic, simply impossible to cover in single post. Please, learn!

Inheritance in Class Vs Protocol

I am little bit messed up with the following concept:
Code 1 :
class New{
func abc(){
print("new class")
}
}
class ClassNew: New {
override func abc() {
print("derived class")
}
}
Code 2:
protocol New{}
extension New{
func abc(){
print("new protocol")
}
}
class ClassNew: New {
func abc() {
print("derived protocol")
}
}
What is the difference between Code 1 and Code 2 as both of them serves the same purpose?
In code 2, classNew is inheriting from new protocol or just conforming to the protocol?
Any Explanation will be highly appreciated!
Code 1 and code 2 are fundamentally not the same.
What is the difference between Code 1 and Code 2 as both of them serves the same purpose?
No they don't. The first one defines a class hierarchy, the second defines a protocol (an API, if you like) and a type that conforms to it.
In code 2, classNew is inheriting from new protocol or just conforming to the protocol?
It's conforming to the protocol. There's no inheritance involved (if you are being pedantic).
Code 1 defines a base class and a class that inherits from it. The subclass overrides the base class for the function abc() and it behaves as you would expect given a class hierarchy i.e.
let x: New = ClassNew()
let y: ClassNew = ClassNew()
print(x.abc()) // prints "derived class"
print(y.abc()) // prints "derived class"
Both print statements call the derived class's abc()
In code 2 you define a protocol with no methods, and an extension to the protocol with an extension method. Note that this is not a "default method" there is nothing in the protocol to default. You then define a class that conforms to the protocol and adds a new method that happens to have the same name as the extension method. The distinction (from the pure class hierarchy) is important because the version of abc() called is determined statically at compile time
protocol New2{}
extension New2{
func abc(){
print("new protocol")
}
}
class ClassNew2: New2 {
func abc() {
print("derived protocol")
}
}
let y2: ClassNew2 = ClassNew2()
let x2: New2 = y2
print(x2.abc()) // prints "new protocol"
print(y2.abc()) // prints "derived protocol"
Even though x2 and y2 are the same object different versions of the function are called. This is because the compiler is not allowed to assume anything about x2 except what it can infer from the protocol. So it doesn't know that the object has anabc() of its own so it must call the extension function.
If you had defined the protocol like this:
protocol New3{
func abc()
}
extension New3{
func abc(){
print("new protocol")
}
}
class ClassNew3: New3 {
func abc() {
print("derived protocol")
}
}
let y3: ClassNew3 = ClassNew3()
let x3: New3 = y3
print(x3.abc()) // prints "derived protocol"
print(y3.abc()) // prints "derived protocol"
This time the compiler knows that the object should have a function abc() and will only use the extension function if it doesn't. Then the normal inheritance rules for classes apply.
Code 2 is an example of protocol extensions. Big difference is that you can't call super.abc() in Code 2 — you have to provide a method implementation that is not in any super context.
Just my opinion — do not use protocol default implementation, as compiler won't save you if you forget to provide "override" when you really need one.
classNew is simply conforming to the protocol in the second code. This is true to every protocol in Swift as inheritance is not currently supported with protocols.
This also answers your first question: there is no significant difference between the two codes in this use case, but, generally speaking, a protocol is good for other kinds of things than subclassing.
Code 1 and Code 2 are exactly same, just conceptually different.
If we put it in simple words, a Class defines the object itself, and a Protocol defines the behaviour of the object.
If compared to Java, a protocol is similar to Interfaces.
Checkout the Protocol Documentation

Overriding superclass initializer in Swift

I've found numerous examples for using the Singleton pattern in Swift 3. I'm trying to use this method:
class Records: RailsData {
static let shared = Records()
private init() {}
...
}
When I do, I get the compiler error:
Overriding declaration requires an 'override' keyword
When I add the override keyword, it compiles and everything seems to work. Is the override needed because I'm subclassing RailsData? RailData is an abstract class in that it is not instantiated directly. I also didn't make it a singleton.
I'm trying to make sure I don't get bit later by using the override modifier...
You are not doing anything wrong ;) The override modifier is required because your RailsData superclass declares a designated initializer with the exact same signature:
class RailsData {
init() {...}
...
}
as such, the override modified is made necessary in your subclass. The same thing applies to inherited methods as well. From the The Swift Programming Language book:
When you write a subclass initializer that matches a superclass designated initializer, you are effectively providing an override of that designated initializer. Therefore, you must write the override modifier before the subclass’s initializer definition. This is true even if you are overriding an automatically provided default initializer.
As with an overridden property, method or subscript, the presence of the override modifier prompts Swift to check that the superclass has a matching designated initializer to be overridden, and validates that the parameters for your overriding initializer have been specified as intended.
Rest assured, you won't be bitten by this later on ;)
Motivating example. To see this initializer overriding in action, try this example:
class SuperClass {
init(x: Int) {
print("3. SuperClass.init(x: \(x))")
}
convenience init() {
print("1. SuperClass.init()")
self.init(x: 123) // Will be overridden by subclass.
}
}
class SubClass: SuperClass {
override init(x: Int) {
print("2. SubClass.init(x: \(x))")
super.init(x: x)
print("4. SubClass.init(x: \(x))")
}
}
// Calls inherited convenience initializer.
let sub = SubClass()
outputs:
SuperClass.init()
SubClass.init(x: 123)
SuperClass.init(x: 123)
SubClass.init(x: 123)
Abstract classes. By the way, there is no direct, linguistic support for abstract classes in Swift — nor in Objective-C — but at least Cupertino is thinking about it ;) Currently, such a concept is merely a soft convention used by frameworks authors, etc.
To add to #Paulo Matteo's comprehensive answer, this is most likely the fix you need:
override private init() {
super.init()
}