Swift和Interface Builder中的单身人士

问题描述 投票:3回答:3

Background

我的应用程序中有一个单例类,根据init()中的单行单例(带有私有this blog post)声明。具体来说,它看起来像这样:

@objc class Singleton {
    static let Singleton sharedInstance = Singleton()
    @objc dynamic var aProperty = false

    private init() {
    }
}

我想将aProperty的状态绑定到菜单项是否隐藏。

How I tried to solve the problem

以下是我执行此操作的步骤:

  1. 转到Interface Builder中的对象库,并将一个通用的“对象”添加到我的应用程序场景中。在Identity检查器中,将“Class”配置为Singleton
  2. 通过Ctrl键从Interface Builder中的单例对象拖动到我的App Delegate代码,在App Delegate中创建一个引用插座。最终看起来像这样:
@IBOutlet weak var singleton: Singleton!
  1. 转到菜单项的Bindings检查器,在“Availability”下选择“Hidden”,选中“Bind to”,在其前面的组合框中选择“Singleton”,然后在“Model Key Path”下键入aProperty

The issue

不幸的是,这不起作用:更改属性对相关菜单项没有影响。

Investigating the cause

问题似乎在于,尽管将init()声明为私有,但Interface Builder正在设法创建我的单例的另一个实例。为了证明这一点,我将NSLog("singleton init")添加到私有init()方法以及以下代码到我的app委托中的applicationDidFinishLaunching()

NSLog("sharedInstance = \(Singleton.sharedInstance) singleton = \(singleton)")

当我运行应用程序时,会在日志中输出:

singleton init
singleton init
sharedInstance = <MyModule.Singleton: 0x600000c616b0> singleton = Optional(<MyModule.Singleton: 0x600000c07330>)

因此,确实存在两种不同的情况。我还在我的app delegate中的其他地方添加了这段代码:

NSLog("aProperty: [\(singleton!.aProperty),\(String(describing:singleton!.value(forKey: "aProperty"))),\(Singleton.sharedInstance.singleton),\(String(describing:Singleton.sharedInstance.value(forKey: "aProperty")))] hidden: \(myMenuItem.isHidden)")

有一次,这会产生以下输出:

属性:[false,Optional(0),true,Optional(1)] hidden:false

显然,作为一个单身人士,所有的数值都应该匹配,但singleton产生一个输出,而Singleton.sharedInstance产生不同的输出。可以看出,对value(forKey:)的调用与其各自的对象相匹配,因此KVC不应成为问题。

The question

如何在Swift中声明单例类并使用Interface Builder将其连接起来以避免它被实例化两次?

如果那是不可能的,那么我将如何解决在Interface Builder中将全局属性绑定到控件的问题呢?

Is an MCVE necessary?

我希望描述足够详细,但如果有人觉得MCVE是必要的,请留下评论,我将创建一个并上传到GitHub。

swift macos cocoa interface-builder cocoa-bindings
3个回答
4
投票

我只是想通过声明不应该使用单身人士来分享全球状态来开始我的答案。虽然它们在开始时看起来似乎更容易使用,但它们往往会在以后产生许多令人头疼的问题,因为它们几乎可以从任何地方进行更改,使得程序有时无法预测。

话虽这么说,实现你所需要的并不是不可能的,但有一点点的仪式:

@objc class Singleton: NSObject {
    // using this class behind the scenes, this is the actual singleton
    class SingletonStorage: NSObject {
        @objc dynamic var aProperty = false
    }
    private static var storage = SingletonStorage()

    // making sure all instances use the same storage, regardless how
    // they were created
    @objc dynamic var storage = Singleton.storage

    // we need to tell to KVO which changes in related properties affect
    // the ones we're interested into
    override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
        switch key {
        case "aProperty":
            return ["storage.aProperty"]
        default: return super.keyPathsForValuesAffectingValue(forKey: key)
        }

    }

    // and simply convert it to a computed property
    @objc dynamic var aProperty: Bool {
        get { return Singleton.storage.aProperty }
        set { Singleton.storage.aProperty = newValue }
    }
}

1
投票

不幸的是,你无法从Swift中的init返回不同的实例。以下是一些可能的解决方法:

  • 在Interface Builder中为您的类的实例创建一个插座,然后仅在整个代码中引用该实例。 (本身不是单例,但您可以添加一些运行时检查以确保它仅从nib文件而不是代码实例化)。
  • 创建一个帮助器类以在Interface Builder中使用,并将您的单例作为其属性公开。即该助手类的任何实例将始终返回单个实例的单个实例。
  • 创建Swift单例类的Objective-C子类,并使其init始终返回共享的Swift单例实例。

1
投票

在我的特定情况下,有一种解决问题的方法。

回想一下这个问题,我只想根据这个单身人士中的aProperty状态隐藏和取消隐藏菜单。虽然我试图避免编写尽可能多的代码,但是通过在Interface Builder中执行所有操作,在这种情况下,以编程方式编写绑定更加麻烦:

menuItem.bind(NSBindingName.hidden, to: Singleton.sharedInstance, withKeyPath: "aProperty", options: nil)
© www.soinside.com 2019 - 2024. All rights reserved.