【问题标题】:Allowing one method call at a time to a category method ios (@synchronized)一次允许一个方法调用一个类别方法 ios (@synchronized)
【发布时间】:2013-05-23 08:04:26
【问题描述】:

我有一个 UIViewController 和一个用于向 UIViewController 添加方法的类别。类中有一个方法:

@implementation UIViewController (AlertAnimationsAndModalViews)
-(void)someAddedMethod
{
    UIView *someView;
    //do some animation with the view that lasts 3 seconds
    //remove the view and return

}

在任何视图控制器中我都可以调用这个方法

[self someAddedMethod];

但是,我只想允许此方法一次运行一个。例如,如果我一个接一个地拨打两个电话

[self someAddedMethod];//call1
[self someAddedMethod];//call2

我希望第二次通话等到第一次通话完成。我知道 UIView animationWithduration... 在单独的线程中运行,并且看到我无法在类别中创建 iVar,我无法真正使用 @synchronized(someObject)..

有什么建议吗?

提前致谢!

编辑

方法如下:

 -(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
 {
 
 UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, -height, width, height)];
[self.view.superview addSubview:messageLabel];


[UIView animateWithDuration:0.5
                      delay:0
                    options: UIViewAnimationOptionBeginFromCurrentState
                 animations:^{
                     messageLabel.frame = CGRectMake(0, 0, SCREEN_WIDTH, height);
                 }
                 completion:^(BOOL finished){
                     
                     [UIView animateWithDuration: 0.5
                                           delay:duration
                                         options: UIViewAnimationOptionBeginFromCurrentState
                                      animations:^{
                                          messageLabel.frame = CGRectMake(0, -height, SCREEN_WIDTH, height);
                                      }
                                      completion:^(BOOL finished){
                                          [messageLabel removeFromSuperview];
                                      }];
                 }];

}

所以我从屏幕顶部显示一个“横幅”,等待一段时间(CGFloat)然后将视图滑出屏幕并删除。由于这是一个类别,我无法添加实例变量.. 所以我想要实现的是,如果多次调用此方法,我希望第一次调用无需等待即可执行,但之后的每个调用都等到前一个调用完成。

【问题讨论】:

  • 你也想等到动画完成吗?
  • 是的,查看编辑版本

标签: ios objective-c animation uiviewanimation synchronized


【解决方案1】:

如果只是关于动画,您可以查看if ([someView.layer animationForKey:@"sameKeyAsOnCreation"] == nil)。比你只会添加一个动画,如果它当前没有运行。

您也可以使用关联对象自行存储状态(动画正在运行/未运行)。

【讨论】:

    【解决方案2】:

    假设您想在上一个动画完成后开始下一个动画。 这样你就可以使用一些共享的NSMutableArray* _animationQueue 存储:

    -(void)someAddedMethod
    {
        NSTimeInterval duration = 3.0;
    
        void (^animationBlock)() = ^{
            //do some animations here 
            self.view.frame = CGRectOffset(self.view.frame, 40, 0);
        };
    
        __block void (^completionBlock)(BOOL) = ^(BOOL finished){
            [_animationQueue removeObjectAtIndex:0];
            if([_animationQueue count]>0) {
                [UIView animateWithDuration:duration animations:_animationQueue[0] completion:completionBlock];
            }
        };
    
        [_animationQueue addObject:animationBlock];
        if([_animationQueue count]==1) {
            [UIView animateWithDuration:duration animations:animationBlock completion:completionBlock];
        }
    }
    

    请注意,您不需要任何 @synchronized 功能,因为一切都在主线程上进行。

    更新:下面的代码正是您需要的:

    -(void)showTopBannerWithHeight:(CGFloat)height andWidth:(CGFloat)width andMessage:(NSString *)message andDuration:(CGFloat)duration
    {
        static NSMutableArray* animationQueue = nil;
        if(!animationQueue) {
            animationQueue = [[NSMutableArray alloc] init];
        }
    
        void (^showMessageBlock)() = ^{
            UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, width, height)];
            messageLabel.text = message;
            [self.view.superview addSubview:messageLabel];
    
            [UIView animateWithDuration: 0.5
                                  delay:duration
                                options:UIViewAnimationOptionBeginFromCurrentState
                             animations:^{
                                 messageLabel.frame = CGRectOffset(messageLabel.frame, 0, -height);
                             }
                             completion:^(BOOL finished){
                                 [messageLabel removeFromSuperview];
                                 [animationQueue removeObjectAtIndex:0];
                                 if([animationQueue count]>0) {
                                     void (^nextAction)() = [animationQueue objectAtIndex:0];
                                     nextAction();
                                 }
                             }];
        };
    
        [animationQueue addObject:showMessageBlock];
        if([animationQueue count]==1) {
            showMessageBlock();
        }
    }
    

    【讨论】:

    • 是的,我最初使用数组进行了类似的实现。这是一个递归方法,将“方法调用”存储在一个数组中,在方法结束之前,它会再次调用自身并检查是否添加了任何调用,如果它们再次添加。但是由于将其移入一个类别,除非我传入一个共享数组,否则我无法使用这些变量,我认为这会破坏使用类别的原因
    • 您可以使用objc_setAssociatedObject在类别中添加ivar。另请查看我更新的解决方案。
    • 哇,这成功了!多谢!如果没问题,只需几个问题也可以提高我的理解...当您声明静态数组时,为什么将其设置为 nil 然后检查 if(!animationQueue) 然后分配和初始化?如果你将它设置为 nil 你不会总是分配和初始化它吗?以及在动画结束时块 nextAction() 的目的是什么?
    • 静态变量animationQueue 的作用与全局变量相同。最初它是nil,每个函数调用都会检查它是否需要分配NSMutableArray 的实例。分配后,animationQueue 变为非零。
    • nextAction 只是队列中缓存的showMessageBlock 对象之一。
    【解决方案3】:

    尝试使用这个。并在@synchronized指令中使用self

    - (void)criticalMethod {
        @synchronized(self) {
            // Critical code.
        }
    }
    

    注意:@synchronized() 指令将任何 Objective-C 对象(包括 self)作为其唯一参数。该对象称为互斥信号量互斥体。它允许一个线程锁定一段代码以防止它被其他线程使用。

    【讨论】:

    • 如果您真的想等到“3 秒动画”完成后再允许您的方法的另一次调用运行,这是行不通的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-22
    相关资源
    最近更新 更多