Swift: Unable to downcast AnyObject to SKPhysicsBody - ios

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.

Related

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.

Realm crash on iOS 10 with 'String'

I have recently released a new version of our app and during beta testing, it's crashing on all iOS 10 devices but not other versions. Since we have Crashlytics, we found a strange crash message in the backend that we can confirm is the reason all iOS 10 crashing since it's 100% iOS 10 and there's like 40 of them.
It reads as follows:
Fatal Exception: RLMException
Property Article.id is declared as String, which is not a supported managed Object property type. If it is not supposed to be a managed property, either add it to ignoredProperties() or do not declare it as #objc dynamic. See https://realm.io/docs/swift/latest/api/Classes/Object.html for more information.
And here's the object:
class Article: Object {
#objc dynamic var id: String = UUID().uuidString
// others...
override static func primaryKey() -> String? {
return "id"
}
}
As you can see, this is perfectly nomral and runs fine on other iOS. In Realm's doc, it LITERALLY SAYS to use String with #objc dynamic and there's no way it's unsupported. I suspect there's nothing special about Article.id, and since Article starts with A, it happens to be the first String property of all realm Objects. Maybe somehow all Strings stopped working on iOS 10?
Can anyone offer some advice or insights?(Please don't say things like drop iOS 10 support. For now, we need it.)
We ran into the same issue a couple of times, trying to drag Realm fully into Swift. This is not really the answer but more of a workaround we've had success with when needing backward compatibility.
It's an ObjC object, not Swift.
There's something going on with the bridging, perhaps conforming to NSCopy'ing or something along those line, so just change it to read
#objc dynamic var id = NSUUID().uuidString
See the Getting Started Guide in the Models section which calls for using NSUUID
NSUUID: An object representing a universally unique value that bridges
to UUID; use NSUUID when you need reference semantics or other
Foundation-specific behavior.
Turns out it was a Realm's bug. We happen to have another app that runs just fine on iOS 10, and after some inspection we realized that it was using Realm 4.3.2, instead of 4.4.1. After we downgraded Realm to 4.3.2, this problem disappeared.

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

Swift Syntax Update? Classes not allowed in protocol. Can't fill SKTexture

I'm currently working on a tutorial for creating
an iOS Isometric Game. You can find this one here.
I just started coding Swift and because a lot of Syntax errors
appeared while working with tutorials a little older than 3 months
I asked my self if there were some main updates in Swift lately.
Until now, myself or XCode itself managed to fix those little issues, but
I cant help myself with this:
protocol TextureObject {
class var sharedInstance: TextureDroid {get}
var texturesIso:[[SKTexture]?] {get}
var textures2D:[[SKTexture]?] {get}
}
This is the protocol I'm trying to set (exactly like in the tutorial), but XCode won't let me define the class variable. The error code is the following:
"Class properties are only allowed within classes:
use static to declare a static property"
Both replacing "class" to "static" (which makes no logical sense to me) and
deleting the protocol (and define the class that should inherit without the use of the protocol) lead to another error in this code:
class TextureDroid: TextureObject {
class var sharedInstance: TextureDroid {
return textureDroid
}
let texturesIso:[[SKTexture]?]
let textures2D:[[SKTexture]?]
init() {
texturesIso = [[SKTexture]?](count: 2, repeatedValue: nil)
textures2D = [[SKTexture]?](count: 2, repeatedValue: nil)
//Idle
texturesIso[Action.Idle.rawValue] = [
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.N, Action.Idle)),
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.NE, Action.Idle)),
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.E, Action.Idle)),
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.SE, Action.Idle)),
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.S, Action.Idle)),
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.SW, Action.Idle)),
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.W, Action.Idle)),
SKTexture(imageNamed: "iso_3d_"+textureImage(Tile.Droid, Direction.NW, Action.Idle)),
]
This error appears in all the lines where I want to fill the texturesIso
Immutable value 'self.texturesIso' may not be assigned to
Here are my questions:
How can I fix the first error? Is there a new way to define classes inside a protocol?
Are those two errors connected, or is the second just appearing, because i managed to eliminate the first?
How can I fill the SKTexture in the right way? texturesIso.append won't work either.
Am I right with the Swift Update? If yes, is there an overview of all the sudden changes to Swift, because I could'n find one.
I would really appreciate anyone's help, thanks a lot in advance.
This tutorial appears to use an older version of swift. You can either use an older version of xcode that uses an earlier swift compiler or update the code. I will attempt to help you update the code.
"Protocol declarations can’t contain class, structure, enumeration, or other protocol declarations. The protocol member declarations are discussed in detail below." - Apple docs on swift protocol declaration
Just change the class var to static. This doesn't cause the second error, its just that the compiler stops after the first one so the second one isn't revealed.
Immutable value 'self.texturesIso' may not be assigned to
var texturesIso:[[SKTexture]?] {get}
defines a getter but no setter, therefore there is no way for you to set the self.texturesIso property. Change that to {get set}, and doing the same for the textures2d property might be necessary too, and change them to vars in the class.
There was a swift update yes. The new version is Swift 1.2
On top of answering this specific question, when I downloaded the tutorial code, to get it to compile, I also needed to change the touchesEnded to use Set instead of NSSet, which breaks touches.anyObject, so I used touches.first instead. Also, it was trying to change immutable tiles in gameScene, so I changed that to a var.
Note: I just downloaded the code and got it to compile and run, I'm not exactly sure how it is supposed to run, but it seemed ok to me. Also Does swift have class level static variables? has some good information on computed class variables vs static variables, although it doesn't talk about the protocol bit of the question

How do I use CFArrayRef in Swift?

I'm using an Objective-C class in my Swift project via a bridging header. The method signature looks something like this:
- (CFArrayRef)someMethod:(someType)someParameter;
I started by getting an instance of the class, calling the method, and storing the value:
var myInstance = MyClassWithThatMethod();
var cfArr = myInstance.someMethod(someValue);
Then try to get a value in the array:
var valueInArrayThatIWant = CFArrayGetValueAtIndex(cfArr, 0);
However I get the error Unmanaged<CFArray>' is not identical to 'CFArray'. What does Unmanaged<CFArray> even mean?
I looked through How to convert CFArray to Swift Array? but I don't need to convert the array to a swift array (however that would be nice). I just need to be able to get values from the array.
I have also tried the method of passing the CFArray into a function outlined in this answer:
func doSomeStuffOnArray(myArray: NSArray) {
}
However I get a similar error when using it:
doSomeStuffOnArray(cfArr); // Unmanaged<CFArray>' is not identical to 'NSArray'
I am using CFArray because I need to store an array of CGPathRef, which cannot be stored in NSArray.
So how am I supposed to use CFArray in Swift?
As explained in
Working with Core Foundation Types, there are two possible solutions when
you return a Core Foundation object from your own function that is imported in Swift:
Annotate the function with CF_RETURNS_RETAINED or CF_RETURNS_NOT_RETAINED.
In your case:
- (CFArrayRef)someMethod:(someType)someParameter CF_RETURNS_NOT_RETAINED;
Or convert the unmanaged object to a memory managed object with takeUnretainedValue() or takeRetainedValue() in Swift. In your case:
var cfArr = myInstance.someMethod(someValue).takeUnretainedValue()
An Unmanaged is a wrapper for an actual CF value. (Sort of like an optional.) It's there because ARC can't tell from looking at the declaration of someMethod: whether that method retains the value it returns.
You unwrap an Unmanaged by telling ARC what memory management policy to use for the value inside. If someMethod calls CFRetain on its return value:
let cfArr = myInstance.someMethod(someValue).takeRetainedValue()
If it doesn't:
let cfArr = myInstance.someMethod(someValue).takeUnretainedValue()
After you do that, cfArr is a CFArray, so you can use the bridging tricks from the other questions you linked to for accessing it like a Swift array.
If you own the code for someMethod you can change it a bit to not need this. There's a couple of options for that:
Annotate with CF_RETURNS_RETAINED or CF_RETURNS_NOT_RETAINED to tell the compiler what memory behavior is needed
Since it's an ObjC method, bridge to NSArray and return that--it'll automatically become an [AnyObject] array in Swift.

Resources