简体   繁体   中英

objc_copyClassList not even enumerable anymore?

This is a derivative of this and this .

On iOS 15, if I just try to fetch and enumerate the classes of the objc runtime, waiting until viewDidAppear to make sure there wasn't some initialization issue:

var count = UInt32(0)
var classList = objc_copyClassList(&count)!
print("COUNT \(count)")
print("CLASS LIST \(classList)")
for i in 0..<Int(count) {
    print("\(i)")
    classList[i]
}

produces the following before a Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1820e0cdc)

COUNT 28353
CLASS LIST 0x000000010bf24000
0
1
2
2022-02-17 16:24:02.977904-0800 TWiG V[2504:705046] *** NSForwarding: warning: object 0x1dbd32148 of class '__NSGenericDeallocHandler' does not implement methodSignatureForSelector: -- trouble ahead
2022-02-17 16:24:02.978001-0800 TWiG V[2504:705046] *** NSForwarding: warning: object 0x1dbd32148 of class '__NSGenericDeallocHandler' does not implement doesNotRecognizeSelector: -- abort

I don't know how to do any less with it than just fetching the value. I'm not trying to print it or anything, and yet it still fails. Is there some magic I'm missing?

(I do not have hardened runtime turned on, XCode 13.2.1)

I did get an answer for this on the Apple forums. The following code works:

var count: UInt32 = 0
let classList = objc_copyClassList(&count)!
defer { free(UnsafeMutableRawPointer(classList)) }
let classes = UnsafeBufferPointer(start: classList, count: Int(count))
for cls in classes {
    print(String(cString: class_getName(cls)))
}

I was surprised why this works, but mine did not. Here is the relevant explanation:

I avoided this line in your code:

classList[i]

That line trigger's Swift's dynamic cast infrastructure, which relies on -methodSignatureForSelector:, which isn't implemented by the __NSGenericDeallocHandler class.

So, the special sauce is this:

if you're working with an arbitrary class you discover via the Objective-C runtime, you have to be very careful what you do with it. I recommend that you use Objective-C runtime calls to interrogate the class to make sure it behaves reasonably before you let it 'escape' into code that you don't control, like the Swift runtime.

In my example I used the Objective-C runtime routine class_getName to get the class name, but there are a bunch of other things that you can do to identify that you're working with a reasonable class before you let it escape into general-purpose code, like the Swift runtime.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM