项目3:制作图片下载、缓存、图像处理的iOS静态库
2019-04-29 本文已影响1人
肠粉白粥_Hoben
一. 项目需求
![](https://img.haomeiwen.com/i8407639/01e06c0d72bdcf7c.png)
二. 项目架构
-
HobenImageManager
提供图片下载、处理接口 -
HobenImageCache
用于下载与缓存 -
HobenImageProcessManager
用于图片处理
![](https://img.haomeiwen.com/i8407639/9619028c3e056e50.png)
三. 图片下载、处理接口管理
1. 下载、处理图片
对于暴露给外界的接口,根据图片的URL地址来向图片下载缓存管理
请求返回image和下载进度,获得image后再向图片处理管理
请求处理图片,请求完毕后根据回调返回结果给调用接口的方法。
/**
*
根据处理类型下载图片
@param url 图片的URL地址
@param processType 图片处理类型
@param progressBlock 包含CGFloat类型的下载进度回调
@param completedBlock 包含UIImage类型的下载完成回调
*
**/
- (void)requestImageWithUrl:(NSString *)url
processType:(HobenImageProcessType)processType
progressBlock:(HobenImageProgressBlock)progressBlock
completedBlock:(HobenImageCompletedBlock)completedBlock {
[[HobenImageCache sharedInstance] requestImageWithUrl:url progressBlock:progressBlock completedBlock:^(UIImage * _Nullable image) {
[[HobenImageProcessManager sharedInstance] processImage:image processType:processType completedBlock:completedBlock];
}];
}
2. 清除缓存
清除缓存主要向图片下载缓存管理
请求清除缓存,清除缓存完成后会返回一个完成的回调。
/**
*
清除图片缓存
@param completionBlock 清除缓存成功后的回调
*
**/
- (void)removeCacheWithCompletionBlock:(void (^)(void))completionBlock {
[[HobenImageCache sharedInstance] removeCacheWithCompletionBlock:completionBlock];
}
四. 图片下载、缓存管理
1. 图片的下载和缓存
流程图如下,为确保图片不被重复下载,该模块使用单例模式,且保存了正在请求图片的URL,如果存在缓存,则直接回调,如果不存在,则调用下载方法,下载完成后缓存该图片,并及时回调图片和进度。
![](https://img.haomeiwen.com/i8407639/5ff049954220c630.png)
图片的缓存和下载主要使用了第三方库SDWebImage
。
1) 读取缓存图片
以URL为key,直接返回SDImageCache
存储的图片。
- (UIImage *)_imageWithUrl:(NSString *)url {
return [[SDImageCache sharedImageCache] imageFromCacheForKey:url];
}
2) 下载图片和回调进度
调用SDWebImage
的loadImageWithURL:options:progress:completed:
方法,加载完成后,第三方库会自动将该图片缓存起来。
- (void)_requestImageWithUrl:(NSString *)url
progressBlock:(HobenImageProgressBlock)progreeBlock
completedBlock:(HobenImageCompletedBlock)completedBlock {
WEAK_SELF_DECLARED
[[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
CGFloat progress = receivedSize * 1.f / expectedSize;
if (progreeBlock) {
progreeBlock(progress);
}
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
STRONG_SELF_BEGIN
if (image) {
[strongSelf _cacheImageWithUrl:url];
[strongSelf.requestList removeObject:url];
if (completedBlock) {
completedBlock(image);
}
} else {
NSLog(@"Cache ERROR in URL:%@", url);
[strongSelf.requestList removeObject:url];
if (completedBlock) {
completedBlock(nil);
}
}
STRONG_SELF_END
}];
}
2. 清除图片缓存
调用SDImageCache
的相应方法以清除缓存。
- (void)removeCacheWithCompletionBlock:(void (^)(void))completionBlock {
[self.requestList removeAllObjects];
[[SDImageCache sharedImageCache] clearMemory];
[[SDImageCache sharedImageCache] clearDiskOnCompletion:completionBlock];
}
五. 图像处理
图像处理提供的方法主要有以下三种:
typedef NS_ENUM(NSUInteger, HobenImageProcessType) {
HobenImageProcessTypeCommon = 0, // 不处理
HobenImageProcessTypeGaussian, // 高斯模糊
HobenImageProcessTypeWatermark, // 水印
};
1. 高斯模糊处理
在iOS绘图系列:图像模糊之均值模糊详细描述了相关原理,在此不赘述。
- (void)_processGaussianImage:(UIImage *)image completedBlock:(void (^)(UIImage * _Nullable image))completeBlock {
CGFloat blur = 0.5f;
int boxSize = (int)(blur * 40);
// 确保为奇数
boxSize = boxSize - (boxSize % 2) + 1;
CGImageRef imageRef = image.CGImage;
vImage_Buffer inBuffer, outBuffer;
vImage_Error error;
void *pixelBuffer;
// 从CGImage中获取数据
CGDataProviderRef inProvider = CGImageGetDataProvider(imageRef);
CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
// 设置从CGImage获取对象的属性
pixelBuffer = malloc(CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef));
outBuffer.width = inBuffer.width = CGImageGetWidth(imageRef);
outBuffer.height = inBuffer.height = CGImageGetHeight(imageRef);
outBuffer.rowBytes = inBuffer.rowBytes = CGImageGetBytesPerRow(imageRef);
inBuffer.data = (void *)CFDataGetBytePtr(inBitmapData);
outBuffer.data = pixelBuffer;
// 均值模糊
error = vImageBoxConvolve_ARGB8888(&inBuffer,
&outBuffer,
NULL,
0,
0,
boxSize,
boxSize,
NULL,
kvImageEdgeExtend);
if (error) {
NSLog(@"Error from convolution %ld", error);
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(outBuffer.data, outBuffer.width, outBuffer.height, 8, outBuffer.rowBytes, colorSpace, CGImageGetBitmapInfo(imageRef));
CGImageRef resultImageRef = CGBitmapContextCreateImage(context);
UIImage *resultImage = [UIImage imageWithCGImage:resultImageRef];
CGColorSpaceRelease(colorSpace);
free(pixelBuffer);
CFRelease(inBitmapData);
CGColorSpaceRelease(colorSpace);
CGImageRelease(resultImageRef);
if (completeBlock) {
completeBlock(resultImage);
}
}
2. 水印处理
在iOS绘图系列:Core Graphics绘图提到了相关原理,在此不赘述。
- (void)_processWatermarkImage:(UIImage *)image text:(NSString *)text completedBlock:(HobenImageCompletedBlock)completeBlock {
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
NSDictionary *dictionary = @{NSForegroundColorAttributeName: [UIColor whiteColor], NSFontAttributeName: [UIFont boldSystemFontOfSize:image.size.height / 10]};
[text drawAtPoint:CGPointZero withAttributes:dictionary];
UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (completeBlock) {
completeBlock(resultImage);
}
}
六. 导出Framework
之前学习过如何封装Framework,主要的步骤如下:
- 选择暴露的头文件
- 分别在真机环境和模拟器环境下导出Framework
- 通过
lipo -create
方法将两个Framework,合并成一个Framework后output
出来 - 将得到的Framework导入到需要用的文件中
这次使用shell脚本来导出Framework,步骤如注释所示:
#工作路径
WORKSPACE="/Users/xxx/Desktop/HobenImageLib/HobenImageLib.xcworkspace"
CONFIG="Release"
USER_HOME=$(eval echo ~${SUDO_USER})
#project名字
FMK_NAME=${PROJECT_NAME}
OUTPUT_DIR=${USER_HOME}/Desktop/Framework_build_output
INSTALL_DIR=${OUTPUT_DIR}/${FMK_NAME}.framework
DEVICE_DIR=${BUILD_DIR}/${CONFIG}-iphoneos/${FMK_NAME}.framework
TEMP_DEVICE_DIR=${OUTPUT_DIR}/TEMP_DEIVCE_DIR/${FMK_NAME}.framework
SIMULATOR_DIR=${BUILD_DIR}/${CONFIG}-iphonesimulator/${FMK_NAME}.framework
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
#XCode导出真机Framework,注意要先clean再build
xcodebuild -workspace "${WORKSPACE}" -configuration "Release" -scheme "${FMK_NAME}" -sdk iphoneos clean build OBJROOT="${OBJROOT}/DependentBuilds"
mkdir -p "${TEMP_DEVICE_DIR}"
cp -R "${DEVICE_DIR}/" "${TEMP_DEVICE_DIR}/"
#XCode导出模拟器Framework,注意要先clean再build
xcodebuild -workspace "${WORKSPACE}" -configuration "Release" -scheme "${FMK_NAME}" -sdk iphonesimulator clean build OBJROOT="${OBJROOT}/DependentBuilds"
mkdir -p "${INSTALL_DIR}"
cp -R "${TEMP_DEVICE_DIR}/" "${INSTALL_DIR}/"
#将两个Framework合并且导出
lipo -create "${TEMP_DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
rm -rf "${TEMP_DEVICE_DIR}"
# 保存静态库打包的版本信息
hash=$(git log -1 --pretty=format:"%h")
date=$(git log -1 --pretty=format:"%ad" --date=format:"%Y-%m-%d %H:%M:%S")
branch=$(git symbolic-ref --short -q HEAD)
info_plist="${INSTALL_DIR}/Info.plist"
open "${INSTALL_DIR}/../"
七. 导入Framework
在Demo中导入Framework并引用头文件,使用Framework里面暴露的头文件方法:
#import <HobenImageLib/HobenImageLib.h>
...
WEAK_SELF_DECLARED
void (^progressBlock)(CGFloat progress) = ^(CGFloat progress) {
dispatch_async(dispatch_get_main_queue(), ^{
STRONG_SELF_BEGIN
NSLog(@"progress: %lf", progress);
[strongSelf setProgressLabelValue:progress];
strongSelf.progressView.progress = progress;
STRONG_SELF_END
});
};
void (^completedBlock)(UIImage * _Nullable image) = ^(UIImage * _Nullable image) {
STRONG_SELF_BEGIN
PreviewController *vc = [[PreviewController alloc] initWithImage:image];
[strongSelf.navigationController pushViewController:vc animated:YES];
STRONG_SELF_END
};
[[HobenImageManager sharedInstance] requestImageWithUrl:url
processType:self.processType
progressBlock:progressBlock
completedBlock:completedBlock];
八. 成果展示
为了使得加载进度效果显示出来,在模拟器模拟了弱网效果。
![](https://img.haomeiwen.com/i8407639/4a05f71c6c5db937.png)
成果展示如下:
![](https://img.haomeiwen.com/i8407639/8e2a2396e8e1a0cb.gif)
九. Demo
我的Demo在这里,欢迎star~