您当前的位置:首页 > 时尚 > 内容

如何用obs录制cf(cf游戏录制视频软件)

如何用obs录制cf(cf游戏录制视频软件)?如果你对这个不了解,来看看!

iOS RunLoop,下面是洲游历累给大家的分享,一起来看看。

如何用obs录制cf

什么是RunLoop

从字面意思来看,就是运行循环的意思,其实就是在程序运行过程中循环做一些事情

RunLoop的应用范畴

下面几个技术都需要在RunLoop下才能进行

定时器(Timer)、PerformSelector- GCD Async Main Queue- 事件响应、手势识别、界面刷新- 网络请求- AutoreleasePool

在main函数中,如果没有RunLoop,那么下面代码执行完第三行后就会退出程序

获取RunLoop对象

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

Foundation:NSRunLoopCore Foundation:CFRunLoopRef

NSRunLoop是基于CFRunLoopRef的一层OC包装,两者都代表着RunLoop对象

CFRunLoopRef的开源代码地址:https://opensource.apple.com/tarballs/CF/

在代码中的调用方法:

NSRunLoop *runloop = [NSRunLoop currentRunLoop];CFRunLoopRef runloop2 = CFRunLoopGetCurrent();RunLoop与线程每条线程都有唯一的一个与之对应的RunLoop对象RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建RunLoop会在线程结束时销毁

下面两种方式都可以获取到主线程的RunLoop,主线程的RunLoop会在main函数执行UIApplicationMain的时候自动创建,所以调用[NSRunLoop currentRunLoop]时已经有了RunLoop对象

NSLog(@"%p %p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);NSLog(@"%p %p", CFRunLoopGetCurrent(), CFRunLoopGetMain());

在子线程里一开始是没有创建RunLoop对象的,需要调用[NSRunLoop currentRunLoop]才会创建

我们可以从源码CFRunloop.c文件中查看对应实现

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) {t = pthread_main_thread_np(); } __CFLock(&loopsLock); // 如果没有__CFRunLoops字典,先创建一个 if (!__CFRunLoops) { __CFUnlock(&loopsLock);CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); // 然后创建主线程的runloop,对应主线程的key存储到字典中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); } // 从字典__CFRunLoops里面获取runloop // key: pthreadPointer CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); // 如果没有与之对应的runloop对象,就创建一个新的runloop if (!loop) {CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); // 再次判断是否有该runloop对象loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 如果没有,就将新创建的runloop赋值进去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;}RunLoop的底层结构

我们通过源码可以看到CFRunLoopRef的本质是一个__CFRunLoop结构体类型

typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;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;};

再来查看里面的_currentMode类型为CFRunLoopModeRef,表示RunLoop的运行模式,本质是__CFRunLoopMode的结构体类型

typedef struct __CFRunLoopMode *CFRunLoopModeRef;struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock;/* must have the run loop locked before locking this */ CFStringRef _name; Boolean _stopped; char _padding[3]; // _sources0、_sources1就是平时需要处理的事情 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 */};

CFRunLoopRef的内部结构可以用下图来表述

CFRunLoopModeRef在RunLoop中的关系可以用下图表示

CFRunLoopModeRefMode的作用一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer- RunLoop启动时只能选择其中一个Mode,作为currentMode如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入 - 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响切换Mode程序不会退出,也是在内部做的切换如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出RunLoop里面常见的ModekCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopModeMode中成员的用途Source0 - 触摸事件处理 - performSelector:onThread:- Source1 - 基于Port的线程间通信 - 系统事件捕捉(比如捕捉到点击事件,然后再交给Source0来做处理)- Timers - NSTimer定时器 - performSelector:withObject:afterDelay:- Observers - 用于监听RunLoop的状态(监听RunLoop是否没有事件要处理了) - UI刷新(在RunLoop将要进入睡眠时唤醒刷新UI) - Autorelease pool(在RunLoop将要进入睡眠时唤醒进行内存释放)RunLoop的运行逻辑通过代码监听

我们可以通过添加观察者Observer的方式来监听RunLoop的状态以及模式变化

1.通过函数调用的方式来监听

// 创建Observer// kCFRunLoopAllActivities:监听所有状态变化CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);// 添加Observer到RunLoop中CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);// 释放(C语言中create创建后要对应释放观察者)CFRelease(observer);void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){ 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; }}

2.通过block回调来监听

// 创建ObserverCFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopEntry - %@", mode); CFRelease(mode); break; } case kCFRunLoopBeforeTimers: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopBeforeTimers - %@", mode); CFRelease(mode); break; } case kCFRunLoopBeforeSources: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopBeforeSources - %@", mode); CFRelease(mode); break; } case kCFRunLoopBeforeWaiting: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopBeforeWaiting - %@", mode); CFRelease(mode); break; } case kCFRunLoopAfterWaiting: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopAfterWaiting - %@", mode); CFRelease(mode); break; } case kCFRunLoopExit: { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); NSLog(@"kCFRunLoopExit - %@", mode); CFRelease(mode); break; } default: break; }});// 添加Observer到RunLoop中CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);// 释放CFRelease(observer);

下面是RunLoop状态变化的枚举注释

通过源码分析

我们新建一个项目并运行,然后可以通过LLDB的命令bt来查看详细的调用过程

可以看到RunLoop的调用是从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; // 通知observer进入Loopif (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 具体要做的事情result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);if (currentMode->_observerMask & kCFRunLoopExit ) // 通知observer,退出Loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun);rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result;}

我们可以看到核心代码是在__CFRunLoopRun函数中

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl);return kCFRunLoopRunStopped; } else if (rlm->_stopped) {rlm->_stopped = false;return kCFRunLoopRunStopped; } mach_port_name_t dispatchPort = MACH_PORT_NULL; Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ))); if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF(); #if USE_DISPATCH_SOURCE_FOR_TIMERS mach_port_name_t modeQueuePort = MACH_PORT_NULL; if (rlm->_queue) { modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue); if (!modeQueuePort) { CRASH("Unable to get port for run loop mode queue (%d)", -1); } }#endif dispatch_source_t timeout_timer = NULL; struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context)); if (seconds <= 0.0) { // instant timeout seconds = 0.0; timeout_context->termTSR = 0ULL; } else if (seconds <= TIMER_INTERVAL_LIMIT) {dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_retain(timeout_timer);timeout_context->ds = timeout_timer;timeout_context->rl = (CFRunLoopRef)CFRetain(rl);timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of contextdispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout); dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel); uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL); dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL); dispatch_resume(timeout_timer); } else { // infinite timeout seconds = 9999999999.0; timeout_context->termTSR = UINT64_MAX; } Boolean didDispatchPortLastTime = true; int32_t retVal = 0; // ---- 循环具体做的事情 do...while ---- do {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED; voucher_t voucherCopy = NULL;#endif uint8_t msg_buffer[3 * 1024];#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI mach_msg_header_t *msg = NULL; mach_port_t livePort = MACH_PORT_NULL; // windows平台#elif DEPLOYMENT_TARGET_WINDOWS HANDLE livePort = NULL; Boolean windowsMessageReceived = false;#endif__CFPortSet waitSet = rlm->_portSet; __CFRunLoopUnsetIgnoreWakeUps(rl); // 通知observer:即将处理timer if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 通知observer:即将处理sources if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 处理blocks__CFRunLoopDoBlocks(rl, rlm); // 处理source0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { // 处理blocks __CFRunLoopDoBlocks(rl, rlm);} Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI msg = (mach_msg_header_t *)msg_buffer; // 判断有无source1 if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { // 如果有就跳转到handle_msg goto handle_msg; }#elif DEPLOYMENT_TARGET_WINDOWS if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) { goto handle_msg; }#endif } didDispatchPortLastTime = false;if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) // 通知observer:即将休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);__CFRunLoopSetSleeping(rl);// do not do any user callouts after this point (after notifying of sleeping) // Must push the local-to-this-activation ports in on every loop // iteration, as this mode could be run re-entrantly and we don't // want these ports to get serviced. __CFPortSetInsert(dispatchPort, waitSet); __CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl); CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI#if USE_DISPATCH_SOURCE_FOR_TIMERS do { if (kCFUseCollectableAllocator) { // objc_clear_stack(0); // <rdar://problem/16393959> memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; // 等待别的消息来唤醒当前线程 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer. while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue)); if (rlm->_timerFired) { // Leave livePort as the queue port, and service timers below rlm->_timerFired = false; break; } else { if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); } } else { // Go ahead and leave the inner loop. break; } } while (1);#else if (kCFUseCollectableAllocator) { // objc_clear_stack(0); // <rdar://problem/16393959> memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);#endif #elif DEPLOYMENT_TARGET_WINDOWS // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages. __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);#endif __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart)); // Must remove the local-to-this-activation ports in on every loop // iteration, as this mode could be run re-entrantly and we don't // want these ports to get serviced. Also, we don't want them left // in there if this function returns. __CFPortSetRemove(dispatchPort, waitSet); __CFRunLoopSetIgnoreWakeUps(rl); // user callouts now OK again__CFRunLoopUnsetSleeping(rl);if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) // 通知observer:结束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); // 调用handle_msg handle_msg:; __CFRunLoopSetIgnoreWakeUps(rl);#if DEPLOYMENT_TARGET_WINDOWS if (windowsMessageReceived) { // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); if (rlm->_msgPump) { rlm->_msgPump(); } else { MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) { TranslateMessage(&msg); DispatchMessage(&msg); } } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later. // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling. __CFRunLoopSetSleeping(rl); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); __CFRunLoopUnsetSleeping(rl); // If we have a new live port then it will be handled below as normal } #endif // 判断是怎么醒来的 if (MACH_PORT_NULL == livePort) { CFRUNLOOP_WAKEUP_FOR_NOTHING(); // handle nothing } else if (livePort == rl->_wakeUpPort) { CFRUNLOOP_WAKEUP_FOR_WAKEUP(); // do nothing on Mac OS#if DEPLOYMENT_TARGET_WINDOWS // Always reset the wake up port, or risk spinning forever ResetEvent(rl->_wakeUpPort);#endif } #if USE_DISPATCH_SOURCE_FOR_TIMERS // 被timer唤醒 else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer, because we apparently fired early // 处理timers __CFArmNextTimerInMode(rlm, rl); } }#endif#if USE_MK_TIMER_TOO // 被timer唤醒 else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled. // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754 if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer // 处理timers __CFArmNextTimerInMode(rlm, rl); } }#endif // 被gcd唤醒 else if (livePort == dispatchPort) { CFRUNLOOP_WAKEUP_FOR_DISPATCH(); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);#if DEPLOYMENT_TARGET_WINDOWS void *msg = 0;#endif // 处理gcd相关事情 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; didDispatchPortLastTime = true; } else { // 被source1唤醒 CFRUNLOOP_WAKEUP_FOR_SOURCE(); // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again. voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release); // Despite the name, this works for windows handles as well CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); if (rls) {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI // 处理source1mach_msg_header_t *reply = NULL;sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;if (NULL != reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);}#elif DEPLOYMENT_TARGET_WINDOWS sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;#endif } // Restore the previous voucher _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release); } #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);#endif // 处理block__CFRunLoopDoBlocks(rl, rlm); // 根据retVal返回值来决定要做什么if (sourceHandledThisLoop && stopAfterHandle) { retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { retVal = kCFRunLoopRunTimedOut;} else if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped;} else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped;} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished;} #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI voucher_mach_msg_revert(voucherState); os_release(voucherCopy);#endif } while (0 == retVal); // ---- 循环结束 ----- if (timeout_timer) { dispatch_source_cancel(timeout_timer); dispatch_release(timeout_timer); } else { free(timeout_context); } // 如果不等于0就退出loop return retVal;}细节分析

在__CFRunLoopDoObservers函数里会调用通知observer的代码来执行__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) {/* DOES CALLOUT */ CHECK_FOR_FORK(); CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0; if (cnt < 1) return; /* Fire the observers */ STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024) ? cnt : 1); CFRunLoopObserverRef *collectedObservers = (cnt <= 1024) ? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef)); CFIndex obs_cnt = 0; for (CFIndex idx = 0; idx < cnt; idx++) { CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx); if (0 != (rlo->_activities & activity) && __CFIsValid(rlo) && !__CFRunLoopObserverIsFiring(rlo)) { collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo); } } __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); for (CFIndex idx = 0; idx < obs_cnt; idx++) { CFRunLoopObserverRef rlo = collectedObservers[idx]; __CFRunLoopObserverLock(rlo); if (__CFIsValid(rlo)) { Boolean doInvalidate = !__CFRunLoopObserverRepeats(rlo); __CFRunLoopObserverSetFiring(rlo); __CFRunLoopObserverUnlock(rlo); // 这句才是真正处理observer的代码 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info); if (doInvalidate) { CFRunLoopObserverInvalidate(rlo); } __CFRunLoopObserverUnsetFiring(rlo); } else { __CFRunLoopObserverUnlock(rlo); } CFRelease(rlo); } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); if (collectedObservers != buffer) free(collectedObservers);}

在__CFRunLoopDoSources0函数中会调用处理Source0的代码__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) {/* DOES CALLOUT */ CHECK_FOR_FORK(); CFTypeRef sources = NULL; Boolean sourceHandled = false; /* Fire the version 0 sources */ if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) {CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources); } if (NULL != sources) {__CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl);// sources is either a single (retained) CFRunLoopSourceRef or an array of (retained) CFRunLoopSourceRefif (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()) { CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources; __CFRunLoopSourceLock(rls); if (__CFRunLoopSourceIsSignaled(rls)) { __CFRunLoopSourceUnsetSignaled(rls); if (__CFIsValid(rls)) { __CFRunLoopSourceUnlock(rls); // 这句才是处理source0的真正的代码 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info); CHECK_FOR_FORK(); sourceHandled = true; } else { __CFRunLoopSourceUnlock(rls); } } else { __CFRunLoopSourceUnlock(rls); }} else { CFIndex cnt = CFArrayGetCount((CFArrayRef)sources); CFArraySortValues((CFMutableArrayRef)sources, CFRangeMake(0, cnt), (__CFRunLoopSourceComparator), NULL); for (CFIndex idx = 0; idx < cnt; idx++) {CFRunLoopSourceRef rls = (CFRunLoopSourceRef)CFArrayGetValueAtIndex((CFArrayRef)sources, idx);__CFRunLoopSourceLock(rls); if (__CFRunLoopSourceIsSignaled(rls)) { __CFRunLoopSourceUnsetSignaled(rls); if (__CFIsValid(rls)) { __CFRunLoopSourceUnlock(rls); __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info); CHECK_FOR_FORK(); sourceHandled = true; } else { __CFRunLoopSourceUnlock(rls); } } else { __CFRunLoopSourceUnlock(rls); }if (stopAfterHandle && sourceHandled) { break;} }}CFRelease(sources);__CFRunLoopLock(rl);__CFRunLoopModeLock(rlm); } return sourceHandled;}

__CFRunLoopServiceMachPort意味着RunLoop进入睡眠,也就是会阻塞当前线程,不再往后执行代码;只有当RunLoop被唤醒才会继续执行后面的代码。这种阻塞是真正的阻塞,线程会进入休息,不再做任何事情,和写个死循环的阻塞是不一样的,死循环还是会执行代码,线程还是要一直工作的

static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) { Boolean originalBuffer = true; kern_return_t ret = KERN_SUCCESS; for (;;) {/* In that sleep of death what nightmares may come ... */ mach_msg_header_t *msg = (mach_msg_header_t *)*buffer; msg->msgh_bits = 0; msg->msgh_local_port = port; msg->msgh_remote_port = MACH_PORT_NULL; msg->msgh_size = buffer_size; msg->msgh_id = 0; if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); } // 内核层面的api调用 ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL); // Take care of all voucher-related work right after mach_msg. // If we don't release the previous voucher we're going to leak it. voucher_mach_msg_revert(*voucherState); // Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one. *voucherState = voucher_mach_msg_adopt(msg); if (voucherCopy) { if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) { // Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy. // CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged. *voucherCopy = voucher_copy(); } else { *voucherCopy = NULL; } } CFRUNLOOP_WAKEUP(ret); if (MACH_MSG_SUCCESS == ret) { *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL; return true; } if (MACH_RCV_TIMED_OUT == ret) { if (!originalBuffer) free(msg); *buffer = NULL; *livePort = MACH_PORT_NULL; return false; } if (MACH_RCV_TOO_LARGE != ret) break; buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE); if (originalBuffer) *buffer = NULL; originalBuffer = false; *buffer = realloc(*buffer, buffer_size); } HALT; return false;}

通过源码我们可以看到内部会调用内核层面的API来进行真正的休眠

调用API我们可以分为用户态和内核态的两种状态切换,RunLoop进入睡眠就是从用户态切换到内核态的调用,然后有消息唤醒线程时再切换到用户态来处理事件

总结

RunLoop的整个运行逻辑可以用下图来表述

以__CFRUNLOOP_IS_开头的大写函数都是真正去实行调用的函数

RunLoop对GCD的特殊处理

我们运行下面代码,并在GCD的回调里打断点,通过LLDB的指令bt打印来查看RunLoop的执行过程

@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 处理一些子线程的逻辑 // 回到主线程去刷新UI界面 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"11111111111"); }); });}@end

可以看到RunLoop会处理GCD的回到主线程的调用情况,其他时候GCD都是有自己的逻辑去处理的,所以这是一个特殊情况

RunLoop的实际应用解决NSTimer在滑动时停止工作的问题

如果定时器和滚动页面同时存在的情况下,可能会遇到因为页面滚动,而定时器停止的问题,见下面代码

// 通过这种方式添加定时器[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"%d", ++count);}];

原因是因为这样添加定时器会默认指定在RunLoop的NSDefaultRunLoopMode下进行,而处理页面滚动是在RunLoop的UITrackingRunLoopMode模式下进行的

解决办法:通过以下方式添加定时器,并选择NSRunLoopCommonModes模式

static int count = 0;NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"%d", ++count);}]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

NSRunLoopCommonModes并不是真的模式,而是一种标记,会让定时器同时能在这两种模式下都进行工作

选择通用模式会将这两种模式都添加到RunLoop底层__CFRunLoop结构体里的_commonModes成员变量里,_commonModes意味着在通用模式下可以执行的模式列表,而定时器timer也会被加入到_commonModeItems列表中;没有设置为通用模式的定时器timer只存在于__CFRunLoopMode里面的成员变量timers这个列表中

控制线程生命周期(线程保活)

线程在程序运行中执行完代码就会自动销毁了,见下面代码,可以利用RunLoop来进行线程保活

- (void)viewDidLoad { [super viewDidLoad]; Thread *thread = [[Thread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start];}- (void)run { NSLog(@"%s %@", __func__, [NSThread currentThread]);}

完成实现代码如下

@interface Thread : NSThread@end@implementation Thread- (void)dealloc{ NSLog(@"%s", __func__);}@end@interface ViewController ()@property (strong, nonatomic) Thread *thread;@property (assign, nonatomic, getter=isStoped) BOOL stopped;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; __weak typeof(self) weakSelf = self; self.stopped = NO; self.thread = [[Thread alloc] initWithBlock:^{ NSLog(@"%@----begin----", [NSThread currentThread]); // 往RunLoop里面添加Source\Timer\Observer [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf.isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"%@----end----", [NSThread currentThread]); }]; [self.thread start];}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ if (!self.thread) return; [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];}// 子线程需要执行的任务- (void)test{ NSLog(@"%s %@", __func__, [NSThread currentThread]);}- (IBAction)stop { if (!self.thread) return; // 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走) [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];}// 用于停止子线程的RunLoop- (void)stopThread{ // 设置标记为YES self.stopped = YES; // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s %@", __func__, [NSThread currentThread]); // 清空线程 self.thread = nil;}- (void)dealloc{ NSLog(@"%s", __func__); [self stop];}@end细节分析

1.控制器增加一个Thread *thread的属性来在多个方法能操作同一条线程

@property (strong, nonatomic) Thread *thread;

2.通过block的方式来创建线程是为了避免@selector里面会对Controller进行引用,Controller又持有thread,会造成循环引用,无法释放

self.thread = [[Thread alloc] initWithBlock:^{ }];[self.thread start];

3.在子线程里创建一个RunLoop对象,如果没有任何成员变量那么创建完就会马上退出,所以添加一个空白的任务Source1,也就是[[NSPort alloc] init],让RunLoop不会退出

[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

添加的任务是个空的任务,RunLoop其实没有任何事情要执行,所以会进入到睡眠状态,所以会卡住线程不再往下执行代码

// 这句代码不会执行NSLog(@"%@----end----", [NSThread currentThread]);

4.采用循环创建RunLoop而不使用[[NSRunLoop currentRunLoop] run]是因为run方法的底层就是死循环进行runMode: beforeDate:的调用,那么就无法停止所有的RunLoop,所以我们自己实现while循环,并通过状态值来控制什么时候可以停止创建RunLoop

while (weakSelf && !weakSelf.isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}

5.添加任务和结束RunLoop都要在同一个子线程来完成

[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];// 子线程需要执行的任务- (void)test{ NSLog(@"%s %@", __func__, [NSThread currentThread]);}[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];// 用于停止子线程的RunLoop- (void)stopThread{ // 设置标记为YES self.stopped = YES; // 停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent()); NSLog(@"%s %@", __func__, [NSThread currentThread]); // 清空线程 self.thread = nil;}

6.waitUntilDone:表示是否执行完selector的函数调用再继续往下执行代码,如果为NO,那么执行完performSelector,控制器就真的销毁了,这时再去调用到self就会报野指针的错误;设置为YES,代码会等子线程的stopThread执行完毕才会去真正销毁控制器

7.while循环里进行self的判断是为了防止执行了dealloc方法会将弱指针已经至为nil了,那么循环的判断就会为true,再次进入创建RunLoop;所以要确保self有值的情况下判断条件才成立

while (weakSelf && !weakSelf.isStoped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}

[NSDate distantFuture]表示的是未来无限长的时间

封装优化

我们对上述代码进行封装优化,以便更好的集成,如下所示

// PermenantThread.h typedef void (^PermenantThreadTask)(void);@interface PermenantThread : NSObject/** 开启线程 *///- (void)run;/** 在当前子线程执行一个任务 */- (void)executeTask:(PermenantThreadTask)task;/** 结束线程 */- (void)stop;@end// PermenantThread.m/** Thread **/@interface Thread : NSThread@end@implementation Thread- (void)dealloc{ NSLog(@"%s", __func__);}@end/** PermenantThread **/@interface PermenantThread()@property (strong, nonatomic) Thread *innerThread;@property (assign, nonatomic, getter=isStopped) BOOL stopped;@end@implementation PermenantThread#pragma mark - public methods- (instancetype)init{ if (self = [super init]) { self.stopped = NO; __weak typeof(self) weakSelf = self; self.innerThread = [[Thread alloc] initWithBlock:^{ [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf.isStopped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } }]; [self.innerThread start]; } return self;}//- (void)run//{// if (!self.innerThread) return;//// [self.innerThread start];//}- (void)executeTask:(PermenantThreadTask)task{ if (!self.innerThread || !task) return; [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];}- (void)stop{ if (!self.innerThread) return; [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];}- (void)dealloc{ NSLog(@"%s", __func__); [self stop];}#pragma mark - private methods- (void)__stop{ self.stopped = YES; CFRunLoopStop(CFRunLoopGetCurrent()); self.innerThread = nil;}- (void)__executeTask:(PermenantThreadTask)task{ task();}@end

然后在Controller里就可以简单的集成使用了

@interface ViewController ()@property (strong, nonatomic) PermenantThread *thread;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; self.thread = [[PermenantThread alloc] init];}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self.thread executeTask:^{ NSLog(@"执行任务 - %@", [NSThread currentThread]); }];}- (IBAction)stop { [self.thread stop];}- (void)dealloc{ NSLog(@"%s", __func__);}@end

也可以将PermenantThread的实现里RunLoop的创建用C语言的API来实现

/** Thread **/@interface Thread : NSThread@end@implementation Thread- (void)dealloc{ NSLog(@"%s", __func__);}@end/** PermenantThread **/@interface PermenantThread()@property (strong, nonatomic) Thread *innerThread;@end@implementation PermenantThread#pragma mark - public methods- (instancetype)init{ if (self = [super init]) { self.innerThread = [[Thread alloc] initWithBlock:^{ NSLog(@"begin----"); // 创建上下文(要初始化一下结构体) CFRunLoopSourceContext context = {0}; // 创建source CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); // 往Runloop中添加source CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); // 销毁source CFRelease(source); // 启动(第三个参数设置为false表示不退出,等同于加上while循环) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false); // while (weakSelf && !weakSelf.isStopped) {// // 第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop// CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);// } NSLog(@"end----"); }]; [self.innerThread start]; } return self;}//- (void)run//{// if (!self.innerThread) return;//// [self.innerThread start];//}- (void)executeTask:(PermenantThreadTask)task{ if (!self.innerThread || !task) return; [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];}- (void)stop{ if (!self.innerThread) return; [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];}- (void)dealloc{ NSLog(@"%s", __func__); [self stop];}#pragma mark - private methods- (void)__stop{ CFRunLoopStop(CFRunLoopGetCurrent()); self.innerThread = nil;}- (void)__executeTask:(PermenantThreadTask)task{ task();}@end

cf游戏录制视频软件

在经过近三个月的意见收集和细致开发后,玩家期待已久的CF2.0于今日正式登场。在此次为广大CFer带来全新火线游戏的新版本《革新时代》中,包括画面升级、全新模式及地图、枪王排位系统、反外挂神盾系统、更多GP武器、命运级新角色等外界关于2.0的诸多畅想都一一实现,从而使整个CF步入全新时代。随后,CF也将开启史上最大规模的全民豪礼以及商城优惠活动,带来2.0版本的年终狂欢!

CF2.0直观表现——品质升级、界面优化

在长达近三月的准备时间中,外界对CF2.0具体内容的猜测有着诸多版本,内容都有所不同。但在绝大多数的猜测中,画面升级及UI优化这两项更新都必不可少。而12月17日登场的CF2.0也没有让广大玩家失望,针对这一肉眼可直观感受的游戏内容做出巨大改变。

此次更新中,品质升级的对象是运输船、沙漠灰、金字塔、黑色城镇四张经典地图和赛斯、奥摩、斯沃特三个原始游戏角色。全新升级的4张地图中添加了更多的战斗元素和天气变化,并对地图中的战术和细节平衡点进行了优化,力求带给玩家更好的战斗体验;此外,新地图还具有5档画面可供玩家根据自己的喜好和游戏配置进行选择,在兼顾画面流畅性的前提下,让CFer在地图中感受更真切的战场硝烟。

角色方面,同样在兼顾画面流畅性的前提下,通过增加POLY面的数量,配合发线贴图技术,达到提高人物模型整体精度,造型精准,同时满足玩家对流畅性的要求和视觉习惯。更新后,升级的新地图将会加入到对应的团队竞技和爆破模式中,新角色将每位玩家赠送一款,同时经典老地图和原角色依旧保留,以确保可以满足全体玩家的喜好。

在地图品质升级外,界面UI的优化也是直接影响玩家体验的因素。在CF2.0中,不光分辨率从800提升至1024,界面中的好友与通知系统也进行了全新设计,包括更为简洁的好友系统功能区、信息摘要显示、重要信息弹窗、好友群组等新内容进一步保证了玩家及时获得各类通知和简易便捷的使用体验。

全新模式玩法为《穿越火线》注入活力

不断更新和进化的模式玩法是《穿越火线》保持活力,持续领航国内FPS游戏市场的重要原因。在CF2.0中,全新的“间谍模式”和“擂台战模式”又将带来一波全新的射击游戏体验。此外,测试已久的“枪王排位”系统也在CF2.0中正式上线,排位玩法所蕴含的巨大竞技属性无疑会让玩家感受更刺激的枪战世界。

“间谍模式”是植根于经典爆破模式的变种玩法,游戏中保卫者和潜伏者阵营各派出了一名间谍潜伏在敌方队伍中,小队要与间谍相互策应完成爆破或保卫的任务。虽然大体规则相同,但间谍的存在可以带来更丰富的战术变化和波澜起伏的比赛进程。同时,为了增加游戏的平衡性,模式为间谍制定了诸如初始主武器、副武器无子弹,每局前20秒无法造成伤害、伤敌后暴露等限制,使得游戏在既定框架下上演变幻莫测枪战对决。此外,随着一全新模式上线的还有其专属地图“布拉格”,一座充满传奇色彩的小城。

对决是枪战游戏的天然属性,在《穿越火线》中也早有大量玩家自发的进行1V1对抗。面对玩家的这一需求,CF2.0推出全新“擂台战”模式:在经典地图“运输船”中,双方阵营的玩家依次出战,对阵30秒的时间根据所受伤害值决定胜负,败北的阵营将换下一位玩家上场,直至一方所有玩家战败。相信这种充满豪迈气概的新模式将受到钢枪玩家的热捧,同时成为战队间实力对决的又一种方式。

除了创新性的新模式,CF2.0还迎来枪王排位正式入驻,这一通过玩家的实际游戏表现,将玩家实力数据化,又同时具有强大竞技属性的系统早在测试期间就备受外界期待。在正式上线后,通过游戏界面“匹配模式”按钮,玩家可以进入这一囊括团队竞技”“爆破模式”“战场-冰封要塞”“战场-沙漠风暴”四个模式的竞技系统。

枪王排位分为新锐、精英、大师、枪王四大段位,每个段位中又设立6个子段位。随着玩家在枪王排位系统中的不断精进,在每场战斗后,会根据玩家综合个人杀敌、死亡、助攻、拆弹等行为以及团队胜负等游戏表现,玩家的战力值将增加或减少,当战力值积分达到一定段位后玩家将获得对应的称号和技术统计。玩家的枪王排位段位既是进行游戏匹配的依据,也是其在CFer中竞技水平的直观展现。

新角色“审判者”领衔《革新时代》道具更新

CF游戏的历次版本更新都离不开新角色、新武器、新道具等丰富游戏内涵的内容推出。在此次革命性的“CF2.0”新版本中,游戏角色也迎来全新的命运级角色——审判者,该角色拥有自带近身攻击、暗影利刃、挑战模式移动增加、经验增加200%、同房间玩家经验值增加30%、同房间玩家GP增加20%等多种强力属性。同时在生化模式和挑战模式还能变声成为特殊的灵魂武者角色,可以说是真正全模式通杀的超能人物。

在对CF2.0的猜测中,增加GP用途也是众多玩家的呼声。新版本中,不光增加了AK-12(步枪)、TKB-0111和萨维奇110等新GP武器,还将游戏中原有的KTR-08(步枪)、乌兹双枪(冲锋枪)、M1613 LMG(机枪)、RGP(步枪)等众多CF点武器转化为GP购买,从而让玩家可以使用游戏金币体验更多精彩内容。

而除了GP武器,还上线全新的“SS”系列武器让玩家有更多持枪选择,包括M4A1、AK47、高爆手雷、手斧、G3A3、MP5、SPAS(散弹枪)、M37(散弹枪、骑士SR25(狙击枪)等众多武器都有SS版本推出。同时为迎接圣诞节,XM8-圣诞、汤姆逊-圣诞、圣诞节手雷、新年烟火和圣诞雪花名片及喷涂等节日道具也一同上线,为CF2.0再添精彩。

神盾系统携手绿色联盟2.0净化游戏环境

《穿越火线》一直致力于打造干净、无外挂的游戏环节,为数以百万的CFer提供公平、舒心的枪战体验。在CF2.0中,全新的反外挂及反BUG系统“火线神盾”携手绿色联盟2.0上线,拥有强大功能的全新反作弊机制,让广大玩家可以无后顾之忧的去战斗。

“火线神盾”是CF最新开发的反作弊系统,通过实时360度对全游戏地图的扫描,达到对常见的卡点,自瞄,透视等作弊手段彻底监督的目的,且在发现作弊玩家后就会对其采取清出乃至封号等惩罚措施。而“绿色联盟2.0”则是新增“视频举报”功能,玩家可以录制微型视频,将其上传给神盾系统进行审判,如果确定为非法软件将立刻予以处理。同时举报玩家还可获得审判积分,兑换丰厚道具礼包。

12.27将启全民狂欢 庆贺CF2.0上线10天

在国服运行6年多后,《穿越火线》迎来革命性变革,经典地图与角色升级、游戏界面优化、擂台战及间谍模式、海量GP武器、命运级角色、神盾系统、枪王排位等内容造就了一个全新的火线世界。而在CF2.0正式上线后10天后的12月27日,运营团队还将举行盛大的庆贺活动,届时,游戏准点在线的用户不仅能够领取价值惊人的“土豪礼包”,还可获得商城折扣价购买英雄武器的机会。全民狂欢,让我们一同期待!


声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,谢谢。

上一篇: 红茶几月份采摘(红茶什么时候买最好)

下一篇: ab血型的 和缺点(AB血型的厉害之处)



猜你感兴趣

推荐阅读

网站内容来自网络,如有侵权请联系我们,立即删除! | 软文发布 | 粤ICP备2021106084号