当前位置: 首页 > news >正文

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);

相关文章:

iOS--Runloop

Runloop概述 一般来说&#xff0c;一个线程一次只能执行一个任务&#xff0c;执行完成后线程就会退出。就比如之前学OC时使用的命令行程序&#xff0c;执行完程序就结束了。 而runloop目的就是使线程在执行完一次代码之后不会结束程序&#xff0c;而是使该线程处于一种休眠的状…...

Doccano工具安装教程/文本标注工具/文本标注自己的项目/NLP分词器工具/自然语言处理必备工具/如何使用文本标注工具

这篇文章是专门的安装教程&#xff0c;后续的项目创建&#xff0c;如何使用&#xff0c;以及代码部分可以参考这篇文章&#xff1a; NER实战&#xff1a;(NLP实战/命名实体识别/文本标注/Doccano工具使用/关键信息抽取/Token分类/源码解读/代码逐行解读)_会害羞的杨卓越的博客-…...

windows系统之WSL 安装 Ubuntu

WSL windows10 以上才有这个wsl功能 WSL&#xff1a; windows Subsystem for Linux 是应用于Windows系统之上的Linux子系统 作用很简单&#xff0c;可以在Windows系统中获取Linux系统环境&#xff0c;并完全直连计算机硬件&#xff0c;无需要通过虚拟机虚拟硬件 Windows10的W…...

洛谷题解 | P1046 陶陶摘苹果

目录 题目描述 输入格式 输出格式 输入输出样例 说明/提示 AC代码 题目描述 陶陶家的院子里有一棵苹果树&#xff0c;每到秋天树上就会结出 1010 个苹果。苹果成熟的时候&#xff0c;陶陶就会跑去摘苹果。陶陶有个 3030 厘米高的板凳&#xff0c;当她不能直接用手摘到苹果…...

记一次Apache HTTP Client问题排查

现象 通过日志查看&#xff0c;存在两种异常情况。第一种&#xff1a;开始的时候HTTP请求会报超时异常。 762663363 [2023-07-21 06:04:25] [executor-64] ERROR - com.xxl.CucmTool - CucmTool|sendRisPortSoap error,url:https://xxxxxx/realtimeservice/services/RisPort o…...

Linux获取文件属性

以-rw-rw-r-- 1 ubuntu ubuntu 56 八月 1 19:37 1.txt 为例 一、stat函数 功能&#xff1a;获取文件的属性 函数原型&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <unistd.h>int stat(const char *pathname, struct stat *stat…...

String字符串拼接

String字符串拼接 1.简介2.StringBuilder2.1StringBuilder介绍2.2使用说明 3.StringBuffer4.StringJoiner5.String.Join() 1.简介 对于String来说是不可变的&#xff0c;使用修改字符串是在不断地创建新的字符串对象&#xff0c;而不是在原有的对象上修改的。并且对于字符串的…...

在矩池云使用Llama2-7B的具体方法

今天给大家分享如何在矩池云服务器使用 Llama2-7b模型。 硬件要求 矩池云已经配置好了 Llama 2 Web UI 环境&#xff0c;显存需要大于 8G&#xff0c;可以选择 A4000、P100、3090 以及更高配置的等显卡。 租用机器 在矩池云主机市场&#xff1a;https://matpool.com/host-m…...

API教程:轻松上手HTTP代理服务!

作为HTTP代理产品供应商&#xff0c;我们为您带来一份详细的教程&#xff0c;帮助您轻松上手使用API&#xff0c;并充分利用HTTP代理服务。无论您是开发人员、网络管理员还是普通用户&#xff0c;本教程将为您提供操作指南和代码模板&#xff0c;确保您能够顺利使用API并享受HT…...

脑网络通信:概念、模型与应用——Brain network communication: concepts, models and applications

脑网络通信:概念、模型与应用 介绍神经系统是通信网络从图论到大脑网络通信大脑网络通信模型和测量的分类法路由协议最短路径路由导航扩散过程广播(可通信性)参数模型线性阈值模型偏向性随机游走最短路径集合当前和新兴的应用将大脑结构与功能关联起来认知和临床表型的个体间…...

Docker创建tomcat容器实例后无法访问(HTTP状态 404 - 未找到)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…...

oracle数据库dbLink的使用

Oracle的数据库链路&#xff08;dbLink&#xff09;是一种允许在两个不同的数据库实例之间进行通信和数据交换的功能。它可以让你在一个数据库中访问另一个数据库的对象和数据&#xff0c;就像它们属于同一个数据库一样。 创建一个link: CREATE public DATABASE LINK link_sco…...

Coremail中睿天下|2023年第二季度企业邮箱安全态势观察

7月24日&#xff0c;Coremail邮件安全联合中睿天下发布《2023第二季度企业邮箱安全性研究报告》&#xff0c;对2023第二季度和2023上半年的企业邮箱的安全风险进行了分析。 一、垃圾邮件同比下降16.38% 根据Coremail邮件安全人工智能实验室&#xff08;以下简称AI实验室&#…...

ZooKeeper分布式锁、配置管理、服务发现在Java开发中的应用

ZooKeeper提供了多种功能&#xff0c;包括分布式锁、配置管理、服务发现、领导选举等。 下面是一些常见的ZooKeeper功能及其在Java中的应用示例代码。 分布式锁 import org.apache.zookeeper.*; import java.io.IOException; import java.util.concurrent.CountDownLatch;pu…...

openGauss学习笔记-27 openGauss 高级数据管理- JOIN

文章目录 openGauss学习笔记-27 openGauss 高级数据管理- JOIN27.1 交叉连接27.2 内连接27.3 左外连接27.4 右外连接27.5 全外连接 openGauss学习笔记-27 openGauss 高级数据管理- JOIN JOIN子句用于把来自两个或多个表的行结合起来&#xff0c;基于这些表之间的共同字段。 在…...

域名解析优先级

浏览器访问过程解析 访问网址——>首先在本地电脑看看hosts里面是否有域名对应IP地址&#xff0c;如何有直接访问对应IP&#xff0c; 如果没有&#xff0c;则联网询问DNS服务器&#xff08;一般网卡那边都配置了DNS服务器IP&#xff09; linux hosts 路径&#xff1a; w…...

【Opencv】视频跟踪算法KCF

目录 KCF算法简介opencv实现代码copencv实现代码python KCF算法简介 KCF&#xff08;Kernelized Correlation Filter&#xff09;是一种基于核相关滤波器的目标跟踪算法。它通过学习目标的外观特征和使用核相关滤波器进行目标定位。KCF属于传统算法的单目标跟踪器。下面是对KC…...

后端整理(集合框架、IO流、多线程)

1. 集合框架 Java集合类主要有两个根接口Collection和Map派生出来 Collection派生两个子接口 List List代表了有序可重复集合&#xff0c;可以直接根据元素的索引进行访问Set Set代表无序不可重复集合&#xff0c;只能根据元素本身进行访问 Map接口派生 Map代表的是存储key…...

C++ 类和对象篇(二) this指针

目录 一、this指针概念 二、this指针的特性 三、this指针存在哪里&#xff1f; 四、this指针可以为空吗&#xff1f; 一、this指针概念 1.是什么&#xff1f; 它是类内非静态成员函数的隐含形参&#xff0c;this指针指向调用该函数的对象。 this指针是C编译器给每个“非静态…...

Excel快捷键F1-F9详解:掌握实用快捷操作,提升工作效率

Excel是广泛应用于办公场景的优质电子表格软件&#xff0c;然而&#xff0c;许多人只是使用鼠标点击菜单和工具栏来完成操作&#xff0c;而忽略了快捷键的威力。在本文中&#xff0c;我们将详解Excel中的F1-F9快捷键&#xff0c;帮助您掌握实用的快捷操作&#xff0c;提升工作效…...

Webpack 安装教程

Webpack 是一个前端资源加载/打包工具。 安装 Webpack 使用 cnpm 安装 webpack&#xff1a; cnpm install webpack -g 创建项目 接下来我们创建一个目录 app&#xff1a; mkdir app 在 app 目录下添加 runoob1.js 文件&#xff0c;代码如下&#xff1a; app/runoob1.js 文件…...

移远通信首批加入“5G+eSIM计算终端产业合作计划”,助力大屏移动终端全时在线

7月29日&#xff0c;在全球数字娱乐产业盛会 ChinaJoy上&#xff0c;中国联通携手高通公司、GSMA发布了“5GeSIM 计算终端产业合作计划”。 作为全球领先的物联网整体解决方案供应商&#xff0c;移远通信首批加入该计划&#xff0c;副总经理刘明辉受邀参加5GeSIM 计算终端产业合…...

全网最强大的工具箱—utools介绍及分享

今天来介绍一个相见恨晚的PC端工具——utools&#xff0c;什么是utools&#xff1f;用其自身的话来说&#xff1a;“uTools是一个极简、插件化、跨平台的现代化桌面软件。通过自由选配丰富的插件&#xff0c;打造你得心应手的工具集合。”,体验了下&#xff0c;好用且强大&…...

Linux常用基础命令❀

文章目录❀ ❀ls命令 ❀cd命令 ❀pwd命令 ❀date命令 ❀创建、删除文件和目录命令 ❀alias命令 ❀复制、移动、重命名、查看&#xff08;文件、目录&#xff09;命令 ❀find查找、wc统计命令 ❀vi/vim命令 1、打开文件 2、工作模式 vi与vim的四个模式 进入编辑模式…...

SQL-进阶

mysql --local-infile -u root -pset global local_infile 1;load data local infile 目录 into able 表名 fields terminated by , lines terminated by \n;...

[Pytorch]卷积运算conv2d

文章目录 [Pytorch]卷积运算conv2d一.F.Conv2d二.nn.Conv2d三.nn.Conv2d的运算过程 [Pytorch]卷积运算conv2d 一.F.Conv2d torch.nn.functional.Conv2d()的详细参数&#xff1a; conv2d(input: Tensor, weight: Tensor, bias: Optional[Tensor]None, stride: Union[_int, _s…...

主流开源监控系统一览

减少故障有两个层面的意思&#xff0c;一个是做好常态预防&#xff0c;不让故障发生&#xff1b;另一个是如果故障发生&#xff0c;要能尽快止损&#xff0c;减少故障时长。而监控的典型作用&#xff0c;就是帮助我们发现及定位故障&#xff0c;这两个环节对于减少故障时长至关…...

爬虫原理详解及requests抓包工具用法介绍

文章目录 一、什么是爬虫&#xff1f;二、爬虫的分类三、网址的构成四、爬虫的基本步骤五、动态页面和静态页面六、伪装请求头七、requests库介绍1. 概念&#xff1a;2. 安装方式&#xff08;使用镜像源&#xff09;&#xff1a;3. 基本使用&#xff1a;4. response对象对应的方…...

tinkerCAD案例:31. 3D 基元形状简介

tinkerCAD案例&#xff1a;31. 3D 基元形状简介 1 将一个想法从头脑带到现实世界是一次令人兴奋的冒险。在 Tinkercad 中&#xff0c;这将从一个新的设计开始。 在新设计中&#xff0c;简单的原始形状可以通过不同的方式组合成更复杂的形状。 在这个项目中&#xff0c;你将探索…...

Vue2基础一、快速入门

零、文章目录 Vue2基础一、快速入门 1、Vue 概念 &#xff08;1&#xff09;为什么学 前端必备技能 岗位多&#xff0c;绝大互联网公司都在使用Vue 提高开发效率 高薪必备技能&#xff08;Vue2Vue3&#xff09; &#xff08;2&#xff09;Vue是什么 **概念&#xff1a;…...