25

如果我尝试运行以下代码:

photographer = photographer

我得到错误:

将属性分配给自身。


我想将属性分配给自身以强制photographer didSet块运行。

这是一个真实的例子:在2013 年冬季斯坦福 iOS 课程(13:20)的“16. Segues and Text Fields”讲座中,教授建议编写类似于以下的代码:

@IBOutlet weak var photographerLabel: UILabel!

var photographer: Photographer? {
    didSet {
        self.title = photographer.name
        if isViewLoaded() { reload() }
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    reload()
}

func reload() {
    photographerLabel.text = photographer.name
}

注意:我做了以下更改:(1)代码从Objective-C切换到Swift;(2)因为是Swift,所以我用didSet属性的块代替了setPhotographer:方法;(3) 而不是self.view.window我正在使用isViewLoaded,因为前者错误地强制视图在访问该view属性时加载;(4)reload()为简单起见,该方法(仅)更新标签,因为它更接近于我的代码;(5) 添加了摄影师IBOutlet标签以支持这个更简单的代码;(6) 由于我使用的是 Swift,因此isViewLoaded()出于性能原因检查不再存在,现在需要防止崩溃,因为 IBOutlet 被定义为UILabel!而不是UILabel?因此在加载视图之前尝试访问它会使应用程序崩溃;这在 Objective-C 中不是强制性的,因为它使用空对象模式。

我们调用 reload 两次的原因是因为我们不知道该属性是在视图创建之前还是之后设置。例如,用户可能首先设置属性,然后呈现视图控制器,或者他们可能呈现视图控制器,然后更新属性。

我喜欢这个属性如何与视图加载时间无关(最好不要对视图加载时间做任何假设),所以我想在我自己的代码中使用相同的模式(仅稍作修改):

@IBOutlet weak var photographerLabel: UILabel?

var photographer: Photographer? {
    didSet {
        photographerLabel?.text = photographer.name
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    photographer = photographer
}

这里不是创建要从两个地方调用的新方法,我只想要didSet块中的代码。我想viewDidLoad强制didSet调用,所以我将属性分配给它自己。不过,斯威夫特不允许我这样做。如何强制didSet调用?

4

5 回答 5

58

Swift 3.1之前,您可以通过以下方式将属性分配name给自身:

name = (name)

但这现在给出了相同的错误:“将属性分配给自身”

还有许多其他方法可以解决此问题,包括引入临时变量:

let temp = name
name = temp

这太有趣了,不能分享。我相信社区可以想出更多的方法来做到这一点,越疯狂越好

class Test: NSObject {
    var name: String? {
        didSet {
            print("It was set")
        }
    }

    func testit() {
        // name = (name)    // No longer works with Swift 3.1 (bug SR-4464)
        // (name) = name    // No longer works with Swift 3.1
        // (name) = (name)  // No longer works with Swift 3.1
        (name = name)
        name = [name][0]
        name = [name].last!
        name = [name].first!
        name = [1:name][1]!
        name = name ?? nil
        name = nil ?? name
        name = name ?? name
        name = {name}()
        name = Optional(name)!
        name = ImplicitlyUnwrappedOptional(name)
        name = true ? name : name
        name = false ? name : name
        let temp = name; name = temp
        name = name as Any as? String
        name = (name,0).0
        name = (0,name).1
        setValue(name, forKey: "name") // requires class derive from NSObject
        name = Unmanaged.passUnretained(self).takeUnretainedValue().name
        name = unsafeBitCast(name, to: type(of: name))
        name = unsafeDowncast(self, to: type(of: self)).name
        perform(#selector(setter:name), with: name) // requires class derive from NSObject
        name = (self as Test).name
        unsafeBitCast(dlsym(dlopen("/usr/lib/libobjc.A.dylib",RTLD_NOW),"objc_msgSend"),to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
        unsafeBitCast(class_getMethodImplementation(type(of: self), #selector(setter:name)), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
        unsafeBitCast(method(for: #selector(setter:name)),to:(@convention(c)(Any?,Selector,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject 
        _ = UnsafeMutablePointer(&name)
        _ = UnsafeMutableRawPointer(&name)
        _ = UnsafeMutableBufferPointer(start: &name, count: 1)
        withUnsafePointer(to: &name) { name = $0.pointee }

        //Using NSInvocation, requires class derive from NSObject
        let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
        unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
        var localVarName = name
        withUnsafePointer(to: &localVarName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
        invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
    }
}

let test = Test()
test.testit()
于 2015-08-11T00:31:35.423 回答
14

有一些很好的解决方法,但这样做没有什么意义。如果程序员(代码的未来维护者)看到这样的代码:

a = a

他们将删除它。

这样的声明(或解决方法)永远不应出现在您的代码中。

如果您的财产如下所示:

var a: Int {
   didSet {
      // code
   }
}

那么didSet通过 assignment 调用处理程序不是一个好主意a = a

如果未来的维护者在didSet这样的基础上增加了性能改进怎么办?

var a: Int {
   didSet {
      guard a != oldValue else {
          return
      }

      // code
   }
}

真正的解决方案是重构:

var a: Int {
   didSet {
      self.updateA()
   }
}

fileprivate func updateA() {
   // the original code
}

而不是a = a直接调用updateA().

如果我们谈论的是 outlet,一个合适的解决方案是在第一次分配之前强制加载视图:

@IBOutlet weak var photographerLabel: UILabel?

var photographer: Photographer? {
    didSet {
        _ = self.view // or self.loadViewIfNeeded() on iOS >= 9

        photographerLabel?.text = photographer.name // we can use ! here, it makes no difference
    }
}

这将使代码变得viewDidLoad不必要。

现在您可能会问“如果我还不需要视图,为什么还要加载它?我只想将变量存储在这里以备将来使用”。如果这就是您要问的,这意味着您使用视图控制器作为模型类,只是为了存储数据。这本身就是一个架构问题。如果您不想使用控制器,甚至不要实例化它。使用模型类来存储您的数据。

于 2017-03-28T14:36:48.070 回答
4

在此处输入图像描述

我希望有一天#Swift 开发者能解决这个问题 :)

简易拐杖:

func itself<T>(_ value: T) -> T {
    return value
}

采用:

// refresh
style = itself(style)
image = itself(image)
text  = itself(text)

(可选包括)


于 2020-02-13T03:25:35.003 回答
2

创建一个 didSet 调用的函数,然后在您想要更新某些内容时调用该函数?似乎这样可以防止开发人员去WTF?在未来

于 2017-04-21T09:33:43.303 回答
1

@vacawama在所有这些选项上都做得很好。然而,在iOS 10.3中,Apple 禁止了其中一些方式,并且很可能将来会再次这样做。

注意:为了避免风险和未来的错误,我将使用一个临时变量。


我们可以为此创建一个简单的函数:

func callSet<T>(_ object: inout T) {
    let temporaryObject = object
    object = temporaryObject
}

会像这样使用:callSet(&foo)


甚至是一元运算符,如果有合适的...

prefix operator +=

prefix func +=<T>(_ object: inout T) {
    let temporaryObject = object
    object = temporaryObject
}

会像这样使用:+=foo

于 2017-03-28T14:28:30.887 回答