OC RunLoopアプリケーション例

18488 ワード

知識点:1、RunLoopの基礎知識2、RunLoopとNSTimer 3、RunLoopとPerform Selector 4、RunLoop、スレッド、AutoreleasePoolの3者連絡5、RunLoopとスレッド通信6、RunLoopの各種状態傍受7、RunLoopとNSNotificationQueue
####RunLoopとは?
NSRunLoopアップル公式ドキュメントCoreFoundationソース
RunLoop入門私を見れば十分です-簡書RunLoopはもう入門しましたか?応用しない?簡書
RunLoop|Garan no douを深く理解する
runloopベースのスレッドの保存、破棄、通信IOS--RunLoopのインスタンス化説明
RunLoopには5種類の運転モードがあり、そのうちよく見られるのは1.2種類である.
1. kCFRunLoopDefaultMode:App   Mode,         Mode   
2. UITrackingRunLoopMode:     Mode,   ScrollView       ,            Mode   
3. UIInitializationRunLoopMode:      App          Mode,          
4. GSEventReceiveRunLoopMode:           Mode,     
5. kCFRunLoopCommonModes:         Mode,    kCFRunLoopDefaultMode UITrackingRunLoopMode ,        Mode

###RunLoopアプリケーション###1、NSTimerメインスレッドの下:
- (void)timer
{
    NSTimer *aTimer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    // 1、       NSDefaultRunLoopMode ,  RunLoop      ,          
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    // 2、       UITrackingRunLoopMode (  UIScrollView),  RunLoop      ,          
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // 3、   NSRunLoopCommonModes   :UITrackingRunLoopMode NSDefaultRunLoopMode  
    [[NSRunLoop mainRunLoop] addTimer:aTimer forMode:NSRunLoopCommonModes];
}

scheduledTimerWithTimeInterval
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //   
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        //          RunLoop:[[NSRunLoop currentRunLoop] addTimer: t forMode: NSDefaultRunLoopMode];
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerFired:)userInfo:nil repeats:YES];
        //       currentRunLoop run  
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
    
}


#####RunLoopのrunとstop 1、CFRunLoopStopはすべてCFRunloopでrunloopを実行することを直接停止することができます
- (void)testTimer
{
    NSTimer *aTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(runrun) userInfo:nil repeats:YES];
    //     
    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
    //   UI      ,      UITrackingRunLoopMode  ,    NSDefaultRunLoopMode    
    [currentRunLoop addTimer:aTimer forMode:NSDefaultRunLoopMode];
    // run     , 
    //   1
    //   2
    //   3
}


方式1:run
    /*  1:
            NSDefaultRunLoopMode  
       runloop  :
     1、  CFRunLoopStop  
     2、(1)    timer    port;(2)        (      )
     */
    [currentRunLoop run];

方式2:runUntilDate
    /*  2:
            NSDefaultRunLoopMode  
       runloop  :
     1、  CFRunLoopStop  
     2、(1)    timer    port;(2)      
     */
    [currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];//

方式3:runMode:beforeDate:(実はCFRunLoopRunInModeを呼び出します)
    // returnAfterSourceHandled   YES,      timer   ,runloop    
    CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);returnAfterSourceHandled = YES   ;
    /*  3:
       runMode、    ,         
       runloop  :
        (1)  CFRunLoopStop;
        (2)    timer    port;
        (3)      
     */

    BOOL result =  [currentRunLoop runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:20]];
    if (result) {
        //    PerfromSelector*      Input Source       ,Run Loop      YES,    NO。
    }else {
        // NO
    }

}

あるイベントを一定時間リスニングしたり、30分以内にonTimerFiredを30 sおきに実行したりします.
@autoreleasepool {
    
    NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
                                                    target:self
                                                  selector:@selector(onTimerFired:)
                                                  userInfo:nil
                                                   repeats:YES];

    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}

#####2、performSelector: withObject: afterDelay: inModes
プライマリスレッド
    //   performSelector      RunLoopMode  
    //   performSelector        NSTimer   afterDelay
    [self performSelector:@selector(performSelector) withObject:nil afterDelay:2   inModes:@[NSDefaultRunLoopMode]];

サブスレッド
    BBThread *thread = [[BBThread alloc] initWithBlock:^{
        
        [self performSelector:@selector(performSelector) withObject:nil afterDelay:2 inModes:@[NSDefaultRunLoopMode]];
        //     currentRunLoop   
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        [currentRunLoop run];
         
    }];
    [thread start];

###3、常駐スレッドAFNetworking 2.0には、すべてのリクエストを専門に処理する常駐スレッドのコールバックイベントが作成されています.
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         //           port,       Thread     
        [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;
}

####4、スレッド、RunLoop、AutoreleasePoolの3つの関係########スレッドとRunLoop CFRunLoopGetMain()とCFRunLoopGetCurrent()のソースコード
///    Dictionary,key   pthread_t, value   CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
///    loopsDic    
static CFSpinLock_t loopsLock;
 
///      pthread     RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);
    
    if (!loopsDic) {
        //       ,     Dic,           RunLoop。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }
    
    ///     Dictionary    。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
    
    if (!loop) {
        ///     ,    
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        ///       ,      ,          RunLoop。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }
    
    OSSpinLockUnLock(&loopsLock);
    return loop;
}
 
CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}
 
CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

上のコードから分かるように,スレッドとRunLoopの間には一つ一つ対応しており,その関係はグローバルなDictionaryに保存されている.プログラムが作成されたばかりの頃はRunLoopはありませんでしたが、自分から取得しなければ、それはずっとありません.RunLoopの作成は初回取得時に発生し,RunLoopの破棄はスレッド終了時に発生する.1つのスレッドの内部でのみRunLoopを取得できます(メインスレッドを除きます).
#######AutoreleasePoolはRunLoop 1、1つのスレッドに対応するRunLoop(メインスレッドシステムが起動し、他のスレッドはロードして取得して起動する必要がある)2、スレッドがRunLoopを起動していない場合、autoreleaseオブジェクトに遭遇すると次のような状況になります.
1)、           autorelease  ,         key      poolpage;
2)、    page.add(obj)OK;
3)、        autoreleaseNoPage,         page;
4)、    ,       page pop。

3、スレッドがRunLoopを起動し、RunLoop内部でAutoreleasePoolを管理する
1)、RunLoop    objc_autoreleasePoolPush;
2)、RunLoop     objc_autoreleasePoolPop objc_autoreleasePoolPush;
3)、RunLoop    objc_autoreleasePoolPop;

4、またRunLoopを起動する前に@autoreleasepool{...}ラップの意味:大きな解放プールを作成し、{}を解放する間に作成された一時オブジェクトを解放します.一般的に良いフレームワークの著者はそうします(上のAFNetworkingはそうします).
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         //           port,       Thread     
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 
        [runLoop run];
    }
}

#####NSRunLoopとNSMachPortからのスレッド通信の例:
- (void)testDemo3
{
    //                  ,       NSMachPort  
    NSMachPort *mainPort = [[NSMachPort alloc]init];
    NSPort *threadPort = [NSMachPort port];
    //               
    threadPort.delegate = self;

    //    runloop     
    [[NSRunLoop currentRunLoop]addPort:mainPort forMode:NSDefaultRunLoopMode];

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        //    Port
        [[NSRunLoop currentRunLoop]addPort:threadPort forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

    });

    NSString *s1 = @"hello";

    NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSMutableArray *array = [NSMutableArray arrayWithArray:@[mainPort,data]];
        // 2  threadPort      ,     :    。msgid     。
        //components,        。reserved:         (         ,             ...)
        [threadPort sendBeforeDate:[NSDate date] msgid:1000 components:array from:mainPort reserved:0];

    });

}

//  NSMachPort       ,      ,      id。       NSPortMessage       
- (void)handlePortMessage:(id)message
{

    NSLog(@"     ,   :%@",[NSThread currentThread]);

    //   KVC     
    NSArray *array = [message valueForKeyPath:@"components"];

    NSData *data =  array[1];
    NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",s1);

//    NSMachPort *localPort = [message valueForKeyPath:@"localPort"];
//    NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];

}

次のように印刷します.
2016-11-23 16:50:20.604 TestRunloop3[1322:120162]      ,   :{number = 3, name = (null)}
2016-11-23 16:50:26.551 TestRunloop3[1322:120162] hello


######カスタム入力ソースでスレッド通信を実現
- (void)testDemo4
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSLog(@"starting thread.......");

        _runLoopRef = CFRunLoopGetCurrent();
        //   _source_context。
        bzero(&_source_context, sizeof(_source_context));
        //             ,       
        _source_context.perform = fire;
        //  
        _source_context.info = "hello";
        //    source
        _source = CFRunLoopSourceCreate(NULL, 0, &_source_context);
        // source     RunLoop  
        CFRunLoopAddSource(_runLoopRef, _source, kCFRunLoopDefaultMode);

        //  runloop         YES,          
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 9999999, YES);

        NSLog(@"end thread.......");
    });


    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        if (CFRunLoopIsWaiting(_runLoopRef)) {
            NSLog(@"RunLoop         ");
            //      
            CFRunLoopSourceSignal(_source);
            //    ,              ,        
            CFRunLoopWakeUp(_runLoopRef);
        }else {
            NSLog(@"RunLoop       ");
            //      ,          ,         ,            
            CFRunLoopSourceSignal(_source);
        }
    });

}

//             
static void fire(void* info){

    NSLog(@"           ");

    printf("%s",info);
}


出力結果は次のとおりです.
2016-11-24 10:42:24.045 TestRunloop3[4683:238183] starting thread.......
2016-11-24 10:42:26.045 TestRunloop3[4683:238082] RunLoop          
2016-11-24 10:42:31.663 TestRunloop3[4683:238183]            
hello
2016-11-24 10:42:31.663 TestRunloop3[4683:238183] end thread.......


#####CFRunLoopObserverRefは観察者であり、RunLoopの状態変化を傍受できる
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //     
    /*
           CFAllocatorRef allocator:       CFAllocatorGetDefault()    
           CFOptionFlags activities:       kCFRunLoopAllActivities       
           Boolean repeats:YES:     NO:   
           CFIndex order:   ,   0  
           :       observer:    activity:     
     */
    /*
         
     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),   //       RunLoop
     kCFRunLoopBeforeTimers = (1UL << 1), //     Timer
     kCFRunLoopBeforeSources = (1UL << 2), //     Source
     kCFRunLoopBeforeWaiting = (1UL << 5), //      
     kCFRunLoopAfterWaiting = (1UL << 6),//        
     kCFRunLoopExit = (1UL << 7),//     RunLoop
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     };
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop  ");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop   Timers ");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop   Sources ");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop    ");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop   ");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop   ");
                break;
                
            default:
                break;
        }
    });
    
    //  RunLoop     
    /*
           CFRunLoopRef rl:     RunLoop,          RunLoop
           CFRunLoopObserverRef observer    
           CFStringRef mode    RunLoop           
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    /*
     CF     (Core Foundation)
         Create、Copy、Retain      ,       ,         release
     GCD   iOS6.0           ,6.0  GCD      ARC ,         
     */
    CFRelease(observer);
}



CFRunLoopObserverRef使用demo:メインスレッドRunLoopの空き時間を利用して処理し、UIのことをいくつかして、カートンを減らします(これでマルチスレッドは必要ありません)
 CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        if (activity == kCFRunLoopBeforeWaiting) {
            // RunLoop    
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);

NSNotificationQueueもrunloopと関係があるOC--NSNotificationCenter再認識