Crash when cast object inside for loop only on release mode - swift

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.

Related

Swift 3 NSArray compare to nil

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

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.

Core Data - Change NSManagedObject array into array of Strings using valueForKey -OSX

So iv using an NSTokenField to allow data entry, the TokenField will suggest thing when the user starts typing. I want it to suggest things that are already inside core data.
To do this i have this function being called when the cell moves to superview (This is all happening inside a custom table view cell)
var subjectInformation = [NSManagedObject]()
let appDel = NSApplication.sharedApplication().delegate as! AppDelegate
let context = appDel.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "SubjectInformation")
do {
let results = try context.executeFetchRequest(fetchRequest)
subjectInformation = results as! [NSManagedObject]
} catch {
}
this returns an array of NSManagedObjects, now i want for every object in managed object get get the valueForKey("subjectName") as insert it into a array of string so that i can return that inside this token field Function
func tokenField(tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>) -> [AnyObject]? {
return subjectInformation //this is where is should return an array eg; ["English","Maths","Science"]
How would i do this? Thanks :)
If you properly subclassed your NSManagedObject you can use expressive Swift style filters and maps. You would cast your results array to [SubjectInformation] and
let subjectList = subjectInformation.map { $0.subjectName }
Try this:
(subjectInformation as! NSArray).valueForKeyPath("#unionOfObjects.subjectName")
This should return an array of the subjectNames of all the subjectInformation items.

App Delegate weird error when trying to add element to NSUserDefaults

I've got a really weird error while running my app on Xcode 7 (Swift 2) that shows a "Thread 1: signal SIGABRT" running error message in the App Delegate class of my app. However I've actually already got this "Thread 1: signal SIGABRT" running error message in the App Delegate class lots of times, mainly when deleting an outlet reference in my code and forgetting to also delete it from storyboard. But that's certainly the first time I've got this same error when trying to make the command:
let wasteGain = WastesGainsClass(value: enteredMoney, originOrCat: segControlArray[segControl.selectedSegmentIndex], specification: plusEspecTField.text!, date: dateArray, mode: "gain")
gains.append(wasteGain)
NSUserDefaults.standardUserDefaults().setObject(gains, forKey: "gains")
What happens is that if I just comment the line NSUserDefaults.standardUserDefaults().setObject(gains, forKey: "gains") the app doesn't crash! So the error might just be in that line.
If anyone could help me, I`d thank you so much.
PS: WastesGainsClass format is like this:
class WastesGainsClass {
var value:Int = 0
var origin:String
var specification:String
var date:[String]
var mode:String
var rowMode:Int = 0
init(value:Int, originOrCat:String, specification:String, date:[String], mode:String) {
self.value = value
self.origin = originOrCat
self.specification = specification
self.date = date
self.mode = mode
}
}
From documentation:
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
In Swift you can also use:
Int, UInt, Double, Float and Bool types because they are automatically bridged to NSNumber;
String bridged to NSString
[AnyObject] because it is bridged to NSArray;
[NSObject: AnyObject] because it is bridged to NSDictionary.
Of course type of array elements and dictionary values must be one of above types. Dictionary key type must be NSString (or bridged String).
To store instances of any other class you have two options:
Your custom class must be subclass of NSObject and conform to
NSCoding protocol and then you can archive object of this class to NSData with NSKeyedArchiver.archivedDataWithRootObject() and save it to NSUserDefaults and later retrieve it from NSUserDefaults and unarchive with NSKeyedUnarchiver.unarchiveObjectWithData():
import Foundation
class WastesGainsClass: NSObject, NSCoding {
var value: Int
init(value: Int) {
self.value = value
}
required init(coder aDecoder: NSCoder) {
value = aDecoder.decodeObjectForKey("value") as! Int
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(value, forKey: "value")
}
}
var gains = [WastesGainsClass(value: 1), WastesGainsClass(value: 2)]
NSUserDefaults.standardUserDefaults().setObject(gains.map { NSKeyedArchiver.archivedDataWithRootObject($0) }, forKey: "gains")
if let gainsData = NSUserDefaults.standardUserDefaults().objectForKey("gains") as? [NSData] {
gains = gainsData.map { NSKeyedUnarchiver.unarchiveObjectWithData($0) as! WastesGainsClass }
}
You can save your custom object properties to dictionary and store that
dictionary in NSUserDefaults:
import Foundation
class WastesGainsClass {
var value: Int
init(value: Int) {
self.value = value
}
}
extension WastesGainsClass {
convenience init(dict: [NSObject: AnyObject]) {
self.init(value: dict["value"] as? Int ?? 0)
}
func toDict() -> [NSObject: AnyObject] {
var d = [NSObject: AnyObject]()
d["value"] = value
return d
}
}
var gains = [WastesGainsClass(value: 1), WastesGainsClass(value: 2)]
NSUserDefaults.standardUserDefaults().setObject(gains.map { $0.toDict() }, forKey: "gains")
if let dicts = NSUserDefaults.standardUserDefaults().objectForKey("gains") as? [[NSObject: AnyObject]] {
gains = dicts.map { WastesGainsClass(dict: $0) }
}
NSUserDefaults unfortunately can't accept arbitrary objects, only objects that can be encoded in a Property List. See Apple's reference guide for Property Lists to learn which objects can be stored.
If you need to save several WastesGainsClass objects, you may wish to write a method that returns a Dictionary encoding their Property List-representable properties, and an initializer that accepts such a Dictionary to restore the object.
However, if you truly need to save multiple custom objects like this, you probably don't want to use NSUserDefaults at all. Consider a document-based app, and look into NSCoding.
The code you posted tries to save an array of custom objects to NSUserDefaults. You can't do that. Implementing the NSCoding methods doesn't help. You can only store things like NSArray, NSDictionary, NSString, NSData, NSNumber, and NSDate in NSUserDefaults.
You need to convert the object to NSData (like you have in some of the code) and store that NSData in NSUserDefaults. You can even store an NSArray of NSData if you need to.
see this post : Attempt to set a non-property-list object as an NSUserDefaults

App Crashing Swift Xcode 6.1 With Certain Method

My app is crashing on this method:
func loadData() {
timelineData.removeAllObjects()
var findTimelineData:PFQuery = PFQuery(className:"AllTweets")
findTimelineData.findObjectsInBackgroundWithBlock {
(objects:[AnyObject]!, error:NSError!)-> Void in
if (error == nil) {
for object:AnyObject in objects {
self.timelineData.addObject(object as PFObject)
}
let array:NSArray = self.timelineData.reverseObjectEnumerator().allObjects
self.timelineData = array as NSMutableArray
self.tableView.reloadData()
}
}
}
The error that it throws is:
ECX_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0)
I am calling this method in my viewDidAppear method.
Does anybody have any ideas why this is happening?
The reverseObjectEnumerator().allObjects will return a NSArray, no matter the type of the original array is Mutable or not. (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSEnumerator_Class/index.html#//apple_ref/occ/instp/NSEnumerator/allObjects)
In swift, if you need a mutable array, then use
var mutableArray = array.reverseObjectEnumerator().allObjects
var is for mutable and let is the opposite.