原文:http://www.cnblogs.com/zjoch/p/6018289.html
一.前言
iOS開發(fā)更新APP我覺得是比較坑的就是審核時(shí)間比較長(zhǎng),審核比較嚴(yán),對(duì)于剛?cè)胄械男』锇閬?lái)說(shuō),雷區(qū)比較多;所以熱更新是比較重要的;
大家也許會(huì)發(fā)現(xiàn)我們常用的QQ現(xiàn)在下來(lái)也就一百多兆,但是用了幾個(gè)月后發(fā)現(xiàn)QQ在手機(jī)上占有一個(gè)多G的內(nèi)存,特別是手機(jī)內(nèi)存比較小的小伙伴,這是因?yàn)槟阍谑褂眠^(guò)程中,有一些功能是你下載下來(lái)的;
二.創(chuàng)建Framework
1.新建項(xiàng)目
新建一個(gè)Cocoa Touch Framework項(xiàng)目,然后在這個(gè)項(xiàng)目里面寫你的新的功能,比如我創(chuàng)建了一個(gè)控制器,在控制器里面加載一張圖和一個(gè)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里面新建一個(gè)Aggregate
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.運(yùn)行打包
運(yùn)行工程,將生成的framework包壓縮zip,然后上傳服務(wù)器;
例如:http://7xqdun.com1.z0.glb.clouddn.com/FunctionZFJ1.framework.zip
三.創(chuàng)建項(xiàng)目
在項(xiàng)目中我們主要是下載和讀取framework包;我們先要獲取功能列表,在此我在本地寫了一個(gè)功能列表,大家如果用得到可以將功能列表存放在服務(wù)器上;
1.創(chuàng)建功能列表數(shù)據(jù)
我添加了四個(gè)功能模塊,存在NSUserDefaults里面;其中功能1和功能2有下載地址,其他的沒(méi)有;功能1是個(gè)NSObject,功能2直接是一個(gè)控制器;
isopen:1表示打開,0表示關(guān)閉;
- - (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.展示功能列表
在功能列表主要用于展示所有打開過(guò)的功能,也就是isopen為1的所有功能;
a.獲取本地所有打開的數(shù)據(jù),然后在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.點(diǎn)擊對(duì)于的tableviewcell 的時(shí)候跳轉(zhuǎn)對(duì)應(yīng)的framework讀取出來(lái)的方法
注意,我將不同的framework存放在不同的文件夾下,以mid作為區(qū)分;
- - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
- [tableView deselectRowAtIndexPath:indexPath animated:YES];
- NSDictionary *dict = self.dataArray[indexPath.row];
- //獲取framework的路徑名,我已mid區(qū)分
- 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加載出錯(cuò)");
- }
-
- 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.更多功能
在這里我們可以打開或者關(guān)閉某個(gè)功能;
a.獲取所以功能,包括打開或者關(guān)閉狀態(tài)的;然后在tableview上顯示;
- <span style="font-size:18px;">#pragma mark - 獲取全部數(shù)據(jù)
- - (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){
- //已經(jīng)打開的功能
- [openYES addObject:muDict];
- }else{
- //沒(méi)有打開的功能
- [openNO addObject:muDict];
- }
- }
-
- [self.dataArray addObject:openNO];
- [self.dataArray addObject:openYES];
-
- [self.tableview reloadData];
- }</span>
b.打開功能
打開某個(gè)功能就是下載對(duì)應(yīng)的framework,把下載下來(lái)的zip包進(jìn)行解壓一下然后獲取到framework,接著刪除zip包,把framework放在對(duì)于的目錄下;最后改變本地列表功能的狀態(tài);
- <span style="font-size:18px;">#pragma mark - 開啟某個(gè)功能 先下載數(shù)據(jù)
- - (void)SSZipArchiveDataBaseWithDict:(NSMutableDictionary *)dict{
- NSString *requestURL = dict[@"downurl"];
- if(requestURL==nil || requestURL.length==0){
- self.progresslabel.text = [NSString stringWithFormat:@"%@-沒(méi)有下載地址,不能開啟!",dict[@"name"]];
- UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:@"沒(méi)有下載地址,不能開啟" 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:@"%@下載進(jìn)度:%.2f",dict[@"name"],progress];
- }];
- [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
- NSLog(@"下載成功");
- NSString *destinationPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];
- //對(duì)下載下來(lái)的ZIP包進(jìn)行解壓
- 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];//解壓成功后更新本地功能列表狀態(tài)
- }else{
- NSLog(@"解壓失敗 --- 開啟失敗");
- }
-
- } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
- NSLog(@"下載失敗 --- 開啟失敗");
-
- }];
- [operation start];
- }
- </span>
更新本地?cái)?shù)據(jù)
- <span style="font-size:18px;">#pragma mark - 更新本地?cái)?shù)據(jù)
- - (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];//重新獲取數(shù)據(jù) 更新列表
- if(self.refreshData){
- self.refreshData();
- }
- }else{
- NSLog(@"c操作失敗");
- }
- }
- </span>
c.關(guān)閉功能
關(guān)閉某個(gè)功能,也就是刪除某個(gè)功能的framework,然后更改功能列表的狀態(tài);
- <span style="font-size:18px;">#pragma mark - 關(guān)閉某個(gè)功能
- - (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;
- //關(guān)閉某個(gè)功能 就是刪除本地的framework 然后修改本地功能狀態(tài)
- BOOL isScu = [fileMgr removeItemAtPath:savedPath error:&err];
- if(isScu){
- [dict setValue:@"0" forKey:@"isopen"];
- [self updataBaseWithDict:dict];
- }else{
- NSLog(@"關(guān)閉失敗");
- }
- }else{
- NSLog(@"關(guān)閉失敗");
- }
- }
- </span>
d.效果圖
四.源代碼
在這里面有,兩個(gè)framework的源代碼,可項(xiàng)目的代碼;
注意,如果有多個(gè)功能的framework,記住多個(gè)framework的命名在同一個(gè)功能里面不能重復(fù),不然調(diào)取失敗;