美文网首页
从SDWebImage源码中学到的

从SDWebImage源码中学到的

作者: 击水湘江 | 来源:发表于2017-04-24 19:11 被阅读6次

    亲,我的简书已不再维护和更新了,所有文章都迁移到了我的个人博客:https://mikefighting.github.io/,欢迎交流。

    保证一段代码在主线程中运行,怎么做更好?

    可以使用一个宏来替代,这样代码更加整洁,如

    #define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }
    
    #define dispatch_main_async_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
    
    Block除了常见的回调,还有什么应用场景?

    在具体的处理方式需要客户端传入时。(在模板方法设计模式中,其调用的是子类的实现)。
    如:

    typedef NSString *(^SDWebImageCacheKeyFilterBlock)(NSURL *url);
    @property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
    

    该处就是利用Block将处理CacheKey的方法开放给了客户端。通过block的返回值获取。其实这里用代理也可以实现,但是代理相对来说代码量会更多,并且代码较为分散。

    我们在加锁的时候一直用@synchronized (self)合理吗?

    不合理,@synchronized (objc),只要这个objc是同一个对象,那么就会获得同一把锁。如果访问的是两种不同的资源,那么就需要使用两种不同的objc,比如:

       @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
      @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
    

    这里分别是对runningOperationsfailedURLs同步,那么就需要使用两种不同的objc,当然都用self不能算错,但是将不需要同步的代码同步了,就降低了系统的性能。另外使用self,还容易引起死锁,比如下代码:

     //class A
        @synchronized (self) {
        [_sharedLock lock];
        NSLog(@"code in class A");
        [_sharedLock unlock];
     }
     
      //class B
       [_sharedLock lock];
        @synchronized (objectA) {
            NSLog(@"code in class B");
        }
        [_sharedLock unlock];
    

    如果要让数组中的每个对象都调用某个方法怎么做?

    - (void)makeObjectsPerformSelector:(SEL)aSelector`
    - (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(nullable id)argument`
    

    以上两个方法可以实现,而不需要分别遍历每个对象,然后分别调用performSelector:

    内存缓存为啥要用NSCache

    NSCache和NSDictionary极其相似,他的方法如下:

    - (nullable ObjectType)objectForKey:(KeyType)key;
    - (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
    - (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
    - (void)removeObjectForKey:(KeyType)key;
    - (void)removeAllObjects;
    @property NSUInteger totalCostLimit;    // limits are imprecise/not strict
    @property NSUInteger countLimit;    // limits are imprecise/not strict
    @property BOOL evictsObjectsWithDiscardedContent;
    

    从中可以看到,他和NSDictioary非常相似,但是为啥在做内存缓存时要用它呢?原因在于:

    1. 当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出“低内存”通知时手工删减缓存。
    2. NSCache还是线程安全的。
    3. NSDictionary中的key必须实现NSCopying协议,NSCache中的key不必实现copy因为它是"保留"键的(强引用),而不是"拷贝"键的。
    4. 如果缓存设置超过了设置的最大值,则会清除旧的数据,保留最新缓存的数据。

    FOUNDATION_STATIC_INLINE放在方法名前有何作用?

     FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
    return image.size.height * image.size.width * image.scale * image.scale;
    

    }

    内联函数的代码会被直接嵌入在它被调用的地方,调用几次就嵌入几次,没有使用call指令。这样省去了函数调用时的一些额外开销,比如保存和恢复函数返回地址等,可以加快速度。不过调用次数多的话,会使可执行文件变大,这样会降低速度。相比起宏来说,内核开发者一般更喜欢使用内联函数。因为内联函数没有长度限制,格式限制。编译器还可以检查函数调用方式,以防止其被误用。

    inline(内联函数)在什么时候使用?

    SDWebIamgeCompat中使用了inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image),这是个内联函数(函数代码被放入符号表中,在使用时进行替换,比调用一般的函数更加高效),那么我们在什么时候使用内联函数呢?经过查找相关资料,总结下inline的使用场合:
    使用inline的场合:

    1. 想要使用inline替换#define时。
    2. 函数很短。(如果函数的代码较长,使用内联将消耗过多内存)
    3. 函数调用很频繁。
      不应使用inline的场合:
    4. 很大的函数。
    5. 和I/O相关的函数。
    6. 构造函数和析构函数。
    7. 开放框架时候,使用inline可能会破坏框架的兼容性。

    获取某个目录下文件的个数:

     NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
     count = [[fileEnumerator allObjects] count];
    

    如何让某个属性只在固定版本的时候才会有?

     #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
     @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
     #endif
    

    上面这段代码就可以让backgroundTaskId在iphone的版本在4.0以后才会有。

    Notification的方法调用所在的线程是根据Post时候所在线程决定的,

     dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter]   postNotificationName:SDWebImageDownloadStopNotification object:self];
        });  
    

    这样注册该通知的对象就可以在主线程中调用响应的方法了。

    NSRunLoop,为了保持后台下载图片的线程一直存在,就使用了RunLoop

    CFRunLoopRun()CFRunLoopStop(CFRunLoopGetCurrent())分别用来开始和结束一个RunLoop

    分类中需要填加属性怎么办?

    如果分类中的属性只是分类内部使用,那么其实可以直接使用关联,而不必非要显式创建一个属性,这样也可以直接使用.语法,这时没有属性,所以.语法的无论是在=左边,还是在=右边最终都会调用这个方法,例如:

    static char imageURLStorageKey;
      - (NSMutableDictionary *)imageURLStorage {
    NSMutableDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
    if (!storage)
    {
        storage = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return storage;
    }
    

    如何让一个id类型的对象调用某个具体的方法?

    让这个对象遵守某项协议就可以调用,如下

     for (id <SDWebImageOperation> operation in operations) {
     if (operation) {
         [operation cancel];
     }
    

    同理,如果想让某个方法的返回值具有某个方法,也可以让这个返回值遵守某协议,如:

    - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                             options:(SDWebImageOptions)options
                                            progress:  (SDWebImageDownloaderProgressBlock)progressBlock
                                           completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;
    

    设计接口时,要尽量考虑使用者的习惯,并对常见错误进行处理

    在需要传入URL参数的地方,使用者很可能不小心传入了字符串,这个时候要么在方法中抛出异常,要么就在内部判断类型并替使用者做相应的转换,如:

    - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url {
         if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    }
    

    未完,待续

    相关文章

      网友评论

          本文标题:从SDWebImage源码中学到的

          本文链接:https://www.haomeiwen.com/subject/wggpzttx.html