iOS 上传图片和下载网络数据封装Swift

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yaojinhai06/article/details/87930881

导语:

下载数据和上传图片是每个开发人员必备的技能,也是最基本的.网上有很多优秀的第三方工具,我们常用的有AFN,YTKNetwork,Alamofire等,这些都是封装很好的,虽然需要研究一下,但是确实很好用.

但是,我们用这些框架也有几个问题需要考虑一下,

  1. 这些功能都很强大,但是我们真正用到的却少之又少,有点得不偿失.
  2. 如果版本不合适,需要更换版本,成本也可能会高
  3. 当出现bug的时候,一般很难解决.
  4. 对于没有及时升级的第三方库来说,有很多新特性和优化性能不能及时优化.
  5. 如果第三方不在维护,那么就需要另外在找代替品,需要修改大量代码,成本会更高.
  6. 加入这些第三方库,会增大应用程序包,

这是从这几个方面来说的,虽然这写问题有很少,但是也要考量一下.

正题

说了这么多,那么我们自己封装一个下载和上传的工具,就可以解决上述的问题.那么我们就开始自己来封装吧.

注:自己封装的库,成本很低,有没有bug那要看你自己了,哈哈相信自己没问题.......

思路

首先需要一个管理下载的类,他只负责下载,并用bock回调进度,并且回调是否成功.另外需要一个类,负责管理所有的下载队列,如果开始下载就需要添加到队列里,当下载成功后从队列里移除,如果有多个下载,就需要再次取出下一个继续下载,直到全部下载完成.

我们还需要创建一个具体的请求下载的信息类,包含了请求的信息,以及队请求信息的封装,我们用的把下载和这个请求的类组合,这样避免了继承带来的冗杂的操作,使我们更加关注请求的信息,而不用去关注请求的具体实现过程.最后就是继承这个类,开始请求,以满足要求.

我们直接上代码说明:

  • 我们首先需要一个类RequestOperation继承Operation,如果对这个多线程不是很熟悉,这里有篇文章可以先看看.
enum HttpMethodType : String{
    case GET = "GET"
    case POST = "POST"
}

class RequestOperation: Operation ,URLSessionDataDelegate{
    var savlePath: String!
    var stringURL: String!
    
    var request: URLRequest!
    
    var statusCode = 0;
    
    var progress: proBlock!
    var finiTask: finishedTask!;
    var memeryProgress: Float = 0;
    static let queue = RequestManager.shareInstance;
    
    var startDown: startLoaing!
    
    private var session: URLSession!

    private var responseData = Data();
    
    var methodType: HttpMethodType = .GET;
    var parameter = [String:String]();
    
    
    var pathName = "";
    
    
    
    private var totalBytesExpectedToReceive: Int64 = 0;
    

    
    private var requestTask: URLSessionDataTask!
    
    fileprivate var _cancle = false;
    override var isCancelled: Bool {
        return _cancle;
    }
    
    fileprivate var _ready = true;
    override var isReady: Bool {

        return _ready;
    }
    
    fileprivate var _executing = false;
    override var isExecuting: Bool {
        return _executing;
    }
    
    fileprivate var _finished = false
    override var isFinished: Bool {
        return false;
    }
    
    var state = HttpState.ready;
    
    override func cancel() {
        super.cancel();
        _cancle = true;
        _finished = true;
        requestTask.cancel();

    }

    
    class func createOperation(request: URLRequest) -> RequestOperation {
        let operation = RequestOperation();
        operation.request = request;
        operation.stringURL = request.url?.absoluteString;
        return operation;
        
    }


    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        responseData.append(data);
        let totalBytesReceived = responseData.count;
        
        if self.progress != nil && !self.isCancelled{
            
            let exten = Float(Float(totalBytesReceived)/Float(totalBytesExpectedToReceive));
            DispatchQueue.main.async(execute: {
                self.memeryProgress = exten;
                self.progress(exten);
            })
            
        }
        
    }
    
 
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        if isCancelled {
            completionHandler(.cancel);
        }
       totalBytesExpectedToReceive = response.expectedContentLength
        completionHandler(.allow);
    }
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        
        printResult(request: task.originalRequest ?? request,data: responseData as Data);
        
        
        self._finished = true;
        self._executing = false;
        self.state = .finshed;
        
        statusCode = (task.response as? HTTPURLResponse)?.statusCode ?? -1;
        
        DispatchQueue.main.async(execute: {
            
            

            
            if self._cancle {
                self.finiTask(self.responseData, false);
            }else{
                self.finiTask(self.responseData,error == nil);
            }
            if error != nil {
                print("\n------------------\n\t http error message:\n\n\t \(error!)\n\n--------------\n");
            }
            if self.stringURL != nil {
                RequestOperation.queue.cancel(self.stringURL);
            }
        })
    }
    
    func printResult(request: URLRequest,data: Data) -> Void {
        
        let body = String.init(data: request.httpBody ?? Data(), encoding: String.Encoding.utf8) ?? "";
        
        
        if let dict = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary, let resultDict = dict {
            
            printObject("============================================================================================================================\n\nrquestURL: \n \(request) \n\nrequestHeader:\n\(request.allHTTPHeaderFields ?? [:])\n\nrequestMethod: \n \(request.httpMethod ?? "GET") \nrequestBody \n\(body)\n\n---------------------------------返回数据:\n\nresponse result: \n \(resultDict) \n\n============================================================================================================================\n\n");
        }else {
            printObject("============================================================================================================================\n\nrquestURL: \n \(request) \n\nrequestHeader:\n\(request.allHTTPHeaderFields ?? [:])\n\nrequestMethod: \n \(request.httpMethod ?? "GET") \nrequestBody \n\(body)\n\n---------------------------------返回数据:\n\nerror: \n \(String.init(data: data, encoding: String.Encoding.utf8)) \n\n============================================================================================================================\n\n");
        }
        
       
        
    }
   
    
    override func main() {

        
        session = URLSession(configuration: URLSessionConfiguration.ephemeral, delegate: self, delegateQueue: OperationQueue.current);
        
        
        requestTask = session.dataTask(with: request!);
        requestTask.resume();
        
        _executing = true;
        _ready = false;
        state = .execing;

        
        DispatchQueue.main.async(execute: {
        
            if self.startDown != nil {
                self.startDown();
            }
        
            UIApplication.shared.isNetworkActivityIndicatorVisible = true;
        })

   
    }
}

枚举HttpMethodType,是一个请求的方法,GET和POST,GET是请求数据的,POST是发送数据的.这个里主要的属性有,开始下载,下载进,下载完成,失败回调blok,并且覆盖了父类的一些方法.

  • 这是一个下载的工具类的封装,我们需要在创建一个类去管理这个下载工具类.
class RequestManager: OperationQueue {
    static let shareInstance = RequestManager();
    fileprivate var _operations = [RequestOperation]();
    override var operations: [Operation] {
        get {
            return _operations;
        }
    }
    
    fileprivate var dataOperations = [RequestOperation]();
    
    fileprivate override init() {
        super.init();
        self.maxConcurrentOperationCount = 3;
    }
    class func cancel(_ url: String) {
        shareInstance.cancel(url);
    }
    class func addOperationReqest(_ operation: RequestOperation){
        shareInstance.addOperationReqest(operation);
    }
    
    class func addJsonRequest(_ operation: RequestOperation){
        shareInstance.addJsonRequest(operation);
    }
    func addJsonRequest(_ operation: RequestOperation){
        objc_sync_enter(self);
        dataOperations.append(operation);
        operation.start();
        objc_sync_exit(self);
    }
    
 
    
    func cancel(_ url: String) -> Void {
        objc_sync_enter(self);
        var index = -1;
        for item in operations {
            index += 1;
            guard let temp = item as? RequestOperation else{
                continue;
            }
            if temp.stringURL == url {
                temp.cancel();
                temp.memeryProgress = 0;
                break;
            }
        }
        startRun(index);
        
        if index == -1 {
            for item in dataOperations {
                index += 1;
                if item.stringURL == url {
                    dataOperations.remove(at: index);
                    break;
                }
            }
        }
        
        RequestManager.resetIndicatorVisible();
        objc_sync_exit(self);

    }
    
    fileprivate func startRun(_ atIndex: Int){
        objc_sync_enter(self);
        if atIndex > -1 && atIndex < _operations.count {
            _operations.remove(at: atIndex);
        }
        
        if _operations.count >= self.maxConcurrentOperationCount {
            
            var index = 0;
            
            for item in _operations {
                if item.isExecuting && !item.isFinished {
                    index += 1;
                }
            }
            if index < self.maxConcurrentOperationCount {
                for item in _operations {
                    if !item.isExecuting && item.isReady && !item.isFinished {
                        if index > self.maxConcurrentOperationCount {
                            break;
                        }
                        item.start();
                        item.state = .execing;
                        index += 1;
                    }
                }
            }
        }
        objc_sync_exit(self);
    }
    
    class func resetIndicatorVisible(){
        DispatchQueue.main.async { 
            if shareInstance.operations.count == 0 {
                UIApplication.shared.isNetworkActivityIndicatorVisible = false;
            }
        }
    }
    
    func addOperationReqest(_ operation: RequestOperation) -> Void {
        objc_sync_enter(self);
        for item in operations {
            guard let source = item as? RequestOperation else{
                continue;
            }
            if source.stringURL == operation.stringURL{
                configParate(source, des: operation);
                return;
            }
        }
        
        addOperation(operation);
        objc_sync_exit(self);
    }

    override func addOperation(_ op: Operation) {
    
        if _operations.count > 20 {
            return;
        }
    
        let temp = op as! RequestOperation;
        
        _operations.append(temp);
        if _operations.count < self.maxConcurrentOperationCount {
            temp.start();
            temp.state = .execing;
        }
    }

    func configParate(_ source: RequestOperation,des: RequestOperation)  {
        
        des.memeryProgress = source.memeryProgress;
        des.state = source.state;
        source.startDown = {
            ()->() in
            if des.startDown != nil {
                des.startDown();
            }
        }
        source.progress = {
            (pros) -> Void in
            if des.progress != nil {
                des.progress(pros);
            }
        }
        source.finiTask = {
            (data,success) -> Void in
            des.finiTask(data,success);
        }
 
    }
}

这个类实现了,管理下载的功能,他是一个队列,保存了所有的下载,并且也可以配置同时下载的最大个数,还可以根据URL获取下载的状态,保证了每一个链接在队列里只有一份,防止多重下载.

  • 下面我们创建我们的信息请求类,这个主要包含了,请求的信息,对请求的信息封装,对下载的对调也做了处理.
class SuperHttpRequest: NSObject {
    // 文件保存路劲
    var savePath: String = ""
    // 文件名字
    var fileName: String = ""
    
    var respType = ResponseType.typeJson;
    
    var downProgress: proBlock!
    
    var startDown: startLoaing!
    
    var isAddToken = true;
    
    var cls: AnyClass!

    
    var memeryProgress: Float = 0;
    var stringURL = "";
    
    
    var httpMethod: HttpMethodType = .GET;
    var postType = PostParamterType.formdata; // 0 formdate 1 json
    enum PostParamterType {
        case json
        case formdata
    }
    
    
    var state = HttpState.ready;
    
    var bodyParameter: [String:String]!;
    
    
    lazy var paramterList = [HttpParamterModel]();
    
    lazy var headerParamter = [String:String]();
    
    var albumId: Int64 = 1;
    
    var tableName = "";
    var columnsName = "";
    
    var pathName = "";
    
   

    
   fileprivate override init() {
        super.init();
    }
    convenience init(baseUrl: String) {
        self.init();
        self.stringURL = baseUrl;
        self.fileName = baseUrl;
    
    }


    class func cancel(_ url: String) {
        RequestManager.cancel(url);
    }
    
    func add(value: String,key: String) {
        let model = HttpParamterModel(key: key, value: value);
        paramterList.append(model);
        
    }
    
    func add(valueInt: Int, key: String) -> Void {
        add(value: "\(valueInt)", key: key);
    }

    
    func addBodyInt(valueInt: Int,key: String) -> Void {
        addBody(value: "\(valueInt)", key: key);
    }
    
    func addBody(value: String?,key: String) -> Void {
        
        if let value = value {
            let model = HttpParamterModel(key: key, value: value,isBody: true);
            paramterList.append(model);
        }
        
        if bodyParameter == nil {
            bodyParameter = [String:String]();
        }
        if let va = value{
            bodyParameter[key] = va;
        }
    }
    func addHeader(value: String,key: String) -> Void {
        headerParamter[key] = value;
    }
    func addHeader(intValue: Int,key: String) -> Void {
        addHeader(value: "\(intValue)", key: key);
    }
    
    func addBodyImageData(image: UIImage?,key: String) -> Void {
        
        if let image = image {
            let model = HttpParamterModel(key: key, valueData: image.pngData()!);
            paramterList.append(model);
        }
       
    }
    
    func addBodyArray(values: [Int],key: String) -> Void {
        
        let data = try? JSONSerialization.data(withJSONObject: values, options: .prettyPrinted);
        let strl = String.init(data: data ?? Data(), encoding: .utf8);
        self.addBody(value: strl, key: key);
        
    }
    
    func configParater()  {
        
    }


    
    private func bodyBoundary() -> String {
        return "wfWiEWrgEFA9A78512weF7106A";
    }
    
    //MARK: - 创建 request
 
    func createRequest() -> URLRequest {
        
        configParater();

        
        var paterString = baseURLAPI.appending("/\(self.pathName)").encode;
        
        if self.stringURL.hasPrefix("http") {
            paterString = self.stringURL;
        }
        
        let keyValue = HttpParamterModel.getPairKeyAndValue(list: paramterList);
        paterString = paterString + keyValue;
        
        
        var request = URLRequest(url: URL(string:paterString)!, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 25);
        request.httpMethod = httpMethod.rawValue;
        
        if postType == .json && bodyParameter != nil {
            
            
            let bodyData = try? JSONSerialization.data(withJSONObject: bodyParameter, options: .prettyPrinted);
            
            request.httpBody = bodyData;
            request.setValue("application/json;charset=UTF-8", forHTTPHeaderField: "Content-Type")
            request.setValue("\(bodyData!.count)", forHTTPHeaderField: "Content-Length")
            
        }else if let bodyData = HttpParamterModel.getHttpBodyData(paramterList: paramterList, bodyBoundary: bodyBoundary()) {
            request.httpBody = bodyData
            request.setValue("multipart/form-data;boundary=" + bodyBoundary(), forHTTPHeaderField: "Content-Type");
            request.setValue("\(bodyData.count)", forHTTPHeaderField: "Content-Length")
            
        }
        
        if headerParamter.count > 0 {
            for item in headerParamter {
                request.setValue(item.value, forHTTPHeaderField: item.key);
            }
        }
        
        request.setValue("UTF-8", forHTTPHeaderField: "Accept-Charset");
        request.setValue("no-cache", forHTTPHeaderField: "Cache-Control");

        let times = Date().timestamp;
        request.setValue(times, forHTTPHeaderField: RequestConfigList.timesamp);
        request.setValue(RequestConfigList.getTokenValue(time: times), forHTTPHeaderField: RequestConfigList.assetionkey);

        
        
        
        let infoDict = Bundle.main.infoDictionary;
        if let appVersion = infoDict?["CFBundleShortVersionString"] as? String{
            request.setValue(appVersion, forHTTPHeaderField: "version");
        }
        
        return request;
        
    }
    

    
    // 只从网络上获取
    //    是否强制刷新 数据json
    
    func loadJsonStringFinished(forceRefresh: Bool = true, _ finished:@escaping finishedTask){
        
        
        
        let requestURL = createRequest();
        self.fileName = (requestURL.url?.absoluteString)!;

//        !forceRefresh
        if  !forceRefresh {
            fetchMemeryData(finished);
            return;
        }
        let request = RequestOperation.createOperation(request: requestURL);
        
        let list = HttpParamterModel.getBodyParamter(list: paramterList, isBody: false);
        var parameter = [String:String]();
        for item in list {
            parameter[item.key] = item.value;
        }
        request.parameter = parameter;
        
        request.finiTask = {
            (data,success) -> Void in
            if success {
                
                if !self.savePath.isEmpty {
//                    ReadFileManager.writeFilePath(data! as! Data, path: self.savePath);
                }else if !self.fileName.isEmpty {
//                    ReadFileManager.writeFile(data! as! Data, name: self.fileName);
                }
                let result = self.getResponsTypeData(data as? Data);

                
                finished(result, true);
            }else{
//                let result = self.getDataFromMemer();
                finished(nil,false);
//                let delegate = UIApplication.shared.delegate as? AppDelegate;
                
            }
        }
        
        RequestManager.addJsonRequest(request);
    }

    deinit {
        print("allinit =\(self)");
    }
    
    // 只从 本地获取
  
    func getDataFromMemer(exp: Bool = false) -> AnyObject? {
        
//        return nil;
        
        let fileMger = ReadFileManager(name: "audio");
        fileMger.isExpCheck = exp;
        var data = fileMger.readFileData(self.fileName);
        if data == nil {
            data = fileMger.readFileData(self.savePath);
        }
        let result = getResponsTypeData(data);
        return result;
    }

    
    
// 从内存获取 如果 当前的 数据 超过两天 就重新获取
    
    func fetchMemeryData(exp: Bool = false,_ finished: @escaping finishedTask){
        
        let requestURL = createRequest();
        self.fileName = (requestURL.url?.absoluteString)!;
        
        let result = getDataFromMemer(exp: exp);
        if result != nil {
            if Thread.current.isMainThread {
                finished(result, true);
            }else{
                DispatchQueue.main.async(execute: {
                    finished(result, true);
                })
            }
        }else{
            loadJsonStringFinished(finished);
        }
    }
     
   
    fileprivate func getDataFromTempPath() -> AnyObject? {
        if self.respType == .typeData {
            let files = ReadFileManager(name: "image", path: "path");
            let dict = files.isHasFile(self.stringURL);
            if dict.has == true {
                return files.readFileData(self.stringURL) as AnyObject?;
            }
        }
        return nil;
    }
    
    fileprivate func getResponsTypeData(_ data: Data?) -> AnyObject? {
        
        guard let data = data else {
            return nil;
        }
        
        var result : AnyObject? = nil;
        
        switch self.respType {
        case ResponseType.typeData:
            result = data as AnyObject?;
            break;
        case ResponseType.typeJson:
            do {
                
                let dict = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers);
                
                
                if let clls = cls as? BaseModel.Type , let nDict = dict as? NSDictionary {
                    let model = BaseModel(anyCls: clls, dict: nDict);
                    result = model;
                }else {
                    result = dict as AnyObject?;
                    
                    if (result as? NSDictionary) != nil {
                        
                    }else{
                        printErrorInfo(data: data);

                    }
                    
                   
                }
            }catch {
                printErrorInfo(data: data);
            }
            break;
        case ResponseType.typeImage:
            result = UIImage(data: data);
            break;
        case ResponseType.typeString:
            result = NSString(data: data, encoding: String.Encoding.utf8.rawValue);
        case .typeModel:
            
            guard let dict = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary ,
                let nDict = dict else{
                    
                    printErrorInfo(data: data);
                    
                return nil;
            }
            printObject("dict is = \(nDict)");
            result = BaseModel(dictM: nDict);
        }
        
        return result;
    }
    
    func printErrorInfo(data: Data) -> Void {
        
        print("******\n\n json error debug: \n\n\n\n");
        let errorInfo = NSString(data: data, encoding: String.Encoding.utf8.rawValue);
        print("****************************** \n\n \(String(describing: errorInfo)) \n\n******************************")
    }
    
}
  • 使用方法:

我们直接使用继承SuperHttpRequest的子类即可,下面是一个例子:

class UserRequest : SuperHttpRequest {
    
    override init() {
        super.init();
    }
    
    
    // MARK:  登录有关的 接口
    
    convenience init(login account: String,passWord: String) {
        self.init();
        self.pathName = "login";
        self.httpMethod = .POST;
        add(value: account, key: "account");
        add(value: passWord, key: "password");
    }
}

这个一个登录的使用方法,需要添加一个接口,把参数传过去即可,下面是调用方法:

let request = UserRequest(login: "手机号", passWord: "密码");
        request.loadJsonStringFinished { (result, success) in
            // 返回结果
        };

这里在啰嗦一句,这个默认的返回是字典类型的,如果仔细观察,你就会发现,其实返回参数是可以定制的,比如返回Data或者是数据模型Model或者图片,还有字符串,目前支持这几种类型,如果不够,我们也可以自己添加新的类型.

结语

封装一个新的工具,不但可以学到很多知识,而且还可以提高自己的技术,这对我们反而更好,即使有bug也不要气馁,天下没有完美的事情,天不足西北,地不满东南.何况代码乎,努力总没有错......对了OC版本也是一样的

猜你喜欢

转载自blog.csdn.net/yaojinhai06/article/details/87930881