【原】SDWebImage源码阅读(二)

时间:2023-02-21 13:37:34

【原】SDWebImage源码阅读(二)

本文转载请注明出处 —— polobymulberry-博客园

1. 解决上一篇遗留的坑


上一篇中对sd_setImageWithURL函数简单分析了一下,还留了一些坑。不过因为我们现在对这个函数有一个大概框架了,我们就按顺序一个个来解决。

首先是这一句代码:

objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

就是给UIImageView当前这个对象添加一个NSString的关联对象url。相当于现在这个图片的url属性绑定到了UIImageView对象上。如果对这个函数有疑问,请移步我的这篇博客

下面简单的部分我就不说了,直接跳到

if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}

首先是SDWebImageAvoidAutoSetImage,我们看看它的注释

/**
* By default, image is added to the imageView after download. But in some cases, we want to
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success
*/

翻译过来就是说,默认情况下是等image完全从网络端下载完后,就会直接将结果设置到UIImageView。但是有些人想在获取到图片后,对图片做一些处理,比如使用filter去渲染图片或者给图片加个cross-fade animation(淡出动画)显示出来。那你就设置这个选项。然后得手动去处理图片下载完成后的事情。

上面说了要手动处理了,很自然你就会想到,这个手动处理就是compeletedBlock啊!当然,除了有这个枚举选项时需要手动处理,其实只要你自定义了compeletedBlock,都会调用你自定义处理的函数。你说我怎么知道的?你看下面的代码,如果你自定义了下载完成后的处理方式,并且也确实下载完成了(finished为YES),就执行自定义方式:

if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}

最后还剩下一个情况,就是url不存在的情况(注意上面讲的是if(url){…},下面讲的是在else{…}):

dispatch_main_async_safe(^{
[self removeActivityIndicator];
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:- userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});

首先自定义一个NSError的对象,表示url为空的错误。然后传给compeletedBlock。


知识点:NSError构造方法errorWithDomain

+ (instancetype)errorWithDomain:(NSString *)domain code:(NSInteger)code userInfo:(nullable NSDictionary *)dict;

具体细节可以移步我的这篇博客


其实目前来说,我心中还有两个最大的疑惑,一个就是operation怎么执行的一个就是如何自定义compeletedBlock

2. operation执行过程

我们可以看到这里downloadImageWithURL其实是SDWebImageManager的方法

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

我们进去该函数实现,快两百行的代码了。好吧,先歇着,我们看看注释(我直接贴出我翻译过后的注释)。

/**
* 如果图片不在缓存中,根据指定的URL下载图片,否则使用缓存中的图片。.
*
* @param url 图片的URL
* @param options 该请求所要遵循的选项。(前面已经介绍了两个)
* @param progressBlock 当图片正在下载时调用该block。
* @param completedBlock 当操作完成后调用该block。
*
* 该参数是必须的。(指的是completedBlock)
*
* 该block没有返回值并且用请求的UIImage作为第一个参数。
* 如果请求出错,那么image参数为nil,而第二参数将包含一个NSError对象。
*
* 第三个参数是'SDImageCacheType'枚举,表明该图片重新获取方式是从本地缓存(硬盘)或者
* 从内存缓存,还是从网络端重新获取image一遍。.
*
* 当options设为SDWebImageProgressiveDownload并且此时图片正在下载,finished将设为NO
* 因此这个block会不停地调用直到图片下载完成,此时才会设置finished为YES.
*
* @return 返回一个遵循SDWebImageOperation协议的NSObject. 应该是一个SDWebImageDownloaderOperation的实例
*/

上面的注释有几个不认识的概念。一个是SDImageCacheType,另一个是options中的SDWebImageProgressiveDownload,还有一个SDWebImageDownloaderOperation

2.1 SDImageCacheType


typedef NS_ENUM(NSInteger, SDImageCacheType) {
/**
* 该图片无法从SDWebImage的缓存中获取,必须从web端下载。
*/
SDImageCacheTypeNone,
/**
* 图片从硬盘缓存(disk cache)中获取
*/
SDImageCacheTypeDisk,
/**
* 图片从硬盘缓存(disk cache)中获取
*/
SDImageCacheTypeMemory
};

具体缓存实现方式我放在SDWebImage源码阅读(五)了。

2.2 SDWebImageProgressiveDownload


如果在加载图片中设定了该选项,那么图片会随着下载的进度一点点地显示出来。缺省情况下,图片是下载完成后一次显示出来的。

2.3 SDWebImageDownloaderOperation


看到这个类,我内心是愉快的。之前我不是说这个opertion应该和NSOperation有些关系吗?这个类就是NSOperation的子类啊,并且遵循SDWebImageOperation协议。这下SDWebImageDownloaderOperation将NSOperation和SDWebImageOperation联系在了一起,我们可以看下它的声明:

@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation>

所以不用说,这个类一定是个重头戏。

但是我们搜索SDWebImageDownloaderOperation,发现SDWebImageManager中的downloadImageWithURL函数并没有返回SDWebImageDownloaderOperation。这一点很让人疑惑。不过我们发现SDWebImageDownloaderOperation遵循SDWebImageOperation协议,会不会和downloadImageWithURL的返回值id<SDWebImageOperation>有关系?而且看到这,我有些迷糊了。函数返回值为id<protocol>,这是什么返回值?什么时候需要这样用?感觉源码阅读进行不下去了。。。先不急,既然注释说返回的是SDWebImageDownloaderOperation,那么肯定就是啦。

我先在所有工程中搜索SDWebImageDownloaderOperation,发现在SDWebImageDownloader中也有一个downloadImageWithURL函数。而且里面就定义了一个opertion,这个opertion就是SDWebImageDownloaderOperation 类型,并且这个函数也是返回operation的。

__block SDWebImageDownloaderOperation *operation;

回到我们的SDWebImageManager中的downloadImageWithURL函数中,搜索downloadImageWithURL,找找看是不是有蛛丝马迹。果然,下面这段代码:

id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)

此处的downloadImageWithURL是SDWebImageDownloader的一个方法。

好,但此为止,我们也只是觉得上面那些东西有联系,但是联系并不是很清晰。而且已经有了一个downloadImageWithURL,还要弄一个干什么?

太多问题了,我现在也只能大概猜测到底怎么回事了,在继续探索之前,我们先整理这里面的关系:

【原】SDWebImage源码阅读(二)

上面文字部分,我用红色标注了两个问题,我们先解决第一个


问题一:函数返回值为id<protocol>,这是什么返回值?

其实返回的是一个id类型,只是这个id类型一定要遵循里面的protocol,比如id<SDWebImageOperation>,那么因为SDWebImageDownloaderOperation遵循SDWebImageOperation协议,所以可以作为返回类型。



问题二:已经有了一个downloadImageWithURL,还要弄一个干什么?

这个说实话,我也不是很清楚,只能找这两个函数之间的关联了。其实更准确地说是找SDWebImageManager中downloadImageWithURL中的subOperation(SDWebImageDownloaderOperation)和operation的关系。根据这个思路,我发现了subOperation只在这个函数里面出现了两次。第一次是定义的地方,第二次就是:

operation.cancelBlock = ^{
[subOperation cancel]; @synchronized (self.runningOperations) {
[self.runningOperations removeObject:weakOperation];
}
};

无语,你辛辛苦苦弄了个subOperation,结果就亮了个像,还是cancel,就没了。太没人性了。不过是细想其实是有原因的,我在SDWebImageDownloaderOperation的downloadImageWithURL函数注释中找到了答案:

@return A cancellable SDWebImageOperation

一个cancellable的SDWebImageOperation,是不是和这里只用了cancel对应上了。虽然找到了点联系,不过还是流于表面,这与为什么这么做,这么做的理由还不是很清楚。

我们还是细细分析cancelBlock那段代码

首先是一个block,operation.cancelBlock有一个对应的setCancelBlock函数:

- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
// 检测self(是一个SDWebImageCombinedOperation类型的operation)是否取消了,如果取消了,就执行对应的cancelBlock函数。
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil; //不要忘了置cancelBlock为nil,否则会crash
} else {
_cancelBlock = [cancelBlock copy];
}
}

也就是说operation如果取消了,那么就会执行subOperation的cancel函数。并且从runningOperations中移除该operation,因为是block,为了避免循环引用,所以使用了weakOperation。runningOperations大概从名字也能猜到,用来存储正在运行的operation。既然当前operation被取消了,肯定要从runningOperations移除的嘛!

注意此处的operation的类型是SDWebImageCombinedOperation,具体定义如下:

// 遵循SDWebImageOperation
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation> @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
// 注意SDWebImageCombinedOperation遵循SDWebImageOperation,所以实现了cancel方法
// 在cancel方法中,主要是调用了cancelBlock,这个设计很值得琢磨
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
// 根据cacheType获取到image,这里虽然名字用的是cache,但是如果cache没有获取到图片
// 还是要把image下载下来的。此处只是把通过cache获取image和通过download获取image封装起来
@property (strong, nonatomic) NSOperation *cacheOperation; @end

还有一个问题就是@synchronized是什么?

知识点:@synchronized

避免多个线程执行同一段代码,主要防止当前operation会被多次remove,从而造成crash。这里括号内的self.runningOperations是用作互斥信号量。 即此时其他线程不能修改self.runningOperations中的属性。


虽然看懂了这段代码,可是后面不知道该看什么了。

所以我还是从头看这段代码(SDWebImageManager中downloadImageWithURL),看能不能找到点什么头绪:

// 如果调用此方法,而没有传completedBlock,那将是无意义的
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

不要将completedBlock参数设为nil,因为这样做是毫无意义的。如果你是想使用downloadImageWithURL来预先获取image,那就应该使用[SDWebImagePrefetcher prefetchURLs],而不是直接调用SDWebImageManager中的downloadImageWithURL函数。

// 使用NSString对象而非NSURL作为url是常见的错误. 因为某些奇怪的原因,Xcode不会报任何类型不匹配的警告,这里允许传NSString对象给URL。
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
} // 防止传了一个NSNull值给NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}

我觉得此处对于细节地处理很值得学习。

__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;

这个没啥好说的,避免循环引用,使用了weak。

    BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
} if (url.absoluteString.length == || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}

这个failedURLs从字面上理解就是一组下载失败的图片URL。所以这段代码也很好理解,就是如果这个图片url无法下载,那就使用completedBlock进行错误处理。那什么情况下算这个图片url无法下载呢?第一种情况是该url为空,另一种情况就是如果是failedUrl也无法下载,但是要避免无法下载就放入failedUrl的情况,就要设置options为SDWebImageRetryFailed。一般默认image无法下载,这个url就会加入黑名单,但是设置了SDWebImageRetryFailed会禁止添加到黑名单,不停重新下载。

如果该url可以下载,那么就添加一个新的operation到runningOperations中。

@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}

剩下的100多行就是为了生成一个cacheOperation。那它到底是何方神圣?它是一个NSOperation,所以加入NSOperationQueue会自动执行。不过我还是全局搜索cacheOperation,发现它在SDWebImageCombinedOperation中的cancel方法中调用了:

- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock(); // TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
}

还记得SDWebImageCombinedOperation遵循SDWebImageOperation协议吗?这就是SDWebImage实现的cancel。而cacheOperation是NSOperation,所以调用自身的cancel。注意是在这才会设置cancelled设为YES。

好,现在回来看这个queryDiskCacheForKey函数。在此之前,先看上面有段代码,用图片的url来获取cache对应的key,也就是说cache中如果已经有了该图片,那就返回该图片在cache中对应的key,你可以根据这个key去cache中获取图片。

NSString *key = [self cacheKeyForURL:url];

获取到key后,你就可以使用queryDiskCacheForKey函数去查找了:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
// 如果doneBlock不存在。那么就return nil。这个处理和downloadImageWithURL的completedBlock很类似
if (!doneBlock) {
return nil;
}
// 如果key为nil,说明cache中没有该image。所以doneBlock中传入SDImageCacheTypeNone,表示cache中没有图片,要从网络重新获取。
if (!key) {
doneBlock(nil, SDImageCacheTypeNone);
return nil;
}
    // 如果key不为nil
// 首先在内存cache中查找
UIImage *image = [self imageFromMemoryCacheForKey:key];
// 找到了,就传入SDImageCacheTypeMemory,说是在内存cache中获取的
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
} // 否则,说明图片就在磁盘cache中。
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
} @autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
// 如果磁盘中得到了该image,并且还需要缓存到内存中,为了同步最新数据
if (diskImage && self.shouldCacheImagesInMemory) {
                // 后面细讲
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
// 传入SDImageCacheTypeDisk,说明是从磁盘中获取的
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
}); return operation;
}

其实这段代码如果不深究的话,也很容易理解的。我直接把说明写成注释在上面了。不过这里我还有个疑问,就是为啥operation要在查找硬盘缓存时,才创建了一个新的operation?这里我谈谈我的想法:因为“图片可以用”这个状态意味着图片必须在内存中了。图片在网络还是在硬盘,其实相对来说并没有本质区别,最后都是要加进内存的。所以这里就有一个加载到内存的过程,需要产生一个NSOperation,也就理所当然会发生cancel。我这也不是胡乱猜的,函数中有一个self.ioQueue。表明这是一个io序列(dispatch_queue_t)。

这下再回到downloadImageWithURL里面剩下的代码,就会很轻松了。因为它无非就是要处理上面那几种cache情况嘛。

我们还是一点点来看done^{}中的代码:

if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
} return;
}

这段代码简单,不解释了,随时判断该operation是否已经cancel了。

下面又是一段巨长的代码,我们先看看if中表示什么:

if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
// ...
}

我们先看后面那个delegate方法:

/**
* 当image无法在缓存中找到,调用该函数控制该image的下载
*
* @param imageManager 当前的`SDWebImageManager`
* @param imageURL 需要下载的image的URL
*
* @return 返回NO表示当图片缓存未命中,反而阻止图片下载。如果该函数没实现,相当于返回YES。
*/
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;

这里要着重说明下此处return的含义。

注意在if里面最后一组||表达式,使用了短路判断(A||B,只要A为真,就不用判断B了),也就是说,如果delegate没有实现上面那个函数,整个表达式就为真,相当于该函数返回了YES。如果delegate实现了该函数,那就执行该函数,并且判断该函数执行结果。如果函数返回NO,那么整个if表达式都为NO,那么当图片缓存未命中时,图片下载反而被阻止。

目前我看的源码中并没有地方实现了该函数,所以就当if后半段恒为YES。我们主要还是看前面那个||表达式:

(!image || options & SDWebImageRefreshCached)

如果没有缓存到image,或者options中有SDWebImageRefreshCached选项,就执行if语句。现在我们深入看看if判断下的代码到底执行了什么,首先又是一个if语句:

if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// 如果图片在缓存中找到,但是options中有SDWebImageRefreshCached
// 那么就尝试重新下载该图片,这样是NSURLCache有机会从服务器端刷新自身缓存。
        completedBlock(image, nil, cacheType, YES, url);
});
}

下面的代码就表示开始要下载图片了。

首先定义了一个SDWebImageDownloaderOptions枚举值downloaderOptions,并根据options来设置downloaderOptions。基本上SDWebImageOptions和SDWebImageDownloaderOptions是一一对应的。只需要注意最后一个选项SDWebImageRefreshCached,这个得先强制关闭ProgressiveDownload方式。那后面的SDWebImageDownloaderIgnoreCachedResponse是什么意思呢?可能会有这样的疑惑,不是已经从imageCache中获取到了image了吗?还要Ignore干啥?这里简单提下,后面会详解:因为SDWebImage有两种缓存方式,一个是SDImageCache,一个就是NSURLCache,所以知道为什么这个选项是Ignore了吧,因为已经从SDImageCache获取了image,就忽略NSURLCache了。

if (image && options & SDWebImageRefreshCached) {
// 相当于downloaderOptions = downloaderOption & ~SDWebImageDownloaderProgressiveDownload);
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// 相当于 downloaderOptions = (downloaderOptions | SDWebImageDownloaderIgnoreCachedResponse);
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}

然后生成了一个subOperation,这段代码也很长,我大致看了下SDWebImageDownloader中的downloadImageWithURL函数,感觉终于到了 “真正”下载的代码了。为什么这么说了?因为里面代码大部分都是iOS自带框架底层的代码了。总算到头了。不过这段代码我准备下一篇再看。

直接跳出这个subOperation的赋值语句,来到对应的else if语句:

else if (image) {
       // 从缓存中获取到了图片,而且不需要刷新缓存的
        // 直接执行completedBlock,其中error置为nil即可。
dispatch_main_sync_safe(^{
if (!weakOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
// 执行完后,说明图片获取成功,可以把当前这个operation溢移除了。
        @synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
else {
// 又没有从缓存中获取到图片,shouldDownloadImageForURL又返回NO,不允许下载,悲催!
        // 所以completedBlock中image和error均传入nil。
dispatch_main_sync_safe(^{
if (!weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}

恩,SDWebImageManager中的downloadImageWithURL函数我们还剩下那个最精彩的SDWebImageDownloader中的downloadImageWithURL函数,留着下一篇阅读。

【原】SDWebImage源码阅读(二)的更多相关文章

  1. 【原】SDWebImage源码阅读(一)

    [原]SDWebImage源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 一直没有系统地读过整套源码,就感觉像一直看零碎的知识点,没有系统读过一本专业经典书 ...

  2. 【原】SDWebImage源码阅读(五)

    [原]SDWebImage源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 前面的代码并没有特意去讲SDWebImage的缓存机制,主要是想单独开一章节专门讲 ...

  3. 【原】SDWebImage源码阅读(四)

    [原]SDWebImage源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 SDWebImage中主要实现了NSURLConnectionDataDelega ...

  4. 【原】SDWebImage源码阅读(三)

    [原]SDWebImage源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1.SDWebImageDownloader中的downloadImageWithURL 我们 ...

  5. SDWebImage 源码阅读分享

    SDWebImage 源码阅读分享 疑问列表 SDWebImage 整体框架图,主要的类包含哪些 SDWebImage 如何进行缓存管理,过期失效策略,缓存更新 SDWebImage 如何多线程处理的 ...

  6. SDWebImage源码阅读-第三篇

    这一篇讲讲不常用的一些方法. 1 sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: com ...

  7. SDWebImage源码阅读-第一篇

    一 题外话 之前写过一篇最新版SDWebImage的使用,也简单的介绍了一下原理.这两天正梳理自己的知识网络,觉得有必要再阅读一下源码,一是看具体实现,二是学习一下优秀开源代码的代码风格,比如接口设计 ...

  8. &lpar;原&rpar;NSQ源码阅读和分析&lpar;1&rpar;

    原文出处:https://www.cnblogs.com/lihaiping/p/12324371.html 本文记录自己在阅读和学习nsq源码的时候的一些学习笔记,主要目的是个人总结和方便后期查阅. ...

  9. xxl-job源码阅读二(服务端)

    1.源码入口 xxl-job-admin是一个简单的springboot工程,简单翻看源码,可以很快发现XxlJobAdminConfig入口. @Override public void after ...

随机推荐

  1. android socket 线程连接openwrt与arduino单片机串口双向通信

    package zcd.netanything; import java.io.BufferedReader; import java.io.InputStreamReader; import jav ...

  2. 【c&num;搬砖记】用Docx导出word格式的docx文件

    DocX开源网址:http://docx.codeplex.com/ 1.引入DocX.dll 调用ReplaceText()方法替换模板中的字符.只支持docx格式的word文档 using (Do ...

  3. LeetCode Closest Binary Search Tree Value II

    原题链接在这里:https://leetcode.com/problems/closest-binary-search-tree-value-ii/ 题目: Given a non-empty bin ...

  4. &lbrack;原创&rsqb;java WEB学习笔记68:Struts2 学习之路-- 类型转换与复杂属性配合使用

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  5. iOS导航栏-导航栏透明

    设置一张透明图片:nav_bargound.png  //导航栏背景     [self.navigationController.navigationBar setBackgroundImage:[ ...

  6. CodeIgniter 3&period;0&plus; 部署linux环境 session报错

    codeigniter Message: mkdir(): Invalid path Filename: drivers/Session_files_driver.php 看起来像权限问题,在默认情况 ...

  7. Linux基础命令---验证组文件grpck

    grpck grpck指令可以验证组文件“/etc/group”和“/etc/gshadow”的完整性.检查的内容包括:正确的字段数.唯一有效的组名称.有效的组标识符.成员和管理员的有效列表.“/et ...

  8. Jenkins的详细安装

    操作环境:Windows 一.环境准备 1 安装JDK 本文采用jdk-8u111-windows-x64.exe: 2 配置tomcat 本文采用tomcat8,无需安装,配置JAVA_HOME及J ...

  9. 针对appium的webdriver执行swipe无效的解决办法

    self.driver.swipe(x1,y1,x2,y1,t) 当时代码里有如上这么一句,当时源码是这么说的: # convenience method added to Appium (NOT S ...

  10. HDU 4455&period;Substrings

    Substrings Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...