从协议扩展中调用选择器

13

我正在构建一个简单的主题引擎,并希望添加一个扩展,将 UISwipeGestureRecognizer 添加到 UIViewController

这是我的代码:

protocol Themeable {
    func themeDidUpdate(currentTheme: Theme) -> Void
}

extension Themeable where Self: UIViewController {
    func switchCurrentTheme() {
        Theme.switchTheme()
        themeDidUpdate(Theme.currentTheme)
    }

    func addSwitchThemeGestureRecognizer() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(Self.switchCurrentTheme))
        gestureRecognizer.direction = .Down
        gestureRecognizer.numberOfTouchesRequired = 2
        self.view.addGestureRecognizer(gestureRecognizer)
    }
}
当然,编译器找不到#selector(Self.switchCurrentTheme),因为它没有通过@objc指令公开。 我的扩展中是否可以添加此行为?
更新:Theme是Swift枚举,因此我无法在Themeable协议前添加@objc。
4个回答

22

我能想到的最干净、最可行的解决方案是,在UIViewController上定义一个私有扩展,其中包含所需的方法。通过将作用域限制为private,可以将对此方法的访问隔离在定义协议的源文件内部。以下是示例:

protocol Themeable {
    func themeDidUpdate(currentTheme: Theme) -> Void
}

fileprivate extension UIViewController {
    @objc func switchCurrentTheme() {
        guard let themeableSelf = self as? Themeable else {
            return
        }

        Theme.switchTheme()
        themeableSelf.themeDidUpdate(Theme.currentTheme)
    }
}

extension Themeable where Self: UIViewController {
    func addSwitchThemeGestureRecognizer() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
        gestureRecognizer.direction = .Down
        gestureRecognizer.numberOfTouchesRequired = 2
        self.view.addGestureRecognizer(gestureRecognizer)
    }
}

1
谢谢这个!非常顺利! :) 在Swift 3中,UIViewController的私有扩展现在需要使用fileprivate代替。此外,switchCurrentTheme函数应该是fileprivate而不是internal。 - Justin Stanley

1
我找到了一种解决方案。可能不是完美的,但它可以工作。 由于我无法将Themeable协议定义为@objc,因为它使用了仅限Swift的enum,所以我决定将我想要调用的方法移动到“parent”协议中,并将该协议定义为@objc。 这似乎可以工作,但老实说,我并不喜欢这样做...
@objc protocol ThemeSwitcher {
    func switchCurrentTheme()
}

protocol Themeable: ThemeSwitcher {
    func themeDidUpdate(currentTheme: Theme) -> Void
}

extension Themeable where Self: UIViewController {
    func switchCurrentTheme() {
        Theme.switchTheme()
        themeDidUpdate(Theme.currentTheme)
    }

    func addSwitchThemeGestureRecognizer() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
        gestureRecognizer.direction = .Down
        gestureRecognizer.numberOfTouchesRequired = 2
        self.view.addGestureRecognizer(gestureRecognizer)
    }
}

1
仍然无法正常工作,因为我无法将@objc应用于实现Themeable协议的ViewController - OgreSwamp

0
你有没有考虑过创建一个包装器,让你能够从一个 @objc 函数中调用你的非 @objc 函数?
@objc class Wrapper: NSObject {
    let themeable: Themeable

    init(themeable: Themeable) {
        self.themeable = themeable
    }

    func switchCurrentTheme() {
        Theme.switchTheme()
        themeable.themeDidUpdate(Theme.currentTheme)
    }
}

protocol Themeable {
    func themeDidUpdate(currentTheme: Theme) -> Void
}

extension Themeable where Self: UIViewController {
    func addSwitchThemeGestureRecognizer() {
        let wrapper = Wrapper(themeable: self)
        let gestureRecognizer = UISwipeGestureRecognizer(target: wrapper, action:#selector(Wrapper.switchCurrentTheme))
        gestureRecognizer.direction = .Down
        gestureRecognizer.numberOfTouchesRequired = 2
        self.view.addGestureRecognizer(gestureRecognizer)
    }
}

这是一个很棒的想法,但是无法让它工作。:( - dwlz

0

这里有一个类似的用例,你可以使用 dynamic 关键字在不使用 @objc 的情况下通过选择器调用方法。这样做,你就是在指示编译器隐式地使用动态分派。

import UIKit

protocol Refreshable: class {

    dynamic func refreshTableData()

    var tableView: UITableView! {get set}
}

extension Refreshable where Self: UIViewController {

    func addRefreshControl() {
        tableView.insertSubview(refreshControl, at: 0)
    }

    var refreshControl: UIRefreshControl {
        get {
            let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
            if let control = _refreshControl[tmpAddress] as? UIRefreshControl {
                return control
            } else {
                let control = UIRefreshControl()
                control.addTarget(self, action: Selector(("refreshTableData")), for: .valueChanged)
                _refreshControl[tmpAddress] = control
                return control
            }
        }
    }
}

fileprivate var _refreshControl = [String: AnyObject]()

class ViewController: UIViewController: Refreshable {
    @IBOutlet weak var tableView: UITableView! {
        didSet {
            addRefreshControl()
        }
    }

    func refreshTableData() {
        // Perform some stuff
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接