This time I am drilling into Swift values without using objective-C APIs as in the previous case but by using the Mirror API. Good thing it works with structs as well as classes:
func printValue(label: String?, valuePtr: UnsafeRawPointer, value: Any, indentationLevel: Int = 0) {
let m = Mirror(reflecting: value)
print(indent(indentationLevel), terminator: "")
// let size = MemoryLayout.size(ofValue: value) // always returns 32 🤔🤔🤔
let size = size(of: value, display: m.displayStyle) // 🤔🤔🤔
let sz = m.displayStyle == .class ? 8 : size
let display = m.displayStyle != nil ? "\(m.displayStyle!) " : ""
var nameAndType = "var " + label! + ": " + display + "\(m.subjectType)"
while nameAndType.count < 32 - indentationLevel * 4 { nameAndType += " " }
print("\(nameAndType) // \(sz) bytes (", terminator: "")
printAnyBytes("", valuePtr, offset: 0, size: sz, terminator: ")")
if m.displayStyle == .class {
print(" -> \(size) bytes", terminator: "")
}
print()
for child in m.children {
var value = child.value
printValue(label: child.label, valuePtr: &value, value: child.value, indentationLevel: indentationLevel + 1)
}
}
Uninteresting bits
func indent(_ level: Int) -> String {
(0 ..< level).reduce("") { r, e in r + " " }
}
func printAnyBytes(_ title: String, _ ptr: UnsafeRawPointer, offset: Int, size: Int, terminator: String = "\n") {
let p = unsafeBitCast(ptr, to: UnsafePointer<UInt8>.self)
printBytes(title, p, offset: offset, size: size, terminator: terminator)
}
func printBytes(_ title: String, _ ptr: UnsafePointer<UInt8>, offset: Int, size: Int, terminator: String = "\n") {
print("\(title)", terminator: "")
for i in 0 ..< size {
let ch = (ptr + offset + i).pointee
if i != 0 {
print(" ", terminator: "")
}
print(String(format: "%02x", ch), terminator: "")
}
print("", terminator: terminator)
}
With this implementation for the following example types:
class MyClass {
var foo: Int16 = .max
var bar: Double = .infinity
}
struct MyStruct {
var foo: UInt8 = 0x11
var myClass = MyClass()
var bar: UInt16 = 0x3333
let foo2: Int8 = 0x22
var baz: UInt32 = 0x44444444
let bar2: Int16 = -0x3333
var qux: UInt64 = 0x5555555555555555
var baz2: Int32 = -0x44444444
let quux: (UInt8, Int) = (0x77, 0x6666666666666666)
var qux2: Int64 = -0x5555555555555555
let cgPoint = CGPoint(x: 1.2345, y: 3.1415)
}
var v = MyStruct()
printValue(label: "v", valuePtr: &v, value: v)
I am getting this autogenerated output:
var v: struct MyStruct // 65 bytes 🛑 (11 c0 0e 05 01 00 00 00 00 f9 c1 02 00 60 00 00 33 33 22 05 44 44 44 44 cd cc 00 00 00 00 00 00 55 55 55 55 55 55 55 55 bc bb bb bb 01 00 00 00 66 66 66 66 66 66 66 66 77 60 f8 6a 01 00 00 00 ab) 🛑
var foo: UInt8 // 1 bytes (11)
var myClass: class MyClass // 8 bytes (00 f9 c1 02 00 60 00 00) -> 10 bytes 🛑
var foo: Int16 // 2 bytes (ff 7f)
var bar: Double // 8 bytes (00 00 00 00 00 00 f0 7f)
var bar: UInt16 // 2 bytes (33 33)
var foo2: Int8 // 1 bytes (22)
var baz: UInt32 // 4 bytes (44 44 44 44)
var bar2: Int16 // 2 bytes (cd cc)
var qux: UInt64 // 8 bytes (55 55 55 55 55 55 55 55)
var baz2: Int32 // 4 bytes (bc bb bb bb)
var quux: tuple (UInt8, Int) // 9 bytes 🛑 (77 21 fc 00 00 60 00 00 66) 🛑
var .0: UInt8 // 1 bytes (77)
var .1: Int // 8 bytes (66 66 66 66 66 66 66 66)
var qux2: Int64 // 8 bytes (ab aa aa aa aa aa aa aa)
var cgPoint: struct CGPoint // 16 bytes (8d 97 6e 12 83 c0 f3 3f 6f 12 83 c0 ca 21 09 40)
var x: Double // 8 bytes (8d 97 6e 12 83 c0 f3 3f)
var y: Double // 8 bytes (6f 12 83 c0 ca 21 09 40)
which is almost perfect – I marked errors with , they are related to padding bytes inserted to align field on their natural boundaries, and this implementation doesn't handle padding correctly (nor does it seem that Mirror API can return me values' offsets, just the values themselves).
The big question I had during implementing this is how to get the size of Any
value correctly, specifically the value
field of "Mirror.Child"
which is typed as:
public typealias Child = (label: String?, value: Any)
NaĂŻvely using MemoryLayout.size(of: any)
results into 32 (see the lines marked with ) hence I implemented my own code to get the size, the code I can't be less proud of
func size(of value: Any, display: Mirror.DisplayStyle?) -> Int {
switch value {
case is Bool: return MemoryLayout<Bool>.size
case is Int8: return MemoryLayout<Int8>.size
case is UInt8: return MemoryLayout<UInt8>.size
case is Int16: return MemoryLayout<Int16>.size
case is UInt16: return MemoryLayout<UInt16>.size
case is Int32: return MemoryLayout<Int32>.size
case is UInt32: return MemoryLayout<UInt32>.size
case is Int64: return MemoryLayout<Int64>.size
case is UInt64: return MemoryLayout<UInt64>.size
case is Int: return MemoryLayout<Int>.size
case is UInt: return MemoryLayout<UInt>.size
case is Float: return MemoryLayout<Float>.size
case is Double: return MemoryLayout<Double>.size
case is CGFloat: return MemoryLayout<CGFloat>.size
default:
switch display {
case .tuple, .class, .struct:
return Mirror(reflecting: value).children.reduce(0) { r, e in
let val = e.value
return r + size(of: val, display: Mirror(reflecting: val).displayStyle)
}
default:
fatalError("unhandled: \(value), type: \(type(of: value)), display: \(display)")
}
}
}
There must be a better way to get the size of Any
value, but what is it?