Error: segmentation fault
As you see, after I exchange Foo and Poo's extension method, I call Foo's instance method foo, it crash.
I know the method 'foo.foo()' called by objc_msgSend dynamic dispatch.
This is a low-level implementation detail, but subclasses of NSObject use a different implementation of reference counting from pure Swift classes. That's why you get a crash in the Swift runtime: it's trying to retain a subclass of NSObject using the logic that makes sense for pure Swift objects.
(Strictly speaking, maybe this retain isn't necessary, but I still wouldn't bet on this working in general.)
As an aside, if you're going to use the Objective-C runtime to change method implementations, you should always mark those methods as dynamic. Otherwise, the compiler might try to inline calls to those methods, and you won't get the implementation you're expecting.
It's never correct to return a pointer out of withUnsafeMutablePointer; the pointer you get within the closure is only guaranteed to be be valid within the body of that closure. It's also not correct to swap implementations in a witness table or vtable ever, since methods can have different calling conventions for different types, and on platforms with pointer authentication.
That said, it's fine to play around with this for fun. Just don't ship it in anything.
My guess is that you're correct: the protocol witness implementation for the class is trying to do a vtable dispatch, but structs don't have vtables (because they don't have subclasses).
There's a difference between what the compiler happens to do today, and what it promises it will do in all versions. inout parameters may have the memory address of the original value, or they may have the memory address of a temporary value which will be assigned back to the original after the call. (That's how you can pass a computed property inout.) The compiler doesn't make any promises about which one will choose in any particular situation, so you should always assume the pointer will not be valid after the call.
What you're seeing here is that, in this particular code sample with this particular compiler version, the compiler chooses to pass the original address. But it can change that decision for any reason at any time, so you shouldn't write code that only works if it passes the original address.
(Having said that, if you're just experimenting to learn how Swift works, this is fine. Just don't expect it to keep working.)