【iOS】实现一键切换开发环境的功能

503 阅读4分钟

【iOS】实现一键切换开发环境的功能

前言

接手的项目中到处分散着各种环境变量,像网络请求的 host URL,web URL, 还有各种第三方的 APPID, KEY, 每一项都有测试环境、预发布环境和正式环境三套值。这些值都是写死在代码中的,每次切换环境都要全局搜索然后一个个地手动修改,非常麻烦。

而且因为这些是项目的全局变量,所以即便只改了一个,项目也几乎要重新编译才能运行,每次打不同环境的包都得做一遍重复无意义的工作,浪费时间,有这十几分钟摸 🐟 也行啊。所以想着搞一个一键切换环境的功能,请大家多提建议,多指正!

实现

1. 存储环境变量

在项目中新建一个 Environment.plist 文件,将各个环境下的环境变量、baseURL、第三方 APPID、KEY 等都保存在文件中,另外还需要一个 key 来保存当前环境的值。

其实也就是用 json 存储。当然用其他格式也行,plist 文件是因为它可以直接在 Xcode 中编辑,方便查看和修改。

注意,如果要在 app 运行时修改 plist 文件,需要将 plist 文件复制到可读写的目录下,比如 Document 目录。

比如下面的数据结构:

 {
      // 这个key保存当前环境
      "ENVIRONMENT": "TEST",
      "TEST": {
         "baseURL": "https://test.com",
         "appID": "123456",
         "appKey": "abcdefg"
      },
      "PROD": {
         "baseURL": "https://prod.com",
         "appID": "654321",
         "appKey": "gfedcba"
      },
      "PRE": {
         "baseURL": "https://pre.com",
         "appID": "123123",
         "appKey": "abcabc"
      }
 }

2. EnvironmentManager

新建 EnvironmentManager 类,用来管理环境变量,提供一些方法来获取和修改环境变量。

注意,要修改 plist 文件,需要将其复制到可读写的目录下,比如 Document 目录。

2.1 初始化

@interface EnvironmentManager ()

// 保存 Environment.plist 整个文件的字典
@property (nonatomic, strong) NSMutableDictionary *config;

// 保存当前环境的字典
@property (nonatomic, strong) NSDictionary *envDic;

// Environment.plist 复制到本地 document 目录的路径
@property (nonatomic, strong) NSString *cachedConfigPath;

@implementation EnvironmentManager

+ (instancetype)sharedManager {
    static EnvironmentManager *sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[self alloc] init];
    });
    return sharedManager;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        self.cachedConfigPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"Environment.plist"];

        // 检查缓存目录是否有配置文件 Environment.plist ,没有则从项目中复制一份
        if (![[NSFileManager defaultManager] fileExistsAtPath:self.cachedConfigPath]) {
            // 项目源文件中 Environment.plist 的路径
            NSString *configPath = [[NSBundle mainBundle] pathForResource:@"Environment" ofType:@"plist"];
            // 复制到文档目录中,以便后续修改
            [[NSFileManager defaultManager] copyItemAtPath:configPath toPath:self.cachedConfigPath error:nil];
        }

        self.config = [NSMutableDictionary dictionaryWithContentsOfFile:self.cachedConfigPath];
        NSString *env = self.config[@"ENVIRONMENT"];
        self.envDic = self.config[env];
    }
    return self;
}

@end

2.2 获取当前环境及各项配置的值

- (NSString *)currentEnvironment {
    return self.config[@"ENVIRONMENT"];
}
- (NSString *)baseURL {
    return self.config[@"baseURL"];
}
- (NSString *)appID {
    return self.config[@"appID"];
}
- (NSString *)appKey {
    return self.config[@"appKey"];
}

2.3 实现切换环境的方法

切换环境的方法,其实就是修改 plist 文件中的 ENVIRONMENT 这个 key 对应的值。

我这里因为项目中的各种缓存非常多,所以切换环境之后直接一刀切,清除所有项目的缓存,然后重启 app。

如果你的项目不需要清除缓存,可以不用重启 app,直接重新加载一下页面即可。

- (void)switchEnvironment:(NSString *)environment {
    // 修改 ENVIRONMENT 这个 key 对应的值
    self.config[@"ENVIRONMENT"] = environment;
    // 保存当前修改到缓存文件中
    [self.config writeToFile:self.cachedConfigPath atomically:YES];
    // 清除所有本地缓存
    [self clearCache];

    // 弹窗提示
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"切换完成,请手动重启"
                                                                   message:nil
                                                            preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        // 退出app
        exit(0);
    }]];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}

- (void)clearCache {
    // 删除 NSUserDefaults 中的所有数据
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *dictionary = [defaults dictionaryRepresentation];
    for (NSString *key in dictionary) {
        [defaults removeObjectForKey:key];
    }
    [defaults synchronize];

    // 删除应用程序的缓存目录
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    [[NSFileManager defaultManager] removeItemAtPath:cachePath error:nil];
    // 删除应用程序的文档目录,除了配置环境变量的 Environment.plist
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsPath error:nil];
        for (NSString *file in files) {
            if ([file isEqualToString:@"Environment.plist"]) {
                continue;
            }
            NSString *path = [documentsPath stringByAppendingPathComponent:file];
            [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
        }
    // 删除应用程序的临时目录
    NSString *tempPath = NSTemporaryDirectory();
    [[NSFileManager defaultManager] removeItemAtPath:tempPath error:nil];
}

2.4 显示切换环境的弹窗

如下,弹窗会显示 TEST, PRE, PROD 这三个环境,点击其中一个,就会切换到对应的环境。

- (void)showChooseEnvironmentAlert {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"切换环境"
                                                                   message:nil
                                                            preferredStyle:UIAlertControllerStyleAlert];

    // 可以不排序,直接遍历 self.config 的 key 即可
    // 但是如果你的项目中有很多环境,建议排序一下,要不弹出来的选项就是乱序的
    NSArray *sortedKeys = [self.config allKeysSorted];
    for (NSString *key in sortedKeys) {
        if ([key isEqualToString:@"ENVIRONMENT"]) {
            continue;
        }
        NSDictionary *envDic = self.config[key];
        [alert addAction:[UIAlertAction
                          actionWithTitle:key
                          style:UIAlertActionStyleDefault
                          handler:^(UIAlertAction * _Nonnull action) {
            // 点击之后,切换到对应的环境
            [self switchEnvironment:key];
        }]];
    }
    [alert addAction:[UIAlertAction
                      actionWithTitle:@"取消"
                      style:UIAlertActionStyleDefault
                      handler:^(UIAlertAction * _Nonnull action) {
    }]];

    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}

2.5 暴露外部需要用的方法

// EnvironmentManager.h
@interface EnvironmentManager : NSObject

+ (instancetype)sharedManager;

// 当前环境
- (NSString *)currentEnvironment;

// 显示切换环境的弹窗
- (void)showChooseEnvironmentAlert;

// 获取 baseURL
- (NSString *)baseURL;

// 获取 appID
- (NSString *)appID;

// 获取 appKey
- (NSString *)appKey;

@end

3. 替换原来定义的环境变量

将用到环境变量地方,替换成 EnvironmentManager 中定义的方法,如[[EnvironmentManager sharedManager] baseURL]。 也可以定义成宏:#define kBaseURL [[EnvironmentManager sharedManager] baseURL]

4. 使用

在你的项目中想实现该功能的地方,直接调用[[EnvironmentManager sharedManager] showChooseEnvironmentAlert]即可。 你也可以使用[[EnvironmentManager sharedManager] currentEnvironment]获取并显示当前环境。

有什么不对的地方或者有什么建议,欢迎指出!