Swift override function in extension
Asked Answered
R

3

7

If I have a class:

class Spaceship<FuelType> {
    function prepareForLiftoff() throws {
        //Start the countdown!
    }
}

I originally assumed that I would be able to override prepareForLiftoff without subclassing by adding an extension:

extension Spaceship where FuelType: CollectionType {
    func prepareForLiftoff() throws {}
} 

This code doesn't compile though, the error says invalid redeclaration of the function, which makes sense.

My question is: Is there anyway to override a function of a particular class? In other words can I replace the functionality under certain conditions like the example above where FuelType: CollectionType. If not, is there any other workaround or way to achieve that behavior (maybe declaring another protocol, idk)

Now that I think about it more, I would have to say that's not possible because what's to stop someone from overriding any of the standard library functions?

Rubbico answered 23/11, 2015 at 1:24 Comment(0)
G
24

From the documentation:

Extensions can add new functionality to a type, but they cannot override existing functionality.

The documentation lists carefully and precisely what an extension is allowed to do.

As to your question:

Is there anyway to override a function of a particular class

Yes, it's called subclassing.

Ghana answered 23/11, 2015 at 1:37 Comment(6)
Fair enough, I had my fingers crossed.Rubbico
How would one override a function of a struct?Trotskyite
@matt: Do you have an idea why overriding in an extension in a different framework is possible: #46124463 ?Electrophorus
@MartinR bugs.swift.org/browse/SR-3228 and bugs.swift.org/browse/SR-2935Ghana
@matt: Thanks! – (I wonder a bit why SR-3228 is closed as a duplicate of SR-2935, the latter seems to be specific about overriding Objective-C methods, but that is not important to me at present.)Electrophorus
You can override a function of a parent class in an extension. The question is if you should.Etiology
B
2

Instead of overriding, you may like to try swizzling. For example the following code allows you to run your own viewWillAppear. Swift 3:

extension UIViewController {
    open override class func initialize() {
        // make sure this isn't a subclass
        guard self === UIViewController.self else { return }
        DispatchQueue.once(token: "viewWillAppear") {
            let originalSelector = #selector(self.viewWillAppear(_:))
            let swizzledSelector = #selector(self.proj_viewWillAppear1(animated:))
            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }

    func proj_viewWillAppear1(animated: Bool) {
        self.proj_viewWillAppear1(animated: animated)
        let viewControllerName = NSStringFromClass(type(of: self))
        print("viewWillAppear: \(viewControllerName)")
    }
}

Update 20170621

public extension DispatchQueue {
    private static var _onceTracker = [String]()

    public class func once(file: String = #file, function: String = #function, line: Int = #line, block:(Void)->Void) {
        let token = file + ":" + function + ":" + String(line)
        once(token: token, block: block)
    }

    public class func once(token: String, block:(Void)->Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        if _onceTracker.contains(token) {
            return
        }

        _onceTracker.append(token)
        block()
    }
}
Billmyre answered 12/1, 2017 at 17:18 Comment(5)
Hi, can i make this work now? there is no DispatchQueue.once. thanksJigging
@Jigging sure you can create an extension like the one I added, or come up with your own version of implementation.Billmyre
You shouldn't swizzle generally speakingRubbico
I'm with @barndog. Swizzling should be avoided at all costs.There's a better than even chance Apple will not allow swizzled apps in the App Store starting with iOS 14.Bodgie
I totally agree with you guys, but I was just providing a solution to address the specific technical question from OP. Generally speaking, even subclassing should not be used widely as composition is always above inheritance. But IMO stackoverflow is not the place that you just throw SOLID principles and protocol oriented programming onto others face and let them learn how to code properly; a question was asked, and a solution has been given, and that's it.Billmyre
E
0

You can if the function is in another module. But you do lose access to the original function so the usefulness is rather limited unless you intend to completely replace its functionality with your own.

extension String {
    func lowercased() -> String {
        print("I won't lower my case!")
        return self
    }
}

"I am a string".lowercased() 
===> 
"I won't lower my case!"
"I am a string"
Estrin answered 3/5 at 15:30 Comment(3)
This is a bad idea. What happens if more than one extension tries to replace the method? It's undefined behavior. Never rely on this.Limbate
The extension override is local to the module in which it is declared so it should not be possible extend it twice. That said, this is just in response to the OP that it is possible to do so. I would not recommend using this unless you are prepared to completely implement a replacement to what you override. Can't really think of any good use cases for this actually :)Estrin
But you don't know that some other code (or 3rd party library) hasn't also attempted to override the same function in its own extension. Please read the quote from the documentation in the accepted answer.Limbate

© 2022 - 2024 — McMap. All rights reserved.