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.
Related
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/
Now i have one issue during ios app development.
I made one viewpagercontrollerview include several viewcontroller.
Each Viewcontroller include tableview.
I used UISearchController in this tableview.
But, whenever i click searchcontroller and tab scrolled, then error is occured.
2016-08-08 22:07:11.211 ToolManager[3913:121312] *** Assertion failure in -[_UIQueuingScrollView _setWrappedViewAtIndex:withView:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/_UIQueuingScrollView.m:332
2016-08-08 22:07:11.222 ToolManager[3913:121312] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unexpected subviews'
*** First throw call stack:
(
0 CoreFoundation 0x0000000104c3dd85 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001046b1deb objc_exception_throw + 48
2 CoreFoundation 0x0000000104c3dbea +[NSException raise:format:arguments:] + 106
3 Foundation 0x00000001042fbd5a -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 198
But, Without click searchcontrol and scroll,then work is done well.
I saw several resolution...
one method is dismiss searchcontroller before scrool tab.
But how can i capture tab scroll!
now I add searchcontroll in each viewcontroller.
#interface CurrentToolListViewController : BaseViewController<UITableViewDelegate, UITableViewDataSource,
UISearchResultsUpdating, UISearchControllerDelegate>
#property (nonatomic, strong) UISearchController *searchController;
#property (nonatomic, copy) NSString *filterString;
#end
and .m file
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.delegate = self;
[self.searchController.searchBar sizeToFit];
self.tblCurToolList.tableHeaderView = self.searchController.searchBar;
self.definesPresentationContext = YES;
pls help me.
You have to make your searchController inactive in your viewWillDisappear(_:) method
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.searchController.active = NO;
}
Edit
The above code should fix your app from crashing if you swipe to the next ViewController, but if you are using a UIButton to call setViewController, then you have to check if the searchController is active (using an if statement) in the UIButton's #IBAction. Inside the if statement, type self.searchController.active = NO;, and then return.
This just makes the searchController inactive, and then you have to press the button again to make it call the appropriate method (setViewController)
I have a situation where I am trying to resolve these Crashlytics issues and I have this crash log
Thread : Crashed: com.apple.main-thread
0 libobjc.A.dylib 0x34217f46 objc_msgSend + 5
1 UIKit 0x29a2d5a3 -[UIWebView webView:decidePolicyForNavigationAction:request:frame:decisionListener:] + 182
2 CoreFoundation 0x2630cad4 __invoking___ + 68
3 CoreFoundation 0x26239645 -[NSInvocation invoke] + 300
4 CoreFoundation 0x2623d0c7 -[NSInvocation invokeWithTarget:] + 50
5 WebKitLegacy 0x326d9261 -[_WebSafeForwarder forwardInvocation:] + 224
6 CoreFoundation 0x2630b62f ___forwarding___ + 354
7 CoreFoundation 0x2623d008 _CF_forwarding_prep_0 + 24
8 CoreFoundation 0x2630cad4 __invoking___ + 68
9 CoreFoundation 0x26239645 -[NSInvocation invoke] + 300
10 WebCore 0x31c02729 HandleDelegateSource(void*) + 100
11 CoreFoundation 0x262cefbf __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14
12 CoreFoundation 0x262ce461 __CFRunLoopDoSources0 + 364
13 CoreFoundation 0x262cca35 __CFRunLoopRun + 772
14 CoreFoundation 0x2621a3b1 CFRunLoopRunSpecific + 476
15 CoreFoundation 0x2621a1c3 CFRunLoopRunInMode + 106
16 GraphicsServices 0x2d801201 GSEventRunModal + 136
17 UIKit 0x2988443d UIApplicationMain + 1440
18 abc 0x0030dcd7 main (main.m:14)
I can understand that its some callback on webview delegate and bad excess has occurred, so to rectify this I handled the delegates via
[self.webview stopLoading];
self.webview.delegate =nil;
in all the classes, yet I can see this crash. Can you enlighten me what's possibly going wrong and some approach to rectify this?
The following might be the case here
The user is presented a screen with UIWebView
The UIViewController sets self as the delegate Web page starts downloading
The User quits screen
UIViewController gets deallocated UIWebView finishes loading and sends I am finished loading message to its delegate
or
some other delegate method gets called when the webview object is no more.i.e dangling pointer effect
1.Always make sure you stop loading the webView and remove the delegate before leaving the view
Before releasing an instance of UIWebView for which you have set a
delegate, you must first set its delegate property to nil. This can
be done, in your dealloc method
Here is the reference
// If ARC is used
- (void)dealloc {
[_webView setDelegate:nil];
[_webView stopLoading];
}
// If ARC is not used
- (void)dealloc {
[webView setDelegate:nil];
[webView stopLoading];
[webView release];
[super dealloc];
}
// ARC - Before iOS6 as its deprecated from it.
- (void)viewWillUnload {
[webView setDelegate:nil];
[webView stopLoading];
}
2.Make sure you are not stopLoading and setDelegate to nil in viewWillDisappear
if the ViewController is a child of a another ViewController, u can
trigger the removal of the ViewController's view from the parent
ViewController's view with an animation. At the same time, u can
remove the ViewController from its parent and nil out its reference.
at this point ViewController will be nil and viewWillDisappear
will never be called, meaning the WebView delegate will never be
cleaned up
Use dealloc and ensure that your WebView is always cleaned up.
3.Make sure you set the ContentOffset of the subviews of webview to CGPointZero without animation
In iPad in some versions while webview is scrolling if you close the parent
viewcontroller without setting ContentOffset to CGPointZero this
kind of problems will come
so its better to you call the following code of in parent viewcontroller before closing it
for (id subview in webView.subviews){
if ([[subview class] isSubclassOfClass: [UIScrollView class]]){
[subview setContentOffset:CGPointZero animated:NO];
}
}
Hope this helps.Feel free to ask your doubts.
4.Generally speaking You should not embed UIWebView objects in UIScrollView objects. If you do so, unexpected behavior can result because touch events for the two objects can be mixed up and wrongly handled.
Here is the reference
Try disabling your UIWebView's scrolling behaviour before the ViewController deallocs it
for (id subview in webView.subviews){
if ([[subview class] isSubclassOfClass: [UIScrollView class]]){
[subview setContentOffset:CGPointZero animated:NO];
}
}
p.s. Dipen Chudasama's approach is correct, according to Apple's documentation, you should really set the delegate property to nil before releasing the webview, assuming you have released the webview correctly inside dealloc function but not viewWillDisappear
UIWebViews delegate uses assign and not weak. So you need to nullify the delegate when the webView's controller gets deallocated.
Example:
- (void)dealloc
{
self.webview.delegate =nil;
}
Are you adding a web view within your subclass of a web view? Generally that's the problem and if so, changing the superclass to a UIView would solve the problem.
Use visa versa means first nil your delegate and then after stop loading web view may be help you.
like this.
[_webView setDelegate:nil];
[_webView stopLoading];
As per apple document : Important Before releasing an instance of UIWebView for which you have set a delegate, you must first set its delegate property to nil.
Use [listener use] instead, to tell your UIWebView to handle clicked URL.
-(void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id )listener
{
[listener use]
}
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.
Let me explain my problem. I have 3 UIView: a LoginView, LibraryView, and a StoreView. I have this code to switch from one UIView to another:
- (void)showView:(NSInteger)viewTag
{
if (viewTag == 1)
{
if (self.loginView)
{
self.loginView = nil;
self.loginView.delegate = nil;
}
LoginView *loginPage = [[LoginView alloc]initWithFrame:self.view.bounds];
[loginPage setDelegate:self];
self.loginView = loginPage;
[loginPage release];
[self.view addSubview:self.loginView];
}
else if(viewTag == 2)
{
if (self.libraryView)
{
self.libraryView = nil;
self.libraryView.delegate = nil;
}
LibraryView *libraryPage = [[LibraryView alloc]initWithFrame:self.view.bounds];
[libraryPage setDelegate:self];
self.libraryView = libraryPage;
[libraryPage release];
[self.view addSubview:self.libraryView];
}
else
{
if (self.bookStoreView)
{
self.bookStoreView = nil;
self.bookStoreView.delegate = nil;
}
BookStoreView *bookStore = [[BookStoreView alloc]initWithFrame:self.view.bounds];
[bookStore setDelegate:self];
self.bookStoreView = bookStore;
[bookStore release];
[self.view addSubview:self.bookStoreView];
}
}
Basically, that is how I initialize the UIViews. And here are the buttons used to switch between them:
- (void)loginViewToLibraryView
{
[self.loginView removeFromSuperview];
[self showView:2];
}
- (void)libraryViewToStoreView
{
[self.libraryView removeFromSuperview];
[self showView:3];
}
//so on...
The problem arises when the I call the function libraryViewToLoginView and storeViewToLoginView. The app crashes whenever I call these functions, which is weird because the two functions worked properly before. I checked Profile and it gave me this stack trace:
# Address Category Event RefCt Timestamp Size Responsible Library Responsible Caller
0 0xc4dcac0 CALayer Malloc 1 00:02.233.004 48 UIKit -[UIView _createLayerWithFrame:]
1 0xc4dcac0 CALayer Retain 3 00:02.238.317 0 QuartzCore CA::Layer::insert_sublayer(CA::Transaction*, CALayer*, unsigned long)
2 0xc4dcac0 CALayer Release 2 00:02.238.324 0 UIKit -[UIView(Internal) _addSubview:positioned:relativeTo:]
3 0xc4dcac0 CALayer Retain 3 00:02.238.518 0 QuartzCore -[CALayerArray copyWithZone:]
4 0xc4dcac0 CALayer Release 2 00:02.238.602 0 UIKit -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]
5 0xc4dcac0 CALayer Retain 3 00:02.238.665 0 QuartzCore -[CALayerArray copyWithZone:]
6 0xc4dcac0 CALayer Release 2 00:02.238.796 0 UIKit -[UIView(Internal) _didMoveFromWindow:toWindow:]
7 0xc4dcac0 CALayer Retain 3 00:05.107.397 0 QuartzCore -[CALayerArray copyWithZone:]
8 0xc4dcac0 CALayer Release 2 00:05.107.539 0 UIKit -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]
9 0xc4dcac0 CALayer Retain 3 00:05.107.613 0 QuartzCore -[CALayerArray copyWithZone:]
10 0xc4dcac0 CALayer Release 2 00:05.107.700 0 UIKit -[UIView(Internal) _didMoveFromWindow:toWindow:]
11 0xc4dcac0 CALayer Retain 2 00:06.105.958 0 QuartzCore -[CALayerArray copyWithZone:]
12 0xc4dcac0 CALayer Release 2 00:06.108.134 0 UIKit -[UIView dealloc]
13 0xc4dcac0 CALayer Release 1 00:06.108.492 0 UIKit -[UIView dealloc]
14 0xc4dcac0 CALayer Zombie -1 00:06.115.332 0 QuartzCore CA::release_objects(X::List<void const*>*)
As you can see, it is a bunch of calls on the CALayer which I don't really understand. I want to understand why is this happening. Can anyone explain?
Frankly I have not understood everything that you want to achieve here. But you should consider the sequence of events. See my comments:
if (self.loginView)
{
self.loginView = nil;
//self.loginView is nil now. What so you think doese happen on the next line?
self.loginView.delegate = nil;
// change the sequence of this lines and it will be ok.
}
LoginView *loginPage = [[LoginView alloc]initWithFrame:self.view.bounds];
[loginPage setDelegate:self];
self.loginView = loginPage;
[loginPage release]; //here you release the object. it is gone now. However, there are still references to it.
[self.view addSubview:self.loginView]; // here you add the released object. What do you expect to happen?
// Switch those two statmetns and you should be fine.
Well, it may even work as those statmens are next to each other. And when you remove it from its super view it gets released again. And there your app might crash.
BTW, without having it retained you should not release it at all. Or am I wrong here? However, addSubview should retain it and removeFromSuperview will release it, so no additional release would be required. Onece removed it should be gone. (If not retained somewhere else)