从协议扩展调用选择器

时间:2016-04-14 16:52:09

标签: ios swift swift2 protocol-extension

我正在构建简单的主题引擎,并希望有一个将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枚举,因此我无法在@objc协议前添加Themeable

4 个答案:

答案 0 :(得分:14)

我能想到的最干净,最有效的解决方案是使用相关方法在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 :(得分:0)

我找到了解决方案。可能不是完美的,但它的确有效。 我无法将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)
    }
}

答案 2 :(得分: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)
    }
}

答案 3 :(得分: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
    }
}