web-dev-qa-db-ja.com

プロパティをそれ自体に割り当てたい場合はどうなりますか?

次のコードを実行しようとすると:

_photographer = photographer
_

エラーが発生します:

プロパティをそれ自体に割り当てる。


プロパティをそれ自体に割り当てて、photographerdidSetブロックを強制的に実行したいと思います。

実際の例を次に示します。 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ブロックにコードが必要です。 viewDidLoaddidSetを強制的に呼び出させたいので、プロパティをそれ自体に割り当てます。 Swiftでは、それはできません。didSetを強制的に呼び出すにはどうすればよいですか?

19
Senseful

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()
45
vacawama

いくつかの良い回避策がありますが、それを行う意味はほとんどありません。プログラマー(コードの将来のメンテナー)が次のようなコードを見た場合:

_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を使用していることを意味します。それ自体がアーキテクチャの問題です。コントローラを使用したくない場合は、インスタンス化すらしないでください。モデルクラスを使用してデータを保存します。

14
Sulthan

DidSetが呼び出す関数を作成し、何かを更新するときにその関数を呼び出しますか?このように、開発者がWTFに移行するのを防ぐことができますか?将来は

2
cjnevin

@ 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_

1
Jakub Truhlář

enter image description here

いつか#Swift開発者がこれを修正することを願っていますmiscuzzi:)

単純な松葉杖:

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

使用:

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

(オプションを含む)


0
Sergey Sergeyev