swift reflection setvalue

swift - Usando la reflexión para establecer las propiedades del objeto sin usar setValue forKey



reflection (2)

En Swift no es posible usar .setValue(..., forKey: ...)

  • campos de tipo anulable como Int ?
  • Propiedades que tienen una enum como es tipo
  • una matriz de objetos anulables como [MyObject?]

Hay una solución alternativa para esto y es anular el método setValue forUndefinedKey en el propio objeto.

Desde que estoy escribiendo un mapeador de objetos general basado en la reflexión. Ver EVReflection Me gustaría minimizar este tipo de mapeo manual tanto como sea posible.

¿Hay alguna otra manera de establecer esas propiedades automáticamente?

La solución se puede encontrar en una prueba de unidad en mi biblioteca here Este es el código:

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


Desafortunadamente, esto es imposible de hacer en Swift.

KVC es una cosa Objective-C. Los opcionales Pure Swift (combinación de Int y Opcional) no funcionan con KVC. Lo mejor que hacer con Int? sería reemplazar con NSNumber? y KVC funcionará. Esto se debe a que NSNumber sigue siendo una clase Objective-C. Esta es una triste limitación del sistema de tipos.

Para sus enumeraciones, sin embargo, todavía hay esperanza. Sin embargo, esto no reducirá la cantidad de codificación que tendría que hacer, pero es mucho más limpio y, en el mejor de los casos, imita el KVC.

  1. Crea un protocolo llamado Settable

    protocol Settable { mutating func setValue(value:String) }

  2. Haga que su enumeración confirme al protocolo

    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. Cree un método: 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. Ahora puede llamar self.setEnumValue(".FirstType",forKey:"types")

Encontré una forma de evitar esto cuando estaba tratando de resolver un problema similar: que KVO no puede establecer el valor de un campo de protocolo Swift puro. El protocolo debe estar marcado como @objc, lo que causó mucho dolor en mi base de código. La solución es buscar el Ivar utilizando el tiempo de ejecución C objetivo, obtener el desplazamiento de campo y establecer el valor utilizando un puntero. Este código funciona en un patio en 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)

Notas:

Edición: ahora hay un marco denominado Runtime en https://github.com/wickwirew/Runtime que proporciona un modelo Swift puro del diseño de memoria Swift 4+, lo que le permite calcular de forma segura el equivalente de ivar_getOffset sin invocar el tiempo de ejecución de Obj C . Esto permite configurar propiedades como esta:

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

Este es probablemente un buen camino hacia adelante hasta que la capacidad equivalente se convierta en parte de Swift.