iOS--Runloop

Runloop概述

一般来说,一个线程一次只能执行一个任务,执行完成后线程就会退出。就比如之前学OC时使用的命令行程序,执行完程序就结束了。
而runloop目的就是使线程在执行完一次代码之后不会结束程序,而是使该线程处于一种休眠的状态,等待有事件需要处理的时候,再醒来处理。

简单的来说,runloop可以让线程在需要做事的时候忙起来,不需要的时候让线程休眠,使程序不会结束

Runloop基本作用

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

Runloop伪代码

int main(int argc, char *argv[]) {@atuoreleasepool {int retVal = 0;do {// 睡眠中等待消息int message = sleep_and_wait();// 处理消息retVal = process_message(message);} while (0 == retVal);return 0;}
}

Runloop会一直在do-while循环中执行,这也就是我们写的程序不会在执行完一次代码之后就退出的原因了。

Runloop模型图

看一下苹果官方给出的RunLoop模型图
在这里插入图片描述

Runloop对象

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

Runloop对象的获取

Runloop对象主要有两种获取方式:

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

NSRunloop类是Fundation框架中Runloop的对象,并且NSRunLoop是基于CFRunLoopRef的封装,提供了面向对象的API,但是这些API不是线程安全的。

// Core Foundation
CFRunLoopRef runloop = CFRunLoopGetCurrent(); // 获得当前RunLoop对象
CFRunLoopRef runloop = CFRunLoopGetMain(); // 获得主线程的RunLoop对象

CFRunLoopRef类是CoreFoundation框架中Runloop的对象,并且其提供了纯C语言函数的API,所有这些API都是线程安全

在这里插入图片描述
看一下CoreFoundation框架中这两个函数的具体实现:

CFRunLoopRef CFRunLoopGetCurrent(void) {CHECK_FOR_FORK();CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if (rl) return rl;return _CFRunLoopGet0(pthread_self());
}CFRunLoopRef CFRunLoopGetMain(void) {CHECK_FOR_FORK();static CFRunLoopRef __main = NULL; // no retain neededif (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS neededreturn __main;
}

发现其都调用了_CFRunLoopGet0这个方法,顺便就来看看

_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);//如果这个__CFRunLoops字典不存在,即程序刚开始运行if (!__CFRunLoops) {__CFSpinUnlock(&loopsLock);//第一次进入时,创建一个临时字典dictCFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);//根据传入的主线程,获取主线程对应的RunLoopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());//保存主线程的Runloop,将主线程-key和RunLoop-Value保存到字典中CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);//此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoopsif (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {//释放dict,因为我们已经将dict的内存保存了,该临时变量也就没用了,要及时的释放掉CFRelease(dict);}//释放mainRunLoop,刚才用于获取主线程的Runloop,已经保存了,就可以释放了CFRelease(mainLoop);__CFSpinLock(&loopsLock);}//以上说明,第一次进来的时候,不管是getMainRunLoop还是get子线程的runLoop,主线程的runLoop总是会被创建//从全局字典里获取对应的RunLoopCFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));__CFSpinUnlock(&loopsLock);//如果找不到对应的Runloopif (!loop) {//创建一个该线程的RunloopCFRunLoopRef newLoop = __CFRunLoopCreate(t);__CFSpinLock(&loopsLock);//再次在__CFRunLoops中查找该线程的Runlooploop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));//如果在字典中还是找不到该线程的Runloopif (!loop) {//把刚创建的该线程的newLoop存入字典__CFRunLoops,key是线程tCFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);//并且让loop指向刚才创建的Runlooploop = newLoop;}// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it__CFSpinUnlock(&loopsLock);//loop已经指向这个newLoop了,他也就可以释放了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);}}//返回该线程对应的Runloopreturn loop;
}

在这里插入图片描述

(从学姐的学姐那里偷的)通过这段源码,我们可以看到:

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取线程的RunLoop时创建,RunLoop会在线程结束时销毁
  • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
  • 主线程的runloop在程序运行启动时就会启动,在main.m函数中,通过UIApplicationMain开启主线程的
    runloop:请添加图片描述

Runloop与线程的关系

在这里插入图片描述

在这里插入图片描述

CFRunLoopRef源码部分

看一下CFRunLoopRef的源码:

struct __CFRunLoop {CFRuntimeBase _base;pthread_mutex_t _lock;			/* locked for accessing mode list */__CFPort _wakeUpPort; // 使用 CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloopBoolean _unused;volatile _per_run_data *_perRunData;              // reset for runs of the run looppthread_t _pthread; 		// runloop对应的线程uint32_t _winthread;CFMutableSetRef _commonModes;  // 存储的是字符串,记录所有标记为common的modeCFMutableSetRef _commonModeItems; // 存储所有commonMode的item(source、timer、observer)CFRunLoopModeRef _currentMode; // 当前运行的modeCFMutableSetRef _modes;           // 装着一堆CFRunLoopModeRef类型struct _block_item *_blocks_head; // do blocks时用到struct _block_item *_blocks_tail;CFAbsoluteTime _runTime;CFAbsoluteTime _sleepTime;CFTypeRef _counterpart;
};

Runloop中除了记录了一些属性外,重点还是以下几个

pthread_t _pthread; 		// runloop对应的线程
CFMutableSetRef _commonModes;  // 存储的是字符串,记录所有标记为common的mode
CFMutableSetRef _commonModeItems; // 存储所有common标记的mode的item(source、timer、observer)
CFRunLoopModeRef _currentMode; // 当前运行的mode
CFMutableSetRef _modes;           // 装着一堆CFRunLoopModeRef类型,runloop中的所有模式

我的理解就是:RunLoop中主要的变量就是_pthread、_currentMode、_modes,_currentMode主要就是为了在_modes中找当前对应的mode的item,然后发送消息。而_commonModes_commonModeItems完全就是为了common标记mode准备的,如果我们选择的mode是commonMode,那么就不用在_modes中找每个mode对应的item了,因为被标记的mode的item都在_commonModeItems中,直接给他里边的所有item发消息就完了!

RunLoop的结构

请添加图片描述
请添加图片描述

Runloop相关的类

与Runloop相关的类主要有以下几个:
在这里插入图片描述

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

RunLoop机制

这些相关类就跟套娃似的,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer,这句话其实就是5个相关类的关系请添加图片描述

  • 一个RunLoop对象(CFRunLoopRef)中包含若干个运行模式(CFRunLoopModeRef)。而每一个运行模式下又包含若干个输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)、观察者(CFRunLoopObserverRef)
  • 每次RunLoop启动时,只能指定其中一个运行模式(CFRunLoopModeRef),这个运行模式(CFRunLoopModeRef)被称作CurrentMode。
  • 如果需要切换运行模式(CFRunLoopModeRef),只能退出Loop,再重新指定一个运行模式(CFRunLoopModeRef)进入。
  • 这样做主要是为了分隔开不同组的输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)、观察者(CFRunLoopObserverRef),让其互不影响

CFRunLoopModeRef类

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; // Sources0CFMutableSetRef _sources1; // Sources1CFMutableArrayRef _observers; // 观察者CFMutableArrayRef _timers; // 定时器
------------------------------------------CFMutableDictionaryRef _portToV1SourceMap;//字典    key是mach_port_t,value是CFRunLoopSourceRef__CFPortSet _portSet;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERSdispatch_source_t _timerSource;dispatch_queue_t _queue;Boolean _timerFired; // set to true by the source when a timer has firedBoolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOOmach_port_t _timerPort;Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWSDWORD _msgQMask;void (*_msgPump)(void);
#endifuint64_t _timerSoftDeadline; /* TSR */uint64_t _timerHardDeadline; /* TSR */
};

在这里插入图片描述

五种运行模式

系统默认注册的五个mode:

  • kCFRunLoopDefaultMode: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里

CommonModes

在RunLoop对象中,前面有一个有一个叫CommonModes的概念,它记录了所有标记为common的mode:

//简化版本
struct __CFRunLoop {pthread_t _pthread;CFMutableSetRef _commonModes;//存储的是字符串,记录所有标记为common的modeCFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)CFRunLoopModeRef _currentMode;//当前运行的modeCFMutableSetRef _modes;//存储的是CFRunLoopModeRef对象,不同mode类型,它的mode名字不同
};
  • 一个Mode可以将自己标记为Common属性,通过将其ModeName添加到RunLoop的commonModes中。
  • 每当RunLoop的内容发生变化时,RunLoop都会将_commonModeItems里的Source/Observer/Timer同步到具有Common标记的所有Mode里。其底层原理如下:
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {CHECK_FOR_FORK();if (__CFRunLoopIsDeallocating(rl)) return;__CFRunLoopLock(rl);if (!CFSetContainsValue(rl->_commonModes, modeName)) {//获取所有的_commonModeItemsCFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;//获取所有的_commonModesCFSetAddValue(rl->_commonModes, modeName);if (NULL != set) {CFTypeRef context[2] = {rl, modeName};//将所有的_commonModeItems逐一添加到_commonModes里的每一个ModeCFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);CFRelease(set);}} else {}__CFRunLoopUnlock(rl);
}

CFRunLoop对外暴露的管理Mode接口只有下面两个

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);

什么是Mode Item?Mode到底包含哪些类型的元素?

  • RunLoop需要处理的消息,包括time以及source消息,他们都属于Mode item
  • RunLoop也可以被监听,被监听的对象是observer对象,也属于Mode item
  • 所有的mode item都可以被添加到Mode中,Mode中可以包含多个mode item,一个item也可以被加入多个mode。但一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,则RunLoop会退出,不进入循环
  • mode暴露的mode item的接口有下面几个:
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
  • 我们仅能通过操作mode name来操作内部的mode,当你传入一个新的mode name但RunLoop内部没有对应的mode时,RunLoop会自动帮你创建对应的CFRunLoopModeRef
  • 对于一个RunLoop来说,其内部的mode只能增加不能删除

CFRunLoopSourceRef类

根据官方描述,CFRunLoopSourceRefinput sources的抽象。
CFRunLoopSource分为Source0Source1两个版本。
它的结构如下:

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

可一通过共用体union看出,它有两个版本, Source0Source1:

Source0

Source0只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。

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

Source1包含了mach_port和一个回调(函数指针),Source1可以监听系统端口,通过内核和其他线程通信,接收、分发系统事件,他能主动唤醒RunLoop(由操作系统内核进行管理)

注意:Source1在处理的时候会分发一些操作给Source0去处理。

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_IPHONEmach_port_t	(*getPort)(void *info);void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#elsevoid *	(*getPort)(void *info);void	(*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

Source1 :基于mach_Port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的RunLoop(iOS里进程间通信开发过程中我们一般不主动使用)。mach_port大家就理解成进程间相互发送消息的一种机制就好, 比如屏幕点击, 网络数据的传输都会触发sourse1。

举例说明source0和source1:
一个APP在前台静止着,此时,用户用手指点击了一下APP界面,那么过程就是下面这样的:
我们触摸屏幕,先摸到硬件(屏幕),屏幕表面的事件会先包装成Event,Event先告诉source1(mach_port),source1唤醒RunLoop,然后将事件Event分发给source0,然后由source0来处理

CFRunLoopTimerRef类

CFRunLoopTimer是基于时间的触发器,其包含一个时间长度、一个回调(函数指针)。当其加入runloop时,runloop会注册对应的时间点,当时间点到时,runloop会被唤醒以执行那个回调。
并且CFRunLoopTimerNSTimertoll-free bridged(对象桥接),可以相互转换。其结构如下:

struct __CFRunLoopTimer {CFRuntimeBase _base;uint16_t _bits;pthread_mutex_t _lock;CFRunLoopRef _runLoop;CFMutableSetRef _rlModes;//包含timer的mode集合CFAbsoluteTime _nextFireDate;CFTimeInterval _interval;        /* immutable */CFTimeInterval _tolerance;          /* mutable */uint64_t _fireTSR;            /* TSR units */CFIndex _order;            /* immutable */CFRunLoopTimerCallBack _callout; //timer的回调   CFRunLoopTimerContext _context;   //上下文对象
};

对于NSTimer

scheduledTimerWithTimeIntervalRunLoop的关系

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

系统会将NSTimer自动加入NSDefaultRunLoopMode模式中,所以它就等同于下面代码:

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

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

这是个使用NSTimer的经典问题,当时写NSTimer的时候疯狂折磨我,就是我们在定义一个定时器后,并且界面存在一个滚动视图,当我们拖动滚动视图的时候其NSTimer停止执行事件了,等到拖拽完了之后它才会继续开始执行事件。
举例如下

self.scr = [[UIScrollView alloc] init];
self.scr.frame = CGRectMake(100, 200, 100, 100);
self.scr.contentSize = CGSizeMake(300, 100);
self.scr.backgroundColor = [UIColor orangeColor];
[self.view addSubview:self.scr];static int count = 0;
// 带有 scheduledTimer 就会将定时器添加到默认模式下
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {NSLog(@"%d", ++count);
}];
  • 我们发现,当我们在拖拽的时候,定时器的事件不执行了,等我们拖拽停止的时候,它又开始运行了,所以其中隔了几秒。

造成这种问题的原因就是:

  • 当我们不做任何操作的时候,RunLoop处于NSDefaultRunLoopMode
  • 当我们进行拖拽时,RunLoop就结束NSDefaultRunLoopMode,切换到了UITrackingRunLoopMode模式下,这个模式下没有添加该NSTimer以及其事件,所以我们的NSTimer就不工作了
  • 当我们松开鼠标时候,RunLoop就结束UITrackingRunLoopMode模式,又切换回NSDefaultRunLoopMode模式,所以NSTimer就又开始正常工作了

想要解决这个问题也很简单,我们直接让NSTimer在两种mode下都能工作就完了,这就用到我们之前不太清楚其用法的NSRunLoopCommonModes了:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

当然你也可以把NSTimer分别加入到NSDefaultRunLoopModeUITrackingRunLoopMode,这两种写法是相同的,因为系统的mode是默认在_commonModes中的:

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

修改之后,我们不论再怎么拖拽,其也会正常运行了。

CFRunLoopObserverRef类

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

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {CFRuntimeBase _base;pthread_mutex_t _lock;CFRunLoopRef _runLoop;//监听的RunLoopCFIndex _rlCount;//添加该Observer的RunLoop对象个数CFOptionFlags _activities;		/* immutable */CFIndex _order;//同时间最多只能监听一个CFRunLoopObserverCallBack _callout;//监听的回调CFRunLoopObserverContext _context;//上下文用于内存管理
};

Runloop的六种状态

//观测的时间点有一下几个
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoopkCFRunLoopBeforeTimers = (1UL << 1), // 即将处理TimerkCFRunLoopBeforeSources = (1UL << 2), // 即将处理SourcekCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒kCFRunLoopExit = (1UL << 7),// 即将退出RunLoopkCFRunLoopAllActivities = 0x0FFFFFFFU
};

这六种状态都可以被observer观察到,我们也可以利用这一方法写一些特殊事件,创建监听,监听RunLoop的状态变化:

// 创建observer
CFRunLoopObserverRef ob = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {switch (activity) {case kCFRunLoopEntry:NSLog(@"kCFRunLoopEntry");break;case kCFRunLoopBeforeTimers:NSLog(@"kCFRunLoopBeforeTimers");break;case kCFRunLoopBeforeSources:NSLog(@"kCFRunLoopBeforeSources");break;case kCFRunLoopBeforeWaiting:NSLog(@"kCFRunLoopBeforeWaiting");break;case kCFRunLoopAfterWaiting:NSLog(@"kCFRunLoopAfterWaiting");break;case kCFRunLoopExit:NSLog(@"kCFRunLoopExit");break;default:break;}
});
// 添加observer到runloop中
CFRunLoopAddObserver(CFRunLoopGetMain(), ob, kCFRunLoopCommonModes);
CFRelease(ob);

程序启动之后,RunLoop是在不停的监听状态并做出反应的。

Runloop的内部逻辑

RunLoop的内部逻辑如下:
在这里插入图片描述

__CFRunLoopRun源码实现

精简后的__CFRunLoopRun函数,保留了主要代码,看一下具体实现

//用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找到对应的modeCFRunLoopModeRef 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)回调,处理Source0sourceHandledThisLoop = __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,执行blockelse 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);//设置do-while之后的返回值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循环。当你调用CFRunLoopRun()时,线程就会一直停留在这个循环里,知道超时或者被手动调用,该函数才会返回。

并且其并不只是这么简单,还有很多细节处理(判空什么的)都是在相应的方法里的。

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: 即将触发 Source (非基于port的,Source0) 回调。__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);/// 4. 触发 Source0 (非基于port的) 回调。__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. sleep to wait msg.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的杂七杂八

RunLoop在实际开发中的应用

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

RunLoop启动方法

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

RunLoop关闭方法

  • 将运行循环配置为使用超时值运行。
  • 手动停止。
    这里需要注意,虽然删除runloop的输入源和定时器可能会导致运行循环的退出,但这并不是个可靠的方法,系统可能会添加输入源到runloop中,但在我们的代码中可能并不知道这些输入源,因此无法删除它们,导致无法退出runloop。

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

ImageView延迟显示

当界面中含有UITableView,而且每个UITableViewCell里边都有图片。这是当我们滚动UITableView的时候,如果有一堆的图片需要显示,那么可能出现卡顿的情况。

如何解决这个问题?

我们应该推迟图片的实现,也就是ImageView推迟显示图片。当我们滑动时不要加载图片, 拖动结束在显示:

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

常驻线程

开发应用程序的过程中,如果后台操作十分频繁,比如后台播放音乐、下载文件等等,我们希望执行后台代码的这条线程永远常驻内存,我们可以添加一条用于常驻内存的强引用子线程,在该线程的RunLoop下添加一个Sources,开启RunLoop:

@property (nonatomic, strong) NSThread *thread;- (void)viewDidLoad {[super viewDidLoad];self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];[self.thread start];
}
- (void)runThread {NSLog(@"----run-----%@", [NSThread currentThread]);//如果不加这句,会发现runloop创建出来就挂了,因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。//下面的方法给runloop添加一个NSport,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉//方法1[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];// 方法2
//    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];// 方法3
//    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];// 方法1 ,2,3实现的效果相同,让runloop无限期运行下去[[NSRunLoop currentRunLoop] run];// 测试是否开启了RunLoop,如果开启RunLoop,则来不了这里,因为RunLoop开启了循环。NSLog(@"未开启RunLoop");
}
//我们同时在我们自己新建立的这个线程中写一下touchesBegan这个方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {// 利用performSelector,在self.thread的线程中调用runTest方法执行任务[self performSelector:@selector(runTest) onThread:self.thread withObject:nil waitUntilDone:NO];
}- (void)runTest {NSLog(@"----runTest------%@", [NSThread currentThread]);
}

我们发现线程启动RunLoop成功了,没有打印未开启RunLoop,并且通过输出线程,发现执行点击事件的也是我们创建的这个线程,这样我们就达到常驻线程的目的了,该线程self.thread一直在等待一个事件加入其中,然后执行。

线程保活

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

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

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

@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) BOOL stopped;- (void)viewDidLoad {[super viewDidLoad];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 = [[NSThread alloc] initWithBlock:^{NSLog(@"Thread---begin");//向当前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(@"Thread---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 {//子线程中调用stopif (_stopped == NO ) {[self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];}}//停止子线程的runloop
- (void)stop {//设置标记yesself.stopped = YES;//停止runloopCFRunLoopStop(CFRunLoopGetCurrent());NSLog(@"%s, %@", __func__, [NSThread currentThread]);//解除引用, 停止runloop这个子线程就会deallocself.thread = nil;
}- (void)dealloc {NSLog(@"%s", __func__);
}

这样我们就实现了线程保活,其中我们要注意,线程的管理是系统管理的,哪怕是在这个页面新建的线程,线程是否销毁和页面的销毁没有任何关系,这取决于系统
那么我们在某个页面销毁的时候就会存在页面新建的线程没有销毁这个问题。
解决这个问题最简单的办法就是,在销毁这个页面的时候,我们再重新调用一次stop方法,并将我们这个线程指向置为nil。
请添加图片描述

定时器NSTimer

在实际开发中,一般不把timer放到主线程的RunLoop中,因为主线程在执行阻塞的任务时,timer计时会不准。
如何让计时准确?如果timer在主线程中阻塞了怎么办?

  • 放入子线程中(即要开辟一个新的线程,但是成本是需要开辟一个新的线程)
  • 写一种跟RunLoop没有关系的计时,即GCD。(不会阻塞,推荐使用这种)
// GCD定时器(常用)
// 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 1.创建一个GCD定时器
/*第一个参数:表明创建的是一个定时器第四个参数:队列*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 需要对timer进行强引用,保证其不会被释放掉,才会按时调用block块
// 局部变量,让指针强引用
self.timer = timer;
// 2.设置定时器的开始时间,间隔时间,精准度
/*第1个参数:要给哪个定时器设置第2个参数:开始时间第3个参数:间隔时间第4个参数:精准度 一般为0 在允许范围内增加误差可提高程序的性能GCD的单位是纳秒 所以要*NSEC_PER_SEC*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);// 3.设置定时器要执行的事情
dispatch_source_set_event_handler(timer, ^{NSLog(@"---%@--",[NSThread currentThread]);// 取消定时if (判断条件) {dispatch_source_cancel(timer);self.timer = nil;}
});
// 4.启动
dispatch_resume(timer);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/23355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

更新页面无法回显

需求与问题&#xff1a; 在菜品管理开发中&#xff0c;我需要修改菜品&#xff0c;第一步是回显页面&#xff0c;但在我再三确认代码无误的情况下依旧无法回显内容 问题发现与解决&#xff1a; 经过排查&#xff0c;我发现我的DishDTO内容如下&#xff1a; Data public clas…

【C++】类和对象-多态

1.多态的基本语法 代码 #include <iostream> using namespace std; /******************************************/ class Animal { public://speak函数就是虚函数//函数前面加上virtual关键字&#xff0c;变成虚函数&#xff0c;//那么编译器在编译的时候就不能确定函数…

【黑马头条之kafka及异步通知文章上下架】

本笔记内容为黑马头条项目的kafka及异步通知文章上下架部分 目录 一、kafka概述 二、kafka安装配置 三、kafka入门 四、kafka高可用设计 1、集群 2、备份机制(Replication&#xff09; 五、kafka生产者详解 1、发送类型 2、参数详解 六、kafka消费者详解 1、消费者…

助力工业物联网,工业大数据之服务域:油站主题分析【二十六】

文章目录 07&#xff1a;服务域&#xff1a;油站主题分析08&#xff1a;服务域&#xff1a;油站主题实现 07&#xff1a;服务域&#xff1a;油站主题分析 目标&#xff1a;掌握油站主题的需求分析 路径 step1&#xff1a;需求step2&#xff1a;分析 实施 需求&#xff1a;统计…

Flink - sink算子

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 1. Kafka_Sink 2. Kafka_Sink - 自定义序列化器 3. Redis_Sink_String 4. Redis_Sink_list 5. Redis_Sink_set 6. Redis_Sink_hash 7. 有界流数据写入到ES 8. 无界流数据写入到ES 9. 自定…

小程序自定义tabBar+Vant weapp

1.构建npm&#xff0c;安装Vant weapp&#xff1a; 1&#xff09;根目录下 &#xff0c;初始化生成依赖文件package.json npm init -y 2&#xff09;安装vant # 通过 npm 安装 npm i vant/weapp -S --production 3&#xff09;修改 package.json 文件 开发者工具创建的项…

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析 实验四 蜂鸣器

目录 前言 一、原理图及知识点介绍 1.1、蜂鸣器原理图&#xff1a; 二、代码分析 前言 第一个实验:51单片机&#xff08;普中HC6800-EM3 V3.0&#xff09;实验例程软件分析 实验一 点亮第一个LED_ManGo CHEN的博客-CSDN博客 第二个实验:51单片机&#xff08;普中HC6800-EM…

深度学习实战46-基于CNN的遥感卫星地图智能分类,模型训练与预测

大家好,我是微学AI,今天给大家介绍一下深度学习实战46-基于CNN的遥感卫星地图智能分类,模型训练与预测。随着遥感技术和卫星图像获取能力的快速发展,卫星图像分类任务成为了计算机视觉研究中一个重要的挑战。为了促进这一领域的研究进展,EuroSAT数据集应运而生。本文将详细…

嵌入式面试刷题(day3)

文章目录 前言一、怎么判断两个float是否相同二、float数据可以移位吗三、数据接收和发送端大小端不一致怎么办四、怎么传输float类型数据1.使用联合进行传输2.使用字节流3.强制类型转换 总结 前言 本篇文章我们继续讲解嵌入式面试刷题&#xff0c;给大家继续分享嵌入式中的面…

python+django+mysql项目实践二(前端及数据库)

python项目实践 环境说明&#xff1a; Pycharm 开发环境 Django 前端 MySQL 数据库 Navicat 数据库管理 前端模板 添加模板 在templates下创建 views文件中添加 创建数据库 连接数据库 在setting文件中进行配置 创建表

车载软件架构 —— 车载软件安全启动关键技术解读

车载软件架构 —— 车载软件安全启动关键技术解读 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 没有人关注你。也无需有人关注你。你必须承认自己的价值,你不能站在他人的角度来反对自己。人生…

uniapp uview文件上传的文件不是文件流,该如何处理?用了uni.chooseImage预览功能要如何做

在使用uniapp开发&#xff0c;运用的ui是用uview&#xff0c;这边需要做一个身份认证&#xff0c;如下图 使用的是uview的u-upload组件&#xff0c;可是这个组件传给后端的不是文件流 后端接口需要的是文件流格式&#xff0c;后面使用了uniapp的选择图片或者拍照的api&#x…

亚马逊云科技七项生成式AI新产品生成式AI,为用户解决数据滞后等难题

7月27日&#xff0c;亚马逊云科技在纽约峰会上一连发布了七项生成式AI创新&#xff0c;涵盖了从底层硬件到工具、软件、再到生态的全方位更新&#xff0c;成为它在该领域迄今最全面的一次升级展示&#xff0c;同时也进一步降低了生成式AI的使用门槛。 亚马逊云科技凭借自身端到…

全志F1C200S嵌入式驱动开发(从DDR中截取内存)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 linux内核起来的时候,不一定所有的内存都是分配给linux使用的。有的时候,我们是希望能够截留一部分内存的。为什么保留这部分内存呢?这里面可以有很多的用途。比如说,第一,如果…

非阻塞IO

非阻塞IO fcntl 一个文件描述符, 默认都是阻塞IO。fcntl可以将某个文件描述符设置为非阻塞IO&#xff0c;先看一下文档介绍。 传入的cmd的值不同&#xff0c;后面追加的参数也不相同。 fcntl函数有5种功能: 复制一个现有的描述符&#xff08;cmd F_DUPFD&#xff09;。获得…

【SpringBoot】86、SpringBoot中集成Quartz根据Cron表达式获取接下来5次执行时间

本篇文章根据集成 Quartz 根据 Cron 表达式获取接下来的 5 次执行时间,在配置定时任务时,可以清晰地知道自己的 Cron 表达式是否正确,对于 Quartz 不熟悉的同学可以先看看我之前的文章 【SpringBoot】82、SpringBoot集成Quartz实现动态管理定时任务 【SpringBoot】83、Spri…

安全基础 --- 编码(02)+ form表单实现交互

浏览器解析机制和XSS向量编码 <!-- javascript伪协议不能被urlcode编码&#xff0c;但可以被html实体编码:也是js协议的一部分&#xff0c;不能被编码js协议被解码后&#xff0c;URL解析器继续解析链接剩下的部分unicode编码可识别实现解码但符号不能被编码&#xff0c;编码…

11.物联网操作系统内存管理

一。STM32编译过程及程序组成 STM32编译过程 程序的组成、存储与运行 MDK生成的主要文件分析 1.STM32编译过程 1.源文件&#xff08;Source code&#xff09;--》目标文件&#xff08;Object code&#xff09; .c(C语言)通过armcc生成.o&#xff0c;.s&#xff08;汇编&…

ELD透明屏在智能家居中有哪些优点展示?

ELD透明屏是一种新型的显示技术&#xff0c;它能够在不需要背光的情况下显示图像和文字。 ELD透明屏的原理是利用电致发光效应&#xff0c;通过在透明基板上涂覆一层特殊的发光材料&#xff0c;当电流通过时&#xff0c;发光材料会发出光线&#xff0c;从而实现显示效果。 ELD…

Android 从LibVLC-android到自编译ijkplayer播放H265 RTSP

概述 ijkplayer: Android/iOS video player based on FFmpeg n3.4, with MediaCodec, VideoToolbox support. 官方的描述就这么简单的一句话&#xff0c;但丝毫都不影响它的强大。 从LibVLC 到 ijkplayer 截止到2023.7.20 LibVLC-Android 最大的问题在与OOM&#xff0c;测试了…