Swift 2.0获取镜像超类属性

10

我需要将我的类属性转换成字典形式。为了简化,我创建了一个协议,并提供了以下默认实现:

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()实现,以便包括超类属性(以及任何超类的超类等属性)?

6个回答

17
一种可能的解决方案是将 toDictionary() 实现为 Mirror 本身的一个方法,这样您可以递归遍历到超类镜像:

一种可能的解决方案是将 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()
    }
}

5
你可以简单地循环遍历所有的superclassMirror来获取属性。无论有多少层都没有关系。 var mirror: Mirror? = Mirror(reflecting: child) repeat { for property in mirror!.children { print("property: \(property)") } mirror = mirror?.superclassMirror() } while mirror != nil

1
这里是上面的toDictionary的完整版本。
它使用toDictionary()方法作为Mirror的扩展。
我添加了递归,使其可以处理任何深度的类层次结构。
它在self >> self.super >> self.super.super ..等上获取mirror.children
您还可以告诉它停止/不包括某个类,例如 "NSObject" "UIViewController"。
我计划将其用于将我的Model对象转换为CKRecord。
优点:模型对象本身不需要知道CKRecord。
“辅助方法”如上所述也可以工作,但是由于那只使用Mirror中的方法,因此将所有代码添加为Mirror的扩展也是有道理的。
只需将下面的所有代码粘贴到Playground中即可运行。
在代码的底部块处开始执行,请参见:
// EXECUTION STARTS HERE
    //: Playground - noun: a place where people can play

import UIKit

//https://dev59.com/U1sX5IYBdhLWcg3wY-gh
//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
    https://dev59.com/U1sX5IYBdhLWcg3wY-gh
    */

    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

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()

你可以通过调用帮助类来实现该协议。

0

我的解决方案是使用

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

不要使用

Mirror(representing: self)

properties 是一个 DictionnaryLiteral 对象,其中包含我想要镜像的属性。


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"}]

在以下代码的某一行出现了错误:if case .some(let value) = optional // 错误为 - 使用了未声明的标识符以及case let rawRepresentable as RawRepresentable: // 错误为:协议 'RawRepresentable' 只能用作泛型约束,因为它具有 Self 或关联类型。 - Rajinderpal Singh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接