Что делать, если я хочу присвоить себе?
Если я попытаюсь запустить следующий код:
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