iOS-SDWebimage底层实现原理

其实有些框架的实现原理,并没有想象中那么难,思想也很简单,主要是更新第三方框架的作者对自己写的代码,进行了多层封装,使代码的可读性降低,也就使得框架看起来比较难.我来实现以下SDWebimage的的曾实现.

实现过程中可能遇到的问题:

1.UI卡顿: 当界面中需要下载多张图片的时候,由于图片下载是耗时操作,会短暂的占据着主线程的执行,也就会是UI界面看起来卡顿.

  解决办法: 需要把耗时操作放在子线程中执行(若是对多线程不了解,我之前的博客写过关于多线程的知识)

 NSBlockOperation *download = [self.operations objectForKey:app.icon];

            if (download) {

                NSLog(@"该图片正在下载,请稍等");
            }else
            {
                //封装下载图片的操作
                download = [NSBlockOperation blockOperationWithBlock:^{

                    NSURL *url = [NSURL URLWithString:app.icon];

                    //耗时操作,模拟网速慢的情况
                    for (NSInteger i =0; i <1000000000; i++) {

                    }

                    NSData *data = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];

                    NSLog(@"下载第%zd行cell对应的图片",indexPath.row);

                    //容错处理
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return ;
                    }

                    //保存图片到内存缓存中
                    [self.images setObject:image forKey:app.icon];

                    //保存数据到沙盒(磁盘缓存)
                    [data writeToFile:fullPath atomically:YES];

                    //线程间通信 主线程中设置图片
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //cell.imageView.image = image;
                        //刷新
                        //[tableView reloadData];
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
                    }];
                }];

                //把操作保存到操作缓存中
                [self.operations setObject:download forKey:app.icon];

                //把操作添加到队列
                [self.queue addOperation:download];
            }

2.重复加载问题(一)

在下载图片的地方,打印相关的下载的图片的信息,你会发现图片会重复下载

解决办法: 1.内存缓存处理  2.二级缓存处理

内存的大小毕竟有限的,在开发中我们一般采用二级缓存处理.

二级缓存处理过程:

1.在显示图片之前,先检查内存缓存中时候有该图片

2.如果内存缓存中有图片,那么就直接使用,不下载

3.如果内存缓存中无图片,那么再检查是否有磁盘缓存

4.如果磁盘缓存中有图片,那么直接使用,还需要保存一份到内存缓存中(方便下一次使用)

5.如果磁盘缓存中无图片,那么再去下载,并且把下载完的图片保存到内存缓存和磁盘缓存

 //保存图片到内存缓存中
  [self.images setObject:image forKey:app.icon];

  //保存数据到沙盒(磁盘缓存)
   [data writeToFile:fullPath atomically:YES];

3.图片不显示

原因: 图片的显示操作是异步执行的,也就是说需要重新刷新该行的cell

 //线程间通信 主线程中设置图片
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //cell.imageView.image = image;
                        //刷新
                        //[tableView reloadData];
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
                    }];

4.图片重复下载问题(二)

这种情况需要模拟一定的情况,例如网络信号不好(网络信号不好可以用耗时操作代替),造成这种情况的原因,就是在网络情况不好的情况下,用户重复滑动.就会重复的发送下载图片的请求,最终造成重复下载.

解决办法: 我在这里用的操作队列,对应的解决办法,就是把对应的事件以字典的方式存储到内存,放置重复发送下载图片的请求

 //把操作保存到操作缓存中
                [self.operations setObject:download forKey:app.icon];

5.图片显示错乱的问题

由于cell的重用导致,用户下拉或者上拉,当网络不好的情况,该cell的图片还没有被加载,但是对应的cell已经被显示,就会显示cell被重用之前的数据,造成数据混乱

解决办法: 设置每个cell中image为nil或者设置默认图片.

6.中间还要加一些容错处理.

例如: 若是服务器返回的url是错误的,就会造成程序闪退,需要做处理

 NSData *data = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];

                    NSLog(@"下载第%zd行cell对应的图片",indexPath.row);

                    //容错处理
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return ;
                    }

7.内存缓存的处理

-(void)didReceiveMemoryWarning
{
    //清空内存缓存
    [self.images removeAllObjects];

    //取消队列中所有的操作
    [self.queue cancelAllOperations];
}

完整的代码如下:

模型中的代码:

#import <Foundation/Foundation.h>

@interface BOAppModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *icon;
@property (nonatomic, strong) NSString *download;

- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)appModelWithDict:(NSDictionary *)dict;
@end
#import "BOAppModel.h"

@implementation BOAppModel
- (instancetype)initWithDict:(NSDictionary *)dict{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}
+ (instancetype)appModelWithDict:(NSDictionary *)dict{

    return [[BOAppModel alloc] initWithDict:dict];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{

}
@end

控制器空代码如下:

#import "ViewController.h"
#import "BOAppModel.h"

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

@implementation ViewController

#pragma mark ------------------
#pragma mark lazy loading

-(NSOperationQueue *)queue
{
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc]init];
    }
    return _queue;
}

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

-(NSArray *)apps
{
    if (_apps == nil) {

        //加载plist文件中的数据
        NSArray *arrayM = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];

        //字典转模型(字典数组-->模型数组)
        NSMutableArray *arr = [NSMutableArray array];
        for (NSDictionary *dict in arrayM) {
            [arr addObject: [BOAppModel appModelWithDict:dict]];
        }

        _apps = arr;

    }
    return _apps;
}

#pragma mark ------------------
#pragma mark UItableViewDataSource

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.apps.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //01 创建cell
    static NSString *ID = @"app";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    //02 设置cell
    //001 拿到该行cell对应的数据
    XMGApp *app = self.apps[indexPath.row];
    //002 设置标题
    cell.textLabel.text = app.name;
    //003 设置子标题
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",app.download];
    //004 设置图片

    /*
     内存缓存处理:
     [1]在显示图片之前,先检查缓存中是否有该图片(是否已经下载过)
     [2]如果缓存中有图片,那么就直接使用,不下载
     [3]如果缓存中无图片,那么再去下载,并且把下载完的图片保存到内存缓存
     */
    /*
     二级(内存-磁盘)缓存处理:
     [1]在显示图片之前,先检查内存缓存中是否有该图片
     [2]如果内存缓存中有图片,那么就直接使用,不下载
     [3]如果内存缓存中无图片,那么再检查是否有磁盘缓存
     [4]如果磁盘缓存中有图片,那么直接使用,还需要保存一份到内存缓存中(方便下一次)
     [5]如果磁盘缓存中无图片,那么再去下载,并且把下载完的图片保存到内存缓存和磁盘缓存
     */

    //检查内存缓存
    UIImage *image = [self.images objectForKey:app.icon];

    if (image)
    {
        cell.imageView.image = image;
         NSLog(@"第%zd行cell对应的图片从内存缓存中获取",indexPath.row);
    }else
    {
        //文件名称
        NSString *fileName = [app.icon lastPathComponent];

        //获得缓存路径
        NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

        //拼接文件的全路径
        NSString *fullPath = [cache stringByAppendingPathComponent:fileName];

        //检查磁盘缓存
        NSData *data = [NSData dataWithContentsOfFile:fullPath];
        data = nil;
        if (data)
        {
            //如果有磁盘缓存,那么久直接使用
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;

            //还需要保存一份到内存缓存中
            [self.images setObject:image forKey:app.icon];

            NSLog(@"第%zd行cell对应的图片使用了磁盘缓存--%@",indexPath.row,fullPath);
        }else
        {
            //开子线程下载图片
            /* 对操作进行缓存处理
             如果没有内存缓存也没有磁盘缓存,则
             001 先检查该图片的下载操作是否已经存在(该图片是否正在下载)
             002 如果下载操作已经存在,那么就等待即可
             003 如果下载操作不存在,那么再去下载
             */

            //清空cell的图片
            //cell.imageView.image = nil;
            //设置占位图片
            cell.imageView.image = [UIImage imageNamed:@"Snip20161203_14"];

            NSBlockOperation *download = [self.operations objectForKey:app.icon];

            if (download) {

                NSLog(@"该图片正在下载,请稍等");
            }else
            {
                //封装下载图片的操作
                download = [NSBlockOperation blockOperationWithBlock:^{

                    NSURL *url = [NSURL URLWithString:app.icon];

                    //耗时操作,模拟网速慢的情况
                    for (NSInteger i =0; i <1000000000; i++) {

                    }

                    NSData *data = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];

                    NSLog(@"下载第%zd行cell对应的图片",indexPath.row);

                    //容错处理
                    if (image == nil) {
                        [self.operations removeObjectForKey:app.icon];
                        return ;
                    }

                    //保存图片到内存缓存中
                    [self.images setObject:image forKey:app.icon];

                    //保存数据到沙盒(磁盘缓存)
                    [data writeToFile:fullPath atomically:YES];

                    //线程间通信 主线程中设置图片
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //cell.imageView.image = image;
                        //刷新
                        //[tableView reloadData];
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationMiddle];
                    }];
                }];

                //把操作保存到操作缓存中
                [self.operations setObject:download forKey:app.icon];

                //把操作添加到队列
                [self.queue addOperation:download];
            }
        }
    }

    //03 返回cell
    return cell;
}

-(void)didReceiveMemoryWarning
{
    //清空内存缓存
    [self.images removeAllObjects];

    //取消队列中所有的操作
    [self.queue cancelAllOperations];
}

/*
 001 UI卡顿 ---> 开子线程下载图片
 002 重复下载 --->内存缓存 -->磁盘缓存
 003 图片不显示 --->图片的显示操作是异步执行的
 004 新的重复下载
 005 图片显示错乱的问题-->cell的重用
 006 自己寻找
 */
/*
 沙盒文件:
    Documents 官方规定不能缓存处理
    Library
        cache 缓存文件
        偏好设置
    Tmp 临时文件存储路径(随时可能被删除)
 */
@end
时间: 2024-10-14 08:45:27

iOS-SDWebimage底层实现原理的相关文章

SDWebImage底层实现原理

SDWebImage底层实现有沙盒缓存机制,主要由三块组成 1.内存图片缓存2.内存操作缓存3.磁盘沙盒缓存 内部实现过程:第一步,下载SDWebImage,导入工程. 第二步,在需要的地方导入头文件 1 #import "UIImageView+WebCache.h" 第三步,调用sd_setImageWithURL:方法缓存图片,注意,这就是新版本的新方法,旧方法是setImageWithURL:.下面将几个方法都介绍一下. 1. sd_setImageWithURL: 1 //图

iOS分类底层实现原理小记

http://www.jianshu.com/p/b7169a5a558e OS 分类底层是怎么实现的?本文将分如下四个模块进行探究 分类的结构体 编译时的分类 分类的加载 总结 本文使用的runtime源码版本是 objc4 - 680文中类与分类代码如下 //类 @interface Person : NSObject @property (nonatomic ,copy) NSString *presonName; @end @implementation Person - (void)d

IOS SDWebImage实现原理详解

在之前我写过SDWebImage的使用方法,主要是用与获取网络图片,没有看过的朋友可以看看. 这篇文章将主要介绍SDWebImage的实现原理,主要针对于获取网络图片的原理,如果没有第三方我们该怎么去做,当然我知识用文字去介绍,我想花大把的时间去深入理解我们用不到的东西,是很不值得的,不过兴趣的朋友可以去其他博客上查找相应信息,毕竟学无止境.好了下面开始进入正题. 1)当我门需要获取网络图片的时候,我们首先需要的便是URl没有URl什么都没有,获得URL后我们SDWebImage实现的并不是直接

iOS 简易底层 Basement-敏捷开发

iOS 简易底层 我自己在使用的Basement 简易可自定义度底层 1. 没有的东西: 网络请求  数据存储 - ASI二次开发 AF MK 各有所好. 所以不写. CoreData 使用 各有所好, 推荐Magic FMDB - 数据库相关操作 - 各有所好不写 YTKeyValue - 键值存储(数据库) 2. 写了的东西:HMSegmentControl 方便 简单 好看的Segment INTULocationManager 位置管理器 加入了iOS8.0新方法 MBProgressH

迭代器Iterator的底层实现原理

第一步:没有接口的迭代器简单实现原理 1 package com.bjsxt.xiaofei; 2 /** 3 * 迭代器底层原理 4 * 方法: 5 * hasNext() 6 * next() 7 * remove() 8 * @ClassName: MyAarryList 9 * @Description: TODO(这里用一句话描述这个类的作用) 10 * @author 尚晓飞 11 * @date 2014-7-29 下午7:06:09 12 * 13 */ 14 public cl

浅议事件异步处理底层实现原理

//主类 package cn.com.likeshow.bluetoothchat; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.widget.LinearLayout; import android.widget.TextView; public class MainActivity extends Activity { @Override pr

由PHP底层工作原理说起

之前做过.net,java开发,也写过几个Php的网站,似乎3种主要编程语言都接触了.但是越来越觉得自己对编程的整个流程缺乏一个整体的认识,尤其是底层的机制.譬如网络编程,编译原理,服务器端,数据库存储引擎原理等.于是看了一些书,比较经典的有apue,unp,tcp/ip,nginx,mysql的innodb存储引擎,深入理解jvm.渐渐发现无论用什么语言做开发,背后都有linux,shell,c/c++,nginx服务器,mysql的身影.也许只有掌握了这些核心的原理知识,一个程序员才具有核心

IOS系统推送原理

IOS推送大致原理如下图 1.Provider:就是为指定IOS设备应用程序提供Push的服务器,(如果IOS设备的应用程序是客户端的话,那么Provider可以理解为服务端[消息的发起者]): 2.APNS:Apple Push Notification Service[苹果消息推送服务器]: 3.iPhone:用来接收APNS下发下来的消息: 4.Client App:IOS设备上的应用程序,用来接收iphone传递APNS下发的消息到制定的一个客户端 app[消息的最终响应者]: 上图可以

那些年读过的书《Java并发编程的艺术》一、并发编程的挑战和并发机制的底层实现原理

一.并发编程的挑战 1.上下文切换 (1)上下文切换的问题 在处理器上提供了强大的并行性就使得程序的并发成为了可能.处理器通过给不同的线程分配不同的时间片以实现线程执行的自动调度和切换,实现了程序并行的假象. 在单线程中:线程保存串行的执行,线程间的上下文切换不会造成很大的性能开销. 而在多线程中:线程之间频繁的调度需要进行上下文切换以保存当前执行线程的上下文信息和加载将要执行线程的上下文信息,而上下文切换时需要底层处理器.操作系统.Java虚拟机提供支持的会消耗很多的性能开 销.如果频繁的进行

PHP底层工作原理

分类: PHP本质2011-11-15 15:55 2840人阅读 评论(0) 收藏 举报 php工作apacheextensionzendvariables 目录(?)[+] 简介 先看看下面这个过程: 我们从未手动开启过PHP的相关进程,它是随着Apache的启动而运行的: PHP通过mod_php5.so模块和Apache相连(具体说来是SAPI,即服务器应用程序编程接口): PHP总共有三个模块:内核.Zend引擎.以及扩展层: PHP内核用来处理请求.文件流.错误处理等相关操作: Ze