2015-03-04 20:19:50 u011290399 阅读数 2127

该篇文章内容大致都是说明内容,无代码模块

在项目开发过程中,经常会使用到第三方的框架,最多的无非是网络请求框架

AFN、ASI、还有使用Swift写的Alamofire框架

如果将这些第三方框架的请求放置在Controller中,在更换第三方框架时,整个框架都将在报错。

所以在开发过程中,要对第三方框架进行二次封装,屏蔽Controller与这些第三方框架之间的耦合

开发人员进行二次封装后,不管是第三方框架升级,还是更换新得框架,只需要修改封装文件的代码,而无需修改Controller中得内容即可实现第三方框架的更换。

2018-12-11 17:12:13 qq_25639809 阅读数 1544

GitHub地址:https://github.com/Moya/Moya.git
Moya基于Alamofire进行封装,使用更加简单,维护更加方便。
1.使用CocoaPods导入Moya:

pod 'Moya', '~> 11.0.2'

2.创建一个对象实现TargetType协议的方法

import Foundation
import Moya

enum ScanServer {
    case qrLogin(appname: String, nonce: String, address: String)
    case qrLoginConfirm(appname: String, nonce: String, address: String, confirm: String)
}

extension ScanServer: TargetType {
    
    var headers: [String : String]? {
        return ["Content-type" : "application/json"]
    }
    
    var baseURL: URL {
        return URL(string: GlobalConfig.current.scanServerBase)!
    }
    
    var path: String {
        switch self {
        case .qrLogin(_):
            return "/qrlogin"
        case .qrLoginConfirm(_):
            return "/qrconfirm"
        }
    }
    
    var method: Moya.Method {
        switch self {
        case .qrLogin(_):
            return .get
        default:
            return .post
        }
    }
    
    var parameters: [String: Any]? {
        var paramsDict: [String : Any] = [:]
        switch self {
        case .qrLogin(let appname, let nonce, let address):
            paramsDict["appname"] = appname
            paramsDict["nonce"] = nonce
            paramsDict["address"] = address
        case .qrLoginConfirm(let appname, let nonce, let address, let confirm):
            paramsDict["appname"] = appname
            paramsDict["nonce"] = nonce
            paramsDict["address"] = address
            paramsDict["confirm"] = confirm
        }
        return paramsDict
    }
    
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
    
    var sampleData: Data {
        return "".data(using: .utf8)!
    }
    
    var task: Task {
        switch self {
        case .qrLogin(_):
            return .requestParameters(parameters: parameters ?? [:], encoding: URLEncoding.default)//get方式参数拼接成url
        default:
            return .requestParameters(parameters: parameters ?? [:], encoding: JSONEncoding.default)//post方式参数是json格式
        }
    }
    
}

3.创建一个对象实现MoyaProviderType协议的方法

import Moya
import Result
import SwiftyJSON

enum ProviderError: LocalizedError {
    case server
    case data
    case message(msg: String)
    
    var description: String {
        switch self {
        case .server:
            return "Failed to connect to server".localized
        case .data:
            return "Failed to get data".localized
        case .message(let msg):
            return msg
        }
    }
}

let networkPlugin = NetworkActivityPlugin { (change, _) in
    switch(change){
    case .ended:
        DispatchQueue.main.async {
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
        }
    case .began:
        DispatchQueue.main.async {
            UIApplication.shared.isNetworkActivityIndicatorVisible = true
        }
    }
}

let scanClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<ScanServer>.RequestResultClosure) in
    do {
        var request: URLRequest = try endpoint.urlRequest()
        request.timeoutInterval = 30
        done(.success(request))
    } catch  {
        print("\(error)")
    }
}

class ScanProvider {
    
    static let shared = ScanProvider()
    
    let provider = MoyaProvider<ScanServer>(requestClosure: scanClosure, plugins: [networkPlugin])
    
    private func failureAction(error: ProviderError) {
        if NetworkingManager.status == .none || NetworkingManager.status == .unknown {
            Toast.showMessage(message: NetworkingManager.status.description)
        } else {
            Toast.showMessage(message: error.description)
        }
    }
    
    func qrLogin(appname: String,
                 nonce: String,
                 address: String,
                 completion: @escaping (Result<Bool, ProviderError>) -> Void) {
        provider.request(.qrLogin(
            appname: appname,
            nonce: nonce,
            address: address)) { result in
                switch result {
                case .success(let responseData):
                    if let response = JSONResponseFormatter(responseData.data) {
                        print(response)
                        if let status = response["state"] as? Int {
                            completion(.success(status == 1 ? true : false))
                        }
                    }
                case .failure(_):
                    self.failureAction(error: .server)
                    completion(.failure(.server))
                }
        }
    }
    
    func qrLoginConfirm(appname: String,
                        nonce: String,
                        address: String,
                        confirm: String,
                        completion: @escaping (Result<Bool, ProviderError>) -> Void) {
        provider.request(.qrLoginConfirm(
            appname: appname,
            nonce: nonce,
            address: address,
            confirm: confirm)) { result in
                switch result {
                case .success(let responseData):
                    if let response = JSONResponseFormatter(responseData.data) {
                        print(response)
                        if let status = response["state"] as? Int {
                            completion(.success(status == 1 ? true : false))
                        }
                    }
                case .failure(_):
                    self.failureAction(error: .server)
                    completion(.failure(.server))
                }
        }
    }
}

4.发起请求

ScanProvider.shared.qrLogin(
    appname: result.qrStringAppName,
    nonce: result.qrStringNonce,
    address: result.address
) {[weak self] (res) in
    guard let `self` = self else { return }
    switch res {
    case .success(_):
        print("success")
    case .failure(_):
        print("failure")
    }
}
2018-01-25 10:42:40 u011146511 阅读数 2453

参考原码解析:https://www.cnblogs.com/machao/p/6856603.html

安装使用参考一:http://blog.csdn.net/codingfire/article/details/51940212

安装使用参考二:https://www.jianshu.com/p/42aa9b5b70a3

原码解读:https://www.cnblogs.com/machao/p/6640248.html?utm_source=tuicool&utm_medium=referral

官网:https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-methods

原码解析:https://www.jianshu.com/p/f39ad2a3c10b

使用参考:https://www.jianshu.com/p/f8c3adb056cf、https://www.jianshu.com/p/903b678d2d3f

参考使用:https://github.com/WangLiquan/EWAlamofireEncapsulation

https://www.jianshu.com/p/c3980b4c07c5

==============get、post请求的简单封装=========

/**
 网络请求中心 get post请求
 使用:
 let loginhttp:LYBHttpManager=LYBHttpManager()
 let paramas:[String:String]=["username":"133355" ,"password":"11111"]
 let url:String="\(BASEURL)"+"index.php?m=Api&c=User&a=login"
 loginhttp.requestWithJson(url: url, httpMethod: .get, params: paramas, success: { (res) in
 
 if  ((res["result"]) != nil) {//判断res中是否存在result这个键
 let resultDict=res["result"] as? NSDictionary
 let  loginresultModel=LYBLoginResultModel.deserialize(from: resultDict)//这是根据字典转模型,还可以直接字符串转模型
 LYBLoginVC.model=loginresultModel//静态变量存储,整个程序生命周期期间且所有类中都可以用
 print("\(String(describing: loginresultModel?.token))")
 }else{
 print("没有")
 }
 
 }) { (error) in
 //返回的错误
 let s = String.init(describing: error)// 吧返回的错误转换成字符串
 MBProgressHUD.showError(s)
 }
 }
 */

import UIKit

class LYBHttpManager: NSObject,URLSessionDelegate,URLSessionDataDelegate {
   //*******注意自定义的manager必须是强引用,否则请求结果都是code=-999 cancelled
    
    static  var manager: SessionManager?=nil
    /**
     SessionManager的配置和创建
     
     */
  func createManage(){
        let config:URLSessionConfiguration = URLSessionConfiguration.default
        //创建httpHeader
        var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
        //给httpheader添加内容
//        defaultHeaders["token"] = "token的内容"
//        defaultHeaders["Authorization"] = ""
        defaultHeaders.updateValue("application/json", forKey: "Accept")
        config.httpAdditionalHeaders = defaultHeaders
//     config.httpAdditionalHeaders=[:]//http请求头,或者直接这样写
        //配置证书验证
//        let serverTrustPolicies: [String: ServerTrustPolicy] = [
//            ///正式环境的证书配置,修改成自己项目的正式url
//            "www.baidu.com": .pinCertificates(
//                certificates: ServerTrustPolicy.certificates(),
//                validateCertificateChain: true,
//                validateHost: true
//            ),
//            //测试环境的证书配置,不验证证书,无脑通过
//            "192.168.1.213:8002": .disableEvaluation,
//            ]
        config.timeoutIntervalForRequest = 20

        config.httpMaximumConnectionsPerHost=5;//最大连接数

//       //根据config创建manager-----带验证域名
//         LYBHttpManager.manager = Alamofire.SessionManager(configuration: config,
//                              delegate: SessionDelegate(),
//                              serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))


        //根据config创建manager-----不验证域名
    LYBHttpManager.manager = Alamofire.SessionManager(configuration: config)
    }
   
  
//========================================
    /**
     核心的方法:get  post请求,返回的是json数据
     pamara1: 请求的地址
     parama2:.get  .post
     parama3:参数
     parama4:成功的回调
     parama5:失败的回调
     */
    public func requestWithJson(url: String,
                            httpMethod: HTTPMethod,
                            params: Dictionary<String,Any>?,
                            success: @escaping success,
                            failure: @escaping failure){
createManage()//配置创建sessionmanager
        if httpMethod == HTTPMethod.get{
            LYBHttpManager.manager!.request(url,
    method: .get,
        parameters: params,
        encoding: URLEncoding.default,
    headers: nil).responseJSON {
            (response) in
//        print("\(response)")
        /*这个的结果(前面有一层SUCCESS,可以用response.result.value取到服务器返回的数据)----SUCCESS: {
        msg = "\U767b\U9646\U6210\U529f";
        result =     {agent = 0;};
         status = 1;
            }*/
//         print("\(response.result)")//结果是SUCCESS或者
//        print("\(response.result.value)")//服务器返回的数据
  switch response.result{
    case .success(let data)://这里的data就是response的数据
     
        if let value = response.result.value as? Dictionary<String,Any>{//吧返回的数据转换成字典
        ///添加一些全部接口都有的一些状态判断
        if value["status"] as! Int == 1010 {
                        return
            }
        
      success(value as NSDictionary)
        }
    case .failure(let err)://这里的err就是网路链接失败返回的数据
        failure(err as AnyObject)
    
            }
        }
    }
    else{
    //post
            LYBHttpManager.manager!.request(url,method: .post,parameters: params!,
    encoding: JSONEncoding.default,
            headers: nil).responseJSON { (response) in
                
                 success(response as AnyObject)
        switch response.result{
            case .success:
            ///添加一些全部接口都有的一些状态判断
            if let value = response.result.value as? Dictionary<String,Any> {
            if value["status"] as! Int == 1010 {
                success(value as AnyObject)
                    return
                        }
        success(value as NSDictionary)
                                    }
        case .failure(let err):
                failure(err as AnyObject)
            break
            }
            }
        }
    }
    
}

 

===========

 

Alamofire进行Https网络请求自签名证书:https://www.jianshu.com/p/1005ce8fec8e

 let url="\(BASEURL)"+LUNBOURL

    let image:UIImage=UIImage.init(named:"bannerhomeOne")!

    //获取数据

    func normalGetData()  {

 

 //get--01--response

 

    Alamofire.request(url).response { (returnResult)in

    if let data = returnResult.data,let utf8Text =String(data: data, encoding: .utf8) {

    print(" utf8Text =\(utf8Text)")

    }

    }

      

//    get--02---responseData

        Alamofire.request(url).responseData { (returnResult)in

            debugPrint(" returnResult =\(returnResult)")

            if let data = returnResult.data,let utf8Text =String(data: data, encoding: .utf8) {

                print(" utf8Text =\(utf8Text)")

            }

        }

        

        

  //get--03--responseString

        Alamofire.request(url).responseString { (returnResult)in

            debugPrint(" Sucess =\(returnResult.result.isSuccess)")

            print(" returnResult =\(returnResult)")

        }

        

        

   //get --04--responseJSON

        Alamofire.request(url).responseJSON { (returnResult)in

            debugPrint("responseJSON -->\(returnResult)")

            if let json = returnResult.result.value {//这个转换的json数据比较正规

                print("responseJSON -->\(json)")

                /*  返回请求地址、数据、和状态结果等信息

                 print(" responseJSON() --> \(returnResult.request!)")

                 print("responseJSON() --> \(returnResult.data!)")

                 print(" responseJSON() --> \(returnResult.result)")

                 */

            }

        }

        

        

//        get--05---queue

//         补充:1.参数:queue:请求队列 --> 就是默认在主线程中执行~我们可以自定义调度队列。

//         官方解释:Response handlers by default are executed on the main dispatch queue. However, a custom dispatch queue can be provided instead.

       

        let customQueue = DispatchQueue.global(qos: .utility)

        Alamofire.request(url).responseJSON(queue: customQueue) { (returnResult)in

            print("请求队列 -->\(returnResult)")

        }

    

    

   //get---06带请求头设置获取数据

            

                let headers: HTTPHeaders = [

                    "Accept":"application/json"//设置客户端以什么格式解析服务器返回的数据

                ]

                Alamofire.request(url, headers: headers).responseJSON { (returnResult)in

                    print("获取数据并设置请求头 --> returnResult =\(returnResult)")

                }

            }

   

    

    

  //GET请求---无参数

    func getData()  {

        Alamofire.request(url, method: .get).responseJSON { (returnResult)in

            print("GET 请求无参数 --> returnResult =\(returnResult)")

        }

    }

  

  

 //POST请求----无参数

    func postData(){

        Alamofire.request(url, method: .post).responseJSON { (returnResult)in

            print("POST 请求无参数 --> returnResult =\(returnResult)")

        }

    }

    

 

 //POST请求有参数

    func postDataWithParams() {

        let param = [String:String]()

        Alamofire.request(url, method: .post, parameters: param).responseJSON { (returnResult)in

            print(" POST 请求有参数参数 --> returnResult =\(returnResult)")

        }

    }

    

 

    

 //   URLRequest的方式请求

    func URLRequestWidth() {

 

        let urls = URL(string: url)

        var urlRequest = URLRequest(url: urls!)

        urlRequest.httpMethod = "POST"

                let param = ["type":"","key":""]

                do {

                    urlRequest.httpBody = try JSONSerialization.data(withJSONObject: param, options: [])

                } catch {

                    print(error)

                }

 

        urlRequest.setValue("application/json", forHTTPHeaderField:"Content-Type")

        Alamofire.request(urlRequest).responseData { (returnResult)in

            debugPrint(returnResult)

            if let data = returnResult.data,let utf8Text = String(data: data, encoding: .utf8) {

                print(" URLRequest请求方式 --> utf8Text =\(utf8Text)")

            }

        }

    }

    

    

//下载不带进度

    func download() {

        // 1.下载图片

        Alamofire.download(url).responseData { (returnResult)in

            if let data = returnResult.result.value {

                let image = UIImage(data: data)

                print("image =\(image!)")

            } else {

                print("下载失败")

            }

        }

 

    }

    

 //下载带进度

    func downoadWithProgress() {

 

    Alamofire.download(url).downloadProgress { (progress)in

    print("下载进度 =\(progress.fractionCompleted)")

    }.responseData { (returnResult) in

    if let data = returnResult.result.value {

    let image = UIImage(data: data)

    print("image =\(image!)")

    } else {

    print("下载失败")

    }

    }

    }

    

 //uploadData----上传data数据

    func uploadData()  {

 

        let imageData =UIImagePNGRepresentation(image)!

        Alamofire.upload(imageData, to: url).responseJSON { response in

            debugPrint(response)

        }

    }

 

 //uploadFile----上传文件

    func uploadFile()  {

        let fileURL = Bundle.main.url(forResource:"video", withExtension:"mov")

 

        Alamofire.upload(fileURL!, to: url).responseJSON { response in

            debugPrint(response)

        }

    }

 

 //uploadMultiFormData----上传多个表单数据

    func uploadMultiFormData()  {

        Alamofire.upload(

            multipartFormData: { multipartFormData in//添加多个表单数据是吧本地url传上去

//                multipartFormData.append(unicornImageURL, withName: "unicorn")

//                multipartFormData.append(rainbowImageURL, withName: "rainbow")

        },

            to: url,

            encodingCompletion: { encodingResult in

                switch encodingResult {

                case .success(let upload,_,_):

                    upload.responseJSON { responsein

                        debugPrint(response)

                    }

                case .failure(let encodingError):

                    print(encodingError)

                }

        }

        )

    }

    

    

 //上传带进度条

    func uploadWithProgress() {

        let fileURL = Bundle.main.url(forResource:"video", withExtension:"mov")

        

        Alamofire.upload(fileURL!, to: url)

            .uploadProgress { progress in // main queue by default

                print("Upload Progress:\(progress.fractionCompleted)")

            }

            .downloadProgress { progress in // main queue by default

                print("Download Progress:\(progress.fractionCompleted)")

            }

            .responseJSON { response in

                debugPrint(response)

        }

    }

 

上传下载参考:https://www.jianshu.com/p/f8c3adb056cf

下载参考:http://www.hangge.com/blog/cache/detail_972.html

=============创建sessionManager和设置Configuration

 

Session Manager

 

高级别的方便的方法,例如Alamofire.request,使用的是默认的Alamofire.SessionManager,并且这个SessionManager是用默认URLSessionConfiguration配置的。

 

例如,下面两个语句是等价的:

 

***Alamofire.request("https://httpbin.org/get")

 

***let sessionManager = Alamofire.SessionManager.default

sessionManager.request("https://xx.com")

我们可以自己创建后台会话和短暂会话的session manager,还可以自定义默认的会话配置来创建新的session manager,例如修改默认的header httpAdditionalHeaders和timeoutIntervalForRequest。

 

 方式一:

let configuration =URLSessionConfiguration.default

let sessionManager = Alamofire.SessionManager(configuration: configuration)

 

方式二:

let configuration = URLSessionConfiguration.background(withIdentifier:"com.example.app.background")

let sessionManager = Alamofire.SessionManager(configuration: configuration)

 

方式三:

let configuration =URLSessionConfiguration.ephemeral

let sessionManager = Alamofire.SessionManager(configuration: configuration)

 

更改configration:

var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders

defaultHeaders["DNT"] ="1 (Do Not Track Enabled)"

 

let configuration =URLSessionConfiguration.default

configuration.httpAdditionalHeaders =defaultHeaders

 

let sessionManager = Alamofire.SessionManager(configuration:configuration)

 

 

 

=============序列化=解析数据====================================

1.**********************IOS系统的序列化器,吧JSON字符串解析成JSON对象(字典)

  //1.这是一个JSON字符串

        let jsonStr ="[{\"name\": \"hangge\", \"age\": 100, \"phones\": [{\"name\": \"公司\",\"number\": \"123456\"}, {\"name\": \"家庭\",\"number\": \"001\"}]}, {\"name\": \"big boss\",\"age\": 1,\"phones\": [{ \"name\": \"公司\",\"number\": \"111111\"}]}]"

        

        //2.吧JSON字符串变成data数据

        if let jsonData = jsonStr.data(using:String.Encoding.utf8, allowLossyConversion:false) {

            

            do{

        //3.吧jsondata转换成JSON对象(即字典,或字典数组)

            if  let dictArr:[[String:Any]]=(tryJSONSerialization.jsonObject(with:jsonData,options:.allowFragments)as?[[String :Any]])  {

                    let phones = dictArr[0]["phones"]as?[[String:Any]]

                    if let name = phones![0]["name"]as?String{

                         print("\(name)")

                    }

                }

            }

            catch{

                

            }

        }

        

=================SwiftJson是Swift 界 JSON 解析之王(不是字典转模型)=============

 

下载地址:https://github.com/SwiftyJSON/SwiftyJSON

 

coacaopods导入SwiftJson框架;

在工程中导入第三方的头文件时,会报以下的错误:cannot load underlying module for ‘***’

这个问题出现的原因还是因为podfile文件,你需要在文件里再加上一句话  source 'https://github.com/CocoaPods/Specs.git'

如果还是这样,需要在LinkFrameworkAndLibrary中加入SwiftFramework.frame work

 

 

2.//**************swiftyjson吧JSON字符串解析成JSON对象(字典)******************不用担心数组越界,不用判断节点,拆包什么的

            do{

                let json = try JSON(data: jsonData, options:.allowFragments)

                if let number = json[0]["phones"][0]["number"].string {

                    // 找到电话号码

                    print("第一个联系人的第一个电话号码:",number)

                }else {

                    // 打印错误信息

                    print(json[0]["phones"][0]["number"])

                }

            }catch{

                

            }

=================================================================           

=======================Alamofire中sessionManager的使用=============

===========请求获取数据=====

LYBHomeHttp类中:

 

    static var sessionManager:SessionManager? = nil//这里的自定义sessionManager必须被强引用,否则不起作用

   

//============//static修饰的属性只能用类调用,

    func sessionM(){

        let  config=URLSessionConfiguration.default

        config.timeoutIntervalForRequest=30//超时时间

        config.httpMaximumConnectionsPerHost=2//最大连接数

        let defaultHeaderss = Alamofire.SessionManager.defaultHTTPHeaders

//        defaultHeaderss["DNT"] = "1 (Do Not Track Enabled)"

        config.httpAdditionalHeaders = defaultHeaderss

        LYBHomeHttp.sessionManager=Alamofire.SessionManager(configuration: config)

        LYBHomeHttp.sessionManager?.request(url, method: .get, parameters: nil, encoding:URLEncoding.default, headers: defaultHeaderss).responseJSON { (response) in

            print(response)//请求到的全部数据--控制台打印SUCCESS:{}

            

            print(response.result)//控制台打印出SUCCESS或者FAILURE

            

            switch response.result{

            case .success(let value):

                print(value)//控制台打印出SUCCESS对应的数据{请求到的后台数据}

            case .failure(let error):

                print(error)//控制台打印出FAILURE对应的错误信息{返回的错误信息}

            }

            

        }

    }

 

=============请求code=-999报错的解决:https://www.jianshu.com/p/e62d87b2e174======

解决:  static var sessionManager:SessionManager? = nil//这里的自定义sessionManager必须被强引用,否则不起作用

 

=======sessionManager上传=====sessionManager下载======

/**
 网络下载,暂停下载,继续接着上一次下载,上传数据
 */

import UIKit

class LYBDownAndUpLoadHttpManager: NSObject {
    static var manager:SessionManager? = nil//这里的sessionManager需要被强引用,否则不起作用
    var cancelledData:Data?//已下载的数据临时存储起来
    let serviceAddresss="http://";//上传到服务器资源地址--上传
    let downloadUrl="http://"//服务器的资源地址--下载
    var downrequest:Request?=nil//下载的请求
    var uprequest:Request? = nil//上传的请求
    
    func createManage(){
        
        let  config=URLSessionConfiguration.default
        config.timeoutIntervalForRequest=30//超时时间
        config.httpMaximumConnectionsPerHost=2//最大连接数
        var defaultHeaderss = Alamofire.SessionManager.defaultHTTPHeaders
        defaultHeaderss.updateValue("application/json", forKey: "Accept")
        config.httpAdditionalHeaders = defaultHeaderss
        
        //根据config创建manager-----不验证域名
        LYBDownAndUpLoadHttpManager.manager=SessionManager(configuration: config)
    }
   
        
   
        //指定下载路径(文件名不变)-这是一个闭包
        let destination: DownloadRequest.DownloadFileDestination = {
            _, response in
            
            let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            let fileURL = documentsURL.appendingPathComponent(response.suggestedFilename!)//response.suggestedFilename!是系统推荐的路径,也可以自己定义路径:"file/mypath.png"
            //两个参数表示如果有同名文件则会覆盖,如果路径中文件夹不存在则会自动创建
            return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
        }
    
    
    /**
     创建下载请求,
     parama1:url 服务器的下载地址
     parama2:destination 本地存储的地址
     */
   
    func createDownloadRequest(downloadUrl: String,success: @escaping success,
        failure: @escaping failure){
        createManage()
        downrequest = ( LYBDownAndUpLoadHttpManager.manager!.download(URLRequest.init(url:URL.init(string: downloadUrl)!), to: destination).responseJSON(completionHandler: { (response) in
            print(response.result)
            switch response.result {
            case .success( _):
                success(response as AnyObject)
            case .failure:
                self.cancelledData = response.resumeData //意外终止的话,把已下载的数据储存起来
                failure(response as AnyObject)
            }
        }))
        
    }
    
    func cancelDownload(){
        downrequest?.cancel()//暂停下载
    }
    
    
    
        /**
         继续下载
         parama1:cancelledDatay上次下载的数数据
         parama2:destination 本地存储的地址
         */
    func continueDownLoad(success: @escaping success,
                          failure: @escaping failure){
        createManage()
        LYBDownAndUpLoadHttpManager.manager!.download(resumingWith: self.cancelledData!, to: destination).responseJSON(completionHandler: { (response) in
            print(response.result)
            switch response.result {
            case .success( _):
                //self.image = UIImage(data: data)
                success(response as AnyObject)
            case .failure:
                self.cancelledData = response.resumeData //意外终止的话,把已下载的数据储存起来
                failure(response as AnyObject)
        }
        })
       
    }
    
//=================================================
    /**
      上传data数据到服务器
     */
    public func uploadWithData(url: String,
                                data: Data,
                                success: @escaping success,
                                failure: @escaping failure){
        createManage()
        let data:Data=UIImage.init(named: "bannerhomeOne")!.pngData()!
        uprequest  =  ( LYBDownAndUpLoadHttpManager.manager!.upload(data, to: serviceAddresss, method: .post, headers: nil).responseJSON(completionHandler: { (response) in
            switch response.result {
            case .success( _):
                
                print("文件下载完毕: \(response)")
            case .failure(let error):
                print(error)
            }
        }))
    }
    
    
    func cancelUpload(){
        uprequest?.cancel()//取消
    
    }
    
    
    /**
      吧本地路径的资源上传到服务器
     parama1:服务器地址
     parama2:本地路径
     */
    public func uploadWithLocalpath(url: String,
                              path: String,
                              success: @escaping success,
                              failure: @escaping failure){
        createManage()
        let   path=NSHomeDirectory() + "file"
        LYBDownAndUpLoadHttpManager.manager!.upload(URL.init(string: path)!, to: serviceAddresss).responseJSON(completionHandler: { (response) in
            switch response.result {
            case .success( _):
                
                print("文件上传完毕: \(response)")
            case .failure(let error):
                print(error)
            }
        })
    }
    
}

=================设置httpheder=========


//*********在iOS7中要中以下方法才有效

private func getRequest(method: Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil) -> NSMutableURLRequest {
        let request = NSMutableURLRequest(URL: NSURL(string: URLString.URLString)!)
        request.HTTPMethod = method.rawValue
        if parameters != nil {
            request.HTTPBody = NSJSONSerialization.dataWithJSONObject(parameters!, options: nil, error: nil)
        }
        request.setValue(API_UA, forHTTPHeaderField: "User-Agent")
        request.setValue(HEADER_ACCEPT, forHTTPHeaderField: "Accept")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        return request
    }




//************iOS8中的社会httpheader要用httpAdditionalHeaders

  //创建httpHeader
        var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
        //给httpheader添加内容
        defaultHeaders["token"] = "token的内容"
        defaultHeaders["Authorization"] = ""
        defaultHeaders.updateValue("application/json", forKey: "Accept")
        config.httpAdditionalHeaders = defaultHeaders //     config.httpAdditionalHeaders=[:]//http请求头,或者直接这样写

===========全局请求头和局部请求头==========

.*******局部请求头*********
客户端每发起一次HTTP请求,请求头信息是必不可少的。这也是同服务器交流的一种手段,在实际的开发中,也肯定会遇到需要自定义请求头的需求,在Alamofire中如何设置请求头:
let headers: HTTPHeaders = [
    "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
    "Accept": "application/json"
]

Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
    debugPrint(response)
}

在request(...)函数中,存在headers这么一个参数,我们只要传入提前写好的字典就行了。

********全局请求头*********
使用URLSessionConfiguration来配置全局的属性,
需要说明的是,Alamofire为每一个请求都设置了默认的请求头,我们简单介绍一下:
(1)Accept-Encoding 表示可接受的编码方式,值为:gzip;q=1.0, compress;q=0.5
(2)Accept-Language 表示可接受的语言,这个在后边的文章中会详细说明
(3)User-Agent 表示用户代理信息,比如:iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0

默认的请求头配置,我们通过SessionManager.default来创建SessionManager:
open static let `default`: SessionManager = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders

        return SessionManager(configuration: configuration)
    }()

 

2016-04-23 16:49:46 xingyun1992 阅读数 4219

做实际项目总是离不开这两步,大多数情况下都是采用第三方框架来辅助完成,swift也不例外,由于swift并不成熟,用swift语言写的第三方框架很不稳定(苹果更新太快了),所幸swift和oc可以混编,所以本次使用的框架都是oc里面常用的。网络请求使用AFNetworking,json解析使用MJExtension。
下面开始正文:

1. swift中使用oc框架

其原理很简单,需要一个.h头文件,里面导入我们需要使用的oc框架的头文件,把这个.h文件配置到项目设置中即可

1).我们采取一个不容易出错的方法来创建.h文件,在swift项目中新建oc类

这里写图片描述

注意语言选择oc语言,创建完后,xcode会有这么一个提示

这里写图片描述

询问是否创建一个桥街头,这波很关键,点创建
这里写图片描述

如图,创建完后在项目设置那里会有配置这个文件的,如果自己创建自己配置,很容易把路径写错~
接下来测试一下我们是否配置正确
在前面创建的test类中写一个方法,并且在swift中调用

这里写图片描述

这里写图片描述

在上面的桥街头文件中导入test.h (很关键!)
这里写图片描述

在swift中调用测试
这里写图片描述

一般会高亮并且能自动提示就没啥子问题了、

2. 引入第三方框架并调用

这里就很简单了,直接把代码拷贝进来,导入头文件就行了

这里写图片描述

并且在桥街头文件中导入框架包

#import "AFNetworking.h"
#import "MJExtension.h"

至于框架的使用方法,我就不再赘述,官方文档写的很清楚了、

 let manager = AFHTTPSessionManager()
        let url = "http://op.juhe.cn/onebox/weather/query?cityname=%e6%b7%b1%e5%9c%b3&key=eb08f814be6e473ec5ad9a6bde57e5e5&dtype=json"

        manager.GET(url, parameters: nil, success: { ( datatask :NSURLSessionDataTask, object :AnyObject?) in
            NSLog("请求成功")            
        },failure: { (datask, object) in
            NSLog("请求失败%@",object)
        })

还有一个要注意的,在高版本的xcode中,不允许使用http请求(只允许HTTPS),需要在项目的plist文件中加入这么一段

<key>NSAppTransportSecurity</key><dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/></dict>

测试一下,如果上述配置正确的话,是能够打印请求成功的~

3.请求成功后的数据解析
看回调方法上,object就是返回的数据,但是他是一个anyobject类型,就相当于oc里面的id类型(任意类型),实测返回的是一个字典类型,倘若你想打印出来,需要先强转成字典(oc里面可以直接打印)

let dict : NSDictionary = object as! NSDictionary
            NSLog("%@", dict)

框架已经帮我们转成字典类型了,现在要做的就是字典转模型了

4.复杂json解析&模型的写法

上面我们已经取到了字典了,其实已经可以利用系统的方法来字典转模型,但是对于比较复杂的json数据,系统的方法还是需要让我们写很多的代码~
这次用来测试的json数据是天气预报的,放出接口文档,上面有返回的json示例,如果你把上面的字典打印出来了,你也将看到大致结构

模型类

class WeatherBean: NSObject {

    var reason : String!
    var error_code : NSNumber!
    var result : Result!


    class Result: NSObject {
        var data:Data!
    }

    class Data: NSObject {
        var realtime:Realtime!
        var life:Life!
        var weather:Array<Weather2>!
        var pm25:Pm25!
        var date : String!
        var isForeign : NSNumber!

    }

    class Realtime: NSObject {
        var city_code:String!
        var city_name:String!
        var date:String!
        var time:String!
        var week:NSNumber!
        var moon:String!
        var dataUptime:NSNumber!
        var weather:Weather!
        var wind:Wind!

    }

    class Weather: NSObject {
        var temperature:String!
        var humidity:String!
        var info:String!
        var img:String!
    }

    class Wind: NSObject {
        var direct:String!
        var power:String!
        var offset:String!
        var windspeed:String!
    }

    class Life: NSObject {
        var data:String!
        var info:Info!
    }
    class Info: NSObject {
        var chuanyi:Array<String>!
        var ganmao:Array<String>!
        var kongtiao:Array<String>!
        var wuran:Array<String>!
        var xiche:Array<String>!
        var yundong:Array<String>!
        var ziwaixian:Array<String>!
    }

    class Weather2: NSObject {
        var date:String!
        var info:Info2!
        var week:String!
        var nongli:String!

        class Info2: NSObject {
            var day:Array<String>!
            var night:Array<String>!
        }

    }

    class Pm25: NSObject {

        var show_desc:String!
        var pm25:Pm252!
        var dateTime:String!
        var key:String!
        var cityName:String!

        class Pm252: NSObject {
            var pm25:String!
            var curPm:String!
            var pm10:String!
            var level:String!
            var quality:String!
            var des:String!

        }


    }


}

最深大概4-5层,算的上是很复杂的了~

Java的小伙伴一定都用过gson,其实mj解析和这个非常像,模型类的写法都一致

 manager.GET(url, parameters: nil, success: { ( datatask :NSURLSessionDataTask, object :AnyObject?) in

            let weat = WeatherBean.mj_objectWithKeyValues(object)
            NSLog("请求成功"+weat.reason+weat.result.data.pm25.cityName)


        },failure: { (datask, object) in
            NSLog("请求失败%@",object)
        })

如此便能解析到所有的数据~

打个断点,可以看到更多数据哦
这里写图片描述

完~

2018-09-12 16:45:51 ios8988 阅读数 558

前言

在现在的app,网络请求是一个很重要的部分,app中很多部分都有或多或少的网络请求,所以在一个项目重构时,我会选择网络请求框架作为我重构的起点。在这篇文章中我所提出的架构,并不是所谓的 最好 的网络请求架构,因为我只基于我这个app原有架构进行改善,更多的情况下我是以app为出发点,让这个网络架构能够在原app的环境下给我一个完美的结果,当然如果有更好的改进意见,我会很乐于尝试。

关于网络请求框架

一个好的网络请求框架对于一个团队来说是十分重要的。如果一个网络请求框架没有封装好,或者是在设计上存在问题,那么在开发上会造成许多问题,就拿这段代码作为例子:

[leaveAPI startWithCompletionBlockWith:^(BaseRequest *baseRequest, id responseObject) {
    //check the response object
    BOOL isSuccess = [leaveAPI validResponseObject:responseObject];
    if (isSuccess) {
        //do something...
    }   
}failure:^(BaseRequest *baseRequest) {
    //do something...
}];

 

上面这段代码存在着不少的问题,比如把请求数据的判断放到了每一个请求中、在leaveAPI的块方法中再次调用leaveAPI、块参数中的baseRequest并没有实质作用等等……针对这些问题我会一一进行修正。

不要让其他人做请求数据有效与否的判断

在上面的代码中,对resposeObject是否有效的判断被设计成了BaseRequest类中的一个方法,程序员需要在调用网络请求后,再调用该方法对responseObject进行判断,这样的设计存在很大的弊端。

在实际应用中,很多时候程序员在调用网络请求后往往会忘记调用该方法对返回结果进行判断,甚至忘记了存在这个方法,自行对responseObject进行判断。首先这造成了大规模的代码重复,另一方面,不同程序员自己编写的判断方法散落在各个请求中,假如app在日后更新过程中改变了这个判断标准,会给修改带来很大困难。

注意在块方法中的循环调用

上面的代码中,在leaveAPI的块方法中,再次调用了leaveAPI中的方法,这样导致了“retain cycle“,实际上正确的调用方法应该是:

[leaveAPI startWithCompletionBlockWith:^(LeaveAPI *api, id responseObject) {
    //check the response object
    BOOL isSuccess = [api validResponseObject:responseObject];
    if (isSuccess) {
        //do something...
    }
}];

 

为什么会出现这样的情况,首先主要是因为整个请求框架的注释不清晰,导致其他程序员对方法的理解存在偏差,进而天马行空,发挥自己的想象力来调用方法。另外由于各个API与BaseRequest的设计上存在问题,导致整个网络请求框架的混乱。

 

不要在单独的API中实现上传下载操作

在旧的网络请求框架中,BaseRequest一开始的设计中并没有针对上传和下载操作进行处理,而且整个BaseRequest的设计中并没有AOP,这个导致了在日后需要增加上传和下载功能的时候只能将他们写到单独的API中,这个导致了代码重复,代码的复用性降低,如:

//
//  FileAPI.m
//

...some methods...

#pragma mark - Upload & Download

-(void)uploadFile:(FileUploadCompleteBlock)uploadBlock errorBlock:(FileUploadFailBlock)errorBlock {
    NSString *url = self.url   
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    manager.requestSerializer = [AFHTTPRequestSerializer serializer];
    manager.operationQueue.maxConcurrentOperationCount = 5;
    manager.requestSerializer.timeoutInterval = 30;
    manager.responseSerializer.acceptableContentTypes =  [NSSet setWithObjects:@"application/json", @"text/html",@"text/json",@"text/javascript",@"text/plain",nil];

    [manager POST:url parameters:[self requestArgument] constructingBodyWithBlock:^(id formData) {

  // upload operation ...

    }success:^(AFHTTPRequestOperation *operation, id responseObject) {

     // do something ...
    }failure:^(AFHTTPRequestOperation *operation, NSError *error) {

  // do something ...
    }];
}

 

在FileAPI.m中,上传操作是这样实现的。写下这段代码的时候是使用AFNetworking 2.0,而现在使用的是AFNetworking 3.0,AFHTTPRequestOperationManager也变成了AFHTTPSessionManger,这个时候散落在各个API的上传方法修改起来就变的很麻烦。

BaseRequest中的设计缺陷

在上文中一直在指出各个API中的缺陷,而也提到很多地方是归咎于BaseReuqest的问题,现在就来看一下它里面的一些缺陷:

首先在整个BaseRequest中,它包括了地址的组装、网络环境的判断、请求的发送等等,基本网络请求的所有操作都是由这一个类来实现。这样就导致了整个类十分庞大,在需要添加新的请求类型如我上文提到的上传与下载时,会难以下手,这就导致了我上文提到的种种问题。

另一方面BaseRequest中没有针对返回数据的处理,这里的处理是指返回数据的缓存操作、数据过滤操作、请求数据为空的处理操作等等,如果这些问题都交给方法调用者来完成的话,会导致某一模块的代码量暴涨(在本app是VC),而且很多时候数据需要的只是一个默认的缓存操作、默认的过滤操作,这个时候重复性的代码会很多,倒不如把这些操作统一处理好,假如有特殊的API需要进行特殊的配置,再由该API对这些配置进行修改,而不需要把这些默认操作交由其他程序员来完成。

我是如何设计新的网络请求框架

上文提到了各种各样的不足,所以是时候针对这些不足进行改进了。

先看大局,再看细节。首先是整个架构的数据流向:

整个网络请求框架中最重要的是其中的NetworkManage,它主要是负责整个请求的处理。

设计中的一些关注重点

首先检测网络状态

当一个请求发起的时候,首先它会检测网络是否联通,假如没有联通的时候会直接弹出一个窗口提醒用户需要先连接网络,而不会进行下一步的请求。而在旧的网络请求框架中,很多时候把这段代码放到了vc,现在将它整合进来。ios开发审核交流群 869685378 欢迎各位大牛来分享交流 IOS,马甲包,低要求,内容开发没有限制,报酬丰厚,实力诚信 Q:782675105 

- (void)addRequest:(BaseRequest*)request {

    //TODO: 检查网络是否通畅
    if(![self checkNetworkConnection])
    {
        [self showNetworkAlertForRequest:request];
        return;
    }

 

[self checkNetworkConnection]:

- (BOOL)checkNetworkConnection
{
    struct sockaddr zeroAddress;
    bzero(&zeroAddress, sizeof(zeroAddress));
    zeroAddress.sa_len = sizeof(zeroAddress);
    zeroAddress.sa_family = AF_INET;

    SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
    SCNetworkReachabilityFlags flags;

    BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
    CFRelease(defaultRouteReachability);

    if (!didRetrieveFlags) {
        printf("Error. Count not recover network reachability flags\n");
        return NO;
    }

    BOOL isReachable = flags & kSCNetworkFlagsReachable;
    BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
    return (isReachable && !needsConnection) ? YES : NO;
}

 

活性组装请求地址

而在进行完网络联通的判断之后,就会对请求的地址进行组装。组装地址的方法并没有太大的变化,但是在旧的请求框架开发的时候,我注意到一个问题:在增加新需求增加新的接口的时候,往往需要连接到测试服务器上进行调试,这时候就需要将请求的地址改成测试服务器的地址。但这往往引发一些问题,因为测试服务器上可能没有正式服务器的一些数据,在测试时往往没有问题,但是转移到正式服务器上就出现了各种问题,所以我就想能不能改成程序员可以改变API连接的地址,而不改变全局的请求框架,让各个API在请求的时候判断自己是否需要连接到测试服务器。

- (NSString *)urlString{
    NSString *url = nil;
    //TODO: 使用副地址
    if ([self.child respondsToSelector:@selector(useViceUrl)] && [self.child useViceUrl]){
        baseUrl = self.config.viceBaseUrl;
    }
    //TODO: 使用主地址
    else{
        baseUrl = self.config.mainBaseUrl;
    }
}

 

让API能够独立配置

组装地址完毕之后,就开始根据API自身的设置来进行配置,在旧的请求框架中,API的是直接继承自BaseRequest这个类,导致了BaseRequest需要完成大量的工作,或是存有大量空方法,可读性与稳定性都很差,很多东西也没有办法让API自己进行独立设置。在新的框架中,我选择将API的设置通过一个叫做APIProtocol的协议来完成,API需要配置的内容可以通过实现该协议的方法来进行配置,否则就会直接使用默认配置

//TODO: 检查是否使用自定义超时时间
    if ([request respondsToSelector:@selector(requestTimeoutInterval)]) {
        self.manager.requestSerializer.timeoutInterval = [request requestTimeoutInterval];
    }
    else{
        self.manager.requestSerializer.timeoutInterval = 60.0;
    }

more methods ...

 

完善返回数据的基础判断

最后在进行完请求判断后,将会对responseObject的有效性进行判断。关于数据的判断我一开始是打算放在BaseRequest中的,因为一开始的想法是希望能够在BaseRequest中做一个默认的判断,假如API自身需要再度对responseObject进行进一步的判断时,可以通过协议方法来重新编写该API独立的判定方法。但这种方法最终被我弃用了,首先responseObject的基础判断在我看来是不应该放在BaseRequest中的,因为BaseRequest是作为一个请求的”中心”,不应该把数据处理的问题交给它处理。另一方面是因为我们需要设计的是基础判断,它和各个API独立的判断方式不是平行关系,而是层次关系,因为在设计的是每一个API都需要进行的判断,假如在整个app中有很多API需要进行独立判断,就意味着需要编写很多次基础判断逻辑,同时假如在日后需要修改这个基础判断内容,代码也散落在各个地方,这不是我们想要的结果。

所以在设计上我最终把这个判断方法放到了NetworkConfig中,新增了一个BaseFilter类,专门用于返回数据的判断,假如我的API需要增加独特的判断方法时,可以直接在请求方法中直接对responseObject进行进一步判断。

NetworkConfig.m:

//NetworkManage.m

if([self.networkConfig.baseFilter validResponseObject:responseObject])
{
request.responseObject = responseObject;
[self handleSuccessRequest:task];
}
else
{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadServerResponse userInfo:nil];
request.responseObject = responseObject;
[self handleFailureRequest:task error:error];
}

 

BaseFilter.m

@implementation BaseFilter

- (BOOL)validResponseObject:(id)responseObject
{
    //TODO: 检查是否返回了数据且数据是否正确
    if (!responseObject && ![responseObject isKindOfClass:[NSDictionary class]] && ![responseObject[@"success"] boolValue]) {
        return NO;
    }
    else
        return YES;
}

@end

 

结语

我相信在软件设计中并不存在最好或者是最正确的架构,因为这是一个很抽象的工作,但我相信我们应该可以设计出一个扩展性良好和简单明了的架构,能够让新加入的程序员快速上手,能够适应软件接下来的开发需要,那这大概是一个好的架构。

 杜玮

没有更多推荐了,返回首页