导语:
下载数据和上传图片是每个开发人员必备的技能,也是最基本的.网上有很多优秀的第三方工具,我们常用的有AFN,YTKNetwork,Alamofire等,这些都是封装很好的,虽然需要研究一下,但是确实很好用.
但是,我们用这些框架也有几个问题需要考虑一下,
- 这些功能都很强大,但是我们真正用到的却少之又少,有点得不偿失.
- 如果版本不合适,需要更换版本,成本也可能会高
- 当出现bug的时候,一般很难解决.
- 对于没有及时升级的第三方库来说,有很多新特性和优化性能不能及时优化.
- 如果第三方不在维护,那么就需要另外在找代替品,需要修改大量代码,成本会更高.
- 加入这些第三方库,会增大应用程序包,
这是从这几个方面来说的,虽然这写问题有很少,但是也要考量一下.
正题
说了这么多,那么我们自己封装一个下载和上传的工具,就可以解决上述的问题.那么我们就开始自己来封装吧.
注:自己封装的库,成本很低,有没有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版本也是一样的