最近需要实现动态配置TabbarIcon的需求,主要是针对节假日活动时,由后台配置富有节日元素的图标,我们移动端来替换展示。乍看这个需求很简单,就是从后台读取数据,拿到图片url后替换tabbar图标资源。可是在做的过程中发现还是需要思考一些东西的。下面我们一步步来实现。
实现思路
黄铜玩家思路
黄铜玩家自然指的的是新手,针对于初级开发工程师,他们拿到这个需求后,一般不会考虑太多,就是干。他们可能会在app启动后的首页调用配置tabbar图标的接口,获取后直接赋值给
UITabBarItem的image和selectedImage。如果后台接口没返回就使用默认图标。伪代码实现可能是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| - (void)configTabBarIcons{ if(success){//请求成功 NSMutableArray *imagesUrlArr;//网络请求获取的icon的url数组 if(imagesUrlArr.count){ //判断图片url数组是否为空 NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; UIImage *image = [UIImage imageWithData:data]; NSMutableArray *imagesArr;//存放image的数组 for (int i = 0; i < itemArray.count; i ++) {//根据item个数遍历替换图片资源 UITabBarItem * item = itemArray[i]; item.image=[[UIImage imageNamed:imagesArr[i]] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; }else{//未获取到图片url数组 //现实默认icon for (int i = 0; i < itemArray.count; i ++) {//根据item个数遍历替换图片资源 UITabBarItem * item = itemArray[i]; item.image=[[UIImage imageNamed:defImageArr[i]] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; } } } else{//接口调用失败 Toast(fail); } }
|
这个伪代码很丑我真的不想再写了。很明显他根本没有过多考虑多种可能性,比如接口调用时机,调用失败情况,代码性能就更不用提了。我下面就针对上面伪代码进行一次优化。
黄金玩家思路
处于这个阶段的开发者接到这个需求后可能会分几步考虑实现步骤:
(1) 接口的调用时机 :动态配置tabbar的图标属于应用层的配置,不应该和普通界面接口一个优先级,通常应该在程序启动时进行配置。可以放在AppDelegate里的 didFinishLaunchingWithOptions:方法内。
(2) 接口的调用失败处理 :正常调试没问题后,不应该认为此功能开发完毕。需要多考虑下其他非正常情况,比如接口调用失败。此时就不应该像 黄铜玩家 那样,直接toast提示失败信息。因为此接口属于应用级别不同于界面接口。我们不应该告诉用户获取配置icon失败等错误信息,用户也不明白这是什么意思。而且要考虑获取接口失败或者获取不到配置icon的数据是需要设置默认的tabbar图标。绝不简单的toast提示。
(3) 性能问题 : 这个阶段开发者都应该具备了代码性能的考虑。针对这个需求,是不是启动应用都需要拉取接口,获取到数据后是不是每次都要通过 dataWithContentsOfURL:方法把Url转为NSData处理,并生成UIImage对象。此时可能会考虑到缓存。思路是在获取到UIImage对象后,写入沙盒 [UIImagePNGRepresentation(image) writeToFile:fullPath atomically:YES]。在需要的时候从沙盒中读取数据 [UIImage imageWithContentsOfFile:fullPath]。期间会发现图片显示问题,查阅资料后通过,图片命名以@2x结尾后存入本地,默认是@1x的。这样以来,每次都先判断沙盒内是否缓存的有对应的图标,有的直接取出使用,没的话再生新的UIImage对象并存入沙盒内。
这样以来看似好了很多,但深入考虑后还是有改进的地方。
钻石玩家思路
姑且称作钻石玩家吧,王者玩家本人还没达到那种意识。需要优化的地方主要在 性能问题 :
首先是 缓存 上面提到了缓存。但做的还不够。这个阶段的高级开发者,首先会综合考虑 dataWithContentsOfURL:这个方法的性能怎样,他是同步还是异步的。处理小资源文件效率怎样。是否需要使用多线程,GCD,NSOperation,去处理。我综合考虑后认为处理icon这些小的图片资源影响不是很大,你当然可以使用更高级的多线程去异步下载图片资源,成功后统一缓存。我这里就不涉及了。在我决定使用 dataWithContentsOfURL:后。我会重新考虑缓存问题。如果每次有新的资源进来就缓存,那之前旧的缓存icon已经没用了却还站着内存。有的人说直接缓存到一定大小后,统一清除缓存。那么我要说,缓存多少后清除,清除时机又要放在哪里合适。就是这俩个问题都搞定了,这也不是最优方案,在清除缓存前,没用的icon仍然占用着内存。我是这样想的,由于此需求是真对tabbarItem的,我们是确定有几个item的,假如是四个,那么需要缓存的图片就是8个 选中和未选择两种状态的icon。再有新的icon需要替换时,我们只需获取到缓存icon文件下的子文件个数,如果为8个,就把这8个旧的icon给清除了,重新缓存新的icon。这样以来就完美了。没用的缓存icon会第一时间被清除。说到这里缓存是说完了。
替换时机 ,指的是拉取到配置图标后何时替换,是获取一个就替换还是8个都获取完成了在替换。这就涉及到两个问题。首先就算你拉取接口成功,返回给你了8个图标url,但也可能在调用 dataWithContentsOfURL:失败返回nil对象。url可能不合法,当然这是后台数据问题,但我们也要兼容考虑。我们可以这样处理,由于dataWithContentsOfURL:失败返回nil,我们可以在获取到图标资源url地址后,通过遍历,如果8个图标url都能成功生成UIImage对象,就代表此时可对tabbar图标替换,只要有任何一个返回nil就不替换使用默认图标。以免造成有的item的图标是旧的有的是新的不统一的bug。
下面把主要代码贴出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| /** 网络图片存入沙盒并返回(针对tabBarIcon使用) @param imageUrl 网络图片地址 @return 沙盒图片返回 */ + (UIImage *)tabBarItemImageUrl:(NSString *)imageUrl { NSArray *stringArr = [imageUrl componentsSeparatedByString:@"/"]; NSString *imageName = stringArr.lastObject; NSString *name = [[imageName componentsSeparatedByString:@"."] firstObject]; imageName = [NSString stringWithFormat:@"%@@2x.png",name]; NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES) lastObject]; NSLog(@"%@",path); NSString *iconFilePath = [path stringByAppendingPathComponent:@"tabbarIcon"]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *fullPath = [iconFilePath stringByAppendingPathComponent:imageName]; // 判断文件是否已存在,存在直接读取 if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) { NSLog(@"存在了"); return [UIImage imageWithContentsOfFile:fullPath]; } //获取iconFilePath下文件个数 NSArray *subPathArr = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:iconFilePath error:nil]; //超过8个说明icon需要更换清除iconFilePath文件 if (subPathArr.count >= 8) { [fileManager removeItemAtPath:iconFilePath error:nil]; } //再从新创建iconFilePath文件 BOOL isDir = NO; // fileExistsAtPath 判断一个文件或目录是否有效,isDirectory判断是否一个目录 BOOL existed = [fileManager fileExistsAtPath:iconFilePath isDirectory:&isDir]; if ( !(isDir == YES && existed == YES) ) { // 在 Caches 目录下创建一个 tabbarIcon 目录 [fileManager createDirectoryAtPath:iconFilePath withIntermediateDirectories:YES attributes:nil error:nil]; } NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; UIImage *image = [UIImage imageWithData:data]; // 将image写入沙河 if ( [UIImagePNGRepresentation(image) writeToFile:fullPath atomically:YES]) { return [UIImage imageWithContentsOfFile:fullPath]; }else { return nil; } }
|
使用时直接调用:
1
| item.image=[[UIImage tabBarItemImageUrl:self.imagesUrlArr[i]] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
好了,以上是本人的拙见,如有不足之处还请指出。