iOS:多环境切换

背景: 在开发过程中,通常有开发、预发布和生产三种环境,每个环境都对应着自己的baseURL、AppId等,为了方便测试同事在多个环境的切换,就写了下面的多环境切换的库。

一、实现
MZEnvironmentConfig
// MZEnvironmentConfig.h
#import 

#import "MZEnvironment.h"

NS_ASSUME_NONNULL_BEGIN

/// 对外提供的全局变量
extern NSString *const kMZEnvironmentChangedKey;

@interface MZEnvironmentConfig : NSObject

/// 所有的环境
@property (nonatomic, strong) NSArray *environments;

/// 建议只用于获取`所有的环境`, 其他操作用下面对应的方法
+ (instancetype)shareConfig;

/// 配置方法, 内部会创建 MZEnvironmentConfig 对象
+ (void)configWithHandler:(void(^)(MZEnvironmentConfig *config))handler;

/// 添加一个环境
- (void)addEnvironment:(MZEnvironment *)environment;

/// 更新当前的环境
+ (void)updateCurrentEnvironment:(MZEnvironment *)environment;

/// 获取当前环境的对象
+ (MZEnvironment *)currentEnvironment;

/// 获取当前的baseURL
+ (NSString *)currentBaseURL;

/// 获取当前H5 URL
+ (NSString *)currentH5URL;

/// 显示切换环境的sheet, 注意: RELEASE环境下不会弹出
+ (void)showEnvironmentsSheet;

@end

NS_ASSUME_NONNULL_END


// MZEnvironmentConfig.m
#import "MZEnvironmentConfig.h"

static NSString *const kMZEnvironmentIndexKey = @"kMZEnvironmentIndexKey";
NSString *const kMZEnvironmentChangedKey = @"kMZEnvironmentChangedKey";

@interface MZEnvironmentConfig ()

@property (nonatomic, strong) NSMutableArray *envs;
@property (nonatomic, assign) NSNumber *currentIndex;
@property (nonatomic, assign) NSInteger releaseIndex;

@end

@implementation MZEnvironmentConfig

+ (instancetype)shareConfig {
    static MZEnvironmentConfig *_shareConfig = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _shareConfig = [[MZEnvironmentConfig alloc] init];
        _shareConfig.envs = [NSMutableArray array];
    });
    return _shareConfig;
}

+ (void)configWithHandler:(void (^)(MZEnvironmentConfig * _Nonnull))handler {
    MZEnvironmentConfig *config = [MZEnvironmentConfig shareConfig];
    !handler ?: handler(config);
#if (!defined RELEASE)
    // 非生产环境才走
    [config __saveToLocal];
#endif
}

+ (void)updateCurrentEnvironment:(MZEnvironment *)environment {
    if (!environment) return;

    __block BOOL isSame = NO;
    __block NSUInteger index = -1;
    [[MZEnvironmentConfig shareConfig].envs enumerateObjectsUsingBlock:^(MZEnvironment * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj == environment) {
            index = idx;
            isSame = [MZEnvironmentConfig shareConfig].currentIndex.integerValue == idx;
            *stop = YES;
        }
    }];

    // 如果要更新的环境就是当前的环境, 则不用再往下执行
    if (isSame) return;

    // 如果在现有环境数组里没有找到, 则插入到现有数组
    if (index == -1) {
        [[MZEnvironmentConfig shareConfig].envs addObject:environment];
        index = [MZEnvironmentConfig shareConfig].envs.count - 1;
    }

    [MZEnvironmentConfig shareConfig].currentIndex = @(index);
    [[NSUserDefaults standardUserDefaults] setValue:@(index) forKey:kMZEnvironmentIndexKey];
    [[NSNotificationCenter defaultCenter] postNotificationName:kMZEnvironmentChangedKey object:@{@"model": environment}];
}

- (void)__saveToLocal {
    NSUserDefaults *dfs = [NSUserDefaults standardUserDefaults];
    NSNumber *index = [dfs valueForKey:kMZEnvironmentIndexKey];
    if (index) {
        self.currentIndex = index;
    } else {
        MZEnvironment *env = [self __findDefaultEnvironment];
        if (env) {
            [dfs setValue:self.currentIndex forKey:kMZEnvironmentIndexKey];
        } else {// 如果没找到, 就用生产环境的数据
            self.currentIndex = @(self.releaseIndex);
            env = self.envs[self.releaseIndex];
        }
        [[NSNotificationCenter defaultCenter] postNotificationName:kMZEnvironmentChangedKey object:@{@"model": env}];
    }
}

- (MZEnvironment *)__findDefaultEnvironment {
    __block MZEnvironment *env = nil;
    [self.envs enumerateObjectsUsingBlock:^(MZEnvironment * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.isDefaultEnvironment) {
            env = obj;
            self.currentIndex = @(idx);
            *stop = YES;
        }
        // 用于记录生产环境的下标
        if (obj.isRelease) {
            self.releaseIndex = idx;
        }
    }];
    return env;;
}

/// 添加一个环境
- (void)addEnvironment:(MZEnvironment *)environment {
    if (environment) {
        [self.envs addObject:environment];
    }
}

- (NSArray *)environments {
    return [MZEnvironmentConfig shareConfig].envs.copy;
}

/// 获取当前环境的对象
+ (MZEnvironment *)currentEnvironment {
#if (!defined RELEASE)
    NSInteger currentIndex = [MZEnvironmentConfig shareConfig].currentIndex.integerValue;
    NSInteger count = [MZEnvironmentConfig shareConfig].envs.count;
    // 如果下标小于数组个数, 那么取值是允许的
    if (currentIndex < count) {
        return [MZEnvironmentConfig shareConfig].envs[currentIndex];
    }
#endif
    return [MZEnvironmentConfig shareConfig].envs.firstObject;
}

/// 获取当前的baseURL
+ (NSString *)currentBaseURL {
    return [self currentEnvironment].baseURL;
}

/// 获取当前H5 URL
+ (NSString *)currentH5URL {
    return [self currentEnvironment].h5URL;
}

/// 显示切换环境的sheet
+ (void)showEnvironmentsSheet {
#if (!defined RELEASE)
    UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:[@"当前环境: " stringByAppendingString:[self currentEnvironment].name] message:nil preferredStyle:UIAlertControllerStyleActionSheet];
    for (MZEnvironment *ev in [MZEnvironmentConfig shareConfig].environments) {
        UIAlertAction *action = [UIAlertAction actionWithTitle:ev.name style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            [self __showAlertControllerWithEnv:ev];
        }];
        [alertVC addAction:action];
    }
    UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
    [alertVC addAction:cancel];
    [[self currentViewController] presentViewController:alertVC animated:YES completion:nil];
#endif
}

+ (void)__showAlertControllerWithEnv:(MZEnvironment *)env {
    UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:[@"把环境切换到: " stringByAppendingString:env.name] message:nil preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
    [alertVC addAction:cancel];
    UIAlertAction *sure = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
        [MZEnvironmentConfig updateCurrentEnvironment:env];
    }];
    [alertVC addAction:sure];
    [[self currentViewController] presentViewController:alertVC animated:YES completion:nil];
}

+ (UIViewController*)currentViewController {
    UIWindow *window;
    if (@available(iOS 13, *)) {
        window = [UIApplication sharedApplication].windows.lastObject;
    } else {
        window = [UIApplication sharedApplication].keyWindow;
    }
    UIViewController* vc = window.rootViewController;
    while (1) {
        if ([vc isKindOfClass:[UITabBarController class]]) {
            vc = ((UITabBarController*) vc).selectedViewController;
        }
        if ([vc isKindOfClass:[UINavigationController class]]) {
            vc = ((UINavigationController*) vc).visibleViewController;
        }
        if (vc.presentedViewController) {
            vc = vc.presentedViewController;
        } else {
            break;
        }
    }
    return vc;
}

@end
MZEnvironment
// MZEnvironment.h
#import 

NS_ASSUME_NONNULL_BEGIN

@interface MZEnvironment : NSObject

/// 环境名
@property (nonatomic, copy) NSString *name;
/// 通用baseURL, 个别不一致的,  可以在其网络请求里单独配置URL
@property (nonatomic, copy) NSString *baseURL;
/// H5 URL, 为了兼顾 H5URL 与 baseURL 不一致的情况
@property (nonatomic, copy) NSString *h5URL;
/// 是否为默认的环境, 该值只是为了筛选测试环境下默认用的环境而已, 建议将 `测试` 环境是为YES
@property (nonatomic, assign) BOOL isDefaultEnvironment;
/// 是否生产环境的配置, 建议将 `生产` 环境设为YES
@property (nonatomic, assign) BOOL isRelease;

- (instancetype)init NS_UNAVAILABLE;
/// 初始化方法, 添加一个环境
+ (instancetype)environmentWithHandler:(void (^)(MZEnvironment * _Nonnull))handler;

@end

NS_ASSUME_NONNULL_END


// MZEnvironment.m
#import "MZEnvironment.h"

@implementation MZEnvironment

+ (instancetype)environmentWithHandler:(void (^)(MZEnvironment * _Nonnull))handler {
    MZEnvironment *env = [[MZEnvironment alloc] init];
    !handler ?: handler(env);
    return env;
}

@end
二、使用
  • 1、在 AppDelegate 里注册:
- (void)configEnvs {
    [MZEnvironmentConfig configWithHandler:^(MZEnvironmentConfig * _Nonnull config) {
#ifdef DEBUG
        // 开发环境可切换的环境
        [config addEnvironment:[MZEnvironment environmentWithHandler:^(MZEnvironment * _Nonnull env) {
            env.name = @"开发";
            env.baseURL = @"https://www.baidu.com";
            env.h5URL = @"https://www.baidu.com/h5";
            env.isDefaultEnvironment = YES;
        }]];

        [config addEnvironment:[MZEnvironment environmentWithHandler:^(MZEnvironment * _Nonnull env) {
            env.name = @"预发布";
            env.baseURL = @"https://www.qq.com";
            env.h5URL = @"https://www.qq.com/h5";
        }]];

        [config addEnvironment:[MZEnvironment environmentWithHandler:^(MZEnvironment * _Nonnull env) {
            env.name = @"生产";
            env.baseURL = @"https://www.taobao.com";
            env.h5URL = @"https://www.taobao.com/h5";
            env.isRelease = YES;
        }]];
#else
        // 生产环境
        [config addEnvironment:[MZEnvironment environmentWithHandler:^(MZEnvironment * _Nonnull env) {
            env.name = @"生产";
            env.baseURL = @"https://www.taobao.com";
            env.h5URL = @"https://www.taobao.com/h5";
            env.isRelease = YES;
        }]];
#endif
    }];
}
  • 2、在控制器里进行获取和修改环境操作
- (IBAction)getCurrentEnv {
    NSLog(@"当前环境为: %@, baseURL = %@", [MZEnvironmentConfig currentEnvironment].name, [MZEnvironmentConfig currentEnvironment].baseURL);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [MZEnvironmentConfig showEnvironmentsSheet];
}

你可能感兴趣的:(iOS:多环境切换)