0

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];
}
Matteo Pacini
  • 21,796
  • 7
  • 67
  • 74
  • 1
    Hmmm....it feels like a long time since ARC arrived. In my opinion, the `[infoView release];` should be uncommented. Just looking at the code, I worry about the `[infoView setAlpha:0.f];` followed by the `[contentView addSubview:infoView];`. I've always had oddities with `alpha`'s of `0` - what happens if you use an initial `alpha` of `0.1` for example. I wonder if adding a `subview` with an `alpha` of `0` actually increases the retain count? – Robotic Cat Feb 08 '15 at 21:06
  • @RoboticCat I've tried changing the alpha. It still crashes :(. I've added the stack trace to the question. – Matteo Pacini Feb 08 '15 at 21:23
  • I think you're going to have to switch to IB and use the old methods for tracking down memory issues using `NSZombies`: https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/MemoryManagementforYourApp/MemoryManagementforYourApp.html#//apple_ref/doc/uid/TP40004652-CH11-SW8 – Robotic Cat Feb 08 '15 at 21:29
  • @RoboticCat I can't, as I'm using `Theos` and Textmate to develop this jailbroken tweak :( (I can't use Xcode). A weird thing happens: when I add the infoView to the contentView, the **retainCount** jumps from 1 to 3 (?). – Matteo Pacini Feb 08 '15 at 21:32
  • 1
    Ignore `retainCount` - it's no use: http://stackoverflow.com/q/1206111/558933. Basically the rule is each `[retain]` must be followed by `[release]`. Actually, now that I think about it, you never `retain` the `infoView` (after you `alloc/init` the view) so the release is unnecessary. Oops. My bad in thinking it was needed. I thinking you're OK without the `release`. – Robotic Cat Feb 08 '15 at 21:40
  • I was wondering: what if `removeFromSuperview` performs a double release? That would explain why I don't need the release line. @RoboticCat the view is retained during alloc/init, which sets the retainCount (although is not reliable) to 1 – Matteo Pacini Feb 08 '15 at 21:50
  • The only thing that comes to mind to better debug the whole thing is to override `retain` and `release` and add logs to that (other than the super call) – DeFrenZ Feb 08 '15 at 22:01
  • 1
    `UIViewAnimationBlockDelegate dealloc` - A CAAnimation delegate is _retained_ by the animation. So this is the source of the problem. – matt Feb 08 '15 at 22:30
  • @matt Any tip on how to fix it? – Matteo Pacini Feb 08 '15 at 23:00
  • **Update**: If I retain the infoView when I retrieve it after the gesture ends (see code: `infoView = (ZPNowPlayingItemInfoView*)subview;` => `[infoView = (ZPNowPlayingItemInfoView*)subview retaint`), the app doesn't crash. Why? :S – Matteo Pacini Feb 09 '15 at 00:01

2 Answers2

2

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.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
0

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.

Hermann Klecker
  • 14,039
  • 5
  • 48
  • 71