Object (Optional Any): get elements - ios

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.

Related

Crash when using error.localizedDescription from completion

I'm trying to check if error.localizedDescription contains a certain string but i keep getting a crash
if error.localizedDescription.contains("\"api.error.cardRejected.2000\"") {
failCompletion()
}
I have even tried to even use another way
if let description = (error! as NSError).userInfo[NSLocalizedDescriptionKey] as? String {
if description.contains("api.error.cardRejected.2000") {
failCompletion()
}
}
I still keep getting the same crash in the logs saying
-[__NSDictionaryM domain]: unrecognized selector sent to instance 0x60000046b520
It works when i check using the debugDescription but i would like to check using the localizedDecription since the debug one only works when debugging
NSError localized description is autogenerated from what's inside, here is what API tells:
/* The primary user-presentable message for the error, for instance for NSFileReadNoPermissionError: "The file "File Name" couldn't be opened because you don't have permission to view it.". This message should ideally indicate what failed and why it failed. This value either comes from NSLocalizedDescriptionKey, or NSLocalizedFailureErrorKey+NSLocalizedFailureReasonErrorKey, or NSLocalizedFailureErrorKey. The steps this takes to construct the description include:
1. Look for NSLocalizedDescriptionKey in userInfo, use value as-is if present.
2. Look for NSLocalizedFailureErrorKey in userInfo. If present, use, combining with value for NSLocalizedFailureReasonErrorKey if available.
3. Fetch NSLocalizedDescriptionKey from userInfoValueProvider, use value as-is if present.
4. Fetch NSLocalizedFailureErrorKey from userInfoValueProvider. If present, use, combining with value for NSLocalizedFailureReasonErrorKey if available.
5. Look for NSLocalizedFailureReasonErrorKey in userInfo or from userInfoValueProvider; combine with generic "Operation failed" message.
6. Last resort localized but barely-presentable string manufactured from domain and code. The result is never nil.
*/
open var localizedDescription: String { get }
so, it is crashed (probably at step 6.) then this NSError is incorrectly constructed - so find who & how constructed it, possibly at some layer on underlying errors some key of userInfo unexpectedly is set as NSDictionary instead of NSError.

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

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.

How to determine HKQuantitySample quantityType?

I am trying to determine what kind of HKQuantityTypeIdentifier matches the measurements provided in a particular Health Kit sample To determine this, I have tried the following:
if(hkSample.quantityType == HKQuantityTypeIdentifierDistanceCycling) ...
if([hkSample.quantityType isEqual: HKQuantityTypeIdentifierDistanceCycling]) ...
and
if([hkSample.quantityType isEqualToString: HKQuantityTypeIdentifierDistanceCycling]) ...
The first two options run but give a negative result even when the types should be matching and the last option gives a run-time NSException error for an unknown method. What's the proper way to determine which type maths an HKQuantitySample type?
quantityType is an HKQuantityType, not a string, so it doesn’t respond to -isEqualToString:. You need to compare it with another HKQuantityType via -isEqual:, like this:
if ([hkSample.quantityType isEqual:[HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDistanceCycling]])

How to handle Dictionary with missing key

I have a dictionary with info (name, age etc) that I need to use in a tableView to populate a profile viewController.
My problem is that I dont know if the user for example has added his name, and therefore if there is a key = "name".
So when I index into the dictionary
print("firstname: \(userDocument["name"]!["first"] as! String)")
the app crash if the key does not exist..
How can I avoid this? should I use if let on all the keys ?
I tried:
print("firstname: \(userDocument["name"]?["first"] as? String)")
but then I get the Optional(Elon Musk) and I dont what that as the label.text.
Any help is much appreciated! Thank you
How about:
var defaultName = "unspecified"
print("firstname: \((userDocument["name"]?["first"] as? String) ?? defaultName)")
This checks whether (userDocument["name"]?["first"] as? String) contains a value, and provides a substitute (defaultName) if it doesn't. You can change the substitute text to whatever you see fit.
The ! means "please crash if it doesn't exist". So what you wrote is crashing because you told the compiler to make it crash. If you don't want the crash, don't do that.
Use ? in the right places which will give you an optional value. Then you can use optional ?? defaultValue to substitute a value in case the optional is nil, or do whatever is appropriate if the value isn't there.

Casting generic array in swift cause fatal error

I have a class which acts as a BLL, wrapping a service protocol. The service protocol provides a list of SerializableObjectProtocol objects. For instance, I have User, which implements SerializedObjectProtocol.
The following function casts a SerializedObjectProtol array into a User
public func Get() -> [T]
{
let result = self._service.Get()
return result as! [T]
}
As a result, I am getting the following error:
array element cannot be bridged to Objective-C
I am aware that the code is error prone, because if the object is not T, down casting cannot happen. As a result, here is what I can verify:
T in constrained to implement SerializedObjectProtol i.e.
class DataLayer<T:SerializableObjectProtocol>
T is type User. The result is an array of user. i.e. [User]
I can get around this issue, but I have to manually cast each item. As a result, this works perfectly fine:
var returnArray = [T]()
for item in result
{
returnArray.append(item as! T)
}
return returnArray;
I have just picked up Swift for a project so I have limited experience with it. As a result, I have gone out to see if what I am trying is possible (casting array [S] to [T]). It seems that it is possible if the array is [Any].
Is this a valid operation in Swift? Or is casting this way not possible.
Generally it is not possible to cast directly between an array of Any to the type it contains, because Any has a completely different representation in memory: sizeof(Any) isn't equal to sizeof(User)! An array of 10 Anys might have a length of 320 bytes, but 10 Users only need 80 bytes, the same applies to any protocol. Conclusion: You need to cast every single item.
Maybe do it like this:
return results.map{ $0 as! User }
or if you're not sure whether every item is a User, you can only return the Users like this:
return results.flatMap{ $0 as? User }
If you're still having problems, please post some minimal code that still produces the error, it's really hard to understand what your code looks like without the actual code

Resources