How to keep IBOutlet property from being released (iOS using ARC) - ios

I am implementing the AQRecorder class from Apple's SpeakHere example into my project using ARC. To get it to compile, I had to create a class (AQRecorderController) that controls the AQRecorder instance (equivalent to the SpeakHereController in the example). AQRecorderController is connected through the nib of my main view controller and implemented as a property. The problem occurs whether or not the property is strong or weak.
My problem is that shortly after loading the view controller, the AQRecorderController is released, but only when tested on device. In the simulator, this does not occur. It occurs for iPad and iPhone, iOS 5 and iOS 6. I need to maintain this reference throughout the lifetime of my view controller for recording purposes (you can't delete the recorder while recording and expect to have a finished file).
Has anyone run into this or anything similar? If the AQRecorderController property is strong, I get a bad access error when trying to use it, if its weak, I just get a nil, and its unusable.
Any help would be greatly appreciated.
formViewController.h:
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#class AQRecorderController;
#interface formViewController : UIViewController <UIActionSheetDelegate, UITableViewDelegate, UIGestureRecognizerDelegate> {
IBOutlet AQRecorderController *aqRecorderController;
}
#property (nonatomic, weak) IBOutlet AQRecorderController *aqRecorderController;
#end
AQRecorderController.h
#import <Foundation/Foundation.h>
#import "AQRecorder.h"
#interface AQRecorderController : NSObject
{
AQRecorder *aqRecorder;
}
#property (readonly) AQRecorder* aqRecorder;
#property (nonatomic, assign) bool isRecording;
#property (nonatomic, strong) NSString* fileName;
-(bool)startRecording;
-(bool)pauseRecording;
-(bool)stopRecording;
-(bool)initializeRecordSettingsWithCompression:(bool)compressionEnabled;
#end
formView.xib:
Here is the stack trace after the AQRecorderController has been released:
2012-10-23 10:34:09.600 TestApp[510:907] (
0 TestApp 0x000f32ab
-[AQRecorderController dealloc] + 138
1 CoreFoundation 0x32247311 CFRelease + 100
2 CoreFoundation 0x3225195d <redacted> + 140
3 libobjc.A.dylib 0x31ad5489 <redacted> + 168
4 CoreFoundation 0x32249441 _CFAutoreleasePoolPop + 16
5 Foundation 0x37303a7f <redacted> + 466
6 CoreFoundation 0x322db5df <redacted> + 14
7 CoreFoundation 0x322db291 <redacted> + 272
8 CoreFoundation 0x322d9f01 <redacted> + 1232
9 CoreFoundation 0x3224cebd CFRunLoopRunSpecific + 356
10 CoreFoundation 0x3224cd49 CFRunLoopRunInMode + 104
11 GraphicsServices 0x32fb52eb GSEventRunModal + 74
12 UIKit 0x34e92301 UIApplicationMain + 1120
13 TestApp 0x00081a9d main + 48
14 TestApp 0x0005aa68 start + 40
)
This is where the recorder is instantiated.
AQRecorderController.mm:
- (void)awakeFromNib
{
aqRecorder = new AQRecorder();
}
This is where the recorder is used. By this point, the AQRecorderController has been released and this code never executes (it causes a crash, because the AQRecorderController has been deallocated).
-(bool)startRecording
{
if (aqRecorder->IsRunning())
{
[self stopRecording];
}
else // If we're not recording, start.
{
#try
{
// Start the recorder
CFStringRef filenameString = (CFStringRef)CFBridgingRetain(self.fileName);
aqRecorder->StartRecord(filenameString);
}
#catch(NSException *ex)
{
NSLog(#"Error: %#", [ex description]);
return NO;
}
[self setFileDescriptionForFormat:aqRecorder->DataFormat() withName:#"Recorded File"];
}
[self checkIfRecording];
return YES;
}
Here is where the AQRecorderController is instantiated.
formViewController.mm:
//this is called in viewDidAppear
-(void)initializeAQRecorder: (NSString*)soundFileName
{
aqRecorderController = [[AQRecorderController alloc] init];
NSLog(#"AQRecorderController is being initialized for file %#",soundFileName);
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *soundFilePath =[[NSString alloc] initWithFormat:#"%#",[documentsDir stringByAppendingPathComponent:soundFileName]];
[aqRecorderController setFileName:soundFilePath];
[aqRecorderController initializeRecordSettingsWithCompression:NO];
}

My problem is that shortly after loading the view controller, the
AQRecorderController is released...I need to maintain this reference
throughout the lifetime of my view controller
Mark your property strong instead of weak. weak means that the object pointed to by aqRecorderController won't be retained by the setter; strong will cause it to be retained.
If the AQRecorderController property is strong, I get a bad access
error when trying to use it, if its weak, I just get a nil, and its unusable.
That sounds like the property is being set to some invalid value somewhere in your program. Since you can't manually retain the object under ARC and you've marked the property weak, it may be released very early on. I'm not sure why you'd have a problem if you mark it strong... it'd help to see the code where you set the variable or property.

You're never setting the AQRecorderController to your formViewController from what I see. You need to do self.aqRecorderController = aqRecorderController, I believe it's just disappearing as soon as you leave the scope where you create the controller.

I got it working for now. I haven't completely fixed it, but I can record without it crashing. I commented out every line having to do with AQRecorderController until it stopped being released, then slowly added them back until I found out where it happens. It looks like the audio session setup code somehow provokes it to release the controller. This is the code that causes it (but no errors are thrown here):
From AQRecorderController.mm:
-(void)initializeRecordSettingsWithCompression:(bool)compressionEnabled
{
OSStatus error = AudioSessionInitialize(NULL, NULL, interruptionListener, (__bridge void*)self);
if (error) printf("ERROR INITIALIZING AUDIO SESSION! %d\n", (int)error);
else
{
UInt32 category = kAudioSessionCategory_PlayAndRecord;
error = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(category), &category);
if (error) printf("couldn't set audio category!");
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, (__bridge void*)self);
if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
UInt32 inputAvailable = 0;
UInt32 size = sizeof(inputAvailable);
// we do not want to allow recording if input is not available
error = AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &inputAvailable);
if (error) printf("ERROR GETTING INPUT AVAILABILITY! %d\n", (int)error);
// we also need to listen to see if input availability changes
error = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioInputAvailable, propListener, (__bridge void*)self);
if (error) printf("ERROR ADDING AUDIO SESSION PROP LISTENER! %d\n", (int)error);
error = AudioSessionSetActive(true);
if (error) printf("AudioSessionSetActive (true) failed");
}
}
So far, this isn't necessary for the functioning of my app, but I am curious as to why it would cause the AQRecorderController instance to release.

Related

Application tried to present a nil modal view controller on target in React Native Objective-C extension

I am trying to port an iOS app over to React Native.
In this iOS app, one of the functions that needs to be done is to integrate it with a PayPal library (which is currently deprecated - we want to move away from it at some point later this year, but do not have the resources to do so now).
All we're doing with this library is obtaining a unique code from PayPal - that requires a View Controller to pop up, accept client credentials and return a code giving access.
I am extremely new to Objective-C.
I've got this so far (note: I have not included all the methods/properties but can include any missing):
COMPLETE PaypalSdk.h and PaypalSdk.m are at the bottom now
I am basing this off of this library:
https://github.com/paypal/PayPal-iOS-SDK/blob/master/SampleApp/PayPal-iOS-SDK-Sample-App/ZZMainViewController.m
And this documentation:
https://github.com/paypal/PayPal-iOS-SDK/blob/master/docs/profile_sharing_mobile.md
However when trying what I am above, I get the following error:
How exactly should I resolve this? It seems to need a View Controller but I'm not totally sure how to launch one from React Native in this context.
All we're trying to get is the shared profile information.
Here is one of the stack traces:
callstack: (
0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48
2 UIKitCore 0x00007fff47a25b1a -[UIViewController _presentViewController:withAnimationController:completion:] + 5247
3 UIKitCore 0x00007fff47a2801b __63-[UIViewController _presentViewController:animated:completion:]_block_invoke + 98
4 UIKitCore 0x00007fff47a28533 -[UIViewController _performCoordinatedPresentOrDismiss:animated:] + 511
5 UIKitCore 0x00007fff47a27f79 -[UIViewController _presentViewController:animated:completion:] + 187
6 UIKitCore 0x00007fff47a281e0 -[UIViewController presentViewController:animated:completion:] + 150
7 myappname 0x000000010f92f8ac -[PaypalSdk getUserAuthorizationForProfileSharing] + 348
8 myappname 0x000000010f92fd99 -[PaypalSdk generateCode:] + 233
9 CoreFoundation 0x00007fff23c7820c __invoking___ + 140
10 CoreFoundation 0x00007fff23c753af -[NSInvocation invoke] + 319
11 CoreFoundation 0x00007fff23c75684 -[NSInvocation invokeWithTarget:] + 68
12 myappname 0x000000010f6e3902 -[RCTModuleMethod invokeWithBridge:module:arguments:] + 2658
13 myappname 0x000000010f6e7a37 _ZN8facebook5reactL11invokeInnerEP9RCTBridgeP13RCTModuleDatajRKN5folly7dynamicE + 791
14 myappname 0x000000010f6e7543 _ZZN8facebook5react15RCTNativeModule6invokeEjON5folly7dynamicEiENK3$_0clEv + 131
15 myappname 0x000000010f6e74b9 ___ZN8facebook5react15RCTNativeModule6invokeEjON5folly7dynamicEi_block_invoke + 25
16 libdispatch.dylib 0x0000000110caddd4 _dispatch_call_block_and_release + 12
17 libdispatch.dylib 0x0000000110caed48 _dispatch_client_callout + 8
18 libdispatch.dylib 0x0000000110cb55ef _dispatch_lane_serial_drain + 788
19 libdispatch.dylib 0x0000000110cb617f _dispatch_lane_invoke + 422
20 libdispatch.dylib 0x0000000110cc1a4e _dispatch_workloop_worker_thread + 719
21 libsystem_pthread.dylib 0x00007fff5246371b _pthread_wqthread + 290
22 libsystem_pthread.dylib 0x00007fff5246357b start_wqthread + 15
)
Here is my complete PayPalSdk.m file:
#import <PayPal-iOS-SDK/PayPalMobile.h>
#import <PayPal-iOS-SDK/PayPalConfiguration.h>
#import <PayPal-iOS-SDK/PayPalOAuthScopes.h>
#import <PayPal-iOS-SDK/PayPalProfileSharingViewController.h>
#import <QuartzCore/QuartzCore.h>
#import "PaypalSdk.h"
#interface PaypalSdk ()
#property(nonatomic, strong, readwrite) IBOutlet UIButton *payNowButton;
#property(nonatomic, strong, readwrite) IBOutlet UIButton *payFutureButton;
#property(nonatomic, strong, readwrite) IBOutlet UIView *successView;
#property(nonatomic, strong, readwrite) PayPalConfiguration *payPalConfig;
#end
#implementation PaypalSdk
#define kPayPalEnvironment PayPalEnvironmentProduction
//int *REQUEST_CODE_PROFILE_SHARING = 3;
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Pinyada PayPal";
// Set up payPalConfig
self.payPalConfig = [[PayPalConfiguration alloc] init];
self.payPalConfig.acceptCreditCards = NO;
self.payPalConfig.merchantName = #"Pinyada PayPal";
self.payPalConfig.merchantPrivacyPolicyURL = [NSURL URLWithString:#"https://www.paypal.com/webapps/mpp/ua/privacy-full"];
self.payPalConfig.merchantUserAgreementURL = [NSURL URLWithString:#"https://www.paypal.com/webapps/mpp/ua/useragreement-full"];
NSLog(#"PayPal iOS SDK version: %#", [PayPalMobile libraryVersion]);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
#warning "Enter your credentials"
[PayPalMobile initializeWithClientIdsForEnvironments:#{PayPalEnvironmentProduction : #"PayPalProductionID",
PayPalEnvironmentSandbox : #"YOUR_CLIENT_ID_FOR_SANDBOX"}];
return YES;
}
/*- (void)generateCode:()code {
NSLog(#"Test");
}*/
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Start out working with the mock environment. When you are ready, switch to PayPalEnvironmentProduction.
[PayPalMobile preconnectWithEnvironment:PayPalEnvironmentProduction];
}
- (IBAction)getUserAuthorizationForProfileSharing:(id)sender {
NSSet *scopeValues = [NSSet setWithArray:#[kPayPalOAuth2ScopeOpenId, kPayPalOAuth2ScopeEmail, kPayPalOAuth2ScopeAddress, kPayPalOAuth2ScopePhone]];
PayPalProfileSharingViewController *profileSharingPaymentViewController = [[PayPalProfileSharingViewController alloc] initWithScopeValues:scopeValues configuration:self.payPalConfig delegate:self];
[self presentViewController:profileSharingPaymentViewController animated:YES completion:nil];
}
- (IBAction)obtainConsent {
// Choose whichever scope-values apply in your case. See `PayPalOAuthScopes.h` for a complete list of available scope-values.
NSSet *scopeValues = [NSSet setWithArray:#[kPayPalOAuth2ScopeOpenId, kPayPalOAuth2ScopeEmail, kPayPalOAuth2ScopeAddress, kPayPalOAuth2ScopePhone]];
PayPalProfileSharingViewController *psViewController;
NSLog(#"PS VIEW CONTROLLER");
NSLog(psViewController);
psViewController = [[PayPalProfileSharingViewController alloc] initWithScopeValues:scopeValues
configuration:self.payPalConfig
delegate:self];
// Access the root view controller
UIViewController *rootviewcontroller= [UIApplication sharedApplication].keyWindow.rootViewController;
// Present the PayPalProfileSharingViewController
[ rootviewcontroller presentViewController:psViewController animated:YES completion:nil];
}
- (void)userDidCancelPayPalProfileSharingViewController:(PayPalProfileSharingViewController *)profileSharingViewController {
// User cancelled login. Dismiss the PayPalProfileSharingViewController, breathe deeply.
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)payPalProfileSharingViewController:(PayPalProfileSharingViewController *)profileSharingViewController
userDidLogInWithAuthorization:(NSDictionary *)profileSharingAuthorization {
// The user has successfully logged into PayPal, and has consented to profile sharing.
NSLog(#"REACT NATIVE ENV test");
// Be sure to dismiss the PayPalProfileSharingViewController.
[self dismissViewControllerAnimated:YES completion:nil];
}
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(generateCode: (RCTResponseSenderBlock)callback) {
[PayPalMobile initializeWithClientIdsForEnvironments:#{PayPalEnvironmentProduction : #"PayPalProductionID",
PayPalEnvironmentSandbox : #"YOUR_CLIENT_ID_FOR_SANDBOX"}];
[self obtainConsent];
NSLog(#"REACT NATIVE ENV test");
//code = #"this is a test!";
// TODO: Implement some actually useful functionality
callback(#[[NSNull null], #"test"]);
}
#end
And here is my complete PaypalSdk.h file:
#import "RCTBridgeModule.h"
#import "PayPalMobile.h"
#interface PaypalSdk : UIViewController <RCTBridgeModule, PayPalProfileSharingDelegate>
#property(nonatomic, strong, readwrite) NSString *environment;
#property(nonatomic, strong, readwrite) NSString *resultText;
#property(nonatomic, strong) UIWindow *window;
#property(nonatomic, strong) UIViewController *rootViewController;
#end
So you need to access the root view controller and then call presentViewController on the same. Something like below should do the trick:
- (IBAction)obtainConsent {
// Choose whichever scope-values apply in your case. See `PayPalOAuthScopes.h` for a complete list of available scope-values.
NSSet *scopeValues = [NSSet setWithArray:#[kPayPalOAuth2ScopeOpenId, kPayPalOAuth2ScopeEmail, kPayPalOAuth2ScopeAddress, kPayPalOAuth2ScopePhone]];
PayPalProfileSharingViewController *psViewController;
psViewController = [[PayPalProfileSharingViewController alloc] initWithScopeValues:scopeValues
configuration:self.payPalConfig
delegate:self];
// Access the root view controller
UIViewController *rootviewcontroller= [UIApplication sharedApplication].keyWindow.rootViewController;
// Present the PayPalProfileSharingViewController
[ rootviewcontroller presentViewController:psViewController animated:YES completion:nil];
}
I haven’t tried this so please check and let me know if this works. Otherwise find a view on which you can present the view controller.
Updating the answer based on the interaction with the OP:
For this to make it work, the view related methods had to be overriden with RCT methods and they need to be called before calling the generatecode RCT method. This along with root view controller seems to fix the issue.
Do not use that SDK. It is very old and deprecated.
If you need a native SDK to process payments with PayPal, you can use express checkout via Braintree. It also requires a web service of your own: https://developer.paypal.com/docs/accept-payments/express-checkout/ec-braintree-sdk/get-started/

Terminating app due to uncaught exception 'NSInvalidArgumentException' in my "game"

I'm following and online course and got stock because even by following the step (or maybe I missed some ? ) i get and error. I made several search on google and here but since i'm new to IOS development, even with answer from others, i can relate to my issue..
here is the error:
2015-06-12 23:03:17.477 Pirate Game[6511:1193126] -[RBTile setWeapon:]: unrecognized selector sent to instance 0x78830dc0
2015-06-12 23:03:17.481 Pirate Game[6511:1193126] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RBTile setWeapon:]: unrecognized selector sent to instance 0x78830dc0'
*** First throw call stack:
(
0 CoreFoundation 0x00874746 __exceptionPreprocess + 182
1 libobjc.A.dylib 0x004fda97 objc_exception_throw + 44
2 CoreFoundation 0x0087c705 -[NSObject(NSObject) doesNotRecognizeSelector:] + 277
3 CoreFoundation 0x007c3287 ___forwarding___ + 1047
4 CoreFoundation 0x007c2e4e _CF_forwarding_prep_0 + 14
5 Pirate Game 0x00014a94 -[RBFactory tiles] + 388
6 Pirate Game 0x00011c6f -[ViewController viewDidLoad] + 143
7 UIKit 0x00d97da4 -[UIViewController loadViewIfRequired] + 771
8 UIKit 0x00d98095 -[UIViewController view] + 35
9 UIKit 0x00c89e85 -[UIWindow addRootViewControllerViewIfPossible] + 66
10 UIKit 0x00c8a34c -[UIWindow _setHidden:forced:] + 287
11 UIKit 0x00c8a648 -[UIWindow _orderFrontWithoutMakingKey] + 49
12 UIKit 0x00c989b6 -[UIWindow makeKeyAndVisible] + 80
13 UIKit 0x00c2ded8 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 3217
14 UIKit 0x00c31422 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1639
15 UIKit 0x00c4a93e __84-[UIApplication _handleApplicationActivationWithScene:transitionContext:completion:]_block_invoke + 59
16 UIKit 0x00c3004a -[UIApplication workspaceDidEndTransaction:] + 155
17 FrontBoardServices 0x031d6c9e __37-[FBSWorkspace clientEndTransaction:]_block_invoke_2 + 71
18 FrontBoardServices 0x031d672f __40-[FBSWorkspace _performDelegateCallOut:]_block_invoke + 54
19 FrontBoardServices 0x031e8d7c __31-[FBSSerialQueue performAsync:]_block_invoke_2 + 30
20 CoreFoundation 0x00796050 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 16
21 CoreFoundation 0x0078b963 __CFRunLoopDoBlocks + 195
22 CoreFoundation 0x0078b7bb __CFRunLoopRun + 2715
23 CoreFoundation 0x0078aa5b CFRunLoopRunSpecific + 443
24 CoreFoundation 0x0078a88b CFRunLoopRunInMode + 123
25 UIKit 0x00c2fa02 -[UIApplication _run] + 571
26 UIKit 0x00c33106 UIApplicationMain + 1526
27 Pirate Game 0x000148da main + 138
28 libdyld.dylib 0x02c00ac9 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
So what i could understand from my search, is that i'm trying to send something that is not the same type. I have the same problem with all 3 custom class.
//
// RBFactory.m
// Pirate Game
//
// Created by Richard Berube on 2015-06-06.
// Copyright (c) 2015 Richard Berube. All rights reserved.
//
#import "RBFactory.h"
#import "RBTile.h"
#implementation RBFactory
-(NSArray *)tiles{
RBTile *tile1 = [[RBTile alloc] init];
tile1.story = #"Captain, we need a fearless leader such as you to undertake a voyage. You must stop the evil pirate Boss before he steals any more plunder. Would you like a blunted sword to get started?";
tile1.backgroundImage = [UIImage imageNamed:#"PirateStart.png"];
CCWeapon *bluntedSword = [[CCWeapon alloc]init];
bluntedSword.name = #"Blunted sword";
bluntedSword.damage = 12;
NSLog(#"%#", bluntedSword);
tile1.weapon = bluntedSword;
tile1.actionButtonName =#"Take the sword";
I tested with breakpoint and the crash occure
tile1.weapon = bluntedSword;
NSLog return:
2015-06-12 23:03:14.602 Pirate Game[6511:1193126] <CCWeapon: 0x786a6f50>
here is the tile class
//
// RBTile.h
// Pirate Game
//
// Created by Richard Berube on 2015-06-06.
// Copyright (c) 2015 Richard Berube. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "CCWeapon.h"
#import "CCArmor.h"
#interface RBTile : NSObject
#property (nonatomic, strong) NSString *story;
#property (strong, nonatomic) UIImage *backgroundImage;
#property (nonatomic, strong) NSString *actionButtonName;
#property (strong, nonatomic) CCWeapon *weapon;
#property (strong, nonatomic) CCArmor *armor;
#property (nonatomic) int healthEffect;
#end
The .m file is empty
//
// RBTile.m
// Pirate Game
//
// Created by Richard Berube on 2015-06-06.
// Copyright (c) 2015 Richard Berube. All rights reserved.
//
#import "RBTile.h"
#implementation RBTile
#end
And the last one is the custom class, one of them actualy, because all 3 are causing the same issue..
//
// CCWeapon.h
// Pirate Game
//
// Created by Richard Berube on 2015-06-10.
// Copyright (c) 2015 Richard Berube. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface CCWeapon : NSObject
#property (strong,nonatomic) NSString *name;
#property (nonatomic) int damage;
#end
A little help would be really apreciated! i'm now stock since 2 days.. i could start over all the video (online course) but that won't teach me how to debug ..
Maybe the last usefull info could be that i used Xcode 6.3.1
THanks !
update 1 : The final project is this one
https://github.com/codecoalition/Pirate-Adventure-Assignment/tree/master/Pirate%20Adventure
my RBtile is the CCtile in the github project
update 2: here is my viewcontroler
//
// ViewController.m
// Pirate Game
//
// Created by Richard Berube on 2015-06-06.
// Copyright (c) 2015 Richard Berube. All rights reserved.
//
#import "ViewController.h"
#import "RBFactory.h"
#import "RBTile.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
RBFactory *factory = [[RBFactory alloc]init];
self.tiles = [factory tiles];
self.character = [factory character];
self.currentPoint = CGPointMake(0, 0);
[self updateTile];
[self updateButtons];
[self updateCharacterStatsForArmor:nil withWeapons:nil withHealthEffect:0];
}
-(void)updateTile{
RBTile *tileModel = [[self.tiles objectAtIndex:self.currentPoint.x] objectAtIndex:self.currentPoint.y];
self.storyLabel.text = tileModel.story;
self.backgroundImageView.image = tileModel.backgroundImage;
self.healthLabel.text = [NSString stringWithFormat:#"%i", self.character.health];
self.damageLabel.text = [NSString stringWithFormat:#"%i", self.character.damage];
self.armorLabel.text = self.character.armor.name;
self.weaponLabel.text = self.character.weapon.name;
[self.actionButton setTitle:tileModel.actionButtonName forState:UIControlStateNormal];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)actionButtonPressed:(UIButton *)sender {
RBTile *tile = [[self.tiles objectAtIndex:self.currentPoint.x] objectAtIndex:self.currentPoint.y];
[self updateCharacterStatsForArmor:tile.armor withWeapons:tile.weapon withHealthEffect:tile.healthEffect];
[self updateTile];
}
- (IBAction)northButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x, self.currentPoint.y +1);
[self updateButtons];
[self updateTile];
}
- (IBAction)southButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x, self.currentPoint.y -1);
[self updateButtons];
[self updateTile];
}
- (IBAction)eastButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x + 1, self.currentPoint.y);
[self updateButtons];
[self updateTile];
}
- (IBAction)westButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x - 1, self.currentPoint.y);
[self updateButtons];
[self updateTile];
}
- (IBAction)restartButtonPressed:(UIButton *)sender {
}
-(void)updateButtons{
self.westButton.hidden = [self tilesExistAtPoint:CGPointMake(self.currentPoint.x -1, self.currentPoint.y)];
self.eastButton.hidden = [self tilesExistAtPoint:CGPointMake(self.currentPoint.x+1, self.currentPoint.y)];
self.northButton.hidden = [self tilesExistAtPoint:CGPointMake(self.currentPoint.x, self.currentPoint.y+1)];
self.southButton.hidden = [self tilesExistAtPoint:CGPointMake(self.currentPoint.x, self.currentPoint.y-1)];
}
-(BOOL)tilesExistAtPoint:(CGPoint)point{
if (point.y >= 0 && point.x >=0 && point.x < [self.tiles count] && point.y < [[self.tiles objectAtIndex:point.x] count]) {
return NO;
}
else{
return YES;
}
}
-(void)updateCharacterStatsForArmor:(CCArmor *)armor withWeapons:(CCWeapon *)weapon withHealthEffect:(int)healtEffect{
if (armor != nil ) {
self.character.health = self.character.health - self.character.armor.health +armor.health;
self.character.armor = armor;
}
else if (weapon != nil){
self.character.damage = self.character.damage - self.character.weapon.damage + weapon.damage;
self.character.weapon = weapon;
}
else if (healtEffect != 0){
self.character.health = self.character.health + healtEffect;
}
else {
self.character.health = self.character.health + self.character.armor.health;
self.character.damage = self.character.damage + self.character.weapon.damage;
}
}
#end
Another update:
I was able to find with the breakpoint that some of my property are not showing up..
When in fact it should be like that:
So now i know that when i try to pass tile1.weapon = bluntedSword; it is not working because tile1.weapon do not exist. But if you look in my RBTile.h all my property are there..
I am following the same course, and my code is the same as yours, and it works. The Tile.m files is empty.
In recent Xcode versions, synthesize statements are not always necessary. Some rare cases, I think, still need them.
I think the reason you don't see the other properties in the debug window is because the code that creates them hasn't run yet.
Try commenting out the lines that related to bluntedSword and see if the other properties are set successfully, or if they also cause the app to crash.
Another thing to try, and I understand how silly it sounds, is to re-type the lines (not copy and paste) and related lines that are causing the problems. Sometimes this works, so it's worth trying.
Good luck!
The root of the issue is this
[RBTile setWeapon:]: unrecognized selector sent to instance 0x78830dc0
That is telling you that your RBTile.weapon property on the RBTile class does not have a setter. Please update your post with the .m file showing how you are creating the weapon property on the RBTile. Odds are you are declaring a getter and potentially forgetting the setter.
Your .m file is empty, which is causing the issue. The header file just defines a contract between an object using your class, and the implementation of the class in the .m file. You can think of it as a bridge of sorts between something using the class, and the actual implementation of the class.
At runtime, the game can't find an implementation of the property to work with, thus the reason why unrecognized selector is being thrown.

removeFromSuperview causes crash (non-ARC)

I'm having a weird issue with UIViews and manual memory management.
I have a view (contentView) which is the main view of a view controller.
After a long press on the contentView, another view is supposed to fade in (on top of it).
When the gestures ends, the additional view fades out.
The issue is:
When the contentView receives a long press, I create the auxiliary view, add it to the contentView, and then release it, which is/was the common practice back in the pre-ARC days.
It works okay on the iPhone, but it crashes on the iPad!
The crashy line is:
[ZPNowPlayingItemInfoView dealloc]
...which gets triggered when I remove the auxiliary view from the contentView.
Any clues on why this happens?
If I comment out the release line (see my comment in the code), it works flawlessly on both devices, but it feels bad.
Here's the code:
-(void)longPressDetected:(UILongPressGestureRecognizer*)longPressGR
{
//Content view of the view controller I'm in
UIView *contentView = MSHookIvar<UIView*>(self, "_contentView");
if (longPressGR.state == UIGestureRecognizerStateBegan) {
id item = MSHookIvar<MPAVItem*>(self, "_item");
ZPNowPlayingItemInfoView *infoView =
[[ZPNowPlayingItemInfoView alloc] initWithFrame:
CGRectMake(0,0,contentView.frame.size.width,contentView.frame.size.height)
item:item];
//infoView retain count: 1
[infoView setAlpha:0.f];
[contentView addSubview:infoView];
//infoView retain count: 3 (???)
//iPad goes berserk on this line
//Commented - Works both on iPhone and iPad
//Uncommented - Works only on iPhone
//[infoView release];
//infoView retain count: 2 (if release is uncommented)
[UIView animateWithDuration:0.35f animations:^{
[infoView setAlpha:1.0f];
} completion:^(BOOL finished) {
//infoView retain count: 3
}];
} else if (longPressGR.state == UIGestureRecognizerStateEnded) {
ZPNowPlayingItemInfoView* infoView = nil;
for (UIView *subview in contentView.subviews) {
if ([subview isKindOfClass:[ZPNowPlayingItemInfoView class]]) {
infoView = (ZPNowPlayingItemInfoView*)subview;
break;
}
}
[UIView animateWithDuration:0.35f animations:^{
[infoView setAlpha:0.f];
} completion: ^(BOOL finished){
[infoView removeFromSuperview];
}];
}
P.S. I need to use manual memory management. This is a tweak for jailbroken devices.
Stack trace:
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libobjc.A.dylib 0x195287bdc 0x19526c000 + 0x1bbdc // objc_msgSend + 0x1c
1 + Musix.dylib 0x10015b19c 0x100154000 + 0x719c // -[ZPNowPlayingItemInfoView dealloc] + 0x48
2 libsystem_blocks.dylib 0x19590d90c 0x19590c000 + 0x190c // _Block_release + 0xfc
3 UIKit 0x188ef8590 0x188eb0000 + 0x48590 // -[UIViewAnimationBlockDelegate dealloc] + 0x44
4 CoreFoundation 0x1845f1374 0x1845ec000 + 0x5374 // CFRelease + 0x208
5 CoreFoundation 0x184601004 0x1845ec000 + 0x15004 // -[__NSDictionaryI dealloc] + 0x8c
6 libobjc.A.dylib 0x19528d720 0x19526c000 + 0x21720 // (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 0x230
7 CoreFoundation 0x1845f4f90 0x1845ec000 + 0x8f90 // _CFAutoreleasePoolPop + 0x18
8 CoreFoundation 0x1846c774c 0x1845ec000 + 0xdb74c // __CFRunLoopRun + 0x5d8
9 CoreFoundation 0x1845f51f0 0x1845ec000 + 0x91f0 // CFRunLoopRunSpecific + 0x188
10 GraphicsServices 0x18d7575a0 0x18d74c000 + 0xb5a0 // GSEventRunModal + 0xa4
11 UIKit 0x188f26780 0x188eb0000 + 0x76780 // UIApplicationMain + 0x5cc
12 Music (*) 0x10006ee28 0x100064000 + 0xae28 // 0x0000adac + 0x7c
13 libdyld.dylib 0x1958e2a04 0x1958e0000 + 0x2a04 // start + 0x0
ZPNowPlayingItemInfoView:
#interface ZPNowPlayingItemInfoView()
#property (nonatomic, retain) MPAVItem* item;
#property (nonatomic, retain) MPUSlantedTextPlaceholderArtworkView *artworkView;
#property (nonatomic, retain) UILabel *artistLabel;
#property (nonatomic, retain) UILabel *albumLabel;
#property (nonatomic, retain) UILabel *songLabel;
#end
ZPNowPlayingItemInfoView dealloc:
-(void)dealloc
{
[super dealloc];
[self.item release];
[self.artworkView release];
[self.artistLabel release];
[self.songLabel release];
}
You have some problem in ZPNowPlayingItemInfoView class. When this problem happens? Only when the object gets deallocated. When you comment [infoView release] out, your object is never deallocated and the problem doesn't arise - you will have a memory leak though.
Inspect what ZPNowPlayingItemInfoView does, especially its dealloc method. Are you sure you are constructing it correctly? Is item always a valid object?
After seeing the ZPNowPlayingItemInfoView dealloc method, the problem is quite clear - [super dealloc] must always be the last call, not the first one. Once you have deallocated the object, accessing its properties is an undefined operation.
When commenting out the release is a working workaround, that indicates that you have released it once too often. It may well be the very one release that you commented out.
removeFromSuperview does reduce the retain count by 1.
I suggest re-visiting the full life cycle of the view object. This can be tricky though. Each retain needs to have exactly one corresponding release or autorelease. Assigning the view to a property using its getter (self.myView = subview) does retain it and re-assigning another view to the property (self.myView = someOhterview) releases subview.
On the contrary accessing the iVar directly (myView = subview) does not maintain the release/retain-cycle.
There is more than that. Adding the view and removing it from an array, set or dictionary will change the retain count accordingly.
So go and have a deeper look at it. Use instruments to observe the retain count.

ios App crashes when deallocating an UIView subclass instance

My app built natively with CocoaTouch and ARC crashes when deallocating a UIView subclass instance.
Here is the crash log.
OS Version: iOS 6.1.3 (10B329)
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00000008
Crashed Thread: 0
0: 0x39de65b0 libobjc.A.dylib objc_msgSend + 16
1: 0x31edb694 CoreFoundation -[NSArray makeObjectsPerformSelector:] + 300
2: 0x33d8c57a UIKit -[UIView(UIViewGestures) removeAllGestureRecognizers] + 146
3: 0x33d8c144 UIKit -[UIView dealloc] + 440
4: 0x00240b36 MyApp -[StandardPanelView .cxx_destruct](self=0x20acba30, _cmd=0x00240985) + 434 at StandardPanelView.m:139
5: 0x39deaf3c libobjc.A.dylib object_cxxDestructFromClass(objc_object*, objc_class*) + 56
6: 0x39de80d2 libobjc.A.dylib objc_destructInstance + 34
7: 0x39de83a6 libobjc.A.dylib object_dispose + 14
8: 0x33d8c26a UIKit -[UIView dealloc] + 734
9: 0x0022aa14 MyApp -[StandardPanelView dealloc](self=0x20acba30, _cmd=0x379f1a66) + 156 at StandardPanelView.m:205
10: 0x39de8488 libobjc.A.dylib (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 168
11: 0x31e98440 CoreFoundation _CFAutoreleasePoolPop + 16
12: 0x31f28f40 CoreFoundation __CFRunLoopRun + 1296
13: 0x31e9bebc CoreFoundation CFRunLoopRunSpecific + 356
14: 0x31e9bd48 CoreFoundation CFRunLoopRunInMode + 104
15: 0x35a502ea GraphicsServices GSEventRunModal + 74
16: 0x33db1300 UIKit UIApplicationMain + 1120
17: 0x00113c50 MyApp main(argc=1, argv=0x2fd3ed30) + 140 at main.m:23
The questions are:
what could be set wrong that makes the internal call to [UIView(UIViewGestures) removeAllGestureRecognizers] crash. One theory is that some gesture in the gestures array is deallocated already somewhere else.
When a UIView contains subviews, how is the sequence of deallocation process?
Some extra background info:
The crash happens, but there is no exact way to reproduce it.
The StandardPanelView instance works as delegate for gestures belongs to its subviews.
We are using flyweight on StandardPanelView instances, i.e., caching and recycling.
Thanks in advance for any hint about how this crash could happen.
My first impression is that you might be trying to access the StandardPanelView, which has just gotten deallocated.
What could be set wrong that makes the internal call to [UIView(UIViewGestures) removeAllGestureRecognizers] crash. One theory is that some gesture in the gestures array is deallocated already somewhere else.
It will not be because a UIGestureRecognizer got deallocated. The UIView strongly holds the UIGestureRecognizers in an NSArray. They will not be deallocated while they are still in the array.
However, the UIGestureRecognizer's delegate may have gotten deallocated. That is only an (assign) property, meaning that it is -not- strongly held, and if the delegate is deallocated, it will be a dangling pointer. So, if in [NSArray makeObjectsPerformSelector:] the delegate is used, this might happen.
When a UIView contains subviews, how is the sequence of deallocation process?
Objects are deallocated from 'parent' to 'child', ie. the superview is deallocated, then the subview, then the gesture recognizers. (Although whether the subviews are dealloced before the gesture recognizers is an implementation detail, so you probably shouldn't depend on it).
We can see this in a simple sample controller:
// The UIView that will be used as the main view
// This is the superview
#interface MyView : UIView
#end
#implementation MyView
- (void)dealloc {
NSLog(#"Dealloc MyView");
}
#end
// This view will be put inside MyView to be used as a subview
#interface MySubview : UIView
#end
#implementation MySubview
- (void)dealloc {
NSLog(#"Dealloc MySubview");
}
#end
// This is the gesture recognizer that we will use
// We will give one to each view, and see when it is deallocated
#interface MyGestureRecognizer : UIGestureRecognizer
#property (nonatomic, copy) NSString *tag;
#end
#implementation MyGestureRecognizer
#synthesize tag;
-(void)dealloc {
NSLog(#"Dealloc MyGestureRecognizer tag: %#", tag);
}
#end
// Just a test view controller that we will push on/pop off the screen to take a look at the deallocations
#interface TestViewController : UIViewController
#end
#implementation TestViewController
- (void)loadView {
self.view = [[MyView alloc] init];
MyGestureRecognizer *recognizer = [[MyGestureRecognizer alloc] initWithTarget:self action:#selector(doStuff)];
recognizer.tag = #"MyViewGestureRecognizer";
recognizer.delegate = self;
[self.view addGestureRecognizer:recognizer];
}
- (void)viewDidLoad {
[super viewDidLoad];
MySubview *subview = [[MySubview alloc] init];
MyGestureRecognizer *recognizer = [[MyGestureRecognizer alloc] initWithTarget:self action:#selector(doStuff)];
recognizer.tag = #"MySubviewGestureRecognizer";
recognizer.delegate = self;
[subview addGestureRecognizer:recognizer];
[self.view addSubview:subview];
}
- (void)doStuff {
// we don't actually care what it does
}
#end
All we are doing is adding MyView to be the main view for TestViewController, and then adding a MySubview inside MyView. We also attach a MyGestureRecognizer to each of the views.
When we push it off the screen, our log output presents:
Dealloc TestViewController
Dealloc MyView
Dealloc MySubview
Dealloc MyGestureRecognizer tag: MySubviewGestureRecognizer
Dealloc MyGestureRecognizer tag: MyViewGestureRecognizer
Sorry for the long answer... It has been about 3 months since you posted it, so maybe you already solved the problem, but in case anyone else stumbles across this answer, I hope it helps out.

Cocos2d crash at [CCDirector setNextScene]

I am having difficulty resolving a crash when changing scenes. This is the dealloc method of the scene being replaced. It is being called and I know that there is no memory leak or heap bloat. Can anyone suggest what possible stupid/obvious mistake(s) I might be making. I apologize for the idiocy.
-(void) dealloc
{
NSLog(#"Dungeon.m dealloc called");
[self removeAllChildrenWithCleanup:true];
[self unscheduleAllSelectors];
[[CCScheduler sharedScheduler]unscheduleAllSelectors];
if (entityList) [entityList release];
if (lopsidedList) [lopsidedList release];
if (monsterNames) [monsterNames release];
if (menuSprites) [menuSprites release];
if (menuLabels) [menuLabels release];
if (monsterLevels) [monsterLevels release];
if (theMagicFactory) [theMagicFactory release];
if (theDM) [theDM release];
if (theDisplay) [theDisplay release];
if (aDungeonLevel) [aDungeonLevel release];
NSLog([NSString stringWithFormat:#"Player Retain Count: %i",[thePlayer retainCount]]);
[super dealloc]
}
I have confirmed that deallocs for the significant listed objects are called:
2012-09-09 10:51:31.840 Pocket Dungeons[947:707] Dungeon.m dealloc called
2012-09-09 10:51:31.878 Pocket Dungeons[947:707] DungeonMaster.m dealloc called.
2012-09-09 10:51:31.910 Pocket Dungeons[947:707] DungeonDisplay.m dealloc called
2012-09-09 10:51:31.915 Pocket Dungeons[947:707] dungeonlevel.m dealloccalled
This is the changing scene code:
else if (whatIsHere==STAIRS_UP) {
if (thePlayer.currentDungeonLevel==1) {
NSLog([NSString stringWithFormat:#"Self Retain Count: %i",[self retainCount]]);
thePlayer.theDungeon = nil;
theDM.theDungeon = nil;
theDisplay.theDungeon = nil;
for (int i=0; i<[aDungeonLevel.mArray count]; i++) {
Monster *aMonster = [aDungeonLevel.mArray objectAtIndex:i];
aMonster.theDungeon = nil;
}
NSLog([NSString stringWithFormat:#"Self Retain Count: %i",[self retainCount]]);
// CCScene *aScene = [Town nodeWithPlayer:thePlayer];
CCScene *aScene = [CharacterMaker2 nodeWithPlayer:thePlayer];
[[CCDirector sharedDirector] replaceScene:aScene];
This is where the crash occurs:
0 0x34311f78 in objc_msgSend ()
1 0x000fef28 in -[CCDirector setNextScene] at /Users/nehrujuman212/Documents/Pocket Dungeons/Pocket Dungeons/libs/cocos2d/CCDirector.m:429
2 0x0014ab44 in -[CCDirectorIOS drawScene] at /Users/nehrujuman212/Documents/Pocket Dungeons/Pocket Dungeons/libs/cocos2d/Platforms/iOS/CCDirectorIOS.m:160
3 0x0014c858 in -[CCDirectorDisplayLink mainLoop:] at /Users/nehrujuman212/Documents/Pocket Dungeons/Pocket Dungeons/libs/cocos2d/Platforms/iOS/CCDirectorIOS.m:721
4 0x33fd386e in CA::Display::DisplayLink::dispatch(unsigned long long, unsigned long long) ()
5 0x33fd37c4 in CA::Display::IOMFBDisplayLink::callback(__IOMobileFramebuffer*, unsigned long long, unsigned long long, unsigned long long, void*) ()
6 0x37387000 in IOMobileFramebufferVsyncNotifyFunc ()
7 0x31bf060c in IODispatchCalloutFromCFMessage ()
8 0x3228af12 in __CFMachPortPerform ()
9 0x32295522 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ ()
10 0x322954c4 in __CFRunLoopDoSource1 ()
11 0x32294312 in __CFRunLoopRun ()
12 0x322174a4 in CFRunLoopRunSpecific ()
13 0x3221736c in CFRunLoopRunInMode ()
14 0x3136e438 in GSEventRunModal ()
15 0x31d16cd4 in UIApplicationMain ()
16 0x0016ea1e in main at /Users/nehrujuman212/Documents/Pocket Dungeons/Pocket Dungeons/main.m:14
Thanks.
Hopefully, I can adequately answer my own question, though the answer could use some further input.
I think the problem was that cocos2d was releasing the "running scene" from CCDirector:setNextScene, after I had allowed it to be deallocated. By carefully checking my releases and retains, I was able to allow it to be retained a bit longer to allow cocos2d time to do its housekeeping.
I was overzealously trying to not retain my object anywhere, ergo I used CCScheduler UnscheduleAllSelectors, which is a no-no for run-of-the-mill guys like me. The take home for me is: be careful not to screw up your code when you're trying to correct it; and be patient: logic is logical. Also, I learned to access the cocos2d source code to help me and also gained better skill at using the instruments, especially examination of the heap.

Resources