zoukankan      html  css  js  c++  java
  • 仿SDWebImage

    1.要解决的问题.

    给单元格设置从网络上下载的图片.

    2.思路:

    >1.先同步下载

      bug:下载是一个耗时操作,会阻塞主线程

    >2.异步下载图片

      bug:图片发生错行.

        >从网络上请求下来的图片与单元格不匹配.为什么会这样?根本原因是因为重用单元格.

    如图所示.

      tableView上有3个单元格.每个单元格有图片,文字.文字是通过plist加载,当向上滚动单元格,让,单元格1滚出界面,那么就会加载第4个单元格,第四个单元格仍然是新创建的,因为当单元格1尚未滚出视图,单元格4已经出来一部分,是不可能重用的.当单元格4完全加载出来,单元格1完全滚出界面,缓存池中就有了可重用单元格,当加载第5个单元格,就不会重写创建单元格,而是到缓存池中找,于是把第1个单元格,放到第5个单元格的位置,如果此时并不根据indexPath设置第五个单元格的数据,那么就会发生单元格重用,第5个单元格和第1个单元格完全一样,因此,通常为了解决单元格重用,需要根据indexPath重新设置单元格5的数据.

      因为从网络上请求数据是比较耗时的,可能会发生这样一种情况:

      单元格1的图片尚未请求到,就已经滚出界面,单元格5重用了单元格1的数据,通过根据indexPath获取plist文件中对应的文字数据,可以方式重用导致的问题.单元格同样要发送请求到网络上请求自己的图片,但是如果单元格1请求图片比较慢,那么,单元格5会先设置自己的图片,然后又设置单元格1的图片,于是发生覆盖.导致图片与文字不匹配.

    那么plist设置文字会发生这种状况么?首先,我觉得如果从plist文件中加载数据也像从网络中请求数据那样耗时,并且取不同的字符串耗时差距比较大,也会发生覆盖问题.但是我觉得从本地取数据是很快的,并且,即是耗时,从plist中取一个长度为10,和长度为100的字符串也是差不多的.这种误差通常应该不会发生

      SDWebImage的解决方式,即是斩断这种多个数据填一单元格的问题,当单元格已经出去了,却没有下载好图片,那么就取消单元格1的下载操作,于是滚到单元格5的时候,这个单元格就只有一个网络请求,不会再有比它更慢的网络请求的图片回来覆盖单元格5对应的正确的数据.

      这种实现比较麻烦,先考虑简单的解决方式.

      方式1:并不直接将网络请求的数据设置给单元格,而是通过给plist文件对应的模型增加一个属性UIImage,因为模型数据总是一一对应的,因为角标不同,当单元格1的图片数据请求回来,设置给模型中的image属性,因为---------会不会发生这种情况,将请求回来的图片设置给模型的image属性的时候发生错乱?比如,单元格1请求数据比较慢,单元格3请求下来的数据设置给单元格1对应的image属性?就目前这种写法,应该不会,因为实际上,每次请求图片都是新开了了线程,如果CPU分配,那么每一个单元格对应着一个线程,也就是单元格3请求数据和单元格1请求图片和设置图片是在不同线程,不会发生设想的问题.假设CPU并不让每一个单元格重新开线程,而是上一个执行完了再将新的任务添加到同一个线程中,也不会发生设想的这种错乱.

      因为设置数据的时候,是在子线程请求图片的,因而可以直接忽略该段代码,因而会导致一个bug,图片请求回来了,却并没有图片,因为控件是懒加载,当第一次布局单元格的时候,并没有图片请求回来,因而layoutSubViews不会布局图片框的位置,当滑动或者点击单元格,又会调用layoutSubViews方法,此时系统意识到有图片,因而会布局图片,该问题可以通过占位图解决,

     方式2:

      思路和前面仍然是一样,不直接将下载的数据和单元格关联,通过一个中介将下载的图片和plist文件一一对应即可.字典是一一对应的,将模型中的url作为键,(每个单元格都不同,因而是唯一的),将图片作为值存储起来.此时还需要在下载图片前先从图片缓存字典中取,如果有,就设置图片,没有就下载,也可以解决错行问题.SDWebImage只不过是通过NSCache解决的,实现思路也是一样.并且NSCache其实和字典也差不多,只需要setObjectForKey存,ObjectForKey取即可.

    >3.有网状态下下载下来的图片需要缓存到本地,这样,当没网,就可以到缓存中取.数据本地化可以使用归档或者plist,我用的是plist,注意,当没有网的时候就可以从沙盒中找.又由于,从沙盒中取效率不如从内存中取高,当第一次从沙盒中加载,再次上下滑动时候,考虑从内存中去,于是可以从沙盒中取出图片后,同时将图片设置给图片缓存.又如果是第一次从网上加载,再次上下滑动,从沙盒中加载,不如直接从内存中加载效率高,考虑从网上下载后同样设置给图片缓存(内存).

    >4.bug3,即使图片下载下来了,但是再次滚动单元格还是会再去下载,这显示是没必要的,我最初的想法是,给模型增加一个BOOL值,当任务被添加到队列中,将BOOL值设置为YES,表示正在下载,不必重复下载,实际上也是可以解决问题的.但是又有一个问题.如果用户清除缓存了,并且程序没有挂掉,也就是说沙盒中,没有图片,那么可以去图片缓存中取,如果重新运行程序,BOOL值又恢复为nil,又从网上加载,貌似没有问题.????为啥还要移除操作缓存????有没有这样一种可能,BOOL值显示为正在下载,但是图片缓存中没有数据,沙盒中也没有数据,但是又因为BOOL值显示正在下载导致无法再次下载???有可能,当收到内存警告,内存中被清空了,沙盒中又被清除缓存,模型中的BOOL值仍然显示着正在下载,导致无法再次下载.-----确实有问题,当收到内存警告,内存缓存中的图片缓存会被清空,并且沙盒中也被清除缓存,此时操作缓存中显示正在下载,不会再次下载,如果设置了占位图,就会发生只有占位图,如果没有设置占位图,就会发生图片复用.因而,当图片下载完成,需要将操作缓存移除.

    >5.通过BOOL值确实可以解决重复下载的问题,但是呢,这样会不会造成代码的耦合性过高呢?假设需要将下载以外的事务抽取出来,不放在控制器中,专门封装到一个管理者类中,操作缓存这一步该如何封装进去?-----如果通过增加BOOL值记录操作缓存,那么需要用到模型,然而,管理者类中又用到模型,耦合性实在太高,故而此种做法不好.

    >6.在增加新的东西前,先优化一下当前代码,你不觉得设置单元格的方法太过于冗长了么?怎么抽取一下?...无论是抽取到自定义单元格中还是抽取到一个单独的方法,需要的参数都很多,暂时不这样做.

    >7.增加一个管理者类,将下载无关的业务逻辑抽取出来,需要在三个类之间进行传值,可以通过block解决.并且比较容易实现.需要注意一个问题

    管理者类中用到了block,

    typedef void(^giveImageToVc)(UIImage * image); 

    属性

    @property (nonatomic,copy) giveImageToVc giveImageToVc;

    //    需要回到主线程操作.
            if (self.giveImageToVc) {
    
                self.giveImageToVc(img);//此处省略self,并不会报错,因为,默认的识别为起别名的block,
    
            }
    
            return ;

    像这样:

    typedef void(^myblock)(UIImage *);
    - (void)test:(NSString *)urlString
    {
    
        myblock(abc);//并且括号中的参数写什么都不会报错

    >8.因为要仿SDWebImage,SDWebImage解决图片错行的方式和上面的方式稍稍不同,其思路是这样的:

    因为根本原因在于多个请求填一个单元格,那么当滚到新的单元格的时候,如果还有旧的单元格请求绑定着新的单元格,那么只需要切断就的请求即可.并且SDWebImage是通过给UIImageView添加分类的方式,因而我也这样做,那么在分类方法中就要做这几件事:

    仍然以上面的示例图片为例,假设单元格1已经滚出屏幕,单元格4滚入屏幕,那么单元格4滚进屏幕,需要做这样的判断,判断单元格1的图片是否下载完成,如果已经完成,因为当下载完成了,就会执行设置图片操作,因为覆盖问题就是一方面是因为重用,一方面也是因为请求图片比较耗时,如果单元格1已经下载完成,那么单元格4下载图片肯定在其后,不会发生覆盖.如果单元格1尚未下载完成,那么就有可能比单元格4下载慢,导致覆盖

    因而,如果满足当滚动到单元格4的时候,单元格1尚未下载完成,那么就取消单元格1的下载操作,执行单元格1的下载操作.

    ?问题1;如何判定单元格1是否下载完成?

    ?问题2:因为要在分类中记录单元格1的urlString,通常是通过属性记录,然而,分类是不能增加属性的.可不可以用静态全局变量

    ?问题3:第一次进来,oldURLString 为nil,将当前urlString赋值给oldURLString,第二次进来,存储的是就是第一个单元格的urlString,界面上能显示3个单元格,显然此时单元格1和单元格2,3不会发生覆盖问题,当加载第四个单元格的时候,需要判断的是第一个单元格是否下载完成,那么如何判断当前滚动出来的单元格是界面中最底下的呢?

    或者说加载第二个单元格时,第一个尚未加载完,取消,这样到加载第四个单元格的时候也不会发生多个数据抢一个单元格的问题.但是这样太狠了,有一些操作是没必要的,损耗效率的.思路有问题.....

    因为加载每个单元格都会调用分类方法,也就是说,只有当第一个单元格成为第4个单元格的时候才可能发生覆盖问题,才需要判断第一个单元格是否下载完成,那么首先要判断当前传进来的URL是否是第四个,这样有问题,如果屏幕显示的图片不止四个呢?屏幕显示多少个单元格取决于单元格高度和手机屏幕,这些都是不可控制的.思路有问题.....

    果然有问题

    问题大大的

  • 相关阅读:
    JVM学习笔记-指向Class类的引用(A Reference to Class Class)
    JVM学习笔记-方法区示例与常量池解析(Method Area Use And Constant Pool Resolution)
    JVM学习笔记-堆(Heap)
    JVM学习笔记-程序计数器(The Program Counter)
    JVM学习笔记-栈(Stack)
    JVM学习笔记-栈帧(The Stack Frame)
    JVM学习笔记-局部变量区(Local Variables)
    html大文件传输源代码
    html大文件传输源码
    html大文件传输插件
  • 原文地址:https://www.cnblogs.com/yufang/p/5291740.html
Copyright © 2011-2022 走看看