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

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

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


person atomikpanda    schedule 03.06.2014    source источник


Ответы (8)


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

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

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

Свизл-код:

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

person mbazaliy    schedule 04.06.2014
comment
Чтобы было ясно, в этом примере используется метод класса, который определен в Objective-C, а не в swift. Я предполагаю, что, поскольку спрашивающий принял ответ, это то, что они хотели, но для записи этот метод не будет использовать метод в чистом быстром классе. - person ipmcc; 05.06.2014
comment
Он работает только в том случае, если класс Swift является подклассом NSObject. - person mbazaliy; 05.06.2014
comment
Кто-нибудь знает, как работает среда выполнения Swift? Я имею в виду, что все знают, как работает obj-c. указатель isa, cmd, как мы вызываем методы, динамическое связывание и другое. Есть много документов о среде выполнения obj-c, библиотеке времени выполнения с открытым исходным кодом. А как же Свифт? Любые ссылки будут полезны. - person BergP; 05.06.2014
comment
AFAIK Swift работает в среде выполнения Obj-c (поэтому вам нужно создать подкласс NSObject), но имеет и может работать в своей собственной среде выполнения. - person Macsen Liviu; 17.06.2014
comment
@mbazaliy, в моем тестировании ваш myDescription() рекурсивно вызывает сам себя и вызывает ошибку переполнения стека. - person Pang; 06.08.2014
comment
Я не уверен, что вам нужно явно создавать подклассы из NSObject, чтобы получить такое поведение, если вы используете директиву Swift @objc в своем определении класса для каждого метода, который вы хотите сделать swizzlable. - person ctpenrose; 13.08.2014
comment
Нет, я не мог использовать класс Swift, который использовал директивы @objc изнутри Swift. Но не исключено, что его можно будет swizzlable из Objective-C. - person ctpenrose; 13.08.2014
comment
Selectorimplements StringLiteralConvertibleprotocol (проверено на Swift 1.1). class_getInstanceMethod(object_getClass(dict),"description") будет работать. - person Matteo Pacini; 10.01.2015
comment
Хороший пример Swizzling со Swift приведен здесь, сразу после Associated Objects - person TheDarkKnight; 09.06.2015
comment
Я согласен @TheDarkKnight. Эта статья хороша. Он хорошо охватывает методы экземпляра. Что касается методов классов, я изучил и поделился своими выводами на странице Swizzle Swift Class Methods. - person finneycanhelp; 23.05.2016
comment
Для тех, кто смотрит на это, упомянутая статья устарела. В современных версиях swift вы не можете переопределить метод initialize. - person Nat; 05.06.2020

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

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

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

person ipmcc    schedule 03.06.2014
comment
А как насчет swizzling стиля указателя isa? stackoverflow.com/q/24050500/404201 - person Jasper Blues; 05.06.2014
comment
isa swizzling также является методом Objective-C. Я не пробовал, но, судя по тому, что я видел, мне это вряд ли поможет. По крайней мере, не из коробки. - person ipmcc; 05.06.2014
comment
Кажется, здесь все работает нормально: bit.ly/TgLN2M . . . Swift, кажется, отдает приоритет статической диспетчеризации, возвращаясь к vtable или, наконец, к диспетчеризации в стиле ObjC, где это необходимо. Если бы мы могли добавлять методы во время выполнения, у него, вероятно, была бы таблица диспетчеризации в стиле ObjC. - person Jasper Blues; 05.06.2014
comment
Я очень сомневаюсь, что статические и vtable-dispatch-методы когда-либо будут swizzle-able с использованием существующих методов swizzling Objective-C, что я пытался сделать, хотя кажется, что это не то, о чем спрашивал OP , учитывая, что они приняли другой ответ. - person ipmcc; 05.06.2014
comment
Это не заняло много времени: github.com/rodionovd/SWRoute/wiki /Function-hooking-in-Swift - person ipmcc; 20.06.2014
comment
Вау, не знал, что такой подход возможен. - person Jasper Blues; 20.06.2014

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

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

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

Метод Swizzle, который вы хотите использовать, должен быть помечен dynamic.

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

Обновлять:

Я расширил этот ответ в сообщении блога< /а>.

person Umberto Raimondi    schedule 13.10.2015

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

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

Используя решение mbazaliy для 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, потому что каждый раз, когда код запускается, он заменяет метод на оригинал — он должен запускаться только один раз.

Мне также нужно было скопировать некоторые активы, на которые есть ссылки в пакете Pod, в основной пакет.

person Nate Flink    schedule 24.02.2016

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

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

person rougeExciter    schedule 14.07.2014

Я хотел бы расширить отличный ответ, предоставленный mbazaliy.

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

например чтобы заменить descriptionmethod на класс 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.

person Matteo Pacini    schedule 10.01.2015
comment
Это не шизлинг. Swizzling предполагает обмен реализациями методов, а не их заменой. - person Vatsal Manot; 10.01.2015
comment
@VatsalManot, можешь уточнить? - person Ali; 01.06.2015
comment
Swizzling — это замена двух реализаций, а не замена одной. - person Vatsal Manot; 01.06.2015

Безопасная, простая, мощная и эффективная структура ловушек для iOS (поддержка Swift и Objective-C). https://github.com/623637646/SwiftHook

Например, это ваш класс

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

#f03c15Необходимы ключевые слова методов @objc и dynamic

#f03c15Класс не должен наследоваться от NSObject. Если класс написан на Objective-C, просто подключите его без дополнительных усилий

  1. Выполните закрытие хука перед выполнением метода указанного экземпляра.
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. Выполните закрытие хука после выполнения указанного метода экземпляра. И получить параметры.
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Ключевое слово @convention(block) необходимо

#f03c15Для подключения к точкам before и after. Аргументы закрытия должны быть пустыми или совпадать с методом. Тип возвращаемого значения должен быть void

  1. Полностью переопределить метод для указанного экземпляра. Вы можете вызвать оригинал с теми же параметрами или другими параметрами. Даже не вызывайте исходный метод, если хотите.
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Для хука с instead. Первый аргумент замыкания должен быть замыканием того же типа, что и метод. Остальные аргументы и тип возвращаемого значения должны совпадать с методом.

  1. Выполните закрытие хука перед выполнением метода всех экземпляров класса.
let token = try? hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.noArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
MyObject().noArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
  1. Выполните закрытие хука перед выполнением метода класса.
let token = try? hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.classMethodNoArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
MyObject.classMethodNoArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
person Yanni    schedule 02.06.2020

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

person corinnekrych    schedule 19.08.2014
comment
Лучше написать это в виде комментария - person عثمان غني; 19.08.2014