CFRunLoop CF 차원에서 CFRunLoopMode 메커니즘 iOS 프로그램 ScrollView의 미끄럼이 왜 이렇게 매끄러운지 알아보기

7466 단어
간단한 소개
간단히 말하면run loop은 이벤트 구동의 큰 순환이다. 아래 코드와 같다.
int main(int argc, char * argv[]) {
     //        
     while (AppIsRunning) {
          //    ,      
          id whoWakesMe = SleepForWakingUp();
          //      
          id event = GetEvent(whoWakesMe);
          //      
          HandleEvent(event);
     }
     return 0;
}

코코아는 Run Loops와 관련된
  • 시스템 수준: GCD,machkernel,block,pthread
  • 응용층: NSTimer, UIEvent, Autorelease, NSObject(NSDelayedPerforming), NSObject(NSThreadPerformAddition), CADisplayLink, CATransition, CAAnimation, dispatchget_main_queue () (GCD에서 dispatch에서mainqueue까지의 Block은 dispatch에서main RunLoop에 실행됨), NSPort, NSURLconnection, AFNetworking (이 제3자 네트워크 요청 프레임워크는 새로운 라인을 열 때 자신의run loop 감청 이벤트를 추가하는 데 사용)
  • Main thread 스택의 위치
    스택 맨 아래쪽은 start(dyld), 위쪽은main, UIAPplication(main.m)-> GSEventRunModal(Graphic Services)-> RunLoopRunSpecific, CFRunLoopRun, CFRunLoopDoSouces0, CFRUNLOOOOP IS CALLING OUT TO A SOURCE0 PERFORM FORM FUCTION - Handle Touch Tovent
    RunLoop 원리
    CFRunLoop 소스 코드:http://opensource.apple.com/source/CF/CF-855.17/
    실행 순서의 위조 코드
    SetupThisRunLoopRunTimeoutTimer(); // by GCD timer
    do {
         __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
         __CFRunLoopDoObservers(kCFRunLoopBeforeSources);
    
         __CFRunLoopDoBlocks();
         __CFRunLoopDoSource0();
    
         CheckIfExistMessagesInMainDispatchQueue(); // GCD
    
         __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
         var wakeUpPort = SleepAndWaitForWakingUpPorts();
         // mach_msg_trap
         // Zzz...
         // Received mach_msg, wake up
         __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
         // Handle msgs
         if (wakeUpPort == timerPort) {
              __CFRunLoopDoTimers();
         } else if (wakeUpPort == mainDispatchQueuePort) {
              // GCD
              __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
         } else {
              __CFRunLoopDoSource1();
         }
         __CFRunLoopDoBlocks();
    } while (!stop && !timeout);
    

    구성
    Thread는 CFRunLoop을 포함하고 CFRunLoop은 CFRunLoopMode를 포함하며 모델은 CFRunLoopSource, CFRunLoopTimer와 CFRunLoopObserver를 포함한다.
    CFRunLoopMode
    RunLoop은 하나의 모드에서만 실행할 수 있습니다. 모드를 바꾸려면 현재 loop을 멈추고 다시 시작해야 합니다.이 메커니즘을 이용하면 ScrollView 과정에서 NSDefaultRunLoopMode의 모델은 UITrackingRunLoopMode로 전환하여 ScrollView의 원활한 슬라이딩이 NSDefaultRunLoopMode에서만 처리할 수 있는 이벤트의 영향을 받지 않도록 합니다.동시에 모델은 맞춤형으로 제작할 수 있습니다.
  • NSDefaultRunLoopMode: 기본값, 유휴 상태
  • UITracking RunLoopMode: ScrollView 슬라이딩 시
  • UIInitializationRunLoopMode: 시작할 때
  • NSRunLoopCommonModes: Mode 집합 Timer의 시간 계산이 scrollView의 미끄럼에 영향을 미치는 문제는 Timer를 NSRunLoopCommonModes에 추가하여 해결할 수 있다
  • // timer   NSDefaultRunLoopMode 
    [NSTimer scheduledTimerWithTimeInterval:1.0
         target:self
         selector:@selector(timerTick:)
         userInfo:nil
         repeats:YES];
    //      NSRunLoopCommonModes 
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
         target:self
         selector:@selector(timerTick:)
         userInfo:nil
         repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

    CFRunLoopTimer
    NSTimer는 RunLoopTimer의 패키지입니다.
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
    
    - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
    
    + (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
    - (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
    

    CFRunLoopSource
  • source0: UIEvent, CFSocket 같은 이벤트 처리
  • source1: Mach port 드라이버, CFMachport, CFMessagePort
  • CFRunLoopObserver
    코코아 프레임워크의 많은 메커니즘, 예를 들어 CAAnimation 등은 모두 RunLoopObserver가 촉발한 것이다.observer에서 현재 상태의 변화를 알립니다.
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
         kCFRunLoopEntry = (1UL << 0),
         kCFRunLoopBeforeTimers = (1UL << 1),
         kCFRunLoopBeforeSources = (1UL << 2),
         kCFRunLoopBeforeWaiting = (1UL << 5),
         kCFRunLoopAfterWaiting = (1UL << 6),
         kCFRunLoopExit = (1UL << 7),
         kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    

    RunLoop 사용 사례
    AFNetworking
    NSOperation+NSURLconnection 병행 모델을 사용하면 NSURLconnection 다운로드가 완료되기 전 라인이 종료되어 NSOperation 대상이 리셋을 받지 못하는 문제에 직면하게 된다.AFNetWorking에서 이 문제를 해결하는 방법은 공식적인 거예요.guidhttps://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLConnection/initWithRequest:delegate:startImmediately:NSURLconnection에 적힌 delegate 방법은connection에서 시작하는 루틴runloop에서 호출되어야 하기 때문에AFNetWorking은 애플의 D를 직접 참고했다emohttps://developer.apple.com/LIBRARY/IOS/samplecode/MVCNetworking/Introduction/Intro.html실현 방법은 단독으로 글로벌 thread를 만들고, 내장된runloop을 설치하며, 모든connection은 이runloop에서 시작합니다. 리셋도 주 라인을 차지하지 않고, CPU 자원을 소모하지 않습니다.
    + (void)networkRequestThreadEntryPoint:(id)__unused object {
         @autoreleasepool {
              [[NSThread currentThread] setName:@"AFNetworking"];
    
              NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
              [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
              [runLoop run];
         }
    }
    
    + (NSThread *)networkRequestThread {
         static NSThread *_networkRequestThread = nil;
         static dispatch_once_t oncePredicate;
         dispatch_once(&oncePredicate, ^{
              _networkRequestThread =
              [[NSThread alloc] initWithTarget:self
                   selector:@selector(networkRequestThreadEntryPoint:)
                   object:nil];
              [_networkRequestThread start];
         });
    
         return _networkRequestThread;
    }
    

    이 방법으로 상주 서비스의 라인을 만들 수 있습니다.
    TableView에서 그림을 부드럽게 스크롤 지연 불러오기
    CFRunLoopMode의 특성을 이용하여 NSDefaultRunLoopMode의 모델에 그림을 불러올 수 있습니다. 이렇게 하면 UITrackingRunLoopMode를 굴릴 때 이 모델이 불러오지 않고 영향을 주지 않습니다.
    UIImage *downloadedImage = ...;
    [self.avatarImageView performSelector:@selector(setImage:)
         withObject:downloadedImage
         afterDelay:0
         inModes:@[NSDefaultRunLoopMode]];
    

    프로그램이 붕괴되었을 때의 신호를 받아 자율적으로 처리한다. 예를 들어 팝업 알림 등이다.
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
    while (1) {
         for (NSString *mode in allModes) {
              CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
         }
    }
    

    비동기 테스트
    - (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
    {
         __block Boolean fulfilled = NO;
         void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
         ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
              fulfilled = block();
              if (fulfilled) {
                   CFRunLoopStop(CFRunLoopGetCurrent());
              }
         };
    
         CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
         CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
         // Run!
         CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
    
         CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
         CFRelease(observer);
    
         return fulfilled;
    }
    

    좋은 웹페이지 즐겨찾기