Swift 3 NSArray compare to nil - swift

I'm trying to migrate an objc project to swift3. I'm not sure how can I compare an array to nil. I have found this topic, but that was 2 years ago and the swift's syntax has changed a lot.
If I have a code like this in swift:
let variable = something as? NSArray
if variable == nil {
// do something
}
It won't let me to compare this variable with nil, causing an error "comparing this variable, always returns false". I have tried comparing variable.description with " ", but does it do the same thing?
By "something" i meant:
var variable = dict.object(forKey: someString) as! NSArray
The main thing I wanted to do with this was:
var variable = dict.object(forKey: someString) as! NSArray
if variable == nil {
//create
}
else {
// append
}

That's what the optional unwrapping syntax is for. You can combine the unwrapping and cast into one if statement:
if let variable = something as? NSArray {
// variable is not nil and is an NSArray
// Now you can do something with it.
} else {
// Either something is nil or it is not able to be cast as an NSArray
// Handle this case.
}
I should also mention that if you don't need to use something in Objective-C, then you should use the Swift-native array type. This can be declared like this:
let someArray = ["string1", "string2"]

This line indicates that variable is and must be an NSArray. If dict.object(forKey: someString) is not an NSArray, this will cause a crash
var variable = dict.object(forKey: someString) as! NSArray
// ^
// This exclamation mark means you are certain this is an NSArray
// Also, because there is no question mark after NSArray, this variable
// is not optional. It cannot be nil
However, you then use
if variable == nil {
And this is where the warning comes from. The variable can never be nil, because the variable is not optional
What you probably want is:
if let variable = dict.object(forKey:someString) as? NSArray
This will return false if:
dict.object(forKey:someString) returns a nil object
the object returned is not an NSArray
After this variable is now a non-optional NSArray. It is guaranteed to be an NSArray and is guaranteed to not be nil. You can use it without unwrapping it. e.g.
if let variable = dict.object(forKey:someString) as? NSArray {
for element in variable {
}
}
else {
//The dict doesn't contain the object yet. `variable` is nil
//Create a new array and add it to dict
let newArray = ["First Value"]
dict[someString] = newArray
}

let variable = something as? NSArray
With this declaration, variable will be an optional type (NSArray?) and never nil. This is because casting with as? returns an optional value that either contains the successfully casted object or nothing. You can see this by alt-clicking the variable name in Xcode.
If you want to know whether it contains a value, you need to use the if let syntax:
if let variable = variable {
// variable is guaranteed to be an NSArray here.
}

You can also use this format with guard-else:
guard let variable = something as? NSArray else {
// your variable is nil. Do something if needed
}
// your variable is available in this scope. Do something when variable contains Array

Related

How can I update this Swift2 for-in loop without casting syntax to match Swift3's casting required syntax?

So I'm tryin gto make the following code work under swift 3, but no matter what I try I just cause new errors. I can't seem to figure out how to cast the dataArray object into anything that will pass. (Original dev didn't type it, and it's always set via notification objects, making tracing it's actual data type down... difficult; best I can tell it's just a dictionary generated from server JSON via parsing)
var dataArray:NSMutableArray = []
func foo(_ notification: Notification)
{
if let id = notification.object as? Int
{
for dataOut in dataArray where Int(dataOut["id"] as! Int) == id {
self.performSegue(withIdentifier: "fooSegue", sender: dataOut)
return;
}
}
}
Trying to compile this produces a syntax error about Type 'NSFastEnumerationIterator.Element' (aka 'Any') has no subscript members.
Is there a necessary reason why dataArray is of type NSMutableArray? If I were receiving some kind of Array object which I assert to contain elements of a Dictionary type, I would do the following:
if let id = id as? Int,
let data = dataArray as NSArray as? [[String:Any]]
{
for element in data where Int(element["id"] as! Int) == id
{
self.performSegue(withIdentifier: "fooSegue", sender: element)
return
}
}
Edited to cast dataArray from NSMutableArray to NSArray to [[String:Any]] and subscript element instead of data.

'-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object' cause crash

I am assigning UserInfo Dictionary from NSUserDefault as NSMutableDictionary.
Now my dicInfo is mutable dictionary, but object it contains are immutable.
So, when i am trying to replace those value it cause crash.
I am attaching image which describe crash report.
If any solution, to how to convert inner object of mutable dictionary to mutable.
Thanks
The NSDictionary class conforms to the NSMutableCopying protocol. As such, we can call the mutableCopy method on an NSDictionary to get an NSMutableDictionary copy of the object.
let dicInfo = userSharedDefaults?.objectForKey(UserDefaultKey.kUserBasicInfo) as? NSDictionary
let mutableDictionary = dicInfo?.mutableCopy
In Swift, we may need to cast this as the correct type:
let mutableDictionary = dicInfo?.mutableCopy as? NSMutableDictionary
var dicInfo = (userSharedDefault.object(forKey: "kUserbasicInfo") as! NSDictionary).mutableCopy() as! NSMutableDictionary
You can also create Mutable Dictionary as follows:
It will fix the crash.
let dicInfo = NSMutableDictionary.init(dictionary: userSharedDefaults?.objectForKey(UserDefaultKey.kUserBasicInfo) as! NSDictionary)
Neither use NSMutableDictionary nor mutableCopy() in Swift to get a mutable dictionary from UserDefaults.
Never do that.
Normally far be it from me to criticize other answers but NSMutableDictionary and mutableCopy() are indeed inappropriate API in Swift.
To get a dictionary from UserDefaults use dedicated method dictionary(forKey:. The default dictionary type is [String:Any]
To make an object mutable simply use the var keyword
var userbasicInfo : [String:Any]
if let dictionary = UserDefaults.standard.dictionary(forKey: UserDefaultKey.kUserBasicInfo) {
userbasicInfo = dictionary
} else {
userbasicInfo = [String:Any]()
}
userbasicInfo[kPin] = 5678
print(userbasicInfo)
UserDefaults.standard.set(userbasicInfo, forKey:UserDefaultKey.kUserBasicInfo)

Type casting operator

In Swift guide that as published on ibooks, as! operator was not mentioned. But in online reference and in some example code, they (i mean Apple in both cases) used as! operator.
Is there a difference between as and as! operators? If there are, can you explain please?
edit: Im so tired that i wrongly typed "is", instead of "as". That is now corrected...
as? will do an optional downcast - meaning if it fails it will return nil
so "Blah" as? Int will return Int? and will be a nil value if it fails or an Int if it does not.
as! forces the downcast attempt and will throw an exception if the cast fails. Generally you will want to favour the as? downcast
//ex optional as?
let nine = "9"
if let attemptedNumber = nine as? Int {
println("It converted to an Int")
}
//ex as!
let notNumber = "foo"
let badAttempt = notNumber as! Int // crash!
( You may find that you that an update is sitting there for the swift guide. It is mentioned for sure in the online version https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html )
operator is the forcefully unwrapped optional form of the as? operator. As with any force unwrapping though, these risk runtime errors that will crash your app should the unwrapping not succeed.
Further, We should use as to upcast if you wish to not write the type on the left side, but it is probably best practice to write it with normal typing as shown above for upcasting.
Example:
You use the as keyword to cast data types. UIWindow rootViewController is of type UIViewController. You downcast it to UISplitViewController.
Another better example can be taken as follows.
var shouldBeButton: UIView = UIButton()
var myButton: UIButton = shouldBeButton as UIButton
The as? operator returns an optional, and then we use optional binding to assign it to a temporary constant, and then use that in the if condition, like we are doing in the below example.
let myControlArray = [UILabel(), UIButton(), UIDatePicker()]
for item in myControlArray
{
if let myLabel = item as? UILabel
{
var storeText = myLabel.text
}
else if let someDatePicker = item as? UIDatePicker
{
var storeDate = someDatePicker.date
}
}

Error using subscript of optional array

When using this code:
let result: AnyObject! = hitResults[0]
I am getting the following error:
[AnyObject]? does not have a member named subscript
Containing function for context:
func handleTap(gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as SCNView
// check what nodes are tapped
let p = gestureRecognize.locationInView(scnView)
let hitResults = scnView.hitTest(p, options: nil)
// check that we clicked on at least one object
if hitResults?.count > 0 {
// retrieved the first clicked object
let result: AnyObject! = hitResults[0]
// get its material
let material = result.node!.geometry?.firstMaterial
// highlight it
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
// on completion - unhighlight
SCNTransaction.setCompletionBlock {
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(0.5)
material?.emission.contents = UIColor.blackColor()
SCNTransaction.commit()
}
material?.emission.contents = UIColor.redColor()
SCNTransaction.commit()
}
}
Does anyone know what the issue here is?
This happens because hitTest returns an optional array, so you need to unwrap before using it.
Instead of checking that the hitResults has a count > 0, you could check that there the first object exists, and then proceed to using that object
if let firstHit = scnView.hitTest(p, options: nil)?.first {
// safely use firstHit here...
}
You can't use a subscript on an optional array. [AnyObject]? Is an optional array of type AnyObject. If you are sure that hitResults is non-nil, you can unwrap it with ! then use a subscript.
let result: AnyObject! = hitResults![0]
Since hitResults is an [AnyObject]?, you can't call subscript on it without unwrapping it first. The safest way to do this is using optional binding, like so:
// check that we clicked on at least one object
if hitResults?.count > 0 {
// retrieved the first clicked object
if let result: AnyObject = hitResults?[0] {
/* safely use result here */
}
}
Or, even better, you can use optional binding with the first property of Array which returns the first element in the array if the Array is not empty, or nil:
// check that we clicked on at least one object and retrieve it
if let result = hitResults?.first {
/* safely use result here */
}

Crash when cast object inside for loop only on release mode

I use method with for loop inside:
func filter (array: NSArray) -> NSMutableArray {
var filteredArray: NSMutableArray = NSMutableArray()
for objects in array as [MyObject] { // this line crash only on release mode
// TODO
}
return filteredArray
}
when it is debug mode it works fine, but when I change to release mode it's crashed on line:
for objects in array as [MyObject]{
When I change method to this one (without casting inside loop) it wont crash on debug also on release mode:
func filter (array: [MyObject]) -> NSMutableArray {
var filteredArray: NSMutableArray = NSMutableArray()
for objects in array {
// TODO
}
return filteredArray
}
Can some explain why?
Hard to say without knowing what's actually inside the NSArray. I suggest setting a breakpoint and inspecting the content of the array variable.
However, the reason is that the as operator fails doing the cast, because at least one element in array is not an instance of (a subclass of) MyObject. I would protect that code by using optional cast, although that would skip the entire for loop if cast fails.
func filter (array: NSArray) -> NSMutableArray {
var filteredArray: NSMutableArray = NSMutableArray()
if let array = array as? [MyObject] {
for objects in array as [MyObject] { // this line crash only on release mode
// TODO
}
}
return filteredArray
}
If you are sure that the array contains MyObject instances, then I would solve the problem in the code that calls this function, using a swift array instead of NSArray, so avoiding cast problems, but of course that depends from your actual code - so this is not a solution that may work in all cases.
Update This solution could also better solve your problem, if it happens that you have an array with elements of mixed types, but you are interested in processing only the ones having MyObject type:
func filter (array: NSArray) -> NSMutableArray {
var filteredArray: NSMutableArray = NSMutableArray()
for element in array {
if let element = element as? MyObject {
// TODO
}
}
return filteredArray
}
The difference is that instead of trying to cast the entire array, the cast is attempted for each element.