iOS RunLoop

​ 前言:一般代码运行完就结束了,为何APP就一直能保持运行状态呢?这个秘诀就是RunLoop,本文先介绍了RunLoop的概念,引出它的作用,并在CoreFoundation框架源码查看它的底层结构;然后介绍RunLoop和线程的关系,并介绍它运行的各种模式;最后介绍RunLoop在实际项目中的应用,比如解决NSTimer在滑动时停止工作的问题,线程保活和性能优化等问题。

一、RunLoop概念

1、RunLoop概念:

​ 顾名思义,运行循环,在程序运行过程中循环做一些事情。应用范畴有:定时器(Timer)、PerformSelector、GCD、事件响应、手势识别、界面刷新、网络请求、AutoreleasePool等。

2、RunLoop作用:

​ 如果没有RunLoop,运行代码

int main(int argc, char * argv[]) {
    @autoreleasepool {
//        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        NSLog(@"Hello world!");
    }
    return 0;
}

程序运行完就结束了,App也就退出了,所以RunLoop作用有以下几条:

​ 1)保持程序的持续运行;

​ 2)处理App中的各种事件(比如触摸事件、定时器事件等);

​ 3)节省CPU资源,该做事时做事,该休息时休息等;

3、RunLoop构成:

​ 下载CoreFoundation框架源码, 在CFRunLoop.c文件中找到RunLoop的底层结构:

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list 线程锁 */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;  // reset for runs of the run loop
    pthread_t _pthread;  //RunLoop对应的线程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;  //记录所有标记为common的mode
    CFMutableSetRef _commonModeItems;  //存储所有commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;  //当前运行的mode
    CFMutableSetRef _modes;  //存储的是CFRunLoopModeRef
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

可见,RunLoop底层是一个结构体,主要包含了一个线程,当前运行的Mode,若干个commonMode,若干个commonModeItem等。

二、RunLoop和线程

​ 程序运行的时候需要一个常驻线程,可以让线程接收到事件的时候干活,没事的时候休眠。我们执行下面的伪代码,一直等待消息,线程就不会退出了。

do {
   // 睡眠中等待消息
   // 接收消息
   // 处理消息
} while (消息 != 退出)

那么RunLoop和线程的关系是怎样呢?

​ 1)每条线程都有唯一的一个与之对应的RunLoop对象;

​ 2)RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value;

​ 3)线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建;

​ 4)RunLoop会在线程结束时销毁;

​ 5)主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop;

RunLoop官方文档中,可以看到RunLoop和线程的关系

RunLoop运行

​ 图中展现了RunLoop 在线程中的作用:从 input sources 和 timer sources 接受事件,然后在线程中处理事件。

三、RunLoop对象

1、获取RunLoop对象:

​ iOS中有两套来访问和使用RunLoop。

​ 1)Foundation框架:NSRunLoop,NSRunLoop是基于CFRunLoopRef的OC的封装;

[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

​ 2)CoreFoundation框架:CFRunLoopRef;

CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
2、CFRunLoopModeRef-运行模式:

​ Mode模式可以看做事件的管家,一个Mode管理着各种事件,在CFRunLoop.c文件中结构:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name; //mode名称
    Boolean _stopped; //mode是否被终止
    char _padding[3];
    CFMutableSetRef _sources0; //sources0 触摸事件处理,performSelector:onThread:
    CFMutableSetRef _sources1; //sources1 基于port的线程通信,系统事件捕捉
    CFMutableArrayRef _observers; //观察者 用于监听RunLoop状态,UI刷新,AutoreleasePool
    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 */
};

一个CFRunLoopMode对象有一个name,若干个sources0、sources1、observers、timers和ports,可见事件都是由Mode在管理,而RunLoop管理Mode。RunLoop运行时总是指定一种Mode,就是currentMode,当切换Mode时必须退出当前Mode,重新进入RunLoop。
RunLoop和Mode关系

在iOS中五种模式如下图:
RunLoopMode

注意:实际开发中有三个使用

​ 1)NSDefaultRunLoopMode:这个是默认模式,使用最多;

​ 2)UITrackingRunLoopMode:界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他 Mode 影响;

​ 3)NSRunLoopCommonModes:这个不是一种模式,默认包括NSDefaultRunLoopMode和UITrackingRunLoopMode;

比如常用定时器代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建tableView
    [self tableView];
    // 创建一个定时器,并把它放在当前的RunLoop上,模式是NSDefaultRunLoopMode
//    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
    
    // 创建一个定时器,但是必须手动把它添加到RunLoop中,添加的时候可以指定Mode
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
    // 默认模式,运行执行timerAction,当滑动tableView时,timerAction就不会执行
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    
    // 通用模式组合,运行执行timerAction,当滑动tableView时,timerAction依然继续执行
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    // UI追踪模式,此时默认不执行timerAction,当滑动tableView时,才会执行timerAction
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:UITrackingRunLoopMode];
}

四、RunLoop运行逻辑

1、RunLoop运行逻辑

在CFRunLoop.c文件中可以找到CFRunLoopRun()、CFRunLoopRunInMode()、CFRunLoopRunSpecific等函数。

// 用DefaultMode启动
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

RunLoop运行逻辑图

如上图所示,RunLoop大概运行逻辑是:

1、通知Observers:即将进入Loop,kCFRunLoopEntry;

2、通知Observers:即将处理Timers,kCFRunLoopBeforeTimers;

3、通知Observers:即将处理Sources,kCFRunLoopBeforeSources;

4、处理blocks和source0,_CFRunLoopDoBlocks和__CFRunLoopDoSources0;

5、如果有source1,跳转到第9步;

6、通知Observer:线程即将休眠,kCFRunLoopBeforeWaiting;

7、休眠,等待唤醒,等待Timer或者source1事件或者被手动唤醒,__CFRunLoopServiceMachPort;

8、通知Observer:线程刚被唤醒,kCFRunLoopAfterWaiting;

9、处理唤醒时收到的消息,之后跳转到第2步,_ CFRunLoopDoTimers、_CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE和__CFRunLoopDoSource1;

10、通知Observer:即将退出Loop,kCFRunLoopExit;

2、RunLoop状态
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),               // 即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),  // 即将处理Timers
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Sources
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),  // 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),          // 即将退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU 
};
3、RunLoop各个事件的作用

1)Source0

  • performSelector:onThread:;

2)Source1

  • 基于Port的线程间通信;
  • 系统事件捕捉,事件响应(严格来说,Source0也参与了事件响应);

3)Timers

  • NSTimer;
  • performSelector:withObject:afterDelay:;

4)Observers

  • 用于监听RunLoop的状态;
  • UI刷新(BeforeWaiting);
  • Autorelease pool(BeforeWaiting);

五、RunLoop在系统的应用

通过打印主线程RunLoop,可以看到,系统默认注册了5个Mode:

  • kCFRunLoopDefaultMode:App的默认 Mode,通常主线程是在这个 Mode 下运行的。
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
  • GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到。
  • kCFRunLoopCommonModes:这是一个占位的 Mode,没有实际作用。
  • UIInitializationRunLoopMode:刚启动 App 时进入的第一个 Mode,启动完成后就不再使用。这个Mode实际测试中没找到

1)AutoreleasePool:App启动后,苹果在主线程 RunLoop 里注册了两个 Observer。

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

2)事件响应:苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。

当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件,Source1 接收IOHIDEvent,之后再回调__IOHIDEventSystemClientQueueCallback()内触发的Source0,Source0再触发的 _UIApplicationHandleEventQueue()

_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin /Move /End /Cancel 事件都是在这个回调中完成的。

3)手势识别:当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。

苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

4)界面更新:当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。

苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数,这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

5)NSTimer、CADisplayLink:都使用到了RunLoop,并受到RunLoop的影响。想知道更多细节,请移步-NSTimer、GCD定时器、CADisplayLink详细分析

6)PerformSelecter:当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

7)关于GCD:当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并执行该block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的

六、RunLoop项目应用

1、解决NSTimer在滑动时停止工作的问题

​如上讲述,创建timer后,将它放进当前的RunLoop,Mode格式为NSRunLoopCommonModes,NSRunLoopCommonModes包含NSDefaultRunLoopMode(没有触摸事件)和UITrackingRunLoopMode(有触摸事件),所以不管有没有滑动事件都会执行timer。NSTimer有个属性tolerance容忍度,NSTimer依赖于RunLoop,所以执行timer的时间不会非常准确,使用的时候需要注意这点。

如果想知道NSTimer、GCD定时器、CADisplayLink更加详细分析,请移步-NSTimer、GCD定时器、CADisplayLink详细分析

2、控制线程生命周期(线程保活)

​ 有时候我们需要常驻线程来处理频繁的事务,比如早期的AFNetworking创建一个常驻线程处理网络事务,比如监测网络状态等。

默认情况一个线程创建出来,运行完要做的事情,线程就会消亡。而程序启动的时候,创建的主线程已经加入到mainRunLoop中,所以主线程不会消亡。

线程保活代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"mainThread = %@", [NSThread mainThread]);
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
    self.thread = thread;
    thread.name = @"KeepThread";
    [self.thread start];
}

// 子线程运行
- (void)runThread {
    NSLog(@"runThread = %@", [NSThread currentThread]);
    // 给runloop添加一个NSPort,就是添加一个事件源,也可以添加一个timer,或者observer,让runloop不会挂掉
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"-----runThread end-----"); //不会执行
}

// 测试线程保活
- (void)testThread {
    NSLog(@"testThread = %@", [NSThread currentThread]);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self performSelector:@selector(testThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

点击屏幕触发performSelector,打印结果:

mainThread = <NSThread: 0x600001488680>{number = 1, name = main} //主线程
runThread = <NSThread: 0x6000014f9100>{number = 5, name = KeepThread} //运行子线程
testThread = <NSThread: 0x6000014f9100>{number = 5, name = KeepThread} //子线程保活
3、性能优化

​ 1)tableView图片显示优化:由于图片渲染到屏幕需要消耗较多资源,为了提高用户体验,当用户滚动Tableview的时候,只在后台下载图片,但是不显示图片,当用户停下来的时候才显示图片。

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName"] afterDelay:1.0 inModes:@[NSDefaultRunLoopMode]];

​ 2)AsyncDisplayKit:AsyncDisplayKit 是 Facebook 推出的用于保持界面流畅性的框架(最新的改为Texture),其原理大致如下:

​ UI 线程中一旦出现繁重的任务就会导致界面卡顿,这类任务通常分为3类:排版,绘制,UI对象操作。排版通常包括计算视图大小、计算文本高度、重新计算子式图的排版等操作。绘制一般有文本绘制 (例如 CoreText)、图片绘制 (例如预先解压)、元素绘制 (Quartz)等操作。UI对象操作通常包括 UIView/CALayer 等 UI 对象的创建、设置属性和销毁。

​ 其中前两类操作可以通过各种方法扔到后台线程执行,而最后一类操作只能在主线程完成,并且有时后面的操作需要依赖前面操作的结果 (例如TextView创建时可能需要提前计算出文本的大小)。ASDK 所做的,就是尽量将能放入后台的任务放入后台,不能的则尽量推迟 (例如视图的创建、属性的调整)。

​ 为此,ASDK 创建了一个名为 ASDisplayNode 的对象,并在内部封装了 UIView/CALayer,它具有和 UIView/CALayer 相似的属性,例如 frame、backgroundColor等。所有这些属性都可以在后台线程更改,开发者可以只通过 Node 来操作其内部的 UIView/CALayer,这样就可以将排版和绘制放入了后台线程。但是无论怎么操作,这些属性总需要在某个时刻同步到主线程的 UIView/CALayer 去。

​ ASDK 仿照QuartzCore/UIKit 框架的模式,实现了一套类似的界面更新的机制:即在主线程的 RunLoop 中添加一个Observer,监听了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。

​ 3)第二条是Facebook写好的框架,下面自己手动写下计算cell的预缓存高度的伪代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CFRunLoopRef currentLoop = CFRunLoopGetCurrent(); //获取当前的RunLoop
    CFStringRef mode = kCFRunLoopDefaultMode; //mode
    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
        // 在RunLoop处于“空闲”状态时进行计算
        NSLog(@"Thread = %@", [NSThread currentThread]);
        sleep(1);
        CFRunLoopRemoveObserver(currentLoop, observer, mode); //移除observer
    });
    CFRunLoopAddObserver(currentLoop, observer, mode); //添加observer
}

觉得写的不错,有些启发或帮助,点个赞哦!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • Runloop是iOS和OSX开发中非常基础的一个概念,从概念开始学习。 RunLoop的概念 -般说,一个线程一...
    小猫仔阅读 993评论 0 1
  • 概述 RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时也是很多常见技术的幕后功臣。尽管在平时多...
    sumrain_cloud阅读 946评论 0 5
  • RunLoop 苹果是如何利用RunLoop实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的,今天我们就了解一...
    AKyS佐毅阅读 810评论 0 23
  • RunLoop 前言 RunLoop是iOS/OS开发中比较基础的一个概念,在苹果开发中用在事件处理,延迟加载,屏...
    etund阅读 2,839评论 6 20
  • 一、概述 一般来说,一个线程只能执行一个任务,执行完就会退出,如果我们需要一种机制,让线程能随时处理时间但并不退出...
    一直在路上66阅读 287评论 0 0