メソッドのperformSelector
ファミリー Swiftでは使用できません 。それでは、@objc
オブジェクトのメソッドをどのように呼び出すことができますか。呼び出されるメソッドは実行時に選択され、コンパイル時には不明です。 NSInvocation
はSwiftでも利用できないようです。
Swiftでは、Objective-CのAnyObject
と同様に、id
型に(@objc
メソッド宣言が表示されている)任意のメソッドを送信できることを知っています。ただし、それでもコンパイル時にメソッド名をハードコーディングする必要があります。実行時に動的に選択する方法はありますか?
クロージャーを使用する
class A {
var selectorClosure: (() -> Void)?
func invoke() {
self.selectorClosure?()
}
}
var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()
これは新しいものではないことに注意してください。Obj-Cでも、新しいAPIはperformSelector
よりもブロックの使用を好みます(respondsToSelector:
とperformSelector:
を使用してデリゲートメソッドを呼び出すUIAlertView
と比較新しいUIAlertController
)。
performSelector:
を使用することは常に安全ではなく、ARCとうまく機能しません(したがって、performSelector:
に対するARCの警告)。
Xcode 7では、performSelectorOnMainThread()
とperformSelectorInBackground()
を含むperformSelectorメソッドの完全なファミリがSwiftで利用可能です。楽しい!
つかいます 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)")
}
コンソールログ:
あいさつ世界
添付オブジェクト:サンシャイン
この代替案は以前に発見されており、デバイスとシミュレーターでもテストしました。アイデアは、次のメソッドを使用することですIControl:
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")
}
コンソールログ:
あいさつ世界
NSTimer
class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval,
target target: AnyObject!,
selector aSelector: Selector,
userInfo userInfo: AnyObject!,
repeats repeats: Bool) -> NSTimer!
Swift
perform(#selector(someSelector), with: nil, afterDelay: 1.0, inModes: [.commonModes])
@JTerryの回答「Swiftではセレクターは必要ありません」に従って、実際のメソッドを変数に割り当てることができます。私の解決策は次のとおりでした(メソッドに1つのパラメーターが必要でした)。
class SettingsMenuItem: NSObject {
...
var tapFunction: ((sender: AnyObject?) -> ())?
}
そして、View Controllerで、この方法で関数を宣言、割り当て、実行します。
class SettingsViewController: UITableViewController {
func editProfile(sender: AnyObject?) {
...
}
...
menuItem.tapFunction = editProfile
...
if let tapFunction = menuItem.tapFunction {
tapFunction(sender: self)
}
}
これはSwiftで使用できます
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false)
func someSelector() {
// Something after a delay
}
これにより、Objective-CのperformSelectorによって実行されることを実行できます。
私もこれに苦労していました。最終的に、ターゲットやセレクターを使用する必要がないことに気付きました。私にとっての解決策は、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();
ええと、目的のメソッドを公開するためにスウィズリングを使用できます!
この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)
}
}
ディスパッチキューの実際の構文は次のとおりです。
dispatch_after(1, dispatch_get_main_queue()) { () -> Void in
self.loadData() // call your method.
}
いつからか正確にはわかりませんが、AppleはXcode 7.1.1でperformSelectorを復活させました(少なくとも私が使用しているバージョンです)。
現在作成しているアプリでは、CoreAnimator(素晴らしいアプリ、BTW)から生成されたUIViewで同様のfunctionNamesを使用してさまざまな関数を呼び出しているため、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)
ときどき(特にtarget/action
パターンを使用している場合)-[UIApplication sendAction:to:from:forEvent:]
メソッド(iOSの場合)を使用する必要がある場合があるため、Swiftでは次のようになります。
UIApplication.sharedApplication()
.sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent)
そのトピックの別の入力です。
時々、関数/メソッドを「間接的に」呼び出さなければなりませんでした。例:特定のセルの個々の関数を呼び出す。 tabelViewの動作を定義するために、構造体の配列をよく使用します。
以前はPerformSelectorなどを使用していましたが、Swiftプログラムでは常に "奇妙"に見えるため、いくつかの研究を行った後、間接関数呼び出しを使用しました。
これは、構文と動作をテストするための私の遊び場の簡単な例です...(xCode 9.4.1)
// Test for indirect function calls
// ------------------------------------------------------------------------
// functions we want to call inderectly
func function1() {
print("function1 active")
}
func function2() {
print("function2 active")
}
func function3() {
print("function3 active")
}
func function4(_ parameter: Int) {
print("function4 use the parameter: \(parameter)")
}
// ------------------------------------------------------------------------
// data structures
// a struct to build array items
struct functionCallTestStruct {
// struct properties
let what: String // a string as an example for other variables
let functionToCall : () // the function as an array element
var functionWithParameter : (Int) -> () // the function as an array element
let parameterForFunction : Int
// Initializer
init(_ what: String,
_ functionToCall: (),
_ functionWithParameter: @escaping (Int) -> (),
_ parameterForFunction: Int) {
self.what = what
self.functionToCall = functionToCall
self.functionWithParameter = functionWithParameter
self.parameterForFunction = parameterForFunction
}
}
// the array which holds the functions we want to call
let functionTestArray : [functionCallTestStruct] = [
functionCallTestStruct("We will call the first function", function1(), function4(_:), 10),
functionCallTestStruct("We will call the second function", function2(), function4(_:), 11),
functionCallTestStruct("We will call the third function", function3(), function4(_:), 12),
]
// ------------------------------------------------------------------------
// Test program
// a loop over the array
for i in 0 ..< functionTestArray.count {
// print explanation (be aware: print is quite lame, .. see the output ;-))
print(functionTestArray[i].what)
// and with this we indirectly call the functions
functionTestArray[i].functionToCall
let myParameter = functionTestArray[i].parameterForFunction
functionTestArray[i].functionWithParameter(myParameter)
}
出力を与えます:
function1 active
function2 active
function3 active
We will call the first function
function4 use the parameter: 10
We will call the second function
function4 use the parameter: 11
We will call the third function
function4 use the parameter: 12
面白い事実:文字列の印刷(何)は、関数がprintで呼び出すよりも遅い...これも警告です:この戦術でシーケンスを信頼しないでください
Swiftの "Matej Ukmar's"コメントから "J Terry's"回答への実世界の例:
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
セレクターがplistファイルからの文字列リテラルで構築される状況があります。 Swiftでセレクターを実行する最速の方法は次のコードで解決されました
var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someString), userInfo: nil, repeats: false)
timer.fire()
timer.invalidate()
私は次のソリューションを使用しています:
// 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()
})