lehn0058
lehn0058

Reputation: 20257

Mirror not working in Swift when iterating through children of an Objective-C object

I have a very strange issue related to using Mirror in Swift. I am iterating over all of the properties in a class called Test:

let test = Test()
let mirror = Mirror(reflecting: test)
for i in mirror.children {
    ...
}

If I implement my Test class in Swift, I have three values in children that is iterated over:

class Test: NSObject {
    var device: NSNumber!
    var version: NSNumber!
    var application: NSString!
}

However, if I implement the Test class in Objective C, I get zero children:

@interface ObjectCTest : NSObject

@property (nonatomic, strong) NSNumber *device;
@property (nonatomic, strong) NSNumber *version;
@property (nonatomic, strong) NSString *application;

@end

Does anyone know what might be going on? I'm starting to think it might be something to do with Xcode project settings, as I have other projects that this works for. Any suggestions are appreciated!

Upvotes: 7

Views: 2277

Answers (3)

Lex
Lex

Reputation: 310

protocol Reflectable: AnyObject {
    func reflected() -> [String: Any?]
}

extension Reflectable {
    
    func reflected() -> [String: Any?] {
        let mirror = Mirror(reflecting: self)
        var dict: [String: Any?] = [:]
        for child in mirror.children {
            guard let key = child.label else {
                continue
            }
            dict[key] = child.value
        }
        return dict
    }
    
    var reflectedString: String {
        let reflection = reflected()
        var result = String(describing: self)
        result += " { \n"
        for (key, val) in reflection {
            result += "\t\(key): \(val ?? "null")\n"
        }
        return result + "}"
    }
    
}

extension Reflectable where Self: NSObject {
    
    func reflected() -> [String : Any?] {
        
        var count: UInt32 = 0
        
        guard let properties = class_copyPropertyList(Self.self, &count) else {
            return [:]
        }
        
        var dict: [String: Any] = [:]
        for i in 0..<Int(count) {
            let name = property_getName(properties[i])
            guard let nsKey = NSString(utf8String: name) else {
                continue
            }
            let key = nsKey as String
            guard responds(to: Selector(key)) else {
                continue
            }
            dict[key] = value(forKey: key)
        }
        free(properties)
        
        return dict
    }
    
}

Upvotes: 2

funct7
funct7

Reputation: 3601

It's seems like the other answer is right about NSObject classes not being able to use mirror.

However, there is a better way to get properties: - valueForKeyPath: or value(forKeyPath:)

Upvotes: 1

Christian Abella
Christian Abella

Reputation: 5797

Mirror does not seem to work with Objective-C classes. But, you can use the class_copyPropertyList function to retrieve all the properties from an Objective-C class.

var outCount : UInt32 = 0
let properties = class_copyPropertyList(ObjectCTest.self, &outCount)

print(outCount)

for i : UInt32 in 0..<outCount
{
  let strKey : NSString? = NSString(CString: property_getName(properties[Int(i)]), encoding: NSUTF8StringEncoding)

  let attrs : NSString? = NSString(CString: property_getAttributes(properties[Int(i)]), encoding: NSUTF8StringEncoding)

  print(strKey)
  print(attrs)
}

Upvotes: 11

Related Questions