zoukankan      html  css  js  c++  java
  • iOS-OC-APP热更新,动态更新(仿QQ打开或关闭某个功能)

    一.前言

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

    二.创建Framework

    1.新建项目

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

    2.添加Aggregate

    在TARGETS里面新建一个Aggregate

    3.添加Run Script脚本

    4.脚本源码

    [objc] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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表示关闭;
    [objc] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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.     //添加假的功能列表  
    8.     NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];  
    9.     if(functionList==nil || functionList.count==0){  
    10.         NSArray *titleArr  = @[@"功能1",@"功能2",@"功能3",@"功能4"];  
    11.         NSArray *className = @[@"HotUpdateControl",@"ZFJViewController",@"",@""];  
    12.         NSArray *classType = @[@"NSObject",@"UIViewController",@"",@""];  
    13.         NSArray *downUrl   = @[  
    14.                                @"http://7xqdun.com1.z0.glb.clouddn.com/HotMudel.framework.zip",  
    15.                                @"http://7xqdun.com1.z0.glb.clouddn.com/FunctionZFJ1.framework.zip",  
    16.                                @"",  
    17.                                @""];  
    18.         NSMutableArray *functionArr = [[NSMutableArray alloc]init];  
    19.         for (int i = 0; i<titleArr.count; i++) {  
    20.             NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];  
    21.             [dict setObject:titleArr[i] forKey:@"name"];  
    22.             [dict setObject:className[i] forKey:@"classname"];  
    23.             [dict setObject:classType[i] forKey:@"classtype"];  
    24.             [dict setObject:@(i) forKey:@"mid"];  
    25.             [dict setObject:@"0" forKey:@"isopen"];//0 未开启  1开启了  
    26.             [dict setObject:downUrl[i] forKey:@"downurl"];  
    27.             [functionArr addObject:dict];  
    28.         }  
    29.         [USER_DEFAULT setObject:functionArr forKey:@"functionList"];  
    30.         [USER_DEFAULT synchronize];  
    31.     }  
    32.       
    33.     DynamicViewController *dvc = [[DynamicViewController alloc]init];  
    34.     UINavigationController *nvc = [[UINavigationController alloc]initWithRootViewController:dvc];  
    35.     self.window.rootViewController = nvc;  
    36.       
    37.     return YES;  
    38. }  

    2.展示功能列表

    在功能列表主要用于展示所有打开过的功能,也就是isopen为1的所有功能;
    a.获取本地所有打开的数据,然后在tableview上显示
    [objc] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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作为区分;
    [objc] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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.       
    13.     NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];  
    14.     if (!bundle || ![bundle load]) {  
    15.         NSLog(@"bundle加载出错");  
    16.     }  
    17.       
    18.     NSString *className = dict[@"classname"];  
    19.     NSString *classtype = dict[@"classtype"];  
    20.       
    21.     Class loadClass = [bundle classNamed:className];  
    22.     if (!loadClass) {  
    23.         NSLog(@"获取失败");  
    24.         return;  
    25.     }  
    26.       
    27.     if([classtype isEqualToString:@"NSObject"]){  
    28.         NSObject *bundleObj = [loadClass new];  
    29.         NSArray *arrVc = [bundleObj performSelector:@selector(getVcs)];  
    30.         TabController *tvc = [[TabController alloc]initwithVcArray:arrVc];  
    31.         [self.navigationController pushViewController:tvc animated:YES];  
    32.     }else if([classtype isEqualToString:@"UIViewController"]){  
    33.         UIViewController *uvc = (UIViewController *)[loadClass new];  
    34.         [self.navigationController pushViewController:uvc animated:YES];  
    35.     }  
    36. }  

    c.效果图
     

    3.更多功能

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

    b.打开功能
    打开某个功能就是下载对应的framework,把下载下来的zip包进行解压一下然后获取到framework,接着删除zip包,把framework放在对于的目录下;最后改变本地列表功能的状态;
    [objc] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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.           
    40.     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {  
    41.         NSLog(@"下载失败 --- 开启失败");  
    42.           
    43.     }];  
    44.     [operation start];  
    45. }  
    46. </span>  
    更新本地数据
    [objc] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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,然后更改功能列表的状态;
    [objc] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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的命名在同一个功能里面不能重复,不然调取失败;
     

    五.效果图

  • 相关阅读:
    HDU1372,BFS象棋马走日
    看完一本小的算法书一个总结吧
    最小生成树Prim
    Junit单元测试的简单使用(主要是在spring框架下的项目)
    并查集
    最新最实用的公式技巧大汇总!
    这款Office密码破解工具,无坚不摧!
    有了它,友谊的船说不翻就不翻!
    Word公式装逼技巧,你绝对不会!
    为什么MathType窗口变灰色
  • 原文地址:https://www.cnblogs.com/zjoch/p/6018289.html
Copyright © 2011-2022 走看看