美文网首页iOS Developer
iOS-OC-APP热更新,动态更新(仿QQ打开或关闭某个功能)

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

作者: 俊俊吖 | 来源:发表于2017-04-13 15:10 被阅读0次

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


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

    在TARGETS里面新建一个Aggregate



    3.添加Run Script脚本



    4.脚本源码
    <pre>

    Sets the target folders and the final framework product.

    如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME

    例如: FMK_NAME = "MyFramework"

    FMK_NAME=${PROJECT_NAME}

    Install dir will be the final output to the framework.

    The following line create it in the root folder of the current project.

    INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework

    Working dir will be deleted after the framework creation.

    WRK_DIR=build
    DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
    SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework

    -configuration ${CONFIGURATION}

    Clean and Building both architectures.

    xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos clean build
    xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator clean build

    Cleaning the oldest.

    if [ -d "${INSTALL_DIR}" ]
    then
    rm -rf "${INSTALL_DIR}"
    fi
    mkdir -p "${INSTALL_DIR}"
    cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"

    Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.

    lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
    rm -r "${WRK_DIR}"
    open "${INSTALL_DIR}" </pre>
    5.运行打包

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

    三.创建项目
    在项目中我们主要是下载和读取framework包;我们先要获取功能列表,在此我在本地写了一个功能列表,大家如果用得到可以将功能列表存放在服务器上;
    1.创建功能列表数据
    我添加了四个功能模块,存在NSUserDefaults里面;其中功能1和功能2有下载地址,其他的没有;功能1是个NSObject,功能2直接是一个控制器;
    isopen:1表示打开,0表示关闭;
    <pre>- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    //添加假的功能列表
    NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
    if(functionList==nil || functionList.count==0){
    NSArray *titleArr = @[@"功能1",@"功能2",@"功能3",@"功能4"];
    NSArray *className = @[@"HotUpdateControl",@"ZFJViewController",@"",@""];
    NSArray *classType = @[@"NSObject",@"UIViewController",@"",@""];
    NSArray *downUrl = @[
    @"http://7xqdun.com1.z0.glb.clouddn.com/HotMudel.framework.zip",
    @"http://7xqdun.com1.z0.glb.clouddn.com/FunctionZFJ1.framework.zip",
    @"",
    @""];
    NSMutableArray *functionArr = [[NSMutableArray alloc]init];
    for (int i = 0; i<titleArr.count; i++) {
    NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
    [dict setObject:titleArr[i] forKey:@"name"];
    [dict setObject:className[i] forKey:@"classname"];
    [dict setObject:classType[i] forKey:@"classtype"];
    [dict setObject:@(i) forKey:@"mid"];
    [dict setObject:@"0" forKey:@"isopen"];//0 未开启 1开启了
    [dict setObject:downUrl[i] forKey:@"downurl"];
    [functionArr addObject:dict];
    }
    [USER_DEFAULT setObject:functionArr forKey:@"functionList"];
    [USER_DEFAULT synchronize];
    }
    DynamicViewController *dvc = [[DynamicViewController alloc]init];
    UINavigationController *nvc = [[UINavigationController alloc]initWithRootViewController:dvc];
    self.window.rootViewController = nvc;
    return YES;
    } </pre>
    2.展示功能列表
    在功能列表主要用于展示所有打开过的功能,也就是isopen为1的所有功能;
    a.获取本地所有打开的数据,然后在tableview上显示
    <pre>- (void)getDataBase{
    [self.dataArray removeAllObjects];
    NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
    for (NSDictionary *dict in functionList) {
    NSInteger isopen = [dict[@"isopen"] integerValue];
    if(isopen==1){
    [self.dataArray addObject:dict];
    }
    }
    [self.tableview reloadData];
    }
    </pre>

    b.点击对于的tableviewcell 的时候跳转对应的framework读取出来的方法
    注意,我将不同的framework存放在不同的文件夹下,以mid作为区分;

    <pre>- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    NSDictionary *dict = self.dataArray[indexPath.row];
    //获取framework的路径名,我已mid区分
    NSString destinationPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];
    NSArray
    arrFramework = [self getFilenamelistOfType:@"framework" fromDirPath:destinationPath];
    NSString *bundlePath = [NSString stringWithFormat:@"%@/%@",destinationPath,[arrFramework lastObject]];
    if (![[NSFileManager defaultManager] fileExistsAtPath:bundlePath]) {
    NSLog(@"文件不存在");
    return;
    }
    NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
    if (!bundle || ![bundle load]) {
    NSLog(@"bundle加载出错");
    }
    NSString *className = dict[@"classname"];
    NSString *classtype = dict[@"classtype"];
    Class loadClass = [bundle classNamed:className];
    if (!loadClass) {
    NSLog(@"获取失败");
    return;
    }
    if([classtype isEqualToString:@"NSObject"]){
    NSObject *bundleObj = [loadClass new];
    NSArray *arrVc = [bundleObj performSelector:@selector(getVcs)];
    TabController *tvc = [[TabController alloc]initwithVcArray:arrVc];
    [self.navigationController pushViewController:tvc animated:YES];
    }else if([classtype isEqualToString:@"UIViewController"]){
    UIViewController *uvc = (UIViewController *)[loadClass new];
    [self.navigationController pushViewController:uvc animated:YES];
    }
    } </pre>
    c.效果图

    3.更多功能
    在这里我们可以打开或者关闭某个功能;
    a.获取所以功能,包括打开或者关闭状态的;然后在tableview上显示;
    <pre>

    pragma mark - 获取全部数据

    • (void)getDataBase{
      [self.dataArray removeAllObjects];
      NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
      NSMutableArray *openYES = [[NSMutableArray alloc]init];
      NSMutableArray *openNO = [[NSMutableArray alloc]init];
      for (NSDictionary *dict in functionList) {
      NSMutableDictionary *muDict = [[NSMutableDictionary alloc]initWithDictionary:dict];
      NSInteger isopen = [muDict[@"isopen"] integerValue];
      if(isopen==1){
      //已经打开的功能
      [openYES addObject:muDict];
      }else{
      //没有打开的功能
      [openNO addObject:muDict];
      }
      }

      [self.dataArray addObject:openNO];
      [self.dataArray addObject:openYES];

      [self.tableview reloadData];
      }
      </pre>

    b.打开功能
    打开某个功能就是下载对应的framework,把下载下来的zip包进行解压一下然后获取到framework,接着删除zip包,把framework放在对于的目录下;最后改变本地列表功能的状态;
    <pre>#pragma mark - 开启某个功能 先下载数据
    -(void)SSZipArchiveDataBaseWithDict:(NSMutableDictionary *)dict{
    NSString *requestURL = dict[@"downurl"];
    if(requestURL==nil || requestURL.length==0){
    self.progresslabel.text = [NSString stringWithFormat:@"%@-没有下载地址,不能开启!",dict[@"name"]];
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:@"没有下载地址,不能开启" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *sureBtn = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];
    [alertController addAction:sureBtn];
    [self presentViewController:alertController animated:YES completion:nil];
    return;
    }
    //下载保存的路径
    NSString *savedPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@.framework.zip",dict[@"mid"]]];
    AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
    NSMutableURLRequest *request = [serializer requestWithMethod:@"POST" URLString:requestURL parameters:nil error:nil];
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
    [operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:savedPath append:NO]];
    [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
    float progress = (float)totalBytesRead / totalBytesExpectedToRead;
    self.progresslabel.text = [NSString stringWithFormat:@"%@下载进度:%.2f",dict[@"name"],progress];
    }];
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSLog(@"下载成功");
    NSString *destinationPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];
    //对下载下来的ZIP包进行解压
    BOOL isScu = [SSZipArchive unzipFileAtPath:savedPath toDestination:destinationPath];
    if(isScu){
    NSLog(@"解压成功");
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    BOOL bRet = [fileMgr fileExistsAtPath:savedPath];
    if (bRet) {
    [fileMgr removeItemAtPath:savedPath error:nil];//解压成功后删除压缩包
    }
    [dict setValue:@"1" forKey:@"isopen"];
    [self updataBaseWithDict:dict];//解压成功后更新本地功能列表状态
    }else{
    NSLog(@"解压失败 --- 开启失败");
    }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    NSLog(@"下载失败 --- 开启失败");
    }];
    [operation start];
    }
    </pre>
    更新本地数据
    <pre>#pragma mark - 更新本地数据
    -(void)updataBaseWithDict:(NSMutableDictionary *)dict{
    NSInteger mid = [dict[@"mid"] integerValue];
    NSMutableArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];
    NSMutableArray *dataArr = [[NSMutableArray alloc]initWithArray:functionList];
    [dataArr replaceObjectAtIndex:mid withObject:dict];
    [USER_DEFAULT setObject:dataArr forKey:@"functionList"];
    BOOL isScu = [USER_DEFAULT synchronize];
    if(isScu){
    [self getDataBase];//重新获取数据 更新列表
    if(self.refreshData){
    self.refreshData();
    }
    }else{
    NSLog(@"c操作失败");
    }
    } </pre>
    c.关闭功能
    关闭某个功能,也就是删除某个功能的framework,然后更改功能列表的状态;
    <pre>#pragma mark - 关闭某个功能
    -(void)delectFunctionZFJWithDict:(NSMutableDictionary *)dict{
    NSFileManager *fileMgr = [NSFileManager defaultManager];
    NSString *savedPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];
    BOOL bRet = [fileMgr fileExistsAtPath:savedPath];
    if (bRet) {
    NSError *err;
    //关闭某个功能 就是删除本地的framework 然后修改本地功能状态
    BOOL isScu = [fileMgr removeItemAtPath:savedPath error:&err];
    if(isScu){
    [dict setValue:@"0" forKey:@"isopen"];
    [self updataBaseWithDict:dict];
    }else{
    NSLog(@"关闭失败");
    }
    }else{
    NSLog(@"关闭失败");
    }
    } </pre>
    d.效果图


    四.源代码

    在这里面有,两个framework的源代码,可项目的代码;
    注意,如果有多个功能的framework,记住多个framework的命名在同一个功能里面不能重复,不然调取失败;
    链接: http://download.csdn.net/detail/u014220518/9643039

    五.效果图



    转载请注明来源:http://blog.csdn.net/u014220518/article/details/52248803

    相关文章

      网友评论

        本文标题:iOS-OC-APP热更新,动态更新(仿QQ打开或关闭某个功能)

        本文链接:https://www.haomeiwen.com/subject/ylmbattx.html