swift2 从协议扩展调用选择器

cnh2zyt3  于 2022-11-06  发布在  Swift
关注(0)|答案(4)|浏览(198)

我正在构建一个简单的主题引擎,并希望有一个扩展添加UISwipeGestureRecognizerUIViewController
下面是我的代码:

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

uinbv5nw

uinbv5nw1#

我所能想到的最简洁的解决方案是使用所讨论的方法在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)
    }
}
tjvv9vkg

tjvv9vkg2#

我找到了一个解决方案。可能不是完美的一个,但它的工作。因为我不能定义Themeable协议为@objc,因为它使用的是Swift唯一的enum,我决定移动方法,我想调用的“父”协议,并定义这个协议为@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)
    }
}
iih3973s

iih3973s3#

您是否考虑过创建一个 Package 器,以便从一个@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)
    }
}
kcrjzv8t

kcrjzv8t4#

这里有一个类似的用例,你可以通过选择器调用一个方法,而不用像swift中那样使用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
    }
}

相关问题