Just trying to be extra careful here...
If I have an app that saved a value in UserDefaults like this in Objective-C:
NSString *newString = textField.text;
[[NSUserDefaults standardUserDefaults] setObject: newString forKey:#"textKey"];
Would this be the proper way of checking whether this value exists when I release an update to the app that is now coded in Swift:
if (UserDefaults.standard.object(forKey: "textKey") as? String) != nil {
print("There is a string")
} else {
print("No string exists")
}
I try to use .string(forKey:) and .bool(forKey:) since they've been introduced, but is it safest since this was saved as an object to pull it out as an object and then test it with "as? String"?
Trickier version of the same question:
An NSMutableArray of NSDictionary objects was saved as an object in UserDefaults
if let oldData = UserDefaults.standard.object(forKey: "theData") as? [NSMutableDictionary] {
}
Will NSDictionary and NSMutableDictionary be interchangeable here?
Thanks!
[NS]UserDefaults is backed by a plist and a dictionary. The Objective-C string was saved to the plist as a <string>Some Text</string>.
That same plist loaded in Swift gives a string for that key.
So you should have no issue using UserDefaults.string to read the value stored with Objective-C. Of course you still need to verify the value actually exists.
if let str = UserDefaults.standard.string(forKey: "textKey") {
print("Found \(str)")
} else {
print("No string for key")
}
Even if the original value isn't a string, using UserDefault.string is safe. It will simply return nil for non-string values.
With regard to NSDictionary and NSMutableDictionary, note that UserDefaults only retrieves immutable NSDictionary. You can't read an NSMutableDictionary (or array) from UserDefaults.
Note you can safely use a Swift dictionary to read the original NSDictionary. No need to use Objective-C classes.
Yes your solution is safe, if the value you saved in the Objective C code is indeed a String type than, when you retrieve that value from NSUserDefaults, it will be a String and will correctly be coerced to a String by the ? operator of your Swift code.
The recommended way (in Swift and Objective-C) is to register each key value pair to provide a default value. This default value is considered until the value is changed the first time.
let userDefaults = UserDefaults.standard
let defaultValues : [String : Any] = ["textKey" : "", "dictionaryKey" : [:]]
userDefaults.register(defaults: defaultValues)
Now you can safely write
if UserDefaults.standard.string(forKey: "textKey")!.isEmpty {
print("No string exists")
} else {
print("There is a string")
}
Of course the developer is responsible for overwriting a value with the same type.
NSMutableDictionary is not related to Swift Dictionary. To get a mutable object just use the var keyword.
var dictionary = UserDefaults.standard.object(forKey: "dictionaryKey") as! [String:Any]
Related
My project uses both Swift and Objective C. I have a singleton written in Objective C which as a property of the type NSDictionary.
#property(nonatomic, strong) NSDictionary *currentDictionary;
This is an old class that's being used throughout my project. I am trying to use this class in a Swift class like this
if let dict = DataManager.sharedManager().currentDictionary
{
//
}
The problem i am facing is that currentDictionary is being set using data from the server. At times this might be
In Objective C classes, i can handle the situation with the following check
if ([currentDictionary isKindOfClass: [NSNull class]])
{
// Do nothing
}
But i am not sure how to implement a similar check in Swift. I tried the following
if let data = DataManager.sharedManager().currentDictionary as? NSNull
{
return
}
But it doesn't work and also i get a compiler warning :
"Cast from "[NSObject : AnyObject]!" to unrelated type "NSNull" always fails"
This is different from checking for Null values within the dicitonary as they will be 'AnyObject's and i can try casting them into the type i want to check.
Can someone please provide any pointers on how to handle this situation properly
First of all, if the variable can contain something else that NSDictionary, don't set its type to NSDictionary. Swift is type safe and it will trust the declared type.
The easiest workaround would be to make it id in Objective-C.
Then in Swift you can simply:
guard let data = DataManager.sharedManager().currentDictionary as? NSDictionary else {
return
}
If you can't change the original code, just create a Swift accessor with correct type using a category, e.g.
#interface DataManager (Swift)
// solution 1, will be converted to AnyObject in Swift
- (id)currentDictionaryForSwift1;
// solution 2, let's handle NSNull internally, don't propagate it to Swift
- (NSDictionary *)currentDictionaryForSwift2;
#end
#implementation DataManager
- (id)currentDictionaryForSwift1 {
return self.currentDictionary;
}
- (NSDictionary *)currentDictionaryForSwift2 {
if (self.currentDictionary == [NSNull null]) {
return nil;
}
return self.currentDictionary;
}
#end
I would recommend you to handle NSNull internally. There should be no need to for other code to handle nil and NSNull separately.
You could actually solve it already in the getter:
- (NSDictionary *)currentDictionary {
if (_currentDictionary == [NSNull null]) {
return nil;
}
return _currentDictionary;
}
or in the setter
- (void)setCurrentDictionary:(NSDictionary *)currentDictionary {
if (currentDictionary == [NSNull null]) {
_currentDictionary = nil;
} else {
_currentDictionary = currentDictionary;
}
}
As you can see, there are multiple solutions but the best solution should improve even your Obj-C code. The difference between NSNull and nil should be handled locally and not propagated.
If you want to validate wether currentDictionary is nil or not, you can use:
guard let currentDictionary = DataManager.sharedManager().currentDictionary else {
return
}
Replace guard-else statement with if-else if you don't want to return early.
If you want to validate contents of currentDictionary is NSNull or not:
if let value = DataManager.sharedManager().currentDictionary["key"] {
// do something with value
}
I have a model object written in Objective-C, which has a property of the type NSMutableArray.
It can either be nil or has a valid object reference.
I am using bridging header and I have a few files written in Swift.
In the Swift file I want to iterate through the objects in the array, only if it has something in it.
How can I achieve this?
I have tried things such as:
if let a = MyObj.myArray {
}
if(MyObj.myArray != nil) {
}
if(MyObj.myArray != NSNull()) {
}
I finally got the hang of optionals in Swift code alone, but I am not able to understand the behavior when I am passing around objects written in Objective-C in Swift code.
My Actual Code Looks like this :
Code :
if let values = attribute.values {
for val in values {
print(val);
}
return true;
}
Exception:
-[NSNull countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance
The error message tells us the problem:
-[NSNull countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance
Your attribute.values isn't nil or an NSMutableArray. It's [NSNull null].
NSNull is a real object that's used instead of nil in collections, because the common Foundation collections (NSArray and NSDictionary) cannot store nil as a value.
We most often come across NSNull when decoding a data structure from JSON. When the JSON decoder finds null (a JSON literal) in an array or object, it stores [NSNull null] in the corresponding NSArray or NSDictionary.
Something like this should work:
if let values = attribute.values where values != NSNull() {
for val in values {
print(val);
}
return true;
}
You should use the new Nullability annotation syntax for your Objective-C properties. These annotations help communicate to Swift whether you intend for an object to be nil or not. For example:
#property (nullable) NSMutableArray* myArray;
With these annotations, your Objective-C objects should work just like a native Swift object. You can do an if let or any other nil check.
if let arr = myObject.myArray {
// Do something with arr
}
else {
// Object is nil do something else
}
if myObject.myArray == nil {
// Array is nil, handle it.
}
You can read more about Nullability annotations for Objective-C at the Apple Swift blog.
https://developer.apple.com/swift/blog/?id=25
This is the userinfo of my silent push notification. How to get value of result in objective-c?
{
aps = {
"content-available" = 1;
result = STOP;
sound = "";
};
}
With KVC you can dive into the nested dictionary with one method call.
[userInfo valueForKeyPath:#"aps.result"];
The accepted answer uses KVC's valueForKey:. Please be aware, that there are differences to NSDictionary's own method objectForKey:. While the first one only takes NSStrings as needed in the question, the second one takes any object that conforms to NSCopying protocol (id<NSCopying>). NSString, NSNumber, NSDate,... do and it is easy to implement NSCopying for your own classes.
Also NSDictionary supports subscription. from the docs:
objectForKeyedSubscript:
Returns the value associated with a given key.
Discussion
This method behaves the same as objectForKey:
Unless you want to do something KVC-specific as in my answer, you should prefer objectForKey: and actually you than could write the compact code
id obj = userInfo[#"aps"][#"result"];
or if you know the class of the object (NSString in your example)
NSString *obj = userInfo[#"aps"][#"result"];
NSLog(#"%#",[[Response valueForKey:#"aps"] valueForKey:#"result"]);
I'm trying to map a dictionary of strings from a JSON fetch to a KVC compliant NSManagedObject, I can successfully use setValue: forKey: but i fail to see how I can map types.
For example I shouldn't be able to set a date to any random string: Printing description of myDate:
asdfsadf
however it worked.
I had a look at https://stackoverflow.com/a/5345023/828859 which provided some useful answers. I can go in and create validation for every single property... but that doesn't seem very DRY because ill have to validate every date and set the out value separately each time i have a date.
I would prefer to mutate by type before I use setValue: forKey: but I don't know how to discriminate on the property type.
What I want to do:
switch([object typeforkey:key]){
case #"NSDate":
//...
value = mutatedDate
//...
}
[object setValue:value forKey:key];
You can ask an object what kind of class it has been instantiated as. So you can do something like:
id myObject = [myDictionary objectForKey:key];
if ([myObject isKindOfClass:[NSDate class]]) {
// Do stuff
}
else if ([myObject isKindOfClass:[NSString class]]) {
// Do other stuff
}
This is because objects are structs containing a pointer with the ivar name isa pointing to an object of type Class, so you can always ask an object what kind of class it comes from.
I ended up using another dictionary for property type mapping. Then a object mapping object checks the object to be map abides by this particular protocol and uses the property type dictionary to convert each property before using setValue:forKey:.
I'm parsing some input which produces a tree structure containing NSDictionary instances on the branches and NSString instance at the nodes.
After parsing, the whole structure should be immutable. I feel like I'm jumping through hoops to create the structure and then make sure it's immutable when it's returned from my method.
We can probably all relate to the input I'm parsing, since it's a query string from a URL. In a string like this:
a=foo&b=bar&a=zip
We expect a structure like this:
NSDictionary {
"a" => NSDictionary {
0 => "foo",
1 => "zip"
},
"b" => "bar"
}
I'm keeping it just two-dimensional in this example for brevity, though in the real-world we sometimes see var[key1][key2]=value&var[key1][key3]=value2 type structures. The code hasn't evolved that far just yet.
Currently I do this:
- (NSDictionary *)parseQuery:(NSString *)queryString {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *pairs = [queryString componentsSeparatedByString:#"&"];
for (NSString *pair in pairs) {
NSRange eqRange = [pair rangeOfString:#"="];
NSString *key;
id value;
// If the parameter is a key without a specified value
if (eqRange.location == NSNotFound) {
key = [pair stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
value = #"";
} else {
// Else determine both key and value
key = [[pair substringToIndex:eqRange.location] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if ([pair length] > eqRange.location + 1) {
value = [[pair substringFromIndex:eqRange.location + 1] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
} else {
value = #"";
}
}
// Parameter already exists, it must be a dictionary
if (nil != [params objectForKey:key]) {
id existingValue = [params objectForKey:key];
if (![existingValue isKindOfClass:[NSDictionary class]]) {
value = [NSDictionary dictionaryWithObjectsAndKeys:existingValue, [NSNumber numberWithInt:0], value, [NSNumber numberWithInt:1], nil];
} else {
// FIXME: There must be a more elegant way to build a nested dictionary where the end result is immutable?
NSMutableDictionary *newValue = [NSMutableDictionary dictionaryWithDictionary:existingValue];
[newValue setObject:value forKey:[NSNumber numberWithInt:[newValue count]]];
value = [NSDictionary dictionaryWithDictionary:newValue];
}
}
[params setObject:value forKey:key];
}
return [NSDictionary dictionaryWithDictionary:params];
}
If you look at the bit where I've added FIXME it feels awfully clumsy, pulling out the existing dictionary, creating an immutable version of it, adding the new value, then creating an immutable dictionary from that to set back in place. Expensive and unnecessary?
I'm not sure if there are any Cocoa-specific design patterns I can follow here?
Expensive and unnecessary?
Yes. Apple's Cocoa APIs regularly say they return an immutable object, but actually return a mutable subclass that's been cast to the immutable version. This is a standard operating procedure and an accepted Cocoa design principle. You just trust that your clients aren't going to cast it back to a mutable version and change things from underneath you.
From Cocoa Core Competencies: Object Mutability:
Receiving Mutable Objects
When you call a method and receive an object in return, the object could be mutable even if the method’s return type characterizes it as immutable. There is nothing to prevent a class from declaring a method to return an immutable object but returning a mutable object in its implementation. Although you could use introspection to determine whether a received object is actually mutable or immutable, you shouldn’t. Always use the return type of an object to judge its mutability.
See also: Cocoa Fundamentals Guide: Cocoa Objects.