搜索
您的当前位置:首页正文

动态配置Tabbar图标最优解

来源:知库网

最近需要实现动态配置TabbarIcon的需求,主要是针对节假日活动时,由后台配置富有节日元素的图标,我们移动端来替换展示。乍看这个需求很简单,就是从后台读取数据,拿到图片url后替换tabbar图标资源。可是在做的过程中发现还是需要思考一些东西的。下面我们一步步来实现。

实现思路

  • 黄铜玩家思路
    黄铜玩家自然指的的是新手,针对于初级开发工程师,他们拿到这个需求后,一般不会考虑太多,就是干。他们可能会在app启动后的首页调用配置tabbar图标的接口,获取后直接赋值给
    UITabBarItem的image和selectedImage。如果后台接口没返回就使用默认图标。伪代码实现可能是这样的:
#pragma mark - 网络请求
- (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。
    下面把主要代码贴出:

/**
网络图片存入沙盒并返回(针对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;
}
}

使用时直接调用:

item.image=[[UIImage tabBarItemImageUrl:self.imagesUrlArr[i]] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

好了,以上是本人的拙见,如有不足之处还请指出。

Top