iOS-OC-APP hot update, dynamic update (like QQ to open or close a function)

I. Introduction

I think it is more pitiful to develop and update the APP for iOS, that is, the review time is relatively long and the review is relatively strict. For those who have just entered the industry, there are many minefields; therefore, hot updates are more important;
You may find that our commonly used QQ is only more than 100 megabytes now, but after using it for a few months, it is found that QQ occupies more than one gigabyte of memory on the mobile phone, especially for those with small mobile phone memory, this is because you During use, some functions are downloaded by you;

2. Create a Framework

1. New project

Create a new Cocoa Touch Framework project, and then write your new function in this project. For example, I created a controller and loaded a picture and a label in the controller;
<span style="font-size:18px;">- (void)uiConfig{
    self.title = @"This is feature 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 = @"This is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2 this is function 2";
    [self.view addSubview:label];
}</span>

2. Add Aggregate

Create a new Aggregate in TARGETS


3. Add Run Script script



4. Script source code

<span style="font-size:18px;"># Sets the target folders and the final framework product.
# If the project name is different from the Framework's Target name, customize FMKNAME
# Example: 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的命名在同一个功能里面不能重复,不然调取失败;

五.效果图




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






Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325559298&siteId=291194637