一.前言

iOS开发更新APP我觉得是比较坑的就是审核时间比较长,审核比较严,对于刚入行的小伙伴来说,雷区比较多;所以热更新是比较重要的;
大家也许会发现我们常用的QQ现在下来也就一百多兆,但是用了几个月后发现QQ在手机上占有一个多G的内存,特别是手机内存比较小的小伙伴,这是因为你在使用过程中,有一些功能是你下载下来的;

二.创建Framework

1.新建项目

新建一个Cocoa Touch Framework项目,然后在这个项目里面写你的新的功能,比如我创建了一个控制器,在控制器里面加载一张图和一个label;
  1. <span style="font-size:18px;">- (void)uiConfig{
  2. self.title = @"这是功能2";
  3. UIImageView *imageView = [[UIImageView alloc]init];
  4. imageView.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);
  5. NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/item/201405/31/20140531174207_hH5u4.thumb.700_0.jpeg"]];
  6. imageView.image = [UIImage imageWithData:data];
  7. [self.view addSubview:imageView];
  8. UILabel *label = [[UILabel alloc]init];
  9. label.backgroundColor = [UIColor clearColor];
  10. label.frame = CGRectMake(0, (ScreenHeight - 100)/2, ScreenWidth, 100);
  11. label.numberOfLines = 0;
  12. label.text = @"这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2这是功能2";
  13. [self.view addSubview:label];
  14. }</span>

2.添加Aggregate

在TARGETS里面新建一个Aggregate

3.添加Run Script脚本

4.脚本源码

  1. <span style="font-size:18px;"># Sets the target folders and the final framework product.
  2. # 如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME
  3. # 例如: FMK_NAME = "MyFramework"
  4. FMK_NAME=${PROJECT_NAME}
  5. # Install dir will be the final output to the framework.
  6. # The following line create it in the root folder of the current project.
  7. INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework
  8. # Working dir will be deleted after the framework creation.
  9. WRK_DIR=build
  10. DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
  11. SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
  12. # -configuration ${CONFIGURATION}
  13. # Clean and Building both architectures.
  14. xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos clean build
  15. xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator clean build
  16. # Cleaning the oldest.
  17. if [ -d "${INSTALL_DIR}" ]
  18. then
  19. rm -rf "${INSTALL_DIR}"
  20. fi
  21. mkdir -p "${INSTALL_DIR}"
  22. cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
  23. # Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
  24. lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
  25. rm -r "${WRK_DIR}"
  26. open "${INSTALL_DIR}"
  27. </span>

5.运行打包

运行工程,将生成的framework包压缩zip,然后上传服务器;
例如:http://7xqdun.com1.z0.glb.clouddn.com/FunctionZFJ1.framework.zip
 

三.创建项目

在项目中我们主要是下载和读取framework包;我们先要获取功能列表,在此我在本地写了一个功能列表,大家如果用得到可以将功能列表存放在服务器上;

1.创建功能列表数据

我添加了四个功能模块,存在NSUserDefaults里面;其中功能1和功能2有下载地址,其他的没有;功能1是个NSObject,功能2直接是一个控制器;
isopen:1表示打开,0表示关闭;
  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  2. // Override point for customization after application launch.
  3. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  4. self.window.backgroundColor = [UIColor whiteColor];
  5. [self.window makeKeyAndVisible];
  6. //添加假的功能列表
  7. NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
  8. if(functionList==nil || functionList.count==0){
  9. NSArray *titleArr  = @[@"功能1",@"功能2",@"功能3",@"功能4"];
  10. NSArray *className = @[@"HotUpdateControl",@"ZFJViewController",@"",@""];
  11. NSArray *classType = @[@"NSObject",@"UIViewController",@"",@""];
  12. NSArray *downUrl   = @[
  13. @"http://7xqdun.com1.z0.glb.clouddn.com/HotMudel.framework.zip",
  14. @"http://7xqdun.com1.z0.glb.clouddn.com/FunctionZFJ1.framework.zip",
  15. @"",
  16. @""];
  17. NSMutableArray *functionArr = [[NSMutableArray alloc]init];
  18. for (int i = 0; i<titleArr.count; i++) {
  19. NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
  20. [dict setObject:titleArr[i] forKey:@"name"];
  21. [dict setObject:className[i] forKey:@"classname"];
  22. [dict setObject:classType[i] forKey:@"classtype"];
  23. [dict setObject:@(i) forKey:@"mid"];
  24. [dict setObject:@"0" forKey:@"isopen"];//0 未开启  1开启了
  25. [dict setObject:downUrl[i] forKey:@"downurl"];
  26. [functionArr addObject:dict];
  27. }
  28. [USER_DEFAULT setObject:functionArr forKey:@"functionList"];
  29. [USER_DEFAULT synchronize];
  30. }
  31. DynamicViewController *dvc = [[DynamicViewController alloc]init];
  32. UINavigationController *nvc = [[UINavigationController alloc]initWithRootViewController:dvc];
  33. self.window.rootViewController = nvc;
  34. return YES;
  35. }

2.展示功能列表

在功能列表主要用于展示所有打开过的功能,也就是isopen为1的所有功能;
a.获取本地所有打开的数据,然后在tableview上显示
  1. - (void)getDataBase{
  2. [self.dataArray removeAllObjects];
  3. NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
  4. for (NSDictionary *dict in functionList) {
  5. NSInteger isopen = [dict[@"isopen"] integerValue];
  6. if(isopen==1){
  7. [self.dataArray addObject:dict];
  8. }
  9. }
  10. [self.tableview reloadData];
  11. }

b.点击对于的tableviewcell 的时候跳转对应的framework读取出来的方法

注意,我将不同的framework存放在不同的文件夹下,以mid作为区分;
  1. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
  2. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  3. NSDictionary *dict = self.dataArray[indexPath.row];
  4. //获取framework的路径名,我已mid区分
  5. NSString *destinationPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];
  6. NSArray* arrFramework = [self getFilenamelistOfType:@"framework" fromDirPath:destinationPath];
  7. NSString *bundlePath = [NSString stringWithFormat:@"%@/%@",destinationPath,[arrFramework lastObject]];
  8. if (![[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
  9. NSLog(@"文件不存在");
  10. return;
  11. }
  12. NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
  13. if (!bundle || ![bundle load]) {
  14. NSLog(@"bundle加载出错");
  15. }
  16. NSString *className = dict[@"classname"];
  17. NSString *classtype = dict[@"classtype"];
  18. Class loadClass = [bundle classNamed:className];
  19. if (!loadClass) {
  20. NSLog(@"获取失败");
  21. return;
  22. }
  23. if([classtype isEqualToString:@"NSObject"]){
  24. NSObject *bundleObj = [loadClass new];
  25. NSArray *arrVc = [bundleObj performSelector:@selector(getVcs)];
  26. TabController *tvc = [[TabController alloc]initwithVcArray:arrVc];
  27. [self.navigationController pushViewController:tvc animated:YES];
  28. }else if([classtype isEqualToString:@"UIViewController"]){
  29. UIViewController *uvc = (UIViewController *)[loadClass new];
  30. [self.navigationController pushViewController:uvc animated:YES];
  31. }
  32. }
  1. c.效果图
 

3.更多功能

在这里我们可以打开或者关闭某个功能;
a.获取所以功能,包括打开或者关闭状态的;然后在tableview上显示;
  1. <span style="font-size:18px;">#pragma mark - 获取全部数据
  2. - (void)getDataBase{
  3. [self.dataArray removeAllObjects];
  4. NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
  5. NSMutableArray *openYES = [[NSMutableArray alloc]init];
  6. NSMutableArray *openNO = [[NSMutableArray alloc]init];
  7. for (NSDictionary *dict in functionList) {
  8. NSMutableDictionary *muDict = [[NSMutableDictionary alloc]initWithDictionary:dict];
  9. NSInteger isopen = [muDict[@"isopen"] integerValue];
  10. if(isopen==1){
  11. //已经打开的功能
  12. [openYES addObject:muDict];
  13. }else{
  14. //没有打开的功能
  15. [openNO addObject:muDict];
  16. }
  17. }
  18. [self.dataArray addObject:openNO];
  19. [self.dataArray addObject:openYES];
  20. [self.tableview reloadData];
  21. }</span>

b.打开功能

打开某个功能就是下载对应的framework,把下载下来的zip包进行解压一下然后获取到framework,接着删除zip包,把framework放在对于的目录下;最后改变本地列表功能的状态;
  1. <span style="font-size:18px;">#pragma mark - 开启某个功能 先下载数据
  2. - (void)SSZipArchiveDataBaseWithDict:(NSMutableDictionary *)dict{
  3. NSString *requestURL = dict[@"downurl"];
  4. if(requestURL==nil || requestURL.length==0){
  5. self.progresslabel.text = [NSString stringWithFormat:@"%@-没有下载地址,不能开启!",dict[@"name"]];
  6. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:@"没有下载地址,不能开启" preferredStyle:UIAlertControllerStyleAlert];
  7. UIAlertAction *sureBtn = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];
  8. [alertController addAction:sureBtn];
  9. [self presentViewController:alertController animated:YES completion:nil];
  10. return;
  11. }
  12. //下载保存的路径
  13. NSString *savedPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@.framework.zip",dict[@"mid"]]];
  14. AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
  15. NSMutableURLRequest *request = [serializer requestWithMethod:@"POST" URLString:requestURL parameters:nil error:nil];
  16. AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
  17. [operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:savedPath append:NO]];
  18. [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
  19. float progress = (float)totalBytesRead / totalBytesExpectedToRead;
  20. self.progresslabel.text = [NSString stringWithFormat:@"%@下载进度:%.2f",dict[@"name"],progress];
  21. }];
  22. [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
  23. NSLog(@"下载成功");
  24. NSString *destinationPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];
  25. //对下载下来的ZIP包进行解压
  26. BOOL isScu = [SSZipArchive unzipFileAtPath:savedPath toDestination:destinationPath];
  27. if(isScu){
  28. NSLog(@"解压成功");
  29. NSFileManager *fileMgr = [NSFileManager defaultManager];
  30. BOOL bRet = [fileMgr fileExistsAtPath:savedPath];
  31. if (bRet) {
  32. [fileMgr removeItemAtPath:savedPath error:nil];//解压成功后删除压缩包
  33. }
  34. [dict setValue:@"1" forKey:@"isopen"];
  35. [self updataBaseWithDict:dict];//解压成功后更新本地功能列表状态
  36. }else{
  37. NSLog(@"解压失败 --- 开启失败");
  38. }
  39. } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
  40. NSLog(@"下载失败 --- 开启失败");
  41. }];
  42. [operation start];
  43. }
  44. </span>

更新本地数据

  1. <span style="font-size:18px;">#pragma mark - 更新本地数据
  2. - (void)updataBaseWithDict:(NSMutableDictionary *)dict{
  3. NSInteger mid = [dict[@"mid"] integerValue];
  4. NSMutableArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
  5. NSMutableArray *dataArr = [[NSMutableArray alloc]initWithArray:functionList];
  6. [dataArr replaceObjectAtIndex:mid withObject:dict];
  7. [USER_DEFAULT setObject:dataArr forKey:@"functionList"];
  8. BOOL isScu = [USER_DEFAULT synchronize];
  9. if(isScu){
  10. [self getDataBase];//重新获取数据 更新列表
  11. if(self.refreshData){
  12. self.refreshData();
  13. }
  14. }else{
  15. NSLog(@"c操作失败");
  16. }
  17. }
  18. </span>
c.关闭功能
关闭某个功能,也就是删除某个功能的framework,然后更改功能列表的状态;
  1. <span style="font-size:18px;">#pragma mark - 关闭某个功能
  2. - (void)delectFunctionZFJWithDict:(NSMutableDictionary *)dict{
  3. NSFileManager *fileMgr = [NSFileManager defaultManager];
  4. NSString *savedPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];
  5. BOOL bRet = [fileMgr fileExistsAtPath:savedPath];
  6. if (bRet) {
  7. NSError *err;
  8. //关闭某个功能 就是删除本地的framework  然后修改本地功能状态
  9. BOOL isScu = [fileMgr removeItemAtPath:savedPath error:&err];
  10. if(isScu){
  11. [dict setValue:@"0" forKey:@"isopen"];
  12. [self updataBaseWithDict:dict];
  13. }else{
  14. NSLog(@"关闭失败");
  15. }
  16. }else{
  17. NSLog(@"关闭失败");
  18. }
  19. }
  20. </span>
 
d.效果图

四.源代码

在这里面有,两个framework的源代码,可项目的代码;
注意,如果有多个功能的framework,记住多个framework的命名在同一个功能里面不能重复,不然调取失败;
 

五.效果图

iOS-OC-APP热更新,动态更新(仿QQ打开或关闭某个功能)的更多相关文章

  1. App热补丁动态修复技术介绍

    安卓App热补丁动态修复技术介绍 来自qq空间团队:微信号qzonemobiledev QQ空间终端开发团队 1.背景 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就 ...

  2. iOS传感器集锦、飞机大战、开发调试工具、强制更新、Swift仿QQ空间头部等源码

    iOS精选源码 飞机大作战 MUPhotoPreview -简单易用的图片浏览器 LLDebugTool是一款针对开发者和测试者的调试工具,它可以帮... 多个UIScrollView.UITable ...

  3. 安卓App热补丁动态修复技术介绍

    版权声明:本文由johncz原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/169 来源:腾云阁 https://www.q ...

  4. Android仿qq聊天记录长按删除功能效果

    最近项目在做IM即时通讯开发,在删除聊天列表的时候跟删除聊天详细信息的时候,产品经理想要跟ios一样,在当前选中行上方弹出一个删除窗口.于是先从网上找demo,找了一个发现是Dialog做的,我感觉没 ...

  5. js高仿QQ消息列表左滑功能

    该组件,主要功能类似于QQ消息列表左滑出现删除.标为已读等按钮的功能:现在的版本用的是纯javaScript编写:后续会跟进 angularJs 开发的类似组件以及jquery的; 下面,就让我们来认 ...

  6. 移动端APP热更新方案(iOS+Android)

    出自:http://www.cnblogs.com/Creator/p/7007694.html 为什么要做热更新 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙 ...

  7. APP热更新方案

    为什么要做热更新 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App.测试.向各个应用市场和渠道换包.提示用户升级.用户下载.覆盖安装. 重 ...

  8. APP热更新方案(转)

    本文转载自[http://creator.cnblogs.com/] 博客地址:Zealot Yin 为什么要做热更新 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就 ...

  9. android Qzone的App热补丁热修复技术

    转自:https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731 ...

随机推荐

  1. rocksdb编译测试的正确姿势

    需要先安装 gflags 在进行 make db_bench 不然运行 db_bench 会出现 Please install gflags to run rocksdb tools 错误 bench ...

  2. c# 抓取Web网页数据分析

    通过程序自动的读取其它网站网页显示的信息,类似于爬虫程序.比方说我们有一个系统,要提取BaiDu网站上歌曲搜索排名.分析系统在根据得到的数据进行数据分析.为业务提供参考数据. 为了完成以上的需求,我们 ...

  3. error C2275: “XXX”: 将此类型用作表达式非法

    在移植c++代码到c的时候,经常会出现一个奇怪的错误,error C2275: “XXX”: 将此类型用作表达式非法 表达式非法,这个错误是由于c的编译器要求将变量的申明放在一个函数块的头部,而c++ ...

  4. 50条LINUX命令整理

    1. find 基本语法参数如下: find [PATH] [option] [action] # 与时间有关的参数: -mtime n : n为数字,意思为在n天之前的“一天内”被更改过的文件: - ...

  5. 「2013-9-5」Configure WingIDE for better display of East Asian Glyphs

    很久没写软件配置相关的博客了.这次对于 WingIDE 在 Windows 下的字体配置,折腾了好一阵子,略曲折,也反映了「不清楚原理和背景的情况下,盲人摸象的效率低下是必然」这条放之四海而皆准的赤果 ...

  6. 安装了linux系统的设备上不了网怎么办

    玩了一阵子的树莓派,曾经计划将其作成一台小小无线路由,但是时间和精力关系始终未成功做成. 同时也有在进行一些arm开发板的学习,突然一天发现arm板直接插上网线不能是不能上网的,又想起之前玩树莓派的时 ...

  7. 20145301&20145321&20145335实验一

    这次实验我的组员为:20145301赵嘉鑫.20145321曾子誉.20145335郝昊 实验内容详见:实验一报告

  8. Java集合框架面试题

    www.cnblogs.com/zhxxcq/archive/2012/03/11/2389611.html 这里的两个图很形象,由于放进图片链接,图片显示不了,所以只能给出该链接. Java集合框架 ...

  9. [ASE][Daily Scrum]11.13

    今天的计划如下: View Shilin Liu 修复残缺地图下的行进问题           Client Jiafan Zhu(回学校了) 和服务器端对接测试 Yiming Liao       ...

  10. 网站实时协作JavaScript库 TogetherJS

    TogetherJS是由Mozilla打造的一款可以给网站添加实时协作功能的JavaScript库,TogetherJS免费并且开源,遵循MPL 2.0开源协议,并且托管在Mozilla服务器上. 为 ...