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

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

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.

"Cannot find 'RealmProperty' in scope" when trying to use optional double, Swift

I'm using Realm Sync to store data for my iOS app, coded in Swift. I wanted to create an optional double property (budget) for a Realm object (User_budgets). I created the object in the Realm schema and then copied in the Data model SDK that Realm produces which is as below:
import Foundation
import RealmSwift
class User_budgets: EmbeddedObject {
let budget = RealmProperty<Double>()
#objc dynamic var date: Date? = nil
}
I then get the error: "Cannot find 'RealmProperty' in scope". I tried changing the code to the below:
#objc dynamic var budget: Double? = nil
But then I get the error: "Property cannot be marked #objc because its type cannot be represented in Objective-C"
I've had a search but can't seem to find anyone who's had this issue before. There's an easy work around, which is simply to make the budget property required (non-optional), but it would be good to know how to be able to create optional double properties in the future. Can anyone help me out?
I believe you're using the wrong definition for that optional as it's only available in beta 10.8.0-beta.0:
What you have is
let budget = RealmProperty<Double>()
and for all other releases it should be
let budget = RealmOptional<Double>()
See RealmProperty and RealmOptional
oh and here's a link to all of the Support Property Types

Implementing Methods from Objective C Library with Swift

I am trying to implement the following method in swift:
From the class FLIROneSDKImageReceiverDelegate, which is subclassed inside my ViewController class as so:
class ViewController: UIViewController, FLIROneSDKImageReceiverDelegate,
FLIROneSDKStreamManagerDelegate,
FLIROneSDKImageEditorDelegate{
Note that I have already created a bridging header etc.
In the FLIROneSDKImageReceiverDelegate header file:
- (void) FLIROneSDKDelegateManager:(FLIROneSDKDelegateManager *)delegateManager didReceiveBlendedMSXRGBA8888Image:(NSData *)msxImage imageSize:(CGSize)size;
Am I wrong in thinking that this is the correct way to implement this function?
func FLIROneSDKDelegateManagerdidReceiveBlendedMSXRGBA8888ImageimageSize(delegateManager: FLIROneSDKDelegateManager!, msxImage: NSData, size: CGSize){
Note that FLIROneSDKDelegateManager is a class.
Off the top of my head, but try this:
func FLIROneSDKDelegateManager(delegateManager: FLIROneSDKDelegateManager!, didReceiveBlendedMSXRGBA8888Image msxImage: NSData!, imageSize size: CGSize) {
// method imp
}
#Laxsnor's solution in the comments on the answer by #aaron-wojnowski helped me too, thanks both.
To consolidate:
The problem is a conflict created by the name FLIROneSDKDelegateManager being used as a both a class name and a function name - which seems to be OK in Objective-C but not in Swift.
Replacing the class FLIROneSDKDelegateManager with NSObject in the function parameter seems to solve the problem without side-effects. This has to be done in both the Objective-C protocol header file and the Swift delegate class source file.
NOTE I also found this same solution applied more broadly to Swift-ify the entire FLIROneSDK at https://github.com/jruhym/flirmebaby.
Happy developing for FLIROne on Swift. (I'm new to FLIROne and relatively new to Swift so apologies if my language isn't quite precise enough.)

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.

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