我可以让#selector 引用 Swift 中的闭包吗?
Can I make #selector refer to a closure in Swift?
我想让我的方法的一个 selector
参数引用一个闭包 属性,它们都存在于同一范围内。例如,
func backgroundChange() {
self.view.backgroundColor = UIColor.blackColor()
self.view.alpha = 0.55
let backToOriginalBackground = {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(backToOriginalBackground), userInfo: nil, repeats: false)
}
但是,这显示错误:Argument of #selector cannot refer to a property
。
当然我可以定义一个新的、单独的方法并将闭包的实现移到它那里,但我想对这么小的实现保持节俭。
是否可以为 #selector
参数设置闭包?
不,#selector 指的是 Objective-C 方法。
尽管如此,您可以做一些更好的事情:向 NSTimer 添加一个扩展,使您可以创建一个不带有目标和选择器但带有闭包的预定计时器。
不能直接解决,但可以采取一些解决方法。看看下面的例子。
/// Target-Action helper.
final class Action: NSObject {
private let _action: () -> ()
init(action: @escaping () -> ()) {
_action = action
super.init()
}
@objc func action() {
_action()
}
}
let action1 = Action { print("action1 triggered") }
let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)
如果您将块的作用域更改为 class 作用域而不是函数,并在那里保留对闭包的引用。
您可以使用函数调用该闭包。在 class。这样您就可以将该闭包作为选择器调用。
像这样:
class Test: NSObject {
let backToOriginalBackground = {
}
func backgroundChange() {
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
}
func test() {
self.backToOriginalBackground()
}
}
正如@gnasher729 指出的那样,这是不可能的,因为选择器只是方法的名称,而不是方法本身。在一般情况下,我会在这里使用 dispatch_after
,但在这种特殊情况下,更好的工具 IMO 是 UIView.animateWithDuration
,因为它正是该功能的用途,而且调整过渡非常容易:
UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}, completion: nil)
我的解决方案是创建一个 class 块变量,例如:
let completionBlock: () -> () = nil
创建调用此 completionBlock 的方法:
func completed(){
self.completionBlock!()
}
在我想像方块一样放置选择器的地方:
func myFunc(){
self.completionBlock = {//what I want to be done}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(Myclass.completed), userInfo: nil, repeats: false)
}
所以我对以类似 swift 的方式将 selector
分配给 closure
的回答与已有的一些答案类似,但我想我会分享一个我如何在 UIViewController
扩展中做到这一点的真实例子。
fileprivate class BarButtonItem: UIBarButtonItem {
var actionCallback: ( () -> Void )?
func buttonAction() {
actionCallback?()
}
}
fileprivate extension Selector {
static let onBarButtonAction = #selector(BarButtonItem.buttonAction)
}
extension UIViewController {
func createBarButtonItem(title: String, action: @escaping () -> Void ) -> UIBarButtonItem {
let button = BarButtonItem(title: title, style: .plain, target nil, action: nil)
button.actionCallback = action
button.action = .onBarButtonAction
return button
}
}
// Example where button is inside a method of a UIViewController
// and added to the navigationItem of the UINavigationController
let button = createBarButtonItem(title: "Done"){
print("Do something when done")
}
navigationItem.setLeftbarButtonItems([button], animated: false)
现在可以了。我在 Swift 4 中为基于块的选择器创建了一个要点。
https://gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba4543
用法:
UIButton().addTarget(Selector, action: Selector { debugPrint("my code here") }, for: .touchUpInside)`
我至少为 UIBarButtonItem 尝试过这个:
private var actionKey: Void?
extension UIBarButtonItem {
private var _action: () -> () {
get {
return objc_getAssociatedObject(self, &actionKey) as! () -> ()
}
set {
objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
convenience init(title: String?, style: UIBarButtonItemStyle, action: @escaping () -> ()) {
self.init(title: title, style: style, target: nil, action: #selector(pressed))
self.target = self
self._action = action
}
@objc private func pressed(sender: UIBarButtonItem) {
_action()
}
}
那么你可以这样做:
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
print("Hello World!")
})
您可以使用支持 UIControl、UIButton、UIRefreshControl、UIGestureRecognizer 和 UIBarButtonItem 的 ActionClosurable。
https://github.com/takasek/ActionClosurable
下面显示 UIBarButtonItem 的示例
// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
print("barButtonItem title")
}
@werediver 的回答非常好。这是一个允许您将其作为函数调用的更新。
import Foundation
public extension Selector {
/// Wraps a closure in a `Selector`.
/// - Note: Callable as a function.
final class Perform: NSObject {
public init(_ perform: @escaping () -> Void) {
self.perform = perform
super.init()
}
private let perform: () -> Void
}
}
//MARK: public
public extension Selector.Perform {
@objc func callAsFunction() { perform() }
var selector: Selector { #selector(callAsFunction) }
}
您需要管理对 Selector.Perform
的强引用。一种方法是将 UIKit 类 子类化,该子类旨在与目标操作一起使用:
/// A `UITapGestureRecognizer` that wraps a closure.
public final class TapGestureRecognizer: UITapGestureRecognizer {
public init(_ perform: @escaping () -> Void) {
self.perform = .init(perform)
super.init(target: self.perform, action: self.perform.selector)
}
public let perform: Selector.Perform
}
let tapRecognizer = TapGestureRecognizer { print("") }
tapRecognizer.perform() // ""
Swift 5.2.x
首先,您需要为您的区块声明“易于使用”typealias
:
typealias Completion = () -> ()
然后,您必须声明 private var 以将“作为门”用于您的函数:
private var action: Completion?
之后,你应该创建一个可以被你的 Selector
调用的函数(它只接受字符串格式)并调用私有完成:
@objc func didAction() {
self.action?()
}
最后,您可以 re-write 您的函数(使用新的 swift 语法),例如:
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(didAction), userInfo: nil, repeats: false)
self.action = backToOriginalBackground
P.S.:请记住,您的变量(或参数,如果您将其嵌入到函数中)必须与您声明的类型相同 typeAlias
所以,在我们的例子中:
var backToOriginalBackground: () -> ()
或者还有:
var backToOriginalBackground: Completion
我想让我的方法的一个 selector
参数引用一个闭包 属性,它们都存在于同一范围内。例如,
func backgroundChange() {
self.view.backgroundColor = UIColor.blackColor()
self.view.alpha = 0.55
let backToOriginalBackground = {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(backToOriginalBackground), userInfo: nil, repeats: false)
}
但是,这显示错误:Argument of #selector cannot refer to a property
。
当然我可以定义一个新的、单独的方法并将闭包的实现移到它那里,但我想对这么小的实现保持节俭。
是否可以为 #selector
参数设置闭包?
不,#selector 指的是 Objective-C 方法。
尽管如此,您可以做一些更好的事情:向 NSTimer 添加一个扩展,使您可以创建一个不带有目标和选择器但带有闭包的预定计时器。
不能直接解决,但可以采取一些解决方法。看看下面的例子。
/// Target-Action helper.
final class Action: NSObject {
private let _action: () -> ()
init(action: @escaping () -> ()) {
_action = action
super.init()
}
@objc func action() {
_action()
}
}
let action1 = Action { print("action1 triggered") }
let button = UIButton()
button.addTarget(action1, action: #selector(action1.action), forControlEvents: .TouchUpInside)
如果您将块的作用域更改为 class 作用域而不是函数,并在那里保留对闭包的引用。
您可以使用函数调用该闭包。在 class。这样您就可以将该闭包作为选择器调用。
像这样:
class Test: NSObject {
let backToOriginalBackground = {
}
func backgroundChange() {
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(test), userInfo: nil, repeats: false)
}
func test() {
self.backToOriginalBackground()
}
}
正如@gnasher729 指出的那样,这是不可能的,因为选择器只是方法的名称,而不是方法本身。在一般情况下,我会在这里使用 dispatch_after
,但在这种特殊情况下,更好的工具 IMO 是 UIView.animateWithDuration
,因为它正是该功能的用途,而且调整过渡非常容易:
UIView.animateWithDuration(0, delay: 0.5, options: [], animations: {
self.view.backgroundColor = UIColor.whiteColor()
self.view.alpha = 1.0
}, completion: nil)
我的解决方案是创建一个 class 块变量,例如:
let completionBlock: () -> () = nil
创建调用此 completionBlock 的方法:
func completed(){
self.completionBlock!()
}
在我想像方块一样放置选择器的地方:
func myFunc(){
self.completionBlock = {//what I want to be done}
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: #selector(Myclass.completed), userInfo: nil, repeats: false)
}
所以我对以类似 swift 的方式将 selector
分配给 closure
的回答与已有的一些答案类似,但我想我会分享一个我如何在 UIViewController
扩展中做到这一点的真实例子。
fileprivate class BarButtonItem: UIBarButtonItem {
var actionCallback: ( () -> Void )?
func buttonAction() {
actionCallback?()
}
}
fileprivate extension Selector {
static let onBarButtonAction = #selector(BarButtonItem.buttonAction)
}
extension UIViewController {
func createBarButtonItem(title: String, action: @escaping () -> Void ) -> UIBarButtonItem {
let button = BarButtonItem(title: title, style: .plain, target nil, action: nil)
button.actionCallback = action
button.action = .onBarButtonAction
return button
}
}
// Example where button is inside a method of a UIViewController
// and added to the navigationItem of the UINavigationController
let button = createBarButtonItem(title: "Done"){
print("Do something when done")
}
navigationItem.setLeftbarButtonItems([button], animated: false)
现在可以了。我在 Swift 4 中为基于块的选择器创建了一个要点。
https://gist.github.com/cprovatas/98ff940140c8744c4d1f3bcce7ba4543
用法:
UIButton().addTarget(Selector, action: Selector { debugPrint("my code here") }, for: .touchUpInside)`
我至少为 UIBarButtonItem 尝试过这个:
private var actionKey: Void?
extension UIBarButtonItem {
private var _action: () -> () {
get {
return objc_getAssociatedObject(self, &actionKey) as! () -> ()
}
set {
objc_setAssociatedObject(self, &actionKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
convenience init(title: String?, style: UIBarButtonItemStyle, action: @escaping () -> ()) {
self.init(title: title, style: style, target: nil, action: #selector(pressed))
self.target = self
self._action = action
}
@objc private func pressed(sender: UIBarButtonItem) {
_action()
}
}
那么你可以这样做:
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, action: {
print("Hello World!")
})
您可以使用支持 UIControl、UIButton、UIRefreshControl、UIGestureRecognizer 和 UIBarButtonItem 的 ActionClosurable。 https://github.com/takasek/ActionClosurable
下面显示 UIBarButtonItem 的示例
// UIBarButtonItem
let barButtonItem = UIBarButtonItem(title: "title", style: .plain) { _ in
print("barButtonItem title")
}
@werediver 的回答非常好。这是一个允许您将其作为函数调用的更新。
import Foundation
public extension Selector {
/// Wraps a closure in a `Selector`.
/// - Note: Callable as a function.
final class Perform: NSObject {
public init(_ perform: @escaping () -> Void) {
self.perform = perform
super.init()
}
private let perform: () -> Void
}
}
//MARK: public
public extension Selector.Perform {
@objc func callAsFunction() { perform() }
var selector: Selector { #selector(callAsFunction) }
}
您需要管理对 Selector.Perform
的强引用。一种方法是将 UIKit 类 子类化,该子类旨在与目标操作一起使用:
/// A `UITapGestureRecognizer` that wraps a closure.
public final class TapGestureRecognizer: UITapGestureRecognizer {
public init(_ perform: @escaping () -> Void) {
self.perform = .init(perform)
super.init(target: self.perform, action: self.perform.selector)
}
public let perform: Selector.Perform
}
let tapRecognizer = TapGestureRecognizer { print("") }
tapRecognizer.perform() // ""
Swift 5.2.x
首先,您需要为您的区块声明“易于使用”typealias
:
typealias Completion = () -> ()
然后,您必须声明 private var 以将“作为门”用于您的函数:
private var action: Completion?
之后,你应该创建一个可以被你的 Selector
调用的函数(它只接受字符串格式)并调用私有完成:
@objc func didAction() {
self.action?()
}
最后,您可以 re-write 您的函数(使用新的 swift 语法),例如:
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(didAction), userInfo: nil, repeats: false)
self.action = backToOriginalBackground
P.S.:请记住,您的变量(或参数,如果您将其嵌入到函数中)必须与您声明的类型相同 typeAlias
所以,在我们的例子中:
var backToOriginalBackground: () -> ()
或者还有:
var backToOriginalBackground: Completion