通过GCD问题单身模式

时间:2012-03-16 15:11:37

标签: objective-c ios

我正在使用GCD为我的应用程序提供单例类,以用作消息/警报系统。单例包含一个名为addText的方法,它将消息添加到NSMutable数组,然后将数组中的每条消息显示给用户。

标题如下:

#import <UIKit/UIKit.h>

@interface Banner : UIView

+ (Banner*) sharedBanner;
- (id) initWithWindow:(UIWindow*)window;
- (void) addText:(NSString*)bannerText;
- (void) callNext;

@end

实施如下:

#import "Banner.h"

static Banner* sharedBanner=nil;
static dispatch_queue_t serialQueue;

@implementation Banner
{
    @private
    UILabel* bannerLabel;
    NSMutableArray* textStrings;
    BOOL triggered;
    int counter;

    NSTimer* timer;
    UIButton* bannerButton;

    CGRect defaultFrame;
}

+ (id)allocWithZone:(NSZone *)zone {
    static dispatch_once_t onceQueue;

    dispatch_once(&onceQueue, ^{
        serialQueue = dispatch_queue_create("MyQueue.BannerQueue", NULL);
        if(sharedBanner == nil)
        {
            sharedBanner = [super allocWithZone:zone];
        }
    });
    return sharedBanner;
}


+ (Banner*) sharedBanner
{
    static dispatch_once_t onceQueue;
    dispatch_once(&onceQueue, ^{
        sharedBanner = [[Banner alloc] init];
    });
    return sharedBanner;
}

- (id) initWithWindow:(UIWindow*)window
{
    UIView* __block obj;

    dispatch_sync(serialQueue, ^
    {
        obj = [super initWithFrame:CGRectMake(0, 20, window.bounds.size.width, 0)];

        if (obj)
        {
            [self setBackgroundColor:[UIColor clearColor]];
            defaultFrame = self.frame;      
            timer = nil;
            counter = 0;
            triggered = NO;
            bannerLabel = [[UILabel alloc] initWithFrame:obj.bounds];
            textStrings = [[NSMutableArray alloc] init];  
            [bannerLabel setNumberOfLines:0];
            [bannerLabel setLineBreakMode:UILineBreakModeWordWrap];
            [bannerLabel setTextAlignment:UITextAlignmentCenter];
            [bannerLabel setBackgroundColor:[UIColor blackColor]];
            [bannerLabel setTextColor:[UIColor whiteColor]];
            [bannerLabel setAlpha:0.55];
            [self addSubview:bannerLabel];

            bannerButton = [[UIButton alloc] initWithFrame:self.bounds];
            [bannerButton addTarget:self action:@selector(complete)    forControlEvents:UIControlEventTouchUpInside];
            [bannerButton setBackgroundColor:[UIColor clearColor]];
            [bannerButton setEnabled:NO];
            [self addSubview:bannerButton];
    [window addSubview:self];
            [window bringSubviewToFront:self];
    }
    });
    self=(Banner*)obj;
    return self;
}


- (void) trigger
{
triggered = YES;
if ([textStrings count] > 0)
{       
    [bannerLabel setText:[textStrings objectAtIndex:0]];
    [bannerLabel sizeToFit];
    [self setFrame:CGRectMake(0, self.window.bounds.size.height - bannerLabel.frame.size.height, self.window.bounds.size.width, bannerLabel.frame.size.height)];
    [bannerLabel setFrame:self.bounds];
    [bannerButton setFrame:self.bounds];

    [bannerButton setEnabled:YES];
    [UIView animateWithDuration:1
                     animations:^
     {
         [bannerLabel setAlpha:1];
     }
                     completion:^(BOOL finished)
     {
         if (finished)
         {
             timer = [NSTimer scheduledTimerWithTimeInterval:4
                                                       target:self
                                                     selector:@selector(fade)
                                                     userInfo:nil
                                                      repeats:NO];
         }
     }];
}
else
{
    triggered = NO;
}
}

- (void) fade
{
[bannerButton setEnabled:NO];
if (timer)
{
timer = nil;
}
    [UIView animateWithDuration:1
                 animations:^
     {
     [bannerLabel setAlpha:0];
     }
                 completion:^(BOOL finished)
     {
     [self setFrame:defaultFrame];
     [bannerLabel setFrame:defaultFrame];
     [bannerButton setFrame:defaultFrame];

     DebugLog(@"BANNER_textStrings: %@", [textStrings objectAtIndex:0]);
     [textStrings removeObjectAtIndex:0];
     [self trigger];
 }];    
}

- (void) addText:(NSString*)bannerText
{
    dispatch_sync(serialQueue, ^
    {
        [textStrings addObject:bannerText];

         if (!triggered)
        {
            [self trigger];
        }
    });
}

    - (void) callNext
{
    counter++;
    if (!triggered)
    {
    [self trigger];
    }   
}

@end

问题是,多条消息同时显示,当它们淡出时,应用程序在淡入淡出方法中的“removeItemAtIndex:0”行崩溃。

任何人都能明白我做错了吗?

1 个答案:

答案 0 :(得分:2)

如何创建此Singleton的实例? +allocWithZone然后-initWithWindow?然后调用+sharedBanner来获取共享实例? ..but +sharedBanner进入一个新实例? ..为什么有+ allocWithZone?您是否尝试仅分配一个,但多次致电initinitWithWindow?你只能初始化一个对象..尽管奇怪的alloc / init东西,如果你必须调用一个方法来创建它而另一个方法来获取共享实例,这对于Singleton来说是无用的。你的意思是从-initWithWindow打电话给+sharedBanner,但后来意识到你总是要用一个窗口来调用sharedBanner,这会打破这一点......但是,这仍然无法解释为什么你重写+ allocWithZone ...我无法真实地计算出你创建的实例数量,启动你所创建实例的次数。我确定它不是线程安全的。

我想在-initWithWindow中使用serialQueue适合可怕的Singleton,但使用私有队列意味着你不知道它将运行哪个线程以及你在那里做的所有GUI内容要在主线程上完成。然后,您将[super initWithFrame]的结果分配给obj,然后在self上调用方法。之后obj被分配到self。这是故意的吗?

-trigger对串行队列中的GUI对象做了一些不安全的事情(除了你从-callNext调用它,这不是线程安全的),然后设置一个定时器,我不知道如何在没有runloop的情况下开始工作。

你完全不需要Singleton或任何GCD的东西。这对你造成了极大的伤害,即使你能使它发挥作用,你也不会在这里做任何可以从中受益的事情。你正在接近简单,快速和传统的东西,使它变得更慢,更复杂和越野车,而且非常规。不要覆盖allocWithZone,没有sharedBanner,没有serialQueue而且不会发送任何内容,一次,同步或异步(在此代码中) )。

在ViewController中有一个Banner的实例变量。创建一个横幅的vanilla实例,并在想要显示警报时发送-addText:消息。如果您需要从某个地方显示警报而无法访问视图控制器或窗口(如模型),则应重新考虑您的设计并将错误消息传递回调用链,而不是使用Singleton并将依赖项添加到UIView子类。在编写-init方法时遵循标准惯例。它总是一样的,会变成自动的。使用笔尖布局和配置视图。不要将视图添加到窗口或将窗口置于前面。这些是ViewController的作业。

在主线程上完成所有操作,并且只与主线程进行交互。如果您需要使用serialQueues进行锁定,请在与GUI交互之前在某个级别执行此操作。