Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override swift protocol functions for subclasses (e.g. UILabel from UIView)

I'm trying to implement an extended function that should differ based on what type of class is using it. The objects need to be UIView (or subclass). It should always use function extended on the specified type, but if it doesn't conform to any of them they should use the UIView method instead (as a fallback).

Here is an example of what I'm trying to do:

protocol aProtocol {
    typealias completionBlock = (_ finished:Bool)->()
    func doSomething(completion: completionBlock)
}


extension UIView: aProtocol {
    func doSomething(completion: (Bool) -> ()) {
        print("Im an UIView")
    }
}

extension aProtocol where Self: UILabel {
    func doSomething(completion: (Bool) -> ()) {
        print("im an UILabel")
    }
}

extension aProtocol where Self: UIImageView {
    func doSomething(completion: (Bool) -> ()) {
        print("im an UIImageView")
    }
}

Excecute:

UIView().doSomething { (foo) in } // Should print "Im an UIView"
UIButton().doSomething { (foo) in } // Should print "Im an UIView" (UIButton doesent have specified extended function so should fall back on the UIView function)
UILabel().doSomething { (foo) in } // Should print "im an UILabel"
UIImageView().doSomething { (foo) in } // Should print "im an UIImageView"

Which now prints:

Im an UIView
Im an UIView
Im an UIView
Im an UIView

This means that it always uses the UIView method, even though I want it to use it's own methods. My goal is so it prints:

Im an UIView
Im an UIView
im an UILabel
im an UIImageView
like image 623
Pointblaster Avatar asked Aug 05 '19 11:08

Pointblaster


People also ask

Can I override function in extension Swift?

If a protocol defines a method, and provides a default implementation in an extension, a class can override that method, and the override will be called for any reference to the instance, even if the reference's type is declared as the protocol.

What is override func in Swift?

Swift version: 5.6. The override is used when you want to write your own method to replace an existing one in a parent class. It's used commonly when you're working with UIViewControllers , because view controllers already come with lots of methods like viewDidLoad() and viewWillAppear() .


Video Answer


2 Answers

You can achieve that as below, You just need to expose aProtocol to Objective-c runtime for overriding its methods in the extension.

@objc protocol aProtocol {
    typealias completionBlock = (_ finished:Bool)->()
    func doSomething(completion: completionBlock)
}

extension UIView: aProtocol {
    func doSomething(completion: (Bool) -> ()) {
        print("Im an UIView")
    }
}

extension UILabel {
    override func doSomething(completion: (Bool) -> ()) {
        // you can call super.doSomething(completion: completion)
        print("im an UILabel")
    }
}

extension UIImageView {
    override func doSomething(completion: (Bool) -> ()) {
        // you can call super.doSomething(completion: completion)
        print("im an UIImageView")
    }
}

Output:

Im an UIView
Im an UIView
im an UILabel
im an UIImageView
like image 81
Kamran Avatar answered Oct 16 '22 11:10

Kamran


- The answer

concrete type conforming to a protocol will used over the protocol constraint. So by changing this:

extension UIView: aProtocol {
    func doSomething(completion: (Bool) -> ()) {
        print("Im an UIView")
    }
}

to this:

extension aProtocol where Self: UIView {
    func doSomething(completion: (Bool) -> ()) {
        print("im an UIView")
    }
}

extension UIView: aProtocol {}

Your code will work as you expect.

- The alternative

You can achieve all of your desired prints with this:

extension aProtocol {
    func doSomething(completion: completionBlock) {
        print("im a \(type(of: self))")
    }
}

extension UIView: aProtocol {}

This means you can check the actual type of the object right inside the protocol extension.

- The explanation

Protocol extensions doesn't override any method. In fact, they are just default implementation if the actual concrete type doesn't implement it.

And protocol constraint defines witch type can infer it's default implementation. So:

extension aProtocol where Self: UILabel

means any subcalss of UILabel that is conformed to aProtocol and does not implement the requirements should infer de default implementation. So this will work only if UILabel conforms to aProtocol directly:

extension UILabel: aProtocol {}

or if it's supercalss conforms to it:

extension UIView: aProtocol {}
like image 37
Mojtaba Hosseini Avatar answered Oct 16 '22 09:10

Mojtaba Hosseini