Koan-Sin Tan, freedom@computer.org, Aug 10th, 2025, COSCUP 2025 Learning Swift: A Stupid Way 1
• feel free to interrupt me a nytime 2
About me • Le a rnt to use open-source softw a re before the term “open source” w a s coined on VAX-11/780. • Le a rnt some Sm a llT a lk-80 in e a rly 1990s. • Le a rnt some Objective-C progr a mming on NeXT m a chines. • W a s excited to he a r “Swift is the Objective-C without the C”. Thought it’s more Sm a llT a lk-80 like. You know it’s not. • Know very little Swift 3 http://gunkies.org/w/images/c/c1/DEC-VAX-11-780.jpg
using Swift API from PrivateFrameworks • Some new Apple fr a meworks a re Swift only, either public or priv a te fr a meworks • CoreML: Objective-C a nd Swift • Found a tionModels, ModelC a t a log: Swift only 4
Why using Swift APIs in Apple’s private frameworks is an issue? • Although there m a ny “public symbols” we c a n use / link, there a re no decl a r a tions. • For Objective-C, there a re sever a l cl a ss-dump tools a v a il a ble on the internet. • Find inform a tion from a compiled M a ch-O f ile, the execut a ble form a t used by m a cOS, iOS, a nd other Apple oper a ting systems, by a n a lyzing the Objective-C runtime inform a tion embedded in it a nd gener a tes corresponding Objective-C he a der f iles. • There a re no tools a re good a s cl a ss-dump for dump Swift API to .swiftinterf a ce, which we’ll expl a in l a ter. • If you’re f a mili a r with the Objective-C runtime API, you might wonder if we c a n use it to dyn a mic a lly a ccess construct Swift objects. Altern a tively, is there a corresponding “Swift runtime API”? 5
public vs. private frameworks • public fr a meworks, such a s CoreML, come with C/C++/Objective-C he a ders in He a ders a nd Swift decl a r a tions in Modules. • priv a te fr a meworks h a ve none of them • if you don’t know .tbd, it’s Apple’s (text- b a sed dyn a mic libr a ry stu ff ). While there isn't one single "o ff ici a l speci f ic a tion document" re a dily a v a il a ble to the public, there a re open-source implement a tions in LLVM. 6
swiftinterface • swiftinterf a ce f iles a re a key component of Swift's Module St a bility a nd Libr a ry Evolution fe a tures. They a re a textu a l represent a tion of a Swift module's public API, gener a ted by the Swift compiler during the build process from the origin a l Swift source code. • The .swiftinterf a ce f ile cont a ins: • Decl a r a tions: The n a mes of a ll public types, functions, a nd v a ri a bles. • Type Inform a tion: The sign a tures of these public members, including their return types, p a r a meter types, a nd a ny generic constr a ints. • Module-Speci f ic Inform a tion: Det a ils th a t a llow the compiler to link a g a inst the module correctly. • This f ile is essenti a lly a "st a ble ABI he a der" for Swift modules, ensuring th a t a libr a ry compiled with one version of the Swift compiler c a n be used by a n a pplic a tion compiled with a di ff erent, comp a tible version of the compiler. • When a Swift bin a ry is compiled into a M a ch-O f ile, the origin a l source code a nd the det a iled type inform a tion in the .swiftinterf a ce f ile a re not preserved. While the M a ch-O bin a ry cont a ins a signi f ic a nt a mount of symbol a nd met a d a t a , it is not enough to reverse- engineer the complete a nd a ccur a te Swift-level API. 7
C/C++/Objective-C/Swift symbols on iOS/macOS • C: no function sign a tures, no nothing • C++: function p a r a meters type in m a ngled symbols, vt a ble, etc. The return types a re not in m a ngled n a mes. • Objective-C: dyn a mic type (generic object id, dyn a mic binding, etc.) so there a re m a ny inform a tion. cl a ss-dump somewh a t showed wh a t we c a n from bin a ry f iles. • Swift: Swift n a me m a ngling spec a nd “swift dem a ngle” a re quite comprehensive. 8
modelcatalogdump 9
Using ModelCatalog.framework • With the help of otool a nd lldb, we’ve discovered th a t the modelc a tlogdump utilizes the priv a te fr a mework of the ModelC a t a log.fr a mework. • The ModelC a t a log fr a mework o ff ers very limited Objective-C APIs. • Interestingly, the object cl a sses _TtC…. a re a ctu a lly Swift cl a sses. • The _OBJC_CLASS_$_MCResourceInform a tion a nd _OBJC_CLASS_$_MCResourceSt a tus cl a sses don’t provide a ny useful inform a tion. We’ll revisit this l a ter. • Therefore, we need to ex a mine the Swift API. • Let’s check if we do “import ModelC a t a log” in Swift 10
11
12
13 LLM a nd Di ff usion rel a ted cl a sses in symbol t a ble
import ModelCatalog Let's check if we can make import ModelCatalog in Swift work. Let's use stu ff in /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks By default, it doesn't work $ swiftc test_import_mc_1.swift -F /Library/Developer/CommandLineTools/SDKs/ MacOSX.sdk/System/Library/PrivateFrameworks test_import_mc_1.swift:1:8: error: no such module 'ModelCatalog 1 | import ModelCatalog | `- error: no such module 'ModelCatalog' 2 | $ 14
import ModelCatalog (cont’d) • It seems module.modulesm a p is needed • it seems there is not a single, comprehensive "o ff ici a l" Apple document a tion for the module.modulem a p f ile form a t. We f ind inform a tion in llvm/cl a ng document a tions a nd source code. • Since we a re le a rning in a stupid w a y. Let’s use copy- a nd-p a ste a nd tri a l- a nd-error. I copied module.modulemap from CoreML.framework and change CoreML to ModelCatalog, framework module ModelCatalog { umbrella header "ModelCatalog.h" export * module * { export * } } The ModelCatalog.h could be an empty one. That is, we could create an empty /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks/ModelCatalog.framework/Headers/ModelCatalog.h then we can compile the one line ("import ModelCatalog") program successfully. 15
import ModelCatalog (cont’d) Can we ingore the umbrella header "ModelCatalog.h" line, it seems this is for Objective-C to Swift bridge (for Swift code to use Objective-C binary code). YES, if we remove the line only, we got messages like $ swiftc test_import_mc_1.swift -F /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks/ModelCatalog.framework/Modules/ module.modulemap:4:12: error: inferred submodules require a module with an umbrella 2 | 3 | export * 4 | module * { export * } | `- error: inferred submodules require a module with an umbrella 5 | } 6 | It seems line 4 here 4 | module * { export * } is also for using Objective-C. When we remove it too, it WORKS. That is framework module ModelCatalog { export * } is enough. 16
As we know, /usr/bin/modelcatalogdump could be a good starting point. By setting breakpoints with b -r ModelCatalog in lldb modelcatalogdump, the fi rst method I captured was ModelCatalog`static ModelCatalog.CatalogClient.generativeExperienceEssentialResourcesStatus() -> ModelCatalog.ResourceReadinessStatus By examining exported symbols with nm and swift demangle, we know that ModelCatalog.CatalogClient is a class in the ModelCatalog framework and static ModelCatalog.CatalogClient.generativeExperienceEssentialResourcesStatus() -> ModelCatalog.ResourceReadinessStatus is a class method of CatalogClient. It takes no argument and returns an instance of ModelCatalog.ResourceReadinessStatus, which is a Swift enum. 17
18 as /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks/ModelCatalog.framework/Modules/ ModelCatalog.swiftmodule/arm64e-apple-macos.swiftinterface we can compile and run the following simple swift code import ModelCatalog print("CatalogClient readiness:", CatalogClient.generativeExperienceEssentialResourcesStatus() That is, we fi gured out how to call undocumented Swift API in /System/Library/PrivateFrameworks/. Note that the fi rst 3 lines starting with // are necessary. I copied them from CoreML.framework and change CoreML to ModelCatalog.
swift demangle example 19
Tools • So f a r, we use nm, otool, lldb, a nd swift dem a ngle. Are there a ny other convenient open-source tools. • Yes, to me, a t le a st Ghidr a a nd ipsw a re quite useful. • Ghidr a is a n open-source reversing tool from NSA. It could • a n a lyze dyld_sh a red_c a che, dissemble functions, decompile m a chine code into C/C++ like functions. • ipsw is kind a “iOS/m a cOS Rese a rch Swiss Army Knife”, I used it to • downlo a d Apple .ipsw f iles, extr a ct a nd re a d dyld_sh a red_c a che from Apple .ipsw f ile, a nd decompile some functions with Github Copilot • ipsw a lso provides Objective-C cl a ss-dump a nd swift-dump • Unfortun a tely, swift-dump is not complete. E.g,, it could not gener a te .sw f itinterf a ce f iles a nd didn’t h a ndle generic type well. 20
CatalogClient by ipsw swift- dump • no public • no gener a tiveExperienceEssenti a lResource sSt a tus() • inst a nce v a ri a ble setters a nd getters a re stripped. 21
CatalogClient by Ghidra • Yes, there is C a t a logClient.gener a tiveExperienceEss enti a lResourcesSt a tus() • And we c a n get h a rd to re a d decompiled code. 22
calling a CatalogClient instance method • we c a n f ind th a t there is • disp a tch thunk of ModelC a t a log.C a t a logClient.enoughStor a geForGener a tiveExperiencesEssenti a lResources() a sync throws -> Swift.Bool • c a n we cre a te a nd inst a nce a nd c a ll it? Yes, it’s quite trivi a l. Add decl a r a tions of this function a nd the constructor then we c a n compile it. • note th a t a thunk in Swift is a function or closure th a t del a ys the execution of a piece of code. 23
CatalogClient.generativeExperienceEssen tialResourcesStatus() decompiled by • de-compiled code gener a ted by LLMs might not be a ccur a te, but it’s much e a sier to re a d. $ ipsw dyld disass 23A5308g__iPhone17,1/ dyld_shared_cache_arm64e --vaddr 0x18dfdcbe4 --dec --dec-model "Gemini 2.5 Pro (Preview)" 24
calling a CatalogClient instance method (cont’d) • We c a n compile the following simple code successfully. However it f a iled to run. • With `log stre a m --debug`, we c a n f ind some clues. xxxxxxxxxxxx+0800 0x1f7c3f3 Error 0x0 538 0 modelcatalogd: (ModelCatalogRuntime) ModelCatalog com.apple.modelcatalog.full-access: Rejecting connection from 67927: lacking entitlement • It seems we h a ve to a dd this “non-st a nd a rd” “com. a pple.modelc a t a log.full- a ccess” entitlement. Th a t is, it won’t work on iPhone a nd m a c without Apple Mobile File Integrity (AMFI) dis a bled. • Yes, a fter signing the bin a ry with the entitlement, it works. import ModelCatalog print("CatalogClient readiness:", CatalogClient.generativeExperienceEssentialResourcesStatus()) let client = CatalogClient() print(client) if let ready = try? await client.enoughStorageForGenerativeExperiencesEssentialResources() { print(ready) } else { print("failed") } 25
more CatalogClient methods • with inst a nce methods resources() a nd resourceBundels() of the C a t a logClient cl a ss, we c a n dump a lot of resource inform a tion • remember to codesign with the “com. a pple.modelc a t a log.full- a ccess” entitlement. • Most of the inform a tion we see from modelc a t a logdump is in resources() a nd a nd resourceBundles() outputs import ModelCatalog let client = CatalogClient() let resources = try? client.resources() print("resources:") dump(resources!, indent: 1) let bundles = try? client.resourceBundles() print("resource bundles:") dump(bundles!, indent: 1) 26
resourceBundles 27
there a re some c a ve a ts a nd limit a tions Objective-C runtime API works for non-NSObject subclasses to some extent • Underlying Object Model: Even when written without explicitly inheriting from NSObject, every Swift cl a ss on Apple pl a tforms still executes within a nd uses the Objective-C object model. This me a ns th a t a t a fund a ment a l level, they a re Objective-C objects. The root cl a ss for pure Swift cl a sses is a n intern a l type often referred to a s _SwiftObject, which itself lever a ges the Objective-C runtime. The Objective-C runtime libr a ry provides support for the dyn a mic properties of the Objective-C l a ngu a ge a nd is linked by a ll Objective-C a pps. • Cl a ss-Level APIs (Discovery a nd Introspection): • Functions like objc_lookUpCl a ss(_:) a nd objc_getCl a ssList(_:_:) c a n indeed f ind a nd return Cl a ss objects for pure Swift cl a sses. This is bec a use these cl a sses a re registered with the Objective-C runtime. • You c a n a lso use functions like cl a ss_getN a me(_:) a nd cl a ss_getSupercl a ss(_:) on these Swift cl a sses. • Inst a nce-Level APIs (Cre a tion a nd M a nipul a tion): • cl a ss_cre a teInst a nce(_:_:): You c a n use cl a ss_cre a teInst a nce(_:_:) to cre a te inst a nces of pure Swift cl a sses, a s they a re built upon the Objective-C object model. • object_setIv a r(_:_:_:) a nd cl a ss_getInst a nceV a ri a ble(_:_:): This is where the limit a tions become more pronounced a nd d a ngerous. • While these functions oper a te on Objective-C inst a nce v a ri a bles, directly m a nipul a ting properties of pure Swift cl a sses (especi a lly Swift v a lue types like String, Int, or structs th a t a re not explicitly exposed to Objective-C with @objc) vi a object_setIv a r() is gener a lly not recommended a nd highly problem a tic. • The intern a l memory l a yout (Applic a tion Bin a ry Interf a ce or ABI) of Swift cl a sses a nd their properties, p a rticul a rly for v a lue types, is not st a ble or publicly documented for direct m a nipul a tion. Relying on such intern a l det a ils c a n le a d to cr a shes, memory corruption, or unde f ined beh a vior with future Swift upd a tes. • For object_setIv a r() to work reli a bly, the property typic a lly needs to be a n Objective-C object type (like NSString for a String property) a nd often explicitly m a rked with @objc in the Swift cl a ss decl a r a tion to ensure proper bridging a nd runtime visibility. If the property is a true Swift v a lue type a nd not bridged, object_setIv a r would expect a r a w pointer to the v a lue's stor a ge, which is extremely complex a nd uns a fe to m a n a ge m a nu a lly. • The other w a y is to use Uns a fePointer or Uns a feR a wPointer. • Method Invoc a tion (performSelector): To dyn a mic a lly c a ll methods on a Swift cl a ss inst a nce using Objective-C runtime functions like performSelector, the methods must be explicitly exposed to the Objective-C runtime using the @objc a ttribute. • Key-V a lue Coding (KVC): KVC, a higher-level Objective-C mech a nism for dyn a mic property a ccess, requires the Swift cl a ss to inherit from NSObject a nd its properties to be m a rked with @objc. Therefore, KVC is gener a lly not directly a pplic a ble to pure Swift cl a sses th a t do not derive from NSObject. • Swift Re f lection (Mirror API): It's import a nt to note th a t Swift's Mirror API is a Swift-n a tive re f lection mech a nism. It works with a ll Swift types (including pure Swift cl a sses a nd structs) for introspection (re a ding property n a mes a nd v a lues). However, it does not support direct modi f ic a tion of property v a lues.. • In summ a ry, while b a sic introspection of pure Swift cl a sses is possible through the Objective-C runtime, direct m a nipul a tion of their properties (especi a lly v a lue types) using low-level runtime functions like object_setIv a r() is highly discour a ged due to ABI inst a bility, complex memory m a n a gement, a nd the l a ck of type s a fety. For dyn a mic property a ccess, it is gener a lly s a fer a nd more reli a ble to ensure your Swift cl a sses inherit from NSObject a nd expose relev a nt properties with @objc, then use higher-level mech a nisms like Key-V a lue Coding (KVC) when a ppropri a te. 28
objc_getClassList(_:_:_) in Swift objc_getClassList(_:_:) in Swift The objc_getClassList() is to obtain the list of registered class de fi nitions. Apple's documentation provides sample code on how to use it. However, it's in Objective-C even for the Swift version. 29
objc_getClassList(_:_:_) in Swift (cont’d) • With th a t, we c a n f ind for the v a nill a dumpAllCl a sses (without a dding extr a fr a mework with -fr a mework ...), there a re 9, 680 cl a sses a nd 9,102 of them a re a re NSObject derived. • With -fr a mework Found a tionModels, there a re more cl a sses (26,773 a nd 25,470 of them a re NSObject derived). 30 import Foundation import ObjectiveC func isDerivedFromNSObject(_ aClass: AnyClass) -> Bool { if aClass == NSObject.self { return true } var superclass: AnyClass? = class_getSuperclass(aClass) while let currentSuperclass = superclass { if currentSuperclass == NSObject.self { return true } superclass = class_getSuperclass(currentSuperclass) } return false } var numClasses = objc_getClassList(nil, 0) if (numClasses > 0) { var classes: UnsafeMutablePointer<AnyClass>? = nil classes = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(numClasses)) numClasses = objc_getClassList(AutoreleasingUnsafeMutablePointer<AnyClass>(classes!), numClasses) for i in 0..<Int(numClasses) { // dump(classes![i], indent: 1) if (isDerivedFromNSObject(classes![i])) { print("name =", String(cString: class_getName(classes![i])), "is NSObject derived"); } else { print("name =", String(cString: class_getName(classes![i]))); } } } else { print("no classes found") }
getting a instance variable • a s we s a w, C a t a logClient cl a ss h a s some inst a nce v a ri a bles. c a n we get them with something like .us a geAli a sV a luesForUs a geAli a s? • Unfortun a tely, it seems not. • How a bout other w a ys? • KVC: The ModelC a t a log.C a t a logClient is not NSObject derived. • mirror: yes, dump(client) works. other Mirror methods works. But remember th a t Mirror is re a d only • Objective-C runtime APIs. Yes, it works to some extent. 31 import Foundation import ModelCatalog print("status: ", CatalogClient.generativeExperienceEssentialResourcesStatus()) let client = CatalogClient() print(client) print(client.usageAliasValuesForUsageAlias) if let ready = try? await client.enoughStorageForGenerativeExperiencesEssentialResources() print(ready) } else { print("failed") }
getting a instance variable (cont’d) • With cl a ss_getInst a nceV a ri a ble(_:_:) a nd object_getIv a r(_:_:), we get the client.us a geAli a sV a luesForUs a geAli a s • How a bout setting a n inst a nce v a ri a ble? It’s a bit tricky. 32 import Foundation import ModelCatalog print("status: ", CatalogClient.generativeExperienceEssentialResourcesStatus()) let client = CatalogClient() print(client) let usage = class_getInstanceVariable(CatalogClient.self, "usageAliasValuesForUsageAlias") print(object_getIvar(client, usage!)!)
Sometime we need to set non-NSObject instance variables • st a rting from iOS/m a cOS 26, developers could a ccess AFM on-device with APIs in Found a tionModels.fr a mework. import FoundationModels let session = LanguageModelSession() let results = try? await session.respond(to: "tell me something about systolic array") print(results!) • Apple’s document a tion tells two sessions properties. • Inspecting the the session, e.g., a dding dump(session, m a xDepth: 2) a fter got session, we know there a re more. The Genener a tiveModelInferenceSession is a non- NSObject cl a ss. If we w a nt to construct it ourselves, we need to set non-NSObject inst a nce v a ri a bles. 33 class GenerativeModelInferenceSession { var generator: TokenGeneration.TokenGenerator var guardrails: FoundationModels.LanguageModelSession.Guardrails var stringRenderedPromptSanitizer: GenerativeModels.StringRenderedPromptSanitizer var stringResponseSanitizer: GenerativeModels.StringResponseSanitizer var modelBundleID: Swift.String var useCase: FoundationModels.SystemLanguageModel.UseCase var sessionID: Swift.String }
a tiny example for a String instance variable • String != NSString, there a re a uto-bridging mech a nism, but it’s not wh a t we w a nt. E.g., the iv a r_getTypeEncoding(_:) won’t return “@“ (id, object) for Swift String • object_lookUpCl a ss() a nd cl a ss_cre a teInst a nce() work for both NSObject a nd non-NSObject cl a sses. • object_getIv a r() should not be used. • KVC only works NSObject. • wh a t re a lly works • ObjectIdenti f ier() to get a n object’s a ddress. • Uns a feMut a blePointer/Uns a feMut a bleR a wPointer to ch a nge the v a lue of a pointee. 34 import Foundation @objc class Test: NSObject { @objc var modelBundleID: String = "original" override init() { modelBundleID = "init" } } let t = Test() print("t:") dump(t, indent: 1) let tClass = objc_lookUpClass("test_string_in_class.Test") var t2 = class_createInstance(tClass, 0) print("t2:") dump(t2!, indent: 1) var mbi = class_getInstanceVariable(tClass, "modelBundleID") print("mbi: (mbi!)") // print("modelBundleID: (object_getIvar(t!, mbi!))") will cause segmentation fault // print("modelBundleID: (object_getIvar(t2!, mbi!))”) ditto var bi = "com.apple.fm.language.instruct_3b.fm_api_generic" // object_setIvar(t2!, mbi!, bi) works sometimes // print("after setIvar modelBundleID: (object_getIvar(t2!, mbi!))") works sometimes // dump(t2!, indent: 1) will cause segmentation fault (t2 as! NSObject).setValue("Updated via KVC!", forKey: "modelBundleID") dump(t2!, indent: 1) // KVC works for NSObject-derived classes // Define the print(address:as:) function func print<T>(address p: UnsafeRawPointer, as type: T.Type) { let value = p.load(as: type) print(value) dump(value, indent: 1) print("p: (p), type (type)") } let t2addr = unsafeBitCast(ObjectIdentifier(t2 as AnyObject), to: Int.self) print("t2addr: ", String(format: "0x%lx", t2addr)) let offset = ivar_getOffset(mbi!) print("moduleBundleID offset: ", offset) // get a pointer to String instance variable let p = UnsafeMutableRawPointer(bitPattern: t2addr + offset) print(address: p!, as: String.self) // get a type pointer let typedPtr = p!.bindMemory(to: String.self, capacity: 1) print("pointee:", typedPtr.pointee) typedPtr.pointee = "test setting with poiter" print("pointee:", typedPtr.pointee) print("t2:") dump(t2!, indent: 1)
Swift Runtime API • Yes, there a re some swift_* functions in /usr/lib/swift/libswiftCore.dylib (in dyld_sh a red_c a che). • source code is a v a il a ble • most of them h a ve C he a ders somewhere in the source code • However, it seems they a re not a s complete a s Objective-C. And I could not f ind one to set inst a nce v a ri a bles 35
wrap-up • How to use Swift API in priv a te fr a meworks or non-public Swift API in public fr a meworks • either a dd Module/, including .swiftinterf a ce f iles, or • Objective-C runtime API + Uns a fePointer/Uns a feR a wPointer • Tools: • bin a ry utils (otool, nm, etc.), swift toolch a in (including swift dem a ngle), Ghidr a , ipse 36

Learning Swift: A Stupid Way -- How to use Swift API in private frameworks

  • 1.
    Koan-Sin Tan, freedom@computer.org,Aug 10th, 2025, COSCUP 2025 Learning Swift: A Stupid Way 1
  • 2.
    • feel freeto interrupt me a nytime 2
  • 3.
    About me • Le a rntto use open-source softw a re before the term “open source” w a s coined on VAX-11/780. • Le a rnt some Sm a llT a lk-80 in e a rly 1990s. • Le a rnt some Objective-C progr a mming on NeXT m a chines. • W a s excited to he a r “Swift is the Objective-C without the C”. Thought it’s more Sm a llT a lk-80 like. You know it’s not. • Know very little Swift 3 http://gunkies.org/w/images/c/c1/DEC-VAX-11-780.jpg
  • 4.
    using Swift APIfrom PrivateFrameworks • Some new Apple fr a meworks a re Swift only, either public or priv a te fr a meworks • CoreML: Objective-C a nd Swift • Found a tionModels, ModelC a t a log: Swift only 4
  • 5.
    Why using SwiftAPIs in Apple’s private frameworks is an issue? • Although there m a ny “public symbols” we c a n use / link, there a re no decl a r a tions. • For Objective-C, there a re sever a l cl a ss-dump tools a v a il a ble on the internet. • Find inform a tion from a compiled M a ch-O f ile, the execut a ble form a t used by m a cOS, iOS, a nd other Apple oper a ting systems, by a n a lyzing the Objective-C runtime inform a tion embedded in it a nd gener a tes corresponding Objective-C he a der f iles. • There a re no tools a re good a s cl a ss-dump for dump Swift API to .swiftinterf a ce, which we’ll expl a in l a ter. • If you’re f a mili a r with the Objective-C runtime API, you might wonder if we c a n use it to dyn a mic a lly a ccess construct Swift objects. Altern a tively, is there a corresponding “Swift runtime API”? 5
  • 6.
    public vs. privateframeworks • public fr a meworks, such a s CoreML, come with C/C++/Objective-C he a ders in He a ders a nd Swift decl a r a tions in Modules. • priv a te fr a meworks h a ve none of them • if you don’t know .tbd, it’s Apple’s (text- b a sed dyn a mic libr a ry stu ff ). While there isn't one single "o ff ici a l speci f ic a tion document" re a dily a v a il a ble to the public, there a re open-source implement a tions in LLVM. 6
  • 7.
    swiftinterface • swiftinterf a ce f iles a re a key componentof Swift's Module St a bility a nd Libr a ry Evolution fe a tures. They a re a textu a l represent a tion of a Swift module's public API, gener a ted by the Swift compiler during the build process from the origin a l Swift source code. • The .swiftinterf a ce f ile cont a ins: • Decl a r a tions: The n a mes of a ll public types, functions, a nd v a ri a bles. • Type Inform a tion: The sign a tures of these public members, including their return types, p a r a meter types, a nd a ny generic constr a ints. • Module-Speci f ic Inform a tion: Det a ils th a t a llow the compiler to link a g a inst the module correctly. • This f ile is essenti a lly a "st a ble ABI he a der" for Swift modules, ensuring th a t a libr a ry compiled with one version of the Swift compiler c a n be used by a n a pplic a tion compiled with a di ff erent, comp a tible version of the compiler. • When a Swift bin a ry is compiled into a M a ch-O f ile, the origin a l source code a nd the det a iled type inform a tion in the .swiftinterf a ce f ile a re not preserved. While the M a ch-O bin a ry cont a ins a signi f ic a nt a mount of symbol a nd met a d a t a , it is not enough to reverse- engineer the complete a nd a ccur a te Swift-level API. 7
  • 8.
    C/C++/Objective-C/Swift symbols oniOS/macOS • C: no function sign a tures, no nothing • C++: function p a r a meters type in m a ngled symbols, vt a ble, etc. The return types a re not in m a ngled n a mes. • Objective-C: dyn a mic type (generic object id, dyn a mic binding, etc.) so there a re m a ny inform a tion. cl a ss-dump somewh a t showed wh a t we c a n from bin a ry f iles. • Swift: Swift n a me m a ngling spec a nd “swift dem a ngle” a re quite comprehensive. 8
  • 9.
  • 10.
    Using ModelCatalog.framework • Withthe help of otool a nd lldb, we’ve discovered th a t the modelc a tlogdump utilizes the priv a te fr a mework of the ModelC a t a log.fr a mework. • The ModelC a t a log fr a mework o ff ers very limited Objective-C APIs. • Interestingly, the object cl a sses _TtC…. a re a ctu a lly Swift cl a sses. • The _OBJC_CLASS_$_MCResourceInform a tion a nd _OBJC_CLASS_$_MCResourceSt a tus cl a sses don’t provide a ny useful inform a tion. We’ll revisit this l a ter. • Therefore, we need to ex a mine the Swift API. • Let’s check if we do “import ModelC a t a log” in Swift 10
  • 11.
  • 12.
  • 13.
    13 LLM a nd Di ff usion rel a tedcl a sses in symbol t a ble
  • 14.
    import ModelCatalog Let's checkif we can make import ModelCatalog in Swift work. Let's use stu ff in /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks By default, it doesn't work $ swiftc test_import_mc_1.swift -F /Library/Developer/CommandLineTools/SDKs/ MacOSX.sdk/System/Library/PrivateFrameworks test_import_mc_1.swift:1:8: error: no such module 'ModelCatalog 1 | import ModelCatalog | `- error: no such module 'ModelCatalog' 2 | $ 14
  • 15.
    import ModelCatalog (cont’d) •It seems module.modulesm a p is needed • it seems there is not a single, comprehensive "o ff ici a l" Apple document a tion for the module.modulem a p f ile form a t. We f ind inform a tion in llvm/cl a ng document a tions a nd source code. • Since we a re le a rning in a stupid w a y. Let’s use copy- a nd-p a ste a nd tri a l- a nd-error. I copied module.modulemap from CoreML.framework and change CoreML to ModelCatalog, framework module ModelCatalog { umbrella header "ModelCatalog.h" export * module * { export * } } The ModelCatalog.h could be an empty one. That is, we could create an empty /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks/ModelCatalog.framework/Headers/ModelCatalog.h then we can compile the one line ("import ModelCatalog") program successfully. 15
  • 16.
    import ModelCatalog (cont’d) Canwe ingore the umbrella header "ModelCatalog.h" line, it seems this is for Objective-C to Swift bridge (for Swift code to use Objective-C binary code). YES, if we remove the line only, we got messages like $ swiftc test_import_mc_1.swift -F /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks/ModelCatalog.framework/Modules/ module.modulemap:4:12: error: inferred submodules require a module with an umbrella 2 | 3 | export * 4 | module * { export * } | `- error: inferred submodules require a module with an umbrella 5 | } 6 | It seems line 4 here 4 | module * { export * } is also for using Objective-C. When we remove it too, it WORKS. That is framework module ModelCatalog { export * } is enough. 16
  • 17.
    As we know,/usr/bin/modelcatalogdump could be a good starting point. By setting breakpoints with b -r ModelCatalog in lldb modelcatalogdump, the fi rst method I captured was ModelCatalog`static ModelCatalog.CatalogClient.generativeExperienceEssentialResourcesStatus() -> ModelCatalog.ResourceReadinessStatus By examining exported symbols with nm and swift demangle, we know that ModelCatalog.CatalogClient is a class in the ModelCatalog framework and static ModelCatalog.CatalogClient.generativeExperienceEssentialResourcesStatus() -> ModelCatalog.ResourceReadinessStatus is a class method of CatalogClient. It takes no argument and returns an instance of ModelCatalog.ResourceReadinessStatus, which is a Swift enum. 17
  • 18.
    18 as /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks/ModelCatalog.framework/Modules/ ModelCatalog.swiftmodule/arm64e-apple-macos.swiftinterface we can compileand run the following simple swift code import ModelCatalog print("CatalogClient readiness:", CatalogClient.generativeExperienceEssentialResourcesStatus() That is, we fi gured out how to call undocumented Swift API in /System/Library/PrivateFrameworks/. Note that the fi rst 3 lines starting with // are necessary. I copied them from CoreML.framework and change CoreML to ModelCatalog.
  • 19.
  • 20.
    Tools • So f a r,we use nm, otool, lldb, a nd swift dem a ngle. Are there a ny other convenient open-source tools. • Yes, to me, a t le a st Ghidr a a nd ipsw a re quite useful. • Ghidr a is a n open-source reversing tool from NSA. It could • a n a lyze dyld_sh a red_c a che, dissemble functions, decompile m a chine code into C/C++ like functions. • ipsw is kind a “iOS/m a cOS Rese a rch Swiss Army Knife”, I used it to • downlo a d Apple .ipsw f iles, extr a ct a nd re a d dyld_sh a red_c a che from Apple .ipsw f ile, a nd decompile some functions with Github Copilot • ipsw a lso provides Objective-C cl a ss-dump a nd swift-dump • Unfortun a tely, swift-dump is not complete. E.g,, it could not gener a te .sw f itinterf a ce f iles a nd didn’t h a ndle generic type well. 20
  • 21.
    CatalogClient by ipswswift- dump • no public • no gener a tiveExperienceEssenti a lResource sSt a tus() • inst a nce v a ri a ble setters a nd getters a re stripped. 21
  • 22.
    CatalogClient by Ghidra •Yes, there is C a t a logClient.gener a tiveExperienceEss enti a lResourcesSt a tus() • And we c a n get h a rd to re a d decompiled code. 22
  • 23.
    calling a CatalogClientinstance method • we c a n f ind th a t there is • disp a tch thunk of ModelC a t a log.C a t a logClient.enoughStor a geForGener a tiveExperiencesEssenti a lResources() a sync throws -> Swift.Bool • c a n we cre a te a nd inst a nce a nd c a ll it? Yes, it’s quite trivi a l. Add decl a r a tions of this function a nd the constructor then we c a n compile it. • note th a t a thunk in Swift is a function or closure th a t del a ys the execution of a piece of code. 23
  • 24.
    CatalogClient.generativeExperienceEssen tialResourcesStatus() decompiled by •de-compiled code gener a ted by LLMs might not be a ccur a te, but it’s much e a sier to re a d. $ ipsw dyld disass 23A5308g__iPhone17,1/ dyld_shared_cache_arm64e --vaddr 0x18dfdcbe4 --dec --dec-model "Gemini 2.5 Pro (Preview)" 24
  • 25.
    calling a CatalogClientinstance method (cont’d) • We c a n compile the following simple code successfully. However it f a iled to run. • With `log stre a m --debug`, we c a n f ind some clues. xxxxxxxxxxxx+0800 0x1f7c3f3 Error 0x0 538 0 modelcatalogd: (ModelCatalogRuntime) ModelCatalog com.apple.modelcatalog.full-access: Rejecting connection from 67927: lacking entitlement • It seems we h a ve to a dd this “non-st a nd a rd” “com. a pple.modelc a t a log.full- a ccess” entitlement. Th a t is, it won’t work on iPhone a nd m a c without Apple Mobile File Integrity (AMFI) dis a bled. • Yes, a fter signing the bin a ry with the entitlement, it works. import ModelCatalog print("CatalogClient readiness:", CatalogClient.generativeExperienceEssentialResourcesStatus()) let client = CatalogClient() print(client) if let ready = try? await client.enoughStorageForGenerativeExperiencesEssentialResources() { print(ready) } else { print("failed") } 25
  • 26.
    more CatalogClient methods •with inst a nce methods resources() a nd resourceBundels() of the C a t a logClient cl a ss, we c a n dump a lot of resource inform a tion • remember to codesign with the “com. a pple.modelc a t a log.full- a ccess” entitlement. • Most of the inform a tion we see from modelc a t a logdump is in resources() a nd a nd resourceBundles() outputs import ModelCatalog let client = CatalogClient() let resources = try? client.resources() print("resources:") dump(resources!, indent: 1) let bundles = try? client.resourceBundles() print("resource bundles:") dump(bundles!, indent: 1) 26
  • 27.
  • 28.
    there a re some c a ve a ts a ndlimit a tions Objective-C runtime API works for non-NSObject subclasses to some extent • Underlying Object Model: Even when written without explicitly inheriting from NSObject, every Swift cl a ss on Apple pl a tforms still executes within a nd uses the Objective-C object model. This me a ns th a t a t a fund a ment a l level, they a re Objective-C objects. The root cl a ss for pure Swift cl a sses is a n intern a l type often referred to a s _SwiftObject, which itself lever a ges the Objective-C runtime. The Objective-C runtime libr a ry provides support for the dyn a mic properties of the Objective-C l a ngu a ge a nd is linked by a ll Objective-C a pps. • Cl a ss-Level APIs (Discovery a nd Introspection): • Functions like objc_lookUpCl a ss(_:) a nd objc_getCl a ssList(_:_:) c a n indeed f ind a nd return Cl a ss objects for pure Swift cl a sses. This is bec a use these cl a sses a re registered with the Objective-C runtime. • You c a n a lso use functions like cl a ss_getN a me(_:) a nd cl a ss_getSupercl a ss(_:) on these Swift cl a sses. • Inst a nce-Level APIs (Cre a tion a nd M a nipul a tion): • cl a ss_cre a teInst a nce(_:_:): You c a n use cl a ss_cre a teInst a nce(_:_:) to cre a te inst a nces of pure Swift cl a sses, a s they a re built upon the Objective-C object model. • object_setIv a r(_:_:_:) a nd cl a ss_getInst a nceV a ri a ble(_:_:): This is where the limit a tions become more pronounced a nd d a ngerous. • While these functions oper a te on Objective-C inst a nce v a ri a bles, directly m a nipul a ting properties of pure Swift cl a sses (especi a lly Swift v a lue types like String, Int, or structs th a t a re not explicitly exposed to Objective-C with @objc) vi a object_setIv a r() is gener a lly not recommended a nd highly problem a tic. • The intern a l memory l a yout (Applic a tion Bin a ry Interf a ce or ABI) of Swift cl a sses a nd their properties, p a rticul a rly for v a lue types, is not st a ble or publicly documented for direct m a nipul a tion. Relying on such intern a l det a ils c a n le a d to cr a shes, memory corruption, or unde f ined beh a vior with future Swift upd a tes. • For object_setIv a r() to work reli a bly, the property typic a lly needs to be a n Objective-C object type (like NSString for a String property) a nd often explicitly m a rked with @objc in the Swift cl a ss decl a r a tion to ensure proper bridging a nd runtime visibility. If the property is a true Swift v a lue type a nd not bridged, object_setIv a r would expect a r a w pointer to the v a lue's stor a ge, which is extremely complex a nd uns a fe to m a n a ge m a nu a lly. • The other w a y is to use Uns a fePointer or Uns a feR a wPointer. • Method Invoc a tion (performSelector): To dyn a mic a lly c a ll methods on a Swift cl a ss inst a nce using Objective-C runtime functions like performSelector, the methods must be explicitly exposed to the Objective-C runtime using the @objc a ttribute. • Key-V a lue Coding (KVC): KVC, a higher-level Objective-C mech a nism for dyn a mic property a ccess, requires the Swift cl a ss to inherit from NSObject a nd its properties to be m a rked with @objc. Therefore, KVC is gener a lly not directly a pplic a ble to pure Swift cl a sses th a t do not derive from NSObject. • Swift Re f lection (Mirror API): It's import a nt to note th a t Swift's Mirror API is a Swift-n a tive re f lection mech a nism. It works with a ll Swift types (including pure Swift cl a sses a nd structs) for introspection (re a ding property n a mes a nd v a lues). However, it does not support direct modi f ic a tion of property v a lues.. • In summ a ry, while b a sic introspection of pure Swift cl a sses is possible through the Objective-C runtime, direct m a nipul a tion of their properties (especi a lly v a lue types) using low-level runtime functions like object_setIv a r() is highly discour a ged due to ABI inst a bility, complex memory m a n a gement, a nd the l a ck of type s a fety. For dyn a mic property a ccess, it is gener a lly s a fer a nd more reli a ble to ensure your Swift cl a sses inherit from NSObject a nd expose relev a nt properties with @objc, then use higher-level mech a nisms like Key-V a lue Coding (KVC) when a ppropri a te. 28
  • 29.
    objc_getClassList(_:_:_) in Swift objc_getClassList(_:_:)in Swift The objc_getClassList() is to obtain the list of registered class de fi nitions. Apple's documentation provides sample code on how to use it. However, it's in Objective-C even for the Swift version. 29
  • 30.
    objc_getClassList(_:_:_) in Swift(cont’d) • With th a t, we c a n f ind for the v a nill a dumpAllCl a sses (without a dding extr a fr a mework with -fr a mework ...), there a re 9, 680 cl a sses a nd 9,102 of them a re a re NSObject derived. • With -fr a mework Found a tionModels, there a re more cl a sses (26,773 a nd 25,470 of them a re NSObject derived). 30 import Foundation import ObjectiveC func isDerivedFromNSObject(_ aClass: AnyClass) -> Bool { if aClass == NSObject.self { return true } var superclass: AnyClass? = class_getSuperclass(aClass) while let currentSuperclass = superclass { if currentSuperclass == NSObject.self { return true } superclass = class_getSuperclass(currentSuperclass) } return false } var numClasses = objc_getClassList(nil, 0) if (numClasses > 0) { var classes: UnsafeMutablePointer<AnyClass>? = nil classes = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(numClasses)) numClasses = objc_getClassList(AutoreleasingUnsafeMutablePointer<AnyClass>(classes!), numClasses) for i in 0..<Int(numClasses) { // dump(classes![i], indent: 1) if (isDerivedFromNSObject(classes![i])) { print("name =", String(cString: class_getName(classes![i])), "is NSObject derived"); } else { print("name =", String(cString: class_getName(classes![i]))); } } } else { print("no classes found") }
  • 31.
    getting a instancevariable • a s we s a w, C a t a logClient cl a ss h a s some inst a nce v a ri a bles. c a n we get them with something like .us a geAli a sV a luesForUs a geAli a s? • Unfortun a tely, it seems not. • How a bout other w a ys? • KVC: The ModelC a t a log.C a t a logClient is not NSObject derived. • mirror: yes, dump(client) works. other Mirror methods works. But remember th a t Mirror is re a d only • Objective-C runtime APIs. Yes, it works to some extent. 31 import Foundation import ModelCatalog print("status: ", CatalogClient.generativeExperienceEssentialResourcesStatus()) let client = CatalogClient() print(client) print(client.usageAliasValuesForUsageAlias) if let ready = try? await client.enoughStorageForGenerativeExperiencesEssentialResources() print(ready) } else { print("failed") }
  • 32.
    getting a instancevariable (cont’d) • With cl a ss_getInst a nceV a ri a ble(_:_:) a nd object_getIv a r(_:_:), we get the client.us a geAli a sV a luesForUs a geAli a s • How a bout setting a n inst a nce v a ri a ble? It’s a bit tricky. 32 import Foundation import ModelCatalog print("status: ", CatalogClient.generativeExperienceEssentialResourcesStatus()) let client = CatalogClient() print(client) let usage = class_getInstanceVariable(CatalogClient.self, "usageAliasValuesForUsageAlias") print(object_getIvar(client, usage!)!)
  • 33.
    Sometime we needto set non-NSObject instance variables • st a rting from iOS/m a cOS 26, developers could a ccess AFM on-device with APIs in Found a tionModels.fr a mework. import FoundationModels let session = LanguageModelSession() let results = try? await session.respond(to: "tell me something about systolic array") print(results!) • Apple’s document a tion tells two sessions properties. • Inspecting the the session, e.g., a dding dump(session, m a xDepth: 2) a fter got session, we know there a re more. The Genener a tiveModelInferenceSession is a non- NSObject cl a ss. If we w a nt to construct it ourselves, we need to set non-NSObject inst a nce v a ri a bles. 33 class GenerativeModelInferenceSession { var generator: TokenGeneration.TokenGenerator var guardrails: FoundationModels.LanguageModelSession.Guardrails var stringRenderedPromptSanitizer: GenerativeModels.StringRenderedPromptSanitizer var stringResponseSanitizer: GenerativeModels.StringResponseSanitizer var modelBundleID: Swift.String var useCase: FoundationModels.SystemLanguageModel.UseCase var sessionID: Swift.String }
  • 34.
    a tiny examplefor a String instance variable • String != NSString, there a re a uto-bridging mech a nism, but it’s not wh a t we w a nt. E.g., the iv a r_getTypeEncoding(_:) won’t return “@“ (id, object) for Swift String • object_lookUpCl a ss() a nd cl a ss_cre a teInst a nce() work for both NSObject a nd non-NSObject cl a sses. • object_getIv a r() should not be used. • KVC only works NSObject. • wh a t re a lly works • ObjectIdenti f ier() to get a n object’s a ddress. • Uns a feMut a blePointer/Uns a feMut a bleR a wPointer to ch a nge the v a lue of a pointee. 34 import Foundation @objc class Test: NSObject { @objc var modelBundleID: String = "original" override init() { modelBundleID = "init" } } let t = Test() print("t:") dump(t, indent: 1) let tClass = objc_lookUpClass("test_string_in_class.Test") var t2 = class_createInstance(tClass, 0) print("t2:") dump(t2!, indent: 1) var mbi = class_getInstanceVariable(tClass, "modelBundleID") print("mbi: (mbi!)") // print("modelBundleID: (object_getIvar(t!, mbi!))") will cause segmentation fault // print("modelBundleID: (object_getIvar(t2!, mbi!))”) ditto var bi = "com.apple.fm.language.instruct_3b.fm_api_generic" // object_setIvar(t2!, mbi!, bi) works sometimes // print("after setIvar modelBundleID: (object_getIvar(t2!, mbi!))") works sometimes // dump(t2!, indent: 1) will cause segmentation fault (t2 as! NSObject).setValue("Updated via KVC!", forKey: "modelBundleID") dump(t2!, indent: 1) // KVC works for NSObject-derived classes // Define the print(address:as:) function func print<T>(address p: UnsafeRawPointer, as type: T.Type) { let value = p.load(as: type) print(value) dump(value, indent: 1) print("p: (p), type (type)") } let t2addr = unsafeBitCast(ObjectIdentifier(t2 as AnyObject), to: Int.self) print("t2addr: ", String(format: "0x%lx", t2addr)) let offset = ivar_getOffset(mbi!) print("moduleBundleID offset: ", offset) // get a pointer to String instance variable let p = UnsafeMutableRawPointer(bitPattern: t2addr + offset) print(address: p!, as: String.self) // get a type pointer let typedPtr = p!.bindMemory(to: String.self, capacity: 1) print("pointee:", typedPtr.pointee) typedPtr.pointee = "test setting with poiter" print("pointee:", typedPtr.pointee) print("t2:") dump(t2!, indent: 1)
  • 35.
    Swift Runtime API •Yes, there a re some swift_* functions in /usr/lib/swift/libswiftCore.dylib (in dyld_sh a red_c a che). • source code is a v a il a ble • most of them h a ve C he a ders somewhere in the source code • However, it seems they a re not a s complete a s Objective-C. And I could not f ind one to set inst a nce v a ri a bles 35
  • 36.
    wrap-up • How touse Swift API in priv a te fr a meworks or non-public Swift API in public fr a meworks • either a dd Module/, including .swiftinterf a ce f iles, or • Objective-C runtime API + Uns a fePointer/Uns a feR a wPointer • Tools: • bin a ry utils (otool, nm, etc.), swift toolch a in (including swift dem a ngle), Ghidr a , ipse 36