objc_copyClassList not even enumerable anymore? - ios

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.

Related

Objective-C pointer and swift

I'm following an apple document, but unfortunately the examples are written on objective-c, but I have confidence with Swift language and can not understand the meaning of some things, in particular, in this example:
void RunLoopSourcesPerformRoutine (void *info){
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
this line: RunLoopSource* obj = (RunLoopSource*)info;
the parameter: void *info indicates that info is a pointer to void, then I can put the address of any type of data structure, following various apple documents I saw that the translation of this : void *info into swift language is :
info: UnsafeMutableRawPointer?
Now, the RunLoopSource* obj = (RunLoopSource*)info; line indicates that obj is a variable of type: RunLoopSource, and to this is assigned the value of (RunLoopSource *) info, but precisely What does it mean this statement? : (RunLoopSource *) info, and how it translates in swift language ?
Swift really hates pointer. These 2 lines of code can be converted to Swift as
func RunLoopSourcesPerformRoutine(info: UnsafeMutableRawPointer) {
let obj = info.assumingMemoryBound(to: RunLoopSource.self)
obj.pointee.sourceFired()
}
This specific expression is a "typecast": it's saying that info, which is declared to be a pointer-to-unknown-anything (void *) is actually known by the programmer to be a pointer to a RunLoopSource. This forcibly changes the type of the expression to make the compiler happy as it is assigned to obj.
It is equivalent to using as! in Swift and is idiomatic when you know the semantics of a void * but the syntax doesn't capture it.
(This attempts to answer your question as stated but I'm not sure if you are looking for more information. If so, please clarify and me or someone more expert in unsafe pointers in Swift can help out.)
What you are dealing with (void *info) is a C pointer-to-void, which arrives into Swift as a form of UnsafeRawPointer. This means that type info has been cast away and that memory is being managed elsewhere.
In order to work with this thing as what you believe it to be, i.e. a RunLoopSource, you need to characterize it explicitly as a RunLoopSource. In C, you would cast, as in the example code you posted: (RunLoopSource*)info. In Swift, you rebind.
Observe that in your case this whole thing has been made just a little more complicated by the fact that this UnsafeMutableRawPointer has been wrapped in an Optional, and will have to be unwrapped before you can do anything at all.
Assuming, then, in your case, that info is really an UnsafeMutableRawPointer? bound to a RunLoopSource, you can say:
let rlsptr = info!.assumingMemoryBound(to: RunLoopSource.self)
let rls = rlsptr.pointee
Now rls is a RunLoopSource and you can work with it however you like. Keep in mind, however, that the memory is unmanaged, so you should work with it only here and now.
EDIT By the way, Apple has a really nice document on this entire matter: https://swift.org/migration-guide/se-0107-migrate.html

AnyObject not working in Xcode8 beta6?

In Xcode8 beta6, the following code will cause a warning: 'is' test is always true. But it won't print pass.
struct TestStruct {
}
//warning: 'is' test is always true
if TestStruct() is AnyObject {
print("pass")
}
And the following code will cause a warning: Conditional cast from 'T' to 'AnyObject' always succeeds
public static func register<T>(_ protocolType: T.Type, observer: T) {
//Warning: Conditional cast from 'T' to 'AnyObject' always succeeds
guard let object = observer as? AnyObject else {
fatalError("expecting reference type but found value type: \(observer)")
}
//...
}
The warning works as intended: the false return of TestStruct() is AnyObject, however, does not
The prior version of this answer perceived the warning,
'is' test is always true
as the bug, and contained some discussion as to why this perceived buggy warning would manifest itself. That TestStruct() is AnyObject evaluated to false at runtime, however, was perceived as expected behaviour.
Given the comments to the bug report filed by the OP (SR-2420), it seems the situation is the reverse: since Xcode 8/beta 6, the is test should always evaluate to true, and the bug the OP:s post is the fact that TestStruct() is AnyObject evaluates to false during runtime.
Joe Groff writes:
This is correct, because everything bridges to AnyObject now.
...
is/as AnyObject always succeed for all types now. It's behaving
as intended.
The new SwiftValue box for conversion from Swift values to Obj-C objects
(for additional details, see discussion in the comments below, thanks #MartinR)
It seems as if Swift values that are not explicitly implemented to be bridgeable to Obj-C objects via e.g. conformance to _ObjectiveCBridgeable (see e.g. the following Q&A for details regarding _ObjectiveCBridgeable), will instead automatically make use of the new SwiftValue box to allow conversion to Obj-C objects.
The initial commit message for swift/stdlib/public/runtime/SwiftValue.mm reads:
Runtime: Implement an opaque 'SwiftValue' ObjC class to hold bridged values
If there's no better mapping for a Swift value into an Objective-C
object for bridging purposes, we can fall back to boxing the value in
a class. This class doesn't have any public interface beyond being
NSObject-conforming in Objective-C, but is recognized by the Swift
runtime so that it can be dynamically cast back to the boxed type.
Long story short.
To check if value has a reference type:
if type(of: value) is AnyClass {
// ...
}
To check if type is a reference type:
if SomeType.self is AnyClass {
// ...
}
More helpful answers:
https://stackoverflow.com/a/39185374/746347
https://stackoverflow.com/a/39546887/746347

Playground crashes for Recursive Value Type

Xcode playground crashes for this code - if in project it prevents compilation.
I am trying to declare just a very simple struct:
struct Node<T> {
let value: T
var next: Node<T>?
init(_ value: T) {
self.value = value
next = nil
}
}
If I do that in XCode playground I get following error message: The Communication with the playground service was interrupted unexpectedly.
If I declare this struct in separate file in XCode the project cannot be compiled. All I get is in this case Command failed due to signal: Segmentation fault: 11.
Can somebody help me with this? Is there a workaround? Any help very much appreciated.
Quoting the swift documentation, "Structures are always copied when they are passed around in your code, and do not use reference counting." [1]
A linked list that you are trying to achieve works by storing a pointer or reference to the next node on the current node. This way, each node has the same size. Swift's struct on the other hand are not reference type. The size of each node will be different depending on how many nodes it has to store recursively.
One way to achieve what you are trying to do with struct is using UnsafeMutablePointer. I don't need to warn you because doing this in swift makes you write "unsafe" every few lines.
struct Node<T> {
var x: T
// Setting pointer type to <Node<T>> compiles, but infinite loops on runtime (Swift 1.2)
var next: UnsafeMutablePointer<Void>
}
var second = Node(x: 2, next: nil)
var first = Node(x: 1, next: &second)
print(unsafeBitCast(first.next, UnsafeMutablePointer<Node<Int>>.self).memory.x) // Prints 2
[1] https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html

NSArray element failed to match the Swift Array Element type

I have a Core Data object (Order) which has a method to return an array of another Core Data object (OrderWaypoint).
func getOrderedWaypoints() -> [OrderWaypoint] {
let nameDescriptor = NSSortDescriptor(key: "stop_number", ascending: true)
let sorted: [OrderWaypoint] = self.waypoints.sortedArrayUsingDescriptors([nameDescriptor]) as [OrderWaypoint]
return sorted
}
Everything works as expected in the main target when using the following loop
for waypoint in order.getOrderedWaypoints() {
// do something
}
But when I try the exact same thing in my test target
fatal error: NSArray element failed to match the Swift Array Element
type
I've tried casting the values but can't seem to get it to work. Any ideas why it works in the main target but not the test target?
EDIT
Well breaking it down further, the issue is specifically happening when I try to get an item out of the array.
var orderedList: [OrderWaypoint] = order.getOrderedWaypoints()
for var i = 0; i < orderedList.count; i++ {
var waypoint = orderedList[i]
// do something
}
You cant typecast as sortedArrayUsingDescriptors returns a NSArray and you need to copy all elements from that array into the Swift Typed array.
func getOrderedWaypoints() -> [OrderWaypoint] {
let nameDescriptor = NSSortDescriptor(key: "stop_number", ascending: true)
let sorted = [OrderWaypoint]()
for elem in self.waypoints.sortedArrayUsingDescriptors([nameDescriptor]) {
sorted.append(elem)
}
return sorted
}
Rather than do all that sorting in memory, I will generally have those helper methods that fetch related data get data directly from the database like:
var orderedWayPoints: [WayPoint] {
let request = NSFetchRequest(entityName: "Waypoint")
request.sortDescriptors = NSSortDescriptor(key: "stop_number", ascending: true)
request.predicate = NSPredicate(format: "parent = %#", self)
return try! managedObjectContext.executeFetchRequest(request)
}
The point is that I generally want the data base to do the grunt work of sorting my data rather that the code. And it's not much more code either.
The most likely cause is that the error is true: at least one of the elements in self.waypoints is not actually an OrderWaypoint. It is possible you have some other type in there that responds to the same messages. You may have a superclass of OrderWaypoint (are you using mogenerator, perhaps? are any of these actually _OrderWaypoint objects?) Are you swizzling the objects such that they lie about their true type? KVO can do this.
The fact that this fails in your test target raises the possibility that you're mocking these objects. Is that possible?
I'd start by carefully examining the contents of self.waypoints to make sure it actually contains what you think it does.
The reason it happens only in your test target is that it's a different module from your main application, so you get different versions of your OrderWaypoint class in each module, which Swift considers to be different classes: App.OrderWaypoint and AppTest.OrderWaypoint.
I'm working on solving this in my own project, and I'll update when I have a good solution :)
Update: For all-Swift projects, I think the solution is to remove your non-test .swift files from the test target, and add #testable import YourAppModuleName to your Swift tests. Apparently this is the modern approach and should probably be done anyway. (Though if your app is all Swift you probably won't have this problem.)
This did not work for me, though, because I have tests written in Objective-C that need to access my Swift classes, and I had trouble finding an Objective-C equivalent for importing the app target's Swift module.
My solution was to rewrite the problem code in Objective-C, where it doesn't do such strict type checking. In my case, that was easy because the problematic functions were in an extension on a class that's originally declared in Objective-C, so it's easy to move them over.
Update 2: A slightly underhanded but simpler solution is to remove the app Swift files from the test target, and then set the header search paths in the test target to point to the DerivedSources folder from the app target, and include the App-Swift.h file in the Objective-C tests that need it, instead of the AppTest-Swift.h file which no longer has the app Swift classes because you just removed them from the test target.

Swift: Unable to downcast AnyObject to SKPhysicsBody

Apple has the following method in the SKPhysicsBody class.
/* Returns an array of all SKPhysicsBodies currently in contact with this one */
func allContactedBodies() -> [AnyObject]!
I noticed it returns an array of AnyObject. So I read about how to deal with down casting AnyObject Here
I want to loop through the allContactedBodies array of my physics body. The problem is, no matter what I try I just can't get things to work.
I tried this first:
for body in self.physicsBody.allContactedBodies() as [SKPhysicsBody] {
}
But I get this error.
fatal error: array cannot be downcast to array of derived
I also tried this:
for object in self.physicsBody.allContactedBodies() {
let body = object as SKPhysicsBody
}
But this also crashes with the following:
And similarly I tried this:
for object in self.physicsBody.allContactedBodies() {
let body = object as? SKPhysicsBody
}
There is no crash, but "body" becomes nil.
And if I don't cast at all, I don't get a crash. For example:
for object in self.physicsBody.allContactedBodies() {
}
But obviously I need to cast if I want to use the actual type.
So then as a test I just tried this:
let object: AnyObject = SKPhysicsBody()
let body = object as SKPhysicsBody
And this also results in the same crash that is in the picture.
But other types won't crash. For example, this won't crash.
let object: AnyObject = SKNode()
let node = object as SKNode
So my question is, how can I correctly loop through the allContactedBodies array?
Edit: I am running Xcode 6 beta 4 on iOS 8 beta 4 device.
Edit 2: More Information
Ok so I just did some more testing. I tried this:
let bodies = self.physicsBody.allContactedBodies() as? [SKPhysicsBody]
If "allContactedBodies" is empty, then the cast is successful. But if "allContactedBodies" contains objects, then the cast fails and "bodies" will become nil, so I can't loop through it. It seems that currently it is just NOT POSSIBLE to cast AnyObject to SKPhysicsBody, making it impossible to loop through the "allContactedBodies" array, unless someone can provide a workaround.
Edit 3: Bug still in Xcode 6 beta 5. Workaround posted below still works
Edit 4: Bug still in Xcode 6 beta 6. Workaround posted below still works
Edit 5: Disappointed. Bug still in Xcode 6 GM. Workaround posted below still works
EDIT 6: I have received the following message from Apple:
Engineering has provided the following information:
We believe this issue has been addressed in the latest Xcode 6.1 beta.
BUT IT IS NOT, the bug is still in Xcode 6.1.1!!! Workaround still works.
Edit 7: Xcode 6.3, still not fixed, workaround still works.
After much trial and error, I have found a workaround to my problem. It turns out that you don't need to downcast at all to access the properties of the SKPhysicsBody, when the type is AnyObject.
for object in self.physicsBody.allContactedBodies() {
if object.node??.name == "surface" {
isOnSurface = true
}
}
Update: This was a bug, and it's fixed in iOS 9 / OS X 10.11. Code like the following should just work now:
for body in self.physicsBody.allContactedBodies() {
// inferred type body: SKPhysicsBody
print(body.node) // call an API defined on SKPhysicsBody
}
Leaving original answer text for posterity / folks using older SDKs / etc.
I noticed this in the related questions sidebar while answering this one, and it turns out to be the same underlying issue. So, while Epic Byte has a workable workaround, here's the root of the problem, why the workaround works, and some more workarounds...
It's not that you can't cast AnyObject to SKPhysicsBody in general — it's that the thing(s) hiding behind these particular AnyObject references can't be cast to SKPhysicsBody.
The array returned by allContactedBodies() actually contains PKPhysicsBody objects, not SKPhysicsBody objects. PKPhysicsBody isn't public API — presumably, it's supposed to be an implementation detail that you don't see. In ObjC, it's totally cool to cast a PKPhysicsBody * to SKPhysicsBody *... it'll "just work" as long as you call only methods that the two classes happen to share. But in Swift, you can cast with as/as?/as! only up or down the type hierarchy, and PKPhysicsBody and SKPhysicsBody are not a parent class and subclass.
You get an error casting let obj: AnyObject = SKPhysicsBody(); obj as SKPhysicsBody because even the SKPhysicsBody initializer is returning a PKPhysicsBody. Most of the time you don't need to go through this dance (and have it fail), because you get a single SKPhysicsBody back from an initializer or method that claims to return an SKPhysicsBody — all the hand-wavy casting between SKPhysicsBody and PKPhysicsBody is happening on the ObjC side, and Swift trusts the imported ObjC API (and calls back to the original API through the ObjC runtime, so it works just as it would in ObjC despite the type mismatch).
But when you cast an entire array, a runtime typecast needs to happen on the Swift side, so Swift's stricter type-checking rules come into play... casting a PKPhysicsBody instance to SKPhysicsBody fails those rules, so you crash. You can cast an empty array to [SKPhysicsBody] without error because there aren't any objects of conflicting type in the array (there aren't any objects in the array).
Epic Byte's workaround works because Swift's AnyObject works like ObjC's id type: the compiler lets you call methods of any class on it, and you just hope that at runtime you're dealing with an object that actually implements those methods.
You can get back a little bit of compile-time type safety by explicitly forcing a side cast:
for object in self.physicsBody.allContactedBodies() {
let body = unsafeBitCast(object, SKPhysicsBody.self)
}
After this, body is an SKPhysicsBody, so the compiler will let you call only SKPhysicsBody methods on it... this behaves like ObjC casting, so you're still left hoping that the methods you call are actually implemented by the object you're talking to. But at least the compiler can help keep you honest. (You can't unsafeBitCast an array type, so you have to do it to the element, inside the loop.)
This should probably be considered a bug, so please let Apple know if it's affecting you.

Resources