次のコードを実行しようとすると:
_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にあるため、_setPhotographer:
_メソッドの代わりにプロパティのdidSet
ブロックを使用します。 (3)_self.view.window
_の代わりにisViewLoaded
を使用しています。これは、前者がview
プロパティへのアクセス時に誤ってビューを強制的にロードするためです。 (4)reload()
メソッド(のみ)は、簡単にするためにラベルを更新します。これは、私のコードに似ているためです。 (5)この単純なコードをサポートするために、写真家のIBOutlet
ラベルが追加されました。 (6)Swiftを使用しているため、isViewLoaded()
チェックは単にパフォーマンス上の理由で存在しなくなり、クラッシュを防ぐために必須になりました。 IBOutletは_UILabel!
_ではなく_UILabel?
_として定義されているため、ビューが読み込まれる前にIBOutletにアクセスしようとすると、アプリケーションがクラッシュします。これは、nullオブジェクトパターンを使用するため、Objective-Cでは必須ではありませんでした。
リロードを2回呼び出す理由は、ビューが作成される前または後にプロパティが設定されるかどうかがわからないためです。たとえば、ユーザーが最初にプロパティを設定してからView Controllerを提示したり、ViewControllerを提示してからプロパティを更新したりする場合があります。
ビューがいつロードされるかについてこのプロパティが不可知論的であることが好きなので(ビューのロード時間については何も仮定しないのが最善です)、私自身のコードでこれと同じパターン(わずかに変更されただけ)を使用したいと思います:
_@IBOutlet weak var photographerLabel: UILabel?
var photographer: Photographer? {
didSet {
photographerLabel?.text = photographer.name
}
}
override func viewDidLoad() {
super.viewDidLoad()
photographer = photographer
}
_
ここでは、2つの場所から呼び出される新しいメソッドを作成する代わりに、didSet
ブロックにコードが必要です。 viewDidLoad
にdidSet
を強制的に呼び出させたいので、プロパティをそれ自体に割り当てます。 Swiftでは、それはできません。didSet
を強制的に呼び出すにはどうすればよいですか?
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()
いくつかの良い回避策がありますが、それを行う意味はほとんどありません。プログラマー(コードの将来のメンテナー)が次のようなコードを見た場合:
_a = a
_
彼らはそれを削除します。
このようなステートメント(または回避策)は、コードに表示されないようにする必要があります。
プロパティが次のようになっている場合:
_var a: Int {
didSet {
// code
}
}
_
その場合、割り当て_a = a
_によってdidSet
ハンドラーを呼び出すことはお勧めできません。
将来のメンテナがこのように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
のコードが不要になります。
今、あなたは尋ねているかもしれません「まだビューが必要ないのになぜビューをロードする必要があるのですか?将来の使用のために変数をここに保存したいだけです」。それがあなたが求めているものである場合、それはあなたがデータを保存するためだけに、モデルクラスとしてViewControllerを使用していることを意味します。それ自体がアーキテクチャの問題です。コントローラを使用したくない場合は、インスタンス化すらしないでください。モデルクラスを使用してデータを保存します。
DidSetが呼び出す関数を作成し、何かを更新するときにその関数を呼び出しますか?このように、開発者がWTFに移行するのを防ぐことができますか?将来は
@ 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
_