Swift функция swizzling / время выполнения

перед Swift, в Objective-C я бы swizzle или методы крючка в классе, используя <objc/runtime.h>.

Если у кого-нибудь есть информация по теме изменения среды выполнения Swift и функций подключения, таких как CydiaSubstrate и другие библиотеки, которые помогли в этой области, пожалуйста, сообщите мне.

7 ответов


у меня получилось с методом swizzling в Swift. В этом примере показано, как подключить метод описания к NSDictionary

моя реализация:

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

Swizzling код:

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
    }

редактировать: Этот код будет работать для любого пользовательского класса Swift, наследуемого от NSObject (но не будет работать для классов, которые не.) Больше примеров - https://github.com/mbazaliy/MBSwizzler


вы, вероятно, сможете swizzle swift-сгенерированные классы, которые наследуются от классов Objective-C без проблем, поскольку они, как представляется, используют динамический метод отправки все время. Возможно, вы сможете swizzle методы swift-определенных классов, которые существуют в среде выполнения Objective-C в силу того, что они передаются через мост, но методы Objective-C, скорее всего, будут просто прокси-серверами через мост в среду выполнения swift-side, поэтому неясно, что было бы особенно полезно выпей их.

" чистые " вызовы swift-метода не отправляются динамически через что-либо вроде objc_msgSend и кажется (из кратких экспериментов), что безопасность типа swift реализуется во время компиляции, и что большая часть фактической информации о типе отсутствует (т. е. ушла) во время выполнения для неклассовых типов (оба из которых, вероятно, способствуют предполагаемым преимуществам скорости swift.)

по этим причинам я ожидаю, что многозначительно swizzling swift-только методы будут значительно сложнее, чем методы Objective-C, и, вероятно, будут выглядеть намного больше как mach_override чем метод Objective-C swizzling.


я отвечаю на этот вопрос более чем через год, потому что ни один из других ответов не предоставляет окончательного набора требований для метода swizzling для каждого вида класса.

то, что описано другим, хотя оно будет работать безупречно для расширений классов foundation / uikit (например, NSDictionary), будет просто никогда работа для ваших собственных классов Swift.

Как рассказали здесь, есть дополнительное требование для метода swizzling, кроме расширения NSObject в вашем пользовательском классе.

swift метод, который вы хотите swizzle должен быть помечен dynamic.

Если вы не пометите его, среда выполнения будет просто продолжать вызывать исходный метод вместо swizzled, даже если указатели метода, как представляется, были заменены правильно.

обновление:

я расширил этот ответ в своем блоге.


у меня был проект Xcode 7 iOS, написанный в Swift 2, используя Cocoapods. В конкретном Cocoapod, с источником Objective-C, я хотел переопределить короткий метод, не разветвляя стручок. Написание быстрого расширения не сработает в моем случае.

для использования метода swizzling я создал новый класс Objective-C в своем основном пакете с методом, который я хотел заменить/ввести в cocoapod. (Также добавлен заголовок моста)

использование mbazaliy ' s решение о stackflow, Я поместил свой код, подобный этому, в didFinishLaunchingWithOptions в моем 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)

Это прекрасно работало. Разница между кодом @mbazaliy заключается в том, что мне не нужно было создавать экземпляр SomeClassInAPod класс первый, что в моем случае было бы невозможно.

Примечание: я помещаю код в Appdelegate, потому что каждый раз, когда код запускается, он обменивает метод на оригинал - он должен выполняться только один раз.

мне тоже нужно скопируйте некоторые активы, на которые ссылались в пакете модуля, в основной пакет.


Я бы не сделал этого таким образом, я думаю, что замыкания предоставляют ответы (поскольку они дают вам возможность перехватывать, оценивать и пересылать вызов функции, кроме того, будет легко расширить, когда и если у нас есть отражение.

http://www.swift-studies.com/blog/2014/7/13/method-swizzling-in-swift


Я хотел бы передать великому ответом mbazaliy.

другой способ сделать swizzling в Swift-это предоставить реализацию с использованием блока Objective-C.

например, чтобы заменить descriptionметод на класс NSString мы можем написать:

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)

это работает с Swift 1.1.


потратив на это некоторое время... Проснись сегодня утром.... бета-версии 6 и Проблема исправлена в beta6! Из примечаний к выпуску "Dynamic dispatch теперь может вызывать переопределения методов и свойств, введенных в расширениях классов, фиксируя регрессию, введенную в Xcode 6 beta 5. (17985819)!"