一、SDWebImage原理
二、什么是Block?
三、RunLoop剖析
一个为UIImageView提供一个分类来支持远程服务器图片加载的库。前端
一、一个添加了web图片加载和缓存管理的UIImageView分类
二、一个异步图片下载器
三、一个异步的内存加磁盘综合存储图片而且自动处理过时图片
四、支持动态gif图
五、支持webP格式的图片
六、后台图片解压处理
七、确保一样的图片url不会下载屡次
八、确保伪造的图片url不会重复尝试下载
九、确保主线程不会阻塞
复制代码
一、入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,而后 SDWebImageManager 根据 URL 开始处理图片。
二、进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
三、先从内存图片缓存查找是否有图片,若是内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
四、SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展现图片。
五、若是内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
六、根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操做,因此回主线程进行结果回调 notifyDelegate:。
七、若是上一操做从硬盘读取到了图片,将图片添加到内存缓存中(若是空闲内存太小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展现图片。
八、若是从硬盘缓存目录读取不到图片,说明全部缓存都不存在该图片,须要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
九、共享或从新生成一个下载器 SDWebImageDownloader 开始下载图片。
十、图片下载由 NSURLConnection 来作,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
十一、connection:didReceiveData: 中利用 ImageIO 作了按图片下载进度加载效果。connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 作图片解码处理。
十二、图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。若是有须要对下载的图片进行二次处理,最好也在这里完成,效率会好不少。
1三、在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
1四、通知全部的 downloadDelegates 下载完成,回调给须要的地方展现图片。将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
1五、SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过时图片。
1六、SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
1七、SDWebImagePrefetcher 能够预先下载图片,方便后续使用。
复制代码
一、 SDWebImageDownloaderweb
1.单例,图片下载器,负责图片异步下载,并对图片加载作了优化处理数组
2.图片的下载操做放在一个NSOperationQueue并发操做队列中,队列默认最大并发数是6缓存
3.每一个图片对应一些回调(下载进度,完成回调等),回调信息会存在downloader的URLCallbacks(一个字典,key是url地址,value是图片下载回调数组)中,URLCallbacks可能被多个线程访问,因此downloader把下载任务放在一个barrierQueue中,并设置屏障保证同一时间只有一个线程访问URLCallbacks。,在建立回调URLCallbacks的block中建立了一个NSOperation并添加到NSOperationQueue中。安全
4.每一个图片下载都是一个operation类,建立后添加到一个队列中,SDWebimage定义了一个协议 SDWebImageOperation做为图片下载操做的基础协议,声明了一个cancel方法,用于取消操做。bash
@protocol SDWebImageOperation <NSObject>
-(void)cancel;
@end
复制代码
connection:didReceiveResponse:
connection:didReceiveData:
connectionDidFinishLoading:
connection:didFailWithError:
connection:willCacheResponse:
connectionShouldUseCredentialStorage:
-connection:willSendRequestForAuthenticationChalleng
-connection:didReceiveData:方法,接受数据,建立一个CGImageSourceRef对象,在首次获取数据时(图片width,height),图片下载完成以前,使用CGImageSourceRef对象建立一个图片对象,通过缩放、解压操做生成一个UIImage对象供回调使用,同时还有下载进度处理。
注:缩放:SDWebImageCompat中SDScaledImageForKey函数
解压:SDWebImageDecoder文件中decodedImageWithImage
复制代码
二、SDWebImageDownloaderOption服务器
1.继承自NSOperation类,没有简单实现main方法,而是采用更加灵活的start方法,以便本身管理下载的状态网络
2.start方法中建立了下载使用的NSURLConnections对象,开启了图片的下载,并抛出一个下载开始的通知,数据结构
3.小结:下载的核心是利用NSURLSession加载数据,每一个图片的下载都有一个operation操做来完成,并将这些操做放到一个操做队列中,这样能够实现图片的并发下载。并发
三、SDWebImageDecoder(异步对图片进行解码)
减小网络流量,下载完图片后存储到本地,下载再获取同一张图片时,直接从本地获取,提高用户体验,能快速从本地获取呈现给用户。 SDWebImage提供了对图片进行了缓存,主要由SDImageCache完成。该类负责处理内存缓存以及一个可选的磁盘缓存,其中磁盘缓存的写操做是异步的,不会对UI形成影响。
一、内存缓存及磁盘缓存
1.内存缓存的处理由NSCache对象实现,NSCache相似一个集合的容器,它存储key-value对,相似于nsdictionary类,咱们一般使用缓存来临时存储短期使用但建立昂贵的对象,重用这些对象能够优化新能,同时这些对象对于程序来讲不是紧要的,若是内存紧张就会自动释放。
2.磁盘缓存的处理使用NSFileManager对象实现,图片存储的位置位于cache文件夹,另外SDImageCache还定义了一个串行队列来异步存储图片。
3.SDImageCache提供了大量方法来缓存、获取、移除及清空图片。对于图片的索引,咱们经过一个key来索引,在内存中,咱们将其做为NSCache的key值,而在磁盘中,咱们用这个key值做为图片的文件名,对于一个远程下载的图片其url实做为这个key的最佳选择。
二、存储图片 先在内存中放置一份缓存,若是须要缓存到磁盘,将磁盘缓存操做做为一个task放到串行队列中处理,会先检查图片格式是jpeg仍是png,将其转换为响应的图片数据,最后吧数据写入磁盘中(文件名是对key值作MD5后的串)
三、查询图片 内存和磁盘查询图片API:
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;
复制代码
查看本地是否存在key指定的图片,使用一下API:
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
复制代码
四、移除图片 移除图片API:
- (void)removeImageForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;
复制代码
五、清理图片(磁盘)
清空磁盘图片能够选择彻底清空和部分清空,彻底清空就是吧缓存文件夹删除。
- (void)clearDisk;
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
复制代码
部分清理 会根据设置的一些参数移除部分文件,主要有两个指标:文件的缓存有效期(maxCacheAge:默认是1周)和最大缓存空间大小(maxCacheSize:若是全部文件大小大于最大值,会按照文件最后修改时间的逆序,以每次一半的递归来移除哪些过早的文件,知道缓存文件总大小小于最大值),具体代码参考- (void)cleanDiskWithCompletionBlock;
六、小结 SDImageCache处理提供以上API,还提供了获取缓存大小,缓存中图片数量等API, 经常使用的接口和属性:
(1)-getSize :得到硬盘缓存的大小
(2)-getDiskCount : 得到硬盘缓存的图片数量
(3)-clearMemory : 清理全部内存图片
(4)- removeImageForKey:(NSString *)key 系列的方法 : 从内存、硬盘按要求指定清除图片
(5)maxMemoryCost : 保存在存储器中像素的总和
(6)maxCacheSize : 最大缓存大小 以字节为单位。默认没有设置,也就是为0,而清理磁盘缓存的先决条件为self.maxCacheSize > 0,因此0表示无限制。
(7)maxCacheAge : 在内存缓存保留的最长时间以秒为单位计算,默认是一周
复制代码
实际使用中并不直接使用SDWebImageDownloader和SDImageCache类对图片进行下载和存储,而是使用SDWebImageManager来管理。包括日常使用UIImageView+WebCache等控件的分类,都是使用SDWebImageManager来处理,该对象内部定义了一个图片下载器(SDWebImageDownloader)和图片缓存(SDImageCache)
@interface SDWebImageManager : NSObject
@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
...
@end
复制代码
SDWebImageManager声明了一个delegate属性,实际上是一个id对象,代理声明了两个方法
// 控制当图片在缓存中没有找到时,应该下载哪一个图片
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
// 容许在图片已经被下载完成且被缓存到磁盘或内存前当即转换
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
复制代码
这两个方法会在SDWebImageManager的-downloadImageWithURL:options:progress:completed:方法中调用,而这个方法是SDWebImageManager类的核心所在(具体看源码)
SDWebImageManager的几个API:
(1)- (void)cancelAll : 取消runningOperations中全部的操做,并所有删除
(2)- (BOOL)isRunning :检查是否有操做在运行,这里的操做指的是下载和缓存组成的组合操做
(3) - downloadImageWithURL:options:progress:completed: 核心方法
(4)- (BOOL)diskImageExistsForURL:(NSURL *)url :指定url的图片是否进行了磁盘缓存
复制代码
在使用SDWebImage的时候,使用最多的是UIImageView+WebCache中的针对UIImageView的扩展,核心方法是sd_setImageWithURL:placeholderImage:options:progress:completed:, 其使用SDWebImageManager单例对象下载并缓存图片。
除了扩展UIImageView外,SDWebImage还扩展了UIView,UIButton,MKAnnotationView等视图类,具体能够参考源码,除了可使用扩展的方法下载图片,同时也可使用SDWebImageManager下载图片。
UIView+WebCacheOperation分类: 把当前view对应的图片操做对象存储起来(经过运行时设置属性),在基类中完成 存储的结构:一个loadOperationKey属性,value是一个字典(字典结构: key:UIImageViewAnimationImages或者UIImageViewImageLoad,value是 operation数组(动态图片)或者对象)
UIButton+WebCache分类 会根据不一样的按钮状态,下载的图片根据不一样的状态进行设置 imageURLStorageKey:{state:url}
好比:
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
block(2);
复制代码
经过clang -rewrite-objc WYTest.m命令编译该.m文件,发现该block被编译成这个形式:
NSInteger num = 3;
NSInteger(*block)(NSInteger) = ((NSInteger (*)(NSInteger))&__WYTest__blockTest_block_impl_0((void *)__WYTest__blockTest_block_func_0, &__WYTest__blockTest_block_desc_0_DATA, num));
((NSInteger (*)(__block_impl *, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2);
复制代码
其中WYTest是文件名,blockTest是方法名,这些能够忽略。 其中__WYTest__blockTest_block_impl_0结构体为
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0* Desc;
NSInteger num;
__WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
__block_impl结构体为
struct __block_impl {
void *isa;//isa指针,因此说Block是对象
int Flags;
int Reserved;
void *FuncPtr;//函数指针
};
复制代码
block内部有isa指针,因此说其本质也是OC对象 block内部则为:
static NSInteger __WYTest__blockTest_block_func_0(struct __WYTest__blockTest_block_impl_0 *__cself, NSInteger n) {
NSInteger num = __cself->num; // bound by copy
return n*num;
}
复制代码
因此说 Block是将函数及其执行上下文封装起来的对象 既然block内部封装了函数,那么它一样也有参数和返回值。
一、局部变量截获 是值截获。 好比:
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%zd",block(2));
复制代码
这里的输出是6而不是2,缘由就是对局部变量num的截获是值截获。 一样,在block里若是修改变量num,也是无效的,甚至编译器会报错。
二、局部静态变量截获 是指针截获。
static NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%zd",block(2));
复制代码
输出为2,意味着num = 1这里的修改num值是有效的,便是指针截获。 一样,在block里去修改变量m,也是有效的。
三、全局变量,静态全局变量截获:不截获,直接取值。
咱们一样用clang编译看下结果。
static NSInteger num3 = 300;
NSInteger num4 = 3000;
- (void)blockTest
{
NSInteger num = 30;
static NSInteger num2 = 3;
__block NSInteger num5 = 30000;
void(^block)(void) = ^{
NSLog(@"%zd",num);//局部变量
NSLog(@"%zd",num2);//静态变量
NSLog(@"%zd",num3);//全局变量
NSLog(@"%zd",num4);//全局静态变量
NSLog(@"%zd",num5);//__block修饰变量
};
block();
}
复制代码
编译后
struct __WYTest__blockTest_block_impl_0 {
struct __block_impl impl;
struct __WYTest__blockTest_block_desc_0* Desc;
NSInteger num;//局部变量
NSInteger *num2;//静态变量
__Block_byref_num5_0 *num5; // by ref//__block修饰变量
__WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
( impl.isa = &_NSConcreteStackBlock;这里注意到这一句,即说明该block是栈block) 能够看到局部变量被编译成值形式,而静态变量被编成指针形式,全局变量并未截获。而__block修饰的变量也是以指针形式截获的,而且生成了一个新的结构体对象:
struct __Block_byref_num5_0 {
void *__isa;
__Block_byref_num5_0 *__forwarding;
int __flags;
int __size;
NSInteger num5;
};
复制代码
该对象有个属性:num5,即咱们用__block修饰的变量。 这里__forwarding是指向自身的(栈block)。 通常状况下,若是咱们要对block截获的局部变量进行赋值操做需添加__block 修饰符,而对全局变量,静态变量是不须要添加__block修饰符的。 另外,block里访问self或成员变量都会去截获self。
分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式
其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区
一、不使用外部变量的block是全局block
好比:
NSLog(@"%@",[^{
NSLog(@"globalBlock");
} class]);
复制代码
输出:
__NSGlobalBlock__
复制代码
二、使用外部变量而且未进行copy操做的block是栈block
好比:
NSInteger num = 10;
NSLog(@"%@",[^{
NSLog(@"stackBlock:%zd",num);
} class]);
复制代码
输出:
__NSStackBlock__
复制代码
平常开发经常使用于这种状况:
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block {
block();
NSLog(@"%@",[block class]);
}
复制代码
三、对栈block进行copy操做,就是堆block,而对全局block进行copy,还是全局block
void (^globalBlock)(void) = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",[globalBlock class]);
复制代码
输出:
__NSGlobalBlock__
复制代码
还是全局block
NSInteger num = 10;
void (^mallocBlock)(void) = ^{
NSLog(@"stackBlock:%zd",num);
};
NSLog(@"%@",[mallocBlock class]);
复制代码
输出:
__NSMallocBlock__
复制代码
对栈blockcopy以后,并不表明着栈block就消失了,左边的mallock是堆block,右边被copy的还是栈block 好比:
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block
{
block();
dispatch_block_t tempBlock = block;
NSLog(@"%@,%@",[block class],[tempBlock class]);
}
复制代码
输出:
__NSStackBlock__,__NSMallocBlock__
复制代码
另外,__block变量在copy时,因为__forwarding的存在,栈上的__forwarding指针会指向堆上的__forwarding变量,而堆上的__forwarding指针指向其自身,因此,若是对__block的修改,其实是在修改堆上的__block变量。
即__forwarding指针存在的意义就是,不管在任何内存位置, 均可以顺利地访问同一个__block变量。
__block typeof(self) weakSelf = self;
_testBlock = ^{
NSLog(@"%@",weakSelf);
};
_testBlock();
复制代码
若是要解决这种循环引用,能够主动断开__block变量对self的持有,即在block内部使用完weakself后,将其置为nil,但这种方式有个问题,若是block一直不被调用,那么循环引用将一直存在。 因此,咱们最好仍是用__weak来修饰self
RunLoop是经过内部维护的事件循环(Event Loop)
来对事件/消息进行管理
的一个对象。
一、没有消息处理时,休眠已避免资源占用,由用户态切换到内核态(CPU-内核态和用户态) 二、有消息须要处理时,马上被唤醒,由内核态切换到用户态
为何main函数不会退出?
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
UIApplicationMain内部默认开启了主线程的RunLoop,并执行了一段无限循环的代码(不是简单的for循环或while循环)
//无限循环代码模式(伪代码)
int main(int argc, char * argv[]) {
BOOL running = YES;
do {
// 执行各类任务,处理各类事件
// ......
} while (running);
return 0;
}
复制代码
UIApplicationMain函数一直没有返回,而是不断地接收处理消息以及等待休眠,因此运行程序以后会保持持续运行状态。
NSRunLoop(Foundation)
是CFRunLoop(CoreFoundation)
的封装,提供了面向对象的API RunLoop 相关的主要涉及五个类:
CFRunLoop
:RunLoop对象 CFRunLoopMode
:运行模式 CFRunLoopSource
:输入源/事件源 CFRunLoopTimer
:定时源 CFRunLoopObserver
:观察者
一、CFRunLoop
由pthread
(线程对象,说明RunLoop和线程是一一对应的)、currentMode
(当前所处的运行模式)、modes
(多个运行模式的集合)、commonModes
(模式名称字符串集合)、commonModelItems
(Observer,Timer,Source集合)构成
二、CFRunLoopMode
由name、source0、source一、observers、timers构成
三、CFRunLoopSource
分为source0和source1两种
source0
: 即非基于port的,也就是用户触发的事件。须要手动唤醒线程,将当前线程从内核态切换到用户态source1
: 基于port的,包含一个 mach_port 和一个回调,可监听系统端口和经过内核和其余线程发送的消息,能主动唤醒RunLoop,接收分发系统事件。 具有唤醒线程的能力四、CFRunLoopTimer
基于时间的触发器,基本上说的就是NSTimer。在预设的时间点唤醒RunLoop执行回调。由于它是基于RunLoop的,所以它不是实时的(就是NSTimer 是不许确的。 由于RunLoop只负责分发源的消息。若是线程当前正在处理繁重的任务,就有可能致使Timer本次延时,或者少执行一次)。
五、CFRunLoopObserver
监听如下时间点:CFRunLoopActivity
kCFRunLoopEntry
RunLoop准备启动kCFRunLoopBeforeTimers
RunLoop将要处理一些Timer相关事件kCFRunLoopBeforeSources
RunLoop将要处理一些Source事件kCFRunLoopBeforeWaiting
RunLoop将要进行休眠状态,即将由用户态切换到内核态kCFRunLoopAfterWaiting
RunLoop被唤醒,即从内核态切换到用户态后kCFRunLoopExit
RunLoop退出kCFRunLoopAllActivities
监听全部状态六、各数据结构之间的联系
线程和RunLoop一一对应, RunLoop和Mode是一对多的,Mode和source、timer、observer也是一对多的
关于Mode首先要知道一个RunLoop 对象中可能包含多个Mode,且每次调用 RunLoop 的主函数时,只能指定其中一个 Mode(CurrentMode)。切换 Mode,须要从新指定一个 Mode 。主要是为了分隔开不一样的 Source、Timer、Observer,让它们之间互不影响。
当RunLoop运行在Mode1上时,是没法接受处理Mode2或Mode3上的Source、Timer、Observer事件的
总共是有五种CFRunLoopMode
:
kCFRunLoopDefaultMode
:默认模式,主线程是在这个运行模式下运行
UITrackingRunLoopMode
:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其余Mode影响)
UIInitializationRunLoopMode
:在刚启动App时第进入的第一个 Mode,启动完成后就再也不使用
GSEventReceiveRunLoopMode
:接受系统内部事件,一般用不到
kCFRunLoopCommonModes
:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案
这张图在网上流传比较广。 对于RunLoop而言最核心的事情就是保证线程在没有消息的时候休眠,在有消息时唤醒,以提升程序性能。RunLoop这个机制是依靠系统内核来完成的(苹果操做系统核心组件Darwin中的Mach)。
RunLoop经过mach_msg()
函数接收、发送消息。它的本质是调用函数mach_msg_trap()
,至关因而一个系统调用,会触发内核状态切换。在用户态调用 mach_msg_trap()
时会切换到内核态;内核态中内核实现的mach_msg()
函数会完成实际的工做。 即基于port的source1,监听端口,端口有消息就会触发回调;而source0,要手动标记为待处理和手动唤醒RunLoop
Mach消息发送机制 大体逻辑为: 一、通知观察者 RunLoop 即将启动。 二、通知观察者即将要处理Timer事件。 三、通知观察者即将要处理source0事件。 四、处理source0事件。 五、若是基于端口的源(Source1)准备好并处于等待状态,进入步骤9。 六、通知观察者线程即将进入休眠状态。 七、将线程置于休眠状态,由用户态切换到内核态,直到下面的任一事件发生才唤醒线程。
八、通知观察者线程将被唤醒。 九、处理唤醒时收到的事件。
十、通知观察者RunLoop结束。
一个比较常见的问题:滑动tableView时,定时器还会生效吗? 默认状况下RunLoop运行在kCFRunLoopDefaultMode
下,而当滑动tableView时,RunLoop切换到UITrackingRunLoopMode
,而Timer是在kCFRunLoopDefaultMode
下的,就没法接受处理Timer的事件。 怎么去解决这个问题呢?把Timer添加到UITrackingRunLoopMode
上并不能解决问题,由于这样在默认状况下就没法接受定时器事件了。 因此咱们须要把Timer同时添加到UITrackingRunLoopMode
和kCFRunLoopDefaultMode
上。 那么如何把timer同时添加到多个mode上呢?就要用到NSRunLoopCommonModes
了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
复制代码
Timer就被添加到多个mode上,这样即便RunLoop由kCFRunLoopDefaultMode
切换到UITrackingRunLoopMode
下,也不会影响接收Timer事件
一、怎么建立一个常驻线程?
一、为当前线程开启一个RunLoop(第一次调用 [NSRunLoop currentRunLoop]方法时实际是会先去建立一个RunLoop) 一、向当前RunLoop中添加一个Port/Source等维持RunLoop的事件循环(若是RunLoop的mode中一个item都没有,RunLoop会退出) 二、启动该RunLoop
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
复制代码
二、输出下边代码的执行顺序
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@"3");
});
NSLog(@"4");
- (void)test
{
NSLog(@"5");
}
复制代码
答案是1423,test方法并不会执行。 缘由是若是是带afterDelay的延时函数,会在内部建立一个 NSTimer,而后添加到当前线程的RunLoop中。也就是若是当前线程没有开启RunLoop,该方法会失效。 那么咱们改为:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[[NSRunLoop currentRunLoop] run];
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@"3");
});
复制代码
然而test方法依然不执行。 缘由是若是RunLoop的mode中一个item都没有,RunLoop会退出。即在调用RunLoop的run方法后,因为其mode中没有添加任何item去维持RunLoop的时间循环,RunLoop随即仍是会退出。 因此咱们本身启动RunLoop,必定要在添加item后
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(test) withObject:nil afterDelay:10];
[[NSRunLoop currentRunLoop] run];
NSLog(@"3");
});
复制代码
三、怎样保证子线程数据回来更新UI的时候不打断用户的滑动操做?
当咱们在子请求数据的同时滑动浏览当前页面,若是数据请求成功要切回主线程更新UI,那么就会影响当前正在滑动的体验。 咱们就能够将更新UI事件放在主线程的NSDefaultRunLoopMode
上执行便可,这样就会等用户再也不滑动页面,主线程RunLoop由UITrackingRunLoopMode
切换到NSDefaultRunLoopMode
时再去更新UI
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
复制代码