Использование отражения для задания свойств объекта без использования 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")