2015-11-24 5 views
3

Мне нужно получить свойства моего класса в качестве словаря. Для простоты, я создал протокол, который имеет реализацию по умолчанию следующим образом:Swift 2.0 Получить зеркальные свойства суперкласса

protocol ListsProperties{ 
    func toDictionary() -> [String: AnyObject] 
} 

extension ListsProperties{ 
    func toDictionary() -> [String: AnyObject] { 
     let mirrored_object = Mirror(reflecting: self) 
     var dict = [String: AnyObject]() 
     for (_, attr) in mirrored_object.children.enumerate() { 
      if let propertyName = attr.label as String! { 
       dict[propertyName] = attr.value as? AnyObject 
      } 
     }  

     return dict 
    } 
} 

Мои классы могут соответствовать этому протоколу и будет иметь метод ToDictionary() доступен. Однако это не работает, если я использую метод в подклассе, поскольку он будет производить только свойства, определенные в подклассе, и игнорировать свойства родительского суперкласса.

В идеале я мог бы найти способ вызова метода toDictionary() на зеркальном суперклассе, поскольку тогда он вызывается toDictionary() на своем собственном суперклассе, и компилятор говорит, что зеркало суперкласса не соответствует протоколу, хотя класс, который он зеркалирует.

следующие виды работ, но только тогда, когда есть только один суперкласс так не хватает:

func toDictionary() -> [String: AnyObject] { 
    let mirrored_object = Mirror(reflecting: self) 
    var dict = [String: AnyObject]() 
    for (_, attr) in mirrored_object.children.enumerate() { 
     if let propertyName = attr.label as String! { 
      dict[propertyName] = attr.value as? AnyObject 
     } 
    } 

    // This is an issue as it limits to one subclass 'deep' 
    if let parent = mirrored_object.superclassMirror(){ 
     for (_, attr) in parent.children.enumerate() { 
      if let propertyName = attr.label as String!{ 
       if dict[propertyName] == nil{ 
        dict[propertyName] = attr.value as? AnyObject 
       } 
      } 
     } 
    } 

    return dict 
} 

Любые идеи о том, как я могу изменить реализацию по умолчанию ToDictionary(), чтобы включить суперкласса атрибуты (и атрибуты любых суперклассов суперкласса и т. д.)?

ответ

11

Одним из возможных решений было бы реализовать toDictionary() как метод самого Mirror, так что вы можете перемещаться рекурсивно суперкласса зеркала:

extension Mirror { 

    func toDictionary() -> [String: AnyObject] { 
     var dict = [String: AnyObject]() 

     // Properties of this instance: 
     for attr in self.children { 
      if let propertyName = attr.label { 
       dict[propertyName] = attr.value as? AnyObject 
      } 
     } 

     // Add properties of superclass: 
     if let parent = self.superclassMirror() { 
      for (propertyName, value) in parent.toDictionary() { 
       dict[propertyName] = value 
      } 
     } 

     return dict 
    } 
} 

, а затем использовать его для реализации способа расширения протокола:

extension ListsProperties { 
    func toDictionary() -> [String: AnyObject] { 
     return Mirror(reflecting: self).toDictionary() 
    } 
} 
+0

отличный ответ, спасибо! – ExoticChimp

1

Используя протокол, вы требуете, чтобы все суперклассы применяли такой протокол, чтобы иметь возможность использовать эту функцию.

Я бы использовал вспомогательный класс, чтобы вы могли передать ему любой объект.

class ListsPropertiesHelper { 
    static func toDictionary(mirrored_object: Mirror) -> [String: AnyObject] { 
     var dict = [String: AnyObject]() 
     for (_, attr) in mirrored_object.children.enumerate() { 
      if let propertyName = attr.label as String! { 
       dict[propertyName] = attr.value as? AnyObject 
      } 
     } 
     if let parent = mirrored_object.superclassMirror() { 
      let dict2 = toDictionary(parent) 
      for (key,value) in dict2 { 
       dict.updateValue(value, forKey:key) 
      } 
     } 

     return dict 
    } 

    static func toDictionary(obj: AnyObject) -> [String: AnyObject] { 
     let mirrored_object = Mirror(reflecting: obj) 
     return self.toDictionary(mirrored_object) 
    } 
} 

Затем вы можете использовать его как

let props = ListsPropertiesHelper.toDictionary(someObject) 

Если вы все еще хотите, чтобы иметь возможность написать

let props = someObject.toDictionary() 

можно реализовать протокол с расширением вызывающего вспомогательного класса.

1

Heres полная версия toDictionary выше.

Он использует метод, где ToDictionary() является расширением на зеркале

Я добавил рекурсии так, он может обрабатывать любую глубину иерархии классов.

Это получить mirror.children на себя >> self.super >> self.super.super .. и т.д.

  • Вы также можете сказать ему, чтобы остановиться/не включают класс, например, «NSObject» «UIViewController»

Я планирую использовать это сам, чтобы преобразовать свои объекты модели в CKRecord.

  • Adv: сама модель объекта оленья кожа нужно знать о CKRecord

вспомогательный метод

«вспомогательный метод» выше, будет работать тоже, но как только использует методы в зеркале при добавлении весь код как расширение для Mirror также делает логический смысл.

Просто вставьте весь код ниже на игровой площадке и должен работать.

начало выполнения на нижнем блоке код смотрите:

// ИСПОЛНЕНИЕ НАЧИНАЕТСЯ ЗДЕСЬ

//: Playground - noun: a place where people can play 

import UIKit 

//http://stackoverflow.com/questions/33900604/swift-2-0-get-mirrored-superclass-properties 
//http://appventure.me/2015/10/24/swift-reflection-api-what-you-can-do/ 

//------------------------------------------------------------------------------------------------ 
//TEST DATA STRUCTURES 
//------------------------------------------------------------------------------------------------ 
//Subclasses of NSObject 
//super class of ClassASwift is NSObject 
//But If we scan this tree for ivars then we dont want to include NSObject 
//------------------------------------------------------------------------------------------------ 
class ClassNSObjectA : NSObject{ 
    var textA = "Some textA" 
    var numA = 1111 
    var dateA = NSDate() 
    var arrayA = ["A1", "A2"] 
} 

class ClassNSObjectB : ClassNSObjectA{ 
    var textB = "Some textB" 
    var numB = 1111 
    var dateB = NSDate() 
    var arrayB = ["B1", "B2"] 
} 

class ClassNSObjectC : ClassNSObjectB{ 
    var textC = "Some textC" 
    var numC = 1111 
    var dateC = NSDate() 
    var arrayC = ["C1", "C2"] 
} 

//------------------------------------------------------------------------------------------------ 
//Swift only object tree - no Root 
//super class of ClassASwift is nil 
//------------------------------------------------------------------------------------------------ 
class ClassASwift { 
    var textA = "A Swift" 
    var numA = 1111 
    var dateA = NSDate() 
    var arrayA = ["A1Swift", "A2Swift"] 
} 

class ClassBSwift : ClassASwift{ 
    var textB = "B Swift" 
    var numB = 1111 
    var dateB = NSDate() 
    var arrayB = ["B1Swift", "B2Swift"] 
} 

class ClassCSwift : ClassBSwift{ 
    var textC = "C Swift" 
    var numC = 1111 
    var dateC = NSDate() 
    var arrayC = ["C1Swift", "C2Swift"] 
} 

extension Mirror { 

    //------------------------------------------------------------------------------------------------ 
    //How to scan a tree hierarchy of classes 
    //------------------------------------------------------------------------------------------------ 
    /* 
    TASK: we want to walk the tree from a given instance back up to the root and dump the ivars using Swift reflection. 

    It came from a need to convert any object to CKRecord and have clean model objects for RealmDB. 

    with the following issues: 

    Some objects are Swift only - so no root object. 
    So we scan up the tree recursively till super is nil 
    Some objects have NSObject at the root. 
    We scan up the tree and extract the ivar but we dont want to include the ivars for NSObject 

    We wanted to keep our Model object as Swift as possible. 
    Swift only 
    You can use all the NSObject refection apis but only on NSObject classes so we need to only use Swift Reflection Mirror(reflecting:) 
    ------ 
    We could add this objectToDictionary() method as a func on 
    extension NSObject { 
    scan() 
    } 
    But this only work for subclasses of NSObject 
    ------ 
    Youre not allowed to have an extension on AnyObject so you cant create a swift method objectToDictionary() common to ALL Swift or NSObject Objects 

    Workaround: Have a common parent for our Model objects? 
    Change 
    class ClassA : NSObject{...} 
    class ClassASwift {...} 

    class ClassA : ParentOfNSObject{...} 
    class ClassASwift : ParentOfSwiftObject{...} 
    but what if I want to be able to use objectToDictionary() on ALL Swift or NSObject objects 
    ------ 

    -- Answer -- 
    The purpose of this method is to scan an object hierarchy and convert it to a Dictionary or CKRecord etc. 

    To scan an instance we need access to Mirror and to get the objects ivars (mirror.children) and its super class ivars 

    Answer was to add our extension to Mirror rather than to the object itself (dont add this method as extension to NSObject or some ParentOfSwiftObject) 

    This is similar to a Third method - a helper class 
    the helperclas will just use Mirror 
    the method here just combines the Mirror recursion with my extract code 
    http://stackoverflow.com/questions/33900604/swift-2-0-get-mirrored-superclass-properties 
    */ 

    func objectToDictionary(stopAtParentClassName : String?) -> [String: AnyObject]{ 

     //this is extension on Mirror - self is of type Mirror not the object we're analyzing 
     let dict: [String: AnyObject] = objectToDictionaryForMirror(self, stopAtParentClassName: stopAtParentClassName) 
     return dict 

    } 

    func objectToDictionaryForMirror(mirror: Mirror, stopAtParentClassName : String?) -> [String: AnyObject]{ 
     var dictOfIVars = [String: AnyObject]() 

     let classname = "\(mirror.subjectType)" 
     print("classname:\(classname)") 

     //e.g. if stopAtParentClassName is nil or "NSObject" 
     //stopAtParentClassName can be nil or (set and "NSObject" OR (set and not "NSbject") 
     let stopAtParentClassName_ = stopAtParentClassName ?? "" 

     if (classname == stopAtParentClassName_){ 
      //note : not really an issue for mirror.children as NSObject has none but you can parse for other reflected elements 
      print("NSObject found - skipNSObjectRoot is true - SKIP this class - return empty dictionary") 
     }else{ 

      //------------------------------------------------------------------------------------------------ 
      //Not NSObject or skipNSObjectRoot is false 
      //------------------------------------------------------------------------------------------------ 
      //ITERATE OVER PROPERTIES 
      //I included two ways to do this 
      //v1 - dumps the properties 
      //v2 - append then to a dict which is recursively appended them to outer dict 
      //just remove which ever one you want 
      //------------------------------------------------------------------------------------------------ 
      //v1 
      //public typealias Child = (label: String?, value: Any) 
      //note its mirror.children not self.children 
      //dont use self.children : as you recursively call this method self.children will only reference the base instance 
      //mirror is the recursive mirror so scanMirror(self) >> scanMirror(self.super) >> scanMirror(self.super.super) .. stop when super is nil 
      for case let (label?, value) in mirror.children { 
       print("PROP [\(classname)] label:[\(label)] value:[\(value)]") 
      } 

      //---------------------------------------------- 
      //v2 
      // Properties of this instance: 
      //self <=> Mirror 
      for property in mirror.children { 
       if let propertyName = property.label { 
        dictOfIVars[propertyName] = property.value as? AnyObject 
       } 
      } 

      //------------------------------------------------------------------------------------------------ 
      //Mirror.children only returns ivar of current class 
      //you need to walk up the tree to get all inherited properties 

      //Swift object hierarchy has no root - superclassMirror() will become nil - and recursion will stop 
      //NSObject object hierarchy has root NSObject - superclassMirror() will inlcude this unless stopAtParentClassName is "NSObject" 
      //------------------------------------------------------------------------------------------------ 
      if let superclassMirror = mirror.superclassMirror() { 
       let dictOfIVarsForSuperClass : [String: AnyObject] = objectToDictionaryForMirror(superclassMirror, stopAtParentClassName: stopAtParentClassName) 

       //merge 
       dictOfIVars.merge(dictOfIVarsForSuperClass) 


      }else{ 
       print("class has no superclassMirror") 
      } 
      //--------------------------------------------------------------------- 
     } 
     return dictOfIVars 
    } 
} 

//Used to recursively merge superclass ivar dictionary to subclasses ivar dictionary 
extension Dictionary{ 

    // https://github.com/terhechte/SourceKittenDaemon/blob/83dc62d3e9157b69530ed93b82b5aae9cd225427/SourceKittenDaemon/Extensions/Dictionary%2B.swift 
    mutating func merge(dictionary: Dictionary<Key, Value>) { 
     for (key, value) in dictionary { 
      self[key] = value 
     } 
    } 
} 



func dumpDictionary(anyObject: AnyObject, stopAtParentClassName: String?){ 

    let mirror = Mirror(reflecting: anyObject) 
    //--------------------------------------------------------------------- 
    //SCAN HIERARCHY - return info as a dict 
    //--------------------------------------------------------------------- 

    let dictOfIVars = mirror.objectToDictionary(stopAtParentClassName) 
    print("*****************************************************") 
    print("*********** SCAN COMPLETE - DUMP DICT INFO***********") 
    print("*****************************************************") 

    print("dictOfIVars:\r\(dictOfIVars)") 

    //------------------------------------------------------------------------------------------------ 
    //DEBUGGING - dump the returned dict 
    //------------------------------------------------------------------------------------------------ 
    print("KEYS:") 
    //print("dictOfIVars.keys:\(dictOfIVars.keys)") 
    for key in dictOfIVars.keys.sort(){ 
     print(key) 
    } 
    //------------------------------------------------------------------------------------------------ 
    print("dictOfIVars.keys.count:\(dictOfIVars.keys.count)") 
    //------------------------------------------------------------------------------------------------ 

} 


//EXECUTION STARTS HERE IF YOU PASTE IN TO PLAYGROUND 




print("======================================================================") 
print("START TESTS - open console below ") 
print("======================================================================") 

//------------------------------------------------------------------------------------------------ 
//NSObject class hierarchy 
//------------------------------------------------------------------------------------------------ 

print("") 
print("=====================================================================================================================") 
print("========== TEST 1: recursively iterate up tree of NSObject subclasses - include root object 'NSObject' in scan") 
print("=====================================================================================================================") 

let instanceC : ClassNSObjectB = ClassNSObjectC() 
//Dont include NSObject root in parse 
dumpDictionary(instanceC, stopAtParentClassName: "NSObject") 

print("") 
print("=====================================================================================================================") 
print("========== TEST 2: recursively iterate up tree of NSObject subclasses - DO NOT include root object 'NSObject' in scan") 
print("=====================================================================================================================") 
//Do include NSObject 
dumpDictionary(instanceC, stopAtParentClassName: nil) 

//note were only dumping mirror.children in this example and NSObject doesnt have any but added for completeness. 
//could dump all sub classes of UIViewController - but not include the UIViewController class ivars 

//------------------------------------------------------------------------------------------------ 
//Switft class hierarchy - no root class 
//------------------------------------------------------------------------------------------------ 
print("") 
print("======================================================================================================================") 
print("========== TEST 3: recursively iterate up tree of Swift subclasses") 
print("======================================================================================================================") 
let classBSwift : ClassBSwift = ClassCSwift() 
dumpDictionary(classBSwift, stopAtParentClassName: nil) 

ВЫВОД

====================================================================== 
START TESTS - open console below 
====================================================================== 

===================================================================================================================== 
========== TEST 1: recursively iterate up tree of NSObject subclasses - include root object 'NSObject' in scan 
===================================================================================================================== 
classname:ClassNSObjectC 
PROP [ClassNSObjectC] label:[textC] value:[Some textC] 
PROP [ClassNSObjectC] label:[numC] value:[1111] 
PROP [ClassNSObjectC] label:[dateC] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassNSObjectC] label:[arrayC] value:[["C1", "C2"]] 
classname:ClassNSObjectB 
PROP [ClassNSObjectB] label:[textB] value:[Some textB] 
PROP [ClassNSObjectB] label:[numB] value:[1111] 
PROP [ClassNSObjectB] label:[dateB] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassNSObjectB] label:[arrayB] value:[["B1", "B2"]] 
classname:ClassNSObjectA 
PROP [ClassNSObjectA] label:[textA] value:[Some textA] 
PROP [ClassNSObjectA] label:[numA] value:[1111] 
PROP [ClassNSObjectA] label:[dateA] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassNSObjectA] label:[arrayA] value:[["A1", "A2"]] 
classname:NSObject 
NSObject found - skipNSObjectRoot is true - SKIP this class - return empty dictionary 
***************************************************** 
*********** SCAN COMPLETE - DUMP DICT INFO*********** 
***************************************************** 
dictOfIVars: 
["numB": 1111, "numC": 1111, "arrayB": (
    B1, 
    B2 
), "dateA": 2016-02-17 13:37:35 +0000, "dateC": 2016-02-17 13:37:35 +0000, "dateB": 2016-02-17 13:37:35 +0000, "arrayC": (
    C1, 
    C2 
), "textC": Some textC, "arrayA": (
    A1, 
    A2 
), "textA": Some textA, "numA": 1111, "textB": Some textB] 
KEYS: 
arrayA 
arrayB 
arrayC 
dateA 
dateB 
dateC 
numA 
numB 
numC 
textA 
textB 
textC 
dictOfIVars.keys.count:12 

===================================================================================================================== 
========== TEST 2: recursively iterate up tree of NSObject subclasses - DO NOT include root object 'NSObject' in scan 
===================================================================================================================== 
classname:ClassNSObjectC 
PROP [ClassNSObjectC] label:[textC] value:[Some textC] 
PROP [ClassNSObjectC] label:[numC] value:[1111] 
PROP [ClassNSObjectC] label:[dateC] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassNSObjectC] label:[arrayC] value:[["C1", "C2"]] 
classname:ClassNSObjectB 
PROP [ClassNSObjectB] label:[textB] value:[Some textB] 
PROP [ClassNSObjectB] label:[numB] value:[1111] 
PROP [ClassNSObjectB] label:[dateB] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassNSObjectB] label:[arrayB] value:[["B1", "B2"]] 
classname:ClassNSObjectA 
PROP [ClassNSObjectA] label:[textA] value:[Some textA] 
PROP [ClassNSObjectA] label:[numA] value:[1111] 
PROP [ClassNSObjectA] label:[dateA] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassNSObjectA] label:[arrayA] value:[["A1", "A2"]] 
classname:NSObject 
class has no superclassMirror 
***************************************************** 
*********** SCAN COMPLETE - DUMP DICT INFO*********** 
***************************************************** 
dictOfIVars: 
["numB": 1111, "numC": 1111, "arrayB": (
    B1, 
    B2 
), "dateA": 2016-02-17 13:37:35 +0000, "dateC": 2016-02-17 13:37:35 +0000, "dateB": 2016-02-17 13:37:35 +0000, "arrayC": (
    C1, 
    C2 
), "textC": Some textC, "arrayA": (
    A1, 
    A2 
), "textA": Some textA, "numA": 1111, "textB": Some textB] 
KEYS: 
arrayA 
arrayB 
arrayC 
dateA 
dateB 
dateC 
numA 
numB 
numC 
textA 
textB 
textC 
dictOfIVars.keys.count:12 

====================================================================================================================== 
========== TEST 3: recursively iterate up tree of Swift subclasses 
====================================================================================================================== 
classname:ClassCSwift 
PROP [ClassCSwift] label:[textC] value:[C Swift] 
PROP [ClassCSwift] label:[numC] value:[1111] 
PROP [ClassCSwift] label:[dateC] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassCSwift] label:[arrayC] value:[["C1Swift", "C2Swift"]] 
classname:ClassBSwift 
PROP [ClassBSwift] label:[textB] value:[B Swift] 
PROP [ClassBSwift] label:[numB] value:[1111] 
PROP [ClassBSwift] label:[dateB] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassBSwift] label:[arrayB] value:[["B1Swift", "B2Swift"]] 
classname:ClassASwift 
PROP [ClassASwift] label:[textA] value:[A Swift] 
PROP [ClassASwift] label:[numA] value:[1111] 
PROP [ClassASwift] label:[dateA] value:[2016-02-17 13:37:35 +0000] 
PROP [ClassASwift] label:[arrayA] value:[["A1Swift", "A2Swift"]] 
class has no superclassMirror 
***************************************************** 
*********** SCAN COMPLETE - DUMP DICT INFO*********** 
***************************************************** 
dictOfIVars: 
["numB": 1111, "numC": 1111, "arrayB": (
    B1Swift, 
    B2Swift 
), "dateA": 2016-02-17 13:37:35 +0000, "dateC": 2016-02-17 13:37:35 +0000, "dateB": 2016-02-17 13:37:35 +0000, "arrayC": (
    C1Swift, 
    C2Swift 
), "textC": C Swift, "arrayA": (
    A1Swift, 
    A2Swift 
), "textA": A Swift, "numA": 1111, "textB": B Swift] 
KEYS: 
arrayA 
arrayB 
arrayC 
dateA 
dateB 
dateC 
numA 
numB 
numC 
textA 
textB 
textC 
dictOfIVars.keys.count:12 
3

Вы можете просто перебрать все superclassMirror s получить свойства. Неважно, сколько слоев есть.

var mirror: Mirror? = Mirror(reflecting: child) repeat { for property in mirror!.children { print("property: \(property)") } mirror = mirror?.superclassMirror() } while mirror != nil

0

Вот моя реализация

  • поддерживает цепь наследования
  • поддерживает вложенные объекты
  • разворачивает УСТРОЙСТВА если .some
  • поддерживает перечисления с RawValue
  • поддерживает массивы

.

public protocol Serializable { 
    func serialize() -> [String: Any] 
} 

extension Serializable { 
    public func serialize() -> [String: Any] { 
     var result = [String: Any]() 
     var enumeratingMirror: Mirror? = Mirror(reflecting: self) 

     while true { 
      guard let mirror = enumeratingMirror else { break } 

      for child in mirror.children { 
       guard let label = child.label else { continue } 

       switch child.value { 
       case let serializable as Serializable: 
        if case .some(let value) = optional { 
         result[label] = value 
        } 

       case let rawRepresentable as RawRepresentable: 
        result[label] = rawRepresentable.getValueAsAny() 

       case let optional as Optional<Any>: 
        if case .some(let value) = optional { 
         result[label] = value 
        } 

       default: 
        result[label] = child.value 
       } 
      } 

      enumeratingMirror = mirror.superclassMirror 
     } 

     return result 
    } 
} 

extension Collection where Iterator.Element: Serializable { 
    public func serialize() -> [[String: Any]] { 
     return map { $0.serialize() } 
    } 
} 

extension RawRepresentable { 
    public func getValueAsAny() -> Any { 
     return rawValue 
    } 
} 

Использование

class BaseUser: Serializable { let id: Int = 1 } 
class User: BaseUser { let name: String = "Aryan" } 

let user = User() 
print(user.serialize()) // {"id": 1, "name": "Aryan"} 
print([user].serialize()) // [{"id": 1, "name": "Aryan"}] 
0

Мое решение заключается в использовании

Mirror(self, children: properties, ancestorRepresentation: Mirror.AncestorRepresentation.generated) 

Вместо

Mirror(representing: self) 

свойств объекта DictionnaryLiteral, содержащий свойства, которые я хочу быть зеркальным.