如何做公司的英文网站,wordpress主题生成器,某个产品营销推广方案,企业融资是做什么的文章目录 前言-什么是RunLoop#xff1f;默认情况下主线程的RunLoop原理 1. RunLoop对象RunLoop对象的获取 CFRunLoopRef源码部分#xff08;引入线程相关#xff09; 2. RunLoop和线程3. RunLoop相关的类RunLoop相关类的实现CFRunLoopModeRef五种运行模式CommonModes CFRun… 文章目录 前言-什么是RunLoop默认情况下主线程的RunLoop原理 1. RunLoop对象RunLoop对象的获取 CFRunLoopRef源码部分引入线程相关 2. RunLoop和线程3. RunLoop相关的类RunLoop相关类的实现CFRunLoopModeRef五种运行模式CommonModes CFRunLoopSourceRefCFRunLoopTimerRef定时器滑动不准确 CFRunLoopObserverRef什么是Mode Item RunLoop内部逻辑RunLoop休眠的实现原理RunLoop小结 4. RunLoop实际应用RunLoop的启动方法RunLoop关闭imageView延迟显示常驻线程线程保活NSTimer不准 前言-什么是RunLoop
什么是RunLoop? 跑圈字面上理解确实是这样的。
Apple官方文档这样解释RunLoop RunLoop是与线程息息相关的基本结构的一部分。RunLoop是一个调度任务和处理任务的事件循环。RunLoop的目的是为了在有工作的时候让线程忙起来而在没有工作的时候让线程进入休眠状态。 之所以iOS的app能够持续的响应从而让程序保持运行状态在于其存在一个事件循环Event Loop机制 线程能够随时响应并处理事件的机制这种机制要求线程不能退出从而高效的完成事件调度和处理。
在iOS这种事件循环机制就叫做RunLoop
RunLoop实际上是一个对象对象在循环中处理程序运行过程出现的各种事件比如触摸事件UI刷新事件定时器事件Selector事件从而保持程序的持续运行并且让程序在没有事件处理的时候进入休眠状态从而节省CPU资源达到提升程序性能的目的。
默认情况下主线程的RunLoop原理
#import UIKit/UIKit.h
#import AppDelegate.hint main(int argc, char * argv[]) {NSString * appDelegateClassName;autoreleasepool {// Setup code that might create autoreleased objects goes here.appDelegateClassName NSStringFromClass([AppDelegate class]);}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);: 这是 iOS 应用程序的主运行循环它负责处理用户事件、界面更新和应用程序的主要逻辑。UIApplicationMain 函数创建应用程序对象和主运行循环并传递控制权给应用程序的委托类AppDelegate来处理应用程序的逻辑。
其中的UIApplicationMain函数内部帮我们开启了主线程的RunLoop。UIApplicationMain内部拥有一个无限循环的代码。
function loop() {initialize();do {var message get_next_message();process_message(message);} while (message ! quit);
}程序会一直在do-while循环中执行.
Apple官方的RunLoop模型图
RunLoop就是线程中的一个循环RunLoop在循环中不断检测通过Input sources输入源和Timer sources定时源两种来源等待接受消息然后对接收到的事件通知线程进行处理并在没有事件的时候进行休息。
1. RunLoop对象
RunLoop是一个对象。
RunLoop对象的获取
RunLoop对象是基于CFFoundation框架的CFRunLoopRef类型封装的对象。
NSRunLoop是基于CFRunLoopRef的封装提供了面向对象的API但是这些API不是线程安全的。
[NSRunLoop currentRunLoop];//获得当前RunLoop对象
[NSRunLoop mainRunLoop];//获得主线程的RunLoop对象CoreFoundation框架的 CFRunLoopRef对象
CFRunLoopRef是在CoreFoundation框架内的其提供了纯C语言函数的API所有这些API都是线程安全的。
CFRunLoopGetCurrent();//获得当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象 那么对应的两种方式就是
- (void)getRunLoop {NSRunLoop *runloop [ NSRunLoop currentRunLoop];NSRunLoop *manRlp [NSRunLoop mainRunLoop];CFRunLoopRef cfRlp CFRunLoopGetCurrent();CFRunLoopRef mainCfRlp CFRunLoopGetMain();
}看下CFRunLoopGetCurrent 和 CFRunLoopGetMain的具体实现
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这个函数后面再进行讲解。
CFRunLoopRef源码部分引入线程相关
CFRunLoopRef的源码部分
struct __CFRunLoop {CFRuntimeBase _base;pthread_mutex_t _lock; /* locked for accessing mode list */__CFPort _wakeUpPort; //【通过该函数CFRunLoopWakeUp内核向该端口发送消息可以唤醒runloop】Boolean _unused;volatile _per_run_data *_perRunData; // reset for runs of the run looppthread_t _pthread; //【RunLoop对应的线程】uint32_t _winthread;CFMutableSetRef _commonModes; // 【存储的是字符串记录所有标记为common的mode】CFMutableSetRef _commonModeItems;//【存储所有commonMode的item(source、timer、observer)】CFRunLoopModeRef _currentMode;//【当前运行的mode】CFMutableSetRef _modes;//【存储的是CFRunLoopModeRef】struct _block_item *_blocks_head;//【do blocks时用到】struct _block_item *_blocks_tail;CFTypeRef _counterpart;
};
对于一些属性之外重点需要关注三个成员变量
pthread_t _pthread;【RunLoop对应的线程】
CFRunLoopModeRef _currentMode;【当前运行的mode】
CFMutableSetRef _modes;【存储的是CFRunLoopModeRef】看看RunLoop和线程的关系
2. RunLoop和线程
先看一下_CFRunLoopGet0这个函数是怎么实现的和RunLoop和线程有什么关系。
//全局的Dictionarykey是pthread_t,value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops NULL;
//访问__CFRunLoops的锁
static CFSpinLock_t loopsLock CFSpinLockInit;// should only be called by Foundation
// t0 is a synonym for main thread that always works
//t0是始终有效的“主线程”的同义词//获取pthread对应的RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {if (pthread_equal(t, kNilPthreadT)) {//pthread为空时获取主线程t pthread_main_thread_np();}__CFSpinLock(loopsLock);if (!__CFRunLoops) {__CFSpinUnlock(loopsLock);//第一次进入时创建一个临时字典dictCFMutableDictionaryRef dict CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, kCFTypeDictionaryValueCallBacks);//根据传入的主线程获取主线程对应的RunLoopCFRunLoopRef mainLoop __CFRunLoopCreate(pthread_main_thread_np());//保存主线程将主线程-key和RunLoop-Value保存到字典中CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);//此处NULL和__CFRunLoops指针都指向NULL匹配所以将dict写到__CFRunLoopsif (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)__CFRunLoops)) {//释放dictCFRelease(dict);}//释放mainRunLoopCFRelease(mainLoop);__CFSpinLock(loopsLock);}//以上说明第一次进来的时候不管是getMainRunLoop还是get子线程的runLoop主线程的runLoop总是会被创建//从全局字典里获取对应的RunLoopCFRunLoopRef loop (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));__CFSpinUnlock(loopsLock);if (!loop) {//如果取不到就创建一个新的RunLoopCFRunLoopRef newLoop __CFRunLoopCreate(t);__CFSpinLock(loopsLock);loop (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));//创建好之后以线程为keyrunLoop为value一对一存储在字典中下次获取的时候则直接返回字典内的runLoopif (!loop) {//把newLoop存入字典__CFRunLoopskey是线程tCFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);loop newLoop;}// dont release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it__CFSpinUnlock(loopsLock);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);}}return loop;
}
这段源码告诉我们
每条线程都有唯一的与之对应的RunLoop对象。RunLoop保存在一个全局的Dictionary里面线程作为keyRunLoop作为Value线程刚创建的并没有RunLoop对象。RunLoop会在第一次获取线程的RunLoop创建在线程结束的时候销毁。主线程的RunLoop已经自动获取创建子线程默认没有开启RunLoop.
3. RunLoop相关的类
与RunLoop相关的类有5个。
CFRunLoopRef: 代表了RunLoop对象CFRunLoopModeRef: 代表了RunLoop的运行模式CFRunLoopSourceRef RunLoop模型中提到的输入源。CFRunLoopTimerRef: 定时源CFRunLoopObserverRef: 观察者监听RunLoop状态的改变。
一个RunLoop包含若干个Mode每个Mode又包含若干个Source / Timer / Observer。 每次调用RunLoop的主函数的时候 只允许指定其中的一个运行模式CFRunLoopModeRef就是被称作CurrentMode;如果需要切换Mode只能通过退出Loop然后需要重新指定一个Mode进入这样做主要是为了分隔开不同组的输入源CFRunLoopSourceRef、定时源CFRunLoopTimerRef、观察者CFRunLoopObserverRef让其互不影响如果一个mode中一个Sourcr/Timer/Observer都没有则RunLoop会直接退出不进入循环。
RunLoop的结构和套娃一样RunLoop里面装着ModeMode里面装着Souce / Observer / Timer
RunLoop相关类的实现
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer。 这句话其实就是5个相关类的关系
CFRunLoopModeRef
代表了RunLoop的运行模式但这里请理清概念我们的RunLoop里可以装多个Mode只是我们在指定运行的时候要指定一个Mode
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_tvalue是CFRunLoopSourceRef__CFPortSet _portSet;//保存所有需要监听的port比如_wakeUpPort_timerPort都保存在这个数组中CFIndex _observerMask;
};
一个CFRunLoopModeRef对象有name属性若干source0 source1 timer observer和port可以看出来事件都是由mode在管理而RunLoop负责管理Mode
五种运行模式
系统默认注册的五个Mode
kCFRunLoopDefaultModeApp的默认Mode通常主线程是在这个Mode下运行UITrackingRunLoopMode界面跟踪Mode用于ScrollView追踪触摸滑动保证界面滑动时不受其他 Mode 影响与用户交互事件的ModeUIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode启动完成后就不再使用会切换到kCFRunLoopDefaultModeGSEventReceiveRunLoopMode: 接受系统事件的内部 Mode通常用不到kCFRunLoopCommonModes: 这是一个占位用的Mode作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用并不是一种真正的Mode 伪模式并不是一种真正的模式
其中kCFRunLoopDefaultMode、UITrackingRunLoopMode、kCFRunLoopCommonModes是我们开发中需要用到的模式。
CommonModes
在RunLoop对象中前面有一个CommonModes成员变量。
//简化版本
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);
}
总体来说这个方法的主要作用是将一个指定的 commonModeItems 集合添加到运行循环的共同模式集合中并将 commonModeItems 添加到共同模式集合中的每个 Mode 中以确保共同模式的事件源在多个 Mode 下都能得到处理。它涉及到 CoreFoundation 框架中运行循环的底层操作用于管理运行循环中的事件和模式。
CFRunLoopSourceRef
CFRunLoopSourceRef RunLoop模型中提到的输入源,也就是事件产生的地方。
struct __CFRunLoopSource {CFRuntimeBase _base;uint32_t _bits;pthread_mutex_t _lock;CFIndex _order; //执行顺序CFMutableBagRef _runLoops;//包含多个RunLoop//版本union {CFRunLoopSourceContext version0; /* immutable, except invalidation */CFRunLoopSourceContext1 version1; /* immutable, except invalidation */} _context;
};
刚才上面提到souce存在 source0 和 source1两个版本他们分别做了什么
Source0只包含了一个回调函数指针它并不能主动触发事件。使用时你需要先调用CFRunLoopSourceSignal(source), 将这个Source标记为待处理然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件Source1包含了一个mach_port和一个回调函数指针可以用于内核和其他线程相互发送消息这种Source能主动唤醒RunLoop线程。⚠️对于button的点击事件属于Source0函数的执行内容。点击事件就是Source0进行处理的。Source1则是用来接受和分发事件分发到Souce0进行处理。
CFRunLoopTimerRef
CFRunLoopTimerRef: 定时源- 基于时间的触发器。
CFRunLoopTimerRef是基于时间的触发器它和NSTimer可以混用。其包含一个时间长度和一个回调函数指针。当其加入到RunLoop时RunLoop会注册对应的时间点当时间点到时RunLoop会被唤醒以执行那个回调。
当我们调用NSTimer的scheduledTimerWithTimeInterval的时候
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:selector(run) userInfo:nil repeats:YES];
系统会自动加入NSDefaltRunLoopMode.
等于如下代码
NSTimer *timer [NSTimer timerWithTimeInterval:2.0 target:self selector:selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];定时器滑动不准确
在知乎日报的时候滑动tableView造成了上面自动轮播图的定时器失效问题。 常见的问题就是当我们使用NSTimer每一段时间执行一些事情时滑动UIScrollViewNSTimer就会暂停当我们停止滑动以后NSTimer又会重新恢复的情况
原因就是
当我们不做任何操作的时候RunLoop处于NSDefaultRunLoopMode下。当我们进行拖拽时RunLoop就结束NSDefaultRunLoopMode切换到了UITrackingRunLoopMode模式下这个模式下没有添加NSTimer所以我们的NSTimer就不工作了。当我们松开鼠标时候RunLoop就结束UITrackingRunLoopMode模式又切换回NSDefaultRunLoopMode模式所以NSTimer就又开始正常工作了。
解决
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
在 iOS 中当你滑动 UITableView 或其他滚动视图时主线程上的 RunLoop 切换到 UITrackingRunLoopMode这是一个特殊的运行循环模式用于处理用户交互事件例如滚动手势。在默认情况下如果你在主线程上使用定时器它会在默认的运行循环模式 NSDefaultRunLoopMode 下运行。由于 RunLoop 一次只能处理一个运行循环模式当你滑动时NSDefaultRunLoopMode 被切换到 UITrackingRunLoopMode导致定时器事件暂停直到滑动结束。
为了解决这个问题可以使用 kCFRunLoopCommonModes。代表了一个 “common mode set”它同时包括了 NSDefaultRunLoopMode 和 UITrackingRunLoopMode。通过在定时器的添加中使用 kCFRunLoopCommonModes可以使定时器在默认模式和追踪模式下都得到触发从而避免滑动导致定时器暂停的问题。
CFRunLoopObserverRef
CFRunLoopObserverRef是观察者每个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;//上下文用于内存管理
};//观测的时间点有一下几个
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
};什么是Mode Item
Mode到底包含哪些类型的元素 前面提到过 CFMutableSetRef _commonModeItems存储所有commonMode的item (source、timer、observer)
上面的 Source/Timer/Observer 被统称为 mode item
所有的mode item都可以被添加到Mode中Mode中可以包含多个mode item一个item也可以被加入多个mode。但一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有则RunLoop会退出不进入循环.
RunLoop内部逻辑 RunLoop休眠的实现原理
从用户态切换到内核态 在内核态让线程进行休眠有消息的时候唤起线程回到用户态处理消息
RunLoop小结
RunLoop内部实际是一个do while循环当调用CFRunLoopRun()的时候线程就会一直停留在这个循环里面当超时或者被手动调用的时候该函数才会返回。
RunLoop的运行必定要指定一种mode并且该mode必须注册任务事件。RunLoop是在默认mode下运行的当然也可以指定一种mode运行但是只能在一种mode下运行。RunLoop内部实际上是维护了一个do-while循环线程就会一直留在这个循环里面直到超时或者手动被停止。RunLoop 的核心就是一个 mach_msg() RunLoop 调用这个函数去接收消息如果没有别人发送 port 消息过来内核会将线程置于等待状态否则线程处理事件
4. RunLoop实际应用
控制线程生命周期线程保活解决NSTimer在滑动时停止工作的问题监控应用卡顿性能优化
RunLoop的启动方法
- (void)run; // 默认模式
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;run没有任何条件就可以启动虽然简单但是实际的操控结果并不影响RunLoop是一个do while循环倘若无条件的运行RunLoop将线程永远的放入循环这就使我们没有办法控制循环本身只能靠杀死进程来停止RunLooprunUnitDate设置时间限制。
设置了超时的时间超过这个时间RunLoop就结束了。
runMode:beforeDate:在特定模式下启动。
可以指定runloop以哪种模式运行但是它是单次调用的超时时间到达或者一个输入源被处理则runLoop就会自动退出上述两种方式都是循环调用的
⚠️
第一种会一直运行下去并且一直在NSDefaultRunLoopMode模式下重复调用runMode:beforeDate:方法。第二种会在超时时间之前一直在NSDefaultRunLoopMode模式下调用runMode:beforeDate:方法。第三种则会在超时时间到达或者第一个inputsource被处理前一直调用runMode:beforeDate:方法。
RunLoop关闭
将运行的循环配置设置为超时。手动停止
这里需要注意虽然删除runloop的输入源和定时器可能会导致运行循环的退出但这并不是个可靠的方法系统可能会添加输入源到runloop中但在我们的代码中可能并不知道这些输入源因此无法删除它们导致无法退出runloop。
当我们通过 runUnitDate 和 runMode: beforeDate:方法启动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]];
用户点击屏幕在主线程中三秒之后显示图片但是当用户点击屏幕之后如果此时用户又开始滚动tableview那么就算过了三秒图片也不会显示出来当用户停止了滚动才会显示图片。
这是因为限定了方法setImage只能在NSDefaultRunLoopMode模式下使用。而滚动tableview的时候程序运行在tracking模式下面所以方法setImage不会执行。
常驻线程
开发应用程序的过程中如果后台操作十分频繁比如后台播放音乐、下载文件等等我们希望执行后台代码的这条线程永远常驻内存我们可以添加一条用于常驻内存的强引用子线程在该线程的RunLoop下添加一个Sources开启RunLoop
interface ViewController ()
property (nonatomic, strong) NSThread *thread;
endself.thread [[NSThread alloc] initWithTarget:self selector:selector(runThread) object:nil];[self.thread start];- (void)runThread {NSLog(开启子线程%, [NSThread currentThread]);
// 子线程的RunLoop创建出来需要手动添加事件输入源和定时器 因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器就会立马消亡。//下面的方法给runloop添加一个NSport就是添加一个事件源也可以添加一个定时器或者observer让runloop不会挂掉[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];[[NSRunLoop currentRunLoop] run];// 测试开始RunLoop// 未进入循环就会执行该代码NSLog(failed);
}// 同时在我们自己新建立的这个线程中写一下touchesBegan这个方法测试点击空白处会不会在子线程相应方法
- (void)touchesBegan:(NSSetUITouch * *)touches withEvent:(UIEvent *)event {[self performSelector:selector(runTest) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)runTest {NSLog(子线程点击空白%, [NSThread currentThread]);
}可以看到RunLoop成功启动进入循环点击屏幕的时候也是在子线程调用方法这样子子线程启动完成之后就达到了常驻线程的目的。
线程保活
场景 平时创建子线程时线程上的任务执行完这个线程就会销毁掉。 有时我们会需要经常在一个子线程中执行任务频繁的创建和销毁线程就会造成很多的开销这时我们可以通过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没有itemrunloop就会退出[[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__);
}
NSTimer不准
timer在实际的开发中一般不在主线程的RunLoop里面存在因为主线程在执行阻塞任务的时候timer的计时器也会导致不准确。
如果timer在主线程里面阻塞 如何解决timer不准确的问题。
放入子线程中但是需要开辟线程和控制线程的生命周期成本较大。使用GCD的定时器计时避免阻塞。