Extracting Properties from Custom NSControl

My project has dozens of custom NSControls, in some cases the subclasses are many levels deep, with each level adding custom properties. I need a way to create a function where I pass one of these controls and it returns a dictionary of all its properies and their current values. It does not need to be read/write although that would be nice.

I have tried to use MIRROR, but that only returns the top subclass.

I wish to avoid manually creating dictionaries for each control as this duplicates storage requirements (after all each control already had the values in it), please it make scalablity more difficult.

This is to allow easy population of a Property Inspector Pane in a custom IDE

import Cocoa

import ObjectiveC.runtime

func propertiesDictionary(of object: NSObject) → [String: Any] {

var result: \[String: Any\] = \[:\]

var currentClass: AnyClass? = type(of: object)

while let cls = currentClass,

      cls != NSObject.self {

    var count: UInt32 = 0

    if let propertyList = class_copyPropertyList(cls, &count) {

        for i in 0..<Int(count) {

            let property = propertyList\[i\]

            guard let cname = property_getName(property) else {

                continue

            }

            let name = String(cString: cname)

            // évite doublons héritage

            if result\[name\] != nil {

                continue

            }

            // lecture via KVC

            if let value = object.value(forKey: name) {

                result\[name\] = value

            } else {

                result\[name\] = NSNull()

            }

        }

        free(propertyList)

    }

    currentClass = class_getSuperclass(cls)

}

return result

}

class MyBaseControl: NSControl {

@objc dynamic var borderSize: CGFloat = 2

@objc dynamic var customTitle: String = "Hello"

}

class MyButton: MyBaseControl {

@objc dynamic var isHighlightedCustom: Bool = true

@objc dynamic var iconName: String = "star"

}

let btn = MyButton()

let dict = propertiesDictionary(of: btn)

print(dict)

ChatGPT reply, not tested…

TBH Mirror sure sounds like the right API

When you say

I have tried to use MIRROR, but that only returns the top subclass.

do you mean “the most derived subclass” ?
ie/

 Super 1
    Sub 1 (super is super 1 )
      Sub 2 (super is sub 1)

And if you use mirror on sub 2 you ONY get properties of sub 2 and none of the inherited ones from sub 1 or super 1 ?

EDIT : I found this but I cant get it to work in the online Swift fiddles

that is correct
and note : the other reply also only returns one level, and even then not every thing

See my edit

I just dunno Swift well enough to know why what I found wont work for me but it “seems” plausible

Try this:

class ct1 : NSControl

{

var results: \[String: Any\] = \[:\]

override init(frame frameRect: NSRect)

{

    super.init(frame: frameRect)

    

    setup()

}



required init?(coder: NSCoder) {

    fatalError("init(coder:) has not been implemented")

}



func setup()

{

    self.borderSize = 2

    self.customTitle = "HELLO"

}



var borderSize: CGFloat = 0

{

    willSet

    {

        results\["borderSize"\] = newValue

    }

}



var customTitle: String = ""

{

    willSet

    {

        results\["customTitle"\] = newValue

    }

}

}

class ct2 : ct1

{

override init(frame frameRect: NSRect)

{

    super.init(frame: frameRect)

    

    setup()

}



required init?(coder: NSCoder) {

    fatalError("init(coder:) has not been implemented")

}



override  func setup()

{

    super.setup()

    self.test = 2

}

var test: CGFloat = 0

{

    willSet

    {

        results\["test"\] = newValue

    }

}

}

   let a0 = ct1()

    let a1 = ct2()

   print(a0.results)

    print(a1.results)

technically that works, but requires modifications to every propertie (willset) at every level of every control. That is not possible, for multiple reason, one of which it ignores all properties that belong to the base control (ie. native properties) and duplicates the backing storage required (each control has the values in the control, and now requires a duplicate stored in “results”

I was hoping for a solution that extracts the value already in the control,

But thanks

Much longer example with longer class hierarchy

Seems to work just fine to use Mirror


class Kingdom 
{
    var kingdomName  : String = "Animalia"
}
class Phylum : Kingdom
{
    var phylumName  : String = "Chordata"
}
class Class : Phylum
{
    var cclassName : String  = "Mammalia"
}
class Order : Class
{
    var orderName : String = "Carnivora"
}
class Family : Order
{
    var familyName  : String  = "Canidae"
}
class Genus : Family
{
    var genusName : String = "Canis"
}
class Species : Genus
{
    var speciesName : String = "Canis Familiaris"
}
class Dog: Species {
    var breedName : String = "Labrador"
}

let myDog = Dog()

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