Как реализовать метод Swizzling Swift 3.0?

Как я могу реализовать метод Swizzling в Swift 3.0?

Я прочитал статью об этом, но в этом фрагменте кода

struct Static {
    static var token: dispatch_once_t = 0
}

компилятор выдает мне ошибку

dispatch_once_t недоступен в Swift: используйте вместо этого лениво инициализированные глобалы

3 ответа

Решение

Прежде всего dispatch_once_t недоступно в Swift 3.0. Вы можете выбрать один из двух вариантов:

  1. Глобальная переменная

  2. Статическое свойство struct, enum или же class

Для более подробной информации, смотрите Whither dispatch_once в Swift 3

Для разных целей вы должны использовать разные реализации Swizzling

  • Класс Swizzling CocoaTouch, например UIViewController;
  • Swizzling пользовательский класс Swift;

Класс Swizzling CocoaTouch

пример свистящий viewWillAppear(_:) из UIViewController используя глобальную переменную

private let swizzling: (UIViewController.Type) -> () = { viewController in

    let originalSelector = #selector(viewController.viewWillAppear(_:))
    let swizzledSelector = #selector(viewController.proj_viewWillAppear(animated:))

    let originalMethod = class_getInstanceMethod(viewController, originalSelector)
    let swizzledMethod = class_getInstanceMethod(viewController, swizzledSelector)

    method_exchangeImplementations(originalMethod, swizzledMethod) }

extension UIViewController {

    open override class func initialize() {
        // make sure this isn't a subclass
        guard self === UIViewController.self else { return }
        swizzling(self)
    }

    // MARK: - Method Swizzling

    func proj_viewWillAppear(animated: Bool) {
        self.proj_viewWillAppear(animated: animated)

        let viewControllerName = NSStringFromClass(type(of: self))
        print("viewWillAppear: \(viewControllerName)")
    } 
 }

Swizzling пользовательский класс Swift

Чтобы использовать метод swizzling с вашими классами Swift, необходимо выполнить два требования ( для более подробной информации):

  • Класс, содержащий методы, которые нужно изучать, должен расширяться NSObject
  • Методы, которые вы хотите выпить, должны иметь dynamic атрибут

И пример Swizzling метод пользовательского базового класса Swift Person

class Person: NSObject {
    var name = "Person"
    dynamic func foo(_ bar: Bool) {
        print("Person.foo")
    }
}

class Programmer: Person {
    override func foo(_ bar: Bool) {
        super.foo(bar)
        print("Programmer.foo")
    }
}

private let swizzling: (Person.Type) -> () = { person in

    let originalSelector = #selector(person.foo(_:))
    let swizzledSelector = #selector(person.proj_foo(_:))

    let originalMethod = class_getInstanceMethod(person, originalSelector)
    let swizzledMethod = class_getInstanceMethod(person, swizzledSelector)

    method_exchangeImplementations(originalMethod, swizzledMethod)
}

extension Person {

    open override class func initialize() {
        // make sure this isn't a subclass
        guard self === Person.self else { return }
        swizzling(self)
    }

    // MARK: - Method Swizzling

    func proj_foo(_ bar: Bool) {
        self.proj_foo(bar)

        let className = NSStringFromClass(type(of: self))
        print("class: \(className)")
    }
}

@TikhonovAlexander: Отличный ответ

Я изменил Swizzler, чтобы взять оба селектора и сделал его более общим.

Свифт 3

private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in
    let originalMethod = class_getInstanceMethod(forClass, originalSelector)
    let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
    method_exchangeImplementations(originalMethod, swizzledMethod)
}

// perform swizzling in initialize()

extension UIView {

    open override class func initialize() {
        // make sure this isn't a subclass
        guard self === UIView.self else { return }

        let originalSelector = #selector(layoutSubviews)
        let swizzledSelector = #selector(swizzled_layoutSubviews)
        swizzling(self, originalSelector, swizzledSelector)
    }

    func swizzled_layoutSubviews() {
        swizzled_layoutSubviews()
        print("swizzled_layoutSubviews")
    }

}

Swift 4

private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in
    guard
        let originalMethod = class_getInstanceMethod(forClass, originalSelector),
        let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
    else { return }
    method_exchangeImplementations(originalMethod, swizzledMethod)
}

extension UIView {

    static let classInit: Void = {            
        let originalSelector = #selector(layoutSubviews)
        let swizzledSelector = #selector(swizzled_layoutSubviews)
        swizzling(UIView.self, originalSelector, swizzledSelector)
    }()

    @objc func swizzled_layoutSubviews() {
        swizzled_layoutSubviews()
        print("swizzled_layoutSubviews")
    }

}

// perform swizzling in AppDelegate.init()

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    override init() {
        super.init()
        UIView.classInit
    }

}

Сверкающий на детской площадке

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
    return 1
}
}

extension TestSwizzling {

//In Objective-C you'd perform the swizzling in load(),
//but this method is not permitted in Swift
func initialize1()
{


    let i: () -> () = {


        let originalSelector = #selector(TestSwizzling.methodOne)
        let swizzledSelector = #selector(TestSwizzling.methodTwo)
        let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
        let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)
        method_exchangeImplementations(originalMethod, swizzledMethod)
        print("swizzled")

    }
    i()
}

func methodTwo()->Int{
    // It will not be a recursive call anymore after the swizzling
    return 4
}
}

var c = TestSwizzling()
c.initialize1()
print(c.methodOne())
print(c.methodTwo())

выход: 4 1

Swift пьянящий

@objcMembers
class sA {
    dynamic
    class func sClassFooA() -> String {
        return "class fooA"
    }

    dynamic
    func sFooA() -> String {
        return "fooA"
    }
}

@objcMembers
class sB {
    dynamic
    class func sClassFooB() -> String {
        return "class fooB"
    }

    dynamic
    func sFooB() -> String {
        return "fooB"
    }
}

Swizzling.swift

import Foundation

@objcMembers
public class Swizzling: NSObject {

    public class func sExchangeClass(cls1: AnyClass, sel1: Selector, cls2: AnyClass, sel2: Selector) {

        let originalMethod = class_getClassMethod(cls1, sel1)
        let swizzledMethod = class_getClassMethod(cls2, sel2)

        method_exchangeImplementations(originalMethod!, swizzledMethod!)
    }

    public class func sExchangeInstance(cls1: AnyClass, sel1: Selector, cls2: AnyClass, sel2: Selector) {

        let originalMethod = class_getInstanceMethod(cls1, sel1)
        let swizzledMethod = class_getInstanceMethod(cls2, sel2)

        method_exchangeImplementations(originalMethod!, swizzledMethod!)
    }
}

Использование через Swift

func testSExchangeClass() {
    Swizzling.sExchangeClass(cls1: sA.self, sel1: #selector(sA.sClassFooA), cls2: sB.self, sel2: #selector(sB.sClassFooB))

    XCTAssertEqual("class fooB", sA.sClassFooA())
}

func testSExchangeInstance() {
    Swizzling.sExchangeInstance(cls1: sA.self, sel1: #selector(sA.sFooA), cls2: sB.self, sel2: #selector(sB.sFooB))

    XCTAssertEqual("fooB", sA().sFooA())
}

[Добавить Objective-C в качестве потребителя]

используя через Objective-C

- (void)testSExchangeClass {
    [Swizzling sExchangeClassWithCls1:[cA class] sel1:@selector(cClassFooA) cls2:[cB class] sel2:@selector(cClassFooB)];

    XCTAssertEqual(@"class fooB", [cA cClassFooA]);
}

- (void)testSExchangeInstance {
    [Swizzling sExchangeInstanceWithCls1:[cA class] sel1:@selector(cFooA) cls2:[cB class] sel2:@selector(cFooB)];

    XCTAssertEqual(@"fooB", [[[cA alloc] init] cFooA]);
}

[Objective-C swizzling]

Попробуйте этот фреймворк: https://github.com/623637646/SwiftHook

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

Перехватывать методы в Swift очень просто.

Swift 5.1

Swift использует функцию времени выполнения Objective-C, чтобы сделать метод сменным. Вот вам два пути.

Примечание: open override class func initialize() {} больше не разрешено.

  1. класс наследовать NSObject, и метод должен иметь dynamic атрибут

    class Father: NSObject {
       @objc dynamic func makeMoney() {
           print("make money")
       }
    }
    extension Father {
       static func swizzle() {
           let originSelector = #selector(Father.makeMoney)
           let swizzleSelector = #selector(Father.swizzle_makeMoney)
           let originMethod = class_getInstanceMethod(Father.self, originSelector)
           let swizzleMethod = class_getInstanceMethod(Father.self, swizzleSelector)
           method_exchangeImplementations(originMethod!, swizzleMethod!)
       }
       @objc func swizzle_makeMoney() {
           print("have a rest and make money")
       }
    }
    Father.swizzle()
    var tmp = Father()
    tmp.makeMoney() //  have a rest and make money
    tmp.swizzle_makeMoney() // make money
    
    1. Использовать @_dynamicReplacement(for: )

       class Father {
           dynamic func makeMoney() {
               print("make money")
           }
       }
       extension Father {
           @_dynamicReplacement(for: makeMoney())
           func swizzle_makeMoney() {
               print("have a rest and make money")
           }
       }
       Father().makeMoney() // have a rest and make money
    
Другие вопросы по тегам