# iOS手册

# 简介

本文档主要介绍加加移动服务平台iOS SDK的接入和使用。

  • SDK文件结构功能
├── AppSpSDK.h                      // SDK头文件
├── AppSpService.swift              // SDK功能对外API接口文件              
├── AppSpNotice.swift               // 公告API文件
├── AppSpVersion.swift              // 版本更新API文件
├── AppSpRequest.swift              // 基础请求文件
├── AppSpDeviceInfo.swift           // 获取设备基础信息文件
├── AJAppSpReachability.swift       // 获取网络状态文件
├── AppSpUtils.swift                // 基本通用方法文件
└── AppSpRSACrypt.swift   			// RSA加密文件

  • 支持版本说明
  • iOS 9.0+
  • Xcode 11+
  • Swift 5.0+

# 版本更新记录

上线日期 版本号 更新内容
2020.12.16 0.0.3 添加 log debug开关、自定义设置host服务
2020.11.17 0.0.2 添加网络状态、数据相关优化
2020.10.16 0.0.1 iOS SDK第一版,包含版本更新、公告服务功能

# iOS SDK集成

  • 使用CocoaPods引入 AJSDKAppSp 先进入项目Podfile文件进行配置
platform :ios, '9.0'
use_frameworks!

target '<Your Target Name>' do
	# 示例:0.0.1,可以选择指定发布版本
    pod 'AJSDKAppSp', '~> 0.0.1'
end

  • 然后打开终端,进入Podfile所在目录路径
$ pod install

# 快速接入使用

# 初始化服务

AppKey的获取见 操作手册

import UIKit
//导入头文件
import AJSDKAppSp

//初始化SDK数据 (建议在项目启动以后 AppDelegate 中初始化)
func initAJSDKConfigure() {
    let appKey = "移动管理平台获取自己的AppKey"
    /**
        * AppSpService.shareService.initConfig 该接口SDK 0.0.3版本支持 增加如下功能
        * appkey: 移动服务平台创建应用获取
        * debug: 是否显示调试log 默认开启
        * host: 配置自己的host 服务地址 默认nil
        * AppSpService.shareService.initConfig(appkey: <String>, debug: <Bool>, <host: String?>)
        */
    AppSpService.shareService.initConfig(appkey: "your App Key", debug: true, "your server host")
}

# 版本更新服务

    /*
     获取 版本更新数据
     1、因为是异步,操作UI建议判断是否是主线程
     2、repInfo:服务器响应数据包(具体格式见下文)
     3、errorInfo:请求异常error信息(具体格式见下文)
     */
    func requestUpdateVersion() {
        AppSpService.shareService.checkVersionUpdate { (repInfo) in
            print(repInfo)
        } failure: { (errorInfo) in
            print(errorInfo)
        }
    }

请求返回数据结构如下

repInfo 数据详情

 {
 	"repCode": "0000", //业务返回码,0000表示成功
 	"repMsg": "成功",  //业务日志
 	"repData": {
 		"downloadUrl": "app下载地址",
 		"mustUpdate": false, //是否强制更新,true为强制更新
 		"showUpdate": true, //是否允许弹出更新
 		"updateLog": "更新日志"
 	}
 }

字段 类型 说明
repCode string 业务返回码,0000表示成功
repMsg string 业务日志、异常信息
repData Object 请求业务数据包、详情见下

repData 数据详情

字段 类型 说明
downloadUrl string app下载地址
mustUpdate boolean 是否强制更新,true为强制更新; false为非强制更新
showUpdate boolean 是否提示更新:允许弹出更新
updateLog string 更新日志

errorInfo 数据详情

repCode: 抛出异常Code
repMsg: 异常信息

字段 类型 说明
repCode string 抛出异常Code ; 1001: 请求异常;1202: appKey为空;1203: appKey校验失败;1207: 系统版本号不能为空;1208: 应用版本号不能为空
repMsg string 异常信息

# 公告服务

  /*
        获取 公告信息数据
     1、因为是异步,操作UI建议判断是否是主线程
     2、repInfo:服务器响应数据包(具体格式见下文)
     3、errorInfo:请求异常error信息(具体格式见下文)
     */
    func requestNoticeInfo() {
        AppSpService.shareService.getNoticeInfo { (repInfo) in
            print(repInfo)
        } failure: { (errorInfo) in
            print(errorInfo)
        }

    }

请求返回数据结构如下


repInfo 数据详情

 {
 	"repCode": "0000", //业务返回码,0000表示成功
 	"repMsg": "成功", //业务日志
 	"repData": [ // 返回数据为 Array
 		{
	 		"title": "公告标题",
	 		"details": "公告内容",
	 		"templateType": "dialog", //公告类型( 弹窗:dialog; 水平滚动:horizontal_scroll)
	 		"startTime": 1601186454000, //公告有效开始时间(毫秒级)
	 		"endTime": 1601359255000 //公告有效截止时间(毫秒级)
 		}
 	]
 }
字段 类型 说明
repCode string 业务返回码,0000表示成功
repMsg string 业务日志、异常信息
repData Object 请求业务数据包、详情见下

repData 数据详情

字段 类型 说明
title string 公告标题
details string 公告内容
templateType string 公告类型( 弹窗:dialog; 水平滚动:horizontal_scroll)
startTime timestamp 公告有效开始时间(毫秒级)
endTime timestamp 公告有效截止时间(毫秒级)

errorInfo数据详情

repCode: 抛出异常Code
repMsg: 异常信息

字段 类型 说明
repCode string 抛出异常Code ; 1001: 请求异常;1202: appKey为空;1203: appKey校验失败;1207: 系统版本号不能为空;1208: 应用版本号不能为空
repMsg string 异常信息

# iOS SDK解析

  • 对外提供初始化接口服务
    /**
    * initConfig 该接口SDK 0.0.3版本支持 增加如下功能;
    * appkey: 移动服务平台创建应用获取
    * debug: 是否显示调试log 默认开启
    * host: 配置自己的host 服务地址 默认nil
    */
   @objc public func initConfig(appkey: String,
                               debug: Bool = true,
                               _ host: String? = nil) {
       isDebug = debug
       if host != nil {
           appSpBaseURL = host!
       }
       self.setAppkey(appKey: appkey)
   }	
   
   //初始化使用服务 后续会关闭该方法,建议使用如下 func initConfig() 该方法进行初始化
   @objc public func setAppkey(appKey: String) {
       _appKey = appKey
       deviceInit()
   }
   
   //初始化设备信息
   private func deviceInit() {
       AppSpDeviceInfo.deviceInit()
   }
  • 对外提供功能
 	//获取版本更新接口
    @objc public func checkVersionUpdate(success: @escaping (_ response: [String : Any]) -> (),
                                   failure: @escaping ((_ errorInfo: [String: Any]) -> ())) {
        AppSpVersion.checkVersionUpdate(success: success, failure: failure)
    }
    
    //获取公告信息接口
    @objc public func getNoticeInfo(success: @escaping (_ response: [String : Any]) -> (),
                              failure: @escaping ((_ errorInfo: [String: Any]) -> ())) {
        AppSpNotice.getNotice(success: success, failure: failure)
    }
	

# 数据RSA加密

  • 见AppSpRSACrypt.swift
	// RSA加密
    /// - Parameters:
    ///   - text: 加密字符串
    ///   - publicKey: 秘钥
    /// - Returns: 返回加密后的结果
    open class func encrypt(_ text: String, _ publicKey: String) -> String? {
        guard let textData = text.data(using: String.Encoding.utf8) else { return nil }
        let encryptedData = encryptWithRSAPublicKey(textData, pubkeyBase64: publicKey, keychainTag: publicKey)
        if ( encryptedData == nil ) {
            appSpLog("Error while encrypting")
            return nil
        } else {
            let encryptedDataText = encryptedData!.base64EncodedString(options: NSData.Base64EncodingOptions())
            return encryptedDataText
        }
    }
    

# 基础信息获取

  • 见AppSpDeviceInfo.swift 文件
  • 初始化设备信息接口
//初始化设备信息接口
    class func deviceInit() {
        AppSpRequest.share.request(path: AppSpDeviceInitPath, success: { (repData) in
        }) { (errorData) in
        }
    }
    //获取接口请求的基础参数字典
    class func getDeviceInfo() -> [String: String] {
        return  [
           "brand": getBrandInfo(), //获取手机品牌信息
           "deviceId": getDeviceId(), //获取UUID
           "sdkVersion": AppSpSDKVersion, //SDK发布版本号
            "netWorkStatus": AppSpService.shareService.connectionStatus//网络状态,
           "osVersion": getOSVerison(),//获取系统版本
           "platform": getPlatform(), //获取系统平台
           "screenInfo": getScreenInfo(),//获取屏幕宽高
           "versionCode": getAppBuildCode(),//获取版本号
           "versionName": getAppVersion()//获取版本名
         ]
    }
  • 实现
	//获取版本名
    class func getAppVersion() -> String {
        let infoDictionary = Bundle.main.infoDictionary
        let majorVersion = infoDictionary?["CFBundleShortVersionString"] as! String//主程序版本号
        return majorVersion
    }
    //获取版本号
    class func getAppBuildCode() -> String {
        let infoDictionary = Bundle.main.infoDictionary
        let appBuild = infoDictionary?["CFBundleVersion"] as! String//主程序版本号
        return appBuild
    }
    //获取屏幕宽高
    class func getScreenInfo() -> String {
        let width = UIScreen.main.bounds.size.width
        let height = UIScreen.main.bounds.size.height
        return "\(height)*\(width)"
    }
    //获取系统版本
    class func getOSVerison() -> String {
        return UIDevice.current.systemVersion
    }
    //获取系统平台
    class func getPlatform() -> String {
        return UIDevice.current.systemName
    }
    //获取UUID
    class func getDeviceId() -> String {
        let identifierNumber = UIDevice.current.identifierForVendor
        return identifierNumber?.uuidString ?? ""
    }
    //获取手机品牌信息
    class func getBrandInfo() -> String {
        return UIDevice().iphoneTypeName
    }

# 版本更新信息接口

  • 见AppSpVersion.swift文件
class func checkVersionUpdate(success: @escaping (_ response: [String : Any]) -> (),
                                  failure: @escaping ((_ errorInfo: [String: Any]) -> ())) {
        AppSpRequest.share.request(path: AppSpAppVersionPath,
                                   success: success,
                                   failure: failure)
}

# 公告信息获取接口

  • 见AppSpNotice.swift文件

static func getNotice(success: @escaping (_ response: [String : Any]) -> (),
                         failure: @escaping ((_ errorInfo: [String: Any]) -> ())) {
        AppSpRequest.share.request(path: AppSpNoticePath,
                                   success: success,
                                   failure: failure)
}
    

# 基础网络实现

  • 见AppSpRequest.swift文件
  • 由于网络组建的容易与项目工程冲突使用,该SDK对系统URLSession进行封装使用,详情如下
 //初始化请求session
var _apiSession: URLSession!
override init() {
    super.init()
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = ["Accept": "application/json"]
    configuration.timeoutIntervalForRequest = requestTimeOut
    configuration.timeoutIntervalForResource = responseTimeOut
    _apiSession = URLSession(configuration: configuration)
}

//具体实现细节
func request(path: String,
                 options: [String: Any]? = nil,
                 success: @escaping (_ response: [String : Any]) -> (),
                 failure: @escaping ((_ errorInfo: [String: Any]) -> ())) {
        
    let reqUrlStr = AppSpService.shareService.appSpBaseURL + path
    
    guard let reqUrl = URL(string: reqUrlStr) else {
        failure(self.apiErrorInfo(code: CustomErrorCode, messge: "URL解析异常"))
        return
    }
    var request = URLRequest(url: reqUrl)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Accept")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    //获取appKey
    let appKey = AppSpService.shareService.getAppKey()
    //获取设备信息
    let deviceParams = AppSpDeviceInfo.getDeviceInfo()
    //拼装请求使用数据
    var paramData: [String: Any] = ["appKey": appKey]
    for (key, value) in deviceParams {
        paramData[key] = value
    }
    
    var params: [String: Any] = [
        "data": paramData,
        "sign": ""
    ]
    let oriEnStr = self.toEncryptString(data: paramData)
    if oriEnStr != nil {
       params["sign"]! = AppSpRSACrypt.encrypt(oriEnStr!, AJ_RSA_PUBLIC_KEY_TAG) ?? ""
    }
    appSpLog("========================================")
    appSpLog("reqUrl: \(reqUrl)")
    appSpLog("params: \(params)")
    
    request.httpBody = formateRequestBody(parmas: params)
        
    _apiSession.dataTask(with: request) { (data, response, error) in
        //请求返回
    }.resume()
}
    

  • 网络请求数据解析
_apiSession.dataTask(with: request) { (data, response, error) in
    if (error != nil) {
        if let err = error as NSError? {
            failure(self.apiErrorInfo(code: "\(err.code)", messge: err.localizedDescription))
        } else {
            failure(self.apiErrorInfo(code: CustomErrorCode, messge: "请求异常"))
        }
        return
    }
    
    guard let repData = data else {
        failure(self.apiErrorInfo(code: CustomErrorCode, messge: "服务器数据异常"))
        return
    }
    
    var dict:[String: Any]?
    do {
        dict = try JSONSerialization.jsonObject(with: repData,
                                                options: JSONSerialization.ReadingOptions.init(rawValue: 0)) as? Dictionary
    } catch {
        failure(self.apiErrorInfo(code: CustomErrorCode, messge: "数据解析异常"))
        return
    }
    
    if let repDict = dict {
        let repMsg = repDict["repMsg"] as? String
        if let repCode = repDict["repCode"] as? String {
            if repCode == "0000" {
                success(repDict)
            } else {
                failure(self.apiErrorInfo(code: repCode, messge: repMsg ?? "数据异常"))
            }
        }
        appSpLog("response: \(repDict)")
    } else {
        failure(self.apiErrorInfo(code: CustomErrorCode, messge: "数据解析异常"))
    }
}.resume()
        

# SDK Demo下载

若要参考具体集成流程,可下载我们提供的Demo,

iOS SDK Demo下载地址: https://gitee.com/anji-plus/aj-appsp/demo/ios (opens new window)

iOS SDK地址: https://gitee.com/anji-plus/aj-appsp/sdk/ios (opens new window)