Is it possible to set an initial value when initializing CBCharacteristic? - ios

Admittedly, I have a hard time with the wording of the Apple Docs so I might be missing something. However, it doesn't seem to be the case.
Here's the question.
Given:
let charSomeUUID = CBUUID(string: "12345678-1234-12234-1234-123456789qwe")
someCharacteristic = CBMutableCharacteristic(type: charSomeUUID, properties: [CBCharacteristicProperties.read, CBCharacteristicProperties.notify], value: nil, permissions: CBAttributePermissions.readable)
How would one provide an initial value for the value field?
I see how to do it in didReceiveRead. But I'd like to set an initial value.
I've tried putting a string into a data object and putting it in the value spot and that doesn't seem to work. And the swift 3 equivalent of the (Objective C) example in the Apple docs doesn't seem to work (anymore?).
Thanks for the help.

Apologies.
I actually had another piece of code that was wrong that ended up being the real problem.
For future reverence here is code to show that what I originally thought would not work does, in fact, work.
let stringValue : String = "something"
let dataValue = stringValue.data(using: String.Encoding.utf8)
// Device Info
someCharacteristic = CBMutableCharacteristic(type: someUUID, properties: CBCharacteristicProperties.read, value: dataValue, permissions: CBAttributePermissions.readable)
NOTE: When adding a value by this method your characteristic needs to be read only. Still some things to read up on there.
Failing to use read only results in the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Characteristics with cached values must be read-only'
Sulthan, thank you for your attention. Sorry for the mix up.

Related

Accessing CFArray causes crash in Swift

My following code crashes with EXC_BAD_ACCESS, and I do not understand why. My initial understanding is that the memory retention in this case should be automatic, but it seems I am wrong... Maybe someone can help. Thank you! The code is written in Swift 5 and runs on iOS 15.2 in XCode 13.2.1.
Casting to NSArray causes trouble...
let someFont = CGFont("Symbol" as CFString)!
if let cfTags: CFArray = someFont.tableTags {
let nsTags = cfTags as NSArray
print(nsTags.count) // Prints: 16
let tag0 = nsTags[0] // CRASH: Thread 1: EXC_BAD_ACCESS (code=257, ...)
}
Alternatively, using CFArray-API causes also trouble (The crash message is about a misaligned pointer but the root cause seems also the bad access, which occurs e.g. if I replace UInt32.self by UInt8.self, and hence eliminate the alignment problem).
let someFont = CGFont("Symbol" as CFString)!
if let cfTags: CFArray = someFont.tableTags {
print(CFArrayGetCount(cfTags)) // Prints: 16
let tag0Ptr: UnsafeRawPointer = CFArrayGetValueAtIndex(cfTags, 0)!
tag0Ptr.load(as: UInt32.self)// CRASH :Thread 1: Fatal error: load from misaligned raw pointer
}
The issue here is that the CGFont API uses some advanced C-isms in their storage of table tags, which really doesn't translate to Swift: CGFontCopyTableTags() returns a CFArrayRef which doesn't actually contain objects, but integers. (This is technically allowed through CFArray's interface: it accepts void *s in C, into which you can technically stuff any integer which fits in a pointer, even if the pointer value is nonsense...) Swift expects CFArrays and NSArrays to only ever contain valid objects and pointers, and it treats the return values as such — this is why accessing via NSArray also fails (Swift expects an object but the value isn't an object, so it can't be accessed like a pointer, or retained, or any of the things that the runtime might expect to do).
Your second code snippet is closer to how you'll need to access the value: CFArrayGetValueAtIndex appears to return a pointer, but the value you're getting back isn't a real pointer — it's actually an integer stored in the array directly, masquerading as a pointer.
The equivalent to the Obj-C example from the CGFontCopyTableTags docs of
tag = (uint32_t)(uintptr_t)CFArrayGetValue(table, k);
would be
let tag = unsafeBitCast(CFArrayGetValueAtIndex(cfTags, 0), to: UInt.self)
(Note that you need to cast to UInt and not UInt32 because unsafeBitCast requires that the input value and the output type have the same alignment.)
In my simulator, I'm seeing a tag with a value of 1196643650 (0x47535542), which definitely isn't a valid pointer (but I don't otherwise have domain knowledge to validate whether this is the tag you're expecting).

Object (Optional Any): get elements

I'm trying to pass information with an observed notification in my app.
The standard way to do that, is to set the userinfo. However, the data I want to pass is a Set, not a dictionary.
So, I do this:
NotificationCenter.default.post(name: MY_NOTIFICATION_NAME, object:self.productIds)
The object arrives fine, but now I'm unable to get to it:
in the console I do this:
po notification.object!
▿ 2 elements
- 0 : ZTJ
- 1 : ZTM
However, when I try to get to one of the elements, I get this:
po notification.object![0]
error: <EXPR>:8:21: error: value of type 'Any' has no subscripts
notification.object![0]
What am I doing wrong?
You know that notification.object is a Set, but the compiler doesn't because it's declared as Any, which means it could be anything, and so it can't find which implementation of object[0] it should use.
To read this object you need to cast it to a set.
if let mySet = notification.object as? Set<MyType> {
// Do whatever
}
Keep in mind that the object property of Notification is designed to be used as a filter, If you pass a value when adding an observer, you'll only get notifications that are sent with that exact same object.
The userInfo dictionary is to send related information, like your set. In this case I would send a dictionary like this:
NotificationCenter.default.post(name: MY_NOTIFICATION_NAME, object: nil, userInfo: ["products": productIds])
The notification has an object type of Any?.
When you're poing it in the console, you're asking it to print its description, which Any can do.
When you're asking it to subscript, Any can't do that, because subscripting is not defined on that type. You need to cast it to the expected type:
po (notification.object as? [String])?[0]
In general, it's best to nail down the type of any Any as soon as you can. Think of Any as a box used to send things through the post. The first thing you do is open it and find out what's inside.

Unrecognized Selector sent to instance, not view related

I have an annoying problem in my iOS app. Suddenly, when I start my view controller with a table view inside, I get the following error:
Unrecognized selector sent to instance
After googling a lot about this problem, it seems like it is usually related to a falsely connected IBOutlet/IBAction or generally something with the UI.
But I could exclude these causes, since I removed all IBOutlets and the problem still persisted.
After long hours, I found the solution for this and wanted to share it in a Q&A fashion, in case someone else is having a hard time on this.
The problem was this line of code:
outletCommentShowMore.attributedText = NSAttributedString(
string: isExpanded ? "show less" : "show more",
attributes: [
NSAttributedStringKey.foregroundColor : Theme.primaryTextColor,
NSAttributedStringKey.underlineStyle : NSUnderlineStyle.styleSingle
]
)
The error was that I passed an enum (NSUnderline.styleSingle) directly into the attributes dictionary of an NSAttributedString. But it has to be the rawValue of the enum!
So after changing this to NSUnderline.styleSingle.rawValue my error disappeared
Hope this helps someone

Could not cast value of type 'SPTSession' (0x110afab98) to 'SPTSession' (0x10f17f638)

I am using the Spotify iOS SDK and I am storing a session using NSKeyedUnarchiver
However the following code causes an error(some code omitted just for clarity's sake):
sptSession = NSKeyedUnarchiver.unarchiveObjectWithData(sessionData as! NSData)
let auth = SPTAuth.defaultInstance()
auth.session = sptSession as! SPTSession
The last line of code is throwing the error Could not cast value of type 'SPTSession' (0x110afab98) to 'SPTSession' (0x10f17f638). I read that error as somehow there are two different types of SPTSessions but I'm not sure why or how to resolve the issue.
the answer seems to be related to this SO post:
Swift only way to prevent NSKeyedUnarchiver.decodeObject crash?
I have added an issue to the GH repo for Spotify's iOS SDK (https://github.com/spotify/ios-sdk/issues/759), and posted this temporary workaround as a comment on the issue:
for the time being, I have found a workaround - I can simply write NSKeyedArchiver.setClassName("SPTSession", for: SPTSession.self) before I archive the session, and write NSKeyedUnarchiver.setClass(SPTSession.self, forClassName: "SPTSession") before unarchiving.
While this fixes the crash, I will leave the issue open as I am not sure if a change can be made in the underlying library to fix this

InvalidFirebaseData error when saving multiple values in Firebase

The following code once worked fine and I was able to save multiple values to a Firebase record. However this no longer works since upgrading to Swift 3 (Xcode 8). I now get the following error:
*** Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(setValue:) Cannot store object of type _SwiftValue at mood. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
The above error always mentions the second value, regardless of what type it is (even if it is one of the supported types like NSString). Here's what I have:
postsRef.childByAutoId().setValue(["postedBy": self.currentUser?.uid, "mood": mood, "status": status, "date": convertedDate])
This still seems to comply with the docs on the Firebase website. What am I doing wrong?
Thanks!
Try :-
let toBePosted = ["postedBy": String(self.currentUser!.uid),
"mood": String(mood), "status": String(status),
"date": String(convertedDate)]
postsRef.childByAutoId().setValue(toBePosted){ (error, ref) -> Void in
}
As for converted date you will have to convert it to NSDate format when you retrieve it.
I just had this issue and after a few hours of toying around I think I've got a solution.
It looks like Firebase is wanting you to be very specific with your data types with Swift 3.
You need to straight up tell Swift what kind of dictionary you're going to have. In this case it's probably
let toBePosted : Dictionary<String, Any> = ["postedBy": String(self.currentUser!.uid),
"mood": String(mood), "status": String(status),
"date": String(convertedDate)]
"Any" is a new thing in Swift 3 I think. AnyObject doesn't work here because Swift redefined what AnyObjects are, and Strings/Ints dont seem to be them anymore.
Finally, it seems like you have to have absolutely no optionals in your dictionary. Maybe an optional is a _SwiftValue? Once I got rid of optionals and garunteed each value was going to be there it started working for me.
This worked for me, let me know if you're still having an issue.

Resources