'Mutating method sent to immutable object error' in Xcode - arrays

I am unsure why I get this error when I run my program a certain way.
2014-05-15 16:19:28.932 Puzzle[1002:f803] *** WebKit discarded an uncaught exception in the webView:shouldInsertText:replacingDOMRange:givenAction: delegate: <NSInternalInconsistencyException> -[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent to immutable object
I have an NSMutableArray called 'levelsCompleteArray' that I am trying to cycle through to see when the first NO, (or 0) appears and to set that iteration to a variable called 'picIndex'. If there are no YES's in the array, then the program works fine. When there is one in the next iteration, however, I get the message posted above. Does anyone know why? Code below:
[levelsCompleteArray replaceObjectAtIndex:picIndex withObject:[NSNumber numberWithBool:YES]];
BOOL wonLevel=NO;
int i=picIndex;
while (wonLevel==NO)
{
BOOL status =[[levelsCompleteArray objectAtIndex:i] boolValue];
if (status==1)
{
i=i+1;
if(i==3)
{
picIndex=0;
wonLevel=YES;
}
}
else
{
picIndex=i;
wonLevel=YES;
}
}

It looks like levelsCompleteArray is an NSArray which is immutatable. To turn it into a mutable array try this:
NSMutableArray* mutableLevelsCompleteArray = [[NSMutableArray alloc] initWithArray:levelsCompleteArray];

Related

How to remove an object from an array, where the object conforms to a protocol?

I am keeping track of a number of delegate objects in an array delegates
To qualify as a valid delegate the objects need to conform to the BSBSystemDelegate protocol.
So here is the array declaration:
private var delegates: [BSBSystemDelegate] = []
When an object registers with the BSBSystem, it is appended to the array:
public func registerDelegateWith(_ viewController: BSBSystemDelegate)
{
self.delegates.append(viewController)
}
That's working fine.
The problem I'm running into with swift and it's awful and confusing syntax is when I need to 'deregister' a delegate i.e. remove it from the array, if it exists.
Here's what I've tried:
public function deregisterDelegate(_ viewController: BSBSystemDelegate)
{
for delegate in self.delegates
{
if delegate === viewController
{
self.delegates.removeAll(where: viewController)
}
}
}
That doesn't work.
I just want to remove the object in the array when it's the same object I'm asking to remove.
I've been fighting swift for over an hour. Can someone please explain where I'm going wrong?
Here's is Apple's example:
And here is my code and the crazy dumb error it keeps giving me:
self.delegates.removeAll(where: { $0 === viewController }) will work but your protocol needs to be declared as class-bound in order to use the === operator which only works with reference types.
You would have to declare your protocol as:
protocol BSBSystemDelegate: AnyObject {
...
}
The error message isn't useful because the compiler is confused but if you break out your closure declaration on to a separate line:
let shouldBeRemoved: (BSBSystemDelegate) -> Bool = { $0 === viewController }
self.delegates.removeAll(where: shouldBeRemoved)
You get a more useful binary operator '===' cannot be applied to two 'BSBSystemDelegate' operands message.
Assuming you change your protocol to make it class-bound, as described by Dan, you could also use code like this:
if let index = array.firstIndex(where: { $0 === aFoo }) {
array.remove(at: index)
}
That would probably be faster for a large array, since it would stop on the first occurence of a match. (removeAll(where:) will always check every element in the array for a match.)
However, the code above would only remove the first instance of the object from the array if the exact same object has been added more than once.

Unable to delete all current elements of core data database

I am trying to delete all the data in my core data database but it is throwing up an error and is not affecting the SQLite database at all.
if self.userCredentials.count > 0 {
for loopIndex in 0 ... self.userCredentials.count - 1 {
self.context.delete(self.userCredentials[loopIndex])
self.userCredentials.remove(at: loopIndex)
}
self.saveLogin()
}
func saveLogin() {
do {
try context.save()
print ("Context saved successfully")
}
catch {
print ("Error saving items \(error)")
}
}
Fatal Error: Index out of range
Change your code to:
for userCredential in userCredentials {
context.delete(userCredential)
}
saveLogin()
The problem with your code is that you loop the amount of times of elements that variable userCredentials holds. That is an immutable value which won't be changed and it initialized before it looped even once.
You are accessing the index of an array based on the index. That's not wrong, but shouldn't be done in cases in which a simple for-in (see code above) is sufficient.
After that, you manipulate the element count by removing an element. That doesn't effect the immutable element count that I stated above. Because of that, soon or later you get an index out of bounds exception. You should try to search for what that exactly is, you try to access an element in a list which does not exists.

Error with APAddressBOOK: "fatal error: Array index out of range"

I am receiving this strange error every time I process an address book ( using APAddressBOOK via cocapods) in Swift and after some debugging I found out that and empty object (record with no phone number) within the array causes this issue but am not sure how to get rid of it.
Here is my code:
func getPersonsNo(contactno: AnyObject) -> String {
println(contactno) // **when the object is empty I get this "[]"**
if let numberRaw = contactno.phones?[0] as? String { // at this statement the program crashes with a fatal error
println(numberRaw)
return numberRaw)
}
return " "
}
Any clues what is happening here?
The subscript of an Array doesn't return an optional to indicate if an index is out of range of the array; instead your program will crash with the message “fatal error: Array index out of range”. Applying this your code: when contactno is empty your program will crash because there's no element at index 0 of the array.
To easiest way to solve your problem is probably to use the first property on Array. first will return the first element in the array, or nil if the array is empty. Taking a look at how first is declared:
extension Array {
var first: T? { get }
}
As of Swift 2, first has been made an extension of the CollectionType protocol:
extension CollectionType {
var first: Self.Generator.Element? { get }
}
You'd use this like so:
if let numberRaw = contactno.phones?.first as? String {
// ...
}

Error while using removeChild() and accessing members of array

I am stuck doing this even though I know it's very simple. Yet, I am getting errors.
What I have:
I have 3 arrays.
1st Array contains objects of UpgradeButton class.
2nd Array contains objects of BuyButtonclass.
3rd Array named newCostlyShops contains Numbers.
BuyButton class and UpgradeButton class, both have a shopCode member which is a number; the number which I'm trying to equate.
What I'm trying to do:
My goal is to first look for BuyButton and UpgradeButton objects in the respective arrays which have shopCodes same as those in newCostlyShops.
After that, I removeChild() that object and splice it out from the array.
My Code:
Array 3:
var newCostlyShops:Array = new Array();
newCostlyShops = Object(root).WorkScreen_mc.returnCostlyShops();
trace(newCostlyShops); // this is tracing out the exact shopCodes I want and is working fine.
Deletion and Splicing codes:
for (looper = 0; looper < upgradeButtonsArray.length; looper++) {
for (var secondLooper: int = 0; secondLooper < newCostlyShops.length; secondLooper++) {
if (upgradeButtonsArray[looper].shopCode == newCostlyShops[secondLooper]) {
trace(looper);
trace(upgradeButtonsArray[looper]);
removeChild(upgradeButtonsArray[looper]);
upgradeButtonsArray.splice(looper, 1);
}
}
}
for (looper = 0; looper < buyButtonsArray.length; looper++) {
for (secondLooper = 0; secondLooper < newCostlyShops.length; secondLooper++) {
if (buyButtonsArray[looper].shopCode == newCostlyShops[secondLooper]) {
trace(looper);
trace(buyButtonsArray[looper]);
removeChild(buyButtonsArray[looper]);
buyButtonsArray.splice(looper, 1);
}
}
}
What's wrong with this Code:
I keep getting error
TypeError: Error #1010: A term is undefined and has no properties.
This error comes only after the 1st time this code is run and not the first time it is run. When I remove the removeChild and splice , this traces out objects that are not null, ever. Even after this whole function is called 100 times, the error is not shown. Only when I removeChild and use splice this occurs.
Is there something wrong with what I'm doing? How to avoid this error? This is throwing the whole program haywire. If there is any other alternative to this method, I'm open to take those methods as well as long as I don't get errors and my goal is reached.
It might sounds funny, but.... try to decrement looper after splicing.
trace(looper);
trace(upgradeButtonsArray[looper]);
removeChild(upgradeButtonsArray[looper]);
upgradeButtonsArray.splice(looper, 1);
looper--;
I think after splicing the array all item's are being shifted and you're skipping next one.
Also, you should get some more information with this error, like which class/line is throwing it. Maybe you need to enable "permit debugging" or something?
Bonus suggestion:
For newCostlyShops use Dictionary instead of Array so you won't have to nest for inside for...

Adding values to Array in Swift Class but Array claims it is empty when tested

I am trying to make an array of integers with data I have stored on an online database (Parse). I know for a fact that I am receiving the data and that my variable is storing that data. I also know for a fact that the data value is being added as when I print out the size of the array under my append line, the size constantly increases until it reaches the size of my database (90). However, when I print the size of the array at the end of the method or in my constructor, I get a value of 0. I have a feeling that this problem is happening because of the "self" keyword but I am not sure how to get around it. My end goal is store all the values from the database into an array that I create and can globally access.
Here is the code:
import Foundation
class DataLoader
{
var allData: [Int] = []
init()
{
allData = []
generateAllData()
println(allData.count)
}
private func generateAllData()
{
var tempVal: Int = 0
var tempArray: [Int] = []
Parse.setApplicationId("CENSORED FOR PRIVACY REASONS", clientKey: "CENSORED FOR PRIVACY REASONS")
var query = PFQuery(className: "Snapshot")
query.selectKeys(["objectID"])
query.findObjectsInBackgroundWithBlock
{
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil
{
for obj in objects
{
var temp: String = obj.objectId
var newQuery = PFQuery(className: "Data")
newQuery.getObjectInBackgroundWithId(temp)
{
(dataValue: PFObject!, error: NSError!) -> Void in
if error == nil && dataValue != nil
{
tempVal = dataValue["Year"].integerValue
}
else
{
println(error)
}
}
self.allData.append(tempVal)
println(self.allData.count)
}
}
}
println(allData.count)
}
}
The problem is that the println's at the end of the method and the end of the init are happening while the background operation is still getting the data.
When you call "query.findObjectsInBackgroundWithBlock", it will go off into the background and get the data. But that does not block the next line from being called and it does not stop the function returning.
I suggest you add a callback or delegate method so that the rest of your code knows when the data has completely downloaded.
And you use of "self" is correct. Inside a block, you have to provide that context.
Update:
Adding more info about blocks and callbacks.
When you are using a block, the code after the block continues on without waiting for the block to complete. So a good way to know when the block has finished it to send a callback function as one of the parameters to the function containing the block. Once the block has completed, it can use the callback function to report back.
As a shorter example than your code, have a look at this animation function:
doSomeAnimation() // call the function below
func doSomeAnimation() {
println("Starting doSomeAnimation")
UIView.animateWithDuration(2.0,
animations: { () -> Void in
// animate something here
self.alphaButton.alpha = 1.0
}) { (animationDone) -> Void in
// animation over
println("Animation complete")
}
println("Ending doSomeAnimation")
}
You will see it contains 3 println() statements and the order in which they appear may surprise you:
Starting doSomeAnimation
Ending doSomeAnimation
Animation complete
So the doSomeAnimation() function is over long before the animation has completed. In this trivial example, that doesn't matter, but what if you need to know before the rest of your code could proceed?
One solution is to send a callback function to the function with the block. Here is the previous example with a callback in place:
doSomeAnimation() { (complete : Bool) in
println("Animation callback")
}
func doSomeAnimation(callback : ( Bool ) -> Void) {
println("Starting doSomeAnimation")
UIView.animateWithDuration(2.0,
animations: { () -> Void in
// animate something here
self.alphaButton.alpha = 1.0
}) { (animationDone) -> Void in
// animation over
println("Animation complete")
callback(animationDone)
}
println("Ending doSomeAnimation")
}
So the doSomeAnimation() function is called, but it is provided with a function as its parameter. It performs the animation and when the animation has finished, it uses this supplied callback function to report back to the caller.
And now the println() statements show as follows:
Starting doSomeAnimation
Ending doSomeAnimation
Animation complete
Animation callback
Hopefully this makes it clearer, both what the issue is an a possible solution.

Resources