Swift function swizzling / runtime

后端 未结 8 1184
自闭症患者
自闭症患者 2020-12-05 08:04

Before Swift, in Objective-C I would swizzle or hook methods in a class using .

If anyone has any info on the topic of modifying S

相关标签:
8条回答
  • 2020-12-05 08:15

    I've succeed with method swizzling in Swift. This example shows how to hook description method on NSDictionary

    My implementation:

    extension NSDictionary {
         func myDescription() -> String!{
            println("Description hooked")
            return "Hooooked " + myDescription();
        }
    }
    

    Swizzling code:

    func swizzleEmAll() {
            var dict:NSDictionary = ["SuperSecret": kSecValueRef]
            var method: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("description"))
    
            println(dict.description) // Check original description
    
            var swizzledMethod: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("myDescription"))
            method_exchangeImplementations(method, swizzledMethod)
    
            println(dict.description) //Check that swizzling works
        }
    

    Edited: This code will work for any custom Swift class that inherits from NSObject (but will not work for classes that don't.) More examples - https://github.com/mbazaliy/MBSwizzler

    0 讨论(0)
  • 2020-12-05 08:18

    After spending some time on it... Wake up this morning.... beta 6 is out and Problem Fixed in beta6! From release notes "Dynamic dispatch can now call overrides of methods and properties introduced in class extensions, fixing a regression introduced in Xcode 6 beta 5. (17985819)!"

    0 讨论(0)
  • You would likely be able to swizzle swift-generated classes that inherit from Objective-C classes with no problem, since they appear to use dynamic method dispatch all the time. You may be able to swizzle methods of swift-defined classes that exist in the Objective-C runtime by virtue of being passed across the bridge, but the Objective-C side methods are likely to just be proxies back across the bridge to the swift-side runtime, so it's not clear that it'd be particularly helpful to swizzle them.

    "Pure" swift method calls do not appear to be dispatched dynamically via anything like objc_msgSend and it appears (from brief experimentation) that the type safety of swift is implemented at compile time, and that much of the actual type information is absent (i.e. gone) at runtime for non-class types (both of which likely contribute to the purported speed advantages of swift.)

    For these reasons, I expect that meaningfully swizzling swift-only methods will be significantly harder than swizzling Objective-C methods, and will probably look a lot more like mach_override than Objective-C method swizzling.

    0 讨论(0)
  • 2020-12-05 08:19

    A safe, easy, powerful and efficient hook framework for iOS (Support Swift and Objective-C). https://github.com/623637646/SwiftHook

    For example, this is your class

    class MyObject {
        @objc dynamic func noArgsNoReturnFunc() {
        }
        @objc dynamic func sumFunc(a: Int, b: Int) -> Int {
            return a + b
        }
        @objc dynamic class func classMethodNoArgsNoReturnFunc() {
        }
    }
    

    #f03c15 The key words of methods @objc and dynamic are necessary

    #f03c15 The class doesn't have to inherit from NSObject. If the class is written by Objective-C, Just hook it without any more effort

    1. Perform the hook closure before executing specified instance's method.
    let object = MyObject()
    let token = try? hookBefore(object: object, selector: #selector(MyObject.noArgsNoReturnFunc)) {
        // run your code
        print("hooked!")
    }
    object.noArgsNoReturnFunc()
    token?.cancelHook() // cancel the hook
    
    1. Perform the hook closure after executing specified instance's method. And get the parameters.
    let object = MyObject()
    let token = try? hookAfter(object: object, selector: #selector(MyObject.sumFunc(a:b:)), closure: { a, b in
        // get the arguments of the function
        print("arg1 is \(a)") // arg1 is 3
        print("arg2 is \(b)") // arg2 is 4
        } as @convention(block) (Int, Int) -> Void)
    _ = object.sumFunc(a: 3, b: 4)
    token?.cancelHook() // cancel the hook
    

    #f03c15 The key word @convention(block) is necessary

    #f03c15 For hook at before and after. The closure's args have to be empty or the same as method. The return type has to be void

    1. Totally override the mehtod for specified instance. You can call original with the same parameters or different parameters. Don't even call the original method if you want.
    let object = MyObject()
    let token = try? hookInstead(object: object, selector: #selector(MyObject.sumFunc(a:b:)), closure: { original, a, b in
        // get the arguments of the function
        print("arg1 is \(a)") // arg1 is 3
        print("arg2 is \(b)") // arg2 is 4
    
        // run original function
        let result = original(a, b) // Or change the parameters: let result = original(-1, -2)
        print("original result is \(result)") // result = 7
        return 9
        } as @convention(block) ((Int, Int) -> Int, Int, Int) -> Int)
    let result = object.sumFunc(a: 3, b: 4) // result
    print("hooked result is \(result)") // result = 9
    token?.cancelHook() // cancel the hook
    

    #f03c15 For hook with instead. The closure's first argument has to be a closure which has the same types with the method. The rest args and return type have to be the same as the method.

    1. Perform the hook closure before executing the method of all instances of the class.
    let token = try? hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.noArgsNoReturnFunc)) {
        // run your code
        print("hooked!")
    }
    MyObject().noArgsNoReturnFunc()
    token?.cancelHook() // cancel the hook
    
    1. Perform the hook closure before executing the class method.
    let token = try? hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.classMethodNoArgsNoReturnFunc)) {
        // run your code
        print("hooked!")
    }
    MyObject.classMethodNoArgsNoReturnFunc()
    token?.cancelHook() // cancel the hook
    
    0 讨论(0)
  • 2020-12-05 08:20

    I would like to extend the great answer provided by mbazaliy.

    Another way of doing swizzling in Swift is by providing an implementation using an Objective-C block.

    e.g. to replace descriptionmethod on class NSString we can write:

    let originalMethod = class_getInstanceMethod(NSString.self, "description")
    
    let impBlock : @objc_block () -> NSString =
            { () in return "Bit of a hack job!" }
    
    let newMethodImp = imp_implementationWithBlock(unsafeBitCast(impBlock, AnyObject.self))
    
    method_setImplementation(originalMethod, newMethodImp)
    

    This works as of Swift 1.1.

    0 讨论(0)
  • 2020-12-05 08:21

    I had a Xcode 7 iOS project written in Swift 2, using Cocoapods. In a specific Cocoapod, with Objective-C source, I wanted to override a short method, without forking the pod. Writing a Swift extension wouldn't work in my case.

    For using method swizzling, I created a new Objective-C class in my main bundle with the method I wanted to replace/inject into the cocoapod. (Also added the bridging header)

    Using mbazaliy 's solution on stackflow, I put my code similar to this into the didFinishLaunchingWithOptions in my Appdelegate:

        let mySelector: Selector = "nameOfMethodToReplace"
        let method: Method = class_getInstanceMethod(SomeClassInAPod.self, mySelector)
        let swizzledMethod: Method = class_getInstanceMethod(SomeOtherClass.self, mySelector)
        method_exchangeImplementations(method, swizzledMethod)
    

    This worked perfectly. The difference between @mbazaliy 's code is that I didn't need to create an instance of the SomeClassInAPod class first, which in my case would have been impossible.

    Note: I put the code in the Appdelegate because every other time the code runs, it exchanges the method for the original - it should only run one time.

    I also needed to copy some assets that were referenced in the Pod's bundle to the main bundle.

    0 讨论(0)
提交回复
热议问题