从SDWebImage源码中学到的(一)

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

亲,我的简书已不再维护和更新了,所有文章都迁移到了我的个人博客: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];
    });  

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

如何保持后台下载图片的线程一直存在?

使用RunLoop来获得常线程(具体解释在我的实例化讲解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];
}
}

某个类添加通知的方式

很多时候某个类要发出通知,我们经常放到宏里,但是如果这两个类是相关的,我们其实可以将通知放到对应的头文件中,然后在.m文件中将其赋值。

.h
extern NSString *const SDWebImageDownloadStartNotification;
.m
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";

使用NSURLConnection时,怎样控制是否缓存请求到的数据?

   - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
    responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
    if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
        // Prevents caching of responses
        return nil;
    }
    else {
        return cachedResponse;
    }
}

如果这里返回nil,那么将不会缓存这个response,如果返回cachedResponse表示可以缓存这个response.

IOS5.0之后,如果请求和响应满足以下条件,系统就会在如下目录中生成一个Cache.db这样一个数据库来存储缓响应的数据。

生成的缓存目录

在缓存期间如果访问相同的URL,那么就会直接从这个数据库中得到相应的数据;同时在系统的内存告紧时,会自动把内存缓存清空。
这个缓存协议被回调的条件是:

*  HTTP或者HTTPS请求(如果是自定义的协议,那么协议需要支持缓存)
*  请求必须是成功的(状态码为200-299)
*  响应必须是服务端传回来的,而不是本地缓存传回来的   
*  进行该请求的NSURLRequest对象的`cachePolicy`是允许缓存的
*  服务的响应头含有支持缓存的字段
*  响应的内容大小没有超过缓存的大小(例如,提供磁盘缓存时,响应内容不能超过磁盘缓存的5%)

注意:如果要自定义NSURLCache,那么在自定义NSURLCache进行数据缓存时,一定要在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中进行初始化,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容