# iOS手册 ## 简介 本文档主要介绍加加移动服务平台iOS SDK的接入和使用。 * SDK文件结构功能 ```swift ├── 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文件进行配置 ```swift platform :ios, '9.0' use_frameworks! target '' do # 示例:0.0.1,可以选择指定发布版本 pod 'AJSDKAppSp', '~> 0.0.1' end ``` * 然后打开终端,进入Podfile所在目录路径 ``` $ pod install ``` ## 快速接入使用 ### 初始化服务 AppKey的获取见 **操作手册** ```swift 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: , debug: , ) */ AppSpService.shareService.initConfig(appkey: "your App Key", debug: true, "your server host") } ``` ### 版本更新服务 ```swift /* 获取 版本更新数据 1、因为是异步,操作UI建议判断是否是主线程 2、repInfo:服务器响应数据包(具体格式见下文) 3、errorInfo:请求异常error信息(具体格式见下文) */ func requestUpdateVersion() { AppSpService.shareService.checkVersionUpdate { (repInfo) in print(repInfo) } failure: { (errorInfo) in print(errorInfo) } } ``` **请求返回数据结构如下** **repInfo** 数据详情 ```swift { "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 | 异常信息 | *** ### 公告服务 ```swift /* 获取 公告信息数据 1、因为是异步,操作UI建议判断是否是主线程 2、repInfo:服务器响应数据包(具体格式见下文) 3、errorInfo:请求异常error信息(具体格式见下文) */ func requestNoticeInfo() { AppSpService.shareService.getNoticeInfo { (repInfo) in print(repInfo) } failure: { (errorInfo) in print(errorInfo) } } ``` **请求返回数据结构如下** *** **repInfo** 数据详情 ```swift { "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解析 * 对外提供初始化接口服务 ```swift /** * 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() } ``` * 对外提供功能 ```swift //获取版本更新接口 @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 ```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 文件 * 初始化设备信息接口 ```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()//获取版本名 ] } ``` * 实现 ```swift //获取版本名 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文件 ```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文件 ```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进行封装使用,详情如下 ```swift //初始化请求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() } ``` * 网络请求数据解析 ```swift _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/appsp/demo/ios](https://gitee.com/anji-plus/appsp/demo/ios) iOS SDK地址: [https://gitee.com/anji-plus/appsp/sdk/ios](https://gitee.com/anji-plus/appsp/sdk/ios)