繁体   English   中英

“扩展不能包含存储的属性”阻止我重构代码

[英]"Extensions must not contain stored properties" preventing me from refactoring code

我有一个 13 行的 func,它在我的应用程序中的每个 ViewController 中重复,在整个项目中总共有 690 行代码!

/// Adds Menu Button
func addMenuButton() {
    let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
    let menuImage = UIImage(named: "MenuWhite")
    menuButton.setImage(menuImage, for: .normal)

    menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
    self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
}
/// Launches the MenuViewController
@objc func menuTappedAction() {
    coordinator?.openMenu()
}

要使 menuTappedAction 函数正常工作,我必须像这样声明一个弱变量:

extension UIViewController {

weak var coordinator: MainCoordinator?

但是这样做我得到错误Extensions must not contain stored properties到目前为止我尝试过的:

1) 删除weak关键字会在我的所有应用程序中引起冲突。 2)这样声明:

weak var coordinator: MainCoordinator?
extension UIViewController {

将消除错误,但协调器不会执行任何操作。 任何建议如何解决这个问题?

您可以将addMenuButton()函数移动到具有协议扩展名的协议。 例如:

@objc protocol Coordinated: class {
    var coordinator: MainCoordinator? { get set }
    @objc func menuTappedAction()
}

extension Coordinated where Self: UIViewController {
    func addMenuButton() {
        let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        let menuImage = UIImage(named: "MenuWhite")
        menuButton.setImage(menuImage, for: .normal)

        menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
    }
}

不幸的是,您不能将@objc方法添加到类扩展中(请参阅: this stackoverflow question ),因此您仍然必须像这样设置您的视图控制器:

class SomeViewController: UIViewController, Coordinated {
    weak var coordinator: MainCoordinator?
    /// Launches the MenuViewController
    @objc func menuTappedAction() {
        coordinator?.openMenu()
    }
}

它将为您节省一些代码,并允许您重构更大的函数addMenuButton() 希望这可以帮助!

为了让它在扩展中工作,你必须像这样使它计算属性: -

extension ViewController {

   // Make it computed property
    weak var coordinator: MainCoordinator? {
        return MainCoordinator()
    }

}

您可以使用 objc 关联对象。

extension UIViewController {
    private struct Keys {
        static var coordinator = "coordinator_key"
    }

    private class Weak<V: AnyObject> {
        weak var value: V?

        init?(_ value: V?) {
            guard value != nil else { return nil }
            self.value = value
        }
    }

    var coordinator: Coordinator? {
        get { (objc_getAssociatedObject(self, &Keys.coordinator) as? Weak<Coordinator>)?.value }
        set { objc_setAssociatedObject(self, &Keys.coordinator, Weak(newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }
}

发生这种情况是因为extension不是类,因此它不能包含存储的属性。 即使它们是weak属性。

考虑到这一点,您有两个主要选择:

  1. 快捷方式:协议+协议扩展
  2. 讨厌的 objc 方式:关联对象

选项 1:使用协议和协议扩展:

1.1. 声明你的协议

protocol CoordinatorProtocol: class {
    var coordinator: MainCoordinator? { get set }
    func menuTappedAction()
}

1.2. 创建协议扩展,以便您可以预先实现addMenuButton()方法

extension CoordinatorProtocol where Self: UIViewController {
    func menuTappedAction() {
        // Do your stuff here
    }
}

1.3. 声明weak var coordinator: MainCoordinator? 在将采用此协议的课程中。 不幸的是,你不能跳过这个

class SomeViewController: UIViewController, CoordinatorProtocol {
    weak var coordinator: MainCoordinator?
}

选项 2:使用 objc 关联对象(不推荐)

extension UIViewController {
    private struct Keys {
        static var coordinator = "coordinator_key"
    }

    public var coordinator: Coordinator? {
        get { objc_getAssociatedObject(self, &Keys.coordinator) as? Coordinator }
        set { objc_setAssociatedObject(self, &Keys.coordinator, newValue, .OBJC_ASSOCIATION_ASSIGN) }
    }
}

你可以通过子类化来做到这一点

class CustomVC:UIViewController {

    weak var coordinator: MainCoordinator?

    func addMenuButton() {
        let menuButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        let menuImage = UIImage(named: "MenuWhite")
        menuButton.setImage(menuImage, for: .normal)

        menuButton.addTarget(self, action: #selector(menuTappedAction), for: .touchDown)
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuButton)
    }
    /// Launches the MenuViewController
    @objc func menuTappedAction() {
        coordinator?.openMenu()
    }

}

class MainCoordinator {

    func openMenu() {

    }
}


class ViewController: CustomVC {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

}

使用NSMapTable为您的扩展创建一个状态容器,但请确保您指定对键使用弱引用。

创建一个要在其中存储状态的类。 我们称之为ExtensionState ,然后在扩展文件中创建一个映射作为私有字段。

private var extensionStateMap: NSMapTable<TypeBeingExtended, ExtensionState> = NSMapTable.weakToStrongObjects()

那么你的扩展可以是这样的。

extension TypeBeingExtended {
    private func getExtensionState() -> ExtensionState {
        var state = extensionStateMap.object(forKey: self)

        if state == nil {
            state = ExtensionState()
            extensionStateMap.setObject(state, forKey: self)
        }

        return state
    }

    func toggleFlag() {
        var state = getExtensionState()
        state.flag = !state.flag
    }
}

这适用于 iOS 和 macOS 开发,但不适用于服务器端 Swift,因为那里没有NSMapTable

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM