Bokeh Bokeh - 26 days ago 8
Swift Question

Reading values from CFPropertyList

I am trying to read network interface configuration on macOS using the System Configuration framework with Swift. I am getting a

CFPropertyList
which actually is a
CFDictionary
. Each dictionary entry contains a
CFArray
. With
CFShow
I was able to verify that I got the expected data. What I am actually not able to do is to access the dictionary values. With
CFGetTypeId
I am not getting the same value as returned by
CFArrayGetTypeID()
.

Here is what I tried to get the IP address from the property list:

import SystemConfiguration

func readIP() {

let cfName = "Demo" as CFString
let dynamicStore = SCDynamicStoreCreate(nil, cfName, nil, nil)

let key = "State:/Network/Interface/en0/IPv4" as CFString

guard let plist: CFPropertyList = SCDynamicStoreCopyValue(dynamicStore, key) else {return}

print("CFShow(plist):")
CFShow(plist)

print("Key: \(key):")
print("Value: \(plist)")

let typeIdPList = CFGetTypeID(plist)
let typeIdDictionary = CFDictionaryGetTypeID()

print("typeIdPList: \(typeIdPList)")
print("typeIdDictionary: \(typeIdDictionary)")

guard typeIdPList == typeIdDictionary else {return}

let cfDict: CFDictionary = plist as! CFDictionary

let rawPointerToKeyAddresses = Unmanaged.passUnretained(kSCPropNetIPv4Addresses).toOpaque()
var rawPointerToValue: UnsafeRawPointer?
guard CFDictionaryGetValueIfPresent(cfDict, rawPointerToKeyAddresses, &rawPointerToValue) == true else {return}

let anyRef: CFTypeRef = rawPointerToValue as CFTypeRef

print("Value:")
CFShow(anyRef)

let typeIdValue = CFGetTypeID(anyRef)
let typeIdArray = CFArrayGetTypeID()

print("typeIdValue: \(typeIdValue)")
print("typeIdArray: \(typeIdArray)")

let cfArray: CFArray = anyRef as! CFArray
let typeId = CFGetTypeID(cfArray)
print("typeId: \(typeId)")

let desc = CFCopyDescription(anyRef)
let typeDesc = CFCopyTypeIDDescription(CFGetTypeID(anyRef))

print("CFTypeRef description: \(desc!)")
print("CFTypeRef type description: \(typeDesc!)")
}


The output is as follows:

CFShow(plist):
<CFBasicHash 0x610000069500 [0x7fffc4383da0]>{type = immutable dict, count = 3, entries =>
0 : Addresses = <CFArray 0x610000069440 [0x7fffc4383da0]>{type = immutable, count = 1, values = (
0 : <CFString 0x610000028980 [0x7fffc4383da0]>{contents = "192.168.139.24"}
)}
1 : <CFString 0x610000044c20 [0x7fffc4383da0]>{contents = "BroadcastAddresses"} = <CFArray 0x610000069480 [0x7fffc4383da0]>{type = immutable, count = 1, values = (
0 : <CFString 0x610000044c50 [0x7fffc4383da0]>{contents = "192.168.139.255"}
)}
2 : <CFString 0x7fffc429d300 [0x7fffc4383da0]>{contents = "SubnetMasks"} = <CFArray 0x6100000694c0 [0x7fffc4383da0]>{type = immutable, count = 1, values = (
0 : <CFString 0x6100000289a0 [0x7fffc4383da0]>{contents = "255.255.255.0"}
)}
}
Key: State:/Network/Interface/en0/IPv4:
Value: {
Addresses = (
"192.168.139.24"
);
BroadcastAddresses = (
"192.168.139.255"
);
SubnetMasks = (
"255.255.255.0"
);
}
typeIdPList: 18
typeIdDictionary: 18
Value:
0x0000610000069440
typeIdValue: 1
typeIdArray: 19
typeId: 1
CFTypeRef description: 0x0000610000069440
CFTypeRef type description: CFType

Answer Source

CFPropertyList already supports subscripting, so there is no need to hassle with CFDictionary and raw pointers, you can simplify the code to

let cfName = "Demo" as CFString
let dynamicStore = SCDynamicStoreCreate(nil, cfName, nil, nil)
let key = "State:/Network/Interface/en0/IPv4" as CFString

guard let plist = SCDynamicStoreCopyValue(dynamicStore, key) else {return}
print(plist)

if let addresses = plist[kSCPropNetIPv4Addresses] as? [String] {
    print(addresses)
}

But if you are really curious how to make your CFDictionary/CFArray based approach work: You have to use Unmanaged.fromOpaque() to create a managed pointer from the raw pointer value:

let cfArray = Unmanaged<CFArray>.fromOpaque(rawPointerToValue!).takeUnretainedValue()
for idx in 0..<CFArrayGetCount(cfArray) {
    if let addr = CFArrayGetValueAtIndex(cfArray, idx) {
        let s = Unmanaged<NSString>.fromOpaque(addr).takeUnretainedValue()
        print(s)
    }
}