Использование отражения для задания свойств объекта без использования setValue forKey

В Swift невозможно использовать .setValue(..., forKey: ...)

  • поля с нулевым типом типа Int?
  • свойства, имеющие enum по мере того, как он печатает
  • массив объектов с нулевым значением, например [MyObject?]

Существует одно обходное решение для этого, и это путем переопределения метода setValue forUndefinedKey в самом объекте.

Поскольку я пишу общий инструмент отображения объектов, основанный на отражении. См. EVReflection Я хотел бы как можно больше минимизировать такое ручное сопоставление.

Есть ли другой способ установить эти свойства автоматически?

Обходной путь можно найти в unit test в моей библиотеке здесь Это код:

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.")
        }
    }
}

Ответы

Ответ 1

Я нашел способ обойти это, когда искал решение аналогичной проблемы - что KVO не может установить значение чистого поля протокола Swift. Протокол должен быть отмечен как @objc, что вызвало слишком большую боль в моей базе кода. Обходным путем является поиск Ivar с использованием целевой среды выполнения C, получение смещения поля и установка значения с помощью указателя. Этот код работает на игровой площадке в Swift 2.2:

import Foundation

class MyClass
{
    var myInt: Int?
}

let instance = MyClass()

// Look up the ivar, and it 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)

Примечания:

  • Это, вероятно, довольно хрупко, вы действительно не должны это использовать.
  • Но, возможно, он мог бы жить в тщательно проверенной и обновленной библиотеке отражений, пока Swift не получит правильный API-интерфейс.
  • Это не так далеко от того, что Mirror делает внутри, см. код в Reflection.mm, здесь: https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
  • Тот же метод применяется к другим типам, которые отвергает KVO, но вам нужно быть осторожным, чтобы использовать правильный тип UnsafeMutablePointer. В частности, с протоколами vars, которые составляют 40 или 16 байт, в отличие от простого класса, который имеет 8 байтов (64 бит). См. Майк Эш по теме макета памяти Swift: https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html

Ответ 2

К сожалению, это невозможно сделать в Swift.

KVC - это Objective-C вещь. Опция Pure Swift (комбинация Int и необязательно) не работают с KVC. Лучшее, что можно сделать с Int?, было бы заменить на NSNumber?, и KVC будет работать. Это связано с тем, что NSNumber по-прежнему является классом Objective-C. Это печальное ограничение системы типов.

Но для ваших перечислений все еще есть надежда. Это, однако, не уменьшит количество кодирования, которое вам нужно будет сделать, но оно намного чище и в лучшем случае имитирует KVC.

  • Создайте протокол под названием Settable

    protocol Settable {
       mutating func setValue(value:String)
    }
    
  • Подтвердите свой протокол для протокола

    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")
            }
       }
    }
    
  • Создайте метод: 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)")
       }
    }
    
  • Теперь вы можете позвонить self.setEnumValue(".FirstType",forKey:"types")