Swift 2.0 Get Mirrored Superclass Properties

后端 未结 6 1799
盖世英雄少女心
盖世英雄少女心 2020-12-20 16:16

I need to get the properties of my class as a dictionary. For simplicity, I created a protocol which has a default implementation as follows:

protocol ListsP         


        
相关标签:
6条回答
  • 2020-12-20 16:52

    You can simply loop through all the superclassMirrors get the properties. It doesn't matter how many layers there are.

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

    0 讨论(0)
  • 2020-12-20 17:00

    My solution is to use

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

    Instead of

    Mirror(representing: self)
    

    properties is a DictionnaryLiteral object containing the properties I want to be mirrored.

    0 讨论(0)
  • 2020-12-20 17:04

    One possible solution would be to implement toDictionary() as a method of Mirror itself, so that you can traverse recursively to the superclass 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
        }
    }
    

    and then use that to implement the protocol extension method:

    extension ListsProperties {
        func toDictionary() -> [String: AnyObject] {
            return Mirror(reflecting: self).toDictionary()
        }
    }
    
    0 讨论(0)
  • 2020-12-20 17:04

    By using a protocol, you require that all the superclasses implement such a protocol to be able to use the function.

    I would use a helper class, so that you can pass any object to it.

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

    Then you can use it like

    let props = ListsPropertiesHelper.toDictionary(someObject)
    

    If you still want to be able to write

    let props = someObject.toDictionary()
    

    you can implement the protocol with the extension calling the helper class.

    0 讨论(0)
  • 2020-12-20 17:06

    Heres a full version of toDictionary above.

    It uses the method where toDictionary() is an extension on Mirror

    I added recursion so it can handle any depth of class hierarchy.

    It get mirror.children on self >> self.super >> self.super.super .. etc

    • You can also tell it to stop at/not include a class e.g. "NSObject" "UIViewController"

    I plan to use this myself to convert my Model objects to CKRecord.

    • Adv: the model object itself doesnt need to know about CKRecord

    Helper Method

    The "helper method" above would work too but as that just uses methods in Mirror adding all the code as an extension to Mirror makes logical sense too.

    Just paste all the code below in a Playground and should work.

    Execution start at bottom block of code see:

    //EXECUTION STARTS HERE

        //: 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)
    

    OUTPUT

    ======================================================================
    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
    
    0 讨论(0)
  • 2020-12-20 17:17

    Here is my Implementation

    • supports chain of inheritance
    • supports nested objects
    • unwraps optionals if .some
    • supports enums with rawValue
    • supports arrays

    .

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

    Usage

    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 讨论(0)
提交回复
热议问题