IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> iOS - RunLoop简介 -> 正文阅读

[移动开发]iOS - RunLoop简介


小记一下,没有整理

RunLoop简介

  • RunLoop:顾名思义,运行循环,在程序运行过程中循环做一些事情
  • 应用范畴:定时器,GCD,事件响应,手势识别,界面刷新,网络请求,AutoreleasePool
  • 如果没有RunLoop,程序执行完之后会直接退出
  • 如果有了RunLoop,程序并不会马上退出,而是保持运行状态
  • RunLoop的基本作用:保持程序的持续运行,处理APP中各种事件(触摸事件,定时器事件),节省CPU资源,提高程序性能。
  • 伪代码(main函数里RunLoop的循环伪代码)
    在这里插入图片描述

RunLoop也是一个对象(NSRunLoop,CFRunLoopRef(NSRunLoop是基于CFRunLoopRef(C语言)的一层OC包装)
访问RunLoop对象
在这里插入图片描述
在这里插入图片描述

而且它们都调用了_CFRunLoopGet0这个函数

//全局的Dictionary,key是pthread_t,value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问__CFRunLoops的锁
static CFSpinLock_t loopsLock = CFSpinLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//t==0是始终有效的“主线程”的同义词

//获取pthread对应的RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    //pthread为空时,获取主线程
    t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        //第一次进入时,创建一个临时字典dict
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    //根据传入的主线程获取主线程对应的RunLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    //保存主线程,将主线程-key和RunLoop-Value保存到字典中
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    //此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoops
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
    //释放dict
        CFRelease(dict);
    }
    //释放mainRunLoop
    CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    //以上说明,第一次进来的时候,不管是getMainRunLoop还是get子线程的runLoop,主线程的runLoop总是会被创建

	//从全局字典里获取对应的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
    //如果取不到,就创建一个新的RunLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    //创建好之后,以线程为key,runLoop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runLoop
    if (!loop) {
    //把newLoop存入字典__CFRunLoops,key是线程t
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
    }

	//如果传入线程就是当前线程
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
        //注册一个回调,当线程销毁时,销毁对应的RunLoop
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}


在这里插入图片描述
(从学姐那里盗图)
通过这段源码我们可以看到

每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取线程的RunLoop时创建,RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

RunLoop与线程的关系

在这里插入图片描述

RunLoop相关的类

(关于这五个类的具体描述去看这里吧!
Core Foundation中关于RunLoop的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef
    在这里插入图片描述

在这里插入图片描述

通过 mode name 来操作内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。
在这里插入图片描述

这张图很好,在RunLoop里面有很多Mode,正在用的那个叫currentMode(在这些Mode中的且只有一个)
对里面的source和observe进行处理
这里有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。

同时苹果还提供了一个操作 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用这个字符串来操作 Common Items,或标记一个 Mode 为 “Common”。使用时注意区分这个字符串和其他 mode name。

注意: NSTimer在添加到runloop中的时候要用NSRunLoopCommonModes作为参数。
理由:并且NSTimer创建好之后默认是加入名为kCFRunLoopDefaultMode的model中,所以只有应用程序在kCFRunLoopDefaultMode下,Timer才会工作。如果这个时候滑动了屏幕,那么应用的mode就从名字为kCFRunLoopDefaultMode的mode中退出,进入到名称为UITrackingRunLoopMode的model中。因为NSTimer没有添加到名字为UITrackingRunLoopMode的item中,所以只有等到不再滑动,即回到kCFRunLoopDefaultMode的时候才再次开始工作。通过NSRunLoopCommonModes标记之后,Runloop会把NSTimer加入到Runloop中的commonModeItems中。上面讲过RunLoop 都会自动将 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Model里。

在这里插入图片描述

CFRunLoopModeRef

可以保证模式之间是相互隔离的,互不影响。使运行更加流畅。
在这里插入图片描述

两种常见的Mode

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行

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

苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode。

系统默认注册了5个Mode:

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

RunLoop的运行逻辑

就是处理Mode中的四个(timers,source1,source2,Observers)
在这里插入图片描述

RunLoop的几种状态

在这里插入图片描述

添加Observer监听RunLoop的所有状态

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

RunLoop的内部实现运行逻辑

内部循环睡觉唤醒又睡觉
UI界面的刷新,定时器的处理,点击事件最终都是由RunLoop控制处理的。
RunLoop入口源码
通过打断点。我们猜测RunLoop最先进来的是CFRunLoopRunSpecific这个函数
在这里插入图片描述

在这里插入图片描述


//CFRunLoopRunSpecific源码精简版
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {  
	//通知Observers:进入Loop
	__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
	//具体要做的事情
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
	//通知Observers:退出Loop
	__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

//__CFRunLoopRun的详细过程
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    do {
    //通知Observers:即将处理Timers
         __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //通知Observers:即将处理Sources
         __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理Blocks
	__CFRunLoopDoBlocks(rl, rlm);

		//处理Source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
        	//处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
	}

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

		//判断有无Source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            //如果有Source1,就跳转到handle_msg
                goto handle_msg;
            }

       
	//通知Observers:即将休眠
	__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	__CFRunLoopSetSleeping(rl);
	
	//等待别的消息来唤醒当前线程(不唤醒就不会往下执行了)
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
	__CFRunLoopUnsetSleeping(rl);
	//通知Observers:即将休眠结束
	__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        handle_msg:
       
       if (被timer唤醒) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            //处理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
       }else if (被GCD唤醒) {
            //处理GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {//被Source1唤醒
        //处理Source1    
		__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		}
       
       //处理Blocks 
	__CFRunLoopDoBlocks(rl, rlm);
        

	//设置返回值
	if (sourceHandledThisLoop && stopAfterHandle) {
	    retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
	} else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
	    retVal = kCFRunLoopRunStopped;
	} else if (rlm->_stopped) {
	    rlm->_stopped = false;
	    retVal = kCFRunLoopRunStopped;
	} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
	    retVal = kCFRunLoopRunFinished;
	}
     
    } while (0 == retVal);

    return retVal;
}


调用细节

在这些过程中,有一个调用细节,就是这些过程中通常都会调用一个__CFRUNLOOP_IS_CALLING_OUT_TO开头的函数来处理Observer事件(UI之类的)

__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

验证:
在这里插入图片描述

GCD的很多流程是不依赖于RunLoop的,不过GCD有一种情况回交给RunLoop去处理
也是调用一个很长的函数来交给RunLoop

在这里插入图片描述

总结:
在这里插入图片描述

关于休眠的细节

等待唤醒的时候,是真正的休眠,不会占用CPU内存
但是如果是普通的循环阻塞,就会占用内存空间。(没有切换到内核态,仍然在用户态)

//等待唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

在等待唤醒的这个__CFRunLoopServiceMachPort函数中,有调用一个mach_msg,达到休眠的目的
内核层面API(mach_msg) / 应用层面API(搭网络请求)
在这里插入图片描述

RunLoop响应用户操作的具体流程

  1. Sounce1系统事件捕捉放到队列中
  2. 下一步由Source0处理

RunLoop在实际开发中的应用

  • 线程保活
  • 解决NSTimer在滑动时停止工作的问题
  • 监控应用卡顿
  • 性能卡顿

利用RunLoop解决NSTimer问题

在这里插入图片描述
创建一个NSTimer,是NSRunLoopCommonModes的Mode模式
在NSRunLoopCommonModes模式是存储在commonModesItems中,它里面包含有Default和Track两种状态。这样即便在另一个线程中,timer也不会停。
timer本来是在modes里面的timer里面。当它是NSRunLoopCommonModes模式时,就存储在commonModesItems中了。
在RunLoop运行过程中,是会通知Observer处理Timer的。

线程保活

比如AFNetworking
默认情况在子线程执行任务,执行结束后就会dealloc
在这里插入图片描述
RunLoop成功阻塞了子线程,一直没有打印end,处于了休眠状态

再举一个例子,我们看线程保活后再调用子线程执行任务的话,是不是仍然是之前RunLoop阻塞的线程被唤醒了。是的!

//子线程执行的任务
- (void)test {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

//线程保活
- (void)run {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    
    //往RunLoop里面添加Source/Timer/Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    //启动RunLoop保命,线程保活
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"%s end -----", __func__);
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //子线程执行的任务
    //调用子线程。没有必要用GCD,因为不是刚刚的子线程   YES执行完子线程再打印123,NO边打印子线程边 往下走
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    NSLog(@"123");
}

2021-08-12 08:45:05.043421+0800 RunLoop[27559:1728622] -[ViewController run] <MHThread: 0x600001dc22c0>{number = 6, name = (null)}
2021-08-12 08:45:07.630941+0800 RunLoop[27559:1728548] 123
2021-08-12 08:45:07.698299+0800 RunLoop[27559:1728622] -[ViewController test] <MHThread: 0x600001dc22c0>{number = 6, name = (null)}
2021-08-12 08:45:08.830087+0800 RunLoop[27559:1728548] 123
2021-08-12 08:45:08.830753+0800 RunLoop[27559:1728622] -[ViewController test] <MHThread: 0x600001dc22c0>{number = 6, name = (null)}

可以看到点击后调用的子线程就是RunLoop之前阻塞现在休眠了的子线程,它现在被唤醒了。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-13 12:13:24  更:2021-08-13 12:15:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 0:08:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码