Dynamic method replacement

What problem does this solve?

Type safely replacing/extending the implementation of a method across modules without the need to recompile the module containing the original method.

Oh, sorry, I missed that.

What Arnold is suggesting sounds like it might be a more limited form of an idea I had a few years back:

Among other possible use cases, a feature like this could be adopted by framework developers who wanted to allow limited patching of their behavior by clients for bug fixing or compatibility purposes, which is something app developers have historically done a lot with Objective-C frameworks.

2 Likes

Thanks Joe! That answers the question. But wouldn’t this pitch require that the framework developer anticipate their own bugs and mark their method/properties dynamic? And if you can anticipate the bugs, well... why not just fix them instead?

3 Likes

I’ll echo @dabrahams from the thread Joe linked: Can you provide a code example showing how this would be used to solve a problem in a real framework? You say something similar is possible in Objective-C. How is that used in Objective-C frameworks, e.g. in Cocoa?

Thanks for adding a pointer to this from my recent pitch Allow dynamic keyword on non-objc properties. Adding the inverse link :slight_smile:.

Isn't that what resilience is for? I realize that this proposal is addressing a different problem, but I guess I don't really understand it. As described, it sounds exactly like what we'll do with resilience.

2 Likes

Hello

It is important that method swizzling (also as isa swizzling) in ObjC is not part of language syntax - it is part of ObjC runtime (in C language). It is even was not documented as «method swizzling» feature by Apple in its own documentation. This ability is only side effect of ObjC message-forwarding idea implementation. It is always was some sort of «hacks» in every-day code and even sometimes strongly banned by code conventions. Yes it is can be helpful in some sort of self-written frameworks with rich introspection and dynamism (like Core Data) but in this case ObjC runtime is not the most complex part of your code and it is no so often really needed. Also it is sometimes used in client code to «hack» some system framework behaviours or «bugs». In hands of unexperienced developers (whom most among all) it usually leads to undefined behaviours and hard to fix bugs in own code. This ability is some sort of old pos as function class_poseAs ([MyClass class], [SystemClass class]) which gave as ability to replace class implementation in runtime - it was blocked by Apple many years ago, there was some regrets about it but as we see now it is was not wrong decision.

Should we have method swizzling in pure Swift? I’m not sure.

First of all i think that we need alternative for ObjC KVC/KVO in pure Swift. And Swift KeyPaths, Dynamic member lookup and another dynamic features is first big step to that. May be method swizzling could become more safety to implement and use after that, may be not. As I see it is too early to say clearly.

10 Likes

I think ultimately this comes back to a discussion that we’ve circled around a lot in the dynamic discussion in Swift:

Is a library developer better placed to enforce rules, or should an end user have the capability to override this behaviour?

This is as much an ideological discussion as it is a technical one. And people will sit on both sides of the argument. Much of Objective-C’s claim to fame was the dynamism that allowed a lot of flexibility, but was often called the Wild West of development. Conservative people were not very supportive of this.

I think ultimately the question comes down to putting the shoe on the other foot: if you’re a framework author, what is the impact to you if someone injects and overrides things you’ve defined without understanding them? And if you’re an end user application, what is the impact of libraries that you cannot override when there are behaviours you need to adjust due to bugs or other issues?

From my perspective, the second case is more frustrating. If you’re a library developer, it doesn’t directly affect you. You’re not the one taking the risk things will crash. But if you’re an app developer, you’re the one unable to fix a problem. You’re also the one who’s unable to take a risk fixing this because someone else decided “you won’t need this functionality”.

I feel that the views on this for many years have been skewed toward library developer perspectives, and I say this as a library developer myself. I personally support this functionality being added.

6 Likes

I understand your point, but it is true if we can fix problem via method swizzling. There are a lot of private framework code which can not be fixed without access to source code in any case. Method swizzling can solve only some of obvious problems (crash or exception). Another point is that method swizzling can add ability to malicious your framework code. Or it can break license agreement of framework usage. I have no really answer how we should make it right.

In any case now we have this ability via ObjC runtime. It is good point that we should think about pure Swift alternatives to such cases because once upon a time Swift throws out ObjC runtime or new frameworks will be pure Swift-only.

3 Likes

I think you are right. It is look like we gradually moving to application resilience problem. Until that time we can fix third-party framework «crash» via method swizzling but Swift is becoming more and more popular language. In Swift 5 we should get ABI stability and pure Swift framework will be delivered to clients in binary form more and more often (now most of that is sources). Very, very interesting problem.

1 Like

A solution which respects the current language and requirement constraints would look something like this:

class Thing {
    public func theAnswer() -> Int {
        if let replaceableAnswer = Thing.replaceableAnswer {
            return replaceableAnswer(self, self.theAnswerOriginal)
        }
        else
        {
            return theAnswerOriginal()
        }
    }
    
    private func theAnswerOriginal() -> Int {
        return 20
    }
    
    static var replaceableAnswer: ((_ thing: Thing, _ original: () -> Int) -> Int)?
}

let thing = Thing()

print(thing.theAnswer())

Thing.replaceableAnswer = {
    (thing, original) in
    return original() + 22
}

print(thing.theAnswer())

I think this shows just how Allow dynamic keyword on non-objc properties and this pitch is different.

It appears that dynamic has alight been slightly repurposed in Swift by the creation of @dynamicMemberLookup. Maybe replaceable would be a more accurate keyword for this.

Method swizzling isn't just useful for patching bugs - it's also useful to install hooks. For example, I recently worked on an App which included its own styling/theming system. To make this work, I had to install a hook in UIView so that all instances (including instances of system-provided subclasses like UITableView) automatically pulled their styling attributes from the hierarchy and applied them.

Otherwise, I would have had to subclass every kind of view which was used in the application.

10 Likes

This is different to resilience in that resilience requires recompilation of the module containing the implementation to change an implementation.

dynamic as proposed here allows for methods to be replaced at runtime. Only the replacement method needs to be recompiled.

1 Like

You all know I'm very much on the side of "library author should be in control" (cf. past support for the open/public distinction). For that reason, I'm strongly, strongly against any default replaceability. Given that, what is this proposal for? "When would a library author use this?" is a very good question. And I can think of a handful of answers:

  • A family of libraries that work well together: library Base exposes a dynamic entry point, and library Advanced adds some additional behavior. (This could be from the same vendor, or just an open extension point of some kind. It's probably not the long-term solution we want for registration, but you could implement that.)

  • Dynamically synthesized wrapper code, like KVO. The proposal Arnold's written here doesn't have syntax for that, but it probably has all the run-time underpinnings unless the runtime assumes that the implementation will never change after the first use. I do think it's probably an important use case, though, and even if we don't provide it now we should keep it in mind.

  • Testing, potentially. I'm not sure how I feel about this one, but people have been asking for simpler mock objects than "just do everything in terms of a protocol", and this might be one way. However, we probably wouldn't want to use the normal dynamic modifier for this; either we'd want something special or we'd want to have -enable-testing make everything dynamic. I'm not sure of the performance implications of that.

I'm also wary of anything that requires static initializers to do correctly, since Swift has been trying very hard to get away from them. But it sounds like we have ideas there.

I'm not sure why this is limited to members of types. Any reason why top-level functions and computed properties can't be dynamic?

One last thing: I don't like integer priorities; they run into the "what if two people did this" problem. IM(H?)O we should just say "if two libraries do this, both replacements are applied one on top of another; if library A links to library B, library B's replacement will be the 'inner' one". (Or the other way around. That can be a sub-discussion.)

10 Likes

This is just an unintended omission in the text. dynamic should work on top-level functions/computed properties.

2 Likes

This is a good question. Incomplete knowledge.

For example, developers could anticipate the need for change in security critical code - say a function that enforces a security policy on a server. They could mark the function as dynamic ahead of time.
Later, when they realize their caution was justified they could ship a module only containing the code for the fixed function. The module containing the fixed function can be loaded, while the server application using it continues to run, replacing the original faulty version.

I understand that, but as I've said, the motivation cited in the pitch (“a developer foresees the necessity of future extension/replacement of a method”) seems to be addressed by resilience. What kind of foresight would lead one to make a method dynamically replaceable? “You can do this in Objective-C” isn't a good enough reason to add it to Swift. We need examples that show how/why the capability is needed.

4 Likes

Hi, @Arnold thanks for making this proposal.

Full disclosure I have worked some time ago on a proposal that aims to add features that are similar to this one, but I think in a more secure and controlled way.

FWIW I'm firmly against this approach because most of the time method swizzling is abused mainly by third-party and it makes you lost control of how your code or the code you are working with operate. There are issues from prioritizing witch implementation win to how to protect my code against this approach. I have lost so much time debugging/patching issues introduced by another library swizzled code that thinking about adding this kind of feature in Swift scares me a bit.

On the bright side, I think we can have the same feature by using an Aspect Oriented Programming approach the wrap behavior is the one that allows you to manipulate the output of a function.

@dabrahams can you please explain what resilience is?

@Rod_Brown I have developed libraries for a long time, and I'm against doing magic tricks behind the developer's back I instead prefer always to give them all the tools they might need to work with and if there is a bug I prefer to fix it myself and re-ship the lib.

@Karl we can install hooks with an Aspect Oriented Programming approach

4 Likes

Hi Arnold,

I'm +1 on adding this to Swift, it fills in a hole that is still sadly @objc-only but that should be available to all Swift types everywhere. I am very glad that dynamic isn't the default, and that API authors are in control.

Question on your design though: why is the argument to "for:" a string? It seems that it should be a declname (not in string quotes) so the compiler can type check it, overload resolve it, etc. You should be able to a likewise specify which member of an overload set is important (e.g. through type matching with your implementation) and should fully support Swift's keyword argument setup). It's not clear if your approach already does this - the double quotes make it seem like it isn't intended, but maybe I'm misunderstanding it.

-Chris

8 Likes