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

一.前言

iOS开发更新APP我觉得是比较坑的就是审核时间比较长,审核比较严,对于刚入行的小伙伴来说,雷区比较多;所以热更新是比较重要的;

大家也许会发现我们常用的QQ现在下来也就一百多兆,但是用了几个月后发现QQ在手机上占有一个多G的内存,特别是手机内存比较小的小伙伴,这是因为你在使用过程中,有一些功能是你下载下来的;

二.创建Framework

1.新建项目

新建一个Cocoa Touch Framework项目,然后在这个项目里面写你的新的功能,比如我创建了一个控制器,在控制器里面加载一张图和一个label;

<span style="font-size:18px;">- (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];
}</span>

2.添加Aggregate

在TARGETS里面新建一个Aggregate

3.添加Run Script脚本

4.脚本源码

<span style="font-size:18px;"># 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}"
</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表示关闭;

- (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;
}

2.展示功能列表

在功能列表主要用于展示所有打开过的功能,也就是isopen为1的所有功能;

a.获取本地所有打开的数据,然后在tableview上显示

- (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];
}

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

注意,我将不同的framework存放在不同的文件夹下,以mid作为区分;

- (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];
    }
}
c.效果图

3.更多功能

在这里我们可以打开或者关闭某个功能;

a.获取所以功能,包括打开或者关闭状态的;然后在tableview上显示;

<span style="font-size:18px;">#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];
}</span>

b.打开功能

打开某个功能就是下载对应的framework,把下载下来的zip包进行解压一下然后获取到framework,接着删除zip包,把framework放在对于的目录下;最后改变本地列表功能的状态;

<span style="font-size:18px;">#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];
}
</span>

更新本地数据

<span style="font-size:18px;">#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操作失败");
    }
}
</span>

c.关闭功能

关闭某个功能,也就是删除某个功能的framework,然后更改功能列表的状态;

<span style="font-size:18px;">#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(@"关闭失败");
    }
}
</span>

d.效果图

四.源代码

在这里面有,两个framework的源代码,可项目的代码;

注意,如果有多个功能的framework,记住多个framework的命名在同一个功能里面不能重复,不然调取失败;

链接: https://pan.baidu.com/s/1mieyk3I 密码: mg4m

五.效果图

时间: 2024-12-15 22:32:11

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

Android热补丁动态更新实践

前言 好几个月之前关于Android App热补丁修复火了一把,源于QQ空间团队的一篇文章安卓App热补丁动态修复技术介绍,然后各大厂的开源项目都出来了,本文的实践基于HotFix,也就是QQ空间技术团队那篇文章所应用的技术,笔者会把整个过程的细节和思路在文章中详说,研究这个的出发点也是为了能紧急修复app的bug,而不需要重复发包,不需要用户重新下载app就能把问题解决,个人觉得这个还是蛮有价值的,虽然老板不知道-.. 项目结构 这里笔者创建一个新的项目"HotFixDemo",带大

IOS总结_可收缩分组表格(仿QQ联系人界面)

#import "yxpGroupTBVC.h" #define  DIC_EXPANDED @"expanded" //是否是展开 0收缩 1展开 #define  DIC_ARARRY @"array" #define  DIC_TITILESTRING @"title" #define  CELL_HEIGHT 40.0f @interfaceyxpGroupTBVC ()<UITableViewDataSourc

iOS中app在iTunes中更新版本流程

昨天终于把新版本的app上传了,我能说我在上传的前一分钟都在改bug吗 对这个版本的app我实在是没有什么信心去期盼它可以通过苹果残酷的审核 第一次去做更新版本这件事我也看了很多资料,大多数是网络博客 每次看到那些步骤详细,图文混排的博客,心中一直都存感激之情 程序员的世界总是略显孤独,有时会羡慕运营部的喧闹,但是,看到这么多博客,我想这也是我们的交流方式 我去,想文艺一下发现自己词穷 在已经在iTunes发布app的情况下,进行新版本的发布 首先,需要两个文件:production ver,p

iOS判断APP版本更新并获取更新内容

摘要:之前版本更新控制都是后台来控制的,包括更新提示内容.主要适用于APP1.0 若你的APP已经开始迭代,APP完全可以自己获取版本更新内容等信息. 大家对这个URL应该不陌生: http://itunes.apple.com?lookup?id=   后面APP的  在APPstore中的ID 可以利用这个地址获取想要的信息: 怎么请求就不用说了吧,可以现在Safari中看一下返回报文,你要的都在results  字典中.

App像Web一样发布新版本,安卓App热补丁动态修复技术介绍 转

背景 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App.测试.向各个应用市场和渠道换包.提示用户升级.用户下载.覆盖安装.有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布. 这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装? 解决方案 该方案基于的是android dex分包方案的,关于dex分包方案,网上有几篇解释了,所以这里就不再赘述,具体可

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

该组件,主要功能类似于QQ消息列表左滑出现删除.标为已读等按钮的功能:现在的版本用的是纯javaScript编写:后续会跟进 angularJs 开发的类似组件以及jquery的; 下面,就让我们来认识下怎么使用该程序: 在该程序里,总共分为四个文件: 1 .css文件夹 2. img 图片文件夹 3. js文件 4. index.html  主页面: 稍后,你可以自行下载,打开运行观看效果: 二.代码讲解 1.此html结构,为不可修改结构 <ul class="list-ul"

Android 热补丁动态修复框架小结

转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/49883661: 本文出自:[张鸿洋的博客] 一.概述 最新github上开源了很多热补丁动态修复框架,大致有: https://github.com/dodola/HotFix https://github.com/jasonross/Nuwa https://github.com/bunnyblue/DroidFix 上述三个框架呢,根据其描述,原理都来自:安卓App热补丁

iOS设置app应用程序文件共享

1.iOSapp应用程序文件共享 当我们用itnues连接到设备时,在应用程序栏目下面,文件共享下,点击 对应的程序,即可以在程序右边栏目里面看到应用程序共享的数据, 此时,我们可以通过右下角的 添加 和存储为 导入数据或是导出数据,如图 2. iOS设置app应用程序文件共享 设置流程 xcode 打开项目----在 info.plist 文件,添加 UIFileSharingEnabled 并设置属性为 YES 在app内部,将您希望共享的文件放在应用程序的Documents目录下

JSPatch – 动态更新iOS APP

博文转载至 http://blog.cnbang.net/works/2767/ JSPatch是最近业余做的项目,只需在项目中引入极小的引擎,就可以使用JavaScript调用任何Objective-C的原生接口,获得脚本语言的能力:动态更新APP,替换项目原生代码修复bug. 用途 是否有过这样的经历:新版本上线后发现有个严重的bug,可能会导致crash率激增,可能会使网络请求无法发出,这时能做的只是赶紧修复bug然后提交等待漫长的AppStore审核,再盼望用户快点升级,付出巨大的人力和