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

一般来说,一个线程一次只能执行一个任务,执行完成后线程就会退出。就比如之前学OC时使用的命令行程序,执行完程序就结束了。
而runloop,运行循环,可使线程能随时处理时间但并不退出,这也就是手机app在运行时不会退出的原因。当没有事件时,runloop会进入休眠状态,有事件发生时,runloop再进行相应的处理事件。runloop可以让线程在需要做事的时候忙起来,不需要的时候让线程休眠。

runloop基本作用

  • 保持程序的持续运行
  • 处理app中各种事件
  • 节省CPU资源,提高程序性能:该做事时做事,该休眠时休眠。休眠时不占用CPU

runloop实际上是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行下面图片中的逻辑。线程执行了这个函数后,就会处于这个函数内部的循环中,直到循环结束,函数返回。
在这里插入图片描述

获取runloop

iOS中有2套API来访问和使用runloop

  • Foundation:NSRunLoop
  • Core Foundation:CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层oc封装
在这里插入图片描述

苹果不允许直接创建runloop对象,可通过下面的方法获取runloop对象。

    //获取主线程的runloop
    NSRunLoop *runloop1 = [NSRunLoop mainRunLoop];
    CFRunLoopRef runloop2 = CFRunLoopGetMain();
    
    //获取当前线程的runloop对象
    NSRunLoop *runloop3 = [NSRunLoop currentRunLoop];
    CFRunLoopRef runloop4 = CFRunLoopGetCurrent();

下面的打印显示,虽然两种runloop打印的地址不一样,但是33行打印NSRunLoop时显示的地址却是CFRunLoop,也就说明了两种RunLoop之间的关系就如同上面图片一样,NSRunLoop是对CFRunLoop的一层oc封装。
在这里插入图片描述

CFRunLoopGetMain、CFRunLoopGetCurrent的实现如下

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

这两个方法其实都是调用了_CFRunLoopGet0,_CFRunLoopGet0的实现如下


//全局的Dictionary,key为线程,value是runloop
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问loopsDic 时的锁
//锁的目的是:考虑线程安全问题,防止多条线程同时访问 __CFRunLoop对应的内存空间
static CFLock_t loopsLock = CFLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works

//获取一个线程对应的runloop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //线程t为空时,获取主线程赋给t
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    
    //如果字典为空时,也就是第一次进入
    if (!__CFRunLoops) {
        __CFUnlock(&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);
        //把临时字典dict赋值给全局字典中
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        	//赋值成功,释放dict
            CFRelease(dict);
        }
        //存储mainLoop到字典后,撤销一次mainloop的引用,因为mainLoop存储到字典后会自动retain
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    
    //从字典中根据线程(key)获取对应的runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    //如果没有取到,说明字典中没有对应的loop,获取不到就创建并存储
    //在子线程中第一次获取RunLoop是获取不到的,然后才去创建,这个if语句一般在子线程中第一次获取当前runloop才会进去执行
    if (!loop) {
        //创建一个新的runloop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    	//再一次从全局的字典 __CFRunLoops中获取对应于当前线程的RunLoop
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        //如果还是获取不到RunLoop,就存储当前线程和刚创建的RunLoop到全局字典 __CFRunLoops
        if (!loop) {
            //将新的runloop(value)和线程(key)存入全局字典中
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            //将新的runloop赋给loop
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
        //release局部的newloop
        CFRelease(newLoop);
    }
    //判断t是否为当前线程,如果是就注册一个回调,当线程销毁的时候,也销毁其对应的RunLoop
    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);
        }
    }
    //返回当前线程对应的runloop
    return loop;
}

  • 线程与runloop之间是一一对应的,保存在一个全局的字典中,线程作为key,runloop作为value
  • 线程刚创建时并没有runloop,如果不主动获取,那这个线程就一直不会有。
  • runloop的创建发生在是第一次获取时,runloop的销毁是发生在线程结束时

主线程的runloop在程序运行启动时就会启动
在main.m函数中,通过UIApplicationMain开启主线程的runloop
在这里插入图片描述

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结构也可以简化如下(图的右半部分)
在这里插入图片描述
一个runloop对象中,主要包含了一个线程,若干个mode,若干个commonMode,还有一个当前运行的mode

  • CFRunLoopModeRef代表runloop的运行模式
  • 一个RunLoop包含若干个Mode,每一个Mode又包含若干个Source0/Source1/Timer/Observer(下面有Mode的结构)
  • RunLoop启动时只能选择其中一个Mode,作为currentMode
  • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
    Mode切换不会导致程序退出
    不同mode中的Source0/Source1/Timer/Observer能分隔开来,互不影响
  • 如果Mode中没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

关于上图中的5个类

  • CFRunLoopRef:代表了RunLoop的对象(RunLoop)
  • CFRunLoopModeRef:RunLoop的运行模式(Mode)
  • CFRunLoopSourceRef:RunLoop模型图中的输入源/事件源(Source)
  • CFRunLoopTimerRef:RunLoop模型图中的定时源(Timer)
  • CFRunLoopObserverRef:观察者,能够监听RunLoop的状态变化

CFRunLoopModeRef

CFRunLoopModeRef的结构简化如下

struct __CFRunLoopMode {
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

Mode实际上是Source,Timer 和 Observer 的集合,不同的Mode把不同组的Source、timer、Observer隔绝开来。runloop在某一时刻只能运行在一个mode下,处理这一个mode中的source、timer、observer。

五种Mode

  • kCFDefaultRunLoopMode:App默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
  • UIInitializationRunLoopMode:在刚启动App时第一个mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
  • GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
  • KCFRunLoopCommonModes:并不是一种模式
    只是一个标记,当mode标记为common时,将mode添加到runloop中的_commonModes中。runloop中的_commonModes实际上是一个Mode的集合,可使用CFRunLoopAddCommonMode()将Mode放到CommonModes中。
    每当RunLoop的内容发生变化时,RunLoop都会将_commonModeItems里的同步到具有Common标记的所有的Mode里。

RunLoop中包含多个Mode,但只有一个作为当前Mode
在这里插入图片描述

对于CGRunLoopModeRef,常见的两种mode

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
  • UItrackingRunLoopMode:界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响

Mode中的Source、Observer、Timer三种,他们统称为ModeItem。
下面分别介绍对应的类

CFRunLoopSourceRef

根据官方描述,CFRunLoopSourceRef是input sources的抽象。
CFRunLoopSource分为Source0和Source2两个版本。
它的结构如下


struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits; 	//用于标记Signaled状态,source0只有在被标记为Signaled状态才会被处理
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
	CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};

Source0是App内部事件,由App自己管理的UIEvent、CFSocket都是source0。当一个source0事件准备执行时,必须要先把它标为signal状态,以下是source0结构体

typedef struct {
    CFIndex	version;
    void *	info;
    const void *(*retain)(const void *info);
    void	(*release)(const void *info);
    CFStringRef	(*copyDescription)(const void *info);
    Boolean	(*equal)(const void *info1, const void *info2);
    CFHashCode	(*hash)(const void *info);
    void	(*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*perform)(void *info);
} CFRunLoopSourceContext;

source1结构体

typedef struct {
    CFIndex	version;
    void *	info;
    const void *(*retain)(const void *info);
    void	(*release)(const void *info);
    CFStringRef	(*copyDescription)(const void *info);
    Boolean	(*equal)(const void *info1, const void *info2);
    CFHashCode	(*hash)(const void *info);
#if TARGET_OS_OSX || TARGET_OS_IPHONE
    mach_port_t	(*getPort)(void *info);
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *	(*getPort)(void *info);
    void	(*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
  • source0是非基于Port的。只包含了一个回调(函数指针),它并不能主动触发事件。使用时要先调用 CFRunLoopSourceSignal(source),将这个source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop) 来唤醒RunLoop,让其处理这个事件
  • Source1包含了mach_port和一个回调(函数指针),Source1可以监听系统端口,通过内核和其他线程通信,接收、分发系统事件,他能主动唤醒RunLoop(由操作系统内核进行管理)

CFRunLoopTimerRef

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;                 //标记fire状态
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;      //添加该Timer的runloop
    CFMutableSetRef _rlModes;   //存放所有 包含该timer的 mode的 modeName,意味着一个timer可以在多个mode中存在
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;	 //理想时间间隔	/* immutable */
    CFTimeInterval _tolerance;   //时间偏差       /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};
  • CFRunLoopTimer是基于时间的触发器,其包含一个时间长度、一个回调(函数指针)。当其加入runloop时,runloop会注册对应的时间点,当时间点到时,runloop会被唤醒以执行那个回调
  • CFRunLoopTimer和NSTimer是toll-free bridged(对象桥接),可以相互转换

CFRunLoopObserverRef

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;   //添加了该observer的runloop
    CFIndex _rlCount;
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;			/* immutable */
    CFRunLoopObserverCallBack _callout;	/* immutable */  //设置回调函数,回调指针
    CFRunLoopObserverContext _context;	/* immutable, except invalidation */
};

CFRunLoopObserverRef是观察者可以观察Runloop的各种状态,每个Observer都包含了一个回调(函数指针),当runloop的状态发生变化时,观察者就能通过回调接收到这个变化。

RunLoop的状态有6种 如下

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),         //即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),  //即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),  //刚从休眠中唤醒但还没开始处理事件
    kCFRunLoopExit = (1UL << 7),		  //即将退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU //所有状态
};

创建监听RunLoop的observer(其中一个方法,比较简单)
在这里插入图片描述

当我们父视图上创建一个ScrollerView,创建Observer监听runloop。
滑动ScrollerView从开始到结束的过程中,runloop的mode切换以及runloop状态(仅进入runloop、退出runloop状态)如下

在这里插入图片描述

以上的Source、Timer、Observer被统称为mode item,一个item可以被同时加入多个Mode中,但一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,则runloop会直接退出。

RunLoop的内部逻辑

在这里插入图片描述

//用DefaultMode启动
void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}

//用指定的Mode启动,允许设置RunLoop的超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

//RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
	//首先根据modeName找到对应的mode
	CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
	//如果mode中没有source/timer/observer,直接返回
	if (__CFRunLoopModeIsEmpty(currentMode)) return;
	
	//1.通知Observers:RunLoop即将进入loop
	__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

	//调用函数__CFRunLoopRun 进入loop
	__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
        
        Boolean sourceHandledThisLoop = NO;
        int retVal = 0;
        do {
			//2.通知Observers:RunLoop即将触发Timer回调
			__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
			//3.通知Observers:RunLoop即将触发Source0(非port)回调
			__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
			///执行被加入的block
			__CFRunLoopDoBlocks(runloop, currentMode);

			//4.RunLoop触发Source0(非port)回调
			sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
			//执行被加入的Block
			__CFRunLoopDoBlocks(runloop, currentMode);

			//5.如果有Source1(基于port)处于ready状态,直接处理这个Source1然后跳转去处理消息
			if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) goto handle_msg;
            }

			//6.通知Observers:RunLoop的线程即将进入休眠
			if (!sourceHandledThisLoop) {
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
            }

			//7.调用mach_msg等待接收mach_port的消息。线程将进入休眠,直到被下面某个事件唤醒
			// 一个基于port的Source的事件
			//一个Timer时间到了
			//RunLoop自身的超时时间到了
			//被其他什么调用者手动唤醒
			__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
            }

			//8.通知Observers:RunLoop的线程刚刚被唤醒
			__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

			//收到消息,处理消息
			handle_msg:
			//9.1 如果一个Timer时间到了,触发这个timer的回调
			if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            } 
			//9.2 如果有dispatch到main_queue的block,执行block
			else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 
            //9.3 如果一个Source1(基于port)发出事件了,处理这个事件
            else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                if (sourceHandledThisLoop) {
                    mach_msg(reply, MACH_SEND_MSG, reply);
                }
            }
            //执行加入到loop的block
            __CFRunLoopDoBlocks(runloop, currentMode);


			if (sourceHandledThisLoop && stopAfterHandle) {
                /// 进入loop时参数说处理完事件就返回。
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                /// 超出传入参数标记的超时时间了
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) {
                /// 被外部调用者强制停止了
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                /// source/timer/observer一个都没有了
                retVal = kCFRunLoopRunFinished;
            }

			// 如果没超时,mode里没空,loop也没被停止,那继续loop。
	 	} while (retVal == 0);
    }

	//10. 通知Observers:RunLoop即将退出
	__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

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

RunLoop回调

  • 当App启动时,系统会默认注册五个上面说过的5个mode
  • 当RunLoop进行回调时,一般都是通过一个很长的函数调出去(call out),当在代码中加断点调试时,通常能在调用栈上看到这些函数。这就是runloop的流程:
{
	//	1. 通知Observers,即将进入runloop
	//此处有Observer会创建AutoreleasePool:_objc_autoreleasePoolPush()
	__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
	do {
		//2. 通知Observers:即将触发Timer回调
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
		//3. 通知Observers:即将触发Source0回调
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
		
		//4.触发Source0回调
		__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
		
		//6. 通知Observers,即将进入休眠
		//此处Observer释放并新建autoreleasePool:_objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);

		//7. 休眠,等待唤醒
		mach_msg() -> mach_msg_trap();

		//8. 通知Observers,线程被唤醒
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);

		//9. 如果是Timer唤醒的,回调Timer
		__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
		//9. 如果是被dispatch唤醒的,执行所有调用dispatch_async等方法放入main queue 的block
		__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
		//9.如果runloop是被Source1(基于port)的事件唤醒,处理这个事件
		__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);


	} while (...);

	//10. 通知Observers,即将退出runloop
	//此处有Observer释放AutoreleasePool:_objc_autoreleasePoolPop();
	__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);

RunLoop休眠的实现原理
在这里插入图片描述
从用户态切换到内核态,在内核态让线程进行休眠,有消息时唤起线程,回到用户态处理消息

RunLoop在实际开发中的应用

  • 控制线程生命周期(线程保活)
  • 解决NSTimer在滑动时停止工作的问题
  • 监控应用卡顿
  • 性能优化

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

在这里插入图片描述

创建timer使用了带有scheduledTimer的方法,创建的timer是在runloop默认模式下,也就是NSDefaultRunLoopMode。
当拖动模拟机上的scrollView时,定时器就会失效,停止拖动,定时器恢复。说明定时器并不在UITrackingRunLoopMode模式(mode)下。
只需要将这个timer也添加到UITrackingRunLoopMode模式下就可以正常工作

改为这个样子就可以了
在这里插入图片描述

timer的另一种创建方法
在这里插入图片描述

线程保活

平时创建子线程时,线程上的任务执行完这个线程就会销毁掉。
有时我们会需要经常在一个子线程中执行任务,频繁的创建和销毁线程就会造成很多的开销,这时我们可以通过runloop来控制线程的生命周期

RunLoop启动方法

三种启动RunLoop的方法

  • run,无条件
  • runUntilDate, 设置时间限制
  • runMode:before:Date:,在特定模式下

对于上面三种方法,文档中的总结如下

  • 第一种方法,无条件地进入运行循环是最简单的选项,但也是最不理想的选择。无条件地运行runloop将线程放入永久循环,这使您无法控制运行循环本身。停止runloop的唯一方法是杀死它。也没有办法在自定义模式下运行循环。
  • 第二种设置了超时时间,超过这个时间runloop结束,优于第一种
  • 相对比较好的方式,可以指定runloop以哪种模式运行
    实际上run方法的实现就是无限调用runMode:before:Date:方法
    runUntilDate:也会重复调用runMode:before:Date:方法,区别在于它超时就不会再调用

RunLoop关闭方法

在处理事件之前,有两种方法可以让运行循环退出:

  • 将运行循环配置为使用超时值运行。
  • 手动停止。

这里需要注意,虽然删除runloop的输入源、定时器可能会导致运行循环的退出,但这并不是个可靠的方法,系统可能会添加一些输入源到runloop中,但在我们的代码中可能并不知道这些输入源,因此无法删除它们,导致无法退出runloop。

我们可以通过2、3方法来启动runloop,设置超时时间。但是如果需要对这个线程和它的RunLoop有最精确的控制,而并不是依赖超时机制,这时我们可以通过 CFRunLoopStop() 方法来手动结束一个 RunLoop。
但是 CFRunLoopStop() 方法只会结束当前的runMode:beforeDate: 调用,而不会结束后续的调用

在下面的代码中,因为runMode:beforeDate:方法是单次调用,我们需要给它加上一个循环,否则调用一次runloop就结束了,和不使用runloop的效果一样

这个循环的条件默认设置成yes,当调用stop方法中,执行CFRunLoopStop() 方法结束本次runMode:beforeDate:,同时将循环中的条件设置为NO,使循环停止,runloop退出。

#import "SecondViewController.h"
#import "FTThread.h"


@interface SecondViewController ()

@property (nonatomic, strong) FTThread *thread;  //继承NSThread
@property (nonatomic, assign) BOOL stopped;

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    
    self.view.backgroundColor = [UIColor greenColor];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:button];
    [button addTarget:self action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"执行任务" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 200, 100, 20);
    
    
    UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:stopButton];
    [stopButton addTarget:self action:@selector(pressStop) forControlEvents:UIControlEventTouchUpInside];
    [stopButton setTitle:@"停止RunLoop" forState:UIControlStateNormal];
    stopButton.frame = CGRectMake(100, 400, 100, 20);
    
    self.stopped = NO;
    //防止循环引用
    __weak typeof(self) weakSelf = self;
    
    self.thread = [[FTThread alloc] initWithBlock:^{
        NSLog(@"ft新线程");
        
        //向当前runloop添加Modeitem,添加timer、observer都可以。因为如果mode没有item,runloop就会退出
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"end");
        
    }];
    
    
    [self.thread start];
    
}

- (void)pressPrint {
    //子线程中调用print
    [self performSelector:@selector(print) onThread:_thread withObject:nil waitUntilDone:NO];
}

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

- (void)pressStop {
    
    //子线程中调用stop
    if (_stopped == NO ) {
        [self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];
    }
    
}

//停止子线程的runloop
- (void)stop {
	//设置标记yes
    self.stopped = YES;
    
    //停止runloop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
    
    //解除引用, 停止runloop这个子线程就会dealloc
    self.thread = nil;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //退出当前页面
    
    //保证这个vc销毁时,子线程也要销毁
    [self pressStop];
    [self dismissViewControllerAnimated:YES completion:nil];
    
     
}

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

@end

参考
线程保活

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-14 14:11:29  更:2021-08-14 14:12:03 
 
开发: 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/18 22:49:06-

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