Альтернатива для выполнения выбора в Swift?
Семейство методов performSelector
недоступно в Swift. Итак, как вы можете вызвать метод в объекте @objc
, где метод, который будет вызываться, выбран во время выполнения и не известен во время компиляции? NSInvocation
, по-видимому, также недоступен в Swift.
Я знаю, что в Swift вы можете отправить любой метод (для которого существует объявление метода @objc
), тип AnyObject
, аналогичный id
в Objective-C. Тем не менее, это по-прежнему требует жесткого кодирования имени метода во время компиляции. Есть ли способ динамически выбирать его во время выполнения?
Ответы
Ответ 1
Использование замыканий
class A {
var selectorClosure: (() -> Void)?
func invoke() {
self.selectorClosure?()
}
}
var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()
Обратите внимание, что это ничего нового, даже в Obj-C новые API предпочитают использовать блоки над performSelector
(сравните UIAlertView
, который использует respondsToSelector:
и performSelector:
для вызова методов делегирования, с новым UIAlertController
).
Использование performSelector:
всегда небезопасно и плохо работает с ARC (следовательно, предупреждения ARC для performSelector:
).
Ответ 2
Как и в Xcode 7, в Swift доступно полное семейство методов performSelector, включая performSelectorOnMainThread()
и performSelectorInBackground()
. Наслаждайтесь!
Ответ 3
Подход A
Используйте NSThread.detachNewThreadSelector
, хорошо, что этот подход заключается в том, что мы можем присоединить объект к сообщению. Пример кода в ViewController:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let delay = 2.0 * Double(NSEC_PER_SEC)
var time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
NSThread.detachNewThreadSelector(Selector("greetings:"), toTarget:self, withObject: "sunshine")
})
}
func greetings(object: AnyObject?) {
println("greetings world")
println("attached object: \(object)")
}
Журнал консоли:
мир приветствий
прикрепленный объект: солнечный свет
Подход B
Эта альтернатива была обнаружена ранее, я также тестировал устройство и симулятор. Идея заключается в использовании следующего метода UIControl:
func sendAction(_ action: Selector, to target: AnyObject!, forEvent event: UIEvent!)
Пример кода в ViewController:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var control: UIControl = UIControl()
control.sendAction(Selector("greetings"), to: self, forEvent: nil) // Use dispatch_after to invoke this line as block if delay is intended
}
func greetings() {
println("greetings world")
}
Журнал консоли:
мир приветствий
Подход C
NSTimer
class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval,
target target: AnyObject!,
selector aSelector: Selector,
userInfo userInfo: AnyObject!,
repeats repeats: Bool) -> NSTimer!
Ответ 4
Swift 3
perform(#selector(someSelector), with: nil, afterDelay: 1.0, inModes: [.commonModes])
Ответ 5
В соответствии с ответом @JTerry "Вам не нужны селектора в Swift", вы можете назначить фактические методы для переменных. Мое решение было следующим (мне нужен один параметр в методе):
class SettingsMenuItem: NSObject {
...
var tapFunction: ((sender: AnyObject?) -> ())?
}
И затем в контроллере, который я объявил, назначил и запускал функцию таким образом:
class SettingsViewController: UITableViewController {
func editProfile(sender: AnyObject?) {
...
}
...
menuItem.tapFunction = editProfile
...
if let tapFunction = menuItem.tapFunction {
tapFunction(sender: self)
}
}
Ответ 6
Вы можете использовать это в Swift
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false)
func someSelector() {
// Something after a delay
}
этим вы можете сделать то, что выполняется функцией performSelector в Objective-C
Ответ 7
Я тоже боролся с этим. Наконец я понял, что мне не нужно использовать цели или селектора. Для меня решение было назначить func переменной и вызвать эту переменную. Это даже работает, если вы называете это из других классов. Вот краткий пример:
func Apple() ->Int
{
let b = 45;
return b;
}
func Orange()->Int
{
let i = 5;
return i;
}
func Peach()
{
var a = Apple; // assign the var a the Apple function
var b = Orange; // assisgn the var b to the Orange function
let c = a(); // assign the return value of calling the 'a' or Apple function to c
let d = b(); // assign the return value of calling the 'b' or Orange function d
Pear(a, b)
}
func Pear(x:()->Int, y:()->Int)->Int
{
let w = (x()+y()); // call the x function, then the y function and add the return values of each function.
return w; // return the sum
}
Peach();
Ответ 8
Swift 3.1
Для стандартных закрытий проектов Swift - это элегантное решение, уже рассмотренное в Sulthan answer.
Вызов методов динамически с использованием имен селекторных строк имеет смысл, если каждый из них зависит от устаревшего кода/библиотек Objective-C.
Подклассы NSObject
могут получать сообщения, попытка отправить их в чистый класс Swift приведет к сбою.
#selector(mySelectorName)
может разрешать типизированные имена селекторов только в исходном файле класса.
Пожертвовав проверку типа, селектор можно получить с помощью NSSelectorFromString(...)
(это не безопаснее в любом случае по сравнению с Selector("selectorName:arg:")
просто не возникает предупреждение).
Вызов NSObject
метода экземпляра подкласса
let instance : NSObject = fooReturningObjectInstance() as! NSObject
instance.perform(#selector(NSSelectorFromString("selector"))
instance.perform(#selector(NSSelectorFromString("selectorArg:"), with: arg)
instance.perform(#selector(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)
также с основными и фоновыми вариантами потока:
instance.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
Есть некоторые ограничения:
- Он может принимать только 0-2 аргументов
- аргументы типа значения, такие как целые числа и селекторы, не работают.
- не может обрабатывать возвращаемые типы значений
- возвращает объекты как
Unmanaged<AnyObject>
Таким образом, этот подход с низким уровнем усилий удобен, когда не нужны возвращаемые результаты и аргументы типа значения.
Извлечение NSObject
метода времени выполнения IMP
позволяет сделать типизированный вызов с правильными аргументами и возвращаемым типом.
@convention(c)(types)->type
позволяет отличать результат IMP
к совместимой функции закрытия Swift.
В @convention(c)
не все типы разрешены
- Для классов используйте Any или AnyClass
- Для объектов используется любой или точный тип класса, если его символ доступен
- Для типов значений используйте соответствующий тип
- Для void * используйте OpaquePointer
Это по определению небезопасно, а если сделано некорректно, это приведет к сбоям и побочным эффектам.
Каждый метод Objective-C на уровне C содержит два скрытых аргумента, соответствующих objc_msgSend(id self, SEL op, ...)
, которые должны быть включены в тип функции как @convention(c)(Any?,Selector, ... )
let instance : NSObject = fooReturningObjectInstance() as! NSObject
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = instance.method(for: selector)
unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector,Any?)->Void).self)(instance,selector,arg)
Это статические эквиваленты perform(...)
NSObject.perform(NSSelectorFromString("selector"))
NSObject.perform(NSSelectorFromString("selectorArg:"), with: arg)
NSObject.perform(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)
NSObject.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
NSObject.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
Ограничения:
- Все проблемы типа, упомянутые ранее
- Класс приемника должен иметь определенный символ
Извлечение статического метода времени выполнения IMP
и типов обработки, @convention(c)
применяется
let receiverClass = NSClassFromString("MyClass")
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = method_getImplementation(class_getClassMethod(receiverClass, selector))
let result : NSObject = unsafeBitCast(methodIMP,to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(receiverClass,selector,arg) as! NSObject
Нет практических оснований для этого, но objc_msgSend
можно использовать динамически.
let instance : NSObject = fooReturningObjectInstance() as! NSObject
let handle : UnsafeMutableRawPointer! = dlopen("/usr/lib/libobjc.A.dylib", RTLD_NOW)
let selector : Selector = NSSelectorFromString("selectorArg:")
unsafeBitCast(dlsym(handle, "objc_msgSend"), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(instance,selector,arg)
dlclose(handle)
То же самое для NSInvocation
(это только забавное упражнение, не)
class Test : NSObject
{
var name : String? {
didSet {
NSLog("didSetCalled")
}
}
func invocationTest() {
let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
var localName = name
withUnsafePointer(to: &localName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
}
}
Ответ 9
Да, мы можем использовать swizzling для раскрытия желаемых методов!
Просто добавьте этот extension
и префикс всех вызовов с символом 🚀
.
import Foundation
private var dispatchOnceToken: dispatch_once_t = 0
private var selectors: [Selector] = [
"performSelector:",
"performSelector:withObject:",
"performSelector:withObject:withObject:",
"performSelector:withObject:afterDelay:inModes:",
"performSelector:withObject:afterDelay:",
]
private func swizzle() {
dispatch_once(&dispatchOnceToken) {
for selector: Selector in selectors {
let 🚀selector = Selector("🚀\(selector)")
let method = class_getInstanceMethod(NSObject.self, selector)
class_replaceMethod(
NSObject.self,
🚀selector,
method_getImplementation(method),
method_getTypeEncoding(method)
)
}
}
}
extension NSObject {
func 🚀performSelector(selector: Selector) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector, withObject: object)
}
func 🚀performSelector(selector: Selector, withObject object1: AnyObject?, withObject object2: AnyObject?) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector, withObject: object1, withObject: object2)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval, inModes modes: [AnyObject?]?) {
swizzle()
self.🚀performSelector(selector, withObject: object, afterDelay: delay, inModes: modes)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval) {
swizzle()
self.🚀performSelector(selector, withObject: object, afterDelay: delay)
}
}
Ответ 10
фактический синтаксис для очереди отправки следующий.
dispatch_after(1, dispatch_get_main_queue()) { () -> Void in
self.loadData() // call your method.
}
Ответ 11
Иногда (особенно если вы используете шаблон target/action
), вам может потребоваться использовать метод -[UIApplication sendAction:to:from:forEvent:]
(для iOS), поэтому в Swift это может быть примерно так:
UIApplication.sharedApplication()
.sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent)
Ответ 12
Я не знаю точно с тех пор, как, но Apple вернула выполнитьSelector в Xcode 7.1.1 (по крайней мере, той версии, которую я использую).
В моем приложении, которое я сейчас создаю, я вызываю различные функции с похожими именами функций в UIView, сгенерированном из CoreAnimator (отличное приложение, BTW), поэтому performSelector очень удобен. Вот как я его использую:
//defines the function name dynamically. the variables "stepN" and "dir" are defined elsewhere.
let AnimMethod = "addStep\(stepN)\(dir)Animation"
//prepares the selector with the function name above
let selector: Selector = NSSelectorFromString(AnimMethod)
//calls the said function in UIView named "meter"
meter.performSelector(selector)
Ответ 13
Я использую следующее решение:
// method will be called after delay
func method1() {
......
}
// to replace performSelector
// delay 100 ms
let time : dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC/(USEC_PER_SEC*10)))
dispatch_after(time, dispatch_get_main_queue(), {
self.method1()
})
Ответ 14
У меня есть ситуация, когда селектор построен со строковым литералом, который поступает из файла plist.
Таким образом, самый быстрый способ выполнить некоторый селектор в swift был решен с помощью следующего кода
var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someString), userInfo: nil, repeats: false)
timer.fire()
timer.invalidate()
Ответ 15
Пример реального мира в быстром комментарии "Матея Укмара" к ответу "J Терри":
class Button {
var title:String = "The big button"
var selector: ((sender: AnyObject?, type:String) -> ())?/*this holds any method assigned to it that has its type signature*/
func click(){
selector!(sender: self,type: "click")/*call the selector*/
}
func hover(){
selector!(sender: self,type: "hover")/*call the selector*/
}
}
class View {
var button = Button()
init(){
button.selector = handleSelector/*assign a method that will receive a call from the selector*/
}
func handleSelector(sender: AnyObject?,type:String) {
switch type{
case "click": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type)
case "hover": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type)
default:break;
}
}
}
let view:View = View()
view.button.click()/*Simulating button click*/
view.button.hover()/*Simulating button hover*/
//Output: View.handleSelector() sender: Button, title: The big button, type: click
//Output: View.handleSelector() sender: Button, title: The big button, type: hover