1

有一个小 toast 实用程序源代码 ( https://github.com/Joyfl/JLToast ) 似乎使用 ARC。但我想在手动保留释放(MRR)模式下使用它。

特别是,我不确定center = [[JLToastCenter alloc] init];(对于 ARC 模式)+(id)defaultCenter是否JLToastCenter.m应该重写到center = [[[JLToastCenter alloc] init] autorelease];(对于 MRR 模式),其中center声明为static id center = nil;.

在这篇文章中,@mipadi 给出的答案是“如果变量只被初始化一次,并且应该在应用程序的整个生命周期内一直存在,那么不,它不应该被释放(它的内存基本上会在无论如何,应用程序退出)”。我想这是静态变量centerJLToastCenter.m但不确定时的情况。

下面列出的我自己的 MRR 模式版本添加了 release/autorelease/dealloc 的东西。我还将所有点符号更改为消息样式。

源代码

源代码清单:

JLToastCenter.h

JLToastCenter.m

JLToast.h

JLToast.m

JLToastView.h

JLToastView.m

JLToastCenter.h文件:

#import <Foundation/Foundation.h>

@class JLToast;

@interface JLToastCenter : NSObject
{
    NSOperationQueue *_queue;
}

+ (id)defaultCenter;

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

@end

JLToastCenter.m文件:

#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

JLToast.h文件:

#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

JLToast.m文件:

#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

JLToastView.h文件:

#import <UIKit/UIKit.h>

@interface JLToastView : UIView

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

@end

JLToastView.m文件:

#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
4

2 回答 2

3

您链接到的问题的答案完全适用于您的案例。由于 dispatch_once(),

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

在您的应用程序的生命周期中只执行一次,defaultCenter即第一次调用时。随后的调用defaultCenter只返回center变量的内容,因此您希望该对象保持活动状态。

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

一旦程序控制返回到主事件循环并且当前的自动释放池结束,该对象就会被释放(并可能被释放)。

所以autorelease这里没有!(但是为什么要将项目从 ARC 转换为 MRC??)

于 2013-11-03T11:02:57.723 回答
3

摆脱自动释放!

[JLToastCenter defaultCenter]应该总是返回相同的对象。第一次调用它时,它会创建对象。(这称为“延迟初始化”,因为您仅在需要时创建对象)然后它将指向共享对象的指针存储在静态变量中以保留它。

如果您要添加autorelease,则将在下一次当前自动释放池耗尽时创建并释放该对象。然后静态变量将包含一个指向已释放对象的指针。下次您调用[JLToastCenter defaultCenter],然后向释放的对象发送消息时,可能会发生各种事情(您的应用程序可能会崩溃)。

于 2013-11-03T11:04:26.753 回答