在項(xiàng)目中,遇到一個(gè)問(wèn)題,為GIF圖片右下角加上一個(gè)動(dòng)圖的圖標(biāo),于是百度了一下,發(fā)現(xiàn)普遍都是這樣搞的:
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString: imageUrl]];
[data getBytes:&c length:1];
if (c == 0x47) {
//如果是GIF,即便是長(zhǎng)GIF也會(huì)顯示動(dòng)圖
self.longImageLabelText.text = @"動(dòng)圖";
}
跑在機(jī)子上一看,的確是能加上圖標(biāo)了,但是要卡幾秒。。這是因?yàn)?code>data的解析需要時(shí)間,阻塞了主線程。
于是,我便開(kāi)多一個(gè)線程咯,反正開(kāi)線程又不會(huì)阻塞主線程..
dispatch_group_async(group, queue, ^{
//獲取字節(jié),用于判斷動(dòng)圖
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString: imageUrl]];
[data getBytes:&c length:1];
dispatch_async(dispatch_get_main_queue(), ^{
if (c == 0x47 || isLong) {
self.longImageLabel.hidden = NO;
if (isLong) {
//如果是長(zhǎng)圖
self.longImageLabelText.text = @"長(zhǎng)圖";
}
if (c == 0x47) {
//如果是GIF,即便是長(zhǎng)GIF也會(huì)顯示動(dòng)圖
self.longImageLabelText.text = @"動(dòng)圖";
}
} else {
self.longImageLabel.hidden = YES;
}
});
});
emmm,這樣的確是不阻塞主線程了,但是有時(shí)候圖動(dòng)起來(lái)了都沒(méi)加上相應(yīng)的標(biāo)簽..
一怒之下看了這個(gè)解析GIF的代碼究竟是何方神圣:
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:^(UIImage *image, NSData *imageData) {
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
if (imageFormat == SDImageFormatGIF) {
weakSelf.animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
weakSelf.image = nil;
} else {
weakSelf.image = image;
weakSelf.animatedImage = nil;
}
}
progress:progressBlock
completed:completedBlock];
}
咦,里面已經(jīng)有關(guān)于GIF的判斷了,那其實(shí)我們直接在他判斷為GIF的時(shí)候加上動(dòng)圖的標(biāo)簽就可以了,但是?。∵@是FLAnimatedImageView的內(nèi)容,那當(dāng)然就不可以直接改啦!這時(shí)候就要用到我們的類(lèi)別了!
- (BOOL)cc_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
__block BOOL isGIF = NO;
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:^(UIImage *image, NSData *imageData) {
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
if (imageFormat == SDImageFormatGIF) {
weakSelf.animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
weakSelf.image = nil;
isGIF = YES;
} else {
weakSelf.image = image;
weakSelf.animatedImage = nil;
isGIF = NO;
}
}
progress:progressBlock
completed:completedBlock];
return isGIF;
}
沒(méi)錯(cuò),只要在類(lèi)別中重寫(xiě)這個(gè)函數(shù),然后再加上isGIF的返回類(lèi)型,調(diào)用這個(gè)類(lèi)別的方法,即可獲取是否為GIF了!
更新于8.2
以上方法雖然可以做到判斷GIF,但是還是會(huì)有偶發(fā)現(xiàn)象:GIF標(biāo)簽有時(shí)候并不能及時(shí)地貼上
在debug過(guò)程中可以發(fā)現(xiàn),在加載GIF時(shí),imageFormat并不會(huì)立即被判斷為SDImageFormatGIF,而是SDImageFormatUndefined,然后再看看源碼,發(fā)現(xiàn)是因?yàn)?code>imageData一開(kāi)始被設(shè)置為nil,導(dǎo)致圖片類(lèi)型被判斷為SDImageFormatUndefined了。
為什么Data會(huì)被設(shè)置為nil?
科普一下options參數(shù)的作用,來(lái)源在這里:
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,
SDWebImageLowPriority = 1 << 1,
SDWebImageCacheMemoryOnly = 1 << 2,
SDWebImageProgressiveDownload = 1 << 3,
SDWebImageRefreshCached = 1 << 4,
SDWebImageContinueInBackground = 1 << 5,
SDWebImageHandleCookies = 1 << 6,
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
SDWebImageHighPriority = 1 << 8,
SDWebImageDelayPlaceholder = 1 << 9,
SDWebImageTransformAnimatedImage = 1 << 10,
SDWebImageAvoidAutoSetImage = 1 << 11
};
SDWebImageRetryFailed = 1 << 0,:默認(rèn)情況下,如果一個(gè)url在下載的時(shí)候失敗了,那么這個(gè)url會(huì)被加入黑名單并且library不會(huì)嘗試再次下載,這個(gè)flag會(huì)阻止library把失敗的url加入黑名單(簡(jiǎn)單來(lái)說(shuō)如果選擇了這個(gè)flag,那么即使某個(gè)url下載失敗了,sdwebimage還是會(huì)嘗試再次下載他
SDWebImageLowPriority = 1 << 1,:默認(rèn)情況下,圖片會(huì)在交互發(fā)生的時(shí)候下載(例如你滑動(dòng)tableview的時(shí)候),這個(gè)flag會(huì)禁止這個(gè)特性,導(dǎo)致的結(jié)果就是在scrollview減速的時(shí)候,才會(huì)開(kāi)始下載(也就是你滑動(dòng)的時(shí)候scrollview不下載,你手從屏幕上移走,scrollview開(kāi)始減速的時(shí)候才會(huì)開(kāi)始下載圖片
SDWebImageCacheMemoryOnly = 1 << 2,:這個(gè)flag禁止磁盤(pán)緩存,只有內(nèi)存緩存
SDWebImageProgressiveDownload = 1 << 3,:這個(gè)flag會(huì)在圖片下載的時(shí)候就顯示(就像你用瀏覽器瀏覽網(wǎng)頁(yè)的時(shí)候那種圖片下載,一截一截的顯示(待確認(rèn)))
SDWebImageRefreshCached = 1 << 4,:一個(gè)圖片緩存了,還是會(huì)重新請(qǐng)求.并且緩存?zhèn)嚷砸罁?jù)NSURLCache而不是SDWebImage.URL不變,圖片會(huì)更新時(shí)使用
SDWebImageContinueInBackground = 1 << 5,:啟動(dòng)后臺(tái)下載,加入你進(jìn)入一個(gè)頁(yè)面,有一張圖片正在下載這時(shí)候你讓app進(jìn)入后臺(tái),圖片還是會(huì)繼續(xù)下載(這個(gè)估計(jì)要開(kāi)backgroundfetch才有用)
SDWebImageHandleCookies = 1 << 6,:可以控制存在NSHTTPCookieStore的cookies.
SDWebImageAllowInvalidSSLCertificates = 1 << 7,:允許不安全的SSL證書(shū),在正式環(huán)境中慎用
SDWebImageHighPriority = 1 << 8,:默認(rèn)情況下,image在裝載的時(shí)候是按照他們?cè)陉?duì)列中的順序裝載的(就是先進(jìn)先出).這個(gè)flag會(huì)把他們移動(dòng)到隊(duì)列的前端,并且立刻裝載,而不是等到當(dāng)前隊(duì)列裝載的時(shí)候再裝載.
SDWebImageDelayPlaceholder = 1 << 9,:默認(rèn)情況下,占位圖會(huì)在圖片下載的時(shí)候顯示.這個(gè)flag開(kāi)啟會(huì)延遲占位圖顯示的時(shí)間,等到圖片下載完成之后才會(huì)顯示占位圖.
SDWebImageTransformAnimatedImage = 1 << 10,:是否transform圖片
而源碼中有這樣的語(yǔ)句:
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
可以看到,在顯示占位圖模式(不設(shè)置SDWebImageDelayPlaceholder)中,首先這個(gè)方法會(huì)先加載一張占位圖,與此同時(shí),原圖(GIF等)中的data會(huì)先被設(shè)置為nil,導(dǎo)致這張圖片先會(huì)被設(shè)置為SDImageFormatUndefined。
而在之前的方法中,如果不是GIF類(lèi)型則直接返回NO,也導(dǎo)致了標(biāo)簽會(huì)直接不顯示出來(lái),等到真正加載GIF的時(shí)候,標(biāo)簽已成定局,也就是說(shuō),無(wú)法再為動(dòng)圖加上標(biāo)簽了。
所以,之前提到的直接返回一個(gè)BOOL變量的方法是不太準(zhǔn)確的,最好的方法是使用委托模式或者觀察者模式,委托或者通知。
但是這是一個(gè)類(lèi)別啊喂,怎么擁有delegate變量?委托這條路顯然是走不通的了。那就用通知吧。
我們知道,通知是那種一對(duì)多的關(guān)系,怎么讓通知變成一對(duì)一呢?先來(lái)學(xué)習(xí)一下通知的幾個(gè)變量:
iOS 通知(NSNotification)的簡(jiǎn)單使用中提到,NSNotification中,各種參數(shù)分別是:
name:是消息對(duì)象的唯一標(biāo)識(shí),接受通知消息時(shí)用來(lái)辨別
@property (readonly,copy)NSNotificationName name;
object:一個(gè)對(duì)象,可以理解為針對(duì)某個(gè)對(duì)象的消息
@property (nullable,readonly,retain)id object;
userInfo:一個(gè)字典,用來(lái)傳值
@property (nullable,readonly,copy)NSDictionary *userInfo;
其中,name和object不設(shè)為nil的話就是一對(duì)一關(guān)系了。圖片的URL對(duì)應(yīng)的String即為唯一標(biāo)識(shí)符。
喲西,思路已經(jīng)很清晰了,當(dāng)我們加載到一張GIF圖片的時(shí)候,我們就會(huì)發(fā)出一個(gè)通知,讓cell去顯示這個(gè)動(dòng)圖的標(biāo)簽,否則,則要么隱藏標(biāo)簽,要么顯示長(zhǎng)圖的標(biāo)簽。
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
if (imageFormat == SDImageFormatGIF) {
weakSelf.animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
weakSelf.image = nil;
NSNotification * notice = [NSNotification notificationWithName: imageUrl object: imageUrl userInfo: @{@"isGIF": @"GIF"}];
[[NSNotificationCenter defaultCenter]postNotification:notice];
} else {
weakSelf.image = image;
weakSelf.animatedImage = nil;
NSNotification * notice = [NSNotification notificationWithName: imageUrl object: imageUrl userInfo: @{@"isGIF": @"NotGIF"}];
[[NSNotificationCenter defaultCenter]postNotification:notice];
}
同時(shí),再為cell注冊(cè)通知和響應(yīng)方法
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self selector: @selector(setGIFLabel:) name: imageUrl object: imageUrl];
- (void)setGIFLabel: (NSNotification *)notification {
BOOL isGIF = [notification.userInfo[@"isGIF"] isEqualToString: @"GIF"];
if (isGIF || self.isLong) {
self.longImageLabel.hidden = NO;
if (isGIF)
self.longImageLabelText.text = @"動(dòng)圖";
else
self.longImageLabelText.text = @"長(zhǎng)圖";
} else {
self.longImageLabel.hidden = YES;
}
}
這樣就可以很及時(shí)準(zhǔn)確地將動(dòng)圖貼上自己的標(biāo)簽啦!
更新于8.3
因?yàn)樾阅軉?wèn)題被QA指出來(lái)批評(píng)了..雖然通知不是很耗性能吧,但是一對(duì)一的通知,總感覺(jué)怪怪的..還是改成委托比較好。
之前我說(shuō)過(guò),類(lèi)別不能用委托,所以這次我們改成繼承吧。繼承于基類(lèi)ImageView,然后在Cell中改成自定義imageView即可。
最后聲明協(xié)議和方法,在對(duì)應(yīng)的情形下使用對(duì)應(yīng)的協(xié)議即可。