2013-08-24 10 views
5

मैं एक स्वास्थ्य बार लागू कर रहा हूं जो उपयोगकर्ता इनपुट के माध्यम से एनिमेट करता है।एक लॉक निष्पादित करने के लिए जो एक एनीमेशन पूर्ण होने तक प्रतीक्षा करता है?

ये एनिमेशन इसे एक निश्चित राशि (50 इकाइयों कहते हैं) द्वारा ऊपर या नीचे जाते हैं और बटन प्रेस का परिणाम होते हैं। दो बटन हैं बढ़ना और घटना।

मैं स्वास्थ्य बार पर एक ताला निष्पादित करना चाहता हूं ताकि एक ही समय में केवल एक धागा इसे बदल सके। समस्या यह है कि मुझे डेडलॉक मिल रहा है।

मैं अनुमान लगा रहा हूं क्योंकि एक अलग थ्रेड एक थ्रेड चलाता है जो किसी अन्य थ्रेड द्वारा आयोजित किया जाता है। लेकिन जब एनीमेशन पूरा हो जाता है तो वह ताला रास्ता देगा। [UIView AnimateWithDuration] पूर्ण होने पर आप लॉक को कैसे कार्यान्वित करते हैं?

मुझे आश्चर्य है कि NSConditionLock जाने का तरीका है, लेकिन अगर मैं अनावश्यक जटिलता से बचने के लिए संभव हो तो NSLocks का उपयोग करना चाहता हूं। आप क्या सलाह देते हैं?

(अंततः मैं एनिमेशन "ऊपर क़तार" जबकि उपयोगकर्ता इनपुट जारी रखने के लिए दे करना चाहते हैं, लेकिन अब मैं सिर्फ काम कर ताला पाने के लिए, पहले चाहते हैं तो भी यह ब्लॉक इनपुट।)

(हम्म के लिए आते हैं इसके बारे में सोचें, एक ही UIView के लिए एक समय में केवल एक [UIView AnimateWithDuration] चल रहा है। एक दूसरी कॉल पहले को बाधित करेगी, जिससे पूरा करने वाला हैंडलर पहले के लिए तुरंत चल रहा है। हो सकता है कि पहले लॉक पहले मौका हो अनलॉक करने के लिए। इस मामले में लॉकिंग को संभालने का सबसे अच्छा तरीका क्या है? शायद मुझे ग्रैंड सेंट्रल डिस्पैच पर फिर से जाना चाहिए, लेकिन मैं देखना चाहता था कि कोई आसान तरीका है या नहीं।)

ViewController.h में घोषित करता है:

NSLock *_lock; 

ViewController.m में मेरे पास है:

loadView में:

_lock = [[NSLock alloc] init]; 

ViewController.m के बाकी (प्रासंगिक भागों):

-(void)tryTheLockWithStr:(NSString *)str 
{ 
    LLog(@"\n"); 
    LLog(@" tryTheLock %@..", str); 

    if ([_lock tryLock] == NO) 
    { 
     NSLog(@"LOCKED."); 
    } 
      else 
    { 
      NSLog(@"free."); 
      [_lock unlock]; 
    } 
} 

// TOUCH DECREASE BUTTON 
-(void)touchThreadButton1 
{ 
    LLog(@" touchThreadButton1.."); 

    [self tryTheLockWithStr:@"beforeLock"]; 
    [_lock lock]; 
    [self tryTheLockWithStr:@"afterLock"]; 

    int changeAmtInt = ((-1) * FILLBAR_CHANGE_AMT); 
    [self updateFillBar1Value:changeAmtInt]; 

    [UIView animateWithDuration:1.0 
         delay:0.0 
        options:(UIViewAnimationOptionTransitionNone|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction) 
       animations: 
    ^{ 

     LLog(@" BEGIN animationBlock - val: %d", self.fillBar1Value) 
     self.fillBar1.frame = CGRectMake(FILLBAR_1_X_ORIGIN,FILLBAR_1_Y_ORIGIN, self.fillBar1Value,30); 
    } 
    completion:^(BOOL finished) 
    { 
     LLog(@" END animationBlock - val: %d - finished: %@", self.fillBar1Value, (finished ? @"YES" : @"NO")); 

     [self tryTheLockWithStr:@"beforeUnlock"]; 
     [_lock unlock]; 
     [self tryTheLockWithStr:@"afterUnlock"];   
    } 
    ]; 
} 

-(void)updateFillBar1Value:(int)changeAmt 
{ 
    self.prevFillBar1Value = self.fillBar1Value; 

    self.fillBar1Value += changeAmt; 

    if (self.fillBar1Value < FILLBAR_MIN_VALUE) 
    { 
     self.fillBar1Value = FILLBAR_MIN_VALUE; 
    } 
    else if (self.fillBar1Value > FILLBAR_MAX_VALUE) 
    { 
     self.fillBar1Value = FILLBAR_MAX_VALUE; 
    } 
} 

आउटपुट:

करने के लिए पुन निर्देश: टैप करें "कम करें" एक बार

touchThreadButton1 ..

tryTheLock beforeLock .. मुक्त।

tryTheLock afterLock .. लॉक किया गया। शुरू animationBlock - वैल: 250 अंत animationBlock - वैल: 250 - समाप्त: हाँ

tryTheLock beforeUnlock .. बंद कर दिया।

tryTheLock के बाद अनलॉक .. मुफ्त।

निष्कर्ष: यह अपेक्षा के अनुसार काम करता है।

-

आउटपुट:

निर्देश करने के लिए पुन: टैप करें "कम करें" तेज़ी से दो बार (प्रारंभिक एनीमेशन दखल) ..

touchThreadButton1 ..

tryTheLock पहले लॉक .. मुफ्त।

tryTheLock afterLock .. लॉक किया गया। .. 250 touchThreadButton1

tryTheLock beforeLock .. LOCKED: वैल - animationBlock शुरू करते हैं। * - [एनएसएलॉक लॉक]: डेडलॉक ('(शून्य)') * डीबग करने के लिए _NSLockError() पर तोड़ें।

निष्कर्ष। डेडलॉक त्रुटि। उपयोगकर्ता इनपुट जमे हुए है।

उत्तर

8

निचले भाग में, मेरी मूल जवाब में, मैं अनुरोध किया कार्यक्षमता (प्राप्त करने के लिए यदि आप एक एनीमेशन आरंभ करते हुए पूर्व एनीमेशन अभी भी प्रगति पर एक तरह से वर्णन है, यह बाद में एनीमेशन अप क़तार में केवल वर्तमान लोगों के बाद शुरू कार्य पूर्ण)।

जबकि मैं इसे ऐतिहासिक उद्देश्यों के लिए रखूंगा, तो मैं एक पूरी तरह से अलग दृष्टिकोण का सुझाव देना चाहूंगा। विशेष रूप से, यदि आप किसी ऐसे बटन पर टैप करते हैं जिसके परिणामस्वरूप एनीमेशन हो, लेकिन पिछली एनीमेशन अभी भी प्रगति पर है, तो मैं इसके बजाय सुझाव दूंगा कि आप पुरानी एनीमेशन को हटा दें और तुरंत नई एनीमेशन शुरू करें, लेकिन ऐसा नया करें एनीमेशन जहां भी मौजूदा एक छोड़ दिया जाता है वहां से उठाता है।

  1. iOS संस्करण से पहले iOS 8 करने के लिए, चुनौती यह है कि यदि आप एक नया एनीमेशन शुरू, जबकि एक अन्य कार्य प्रगति पर है, ओएस awkwardly तुरंत जहां मौजूदा एनीमेशन समाप्त हो गया है | करने के लिए कूदता है और से नए एनीमेशन शुरू होता है क्या आप वहां मौजूद हैं।

    पहले से 8 iOS संस्करण में विशिष्ट समाधान के लिए होगा:

    • हड़पने एनिमेटेड देखने के presentationLayer (इस UIView की CALayer की वर्तमान स्थिति है ...यदि आप UIView देखते हैं, जबकि एनीमेशन प्रगति पर है, तो आपको अंतिम मान दिखाई देगा, और हमें वर्तमान स्थिति को पकड़ने की आवश्यकता है);

    • हड़पने कि presentationLayer से एनिमेटेड संपत्ति के मूल्य के वर्तमान मूल्य;

    • एनिमेशन निकालना;

    • एनिमेटेड प्रॉपर्टी को "वर्तमान" मान पर रीसेट करें (ताकि अगली एनीमेशन शुरू करने से पहले यह पूर्व एनीमेशन के अंत तक कूदने के लिए दिखाई न दे);

    • 'नई' मूल्य के लिए एनीमेशन आरंभ;

    तो, उदाहरण के लिए, यदि आप एक frame की बदलती जो एक एनीमेशन की प्रगति में हो सकता है एनिमेट रहे हैं, आप की तरह कुछ कर सकता:

    CALayer *presentationLayer = animatedView.layer.presentationLayer; 
    CGRect currentFrame = presentationLayer.frame; 
    [animatedView.layer removeAllAnimations]; 
    animatedView.frame = currentFrame; 
    [UIView animateWithDuration:1.0 animations:^{ 
        animatedView.frame = newFrame; 
    }]; 
    

    यह पूरी तरह से भद्दापन के सभी समाप्त "वर्तमान" एनीमेशन (और अन्य कतारबद्ध एनिमेशन) के बाद चलाने के लिए "अगली" एनीमेशन को कतारबद्ध करने के साथ जुड़े हुए हैं। आप एक बहुत अधिक उत्तरदायी UI के साथ भी समाप्त होते हैं, (उदाहरण के लिए आपको उपयोगकर्ता की वांछित एनीमेशन शुरू होने से पहले समाप्त होने के लिए पूर्व एनिमेशन की प्रतीक्षा करने की आवश्यकता नहीं है)।

  2. आईओएस 8 में, यह प्रक्रिया असीम रूप से आसान है, अगर आप एक नई एनीमेशन शुरू करेंगे, तो यह अक्सर एनिमेटेड प्रॉपर्टी के वर्तमान मूल्य से एनीमेशन शुरू नहीं करेगा, बल्कि गति की पहचान भी करेगा जो कि वर्तमान में एनिमेटेड संपत्ति बदल रही है, जिसके परिणामस्वरूप पुरानी एनीमेशन और नई एनीमेशन के बीच एक सहज संक्रमण हुआ है।

    इस नए iOS 8 सुविधा के बारे में अधिक जानकारी के लिए, मैं तुम्हें WWDC 2014 वीडियो Building Interruptible and Responsive Interactions का उल्लेख सुझाव देना चाहेंगे।

पूर्णता के लिए के लिए, मैं नीचे मेरी मूल जवाब रखेंगे, के रूप में यह ठीक प्रश्न में उल्लिखित कार्यक्षमता से निपटने की कोशिश करता है (बस सुनिश्चित करने के लिए मुख्य कतार अवरुद्ध नहीं कर रहा है एक अलग तंत्र का उपयोग करता है) । लेकिन मैं वास्तव में वर्तमान एनीमेशन को रोकने और नए तरीके से इस तरह से शुरू करने पर विचार करना चाहता हूं कि जहां भी कोई भी प्रगति एनीमेशन बंद हो गया हो, वहां से शुरू होता है।


मूल जवाब:

मैं एक NSLock (या सेमाफोर, या किसी अन्य समान तंत्र) में एक एनीमेशन लपेटकर क्योंकि कि मुख्य थ्रेड को रोकने में परिणाम कर सकते हैं की सिफारिश नहीं होगा। आप मुख्य धागे को कभी भी अवरुद्ध नहीं करना चाहते हैं। मुझे लगता है कि संचालन आकार बदलने के लिए एक सीरियल कतार का उपयोग करने के बारे में आपकी अंतर्ज्ञान आशाजनक है। और आप शायद एक "आकार बदलने" आपरेशन कि चाहते हैं:

  • शुरू की मुख्य पंक्ति पर UIView एनीमेशन (सभी यूआई अद्यतन मुख्य कतार पर जगह ले लेना चाहिए); और

  • एनीमेशन के समापन ब्लॉक में, ऑपरेशन समाप्त करें (हम तब तक ऑपरेशन पूरा नहीं करते हैं, यह सुनिश्चित करने के लिए कि अन्य कतारबद्ध ऑपरेशन तब तक शुरू नहीं हो जाते जब तक कि यह समाप्त नहीं हो जाता)।

    SizeOperation.h:

    @interface SizeOperation : NSOperation 
    
    @property (nonatomic) CGFloat sizeChange; 
    @property (nonatomic, weak) UIView *view; 
    
    - (id)initWithSizeChange:(NSInteger)change view:(UIView *)view; 
    
    @end 
    

    SizingOperation.m:

    #import "SizeOperation.h" 
    
    @interface SizeOperation() 
    
    @property (nonatomic, readwrite, getter = isFinished) BOOL finished; 
    @property (nonatomic, readwrite, getter = isExecuting) BOOL executing; 
    
    @end 
    
    @implementation SizeOperation 
    
    @synthesize finished = _finished; 
    @synthesize executing = _executing; 
    
    - (id)initWithSizeChange:(NSInteger)change view:(UIView *)view 
    { 
        self = [super init]; 
        if (self) { 
         _sizeChange = change; 
         _view = view; 
        } 
        return self; 
    } 
    
    - (void)start 
    { 
        if ([self isCancelled] || self.view == nil) { 
         self.finished = YES; 
         return; 
        } 
    
        self.executing = YES; 
    
        // note, UI updates *must* take place on the main queue, but in the completion 
        // block, we'll terminate this particular operation 
    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
         [UIView animateWithDuration:2.0 delay:0.0 options:kNilOptions animations:^{ 
          CGRect frame = self.view.frame; 
          frame.size.width += self.sizeChange; 
          self.view.frame = frame; 
         } completion:^(BOOL finished) { 
          self.finished = YES; 
          self.executing = NO; 
         }]; 
        }]; 
    } 
    
    #pragma mark - NSOperation methods 
    
    - (void)setExecuting:(BOOL)executing 
    { 
        [self willChangeValueForKey:@"isExecuting"]; 
        _executing = executing; 
        [self didChangeValueForKey:@"isExecuting"]; 
    } 
    
    - (void)setFinished:(BOOL)finished 
    { 
        [self willChangeValueForKey:@"isFinished"]; 
        _finished = finished; 
        [self didChangeValueForKey:@"isFinished"]; 
    } 
    
    @end 
    

    फिर इन कार्यों के लिए एक कतार परिभाषित:

मैं एक आकार बदलने आपरेशन सुझाव दे सकता है

@property (nonatomic, strong) NSOperationQueue *sizeQueue; 

इस कतार का दृष्टांत को (एक सीरियल कतार के रूप में) सुनिश्चित करें:

self.sizeQueue = [[NSOperationQueue alloc] init]; 
self.sizeQueue.maxConcurrentOperationCount = 1; 

और फिर, कुछ भी विचाराधीन दृश्य में हो जाना है कि बनाता है, करना होगा:

[self.sizeQueue addOperation:[[SizeOperation alloc] initWithSizeChange:+50.0 view:self.barView]]; 

और कुछ भी है कि बनाता है प्रश्न में कमी, देखें:

[self.sizeQueue addOperation:[[SizeOperation alloc] initWithSizeChange:-50.0 view:self.barView]]; 

उम्मीद है कि यह विचार को दर्शाता है। वहाँ संभव शोधन के सभी प्रकार के कर रहे हैं:

  • मैं एनीमेशन वास्तव में धीमी गति से किया जाता है, तो मैं आसानी से एक पूरी गुच्छा अप कतार सकता है, लेकिन आप शायद एक बहुत छोटे मूल्य का उपयोग किया था;

  • यदि ऑटो लेआउट का उपयोग करते हैं, तो आप सीधे फ्रेम समायोजित करने के बजाय चौड़ाई बाधा के constant समायोजित करेंगे और एनीमेशन ब्लॉक में आप layoutIfNeeded करेंगे); और

  • यदि संभवत: चौड़ाई ने अधिकतम अधिकतम/न्यूनतम मानों को मारा है तो आप फ्रेम परिवर्तन नहीं करने के लिए चेक जोड़ना चाहते हैं।

लेकिन कुंजी यह है कि यूआई परिवर्तनों की एनीमेशन को नियंत्रित करने के लिए ताले का उपयोग करना अव्यवस्थित है। आप कुछ भी नहीं चाहते हैं जो किसी भी चीज के लिए मुख्य कतार को अवरुद्ध कर सके लेकिन कुछ मिलीसेकंड। मुख्य कतार को अवरुद्ध करने पर विचार करने के लिए एनिमेशन ब्लॉक बहुत लंबे हैं। तो एक सीरियल ऑपरेशन कतार का उपयोग करें (और यदि आपके पास कई धागे हैं जिन्हें परिवर्तन शुरू करने की आवश्यकता है, तो वे सभी एक ही साझा ऑपरेशन कतार में एक ऑपरेशन जोड़ देंगे, जिससे विभिन्न थ्रेडों के सभी प्रकार से शुरू किए गए परिवर्तनों को स्वचालित रूप से समन्वयित किया जा सके)।

+1

धन्यवाद श्रीमान! मैं सराहना करता हूं कि आप स्पष्ट रूप से NSOperationQueue का प्रदर्शन कैसे करते हैं। आकार की ऑपरेशन को अपनी कक्षा में Encapsulating बल्कि सुरुचिपूर्ण है। यह पूरी तरह से काम करता है। –

+0

@ ब्लैकऑर्किड एफवाईआई, मुझे यकीन है कि इस सवाल से पहले आप लंबे समय से चले गए हैं, लेकिन मैंने इसे नाटकीय रूप से सरल दृष्टिकोण के साथ अपडेट किया है। – Rob

+0

इच्छा है कि मैं इसे एक से अधिक बार बढ़ा सकता हूं। :) –

संबंधित मुद्दे

 संबंधित मुद्दे