天天看點

iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)

一.前言

iOS開發更新APP我覺得是比較坑的就是稽核時間比較長,稽核比較嚴,對于剛入行的小夥伴來說,雷區比較多;是以熱更新是比較重要的;

大家也許會發現我們常用的QQ現在下來也就一百多兆,但是用了幾個月後發現QQ在手機上占有一個多G的記憶體,特别是手機記憶體比較小的小夥伴,這是因為你在使用過程中,有一些功能是你下載下傳下來的;

iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)

二.建立Framework

1.建立項目

建立一個Cocoa Touch Framework項目,然後在這個項目裡面寫你的新的功能,比如我建立了一個控制器,在控制器裡面加載一張圖和一個label;

​​

  1. <span style="font-size:18px;">- (void)uiConfig{  
  2.     self.title = @"這是功能2";  
  3.     UIImageView *imageView = [[UIImageView alloc]init];  
  4.     imageView.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight);  
  5.     NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img4.duitang.com/uploads/item/201405/31/20140531174207_hH5u4.thumb.700_0.jpeg"]];  
  6.     imageView.image = [UIImage imageWithData:data];  
  7.     [self.view addSubview:imageView];  
  8.     UILabel *label = [[UILabel alloc]init];  
  9.     label.backgroundColor = [UIColor clearColor];  
  10.     label.frame = CGRectMake(0, (ScreenHeight - 100)/2, ScreenWidth, 100);  
  11.     label.numberOfLines = 0;  
  12.     label.text = @"這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2這是功能2";  
  13.     [self.view addSubview:label];  
  14. }</span>  

2.添加Aggregate

在TARGETS裡面建立一個Aggregate

iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)

3.添加Run Script腳本

iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)
iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)

4.腳本源碼​

  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,然後上傳伺服器;

三.建立項目

在項目中我們主要是下載下傳和讀取framework包;我們先要擷取功能清單,在此我在本地寫了一個功能清單,大家如果用得到可以将功能清單存放在伺服器上;

1.建立功能清單資料

我添加了四個功能子產品,存在NSUserDefaults裡面;其中功能1和功能2有下載下傳位址,其他的沒有;功能1是個NSObject,功能2直接是一個控制器;

isopen:1表示打開,0表示關閉;​

  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.     NSArray *functionList = [USER_DEFAULT objectForKey:@"functionList"];  
  8.     if(functionList==nil || functionList.count==0){  
  9.         NSArray *titleArr  = @[@"功能1",@"功能2",@"功能3",@"功能4"];  
  10.         NSArray *className = @[@"HotUpdateControl",@"ZFJViewController",@"",@""];  
  11.         NSArray *classType = @[@"NSObject",@"UIViewController",@"",@""];  
  12.         NSArray *downUrl   = @[  
  13.                                @"http://7xqdun.com1.z0.glb.clouddn.com/HotMudel.framework.zip",  
  14.                                @"http://7xqdun.com1.z0.glb.clouddn.com/FunctionZFJ1.framework.zip",  
  15.                                @"",  
  16.                                @""];  
  17.         NSMutableArray *functionArr = [[NSMutableArray alloc]init];  
  18.         for (int i = 0; i<titleArr.count; i++) {  
  19.             NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];  
  20.             [dict setObject:titleArr[i] forKey:@"name"];  
  21.             [dict setObject:className[i] forKey:@"classname"];  
  22.             [dict setObject:classType[i] forKey:@"classtype"];  
  23.             [dict setObject:@(i) forKey:@"mid"];  
  24.             [dict setObject:@"0" forKey:@"isopen"];//0 未開啟  1開啟了  
  25.             [dict setObject:downUrl[i] forKey:@"downurl"];  
  26.             [functionArr addObject:dict];  
  27.         }  
  28.         [USER_DEFAULT setObject:functionArr forKey:@"functionList"];  
  29.         [USER_DEFAULT synchronize];  
  30.     }  
  31.     DynamicViewController *dvc = [[DynamicViewController alloc]init];  
  32.     UINavigationController *nvc = [[UINavigationController alloc]initWithRootViewController:dvc];  
  33.     self.window.rootViewController = nvc;  
  34.     return YES;  
  35. }  

2.展示功能清單

在功能清單主要用于展示所有打開過的功能,也就是isopen為1的所有功能;

a.擷取本地所有打開的資料,然後在tableview上顯示

  1. - (void)getDataBase{  
  2.     [self.dataArray removeAllObjects];  
  3.     for (NSDictionary *dict in functionList) {  
  4.         NSInteger isopen = [dict[@"isopen"] integerValue];  
  5.         if(isopen==1){  
  6.             [self.dataArray addObject:dict];  
  7.     [self.tableview reloadData];  

b.點選對于的tableviewcell 的時候跳轉對應的framework讀取出來的方法

注意,我将不同的framework存放在不同的檔案夾下,以mid作為區分;​

  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.     NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];  
  12.     if (!bundle || ![bundle load]) {  
  13.         NSLog(@"bundle加載出錯");  
  14.     NSString *className = dict[@"classname"];  
  15.     NSString *classtype = dict[@"classtype"];  
  16.     Class loadClass = [bundle classNamed:className];  
  17.     if (!loadClass) {  
  18.         NSLog(@"擷取失敗");  
  19.     if([classtype isEqualToString:@"NSObject"]){  
  20.         NSObject *bundleObj = [loadClass new];  
  21.         NSArray *arrVc = [bundleObj performSelector:@selector(getVcs)];  
  22.         TabController *tvc = [[TabController alloc]initwithVcArray:arrVc];  
  23.         [self.navigationController pushViewController:tvc animated:YES];  
  24.     }else if([classtype isEqualToString:@"UIViewController"]){  
  25.         UIViewController *uvc = (UIViewController *)[loadClass new];  
  26.         [self.navigationController pushViewController:uvc animated:YES];  
c.效果圖      
iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)

3.更多功能

在這裡我們可以打開或者關閉某個功能;

a.擷取是以功能,包括打開或者關閉狀态的;然後在tableview上顯示​

  1. <span style="font-size:18px;">#pragma mark - 擷取全部資料  
  2.     NSMutableArray *openYES = [[NSMutableArray alloc]init];  
  3.     NSMutableArray *openNO = [[NSMutableArray alloc]init];  
  4.         NSMutableDictionary *muDict = [[NSMutableDictionary alloc]initWithDictionary:dict];  
  5.         NSInteger isopen = [muDict[@"isopen"] integerValue];  
  6.             //已經打開的功能  
  7.             [openYES addObject:muDict];  
  8.         }else{  
  9.             //沒有打開的功能  
  10.             [openNO addObject:muDict];  
  11.     [self.dataArray addObject:openNO];  
  12.     [self.dataArray addObject:openYES];  

b.打開功能

打開某個功能就是下載下傳對應的framework,把下載下傳下來的zip包進行解壓一下然後擷取到framework,接着删除zip包,把framework放在對于的目錄下;最後改變本地清單功能的狀态;​

  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.     //下載下傳儲存的路徑  
  11.     NSString *savedPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@.framework.zip",dict[@"mid"]]];  
  12.     AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];  
  13.     NSMutableURLRequest *request = [serializer requestWithMethod:@"POST" URLString:requestURL parameters:nil error:nil];  
  14.     AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];  
  15.     [operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:savedPath append:NO]];  
  16.     [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {  
  17.         float progress = (float)totalBytesRead / totalBytesExpectedToRead;  
  18.         self.progresslabel.text = [NSString stringWithFormat:@"%@下載下傳進度:%.2f",dict[@"name"],progress];  
  19.     }];  
  20.     [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {  
  21.         NSLog(@"下載下傳成功");  
  22.         NSString *destinationPath = [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/FunctionZFJ%@",dict[@"mid"]]];  
  23.         //對下載下傳下來的ZIP包進行解壓  
  24.         BOOL isScu = [SSZipArchive unzipFileAtPath:savedPath toDestination:destinationPath];  
  25.         if(isScu){  
  26.             NSLog(@"解壓成功");  
  27.             NSFileManager *fileMgr = [NSFileManager defaultManager];  
  28.             BOOL bRet = [fileMgr fileExistsAtPath:savedPath];  
  29.             if (bRet) {  
  30.                 [fileMgr removeItemAtPath:savedPath error:nil];//解壓成功後删除壓縮包  
  31.             }  
  32.             [dict setValue:@"1" forKey:@"isopen"];  
  33.             [self updataBaseWithDict:dict];//解壓成功後更新本地功能清單狀态  
  34.             NSLog(@"解壓失敗 --- 開啟失敗");  
  35.     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {  
  36.         NSLog(@"下載下傳失敗 --- 開啟失敗");  
  37.     [operation start];  

更新本地資料​

  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.     }else{  
  14.         NSLog(@"c操作失敗");  

c.關閉功能​

  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.             [dict setValue:@"0" forKey:@"isopen"];  
  11.             [self updataBaseWithDict:dict];  
  12.             NSLog(@"關閉失敗");  
  13.         NSLog(@"關閉失敗");  

d.效果圖

iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)

四.源代碼

在這裡面有,兩個framework的源代碼,可項目的代碼;

注意,如果有多個功能的framework,記住多個framework的命名在同一個功能裡面不能重複,不然調取失敗;

五.效果圖

iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)
iOS-OC-APP熱更新,動态更新(仿QQ打開或關閉某個功能)