版本更新提示搭建(一)逻辑交互

需求如下:

屏幕快照 2019-03-25 上午11.19.36.png

先说下使用场景和需求:
1)登录时版本更新,用户启动app登录或自动登录时,发送请求需要带本应用的当前版本号,服务返回的结果中包含版本更新信息,并且一般立刻弹出提示让用户选择,我们会分为两种,一种是强制更新(左图)一种是非强制更新。
2)在我的→设置→检查更新 模块中进行版本的更新。

思路:

版本更新一般分为:强制更新和非强制性更新。
强制更新的本质是由服务器来判断,需要拿当前版本号(服务器数据库中存储的应用版本号)和应用发送的版本号相比较。

具体实现如下:

我们项目是组件式开发,因此这部分功能的实现,我们放到了Launch模块组件中。

先创建一个服务单例,其中包含(开启第三方、登录成功之后数据初始化、Jpush、Bugly、WX、UM、IQ、RN、配置token相关信息、token刷新无效 则跳转登录)等相关服务。

本篇文章只针对检查版本更新做介绍。

在程序开始启动的时候,增加如下代码

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyWindow];
    [[SCLaunchService shareInstance]startSCServices:nil];
    [[SCLaunchService shareInstance] openThirdServices:launchOptions block:nil env:SC_Env_Type];
    [self showLaunchView];
    [[SCLaunchService shareInstance] checkAppVersion:nil];
    return YES;
}

其中:

 [[SCLaunchService shareInstance] checkAppVersion:nil];

这个功能就是程序一启动就会检查版本情况

#import <Foundation/Foundation.h>
#import "smartSDKHeader.h"
#import "SCEnvParameterConfig.h"

@interface SCLaunchService : NSObject


+ (instancetype)shareInstance;

/**
  开启服务(登录成功后数据初始化)
 */
- (void)startSCServices:(VOIDBLOCK)started;

/**
 开启三方服务
 @param started started description
 */
- (void)openThirdServices:(NSDictionary *)launchOptions block:(VOIDBLOCK)started env:(KEnvType)envType;
@interface SCLaunchService ()<JPUSHRegisterDelegate,JoyRouteProtocol>
@end
@implementation SCLaunchService
//用于登录页面完成之后相关服务的开启
-(void)routeParam:(NSDictionary *)param block:(JoyRouteBlock)block{
    if ([[param objectForKey:@"type"] isEqualToString:@"startSCServices"]) {
        [self startSCServices:^{
            block?block(param,nil):nil;
        }];
    }
}

//创建一个单例
static SCLaunchService *instance = nil;

+ (instancetype)shareInstance
{
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
        instance = [[SCLaunchService alloc] init] ;
    }) ;
    return instance ;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

- (instancetype)init{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super init];
    });
    return instance;
}

- (id)copyWithZone:(NSZone *)zone{
    return instance;
}

- (id)mutableCopyWithZone:(NSZone *)zone{
    return instance;
}

#pragma mark 开启服务
- (void)startSCServices:(VOIDBLOCK)started{
    [self configTokenInfo];
    started?started():nil;
}

#pragma mark 开启三方服务
- (void)openThirdServices:(NSDictionary *)launchOptions block:(VOIDBLOCK)started env:(KEnvType)envType{
    [[SCEnvParameterConfig shareInstance] config:envType];
    //    [LocalCfg config:ENVSTR];
    [[SCUser shareInstance]setCacheInfo];
    [self addJpushWith:launchOptions];
    [self startBuglyService];
    [self startWXService];
    [self startUMServices];
    [self startIQKeyBoard];
    [self startRNService];
    [[JoyRouter sharedInstance]configScheme:@"scbu"];
    started?started():nil;
}

#pragma mark 配置token相关信息
- (void)configTokenInfo{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshTokenInvalid) name:KTOKEN_REFRESH_FAILURED object:nil];
}

#pragma mark token刷新无效 则跳转登录
- (void)refreshTokenInvalid{
    [[SCBleService shareInstance] stopService];
    [[JoyRouter sharedInstance]routerModule:@"user" path:@"login" actionType:JoyRouteActionTypeSetNavRooter parameter:nil block:nil];
}

#pragma mark 极光推送
- (void)addJpushWith:(NSDictionary *)launchOptions{
#if __has_include("JPUSHService.h")
    JPUSHRegisterEntity * entity = [[JPUSHRegisterEntity alloc] init];
    UIUserNotificationSettings *settings = [UIUserNotificationSettings
                                            settingsForTypes:UIUserNotificationTypeBadge categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    //    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
    entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound;
    [JPUSHService registerForRemoteNotificationConfig:entity delegate:self];
    NSString *jpushKey = [SCEnvParameterConfig shareInstance].JPUSH_APPKEY;
    BOOL isEnvRelease = [SCEnvParameterConfig shareInstance].envType == KEnvTypeRelease;
    [JPUSHService setupWithOption:launchOptions appKey:jpushKey
                          channel:nil
                 apsForProduction:isEnvRelease
            advertisingIdentifier:nil];
    [JPUSHService registrationIDCompletionHandler:^(int resCode, NSString *registrationID) {
        NSUserDefaults *deaults = [NSUserDefaults standardUserDefaults];
        [deaults setObject:registrationID forKey:@"registrationID"];
        NSLog(@"resCode : %d,registrationID: %@",resCode,registrationID);
    }];
#else
#endif
}

#pragma mark bugly服务
- (void)startBuglyService{
    NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    [Bugly startWithAppId:[SCEnvParameterConfig shareInstance].BUGLY_APPID];
    BuglyConfig * config = [[BuglyConfig alloc] init];
    config.reportLogLevel = BuglyLogLevelWarn;
    config.version = version;
    [Bugly startWithAppId:[SCEnvParameterConfig shareInstance].BUGLY_APPID config:config];
}

#pragma mark 微信服务
- (void)startWXService{
#if __has_include("WXApi.h")
    [WXApi registerApp:[SCEnvParameterConfig shareInstance].WEIXIN_APPID];
#else
#endif
}

#pragma mark 开启友盟服务
- (void)startUMServices{
    NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
    [MobClick startWithConfigure:UMConfigInstance];
    [MobClick setAppVersion:version];
    NSDictionary *config = @{};
    [GLLogging setupWithConfiguration:config];
}

- (void)startIQKeyBoard{
    //默认为NO
    IQKeyboardManager *manager = [IQKeyboardManager sharedManager]; // 获取类库的单例变量
    manager.enable = YES; // 控制整个功能是否启用
    manager.shouldResignOnTouchOutside = YES;//控制点击背景是否收起键盘
    //    manager.toolbarManageBehaviour = IQAutoToolbarBySubviews;   // 有多个输入框时,可以通过点击Toolbar 上的“前一个”“后一个”按钮来实现移动到不同的输入框
    manager.enableAutoToolbar = NO; // 控制是否显示键盘上的工具条
    //    manager.shouldShowTextFieldPlaceholder = NO;  // 是否显示占位文字
    //    manager.placeholderFont = [UIFont systemFontOfSize:17]; // 设置占位文字的字体
    manager.keyboardDistanceFromTextField = 10.0f;  //// 输入框距离键盘的距离
}

#pragma mark jpush delegate
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler {
#if __has_include("JPUSHService.h")
    // Required
    NSDictionary * userInfo = notification.request.content.userInfo;
    NSDictionary *data = [NSDictionary dictionaryWithJsonString:userInfo[@"data"]];
    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        [JPUSHService handleRemoteNotification:userInfo];
    }
    if ([userInfo[@"msgType"] isEqualToString:@"BppPayCallbackMessage"]) {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"PayBcak" object:self userInfo:@{@"orderId":data[@"orderId"],@"status":data[@"status"],@"payType":data[@"payType"]}];
        return;
    }
    if (@available(iOS 10.0, *)) {
        if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]] && [userInfo[@"msgType"] isEqualToString:@"message_center_detail"]) {
            NSDictionary *aps = [userInfo objectForKey:@"aps"];
            NSString *title = nil;
            if ([aps isKindOfClass:NSDictionary.class]){
                title = [aps objectForKey:@"alert"];
            }
            /// iOS10处理远程推送
            [JPUSHService handleRemoteNotification:userInfo];
            /// 应用处于前台收到推送的时候转成本地通知 ===========================
            UILocalNotification *notification = [[UILocalNotification alloc] init];
            notification.alertBody =title?:@"告警通知";
            notification.userInfo = userInfo;
            [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
            return;
        }
    }
    completionHandler(UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以选择设置
#else
#endif
}

- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
    NSDictionary * userInfo = response.notification.request.content.userInfo;
    NSDictionary *data = [NSDictionary dictionaryWithJsonString:userInfo[@"data"]];
//相关页面跳转在这个地方实现...
    
    if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
#if __has_include("JPUSHService.h")
        [JPUSHService handleRemoteNotification:userInfo];
#else
#endif
    }
    completionHandler();  // 系统要求执行这个方法
}

@end

以上是使用应用启动前这个服务的大致内容。

为了避免这个页面业务太多,针对不同的服务,我们通过分类的形式进行扩展。
比如AppService(检查版本更新服务)

#import "SCLaunchService.h"

typedef void (^checkVersionBlock)(NSInteger isNewst); //0不是最新版本 1:是最新版本 3:接口异常
@interface SCLaunchService (AppService)

//版本更新
- (void)checkAppVersion:(checkVersionBlock)block;

@end
#import "SCLaunchService+AppService.h"
#import "SCBUApiRequest+SCAppVersion.h"
#import "SCVersionUpdateView.h"

@implementation SCLaunchService (AppService)

#pragma mark 检测版本
- (void)checkAppVersion:(checkVersionBlock)block{
    //网络请求调服务端接口 检测是否需要强更
    [SCBUApiRequest checkAppVersion:@{} Success:^(SCBUApiResponse *response) {
        NSDictionary *result = response.responseObject;
//获取本地版本号
        NSString *curVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
//获取服务端版本号
        NSString *serviceVersion = result[@"required_version"];
        int currVersionInt = [self convertVersionStrToInt:curVersion];
        int serviceVersionInt = [self convertVersionStrToInt:serviceVersion];
        if (currVersionInt<serviceVersionInt) {
//required_version 版本号 type更新类型(如果是force 那是就强更、非得话是暂不更新和立即更新)  result[@"update_content"] 更新说明
            SCVersionUpdateView *tipView = [[SCVersionUpdateView alloc] initWithVersion:result[@"required_version"] updateType:result[@"type"] updateCon:result[@"update_content"] updateUrl:result[@"update_url"]];
            [tipView show];
            block?block(0):nil;
        }else{
            block?block(1):nil;
        }
    } Failure:^(NSError *error) {
        block?block(2):nil;
    }];
}

//版本号转换成整形  把版本号转换成数值
- (int)convertVersionStrToInt:(NSString *)versionStr{
    __block int version = 0;
    [[versionStr componentsSeparatedByString:@"."] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (idx == 0) {
            version+= [obj intValue]*1000;
        }else if (idx==1){
            version+= [obj intValue]*100;
        }else if (idx==2){
            version+= [obj intValue];
        }
    }];
    return version;
}


@end

接口调用形式

@implementation SCBUApiRequest (SCAppVersion)
+ (void)checkAppVersion:(NSDictionary *)dic Success:(ApiSuccessBlock)success Failure:(ApiFailureBlock)failure{
    NSString *versionCheckUrl = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"sc_version_check"];
    if (versionCheckUrl) {
        NSString *url = [NSString stringWithFormat:@"%@%@",[SCEnvParameterConfig shareInstance].API_HOST_URL,versionCheckUrl];
        [SCBUApiRequest getJsonWithUrl:url param:nil Success:success failure:failure app:SCAppRequestTypeTSP];
    }

}

@end
屏幕快照 2019-03-25 下午3.33.49.png

弹出发现新版本View具体实现