App crashes on drawing of UILabel - NSString is a zombie - objective-c

I'm working on an iPad app that has a slider that is used to scroll through data. When scrolling, a map is displayed and the data is updated. The problem is, if you scroll fast enough (or somehow trigger the race condition), the app crashes on accessing a zombie NSString. I've been able to track it down in the Profiler and found this:
Event Type RefCt Timestamp Size Responsible Library Responsible Caller
Malloc 1 01:55.166.466 16 Foundation -[NSPlaceholderString initWithFormat:locale:arguments:]
Autorelease <null> 01:55.166.472 0 Foundation +[NSString stringWithFormat:]
CFRetain 2 01:55.166.473 0 My Program -[StateView updateVotes:]
CFRetain 3 01:55.166.476 0 UIKit -[UILabel setText:]
CFRelease 2 01:55.166.504 0 My Program -[StateView updateVotes:]
CFRelease 1 01:55.177.661 0 Foundation -[NSAutoreleasePool release]
CFRelease 0 01:55.439.090 0 UIKit -[UILabel setText:]
Zombie -1 01:55.439.109 0 UIKit -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:lineBreakMode:letterSpacing:includeEmoji:]
I'm using ARC on iOS5, so I'm not in control of the retain/release at all. Even if I was, looking at the above, it is correct. The problem seems to be a race condition between the drawing function and the UILabel string actually changing. The UILabel releases the first string, as a new one has been set, but the drawing function is holding a reference to it somehow, but did not retain it.
As a note, I have not modified the UILabel in any way.
Any ideas?
--- Code added as update:
Slider update:
-(void)sliderValueChanged:(UISlider *)slider {
float position = slider.value - 1790.0f;
int year;
if(position <= 0.0f) {
year = 1789;
} else {
year = 1792 + (floor(position / 4.0f)*4);
}
[self setYear:year];
}
setYear:
-(void)setYear:(int)year {
if (year == currentYear) {
// year didn't change, so don't do anything
return;
}
[yearLabel setText:[[NSString alloc] initWithFormat:#"%i", year]];
currentYear = year;
[self getMapForYear:year];
}
getMapForYear:
-(void) getMapForYear:(int)year {
[self setToMap:[historicalData objectForKey:[NSNumber numberWithInt:year]];
}
setToMap:
-(void) setToMap:(HistoricalMap *)map {
// Label the map
for (State *state in [map states]) {
[mapController setVotes:[state votes] forState:[state abbreviation]];
}
}
setVotes:forState:
-(void)setVotes:(NSNumber *)votes forState:(NSString *)stateAbbreviation {
StateView *state = [states objectForKey:stateAbbreviation];
if (state == nil) {
NSLog(#"Invalid State Votes -- %#", stateAbbreviation);
return;
}
[state updateVotes:votes];
[state setNeedsDisplay];
}
updateVotes:
-(void)updateVotes:(NSNumber *)newVotes {
[self setVotes:newVotes];
NSString *voteString = [[NSString alloc] initWithFormat:#"%#", newVotes];
[voteLabel setText:voteString];
if ([newVotes isEqual:[NSNumber numberWithInt:0]]) {
[[self voteLabel] setHidden:YES];
[[self stateAbbreviationLabel] setHidden:YES];
} else {
[[self stateAbbreviationLabel] setHidden:NO];
[[self voteLabel] setHidden:NO];
}
}

I think you are attempting to do far too much during the slider's movement. Creating and executing core data fetch requests alone would seem to be overkill, let alone updating the entire GUI and a screenful of labels. Have you tested the performance of this on a device?
It could be worth profiling these sections of code and seeing where the time is spent. You could look at caching the fetch requests or the results, for example, or you may have to resort to updating only when the slider has stopped, or only for every n increments along the path.

You havw several memory-leaks with NSString:
[yearLabel setText:[[NSString alloc] initWithFormat:#"%i", year]]; // leak
Create string with stringWithFormat method instead
[yearLabel setText:[NSString stringWithFormat:#"%i", year]];

[NSString stringWithFormat: **is the best way formatting the string than any other..**

Related

AVPlayer -seekToTime: Returns Black Frames

I have a Metal-based application that utilizes AVFoundation for movie playback + seeking. To start, I am only processing .mov files and nothing else, and even the app in question will not process any other format. While it has been working effectively in the past, I recently received feedback from some M1 users about black frames only showing up on their app regardless of which time they set their seek bar to.
I have performed the following troubleshooting in my attempts find the root source for this black texture bug:
Verified that the video being processed is .mov movie file type.
Verified that the CVPixelBufferRef object returned from AVPlayerItemVideoOutput's -copyPixelBufferForItemTime: is valid, i.e. not nil.
Verified that the MTLTexture created from the CVPixelBufferRef is also valid, i.e. also not nil.
Converted the MTLTexture to a bitmap and saved it as a .JPEG image to the user's disk.
The last part is probably the most important step here as the images saved are also all black (for users experiencing the bug) when viewed using Finder, and made me come to the assumption that I might be using AVFoundation somewhat wrong. While I hope that my opening post might not be too long in regards to code, below are the following steps I am performing in order to process videos to be rendered using Metal for your reference.
Inside my reader class, I have the following properties:
VideoReaderClass.m
#property (retain) AVPlayer *vidPlayer;
#property (retain) AVPlayerItem *vidPlayerItem;
#property (retain) AVPlayerItemVideoOutput *playerItemVideoOutput;
#property (retain) AVMutableComposition *videoMutableComp;
#property (assign) AVPlayerItemStatus playerItemStatus;
// The frame duration of the composition, as well as the media being processed.
#property (assign) CMTime frameDuration;
// Tracks the time to insert a new footage to
#property (assign) CMTime startInsertTime;
// Weak reference to a delegate responsible for processing seek:
#property (weak) id<VideoReaderRenderer> vidReaderRenderer;
A method called before reading starts. Handles observer cleanup as well.
- (void)initializeReadMediaPipeline
{
[_lock lock];
_startInsertTime = kCMTimeZero;
_playerItemStatus = AVPlayerItemStatusUnknown;
if(_videoMutableComp)
{
[_videoMutableComp release];
}
_videoMutableComp = [AVMutableComposition composition];
[_videoMutableComp retain];
if(_vidPlayer)
{
[[_vidPlayer currentItem] removeObserver:self
forKeyPath:#"status"
context:MyAddAVPlayerItemKVOContext];
[[_vidPlayer currentItem] removeObserver:self
forKeyPath:#"playbackBufferFull"
context:MyAVPlayerItemBufferFullKVOContext];
[[_vidPlayer currentItem] removeObserver:self
forKeyPath:#"playbackBufferEmpty"
context:MyAVPlayerItemBufferEmptyKVOContext];
[[_vidPlayer currentItem] removeObserver:self
forKeyPath:#"playbackLikelyToKeepUp"
context:MyAVPlayerItemBufferKeepUpKVOContext];
[_vidPlayer release];
_vidPlayer = nil;
}
[_lock unlock];
}
In this class is a public method to be called in a background queue when the app should begin processing a video, or a set of videos.
- (BOOL)addMediaAtLocation:(NSURL *)location
{
BOOL result = NO;
[_lock lock];
NSDictionary* optsInfo =
#{
AVURLAssetPreferPreciseDurationAndTimingKey : #(YES)
};
AVURLAsset* assetURL = [AVURLAsset URLAssetWithURL:location options:optsInfo];
AVAssetTrack* assetTrack = [assetURL tracksWithMediaType:AVMediaTypeVideo].firstObject;
NSError* error = nil;
[_videoMutableComp insertTimeRange:CMTimeRangeMake(kCMTimeZero, assetTrack.timeRange.duration)
ofAsset:assetURL
atTime:_startInsertTime
error:&error];
if(!error)
{
[_vidPlayerItem addObserver:self
forKeyPath:#"status"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:MyAddAVPlayerItemKVOContext];
[_vidPlayerItem addObserver:self
forKeyPath:#"playbackBufferFull"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:MyAVPlayerItemBufferFullKVOContext];
[_vidPlayerItem addObserver:self
forKeyPath:#"playbackBufferEmpty"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:MyAVPlayerItemBufferEmptyKVOContext];
[_vidPlayerItem addObserver:self
forKeyPath:#"playbackLikelyToKeepUp"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:MyAVPlayerItemBufferKeepUpKVOContext];
[_vidPlayer replaceCurrentItemWithPlayerItem:_vidPlayerItem];
}
_vidPlayer = [[AVPlayer alloc] init];
if(_playerItemVideoOutput)
{
[_playerItemVideoOutput release];
_playerItemVideoOutput = nil;
}
[_lock unlock];
}
Called externally by our view controller when seeking is needed.
- (void)seekToFrame:(CMTime)time
{
if(_vidReaderRenderer)
{
if(_vidPlayerItem && _playerItemVideoOutput)
{
[_vidPlayerItem seekTimeTime:time
toleranceBefore:kCMTimeZero
toleranceAfter:kCMTimeZero
completionHandler:^(BOOL finished){
if(finished)
{
CVPixelBufferRef p_buffer = [_playerItemVideoOutput copyPixelBufferForItemTime:time itemTimeForDisplay:nil];
if(p_buffer)
{
[_vidReaderRenderer seekOperationFinished:p_buffer];
}
}
}];
}
}
}
Lastly, this is where I handle the notification when the AVPlayerItem's status is set for "ReadyToPlay."
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
{
// ... Checking for right contexts here, omitting for this example.
if([keyPath isEqualToString:#"status"])
{
AVPlayerItemStatus status = AVPlayerItemStatusUnknown;
// Get the status change from the change dictionary
NSNumber *statusNumber = change[NSKeyValueChangeNewKey];
if([statusNumber isKindOfClass:[NSNumber class]])
{
status = statusNumber.integerValue;
}
// Switch over the status
switch(status)
{
case AVPlayerItemStatusReadyToPlay:
{
// Ready to Play
if(_vidPlayerItem)
{
[_lock lock];
NSDictionary* attribs =
#{
(NSString*)kCVPixelBufferPixelFormatTypeKey : #(kCVPixelFormatType_64RGBAHalf)
};
_playerItemVideoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:attribs];
[_vidPlayerItem addOutput:_playerItemVideoOutput];
[_vidPlayer setRate:0.0];
_playerItemStatus = status;
[_lock unlock];
// "Wake up" the AVPlayer/AVPlayerItem here.
[self seekToFrame:kCMTimeZero];
}
break;
}
}
}
}
The code listing below is the class that also acts the delegate for a custom protocol called VideoReaderRenderer, which handles the seekToTime: completion block, as well as converting the pixel buffer to a MTLTexture:
RendererDelegate.m
At some point in its initialization and before performing any seek operations, I instantiate the CVMetalTextureCacheRef.
CVReturn ret_val = CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, _mtlDevice, nil, &_metalTextureCache);
The method in which my VideoReader class calls inside seekToTime's completion block:
-(void)seekOperationFinished:(CVPixelBufferRef)pixelBuffer
{
CVMetalTextureRef mtl_tex = NULL;
size_t w = CVPixelBufferGetWidth(pixelBuffer);
size_t h = CVPixelBufferGetHeight(pixelBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
CVReturn ret_val = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
_metalTextureCache,
pixelBuffer,
nil,
MTLPixelFormatRGBA16Float,
w,
h,
0,
&mtl_tex);
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
if(ret_val != kCVReturnSuccess)
{
if(mtl_tex != NULL)
{
CVBufferRelease(mtl_tex);
CVPixelBufferRelease(pixelBuffer);
}
return;
}
id<MTLTexture> inputTexture = CVMetalTextureGetTexture(mtl_tex);
if(!inputTexture)
return;
NSSize texSize = NSMakeSize(inputTexture.width, inputTexture.height);
_viewPortSize = (simd_uint2){(uint)texSize.width, (uint)texSize.height};
// Create the texture here.
[_textureLock lock];
if(NSEqualSizes(_projectSize, texSize))
{
if(!_inputFrameTex)
{
MTLTextureDescriptor* texDescriptor = [MTLTextureDescriptor new];
texDescriptor.width = texSize.width;
texDescriptor.height = texSize.height;
texDescriptor.pixelFormat = MTLPixelFormatRGBA16Float;
texDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
_inputFrameTex = [_mtlDevice newTextureWithDescriptor:texDescriptor];
}
id<MTLCommandBuffer> commandBuffer = [_drawCopyCommandQueue commandBuffer];
id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
[blitEncoder copyFromTexture:inputTexture
sourceSlice:0
sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0)
sourceSize:MTLSizeMake(inputTexture.width, inputTexture.height, 1)
toTexture:_inputFrameTex
destinationSlice:0
destinationLevel:0
destinationOrigin:MTLOriginMake(0, 0, 0)];
[blitEncoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
}
[_textureLock unlock];
CVBufferRelease(mtl_tex);
CVPixelBufferRelease(pixelBuffer);
// Added "trouble-shooting" code to save the contents of the MTLTexture as a JPEG onto the user's disk.
if(_inputFrameTex)
{
// ... Save contents of texture to disk as JPEG.
}
}
Once again, my apologies for the rather long post. Additional details include having our rendering being displayed on a custom NSView backed by a CAMetalLayer, where its subsequent draw calls are called by a CVDisplayLink for background rendering. Though I don't think the latter seems to be the source of the problem of seekToTime: returning black frames. Can anyone care to shed some light to my situation? Thank you very much in advance.

SpriteKit Removing sprites to redraw

Currently in my application I've been adopting a technique to remove/re-draw certain sprites.
in example, the app is a poker app. So when a call/raise/check is made, there is a chip that is placed in-front of the player with an SKLabelNode containing the bet,check etc.. However, removing the previous to then re-add the new is inconsistent and causes a lot of EXC_BAD_ACCESS errors. Now, I guess I could program it to nest search for that node and alter the value instead of redrawing. However, it's used in multiple occasions and will at some point rely on removing the child from it's parent.
What I'm asking is what is the best technique to achieve this without the possibility of inconsistent crashes..?
-(void)removePreviousChips:(NSString *)player {
NSString *string = [NSString stringWithFormat:#"chipLabel:%#", player];
SKNode *node = [tableSprite childNodeWithName:string];
[node removeFromParent];
}
-(void)updateChipStacksAndPlayerPositionsWith:(NSDictionary *)dict {
// [tableSprite removeAllChildren];
for (int i = 0; i < 8; i++) {
//loop through seats taken if value then draw.
if (![[dict valueForKey:_seatNames[i]] isEqualToString:#""]) {
[self drawAvatarWithPlayerName:[dict valueForKey:_seatNames[i]] withSeatPosition:[[seatPositions valueForKey:_seatNames[i]] CGPointValue] withChipStack:[[dict valueForKey:#"startingChips"] intValue]];
}
}
if ([self displayStartGameButton]) {
[startGameBG runAction:[SKAction fadeAlphaTo:1 duration:0.5]];
} else {
[startGameBG runAction:[SKAction fadeAlphaTo:0 duration:0.5]];
}
}
Them two examples are consistent ways to crash my app.
EDIT
for example, a better approach would be to detect whether the node is present before the requirement to remove it from it's parent and redraw it. However, something to detect it's presence is not working out for me
SKNode *node = [tableSprite childNodeWithName:[dict valueForKey:_seatNames[i]]];
if (node) {
NSLog(#"node for %# exists", [dict valueForKey:_seatNames[i]]);
} else {
NSLog(#"node for %# doesn't exist", [dict valueForKey:_seatNames[i]]);
}

Need help wih references - ARC + Cocos 2d

ARC is kicking my ARC
I installed cocos 2d and was unhappy seeing as I wanted to use ARC. I am having an issue that I can't quite seem to figure out.
Instruments is saying I have 5 references to my GraphicsManager object, yet I can only conclude 4 of them. I need to create and release many of these.
Class "RowManager" (CCLayer) calls class "GraphicsManager" (CCNode) to make these Objects. RowManager calls its methods to move the objects via selector:
[self schedule:#selector(row:) interval:.01]
Finally, when the object arrives at a point, method "row1Finished" is called who then creates one more reference to the object before calling the object to (die) remove it's own children.
I can get all three animations to stop, get the object to detach from the RowManager and disappear, yet the dealloc method still does not call itself. What am I doing wrong?
To convert my non-arc cocos2d template I followed the instructions here:
http://www.learn-cocos2d.com/2012/04/enabling-arc-cocos2d-project-howto-stepbystep-tutorialguide/
/////////////////
#interface RowManager : CCLayer {
GraphicsManager *graphicsObject;
}
#implementation RowManager
-(id) init
{
if( (self=[super init]) ) {
graphicsObject = [[GraphicsManager alloc] initWithBoatType:#"flatboat" withWord:#"life" usingCharacter:#"Puchai" orientation:#"up" visible:YES];
graphicsObject.tag = 21;
graphicsObject.position = ccp(offset,size.height/4.1);
[self addChild: graphicsObject];
[self schedule:#selector(row1:) interval:.01];
[self schedule:#selector(rotate:) interval:6];
// etc…
-(void)row1:(ccTime)dt{
// scroll the object across the screen
graphicsObject = (GraphicsManager *)[self getChildByTag:21]; // reference: for the row method
graphicsObject.position = ccp(graphicsObject.position.x-1, graphicsObject.position.y);
if (graphicsObject.position.x < 0 - graphicsObject.boatWidth){
[self row1Finished];
}
}
-(void)rotate:(ccTime)dt {
//waggle the object a bit
graphicsObject = (GraphicsManager *)[self getChildByTag:21]; // reference 2: for the rotate method
[graphicsObject runAction:[CCSequence actions:
[CCRotateBy actionWithDuration:1 angle:.8],
[CCRotateBy actionWithDuration:2 angle:-.8],
[CCRotateBy actionWithDuration:1 angle:-.8],
[CCRotateBy actionWithDuration:2 angle:.8],
nil]];
}
-(void) row1Finished {
graphicsObject = (GraphicsManager *)[self getChildByTag:21]; // reference 3: to reference the referenced
[self unschedule:#selector (row1:)];
[self unschedule:#selector(rotateBoat:)];
[graphicsObject die]; // call within object class to remove references
[self removeChild:graphicsObject cleanup:YES];
graphicsObject = nil;
}
/////////////////////
in Graphics Manager Class:
///////////////////
#interface GraphicsManager : CCNode {...}
#property CGFloat boatWidth;
…etc
#implementation GraphicsManager
#synthesize boatWidth;
- (void) die {
[self unschedule:#selector(puchaiRow:)]; // reference 4 animation
[self stopAllActions];
[self removeAllChildrenWithCleanup:YES]; // removes sub objects created in class
NSLog(#"Died");
}
- (void)dealloc
{
CCLOG(#"Graphics Manager dealloc: %#", self);
}
-(id)initWithBoatType:(NSString *)boatType orientation:(NSString *)orientation
{
self = [super init];
if (self){
if ([orientation isEqual: #"upstream"]){
flatBoat = [CCSprite spriteWithFile:#"flatBoat.png"];
flatBoat.anchorPoint = ccp(0,0);
self.boatWidth = [flatBoat boundingBox].size.width;
[self addChild:flatBoat];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"puchai.plist"];
puchai = [CCSprite spriteWithSpriteFrameName:#"puchai1.png"];
puchai.anchorPoint = ccp(0,0);
puchai.position = ccp(20,30);
puchai.tag = 4;
[self addChild:puchai];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"cube.plist"];
cube = [CCSprite spriteWithSpriteFrameName:#"cube1.png"];
cube.scale = .80;
cube.tag = 56;
cube.visible = NO;
cube.position = ccp(150,45);
[self addChild:cube];
cube = [CCSprite spriteWithSpriteFrameName:#"cube2.png"];
cube.scale = .80;
cube.position = ccp(-1 +150,[cube boundingBox].size.height +45);
cube.tag = 57;
cube.visible = NO;
[self addChild:cube];
[self schedule:#selector(puchaiRow:) interval:3]; // reference 4 animation
flatBoatWing = [CCSprite spriteWithFile:#"flatBoatWing.png"];
flatBoatWing.anchorPoint = ccp(0,0);
flatBoatWing.position = ccp(25,17);
[self addChild:flatBoatWing];
}
///etc
-(void)puchaiRow:(ccTime)dt{
puchai = (CCSprite *)[self getChildByTag:4];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:#"puchai.plist"];
NSMutableArray *frames = [[NSMutableArray alloc]init];
for (int i = 1; i<=8; i++) {
NSString *frameName = [NSString stringWithFormat:#"puchai%i.png",i ];
[frames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:frameName]];
}
CCAnimation *a = [CCAnimation animationWithSpriteFrames:frames delay:1.0f/5.0f];
[puchai runAction:[CCAnimate actionWithAnimation:a]];
//////////
here is the zombies:history output.
# Address Category Event Type RefCt Timestamp Size Responsible Caller
0 0x13ea95e0 GraphicsManager Malloc 1 00:02.748.439 208 -[RowManager init]
1 0x13ea95e0 GraphicsManager Retain 2 00:02.748.447 0 -[GraphicsManager initWithBoatType:withWord:usingCharacter:orientation:visible:]
2 0x13ea95e0 GraphicsManager Release 1 00:02.748.449 0 -[GraphicsManager initWithBoatType:withWord:usingCharacter:orientation:visible:]
3 0x13ea95e0 GraphicsManager Retain 2 00:02.841.543 0 -[CCScheduler scheduleSelector:forTarget:interval:paused:repeat:delay:]
4 0x13ea95e0 GraphicsManager Retain 3 00:02.841.572 0 ccArrayAppendObjectWithResize
5 0x13ea95e0 GraphicsManager Retain 4 00:03.477.669 0 -[RowManager row1:] <--- AHA!!!!
6 0x13ea95e0 GraphicsManager Retain 5 00:03.482.438 0 -[RowManager row1Finished]
7 0x13ea95e0 GraphicsManager Release 4 00:03.482.439 0 -[RowManager row1Finished]
8 0x13ea95e0 GraphicsManager Release 3 00:03.482.480 0 -[CCScheduler removeHashElement:]
9 0x13ea95e0 GraphicsManager Release 2 00:03.483.435 0 ccArrayRemoveObjectAtIndex
10 0x13ea95e0 GraphicsManager Release 1 00:03.483.436 0 -[RowManager row1Finished]
you can use (__bridge id) to exclude from ARC, what do you need to exclude & use manual Retain-Release.
Should [self unschedule:#selector (row1:)]; be [self unschedule:#selector (row:)];? That would keep an extra reference alive.
Solved the problem by reviewing the zombies history one more time. In row1, I did not set the object to nil after calling row1finished, breaking the law: for each reference in the same class you must "nillify" all references to zero...
Moral of the story: the reason it was difficult to find the bug was because I had scheduled the row1 routine and was letting it run about 50 times, causing a bunch of retain releases in the zombies history making it impossible to reduce by elimination. I commented out the surrounding code getting rid of all that and was able to get a zombies output that i could parse.
Thanks all for your input! I guess I know how to use instruments a little better now!
-(void)row1:(ccTime)dt{
graphicsObjectRow1 = (GraphicsManager *)[self getChildByTag:11];
graphicsObjectRow1.position = ccp(graphicsObjectRow1.position.x + 1,graphicsObjectRow1.position.y);
// if (graphicsObjectRow1.position.x > screenX/4){
[self row1Finished];
graphicsObjectRow1 = nil;
// graphicsObjectRow1.position = ccp(offset,size.height/4.1);
// }
}

Updating NSView asynchronously from a thread

First of all, I'm an Objective-C novice. So I'm not very familiar with OS X or iOS development. My experience is mostly in Java.
I'm creating an agent-based modeling-framework. I'd like to display the simulations and to do that I'm writing a little application. First, a little bit about the framework. The framework has a World class, in which there is a start method, which iterates over all agents and has them perform their tasks. At the end of one "step" of the world (i.e., after all the agents have done their thing), the start method calls the intercept method of an object that implements InterceptorProtocol. This object was previously passed in via the constructor. Using the interceptor, anyone can get a hook into the state of the world. This is useful for logging, or in the scenario that I'm trying to accomplish: displaying the information in a graphical manner. The call to intercept is synchronous.
Now as far as the GUI app is concerned, it is pretty simple. I have a controller that initializes a custom view. This custom view also implements InterceptorProtocol so that it can listen in, to what happens in the world. I create a World object and pass in the view as an interceptor. The view maintains a reference to the world through a private property and so once I have initialized the world, I set the view's world property to the world I have just created (I realize that this creates a cycle, but I need a reference to the world in the drawRect method of the view and the only way I can have it is if I maintain a reference to it from the class).
Since the world's start method is synchronous, I don't start the world up immediately. In the drawRect method I check to see if the world is running. If it is not, I start it up in a background thread. If it is, I examine the world and display all the graphics that I need to.
In the intercept method (which gets called from start running on the background thread), I set setNeedsToDisplay to YES. Since the start method of the world is running in a separate thread, I also have a lock object that I use to synchronize so that I'm not working on the World object while it's being mutated (this part is kind of janky and it's probably not working the way I expect it to - there are more than a few rough spots and I'm simply trying to get a little bit working; I plan to clean up later).
My problem is that the view renders some stuff, and then it pretty much locks up. I can see that the NSLog statements are being called and so the code is running, but nothing is getting updated on the view.
Here's some of the pertinent code:
MasterViewController
#import "MasterViewController.h"
#import "World.h"
#import "InfectableBug.h"
#interface MasterViewController ()
#end
#implementation MasterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_worldView = [[WorldView alloc] init];
World* world = [[World alloc] initWithName: #"Bhumi"
rows: 100
columns: 100
iterations: 2000
snapshotInterval: 1
interceptor: _worldView];
for(int i = 0; i < 999; i++) {
NSMutableString* name = [NSMutableString stringWithString: #"HealthyBug"];
[name appendString: [[NSNumber numberWithInt: i] stringValue]];
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: name
layer: #"FirstLayer"
infected: NO
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
}
NSLog(#"Added all bugs. Going to add infected");
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: #"InfectedBug"
layer: #"FirstLayer"
infected: YES
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
[_worldView setWorld: world];
//[world start];
}
return self;
}
- (NSView*) view {
return self.worldView;
}
#end
WorldView
#import "WorldView.h"
#import "World.h"
#import "InfectableBug.h"
#implementation WorldView
#synthesize world;
- (id) initWithFrame:(NSRect) frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void) drawRect:(NSRect) dirtyRect {
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
CGContextClearRect(myContext, CGRectMake(0, 0, 1024, 768));
NSUInteger rows = [world rows];
NSUInteger columns = [world columns];
NSUInteger cellWidth = 1024 / columns;
NSUInteger cellHeight = 768 / rows;
if([world running]) {
#synchronized (_lock) {
//Ideally we would need layers, but for now let's just get this to display
NSArray* bugs = [world bugs];
NSEnumerator* enumerator = [bugs objectEnumerator];
InfectableBug* bug;
while ((bug = [enumerator nextObject])) {
if([bug infected] == YES) {
CGContextSetRGBFillColor(myContext, 128, 0, 0, 1);
} else {
CGContextSetRGBFillColor(myContext, 0, 0, 128, 1);
}
NSLog(#"Drawing bug %# at %lu, %lu with width %lu and height %lu", [bug name], [bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight);
CGContextFillRect(myContext, CGRectMake([bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight));
}
}
} else {
[world performSelectorInBackground: #selector(start) withObject: nil];
}
}
- (BOOL) isFlipped {
return YES;
}
- (void) intercept: (World *) aWorld {
struct timespec time;
time.tv_sec = 0;
time.tv_nsec = 500000000L;
//nanosleep(&time, NULL);
#synchronized (_lock) {
[self setNeedsDisplay: YES];
}
}
#end
start method in World.m:
- (void) start {
running = YES;
while(currentIteration < iterations) {
#autoreleasepool {
[bugs shuffle];
NSEnumerator* bugEnumerator = [bugs objectEnumerator];
Bug* bug;
while((bug = [bugEnumerator nextObject])) {
NSString* originalLayer = [bug layer];
NSUInteger originalX = [bug x];
NSUInteger originalY = [bug y];
//NSLog(#"Bug %# is going to act and location %i:%i is %#", [bug name], [bug x], [bug y], [self isOccupied: [bug layer] x: [bug x] y: [bug y]] ? #"occupied" : #"not occupied");
[bug act];
//NSLog(#"Bug has acted");
if(![originalLayer isEqualToString: [bug layer]] || originalX != [bug x] || originalY != [bug y]) {
//NSLog(#"Bug has moved");
[self moveBugFrom: originalLayer atX: originalX atY: originalY toLayer: [bug layer] atX: [bug x] atY: [bug y]];
//NSLog(#"Updated bug position");
}
}
if(currentIteration % snapshotInterval == 0) {
[interceptor intercept: self];
}
currentIteration++;
}
}
//NSLog(#"Done.");
}
Please let me know if you'd like to see any other code. I realize that the code is not pretty; I was just trying to get stuff to work and I plan on cleaning it up later. Also, if I'm violating an Objective-C best practices, please let me know!
Stepping out for a bit; sorry if I don't respond immediately!
Whew, quiet a question for probably a simple answer: ;)
UI updates have to be performed on the main thread
If I read your code correctly, you call the start method on a background thread. The start method contains stuff like moveBugFrom:... and also the intercept: method. The intercept method thus calls setNeedsDisplay: on a background thread.
Have all UI related stuff perform on the main thread. Your best bet is to use Grand Central Dispatch, unless you need to support iOS < 4 or OS X < 10.6 (or was it 10.7?), like this:
dispatch_async(dispatch_get_main_queue(), ^{
// perform UI updates
});

Removing object from NSMutableArray after upgrading to SDK 5 for iOS causes my app to crash

[Edit] I rearranged how I remove obj from the list. That's originally how it was still causing the same error.
This is the code in question. I believe I have the error narrowed down to the line where it removes the object from the _blocks NSMutableArray. I have this exact same code in other parts of my code removing similar object from the same array. For some reason when this function is called it causes the game to crash. It didn't have this problem before I upgraded to the latest XCode which supports iOS SDK 5 and armv7. Before I upgraded XCode it worked fine. I would walk into an item and the item would disappear from the screen. Now it just crashes when I get an item. Any help would be greatly appreciated.
-(void)itemCollision:(Collidable *)obj :(int)itemID :(int)objID: (bool)withPlayer{
[background removeChild:[obj getSprite] cleanup:YES];
[background removeChild:[obj getArrow] cleanup:YES];
[_blocks removeObject:obj];
//[obj release];
if(withPlayer){
if(itemID == 1){
[[SimpleAudioEngine sharedEngine] playEffect:#"Item2.mp3" pitch:1.0f pan:0.0f gain:0.3f];
points += (10 + level * 22);
}
}
}
#import <UIKit/UIKit.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, #"AppDelegate");
[pool release];
return retVal;
}
When the app crashes it highlights this line:
int retVal = UIApplicationMain(argc, argv, nil, #"AppDelegate");
The error message is: "Thread 1: Program received signal: "SIGARBRT".
Here is the code where I call itemCollision:
-(bool) isCollision {
CCSprite *playerSprite = mob;//[mob getSprite];
CGRect playerRect = CGRectMake(playerSprite.position.x - (playerSprite.contentSize.width/2),
playerSprite.position.y - (playerSprite.contentSize.height/2),
playerSprite.contentSize.width,
playerSprite.contentSize.height);
//Player Collision
BOOL collision = FALSE;
for (Block *block in _blocks) {
CCSprite *blockSprite = [block getSprite];
CGRect blockRect = CGRectMake(blockSprite.position.x - (blockSprite.contentSize.width/2),
blockSprite.position.y - (blockSprite.contentSize.height/2),
blockSprite.contentSize.width,
blockSprite.contentSize.height + 1 + [mob getSpeed]);
if (CGRectIntersectsRect(blockRect, playerRect)) {
if ([block getItemID] != 0) {
[self itemCollision:block :[block getItemID] :[block getID] :TRUE];
continue;
}
if(playerSprite.position.y > blockSprite.position.y){
collision = TRUE;
}
if(collision) {
[[SimpleAudioEngine sharedEngine] playEffect:#"squish.caf"];
[self die];
return TRUE;
}
}
}
return FALSE;
}
Here is how I remove objects from _blocks elsewhere in the code. This is in the gravity timer function:
NSMutableArray *blocksToDelete = [[NSMutableArray alloc] init];
for (Block *block in _blocks) {
CCSprite *blockSprite = [block getSprite];
//[block addSpeed:gravity];
[block setPreX:blockSprite.position.x];
[block setPreY:blockSprite.position.y];
blockSprite.position = ccp(blockSprite.position.x, blockSprite.position.y - [block getSpeed]);
if (blockSprite.position.y < -30 + blockSprite.contentSize.height / 2) {
[blocksToDelete addObject:block];
}
if(blockSprite.position.y - blockSprite.contentSize.height < [block getArrow].position.y){
[block getArrow].visible = FALSE;
}
}
for (Block *b in blocksToDelete) {
[_blocks removeObject:b];
[background removeChild:[b getSprite] cleanup:YES];
[background removeChild:[b getArrow] cleanup:YES];
[b release];
}
I get to the items before they reach the point where they get deleted by this function. Again, this code has been working for months until I upgraded my XCode.
If the array had the last retain on the object, it will be dealloced when you remove it. You go on to use the object for the rest of the method, so it needs to stay alive. Certainly checking the retainCount of obj to see if the NSMutableArray has the last retain is the first place to start.
If we assume, for the moment, that that's the case, then try this alternate approach:
-(void)itemCollision:(Collidable *)obj :(int)itemID :(int)objID: (bool)withPlayer
{
[[obj retain] autorelease];
[_blocks removeObject:obj];
[background removeChild:[obj getSprite] cleanup:YES];
[background removeChild:[obj getArrow] cleanup:YES];
if(withPlayer){
if(itemID == 1){
[[SimpleAudioEngine sharedEngine] playEffect:#"Item2.mp3" pitch:1.0f pan:0.0f gain:0.3f];
points += (10 + level * 22);
}
}
}
When the debugger goes to main.m, it's often because it's dying while popping an autorelease pool -- a task you don't have debug symbols for, so it goes to the deepest stack frame for which it has source to show you, which in the case of autorelease pools on the main thread, will be your main() function.
You should also try running this case with the Zombies instrument in Instruments. It can be very helpful for tracking this stuff down.
It's hard to know exactly what's happening beyond the scope of the code sample you've shared, but a wild guess would be that your 'obj' is being over-released. It's released once when you remove it from the array, then you're releasing it again. This may or may not be appropriate considering the retain count of that object as its passed in.
To begin troubleshooting, try using the Instrument Zombie tool - that'll help you observe if it is indeed being overreleased.
You could additionally comment out your:
[obj release]
To see if that stops the error (which may or may not be the solution but should be insightful in any case).