Dynamic method replacement

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

This is the exact same use case that was presented against making class and methods "final" by default (not open). And at that time, it was simply rejected as not meaningful.

1 Like

There were many tradeoffs involved in that discussion, and I think it's a misrepresentation to say that those concerns were "rejected as not meaningful". Swift tries to make API promises as explicit as possible, and that was the guiding principle behind that discussion. Furthermore, classes are not "final" by default but not publicly subclassable by default; a library can still provide its own subclasses, and choose to make a class open in a future version, whereas final is a promise to never have subclasses.

Being able to opt in to dynamic replacement with a proposal like this is more or exactly in line with the philosophy behind the discussion around the default behavior of public classes. In the same way that a library can choose to make its classes open, with this proposal, it can choose to make its definitions replaceable by clients.

1 Like

Method swizzling does not look like a good candidate for that. When you swizzle a method, you do it for all instances. Actually, KVO works by creating a subclass, and use isa swizzling, so only instances that are observed have to pay the cost of KVO.

I imagine, it is possible to do something similar in the Swift runtime with something like vtable swizzling (which would even remove the need for the method to be dynamic)

2 Likes

That would mean that the proposition should instead introduce a new @replaceable attribute. A developer may need to use the dynamic keyword without choosing to make the method replaceable.

That is what dynamic already means in Swift, though, that the method implementation is replaceable at runtime.

To Jordan's point, the @dynamicReplacement(for:) replacement mechanism Arnold proposes here would not be sufficient to implement something like KVO by itself, but the same runtime and compile-time mechanisms dynamic introduce could most likely apply to a dynamic subclassing mechanism as well. We'd still need dynamic as a way to tell the compiler that the method implementation may change at runtime from what the compiler might otherwise assume it is.

3 Likes

I gave an example further up in the conversation:


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.

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.

My approach treats this string as a declname.

The following should type check successfully if Something.init(x:) exists and has the same type signature as init(y: Int) (ignoring parameter labels) or otherwise fail to type check with an error.

extension Something {
   @dynamicReplacement(for: "init(x:)")
   init(y: Int) {
     self.init(x: y + 1)
   }
}

I'm not sure how I feel about this proposal, but either way I do think it would be good to find some solid use cases to support it. I don't think somehow anticipating the need for security patches is a convincing example, but perhaps a somewhat related use case is supporting live reloading/hot reloading of code. This is something that people find useful in other languages, particular in some domains (e.g. developing web services or video games), and I believe something like this proposal would be one way to implement that.

1 Like

May be I’m wrong but now I see this proposal as «We should have the ability to change behaviour of entity via alternative controlled way without Delegate and Inheritance». it is promising.

Now we (as framework developers) can:

  • add Delegate pattern for client code
  • add property as closure
  • recommend to implement subclass with documented primitive methods and subclassing rules
  • write code via Generics

In some case it is not so straightforward approaches for client code. I think dynamic method replacement can change and improve our approaches a lot (as Protocol Oriented Programming already done).