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];});
....
}
Related
I've got stacked on very simple issue. Hasn't been using Objective-C a lot at last time. Could someone help me with:
+(UNAuthorizationStatus) mCheckPermissions {
__block UNAuthorizationStatus oOutput = 0;
UNUserNotificationCenter* oCenter = [UNUserNotificationCenter currentNotificationCenter];
[oCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
oOutput = settings.authorizationStatus;
}];
return oOutput;
}
I need assign the value to oOutput from completion handler. For now it's not assinning value correctly. What am I missing? And please do not reply me something Swift related. The question is about Objective-C.
What you're missing is that getNotificationSettingsWithCompletionHandler is asynchronous.
That means that the "answer" within the block (your settings.authorizationStatus) comes back to you after the entire mCheckPermissions method has finished executing, including the return. The order of execution is like this:
+(UNAuthorizationStatus) mCheckPermissions {
__block UNAuthorizationStatus oOutput = 0;
/* 1 */ UNUserNotificationCenter* oCenter = [UNUserNotificationCenter currentNotificationCenter];
/* 2 */ [oCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
/* 4 */ oOutput = settings.authorizationStatus;
}];
/* 3 */ return oOutput;
}
Therefore it is impossible to return, from the outer method mCheckPermissions, a value that arrives into the block. (Unless you have a time machine in your pocket, so you can dip into the future and find out what the result will be.)
Solution found. Issue closed.
+(UNAuthorizationStatus) mCheckPermissions {
__block UNAuthorizationStatus oOutput = UNAuthorizationStatusNotDetermined;
dispatch_semaphore_t oSemaphore = dispatch_semaphore_create(0);
UNUserNotificationCenter* oCenter = [UNUserNotificationCenter currentNotificationCenter];
[oCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
oOutput = settings.authorizationStatus;
dispatch_semaphore_signal(oSemaphore);
}];
if (![NSThread isMainThread]) {
dispatch_semaphore_wait(oSemaphore,DISPATCH_TIME_FOREVER);
} else {
while (dispatch_semaphore_wait(oSemaphore,DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
return oOutput;
}
In my ARC iOS app I am running a for loop that ends up with a large memory allocation overhead. I want to somehow end my for loop with minimal/no extra memory allocated. In this instance I am using the SSKeychain library which lets me fetch things from a keychain. I usually just use autorelease pools and get my memory removed properly but here I don't know what is wrong because I end up with 70 mb + of memory allocated at the end of the loop. I have been told that I should start/end a run loop to properly deal with this. Thoughts?
for (int i = 0; i < 10000; ++i) {
#autoreleasepool {
NSError * error2 = nil;
SSKeychainQuery* query2 = [[SSKeychainQuery alloc] init];
query2.service = #"Eko";
query2.account = #"loginPINForAccountID-2";
query2.password = nil;
[query2 fetch:&error2];
}
}
What are you using to measure memory usage?
Results of a very simple test...
Running in the simulator, measure only resident memory before and after.
Without autoreleasepool...
Started with 27254784, ended with 30212096, used 2957312
With autoreleasepool...
Started with 27316224, ended with 27443200, used 126976
Obviously, the autoreleasepool is preventing memory from growing too bad, and I don't see anything close to 70MB being used under any circumstance.
You should run instruments and get some good readings on the behavior.
Here is the code I hacked and ran...
The memchecker
static NSUInteger available_memory(void) {
NSUInteger result = 0;
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size) == KERN_SUCCESS) {
result = info.resident_size;
}
return result;
}
And the code...
#define USE_AUTORELEASE_POOL 1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
dispatch_async(dispatch_get_main_queue(), ^{
NSUInteger beginMemory = available_memory();
for (int i = 0; i < 10000; ++i) {
#ifdef USE_AUTORELEASE_POOL
#autoreleasepool
#endif
{
NSError * error2 = nil;
SSKeychainQuery* query2 = [[SSKeychainQuery alloc] init];
query2.service = #"Eko";
query2.account = #"loginPINForAccountID-2";
query2.password = nil;
[query2 fetch:&error2];
}
}
NSUInteger endMemory = available_memory();
NSLog(#"Started with %u, ended with %u, used %u", beginMemory, endMemory, endMemory-beginMemory);
});
return YES;
}
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.
Strangest thing I've seen yet.. NSLog is failing within a method based on something bizarre. Here is the code:
-(void) testBoard {
BoardManager* bm = [BoardManager manager];
Board* board = [bm boardForName:#"fourteen by eight"];
NSLog(#"%#", board);
NSLog(#"%#", board.coords);
}
in Board.m :
-(NSArray*) coords {
if(!_coords.count && _definition) {
NSArray* c = [Board decode:_definition];
[self setCoords:c];
}
return _coords;
}
+(NSArray*) decode:(NSString*)encodedCoords {
NSMutableArray* coords = [NSMutableArray array];
NSArray* tokens = [encodedCoords componentsSeparatedByString:#","];
int i = 0;
NSString* dimStr = [tokens objectAtIndex:i++];
int width = [dimStr substringToIndex:2].intValue;
int height = [dimStr substringWithRange:NSMakeRange(2, 2)].intValue;
int depth = [dimStr substringFromIndex:4].intValue;
NSLog(#"w=%d h=%d d=%d", width, height, depth);
NSString* b128;
NSString* b2;
for(int z=0; z<depth; z++) {
for(int y=0; y<height; y++) {
b128 = [tokens objectAtIndex:i++];
NSLog(#"[%#]", b128);
b2 = [Board base128to2:b128];
NSLog(#"b2=%#",b2);
for(int x=0; x<b2.length; x++) {
if([b2 characterAtIndex:b2.length-1-x] == '1') {
Coord* coord = [Coord x:width-1-x y:height-1-y z:z];
[coords addObject:coord];
}
}
}
}
return coords;
}
Now what happens is, none of the NSLog statements within decode: or methods called from decode: will log to the console. However, NSLog before and after calling decode: work, and the rest of the code around the NSLog's executes fine. I found that I could get all the NSLog statements to work simply by commenting out [coords addObject:coord];. This statement appears after NSLog statements that it is affecting. I also found I could get the NSLog's to work by rearranging a few lines in testBoard: like this:
-(void) testBoard
{
BoardManager* bm = [BoardManager manager];
Board* board = [bm boardForName:#"fourteen by eight"];
NSLog(#"%#", board);
NSString* def = board.definition;
NSArray* coords = [Board decode:def];
NSLog(#"%#", coords);
}
This seems quite bizarre..an xcode Loch Ness monster surfacing?!
Have you tried running in the debugger? What happens when you get to that if statement
if(!_coords.count && _definition)
Are you sure _coords.count is 0 and _definition is not nil
In my experience, NSLog macro expansions can fail. Most of the time it's obvious why, sometimes not.
Just do something like this:
id objToLog = /* whatever */;
NSLog(#"%#", objToLog);
If this approach works, you can go ahead with your development and maybe you figure the problem out later.
my first question on Stackoverflow.
Let me start with a bit of code. It's a bit repetitive so I'm going to cut out the parts I repeat for different arrays (feel free to ask for the others). However, please ignore the code in preference to answering the Qs at the bottom. Firstly: thank you to answerers in advance. Secondly: the freeing of data.
#implementation ES1Renderer
GLfloat **helixVertices;
GLushort **helixIndices;
GLubyte **helixColors;
- (void)freeEverything
{
if (helixVertices != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixVertices[i]);
free(helixVertices);
}
if (helixIndices != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixIndices[i]);
free(helixIndices);
}
if (helixColors != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixColors[i]);
free(helixColors);
}
}
(I will get to the calling of this in a moment). Now for where I malloc() the arrays.
- (void)askForVertexInformation
{
int nrows = self.helper.numberOfAtoms / 300;
int mrows = [self.helper.bonds count] / 300;
int alphaCarbonRows = [self.helper.alphaCarbons count] / 30;
helixVertices = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
helixIndices = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
helixColors = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
for (int i=0; i < alphaCarbonRows + 1; i++)
{
helixVertices[i] = malloc(sizeof(helixVertices) * HELIX_VERTEX_COUNT * 3 * 33);
helixIndices[i] = malloc(sizeof(helixIndices) * HELIX_INDEX_COUNT * 2 * 3 * 33);
helixColors[i] = malloc(sizeof(helixColors) * HELIX_VERTEX_COUNT * 4 * 33);
}
[self.helper recolourVerticesInAtomRange:NSMakeRange(0, [self.helper.alphaCarbons count]) withColouringType:CMolColouringTypeCartoonBlue forMasterColorArray:helixColors forNumberOfVertices:HELIX_VERTEX_COUNT difference:30];
self.atomsToFree = self.helper.numberOfAtoms;
self.bondsToFree = [self.helper.bonds count];
self.alphasToFree = [self.helper.alphaCarbons count];
}
Finally, the bit which calls everything (this is a separate class.)
- (void)loadPDB:(NSString *)pdbToLoad
{
if (!self.loading)
{
[self performSelectorOnMainThread:#selector(stopAnimation) withObject:nil waitUntilDone:YES];
[self.renderer freeEverything];
[renderer release];
ES1Renderer *newRenderer = [[ES1Renderer alloc] init];
renderer = [newRenderer retain];
[self performSelectorOnMainThread:#selector(stopAnimation) withObject:nil waitUntilDone:YES]; // need to stop the new renderer animating too!
[self.renderer setDelegate:self];
[self.renderer setupCamera];
self.renderer.pdb = nil;
[renderer resizeFromLayer:(CAEAGLLayer*)self.layer];
[newRenderer release];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(setup:) object:pdbToLoad];
[self.queue addOperation:invocationOperation];
[invocationOperation release];
}
}
- (void)setup:(NSString *)pdbToLoad
{
self.loading = YES;
[helper release];
[renderer.helper release];
PDBHelper *aHelper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
helper = [aHelper retain];
renderer.helper = [aHelper retain];
[aHelper release];
if (!resized)
{
[self.helper resizeVertices:11];
resized = YES;
}
self.renderer.helper = self.helper;
[self.helper setUpAtoms];
[self.helper setUpBonds];
if (self.helper.numberOfAtoms > 0)
[self.renderer askForVertexInformation];
else
{
// LOG ME PLEASE.
}
[self performSelectorOnMainThread:#selector(removeProgressBar) withObject:nil waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(startAnimation) withObject:nil waitUntilDone:YES];
self.renderer.pdb = pdbToLoad;
self.loading = NO;
}
What I'm doing here is loading a molecule from a PDB file into memory and displaying it on an OpenGL view window. The second time I load a molecule (which will run loadPDB: above) I get the Giant Triangle Syndrome and Related Effects... I will see large triangles over my molecule.
However, I am releasing and reallocating my PDBHelper and ES1Renderer every time I load a new molecule. Hence I was wondering:
1. whether the helixVertices, helixIndices and helixColors which I have declared as class-wide variables are actually re-used in this instance. Do they point to the same objects?
2. Should I be setting all my variables to NULL after freeing? I plan to do this anyway, to pick up any bugs by getting a segfault, but haven't got round to incorporating it.
3. Am I even right to malloc() a class variable? Is there a better way of achieving this? I have no other known way of giving this information to the renderer otherwise.
I can't answer your general questions. There's too much stuff in there. However, this caught my eye:
[helper release];
[renderer.helper release];
PDBHelper *aHelper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
helper = [aHelper retain];
renderer.helper = [aHelper retain];
[aHelper release];
I think this stuff possibly leaks. It doesn't make sense anyway.
If renderer.helper is a retain or copy property, do not release it. It already has code that releases old values when it is assigned new values. Also do not retain objects you assign to it.
You have alloc'd aHelper, so there's no need to retain it again. The above code should be rewritten something like:
[helper release];
helper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
renderer.helper = helper;
Also, I think your helix malloced arrays should probably be instance variables. As things stand, if you have more than one ES1Renderer, they are sharing those variables.