多线程网络03

1 多图下载综合案例

包含数据展示、缓存处理、子线程下载图片以及内存警告处理

#import <UIKit/UIKit.h>
@interface ViewController : UITableViewController

@end

#import "ViewController.h"
#import "AppModel.h"

@interface ViewController ()
@property (strong, nonatomic) NSArray *app;
@property (strong, nonatomic) NSMutableDictionary *images;
@property (strong, nonatomic) NSOperationQueue *queue;
@property (strong, nonatomic) NSMutableDictionary *operations;
@end

@implementation ViewController
#pragma mark - UITableView delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.app.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
    AppModel *app = self.app[indexPath.row];
    cell.textLabel.text = app.name;
    cell.detailTextLabel.text = app.download;
    // 判断图片是否存在于内存缓存中,如果存在,则从内存缓存中取出并使用
    // 如果内存缓存中不存在,则判断是否存在于磁盘缓存中,如果存在,则从磁盘缓存中取出使用
    // 沙盒目录
    // Document:会备份,苹果不允许将备份数据保存在这里
    // Library:包含下面两种
    // 1>Preference:偏好设置 保存账号
    // 2>Cache:缓存文件,会备份
    // tmp:会随时被删除
    UIImage *image = [self.images objectForKey:app.icon];
    if (image) {
        cell.imageView.image = image;
        NSLog(@"%zd处使用了内存缓存中的图片",indexPath.row);
    } else {
        // 判断图片是否存在于磁盘缓存中
        // 1> 获取沙盒路径
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        // 2> 获取文件名称
        NSString *fileName = [app.icon lastPathComponent];
        // 3> 获取路径全称
        NSString *fullName = [path stringByAppendingPathComponent:fileName];
        
        NSData *data = [NSData dataWithContentsOfFile:fullName];
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            // 将图片保存到内存缓存中
            [self.images setObject:image forKey:app.icon];
            
            NSLog(@"%zd处使用了磁盘缓存中的图片",indexPath.row);
        } else {
            // 判断是否正在下载,如果没有添加到队列,则才添加
            NSBlockOperation *download = [self.operations objectForKey:app.icon];
            if (!download) {
                // 清空复用cell中的图片
                cell.imageView.image = nil;
                download = [NSBlockOperation blockOperationWithBlock:^{
                    NSURL *url = [NSURL URLWithString:app.icon];
                    NSData *data = [[NSData alloc] initWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];
                    
                    // 回到主线程刷新UI
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //cell.imageView.image = image;
                        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
                    }];
                    
                    // 容错处理
                    if (!image) {
                        [self.operations removeObjectForKey:app.icon];
                        return;
                    }
                    
                    // 保存到内存缓存中
                    [self.images setObject:image forKey:app.icon];
                    // 保存图片到沙盒缓存
                    [data writeToFile:fullName atomically:YES];
                    
                    NSLog(@"%zd处下载图片",indexPath.row);
                    
                    // 移除图片的下载操作
                    [self.operations removeObjectForKey:app.icon];
                }];
                [self.queue addOperation:download];
                // 将已创建的操作添加到内存中保存,避免重复添加任务
                [self.operations setObject:download forKey:app.icon];
            }
        }
        
    }
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 60;
}

#pragma mark - getter
- (NSArray *)app {
    if (!_app) {
        NSArray *arr = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        NSMutableArray *arrM = [NSMutableArray array];
        for (NSDictionary *dic in arr) {
            AppModel *appM = [AppModel appWithDictionary:dic];
            [arrM addObject:appM];
        }
        _app = arrM;
    }
    return _app;
}

- (NSMutableDictionary *)images {
    if (!_images) {
        _images = [NSMutableDictionary dictionary];
    }
    return _images;
}

- (NSOperationQueue *)queue {
    if (!_queue) {
        _queue = [[NSOperationQueue alloc] init];
        _queue.maxConcurrentOperationCount = 10;
    }
    return _queue;
}

- (NSMutableDictionary *)operations {
    if (!_operations) {
        _operations = [NSMutableDictionary dictionary];
    }
    return _operations;
}
#pragma mark - life cycle
- (void)didReceiveMemoryWarning {
    // 移除图片缓存
    [self.images removeAllObjects];
    // 取消下载任务
    [self.queue cancelAllOperations];
}
@end
2 SDWebImage的使用和说明
// 下载图片且需要获取下载进度
// 内存缓存&磁盘缓存
- (void)download1 {
    NSString *imageStr = @"http://dmimg.5054399.com/allimg/pkm/pk/22.jpg";
    NSURL *url = [NSURL URLWithString:imageStr];
    
    [self.imageView sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@""] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        self.imageView.image = image;
        NSLog(@"cachetype:%zd",cacheType);
    }];
}

// 下载图片,不需要设置
// 内存缓存&磁盘缓存
- (void)download2 {
    NSString *imageStr = @"http://dmimg.5054399.com/allimg/pkm/pk/22.jpg";
    NSURL *url = [NSURL URLWithString:imageStr];
    
    [[SDWebImageManager sharedManager] downloadImageWithURL:url options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        self.imageView.image = image;
        NSLog(@"cachetype:%zd",cacheType);
    }];
}

// 下载图片,不缓存
// 没有任何缓存处理
- (void)download3 {
    NSString *imageStr = @"http://dmimg.5054399.com/allimg/pkm/pk/22.jpg";
    NSURL *url = [NSURL URLWithString:imageStr];
    
    [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:url options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
        
    } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
        // 该处是子线程,需要回到主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];
}

- (void)gif {
    UIImage *image = [UIImage sd_animatedGIFNamed:@"0011"];
    self.imageView.image = image;
}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    // 清空缓存
    // clearDisk:直接删除,然后重新创建
    // cleanDisk:清除过期缓存,计算当前缓存的大小,和当前设置的最大缓存大小比较,如果超过则会继续删除(按照文件创建的先后顺序删除)
    // 过期时间:7天
    [[SDWebImageManager sharedManager].imageCache cleanDisk];
    [[SDWebImageManager sharedManager].imageCache clearDisk];
    // 取消当前所有的操作
    [[SDWebImageManager sharedManager] cancelAll];
}

SDWebImage的框架内部细节

1 最大并发数量 == 6
2 缓存文件保存名称如何处理?拿到图片的URL路径,对路径进行MD5加密
3 该框架内部对内存警告的处理方式?内部通过监听通知的方式清除缓存
4 该框架进行缓存处理的方式:NSCache
5 如何判断图片的类型?判断图片类型的时候,匹配data的一个字节
6 队列中的任务处理方式:FIFO
7 如何下载图片?发送网络请求下载图片,NSURLConnection
8 请求超时的时间:15s

3 NSCache知识点补充

01.NSCache是专门用来进行缓存处理的,
02.NSCache简单介绍:
2-1 NSCache是苹果官方提供的缓存类,具体使用和NSDictionary类似,在AFN和SDWebImage框架中被使用来管理缓存
2-2 苹果官方解释NSCache在系统内存很低时,会自动释放对象(但模拟器演示不会释放)
建议:接收到内存警告时主动调用removeAllObject方法释放对象
2-3 NSCache是线程安全的,在多线程操作中,不需要对NSCache加锁
2-4 NSCache的Key只是对对象进行Strong引用,不是拷贝

03 属性介绍:
name:名称
delegete:设置代理
totalCostLimit:缓存空间的最大总成本,超出上限会自动回收对象。默认值为0,表示没有限制
countLimit:能够缓存的对象的最大数量。默认值为0,表示没有限制
evictsObjectsWithDiscardedContent:标识缓存是否回收废弃的内容

04 方法介绍

- (void)setObject:(ObjectType)obj forKey:(KeyType)key;//在缓存中设置指定键名对应的值,0成本
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;//在缓存中设置指定键名对应的值,并且指定该键值对的成本,用于计算记录在缓存中的所有对象的总成本,出现内存警告或者超出缓存总成本上限的时候,缓存会开启一个回收过程,删除部分元素
- (void)removeObjectForKey:(KeyType)key;//删除缓存中指定键名的对象
- (void)removeAllObjects;//删除缓存中所有的对象
#import "ViewController.h"

@interface ViewController ()<NSCacheDelegate>
@property (strong, nonatomic) NSCache *cache;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.cache.totalCostLimit = 5; // 设置最大成本,如果缓存的成本超过最大成本数量,则会自动收回之前的缓存对象
}

- (IBAction)saveData:(UIButton *)sender {
    NSData *data = [NSData dataWithContentsOfFile:@"/Users/apple/Pictures/Snip20190131_1.png"];
    for (int i = 0; i < 10; i++) {
        //NSData *data = [NSData dataWithContentsOfFile:@"/Users/apple/Pictures/Snip20190131_1.png"];
        //NSCache的Key只是对对象进行Strong引用,不是拷贝
        [self.cache setObject:data forKey:@(i) cost:1];
        NSLog(@"save data %d",i);
    }
}

- (IBAction)findData:(UIButton *)sender {
    NSLog(@"++++++++++++++++++++++++++++++++");
    for (int i = 0; i < 10; i++) {
        NSData *data = [self.cache objectForKey:@(i)];
        if (data) {
            NSLog(@"find data %d",i);
        }
    }
}

- (IBAction)deleteData:(UIButton *)sender {
    [self.cache removeAllObjects];
}

- (void)cache:(NSCache *)cache willEvictObject:(id)obj {
    NSLog(@"回收%zd",[obj length]);
}

- (NSCache *)cache {
    if (!_cache) {
        _cache = [[NSCache alloc] init];
        _cache.delegate = self;
    }
    return _cache;
}
4 位移枚举简单介绍
#import "ViewController.h"

// 枚举第一种写法
typedef enum {
    ZYXTypeOne1,
    ZYXTypeOne2,
}ZYXTypeOne;

// 枚举第二种写法
typedef NS_ENUM(NSInteger, ZYXTypeTwo){
    ZYXTypeTwo1,
    ZYXTypeTwo2,
};

// 枚举第三种写法,位移枚举
// 一个参数可以传递多个值
// 按位与 &  1&1=1 1&0=0 1&0=0 只要有0 则为0
// 按位或 | 1|1= 1 1|0=1 0|0=0 只要有1 则为1
// 如果是位移枚举,观察第一个参数,如果值 != 0 ,那么可以默认传0作为参数,如果传0作为参数,那么效率最高,因为什么也不会做
typedef NS_OPTIONS(NSInteger, ZYXTypeThree) {
    ZYXTypeThree1 = 1 << 0, //1 * 2的0次方 1
    ZYXTypeThree2 = 1 << 1, //1 * 2的1次方 2
    ZYXTypeThree3 = 1 << 2, //1 * 2的2次方 4
    ZYXTypeThree4 = 1 << 3, //1 * 2的3次方 8
};



@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self demo:ZYXTypeThree1 | ZYXTypeThree2 | ZYXTypeThree3 | ZYXTypeThree4];
}


- (void)demo:(ZYXTypeThree)type {
    if (type & ZYXTypeThree1) {
        NSLog(@"%zd",type & ZYXTypeThree1);
    }
    
    if (type & ZYXTypeThree2) {
        NSLog(@"%zd",type & ZYXTypeThree2);
    }
    
    if (type & ZYXTypeThree3) {
        NSLog(@"%zd",type & ZYXTypeThree3);
    }
    
    if (type & ZYXTypeThree4) {
        NSLog(@"%zd",type & ZYXTypeThree4);
    }
    
}
5 Runloop简单介绍

5.1 什么是Ruploop

  • 从字面意思看,就是运行循环,跑圈
  • 基本作用
    1)保持程序的持续运行
    2)处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
    3)节省CPU资源,提高程序性能:该做事时做事,该休息时休息

5.2 main函数中的Runloop


Snip20191212_2.png
  • 第14行代码中的UIApplicationMain函数的内部启动了一个Runloop
  • 所以UIApplicationMain函数一直没有返回,保持了程序的持续运行
  • 这个默认启动的Runloop是跟主线程相关联的

5.3 Runloop对象
iOS中有2套API来访问和使用Runloop

  • Foundation:NSRunLoop
  • Core Foundation:CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表着RunLoop对象
  • NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多了解CFRunLoopRef层面的API(Core Foundation层面)
6 Runloop资料
7 Runloop对象和线程

7.1 RunLoop与线程

  • 每条线程都有唯一的一个与之对应的Runloop对象
  • 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
  • RunLoop在第一次获取时创建,在线程结束是销毁

7.2 获得RunLoop对象

  • Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
  • Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSRunLoop *mainRunloop = [NSRunLoop mainRunLoop];
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
    NSLog(@"%p---%p",mainRunloop,currentRunloop);
    
    NSLog(@"%p---%p",CFRunLoopGetMain(),CFRunLoopGetCurrent());
    NSLog(@"%p",mainRunloop.getCFRunLoop);
    
    // RunLoop与线程的关系:一一对应,主线程的已经创建,子线程的需要手动创建
    [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}

- (void)run {
    NSLog(@"%p--%@",[NSRunLoop currentRunLoop],[NSThread currentThread]);
}
8 RunLoop相关类

8.1 RunLoop相关类

  • Core Foundation中关于RunLoop的5个类
  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopOberverRef
Snip20191212_6.png
  • RunLoop中有多个运行模式,但是RunLoop只能选择一种模式运行
  • Model里面至少要有一个Timer或者是Source

8.2 CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的运行模式
  • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
  • 每次RunLoop启动时,只能指定其中的一个Mode,这个Mode被称作CurrentMode
  • 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
  • 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响

8.3 CFRunLoopModeRef的5种Mode

  • 系统默认注册了5个Mode
  • KCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
  • UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动后就不再使用
  • GSEventReceiveRunLoopMode:接收系统事件内部Mode,通常用不到
  • KCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode

8.4 简单应用
注:界面滚动视图滑动的过程中,会自动切换到“界面追踪模式”,停止滑动后会自动切换会“默认”模式。

- (void)timer1 {
    // 创建定时器
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    // 将定时器添加到runloop中,指定runloop的运行模式为NSDefaultRunLoopMode
    /*
     参数1:定时器
     参数2:runloop运行模式
     */
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    //UITrackingRunLoopMode 界面追踪模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    //NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
    //占用,标签,凡事添加到UITrackingRunLoopMode中的h事件都会被同时添加到打上common标签的运行模式上
    //0 : <CFString 0x7fff86743f40 [0x7fff80615350]>{contents = "UITrackingRunLoopMode"}
    //2 : <CFString 0x7fff80628740 [0x7fff80615350]>{contents = "kCFRunLoopDefaultMode"}
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)timer2 {
    // 该方法会默认将timer添加到当前的runloop中,并且模式设置为默认
    // 但是如果是在子线程,那么需要创建runloop,并且启动runloop
   // NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
    
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
    // 开启runloop
    //[currentLoop run];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容