Memory Leak with ARC - objective-c

+(void)setup {
UIImage* spriteSheet = [UIImage imageNamed:#"mySpriteSheet.png"];
CGRect rect;
animation = [NSMutableArray arrayWithCapacity:numberOfFramesInSpriteSheet];
int frameCount = 0;
for (int row = 0; row < numberFrameRowsInSpriteSheet; row++) {
for (int col = 0; col < numberFrameColsInSpriteSheet; col++) {
frameCount++;
if (frameCount <= numberOfFramesInSpriteSheet) {
rect = CGRectMake(col*frameHeight, row*frameWidth, frameHeight, frameWidth);
[animation addObject:[UIImage imageWithCGImage:CGImageCreateWithImageInRect(spriteSheet.CGImage, rect)] ];
}
}
}
}
Compiled the above code with ARC enabled. The Analyze tool reports a possible memory leak since imageWithCGImage:: returns UIImage with count +1, then reference is lost. Leaks Instrument reports no memory leaks at all. Whats going on here?
Furthermore, since ARC prohibits use of manually using release ect, how does one fix the leak?
Thanks to anyone who can offer any advice.

ARC does not manage C-types, of which CGImage may be considered. You must release the ref manually when you are finished with CGImageRelease(image);
+(void)setup {
UIImage* spriteSheet = [UIImage imageNamed:#"mySpriteSheet.png"];
CGRect rect;
animation = [NSMutableArray arrayWithCapacity:numberOfFramesInSpriteSheet];
int frameCount = 0;
for (int row = 0; row < numberFrameRowsInSpriteSheet; row++) {
for (int col = 0; col < numberFrameColsInSpriteSheet; col++) {
frameCount++;
if (frameCount <= numberOfFramesInSpriteSheet) {
rect = CGRectMake(col*frameHeight, row*frameWidth, frameHeight, frameWidth);
//store our image ref so we can release it later
//The create rule says that any C-interface method with "create" in it's name
//returns a +1 foundation object, which we must release manually.
CGImageRef image = CGImageCreateWithImageInRect(spriteSheet.CGImage, rect)
//Create a UIImage from our ref. It is now owned by UIImage, so we may discard it.
[animation addObject:[UIImage imageWithCGImage:image]];
//Discard the ref.
CGImageRelease(image);
}
}
}
}

None of the core foundation data structure is dealt with ARC. Many a times this creates a problem. In these case we have to manually release the memory.

Related

Objective-C: NSBitmapImageRep SetColor

I'm trying to read an NSImage into an NSBitmapImageRep, for changing the color of some pixels. The Program should read the Color-Value of each pixel, and check if its equal to a color which is selected by a colorwell. I've got an ImageView named "MainImageView" and a ColorWell named "MainColor". That's my code:
- (IBAction)ScanImg:(id)sender {
NSBitmapImageRep *ImgRep = [[NSBitmapImageRep alloc]initWithData: MainImageView.image.TIFFRepresentation];
for (int pixelx = 0; pixelx < ImgRep.pixelsWide; pixelx ++) {
for (int pixely = 0; pixely < ImgRep.pixelsHigh;pixely ++){
if ([ImgRep colorAtX:pixelx y:pixely] == MainColor.color) {
[ImgRep setColor: [NSColor whiteColor] atX:pixelx y:pixely];
} else{
[ImgRep setColor: [NSColor blackColor] atX:pixelx y:pixely];
}
}
}
struct CGImage *CG = [ImgRep CGImage];
NSImage *Image = [[NSImage alloc] initWithCGImage:CG size:ImgRep.size];
MainImageView.image = Image;
}
But the Code changes nothing on the picture! What's the problem? Or is there another way, to change the pixel's Color?
Thank you for your help!
DaRi
I have the same issue. A workaround I found is to use the -setPixel method, although this feels a bit clumsy:
NSUInteger pix[4]; pix[0] = 0; pix[1] = 0; pix[2] = 0; pix[3] = 255; //black
[ImgRep setPixel:pix atX:x y:y];
Note: The pixel data must be arranged according to the color space you are using, in this example 0,0,0,255 is black in RGBA color space.
But in the end, thats still better than copying and modifying the underlying (unsigned char*) data that is returned by [ImgRep bitmapData] ...

Objective C: How to convert CGImageRef to UIImageView?

I'm using the XZINGObjC framework to create an EAN-Barcode-Image. Following the documentation, I'm doing it like
//in viewDidAppear
//XZING: create Matrix
NSString* eanString = #"1234567890123"; //sth. like that
ZXBitMatrix* result = [writer encode:eanString
format:kBarcodeFormatEan13
width:500
height:500
error:&error];
if (result) {
//XZING: convert matrix to CGImageRef
CGImageRef imageRef = [[ZXImage imageWithMatrix:result] cgimage];
//CRASHLINE HERE!! (this is NOT in the XZING documentation, but i cannot figure out the issue!)
UIImage* uiImage = [[UIImage alloc] initWithCGImage:imageRef]; //<--CRASH: EXC_BAD_ACCESS
if(image != nil){
//assigning image to ui
self.barCodeImageView.image = uiImage;
}
It works, if I step through this code using breakpoints! However, I think at some point an Image is not ready for use?! But I cannot find the reason.
What I tried:
using imageRef and uiImage as local variables (EXC_BAD_ACCESS CRASH)
tried that operation in a background thread (EXC_BAD_ACCESS CRASH)
Same here, every solution worked if I used breakpoints and stepped line by line through the code. What is my mistake here? Any ideas? Thanks in advance!
After some try and error programming, I could fix the issue by replacing the following lines
CGImageRef imageRef = [[ZXImage imageWithMatrix:result] cgimage];
UIImage* uiImage = [[UIImage alloc] initWithCGImage:imageRef]; //<--CRASH
with
UIImage* uiImage = [[UIImage alloc] initWithCGImage:[[ZXImage imageWithMatrix:result] cgimage]];
Still, I don't know why?! I'm pretty sure something isn't hold in the memory or maybe the CGImageRef isn't ready if I try to convert it to UIImage.
Problem is with in [ZXImage imageWithMatrix:result], it is creating CGImage and before assigning it to ZXImage which will increase its retain count it is releasing the CGImage by CFRelease.
To fix this issue, replace + (ZXImage *)imageWithMatrix:(ZXBitMatrix *)matrix method with below implementation.
+ (ZXImage *)imageWithMatrix:(ZXBitMatrix *)matrix {
int width = matrix.width;
int height = matrix.height;
int8_t *bytes = (int8_t *)malloc(width * height * 4);
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
BOOL bit = [matrix getX:x y:y];
int8_t intensity = bit ? 0 : 255;
for(int i = 0; i < 3; i++) {
bytes[y * width * 4 + x * 4 + i] = intensity;
}
bytes[y * width * 4 + x * 4 + 3] = 255;
}
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef c = CGBitmapContextCreate(bytes, width, height, 8, 4 * width, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);
CGImageRef image = CGBitmapContextCreateImage(c);
ZXImage *zxImage = [[ZXImage alloc] initWithCGImageRef:image];
CFRelease(colorSpace);
CFAutorelease(image);
CFRelease(c);
free(bytes);
return zxImage;
}
For people writing Swift, I hit the same problem that longilong described. The answer was in the ZXingObjC objective C example projects. Remember: this is for generating a barcode only.
internal func encodeBarcode(){
let writer : ZXMultiFormatWriter! = ZXMultiFormatWriter()
var result: ZXBitMatrix!
let hint: ZXEncodeHints! = ZXEncodeHints()
do {
hint.margin = 0
result = try writer.encode("Example String", format: kBarcodeFormatAztec, width: 500, height: 500, hints: hint)
**let rawBarcode: ZXImage = ZXImage(matrix: result)
barcodeUIImage.image = UIImage(CGImage: rawBarcode.cgimage)**
} catch {
print("failed to generate barcode")
}
}

Reversing sprite animation

After making adjustments to my code as suggested, my animation now works. Now, I'd like to ask how to reverse the movements of the sprite. I am trying to make the sprite move as if the wind is blowing in it. Here's my code now:
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if( (self=[super init]) ) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
self.isTouchEnabled = YES;
CCSprite *black = [CCSprite spriteWithFile:#"b.png"];
black.position = ccp(100, 160);
black.scaleX = 100 / black.contentSize.width;
black.anchorPoint = ccp(0.03, 0);
CCSpriteFrameCache *frame = [CCSpriteFrameCache sharedSpriteFrameCache];
[frame addSpriteFramesWithFile:#"bLongAnimation.plist"];
CCSpriteBatchNode *bHair = [CCSpriteBatchNode batchNodeWithFile:#"bLongAnimation.png"];
[self addChild:bHair];
[self addChild:black z:1 tag:1];
//Animation
NSMutableArray *animateBlackHair = [NSMutableArray arrayWithCapacity:10];
for (int i = 1; i < 10; i++)
{
NSString *animBlackHair = [NSString stringWithFormat:#"b%i.png", i];
CCSpriteFrame *blackFrame = [frame spriteFrameByName:animBlackHair];
[animateBlackHair addObject:blackFrame];
//I added this code block thinking it might work
for(i = 10; i > 1; i--)
{
NSString *revAnimation = [NSString stringWithFormat:#"bRightLong%i.png", i];
CCSpriteFrame *revFrame = [frame spriteFrameByName:revAnimation];
[animateBlackHair1 addObject:revFrame];
}
}
CCAnimation *blowHair = [CCAnimation animationWithSpriteFrames:animateBlackHair delay:0.1];
CCAction *blowingHair = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:blowHair]];
[black runAction:blowingHair];
}
return self;}
My guess didn't work so I was wondering how I can reverse the movements as soon as it finishes the first one?
UPDATE: Never mind, I figured it out. I just moved the for loop for reversing the action outside of the other loop. Thank you for the help.
Try setting the delay on the animation to something other than 0. Try 0.1 or 0.4 or something. If you set it to zero I don't think the animation runs, and if it does, it runs too fast to be visible.

Cocos2D - Detecting collision

I am a beginner in cocos2d and im facing a problem with detecting collision for my coins.
Sometimes it works sometimes it doesn't.
So basically, im creating a game which the user (ship) have to avoid the obstacles and collect coins on the way. The collision of the obstacle works well but not for the coins.
I was thinking maybe the loops for creating many coins is the problem but im not sure.
Can anyone help?
My codes:
- (void)update:(ccTime)dt{
double curTime = CACurrentMediaTime();
if (curTime > _nextBridgeSpawn) {
float randSecs = [self randomValueBetween:3.0 andValue:5.0];
_nextBridgeSpawn = randSecs + curTime;
float randX = [self randomValueBetween:50 andValue:500];
float randDuration = [self randomValueBetween:8.0 andValue:10.0];
CCSprite *bridge = [_bridge objectAtIndex:_nextBridge];
_nextBridge++;
if (_nextBridge >= _bridge.count) _nextBridge = 0;
[bridge stopAllActions];
bridge.position = ccp(winSize.width/2, winSize.height);
bridge.visible = YES;
[bridge runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:randDuration position:ccp(0, -winSize.height)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],
nil]];
this is where i declare my coins (continued from the update method)
int randCoin = [self randomValueBetween:0 andValue:5];
_coin = [[CCArray alloc] initWithCapacity:randCoin];
for(int i = 0; i < randCoin; ++i) {
coin = [CCSprite spriteWithFile:#"coin.png"];
coin.visible = NO;
[self addChild:coin];
[_coin addObject:coin];
}
float randCoinX = [self randomValueBetween:winSize.width/5 andValue:winSize.width - (border.contentSize.width *2)];
float randCoinY = [self randomValueBetween:100 andValue:700];
float randCoinPlace = [self randomValueBetween:30 andValue:60];
for (int i = 0; i < _coin.count; ++i) {
CCSprite *coin2 = [_coin objectAtIndex:i];
coin2.position = ccp(randCoinX, (bridge.position.y + randCoinY) + (randCoinPlace *i));
coin2.visible = YES;
[coin2 runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:randDuration position:ccp(0, -winSize.height-2000)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],
nil]];
}
}
this is to check for collision (also in the update method)
for (CCSprite *bridge in _bridge) {
if (!bridge.visible) continue;
if (CGRectIntersectsRect(ship.boundingBox, bridge.boundingBox)){
bridge.visible = NO;
[ship runAction:[CCBlink actionWithDuration:1.0 blinks:5]];
}
}
}
//this is the collision for coins which only work at times
for (CCSprite *coin2 in _coin) {
if (!coin2.visible) continue;
if (CGRectIntersectsRect(ship.boundingBox, coin2.boundingBox)) {
NSLog(#"Coin collected");
coin2.visible = NO;
}
}
}
Thank you.
Why you just don't check distances?
float dist = ccpDistance(ship.position, coin2.position);
float allowedDist = ship.contentSize.width*0.5 + coin2.contentSize.width*0.5;
if (dist<allowedDist){
NSLog(#"Collision detected");
}
If your sprites are not on the same layer, these layers should have at least same positions.
what is phone in the last piece of code? you check there if it's boundingBox intersects coins one, but I cannot see it anywhere else. Another one, boundingBox will work only if your objects have the same parent, because it returns "local" rect, relatieve to it's parent
another one point is that in case of too fast movement speed of your objects it is possible, that they will pass throw each other during tick time. I mean, that one update will be called before collision, so it will not detect it and the next one will be called after collision was finished, so it will not detect it either.
I've solved my problem by creating a timer in init and call it every 10 secs to create my coins.
My codes:
-(void) createCoins:(ccTime)delta{
CGSize winSize = [CCDirector sharedDirector].winSize;
int randCoin = [self randomValueBetween:0 andValue:10];
_coin = [[CCArray alloc] initWithCapacity:randCoin];
for(int i = 0; i < randCoin; i++) {
coin = [CCSprite spriteWithFile:#"coin30p.png"];
coin.visible = NO;
[self addChild:coin];
[_coin addObject:coin];
}
float randCoinX = [self randomValueBetween:winSize.width/5 andValue:winSize.width - (border.contentSize.width *2)];
float randCoinY = [self randomValueBetween:100 andValue:700];
float randCoinPlace = [self randomValueBetween:30 andValue:60];
for (int i = 0; i < _coin.count; i++) {
CCSprite *coin2 = [_coin objectAtIndex:i];
coin2.position = ccp(randCoinX, (winSize.height + randCoinY) + (randCoinPlace *i));
coin2.visible = YES;
[coin2 runAction:[CCSequence actions:
[CCMoveBy actionWithDuration:8 position:ccp(0, -winSize.height-2000)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],
nil]];
}
}
in the update method:
for (CCSprite *coins in _coin){
if (!coins.visible) continue;
if (CGRectIntersectsRect(phone.boundingBox, coins.boundingBox)) {
NSLog(#"Coin collected");
coins.visible = NO;
}
}
Thank you everyone for the help(:

How to access a property of an object from an NSMutableArray

In my program, I am trying to set the property 'frame' of an object stored in an NSMutableArray. When I try to set the frame of the object, my program receives the signal 'SIGABRT' with the message '__[NSCFNumber setFrame:]: unrecognized selector sent to instance 0x6a8d960.' How do I fix this?
for (int i = 0; i < [computerHand count]; i++)
{
CardView* card = [computerHand objectAtIndex:i];
card.frame = CGRectMake(10+70*i, 340, 60, 85);
}
declaration of computerHand:
#property(retain) NSMutableArray* computerHand;
population of computerHand
-(void) addCardToHand:(NSMutableArray *)hand
{
[hand addObject:[cards objectAtIndex:0]];
NSLog(#"%#", [[cards objectAtIndex:0]class]);
[cards removeObjectAtIndex:0];
}
Note- The NSLog prints '__NSCFNumber' to the console.
code for deck population
-(void) createDeck : (UIView *)view
{
cards = [[NSMutableArray alloc]initWithCapacity:52];
for (int i = 0; i <= 4; i++)
{
for(int j = 0; j <= 13; j++)
{
CardView* card = [[CardView alloc] initWithFrame:CGRectMake(view.center.x - 60/2, view.center.y - 85/2, 60, 85) value:j];
[cards addObject:card];
[view addSubview:card];
}
}
for(int i =0; i<= 52; i++)
{
NSLog(#"%#", [cards objectAtIndex:i]);
}
}
Note: the NSLog command correctly prints a CardView object to the console, but then prints __NSCFNumber when trying to access it from a different scope.
Any help is appreciated.
The issue is you're not getting the correct object out of your array. If you break right after you get your CardView out of your array, and print the description in the console, it's a string and not actually a CardView. There are two things you need to do.
Make sure you only add CardViews to your array. Double check this.
In your for loop you can do this to make sure you are only setting frames on CardViews:
if([card isMemberOfClass:[CardView class]]) {
card.frame = CGRectMake(10+70*i, 340, 60, 85);
}
You still want to make sure that you're actually only putting in CardViews though, but this is a way to debug.