1

There is a small toast utility source code (https://github.com/Joyfl/JLToast) which seems to use ARC. But I want to use it in manual retain-release (MRR) mode.

In particular, I'm not sure if the center = [[JLToastCenter alloc] init]; (for ARC mode) in +(id)defaultCenter of JLToastCenter.m should be re-writing to center = [[[JLToastCenter alloc] init] autorelease]; (for MRR mode), where the center is declared as static id center = nil;.

In this post, the answer given by @mipadi says that "If the variable is initialized only once, and should stay around for the lifetime of the application, then no, it shouldn't be released (its memory will essentially be freed when the application exits, anyway)". I guess this is the case when the static variable center in JLToastCenter.m but not sure about it.

My own version for MRR mode listed below added release/autorelease/dealloc things. I also changed all dot notation into messaging style.

Source code

Source code list:

JLToastCenter.h

JLToastCenter.m

JLToast.h

JLToast.m

JLToastView.h

JLToastView.m

The JLToastCenter.h file:

#import <Foundation/Foundation.h>

@class JLToast;

@interface JLToastCenter : NSObject
{
    NSOperationQueue *_queue;
}

+ (id)defaultCenter;

- (void)addToast:(JLToast *)toast;

@end

The JLToastCenter.m file:

#import "JLToastCenter.h"
#import "JLToast.h"

@implementation JLToastCenter

+ (id)defaultCenter
{
    static id center = nil;
    static dispatch_once_t onceToken; // It makes singleton object thread-safe
    dispatch_once(&onceToken, ^{
        center = [[[JLToastCenter alloc] init] autorelease]; // Added autorelease by me, originally as center = [[JLToastCenter alloc] init];
        [[NSNotificationCenter defaultCenter] addObserver:center selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
    });
    return center;
}

// Added by me
- (void)dealloc
{
    [_queue release];
    
    [super dealloc];
}

- (id)init
{
    self = [super init];
    if( self )
    {
        _queue = [[NSOperationQueue alloc] init];
        [_queue setMaxConcurrentOperationCount:1];
    }
    return self;
}

- (void)addToast:(JLToast *)toast
{
    [_queue addOperation:toast];
}

- (void)deviceOrientationDidChange:(id)sender
{
    if( [[_queue operations] count] )
    {
        [[[[_queue operations] objectAtIndex:0] view] layoutSubviews];
    }
}

@end

The JLToast.h file:

#import <UIKit/UIKit.h>

#define JLToastShortDelay   2.0f
#define JLToastLongDelay    3.5f

@class JLToastView;

@interface JLToast : NSOperation
{
    BOOL _isExecuting;
    BOOL _isFinished;
}

@property (nonatomic, strong) JLToastView *view;
@property (nonatomic, copy) NSString *text; // added by me
@property (nonatomic) NSTimeInterval delay;
@property (nonatomic) NSTimeInterval duration;

+ (id)makeText:(NSString *)text;
+ (id)makeText:(NSString *)text duration:(NSTimeInterval)duration;
+ (id)makeText:(NSString *)text delay:(NSTimeInterval)delay duration:(NSTimeInterval)duration;

- (void)show;
- (void)cancel;

@end

The JLToast.m file:

#import "JLToast.h"
#import "JLToastView.h"
#import "JLToastCenter.h"
#import <dispatch/dispatch.h>

@implementation JLToast

@synthesize view = _view; // added by me
@synthesize text = _text; // added by me

+ (id)makeText:(NSString *)text
{
    return [JLToast makeText:text delay:0 duration:JLToastShortDelay];
}

+ (id)makeText:(NSString *)text duration:(NSTimeInterval)duration
{
    return [JLToast makeText:text delay:0 duration:duration];
}

+ (id)makeText:(NSString *)text delay:(NSTimeInterval)delay duration:(NSTimeInterval)duration
{
    JLToast *toast = [[[JLToast alloc] init] autorelease]; // added autorelease by me
    [toast setText:text];
    [toast setDelay:delay];
    [toast setDuration:duration];
    
    return toast;
}

// added by me
- (void)dealloc
{
    [_view release];
    [_text release];
    
    [super dealloc];
}

- (id)init
{
    self = [super init];
    if( self )
    {
        _view = [[JLToastView alloc] init];
    }
    return self;
}

- (void)show
{
    [[JLToastCenter defaultCenter] addToast:self];
}

- (void)cancel
{
    
}


#pragma mark -
#pragma mark Getter/Setter

- (NSString *)text
{
    return [[_view textLabel] text];
}

- (void)setText:(NSString *)text
{
    [[_view textLabel] setText:text];
    //  [_view layoutSubviews];
}


#pragma mark -
#pragma mark NSOperation Overriding

- (BOOL)isConcurrent
{
    return YES;
}

- (void)start
{
    if( ![NSThread isMainThread] )
    {
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }
    [super start];
}

- (void)main{
    [self willChangeValueForKey:@"isExecuting"];
    
    _isExecuting = YES;
    
    [self didChangeValueForKey:@"isExecuting"];
    
    dispatch_async(dispatch_get_main_queue(), ^{ // Non-main thread cannot modify user interface
        [_view layoutSubviews]; // Calls layoutSubviews before being-shown. added by the original creator devxoul at around 20131013
        [_view setAlpha:0];
        [[[UIApplication sharedApplication] keyWindow] addSubview:_view];
        [UIView animateWithDuration:0.5 delay:_delay options:UIViewAnimationOptionBeginFromCurrentState animations:^{
            [_view setAlpha:1];
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:_duration animations:^{
                [_view setAlpha:1.0001];
            } completion:^(BOOL finished) {
                [self finish];
                [UIView animateWithDuration:0.5 animations:^{
                    [_view setAlpha:0];
                }];
            }];
        }];
    });
}

- (void)finish
{
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
    
    _isExecuting = NO;
    _isFinished = YES;
    
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

- (BOOL)isExecuting
{
    return _isExecuting;
}

- (BOOL)isFinished
{
    return _isFinished;
}

@end

The JLToastView.h file:

#import <UIKit/UIKit.h>

@interface JLToastView : UIView

@property (nonatomic, strong) UIView *backgroundView;
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic) UIEdgeInsets textInsets;

@end

The JLToastView.m file:

#import "JLToastView.h"
#import <QuartzCore/CALayer.h>

#define JLTOAST_LABEL_FONT_SIZE ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) ? 12 : 16)
#define JLTOAST_OFFSET_PORTRAIT_Y ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) ? 30 : 60)
#define JLTOAST_OFFSET_LANDSCAPE_Y ((UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) ? 20 : 40)

@implementation JLToastView

// added by congliu at 20131031Thu 1000am
- (void)dealloc
{
    [_backgroundView release];
    [_textLabel release];
    
    [super dealloc];
}

- (id)init
{
    self = [super init];
    if( self )
    {
        _backgroundView = [[UIView alloc] initWithFrame:CGRectMake( 0, 0, 100, 100 )];
        [_backgroundView setBackgroundColor:[UIColor colorWithWhite:0 alpha:0.7]];
        [[_backgroundView layer] setCornerRadius:5];
        [_backgroundView setClipsToBounds:YES];
        [self addSubview:_backgroundView];
        
        _textLabel = [[UILabel alloc] initWithFrame:CGRectMake( 0, 0, 100, 100 )];
        [_textLabel setTextColor:[UIColor whiteColor]];
        [_textLabel setBackgroundColor:[UIColor clearColor]];
        [_textLabel setFont:[UIFont systemFontOfSize:JLTOAST_LABEL_FONT_SIZE]];
        [_textLabel setNumberOfLines:0];
        [self addSubview:_textLabel];
        
        _textInsets = UIEdgeInsetsMake( 6, 10, 6, 10 );
    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    
    CGFloat deviceWidth = [[UIScreen mainScreen] bounds].size.width;
    
    UIFont *font = [_textLabel font];
    CGSize constraintSize = CGSizeMake( deviceWidth * (280.0f/320.0f), INT_MAX );
    CGSize textLabelSize = [[_textLabel text] sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
    
    [_textLabel setFrame:CGRectMake( _textInsets.left, _textInsets.top, textLabelSize.width, textLabelSize.height )];
    [_backgroundView setFrame:CGRectMake( 0, 0,
                                       [_textLabel frame].size.width + _textInsets.left + _textInsets.right,
                                       [_textLabel frame].size.height + _textInsets.top + _textInsets.bottom )];
    
    NSInteger x, y, width, height;
    CGFloat angle;
    switch( [[UIDevice currentDevice] orientation] )
    {
        case UIDeviceOrientationPortraitUpsideDown:
            width = [_backgroundView frame].size.width;
            height = [_backgroundView frame].size.height;
            x = ([[UIScreen mainScreen] bounds].size.width - width) / 2;
            y = JLTOAST_OFFSET_PORTRAIT_Y;
            angle = M_PI;
            break;
            
        case UIDeviceOrientationLandscapeLeft:
            width = [_backgroundView frame].size.height;
            height = [_backgroundView frame].size.width;
            x = JLTOAST_OFFSET_LANDSCAPE_Y;
            y = ([[UIScreen mainScreen] bounds].size.height - height) / 2;
            angle = M_PI_2;
            break;
            
        case UIDeviceOrientationLandscapeRight:
            width = [_backgroundView frame].size.height;
            height = [_backgroundView frame].size.width;
            x = [[UIScreen mainScreen] bounds].size.width - width - JLTOAST_OFFSET_LANDSCAPE_Y;
            y = ([[UIScreen mainScreen] bounds].size.height - height) / 2;
            angle = -M_PI_2;
            break;
            
        default:
            width = [_backgroundView frame].size.width;
            height = [_backgroundView frame].size.height;
            x = ([[UIScreen mainScreen] bounds].size.width - width) / 2;
            y = [[UIScreen mainScreen] bounds].size.height - height - JLTOAST_OFFSET_PORTRAIT_Y;
            angle = 0;
            break;
            
    }
    
    [self setTransform:CGAffineTransformMakeRotation( angle )];
    [self setFrame:CGRectMake( x, y, width, height )];
}

#pragma mark - hit test

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //    NSLog(@"%@ hitTest", [self class]);
    return nil;
}

@end
Community
  • 1
  • 1
George
  • 3,384
  • 5
  • 40
  • 64

2 Answers2

3

The answer to the question that you linked to applies exactly to your case. Due to the dispatch_once(),

center = [[JLToastCenter alloc] init]; // correct

is executed exactly once in the lifetime of your application, when defaultCenter is called the first time. Subsequent calls to defaultCenter just return the contents of the center variable, so you want that object to stay alive.

With

center = [[[JLToastCenter alloc] init] autorelease]; // wrong

the object would be released (and potentially deallocated) as soon as the program control returns to the main event loop and the current autorelease pool ends.

Therefore no autorelease here! (But WHY do you want to convert a project from ARC to MRC??)

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • To @MartinR Thank you! Then how can `center` be released? I need MRC because I want to put JLToast into another MRC project. Now seems everyone recommend ARC, but I still want MRC, someone warned me before, so I was too scared to adopt ARC :D (Sorry that I accept Jakob's answer because all of you help me out but I cannot accept both...) – George Nov 03 '13 at 13:02
  • 2
    @congliu: You never release it. The object exists until the application terminates. – Martin R Nov 03 '13 at 13:19
  • To @MartinR, But never releasing it, would it be a memory leak? – George Nov 03 '13 at 13:27
  • 1
    @congliu: If an application *terminates*, all its memory is automatically given back to the system. – Martin R Nov 03 '13 at 13:28
3

Get rid of the autorelease!

[JLToastCenter defaultCenter] should always return the same object. The first time you call it, it creates the object. (That's called 'lazy initialisation' because you create the object only when needed) Then it stores a pointer to the shared object in a static variable to keep it around.

If you would add the autorelease, the object would be created and released the next time the current autoreleasepool is drained. Then the static variable would contain a pointer to a released object. The next time you would then call [JLToastCenter defaultCenter], and then send a message to the released object, all kinds of things could happen (your app will probably crash).

Jakob Egger
  • 11,981
  • 4
  • 38
  • 48