Raul Agrait
Raul Agrait

Reputation: 6018

Swift 2 protocol extension not calling overridden method correctly

I've been running into an issue using Swift 2's protocol extensions with default implementations. The basic gist is that I've provided a default implementation of a protocol method which I am overriding in a class that implements the protocol. That protocol extension method is being called from a base class, which is then calling a method which I have overridden in a derived class. The result is that the overridden method is not being called.

I've tried to distill the problem to the smallest possible playground which illustrates the issue below.

protocol CommonTrait: class {
    func commonBehavior() -> String
}

extension CommonTrait {
    func commonBehavior() -> String {
        return "from protocol extension"
    }
}

class CommonThing {
    func say() -> String {
        return "override this"
    }
}

class ParentClass: CommonThing, CommonTrait {
    override func say() -> String {
        return commonBehavior()
    }
}

class AnotherParentClass: CommonThing, CommonTrait {
    override func say() -> String {
        return commonBehavior()
    }
}

class ChildClass: ParentClass {
    override func say() -> String {
        return super.say()
        // it works if it calls `commonBehavior` here and not call `super.say()`, but I don't want to do that as there are things in the base class I don't want to have to duplicate here.
    }
    func commonBehavior() -> String {
        return "from child class"
    }
}

let child = ChildClass()
child.say() // want to see "from child class" but it's "from protocol extension”

Upvotes: 10

Views: 7084

Answers (3)

user3441734
user3441734

Reputation: 17534

that is Swift's behavior. It can be good or bad, depending or your needs. Swift use static dispatch, so which method is called must be known during compilation. There are some advantages, and as usually, some disadvantages. To see, how Swift works at present time, see next very simple example. For me it looks logically ...

protocol P {
    func foo()->Void
}
extension P {
    func foo()->Void {
        print("protocol foo")
    }
}
class A:P {
}
class B:A {
    func foo() {
        print("B foo")
    }
}
class C:B {

}
class D: C {
    // here the implementation must be overriden, 
    // due the indirect inheritance from B
    override func foo() {
        print("D foo")
    }
}
let a = A()      // a is A type
a.foo()          // protocol foo
let b = B()      // B is B type
b.foo()          // B foo
let p:P = B()    // p is protocol P
// compiler is not able to know, what i would like, the dynamicType of p
// can be everything, conforming to protocol P
p.foo()          // protocol foo
(p as? A)?.foo() // protocol foo
// casting to B type, I decided, that p is B type
(p as? B)?.foo() // B foo
(p as? D)?.foo() // nothing is printed, becase the result of casting is nil

// here the types are known at compile time
let c = C()
c.foo()          // B foo
let d = D()
d.foo()          // D foo
let e:C = D()
e.foo()          // D foo

Upvotes: 1

ReduxDJ
ReduxDJ

Reputation: 479

To simplify my lack thereof understanding what the word "Yet" means in a non-specific error. I figured out, that I can't seem write a function with arguments, in overriding extended function, and the compiler gives me an error like this, but if I write a simple function with no arguments, and make a custom implementation and call it with my overridden "simple function" It works:

import Foundation
import UIKit

extension UIViewController {

    func slideInView(direction: Direction = Direction.LEFT, duration: CFTimeInterval = 0.5, closure:()->() ) {

        let animation               = CABasicAnimation(keyPath: "transform.translation.x")
        animation.fromValue         = self.view.bounds.width
        animation.toValue           = 0
        animation.duration          = 0.3
        animation.fillMode          = kCAFillModeForwards;
        animation.removedOnCompletion = false

        UIView.animateWithDuration(0.6, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
            self.view!.layer.addAnimation(animation,forKey:nil);

            }, completion: {(finished) -> () in
                closure()
        });
    }

    func slide()  {
        self.slideInView(.LEFT,duration: 0.66) {
            print("Slide in Left Complete")
        }
    }
}

class OtherUIViewController: UIViewController {



    override  func slide() {
        self.slideFromBottom(.BOTTOM,duration: 0.66) {
            print("Slide in Bottom Complete")
        }
    }

    func slideFromBottom(direction: Direction = Direction.BOTTOM, duration: CFTimeInterval = 0.5, closure:()->() ) {

        let animation               = CABasicAnimation(keyPath: "transform.translation.y")
        animation.fromValue         = self.view.bounds.height
        animation.toValue           = 0
        animation.duration          = 0.3
        animation.fillMode          = kCAFillModeForwards
        animation.removedOnCompletion = false

        UIView.animateWithDuration(0.6, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
            self.view!.layer.addAnimation(animation,forKey:nil);

            }, completion: {(finished) -> () in
                closure()
        });
    }
}

Upvotes: 1

Qbyte
Qbyte

Reputation: 13243

Unfortunately protocols don't have such an dynamic behavior (yet).

But you can do that (with the help of classes) by implementing commonBehavior() in the ParentClass and overriding it in the ChildClass. You also need CommonThing or another class to conform to CommonTrait which is then the superclass of ParentClass:

class CommonThing: CommonTrait {
    func say() -> String {
        return "override this"
    }
}

class ParentClass: CommonThing {
    func commonBehavior() -> String {
        // calling the protocol extension indirectly from the superclass
        return (self as CommonThing).commonBehavior()
    }

    override func say() -> String {
        // if called from ChildClass the overridden function gets called instead
        return commonBehavior()
    }
}

class AnotherParentClass: CommonThing {
    override func say() -> String {
        return commonBehavior()
    }
}

class ChildClass: ParentClass {
    override func say() -> String {
        return super.say()
    }

    // explicitly override the function
    override func commonBehavior() -> String {
        return "from child class"
    }
}
let parent = ParentClass()
parentClass.say()          // "from protocol extension"
let child = ChildClass()
child.say()                // "from child class"

Since this is only a short solution for your problem I hope it fits in your project.

Upvotes: 7

Related Questions