Obtenga una comprensión profunda de RunLoop desde el punto de vista

En Objective-C, se mencionó que RunLoopsiempre tuvimos dificultades para solucionar los siguientes problemas.

¿Qué es RunLoop? ¿Cuál es la relación entre él y los hilos? El llamado RunLoopMode, RunLoopSource, RunLoopObserver, RunLoopTimer¿qué es? ¿Cuál es la conexión?

A continuación, también podríamos tomarlos como puntos, desde el punto hasta la superficie, para analizar RunLoopla verdadera cara del.

escribe primero

En el OSX/iOSsistema , RunLoophay dos relacionados: NSRunLoop y CFRunLoopRef .

  • CFRunLoopRef está dentro del marco CoreFoundation , proporciona API para funciones C puras y todas estas API son seguras para subprocesos.
  • NSRunLoop es un paquete basado en CFRunLoopRef que proporciona API orientadas a objetos, pero estas API no son seguras para subprocesos.

Actualmente, NSRunLoop es una clase en el Foundationmarco , solo podemos ver su archivo de encabezado, pero no su implementación específica. Y CFRunLoopRef es de código abierto, podemos descargar directamente su código fuente. descarga directa

__CFRunLoop

Dado que NSRunLoop  se basa en  la encapsulación de CFRunLoopRef  , comencemos directamente desde el código fuente de CFRunLoopRef para ver RunLoopqué es.

Podemos obtener CFRunLoopla :

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */
    __CFPort _wakeUpPort;			// used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
复制代码

Se puede ver que su esencia también es una estructura, y solo podemos captar el contenido importante que nos importa, como _pthread, _modes, _currentMode, _commonModeItems, _commonModesetc.

Es cierto de que:

  • _pthreadSe puede ver que definitivamente hay una relación RunLoopcon el hilo, ¿cuál es la relación específica? Veremos más tarde.
  • _modesComo mencionamos anteriormente RunLoopMode, aquí hay uno set, obviamente más de uno, pero muchos.
  • _currentMode¿Paño de lana? También se puede saber que la traducción literal es actual mode.
  • _commonModeItems, _commonModesobviamente está relacionado con lo que solemos establecer en commonModeel , y aquí lo _modesdistinguimos del común, que obviamente no es un tipo de modo, lo veremos más adelante.

__CFrunLoopMode

A continuación continuamos viendo __CFRunLoopModela definición de:

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
复制代码

También es una estructura, y también tomamos el _sources0, _sources1, _observers, _timersy así sucesivamente que nos preocupan más.

Tenga en cuenta que ⚠️, aquí están todas las formas en plural, y el tipo de datos también es set o array , lo que prueba que no es una sola existencia.

我们继续撸源码,很容易发现,__CFRunLoopSource__CFRunLoopObserver__CFRunLoopTimer 也都是结构体,也都有其对应的属性。

稍作总结

到这里,其实我们从前面的源码就能知道 RunLoopRunLoopModeRunLoopSourceRunLoopObserverRunLoopTimer 他们之前的关系:

  • RunLoopRunLoopMode 是一对多的关系,也就是一个 RunLoop 包含多种 RunLoopMode,而 _currentMode 说明其多个 mode 并不是同时生效的,一次只是生效其中的一种;
  • RunLoopModeRunLoopSourceRunLoopObserverRunLoopTimer 同样也是一对多的关系,换句话说,就是在一种 RunLoopMode 下,会有多个 RunLoopSource / RunLoopObserver / RunLoopTimer,注意三者并不是互斥关系,一种 RunLoopMode 是可以同时拥有上面三种的,当然,RunLoopSource 又分为通常我们熟知的 source0source1。而上面三者又统称为 modeItem

网上找到一张图来描述他们之前的关系:

imagen.png

这里面还提到了 RunLoop 与线程,以及commonModes 的关系,后面我们继续看。

RunLoop 怎么 Run ?

前面我们知道,RunLoop 其本质是一个结构体,那么,RunLoop 又是怎么运行起来的呢?我们知道 RunLoop 是一个 Loop,也就是循环,通常我们将其理解为 死循环,这又是为何呢?

我们可以从其主运行函数入手得知一二:

void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
复制代码

可以看到,CFRunLoopRun 其本身实现为一个 do-while 循环体,其结束的条件是 Stop 或者 Finish 。而这两种状态在正常情况下始终不会满足,所以说理解一个正常的 RunLoop 是一个循环,还是个 死循环 也是无可厚非的。

另,我们从上面的源码了解到,其 Run 起来的核心函数是 CFRunLoopRunSpecific()

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
	Boolean did = false;
	if (currentMode) __CFRunLoopModeUnlock(currentMode);
	__CFRunLoopUnlock(rl);
	return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
	rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}
复制代码

我们可以大致了解起过程,这个函数传入参数 RunLoop、mode名、时间以及是否 Handled 之后返回的 Boolean 值,其整个过程就是:通过 __CFRunLoopFindMode() 找到 currentMode ,最终通过 __CFRunLoopRun() 运行。

这中间,免不了一堆各种判断,但是不影响主流程。

值得一提的是,我们放眼望去那些个各种 lockunlock,实际上就是我们线程安全的保障。

实际上,RunLoop run起来的姿势还有一种,那就是 CFRunLoopRunInMode()

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
复制代码

从上面的源码知道,其本质还是 CFRunLoopRunSpecific(),跟上面是一致的。

从这里实际上我们也能发现另一个问题:RunLoopRun 总是 run 在某一种 mode 下面的,也就对应前面提到的 _currentMode 的问题了。换句话说就是,一个 RunLoop 有很多 mode,但是每次运行只是指定其一种。我们在让其运行的时候可以指定其运行的 mode,也可以不指定,走默认值,默认值为 kCFRunLoopDefaultMode

接着深入一下,既然只能是一个 mode 在运行,那只有这个 mode 下面的 modeItems 生效是不是就是理所应当,这里就是为什么 Timer 在 kCFRunLoopDefaultMode 下时,滑动会暂停其计时了,因为滑动的时候,mode 切换了。所以,了然与否呢?

RunLoop 与线程的关系

前面,RunLoop 的结构体中,我们看到一个 RunLoop 中对应一个 _pthread,即线程,因为这里并不是类似前面提到的 set OR Array 的这种集合的数据结构。

那么一个线程中,是否能有多个 RunLoop 呢?其实不然,官方根本就没有给予我们自己创建 RunLoop 的入口,结合其源码我们可以肯定,一个线程中也只能有一个 RunLoop

问题是,啥源码呢?我们来看。

我们通常通过下面的方式获取 RunLoop

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

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
	CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
	loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	if (!loop) {
	    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
	    loop = newLoop;
	}
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
	CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}
复制代码

注意这里:CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

很明显的是,这里有个字典(Dictionary),通过这个字典能通过线程的指针,拿到对象应的 RunLoop,所以呢?要是一个线程对应多个 RunLoop ,这里怎么解释?

所以,线程和 RunLoop 之间是一一对应的,并且其关系是保存在一个全局的 Dictionary 里。

实际上,线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

commonMode

截止目前,我们还遗留另一个问题,关于 commonMode 的,这里又是何解呢?

我们先来看这段源码:

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    __CFRunLoopLock(rl);
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
	CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
	CFSetAddValue(rl->_commonModes, modeName);
	if (NULL != set) {
	    CFTypeRef context[2] = {rl, modeName};
	    /* add all common-modes items to new mode */
	    CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
	    CFRelease(set);
	}
    } else {
    }
    __CFRunLoopUnlock(rl);
}
复制代码

这个函数是干啥的呢?其实就是给 RunLoop 添加 commonMode 的,我们可以看到,其最终是添加到了 RunLoop_commonModes 属性里面,但是这里加进去的只是 modename

而在之后呢?注意这个函数 CFSetApplyFunction(),我们可以直接看其注释就知道具体干了啥,就是将所有的 commonModeItems 加到这个新的 commonMode 里面了。具体添加的过程是通过 __CFRunLoopAddItemsToCommonMode() 函数添加的:

static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx) {
    CFTypeRef item = (CFTypeRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
	CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
	CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
	CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}
复制代码

可以看到,其就是将这个 mode 里面的各类 modeItem 添加到 commonMode 里面。

我们在接着往下看,拿 CFRunLoopAddSource 来看:

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rls)) return;
    Boolean doVer0Callout = false;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
	CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	if (NULL == rl->_commonModeItems) {
	    rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	}
	CFSetAddValue(rl->_commonModeItems, rls);
	if (NULL != set) {
	    CFTypeRef context[2] = {rl, rls};
	    /* add new item to all common-modes */
	    CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	    CFRelease(set);
	}
    } else {
	CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	if (NULL != rlm && NULL == rlm->_sources0) {
	    rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
	}
	if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
	    if (0 == rls->_context.version0.version) {
	        CFSetAddValue(rlm->_sources0, rls);
	    } else if (1 == rls->_context.version0.version) {
	        CFSetAddValue(rlm->_sources1, rls);
		__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
		if (CFPORT_NULL != src_port) {
		    CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
		    __CFPortSetInsert(src_port, rlm->_portSet);
	        }
	    }
	    __CFRunLoopSourceLock(rls);
	    if (NULL == rls->_runLoops) {
	        rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
	    }
	    CFBagAddValue(rls->_runLoops, rl);
	    __CFRunLoopSourceUnlock(rls);
	    if (0 == rls->_context.version0.version) {
	        if (NULL != rls->_context.version0.schedule) {
	            doVer0Callout = true;
	        }
	    }
	}
        if (NULL != rlm) {
	    __CFRunLoopModeUnlock(rlm);
	}
    }
    __CFRunLoopUnlock(rl);
    if (doVer0Callout) {
        // although it looses some protection for the source, we have no choice but
        // to do this after unlocking the run loop and mode locks, to avoid deadlocks
        // where the source wants to take a lock which is already held in another
        // thread which is itself waiting for a run loop/mode lock
	rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName);	/* CALLOUT */
    }
}
复制代码

我们这里走的是 modeName != kCFRunLoopCommonModes 的情况,显然就会将 item 同步到 mode 对应的 items 里面了。

⚠️注意,这里并不是调用一次,是将上面 set 里面的每个元素都调用一次上面的过程。也就是将所有的 commonModeItems 加到了新的 mode 里面了。

我们再关注一下 modeName == kCFRunLoopCommonModes 的时候(这是什么情况呢?比如我们让 Timer run 在 commonMode 下的时候),首先是将 sourceitem) 添加到了 RunLoop_commonModeItems 里面。然后呢?

我们注意到这里也有类似的调用:

if (NULL != set) {
    CFTypeRef context[2] = {rl, rls};
    /* add new item to all common-modes */
    CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
    CFRelease(set);
}
复制代码

需要注意的是,这里操作虽然类似,但是因为 set 的不一样(一个是 modes,一个是 items),所以这里如注释所述,就是将 item 同步到 RunLoop 的属性 _commonModes 中的所有 mode 的对应的 sourceItems 集合中。

总结来看的话,commonMode 其实并不同于寻常的 mode,它更多的是一个虚的概念(其他的则是实际的 mode),当是这种模式的时候,若是 mode,会将其添加到 RunLoop_commonModes 属性里面,并将 _commonModeItems 同步到这个 mode 中的 sourceItems 中。若为 item,会先将其添加到 RunLoop_commonModeItems 属性里面,并同步到 _commonModes 属性中的所有 modesourceItems 中。

总结

到这里,我们基本厘清了 RunLoop 中的一堆概念及其关系,在此总结一二:

  • RunLoopRunLoopMode 是一对多的关系,也就是一个 RunLoop 包含多种 RunLoopMode
  • RunLoopModemodeItemRunLoopSourceRunLoopObserverRunLoopTimer) 同样也是一对多的关系,换句话说,就是在一种 RunLoopMode 下,会有多个 modeItem
  • RunLoop 只能运行在一种 RunLoopMode 下,即为 currentMode
  • RunLoopLa relación correspondiente al significado de hilo se almacena en el diccionario global ( __CFRunLoops), que keyes el puntero del objeto hilo, que valuees el RunLoopobjeto .
  • RunLoopLas _commonModespropiedades en el almacenado son commonModetodas las que están marcadas como modes, recién almacenadas modeName;
  • RunLoopAlmacenadas en las _commonModeItemspropiedades , todas se ejecutan a commonModecontinuación modeItems;
  • mode, modeItemen commonModeel , sincronizará RunLooplas dos propiedades _commonModesde y _commonModeItems, y sincronizará todas las commonModesde modeItems;
  • CFRunLoopRefLas operaciones que pueden afectar la seguridad de subprocesos están bloqueadas, por lo que son seguras para subprocesos;

Todo lo anterior, espero ganar algo.

Supongo que te gusta

Origin juejin.im/post/7082739417370066974
Recomendado
Clasificación