web-dev-qa-db-ja.com

setValueforKeyを使用せずにリフレクションを使用してオブジェクトプロパティを設定する

Swiftでは、.setValue(..., forKey: ...)を使用することはできません。

  • Intのようなnull許容型フィールド?
  • タイプとしてenumを持つプロパティ
  • [MyObject?]のようなnull許容オブジェクトの配列

これには1つの回避策があり、それはオブジェクト自体のsetValueforUndefinedKeyメソッドをオーバーライドすることです。

私は反射に基づいた一般的なオブジェクトマッパーを書いているので。 EVReflection を参照してください。この種の手動マッピングを可能な限り最小限に抑えたいと思います。

これらのプロパティを自動的に設定する他の方法はありますか?

回避策は、私のライブラリの単体テストにあります ここ これはコードです:

class WorkaroundsTests: XCTestCase {
    func testWorkarounds() {
        let json:String = "{\"nullableType\": 1,\"status\": 0, \"list\": [ {\"nullableType\": 2}, {\"nullableType\": 3}] }"
        let status = Testobject(json: json)
        XCTAssertTrue(status.nullableType == 1, "the nullableType should be 1")
        XCTAssertTrue(status.status == .NotOK, "the status should be NotOK")
        XCTAssertTrue(status.list.count == 2, "the list should have 2 items")
        if status.list.count == 2 {
            XCTAssertTrue(status.list[0]?.nullableType == 2, "the first item in the list should have nullableType 2")
            XCTAssertTrue(status.list[1]?.nullableType == 3, "the second item in the list should have nullableType 3")
        }
    }
}

class Testobject: EVObject {
    enum StatusType: Int {
        case NotOK = 0
        case OK
    }

    var nullableType: Int?
    var status: StatusType = .OK
    var list: [Testobject?] = []

    override func setValue(value: AnyObject!, forUndefinedKey key: String) {
        switch key {
        case "nullableType":
            nullableType = value as? Int
        case "status":
            if let rawValue = value as? Int {
                status = StatusType(rawValue: rawValue)!
            }
        case "list":
            if let list = value as? NSArray {
                self.list = []
                for item in list {
                    self.list.append(item as? Testobject)
                }
            }
        default:
            NSLog("---> setValue for key '\(key)' should be handled.")
        }
    }
}
19
Edwin Vermeer

同様の問題を解決しようとしていたときに、これを回避する方法を見つけました。KVOは純粋なSwiftプロトコルフィールドの値を設定できません。プロトコルは@objcとマークする必要があります。これは、コードベースに過度の苦痛を引き起こしました。回避策は、Objective Cランタイムを使用してIvarを検索し、フィールドオフセットを取得し、ポインターを使用して値を設定することです。このコードは、Swift 2.2:

import Foundation

class MyClass
{
    var myInt: Int?
}

let instance = MyClass()

// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(instance.dynamicType, "myInt")
let fieldOffset = ivar_getOffset(ivar)

// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(instance)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)

// Set the value using the pointer
pointerToField.memory = 42

assert(instance.myInt == 42)

ノート:

編集:ランタイムと呼ばれるフレームワークが https://github.com/wickwirew/Runtime にあり、純粋なものを提供しますSwift Swift 4+メモリレイアウトのモデル。これにより、Obj Cランタイムを呼び出さなくても、ivar_getOffsetに相当するものを安全に計算できます。これにより、次のようなプロパティを設定できます。 :

let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)

これは、同等の機能がSwift自体の一部になるまで、おそらく良い方法です。

14
Jon N

残念ながら、これはSwiftでは不可能です。

KVCはObjective-Cのものです。純粋なSwiftオプション(IntとOptionalの組み合わせ)はKVCでは機能しません。_Int?_で行う最善の方法は_NSNumber?_に置き換えることであり、KVCは機能します。これは、NSNumberがまだObjective-Cクラスであるためです。これは、型システムの悲しい制限です。

ただし、列挙型にはまだ希望があります。ただし、これによって実行する必要のあるコーディングの量が減ることはありませんが、はるかにクリーンで、最高の状態でKVCを模倣します。

  1. Settableというプロトコルを作成します

    _protocol Settable {
       mutating func setValue(value:String)
    }
    _
  2. 列挙型にプロトコルを確認してもらいます

    _enum Types : Settable {
        case  FirstType, SecondType, ThirdType
        mutating func setValue(value: String) {
            if value == ".FirstType" {
                self = .FirstType
            } else if value == ".SecondType" {
                self = .SecondType
            } else if value == ".ThirdType" {
                self = .ThirdType
            } else {
                fatalError("The value \(value) is not settable to this enum")
            }
       }
    }
    _
  3. メソッドを作成します:setEnumValue(value:value, forKey key:Any)

    _setEnumValue(value:String forKey key:Any) {
        if key == "types" {
          self.types.setValue(value)
       } else {
          fatalError("No variable found with name \(key)")
       }
    }
    _
  4. これで、self.setEnumValue(".FirstType",forKey:"types")を呼び出すことができます
1
avismara