Что делать, если я хочу присвоить себе?

Если я попытаюсь запустить следующий код:

photographer = photographer

Я получаю сообщение об ошибке:

Назначение свойства самому себе.


Я хочу присвоить это свойство самому себе для принудительного запуска блока photographer didSet.

Здесь реальный пример: в лекции "16. Segues and Text Fields" "Зимний 2013 курс Стэнфорда iOS" (13): 20), профессор рекомендует написать код, похожий на следующий:

@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, поскольку он использует шаблон нулевого объекта.

Причина, по которой мы дважды вызываем перезагрузку, состоит в том, что мы не знаем, будет ли свойство задано до или после создания представления. Например, пользователь может сначала установить свойство, затем представить контроллер представления или представить контроллер представления, а затем обновить свойство.

Мне нравится, как это свойство агностически связано с загрузкой представления (лучше не делать никаких предположений о времени загрузки просмотров), поэтому я хочу использовать тот же шаблон (только слегка измененный) в моем собственном коде:

@IBOutlet weak var photographerLabel: UILabel?

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

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

Здесь вместо создания нового метода, который нужно вызывать из двух мест, я просто хочу код в блоке didSet. Я хочу, чтобы viewDidLoad принудительно вызывал didSet, поэтому присваиваю свойство самому себе. Однако Свифт не позволяет мне это делать. Как я могу заставить didSet вызываться?

Ответы

Ответ 1

До 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()

Ответ 2

Есть несколько хороших обходных решений, но в этом мало смысла. Если программист (будущий разработчик кода) видит такой код:

a = a

Они удалят его.

Такое утверждение (или обходное решение) никогда не должно появляться в вашем коде.

Если ваше свойство выглядит следующим образом:

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

тогда не рекомендуется ссылаться на обработчик didSet путем назначения 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().

Если мы говорим о выходах, подходящим решением является принудительная загрузка представлений перед назначением в первый раз:

@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 ненужным.

Теперь вы можете спросить: "Почему я должен загружать представление, если он мне еще не нужен? Я хочу только сохранить мои переменные здесь для будущего использования". Если это то, что вы просите, это означает, что вы используете контроллер вида в качестве своего класса модели, просто для хранения данных. Это проблема архитектуры сама по себе. Если вы не хотите использовать контроллер, даже не создавайте его. Используйте класс модели для хранения ваших данных.

Ответ 3

Сделайте функцию, которую вызовы didSet затем вызывают эту функцию, когда вы хотите что-то обновить? Похоже, это защитит разработчиков от WTF? в будущем

Ответ 4

@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