I get the error: NSArrayM was mutated while being enumerated and I've tried almost Everything I could find on stackoverflow. Keep in mind that I'm learning as I create my game :)
I'm using sprite kit and uses dispatch_async in didmovetoview to load the game.
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// loading stuff, but also this that is causing the error:
[self buildmap];
dispatch_async(dispatch_get_main_queue(), ^(void){
// done loading
});
});
Here is the buildmap that causes the crash.
-(void)buildMap {
int intX = 0;
int intY = 0;
sqlite3_stmt *statement;
if (sqlite3_open([_databasePath UTF8String], &BlockMinerDB) == SQLITE_OK) {
NSString *sql = [NSString stringWithFormat:#"SELECT blocktype, ladder, blockreachable, walkable, zpos, texture, id, bitmask FROM map ORDER BY id DESC"];
const char *qstmt = [sql UTF8String];
if (sqlite3_prepare_v2(BlockMinerDB, qstmt, -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
int blockType = sqlite3_column_int(statement, 0);
int ladder = sqlite3_column_int(statement, 1);
int blockReachable = sqlite3_column_int(statement, 2);
int walkable = sqlite3_column_int(statement, 3);
int zPos = sqlite3_column_int(statement, 4);
NSString *texture = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 5)];
int idBlock = sqlite3_column_int(statement, 6);
uint32_t newBitmask = (uint32_t)[NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 7)];
Blocks *testblock = [[Blocks alloc] initWithBlock:blockType blockId:idBlock ladder:ladder scene:self bitmask:newBitmask blockReachable:blockReachable walkable:walkable zPos:zPos texture:texture x:intX y:intY];
NSLog(#"%#: %i", testblock.name, blockType);
if ((blockType == 2) || (blockType == 3) || (blockType == 4) || (blockType == 5)) {
[blockArrayWalk addObject:testblock];
} else {
[blockArray addObject:testblock];
}
[background addChild:testblock];
intX++;
if (intX == 25) {
intX = 0;
intY++;
}
}
}
}
sqlite3_close(BlockMinerDB);
}
[self buildmap] uses sqlite to retreive the map from the database, i'm looping with a while loop and this is causing the crash. It loops about 20-25 times (of 600) Before crashing.
Within the loop I'm creating a new block (SKSpriteNode subclass) that places the block at an certain position with some information (position, name, physics and such, no Heavy stuff). I was using NSMutableArray to store the blocks for easy access and thought that this was the problem at first, but when I remove the [blockArray addObject:block]; the app still crashes.
If I remove every bit of code in the -(void)buildmap the app doesn't crash.
Is there something with dispatch_async and the while loop that might cause this?
I can't access the code right now but if anyone need to see more code, I can add this in about 8 hours from now ;) Would really appreciate some help, trying to solve this error for 3 weeks on and off now :D
At any point in time Sprite Kit may be enumerating over children (perhaps to draw them) and if your dispatch block then adds a new child node, it would cause the children array to mutate while being enumerated.
You could fill an array of to-be-added nodes in your dispatch block, and then after the dispatch block add them all at once.
Related
Is there a way to build an alert box that shows what the crash reason is? Specifically, what line of code is causing the crash?
My boss is asking for this, and I haven't found a way to make this possible. I'm going through the analytics tab on the device and finding the crash, but he wants something that populates on the device (it's an internal app) giving the reason for the crash.
Is this possible?
If you want to show an alert when app crashes you can't do that but you can read the crash log when you open the app again after a crash.
You could create a method with following logic, it basically reads back trace for last crash when you open the app again after a crash.
aslmsg q, m;
int i;
const char *key, *val;
float how_old = fTime ;
q = asl_new(ASL_TYPE_QUERY);
asl_set_query(q, ASL_KEY_LEVEL, strLoggerLevel ,ASL_QUERY_OP_LESS_EQUAL);
asl_set_query(q, ASL_KEY_FACILITY, [#"YourBundleIdOfAPP" UTF8String] ,ASL_QUERY_OP_EQUAL);
asl_set_query(q, ASL_KEY_TIME, [[NSString stringWithFormat:#"%.f", [[NSDate date] timeIntervalSince1970] - how_old] UTF8String], ASL_QUERY_OP_GREATER_EQUAL);
int goInside=0;
aslresponse r = asl_search(NULL, q);
while (NULL != (m = aslresponse_next(r)))
{
NSString *cValueToWrite;
NSMutableDictionary *tmpDict = [NSMutableDictionary dictionary];
for (i = 0; (NULL != (key = asl_key(m, i))); i++)
{
//get the only required fields
if(i==12 || i==10 || i==11 || i==8 || i==9 ||i==3)
{
NSString *keyString = [NSString stringWithUTF8String:(char *)key];
val = asl_get(m, key);
NSString *string = [NSString stringWithUTF8String:val];
[tmpDict setObject:string forKey:keyString];
}
}
cValueToWrite=[[NSString alloc]initWithFormat:#"\n--------------[Debug]----------------\nDateTime: %#\nApplication: %#\nInfo: %#",[tmpDict valueForKey:#"CFLog Local Time"],[tmpDict valueForKey:#"Sender"],[tmpDict valueForKey:#"Message"]];
}
strLoggerLevel is the NSString which hold the logger type which you want which ranges upto 7
Some relevant info I am on OSX using GCD in Objective-C. I have a background task that produces a very large const char * this is then re-introduced into a new background task. This cycle repeats essentially until the const char* is empty. Currently I am creating and using NSStrings in the blocks and then going back to char* immediately. As you can imagine this does a ton of unnecessary copying of all that.
I am wondering how __block variables work for non-objects or how I can get away from NSStrings?
Or
How is memory managed for non-object types?
It is currently just blowing up with ~2 gigs of memory all from the strings.
Here is how it currently looks:
-(void)doSomething:(NSString*)input{
__block NSString* blockCopy = input;
void (^blockTask)(void);
blockTask = ^{
const char* input = [blockCopy UTF8String];
//remainder will point to somewhere along input
const char* remainder = NULL;
myCoolCFunc(input,&remainder);
if(remainder != NULL && remainder[0] != '\0'){
//this is whats killing me the NSString creation of remainder
[self doSomething:#(remainder)];
}
}
/*...create background queue if needed */
dispatch_async(backgroundQueue,blockTask);
}
There is no need to use NSString at all and no need to use the __block attribute:
-(void)doSomething:(const char *)input{
void (^blockTask)(void);
blockTask = ^{
const char* remainder = NULL;
myCoolCFunc(input,&remainder);
if(remainder != NULL && remainder[0] != '\0'){
[self doSomething:remainder];
}
}
/*...create background queue if needed */
dispatch_async(backgroundQueue,blockTask);
}
There is also little need to use recursion either, as an iterative approach is also possible.
blockTask = ^{
const char* remainder = NULL;
while (YES) {
myCoolCFunc(input,&remainder);
if(remainder == NULL || remainder[0] == '\0')
break;
input = remainder;
}
}
I am working on a Keyboard extension on iOS using Objective C where I am using SQLite. I need to understand a few concepts about SQLite which I didn't get by googling. Let me divide the question in parts.
PART: 1
I have come across a PRAGMA in SQLite called PRAGMA cache_size = pages;
The default size here is 2000 pages. Comparing with the default, according to my understanding,
cache_size > 2000 means more memory usage, more speed (than default).
cache_size < 2000 means less memory usage, less speed (than default).
Am I correct here?
PART: 2
I am trying to change the cache_size in the following way,
if (sqlite3_exec(sqlite3Database, [#"PRAGMA CACHE_SIZE=50;" UTF8String], NULL, NULL, NULL) == SQLITE_OK) {
NSLog(#"Successfully changed cache size");
}
else
NSLog(#"Error: failed to set cache size with message %s.", sqlite3_errmsg(sqlite3Database));
I am using this after opening the database. The following code shows it,
-(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{
// Create a sqlite object.
sqlite3 *sqlite3Database;
// Set the database file path.
NSString *databasePath = [self getDbFilePath];
// Initialize the results array.
if (self.arrResults != nil) {
[self.arrResults removeAllObjects];
self.arrResults = nil;
}
self.arrResults = [[NSMutableArray alloc] init];
// Open the database.
BOOL openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database);
if(openDatabaseResult == SQLITE_OK) {
if (sqlite3_exec(sqlite3Database, [#"PRAGMA CACHE_SIZE=50;" UTF8String], NULL, NULL, NULL) == SQLITE_OK) {
NSLog(#"Successfully changed cache size");
}
else
NSLog(#"Error: failed to set cache size with message %s.", sqlite3_errmsg(sqlite3Database));
// Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite statement.
sqlite3_stmt *compiledStatement;
// Load all data from database to memory.
BOOL prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL);
if(prepareStatementResult == SQLITE_OK) {
// Check if the query is non-executable.
if (!queryExecutable){
// In this case data must be loaded from the database.
// Declare an array to keep the data for each fetched row.
NSMutableArray *arrDataRow;
// Loop through the results and add them to the results array row by row.
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Initialize the mutable array that will contain the data of a fetched row.
arrDataRow = [[NSMutableArray alloc] init];
// Get the total number of columns.
int totalColumns = sqlite3_column_count(compiledStatement);
// Go through all columns and fetch each column data.
for (int i=0; i<totalColumns; i++){
// Convert the column data to text (characters).
char *dbDataAsChars = (char *)sqlite3_column_text(compiledStatement, i);
// If there are contents in the currenct column (field) then add them to the current row array.
if (dbDataAsChars != NULL) {
// Convert the characters to string.
[arrDataRow addObject:[NSString stringWithUTF8String:dbDataAsChars]];
}
}
// Store each fetched data row in the results array, but first check if there is actually data.
if (arrDataRow.count > 0) {
[self.arrResults addObject:arrDataRow];
}
}
}
else {
// This is the case of an executable query (insert, update, ...).
// Execute the query.
int executeQueryResults = sqlite3_step(compiledStatement);
if (executeQueryResults == SQLITE_DONE) {
// Keep the affected rows.
self.affectedRows = sqlite3_changes(sqlite3Database);
// Keep the last inserted row ID.
self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database);
}
else {
// If could not execute the query show the error message on the debugger.
NSLog(#"DB Error: %s", sqlite3_errmsg(sqlite3Database));
}
}
}
else {
// In the database cannot be opened then show the error message on the debugger.
NSLog(#"db error: %s", sqlite3_errmsg(sqlite3Database));
}
// Release the compiled statement from memory.
sqlite3_finalize(compiledStatement);
}
// Close the database.
sqlite3_close(sqlite3Database);
}
But, when I call the method, sqlite3_exec(sqlite3Database, [#"PRAGMA CACHE_SIZE=50;" UTF8String], NULL, NULL, NULL), it always gives SQLITE_OK no matter what I do.
For example, if I do sqlite3_exec(sqlite3Database, [#"abcd bla bla" UTF8String], NULL, NULL, NULL), it returns SQLITE_OK!!
Why is that so?
PART: 3
I want to increase the speed of execution of my queries, but at the same time don't want to use IMDB as the size of the database is huge.
So is PRAGMA page_size = bytes; make any relevance in this case? If yes, then how to do it in Objective C?
Any help is appreciated. Thanks and regards.
i'm trying to get the values of an array randomly but i'm getting an error
here is my code so far:
NSMutableArray *validMoves = [[NSMutableArray alloc] init];
for (int i = 0; i < 100; i++){
[validMoves removeAllObjects];
for (TileClass *t in tiles ) {
if ([self blankTile:t] != 0) {
[validMoves addObject:t];
}
}
NSInteger pick = arc4random() % validMoves.count;
[self movePiece:(TileClass *)[validMoves objectAtIndex:pick] withAnimation:NO];
}
The error you're getting (an arithmetic exception) is because validMoves is empty and this leads to a division by zero when you perform the modulus operation.
You have to explicitly check for the case of an empty validMoves array.
Also you should use arc4random_uniform for avoiding modulo bias.
if (validMoves.count > 0) {
NSInteger pick = arc4random_uniform(validMoves.count);
[self movePiece:(TileClass *)[validMoves objectAtIndex:pick] withAnimation:NO];
} else {
// no valid moves, do something reasonable here...
}
As a final remark not that arc4random_uniform(0) returns 0, therefore such case should be avoided or you'll be trying to access the first element of an empty array, which of course will crash your application.
I have a questions about blocks in Objective-C.
For example I have this code:
__block int count = 0;
void (^someFunction)(void) = ^(void){
count = 4;
};
count +=2;
What would be the proper way to write the same piece of code so the count will become 6, not 2 ?!
Thank you!
I should probably show the actual code because my previous question was blurry.
EDIT:
__block CMTime lastTime = CMTimeMake(-1, 1);
__block int count = 0;
[_imageGenerator generateCGImagesAsynchronouslyForTimes:stops
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
AVAssetImageGeneratorResult result, NSError *error)
{
if (result == AVAssetImageGeneratorSucceeded)
{
NSImage *myImage = [[NSImage alloc] initWithCGImage:image size:(NSSize){50.0,50.0}];
[arrOfImages addObject:myImage];
}
if (result == AVAssetImageGeneratorFailed)
{
NSLog(#"Failed with error: %#", [error localizedDescription]);
}
if (result == AVAssetImageGeneratorCancelled)
{
NSLog(#"Canceled");
}
if (arrOfImages.count > 5)
{
NSLog(#"here");
}
count++;
}];
int f = count+1;
after 10 iterations count is 0...why?!?!
You are not executing the block (calling a block someFunctionmight be a misleading thing)
__block int count = 0;
void (^someBlock)(void) = ^{
count = 4;
};
someBlock();
count +=2;
Call block like this:
someFunction();
So that would be:
__block int count = 0;
void (^someFunction)(void) = ^(void){
count = 4;
};
// call block
someFunction();
count +=2;
Look at the name of the method you are calling; generateCGImagesAsynchronouslyForTimes: completionHandler:.
Asynchronously means that it executes in a different thread (likely via a queue and, as #newaccount points, it may likely be re-scheduled for future execution on the current queue/thread) and the method returns immediately. Thus, when you set f=count+1;, the completion block hasn't even been executed yet because none of the image loads in background threads have been completed.
You need to make a call from that completion block back to your code that needs to respond to the completion. i.e.
^() {
....
dispatch_async(dispatch_get_main_queue(), ^{[self heyManAnImageLoadedDude];});
....
}