Objective-C基礎(chǔ)-RunLoop

1裂明、什么是RunLoop

故名思義就是偶芍,運(yùn)行循環(huán), 在程序運(yùn)行過程中循環(huán)做一些事情拌禾。iOS中有2套API來訪問和使用RunLoop取胎。

  • Foundation:NSRunLoop
  • CFRunLoopRef:CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表著RunLoop對(duì)象
  • NSRunLoop是基于CFRunLoopRef的一層OC包裝
  • CFRunLoopRef是開源的。
  • https://opensource.apple.com/tarballs/CF

2、RunLoop與線程關(guān)系

  • 每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象
  • RunLoop保存在一個(gè)全局的Dictionary里闻蛀,線程作為key匪傍,RunLoop作為value
  • 線程剛創(chuàng)建時(shí)并沒有RunLoop對(duì)象,RunLoop會(huì)在第一次獲取它時(shí)創(chuàng)建
  • RunLoop會(huì)在線程結(jié)束時(shí)銷毀
  • 主線程的RunLoop已經(jīng)自動(dòng)獲染跬础(創(chuàng)建)役衡,子線程默認(rèn)沒有開啟RunLoop

3、獲取RunLoop對(duì)象

  • Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
  • Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象

4薪棒、RunLoop相關(guān)的類

Core Foundation中關(guān)于RunLoop的5個(gè)類

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

Runloop結(jié)構(gòu).jpg

4.1 CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的運(yùn)行模式
  • 一個(gè)RunLoop包含若干個(gè)Mode手蝎,每個(gè)Mode又包含若干個(gè)Source0/Source1/Timer/Observer
  • RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode
  • 如果需要切換Mode俐芯,只能退出當(dāng)前Loop柑船,再重新選擇一個(gè)Mode進(jìn)入
  • 不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
  • 如果Mode里沒有任何Source0/Source1/Timer泼各,RunLoop會(huì)立馬退出
RunloopMode.jpg

iOS 常用3種Mode

  • kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
  • UITrackingRunLoopMode:界面跟蹤 Mode亏拉,用于 ScrollView 追蹤觸摸滑動(dòng)扣蜻,保證界面滑動(dòng)時(shí)不受其他 Mode 影響
  • kCFRunLoopCommonModes:這是一個(gè)占位用的Mode,不是一種真正的Mode

4.2 CFRunLoopObserverRef

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),            //即將進(jìn)入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), //即將處理Time
    kCFRunLoopBeforeSources = (1UL << 2),//即將處理Source
    kCFRunLoopBeforeWaiting = (1UL << 5),//即將進(jìn)入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒
    kCFRunLoopExit = (1UL << 7),             //即將退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

添加Observer監(jiān)聽RunLoop的所有狀態(tài)

- (void)addRunloopObserver {
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
            }
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"kCFRunLoopBeforeTimers");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"kCFRunLoopBeforeSources");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"kCFRunLoopBeforeWaiting");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"kCFRunLoopAfterWaiting");
                break;
            case kCFRunLoopExit: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit - %@", mode);
                CFRelease(mode);
            }
                break;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
}

5及塘、RunLoop的運(yùn)行邏輯

RunLoop的運(yùn)行邏輯
  • Source0: 觸摸事件處理莽使、performSelector:onThread:
  • Source1: 基于Port的線程間通信、系統(tǒng)事件捕捉
  • Timers: NSTimer笙僚、performSelector:withObject:afterDelay:
  • Observers: 用于監(jiān)聽RunLoop的狀態(tài)芳肌、UI刷新(BeforeWaiting)、Autorelease poolBeforeWaiting
Runloop運(yùn)行流程.jpg

6肋层、RunLoop休眠的實(shí)現(xiàn)原理

Runloop休眠原理.jpg

7亿笤、RunLoop應(yīng)用

  • 控制線程生命周期(線程保活)
  • 解決NSTimer在滑動(dòng)時(shí)停止工作的問題 (NSRunLoopCommonModes)
  • 監(jiān)控應(yīng)用卡頓
  • 性能優(yōu)化

線程倍安活

@interface SRThread : NSThread

@end

@implementation SRThread
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

@interface ViewController ()
@property (strong, nonatomic) SRThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end

@implementation ViewController

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self stop];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.stopped = NO;
    self.thread = [[SRThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        
        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
    self.view.backgroundColor = [UIColor greenColor];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if (!self.thread) {
        return;
    }
    
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子線程需要執(zhí)行的任務(wù)
- (void)test {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (void)stop {
    if (!self.thread) {
        return;
    }
    
    // 在子線程調(diào)用stop(waitUntilDone設(shè)置為YES净薛,代表子線程的代碼執(zhí)行完畢后,這個(gè)方法才會(huì)往下走)
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

// 用于停止子線程的RunLoop
- (void)stopThread {
    // 設(shè)置標(biāo)記為YES
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    
    // 清空線程
    self.thread = nil;
}

@end

持久線程封裝

#import <Foundation/Foundation.h>

/**
 該線程執(zhí)行完任務(wù)不會(huì)銷毀蒲拉, 手動(dòng)調(diào)用stop后肃拜,線程銷毀了
 */
@interface SRPermenantThread : NSObject
/**
 在當(dāng)前子線程執(zhí)行一個(gè)任務(wù)
 */
- (void)executeTask:(dispatch_block_t)task;

/**
 結(jié)束線程
 */
- (void)stop;

@end



@interface SRPermenantThread()
@property (strong, nonatomic) NSThread *innerThread;
@end

@implementation SRPermenantThread

- (void)dealloc {
    [self stop];
}

#pragma mark - public methods

- (instancetype)init {
    if (self = [super init]) {
        self.innerThread = [[NSThread alloc] initWithBlock:^{
            // 創(chuàng)建上下文(要初始化一下結(jié)構(gòu)體)
            CFRunLoopSourceContext context = {0};
            
            // 創(chuàng)建source
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            
            // 往Runloop中添加source
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            
            // 銷毀source
            CFRelease(source);
            
            // 啟動(dòng)
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
        }];
        
        [self.innerThread start];
    }
    return self;
}

- (void)executeTask:(dispatch_block_t)task {
    if (!self.innerThread || !task) return;
    
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop {
    if (!self.innerThread) {
        return;
    }
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}


#pragma mark - private methods

- (void)__stop {
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(dispatch_block_t)task {
    task();
}

@end
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雌团,隨后出現(xiàn)的幾起案子燃领,更是在濱河造成了極大的恐慌,老刑警劉巖锦援,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猛蔽,死亡現(xiàn)場離奇詭異,居然都是意外死亡雨涛,警方通過查閱死者的電腦和手機(jī)枢舶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門懦胞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凉泄,你說我怎么就攤上這事躏尉。” “怎么了后众?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵胀糜,是天一觀的道長。 經(jīng)常有香客問我蒂誉,道長教藻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任右锨,我火速辦了婚禮括堤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绍移。我一直安慰自己悄窃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布蹂窖。 她就那樣靜靜地躺著轧抗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞬测。 梳的紋絲不亂的頭發(fā)上横媚,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音月趟,去河邊找鬼灯蝴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛狮斗,可吹牛的內(nèi)容都是我干的绽乔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼碳褒,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼折砸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沙峻,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤永罚,失蹤者是張志新(化名)和其女友劉穎志电,沒想到半個(gè)月后肆氓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竭望,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了删顶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竖螃。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逗余,靈堂內(nèi)的尸體忽然破棺而出特咆,到底是詐尸還是另有隱情,我是刑警寧澤录粱,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布腻格,位于F島的核電站,受9級(jí)特大地震影響啥繁,放射性物質(zhì)發(fā)生泄漏菜职。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一旗闽、第九天 我趴在偏房一處隱蔽的房頂上張望酬核。 院中可真熱鬧,春花似錦适室、人聲如沸愁茁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嘶居,卻和暖如春罪帖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邮屁。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工整袁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人佑吝。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓坐昙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芋忿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炸客,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • RunLoop 的概念 一般來講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù)戈钢,執(zhí)行完成后線程就會(huì)退出痹仙。如果我們需要一個(gè)機(jī)制,讓線...
    Mirsiter_魏閱讀 618評(píng)論 0 2
  • 轉(zhuǎn)自bireme殉了,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_閱讀 1,376評(píng)論 0 5
  • https://blog.ibireme.com/2015/05/18/runloop/ RunLoop 是 iO...
    SmallDe閱讀 699評(píng)論 0 51
  • Runloop是iOS和OSX開發(fā)中非晨觯基礎(chǔ)的一個(gè)概念,從概念開始學(xué)習(xí)。 RunLoop的概念 -般說众弓,一個(gè)線程一...
    小貓仔閱讀 995評(píng)論 0 1
  • 轉(zhuǎn)自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飄金閱讀 985評(píng)論 0 4