SCNetworkKit 1.0.26

SCNetworkKit 1.0.26

Kiểm traKiểm tra
Ngôn ngữNgôn ngữ Obj-CObjective C
Giấy phép MIT
Đã phát hànhLần phát hành cuối cùngTháng 9 năm 2023

Bảo trì bởi Matt Reach.



  • Bởi
  • qianlongxu

SCNetworkKit

CI Status Version License Platform

SCNetworkKit là một thư viện mạng mạnh mẽ, hỗ trợ các nền tảng iOS/macOS. Khi viết thư viện này, tôi đã tham khảo思想 cơ cấu từ các dự án mở-source xuất sắc như MKNetworkKitAFNetworkingMasonryASIHTTPRequest và thực sự cải tiến và phát triển dựa trên thực tế của dự án công ty.

  • Viết bằng ngôn ngữ Objective-C
  • Mang lại tầng封装 của NSURLSession, hỗ trợ tối thiểu iOS 7.0 / OS X 10.9
  • Đ渎ng theo mẫu Service + Request để phân công (học hiểu từ MKNetworkKit)
  • Đ渎ng mẫuatica để thiết lập bộ giải mã phản hồi có thể配置, có thể解析 dữ liệu thành các đối tượng JSON, Model, trong đó liên quan đến việc giải mã Model là một bước nâng cao từ.module giải mã phản hồi của AFNetworking, hoàn thành theo một cách tiếp cận tư duy của mình
  • Hỗ trợ viết chương trình cha concurso (_Practice Programming _úc) thực chất là cách thực hiện việc lấy block làm giá trị trả về (học hiểu từ Masonry)
  • Đ渎ng cách Maker để rút gọn độ dài của API công khai, sử dụng dễ dàng hơn (học hiểu từ Masonry)
  • Tạo cơ chế tự động hủy bỏ, có thể gắn đối tượng yêu cầu mạng lên đối tượng x (thường là ViewController), khi đối tượng x bị hủy bỏ, sẽ tự động hủy bỏ các yêu cầu mạng đã khởi tạo (x thường là ViewController)
  • Yêu cầu hoàn thành, mật hiệu tiến trình hoàn toàn Block hóa, không hỗ trợ đại lý (tôi ưa thích Block)
  • Đdoctype lớp cơ bản hỗ trợ các yêu cầu GET và HTTP không có thểserie, các yêu cầu POST với body sử dụng subclass hoàn thành (học hiểu từ ASIHTTPRequest)
  • Subclass POST hỗ trợ HTTPBodyStream, dễ dàng hoàn thành việc tải lên tệp lớn; bù lại một nhược điểm của MKNetworkKit
  • Đdoctype tải xuống bài kiểm tra hỗ trợSummer lại, và xử lý các trường hợp đặc biệt như 404, sẽ không viết dữ liệu phản hồi khi gặp lỗi 404 vào tệp

Quá trình biến đổi của SCNetworkKit

Năm 2016, tôi chuyển sang công việc phát triển SDK cho nền tảng iOS, để đảm bảo SDK tôi cung cấp đến rất chuyên nghiệp, dễ集成 và không xuất hiện lỗi xung đột loại, tôi đã tránh khỏi việc phụ thuộc vào các dự án mở-source. Thư viện yêu cầu mạng đầu tiên tôi tạo ra là SVPNetworkKit, hvilke jeg择了 MKNetworkKit vì sự đơn giản và nén, và đã tự cải tiến cho phù hợp với yêu cầu của việc tải lên. Quá trình biến đổi của thư viện này là:

SVPNetworkKit -> SLNetworkKit -> SCNetworkKit

  • SVPNetworkKit: Là một mô đun yêu cầu mạng độc lập được viết trước khi chuyển sang phát triển SDK, vì muốnultat một dự án rất chuyên nghiệp, dễ集成 và tránh lỗi xung đột loại, tôi đã tránh khỏi việc phụ thuộc vào các dự án mở-source. Thư viện yêu cầu mạng đầu tiên tôi tạo ra là SVPNetworkKit, VID đã chọn MKNetworkKit vì sự đơn giản và nén, và đã tự cải tiến cho phù hợp với yêu cầu của việc tải lên.
  • SLNetworkKit: Trong quá trình phát triển SDK, tôi là người duy nhất, bắt buộc phải làm nhiều hỗ trợ cơ bản và viết业务, do đó tiến độ thời gian rất gấp. Do đó, tôi đã đổi tên SVPNetworkKit thành SLNetworkKit và cải tiến trên cơ sở này. Ở giai đoạn này, tôi hỗ trợ các phương thức gọi Maker, rút ra module giải mã phản hồi, hỗ trợ giải mã Model, giải mã phản hồi đồng bộ, và cơ chế tự động hủy bỏ.
  • SCNetworkKit: Với sự phát triển của business SDK, yêu cầu để có thể би linh hoạt chọn lọc các thành phần tích hợp, do đó tôi cần phải降级 các库 cơ bản từ SDK và tạo ra một lib SC khổng lồ hơn, mà tất cả các SDK đều phụ thuộc vào nó. SLNetworkKit chính là một trong số đó, vì vậy tôi đã đổi tên trước đó của nó sang SC! Ở giai đoạn này, tôi đã hỗ trợ các yêu cầu POST với bodysequence, để hỗ trợ việc tải lên các tệp lớn! Năm 2017, tôi đã công khai nó.

目录结构

├── Example
│   ├── Server
│   └── SCNetworkDemo
├── LICENSE
├── README.md
├── SCNetworkKit
│   └── Classes
├── SCNetworkKit.podspec
└── _config.yml
  • Example/SCNetworkDemo : 包含了 iOS、macOS 平台配套调用示例
  • SCNetworkKit/Classes : 源码
  • Example/Server : 使用 Express 编写的简单 Node 服务器,主要为 Demo 提供 GET/POST 请求测试支持,客户端上传的文件都放在 Server/upload 文件夹下面。

Server 使用方法

cd Server
//第一次运行需要安装下依赖库,以后执行就不用了
npm install
//启动 server
npm start

安装方式

  • 使用 CocoaPods 安装

    source 'https://github.com/CocoaPods/Specs.git'
    platform :ios, '7.0'
    
    target 'TargetName' do
    pod 'SCNetworkKit'
    end
    
  • 使用源码

    下载最新 release 代码,找到 SCNetworkKit 目录,拖到工程里即可。

使用范例

假设服务器返回的数据格式如下:

{ 
  code = 0;
  content =     {
     entrance =         (
         {
	         isFlagship = 0;
	         name = "\U65f6\U5c1a\U6f6e\U65f6\U5c1a";
	         pic = "http://pic12.shangpin.com/e/s/15/03/03/20150303151320537363-10-10.jpg";
	         refContent = "http://m.shangpin.com/meet/185";
	         type = 5;
         },
         {
            //....
         }
       )
     }
 }

下面演示如何通过配置不同的解析器,从而达到着陆 block 回调不同结果的效果

  • 发送 GET请求,回调原始 Data,不做解析

    SCNetworkRequest *req = [[SCNetworkRequest alloc]initWithURLString:kTestJSONApi params:nil];
    ///因为默认解析器是SCNJSONResponseParser;会解析成JSON对象;所以这里不指定解析器,让框架返回data!
    req.responseParser = nil;
    [req addCompletionHandler:^(SCNetworkRequest *request, id result, NSError *err) {
        
        if (completion) {
            completion(result,err);
        }
    }];
    
    [[SCNetworkService sharedService]startRequest:req];
  • 发送 GET请求,回调 JOSN 对象

    SCNJSONResponseParser *responseParser = [SCNJSONResponseParser parser];
    ///框架会检查接口返回的 code 是不是 0 ,如果不是 0 ,那么返回给你一个err,并且result是 nil;
    responseParser.checkKeyPath = @"code";
    responseParser.okValue = @"0";
    
    ///support chain
    SCNetworkRequest *req = [[SCNetworkRequest alloc]init];
    
    req
    .c_URL(kTestJSONApi)
    .c_ResponseParser(responseParser)
    .c_CompletionHandler(^(SCNetworkRequest *request, id result, NSError *err) {
        
        if (completion) {
            completion(result,err);
        }
    });
    [[SCNetworkService sharedService]startRequest:req];
  • 发送 GET 请求,回调 Model 对象

    SCNetworkRequest *req = [[SCNetworkRequest alloc]initWithURLString:kTestJSONApi params:nil];
    
    SCNModelResponseParser *responseParser = [SCNModelResponseParser parser];
    ///解析前会检查下JSON是否正确;
    responseParser.checkKeyPath = @"code";
    responseParser.okValue = @"0";
    ///根据服务器返回数据的格式和想要解析结构对应的Model配置解析器
    responseParser.modelName = @"TestModel";
    responseParser.targetKeyPath = @"content/entrance";
    req.responseParser = responseParser;
    [req addCompletionHandler:^(SCNetworkRequest *request, id result, NSError *err) {
        
        if (completion) {
            completion(result,err);
        }
    }];
    [[SCNetworkService sharedService]startRequest:req];

由于上面有 JSON 转 Model 的过程,因此在使用之前需要注册一个对应的解析器,你可以到 demo 里搜下 [SCNModelResponseParser registerModelParser:[SCNModelParser class]]; 具体看下究竟。

  • 文件下载

    SCNetworkRequest *get = [[SCNetworkRequest alloc]initWithURLString:kTestDownloadApi2 params:nil];
    //NSString *path = [NSTemporaryDirectory()stringByAppendingPathComponent:@"node.jpg"];
    NSString *path = [NSTemporaryDirectory()stringByAppendingPathComponent:@"test.mp4"];
    NSLog(@"download path:%@",path);
    get.downloadFileTargetPath = path;
    get.responseParser = nil;
    [get addCompletionHandler:^(SCNetworkRequest *request, id result, NSError *err) {
        
        if (completion) {
            completion(path,err);
        }
    }];
    
    [get addProgressChangedHandler:^(SCNetworkRequest *request, int64_t thisTransfered, int64_t totalBytesTransfered, int64_t totalBytesExpected) {
        
        if (totalBytesExpected > 0) {
            float p = 1.0 * totalBytesTransfered / totalBytesExpected;
            NSLog(@"download progress:%0.4f",p);
            if (progress) {
                progress(p);
            }
        }
    }];
    
    [[SCNetworkService sharedService]startRequest:get];
  • 文件上传

    NSDictionary *ps = @{@"name":@"Matt Reach",@"k1":@"v1",@"k2":@"v2",@"date":[[NSDate new]description]};
    SCNetworkPostRequest *post = [[SCNetworkPostRequest alloc]initWithURLString:kTestUploadApi params:ps];
    
    SCNetworkFormFilePart *filePart = [SCNetworkFormFilePart new];
    NSString *fileURL = [[NSBundle mainBundle]pathForResource:@"logo" ofType:@"png"];
    filePart.data = [[NSData alloc]initWithContentsOfFile:fileURL];
    filePart.fileName = @"logo.png";
    filePart.mime = @"image/jpg";
    filePart.name = @"logo";
    
    SCNetworkFormFilePart *filePart2 = [SCNetworkFormFilePart new];
    filePart2.fileURL = [[NSBundle mainBundle]pathForResource:@"node" ofType:@"txt"];
    
    post.formFileParts = @[filePart,filePart2];
    [post addCompletionHandler:^(SCNetworkRequest *request, id result, NSError *err) {
        
        if (completion) {
            completion(result,err);
        }
    }];
    
    [post addProgressChangedHandler:^(SCNetworkRequest *request, int64_t thisTransfered, int64_t totalBytesTransfered, int64_t totalBytesExpected) {
        
        if (totalBytesExpected > 0) {
            float p = 1.0 * totalBytesTransfered / totalBytesExpected;
            NSLog(@"upload progress:%0.4f",p);
            if (progress) {
                progress(p);
            }
        }
    }];
    
    [[SCNetworkService sharedService]startRequest:post];
  • 通过表单POST数据

    NSDictionary *ps = @{@"name":@"Matt Reach",@"k1":@"v1",@"k2":@"v2",@"date":[[NSDate new]description]};
    SCNetworkPostRequest *post = [[SCNetworkPostRequest alloc]initWithURLString:kTestUploadApi params:ps];
    post.parameterEncoding = SCNPostDataEncodingFormData;
    [post addCompletionHandler:^(SCNetworkRequest *request, id result, NSError *err) {
        
        if (completion) {
            completion(result,err);
        }
    }];
    
    [[SCNetworkService sharedService]startRequest:post];

链式编程

SCNJSONResponseParser *responseParser = [SCNJSONResponseParser parser];
///框架会检查接口返回的 code 是不是 0 ,如果不是 0 ,那么返回给你一个err,并且result是 nil;
responseParser.checkKeyPath = @"code";
responseParser.okValue = @"0";
    
///support chain
    
SCNetworkRequest *req = [[SCNetworkRequest alloc]init];
    
req.c_URL(@"http://debugly.cn/dist/json/test.json")
   .c_ResponseParser(responseParser);
   .c_CompletionHandler(^(SCNetworkRequest *request, id result, NSError *err) {
        
            if (completion) {
                completion(result);
            }
       });
[[SCNetworkService sharedService]sendRequest:req];

架构设计

  • 综合参考了 MKNetwork2.0 和 AFNetwork 2.0 的设计,吸取了他们的精华,去掉了冗余的设计,融入了自己的想法,将网络请求抽象为 Request 对象,并由 Service 管理,Service 为 Request 分配代理对象 --- 处理传输数据、请求结束,请求失败等事件,请求结束后通过改变 Rquest 的 state 属性,告知 Request 请求结束,然后根据配置的响应解析器,异步解析数据,结果可能是 data, string, json, model, image 等等;最终通过我们添加到 Request 对象上的 completionBlock 回调给调用层。

设计图

用注册方法解耦

功能强大同时考虑扩展性,本框架支持多种扩展,以解析响应为例,你可以继续创建你需要的解析器;你可以使用你喜欢的 JSON 转换 Model 框架进行解析;还可以让网络库解析更多格式的图片;这些都是可以实现的,而且很简单。

  • 由于框架包含支持 JSON 转换 Model 的 SCNModelResponseParser 响应解析器,因此不得依赖 JSON 转换 Model 的框架。考虑到项目中可能已经有了这样的框架,因此并没有将这块逻辑固定,而是采用注册的方式,来扩展 SCN 的能力!所以使用 Model 解析器之前必须注册一个用于将 JSON 转换为 Model 的类,该类实现 SCNModelParserProtocol 协议!为了方便,最好在 APP 启动时注册或创建 Service 时注册,以避免使用时未注册而导致崩溃!

    @protocol SCNModelParserProtocol <NSObject>
    
    @required;
    + (id)JSON2Model:(id)json modelName:(NSString *)mName;
    
    @end
    
    @interface SCNModelResponseParser : SCNJSONResponseParser
    
    @property (nonatomic,copy) NSString *modelName;
    
    + (void)registerModelParser:(Class<SCNModelParserProtocol>)parser;
    
    @end
    

我在 demo 中使用的是我的另一个工具:SCJSONUtil,具体实现可查阅 demo。

  • 图片解析器默认支持 png、jpg 图片格式。由于 webp 格式由于体积更小,许多厂商开始使用,我的 SDK 也使用了这种格式,因此我在 SDK 中注册了解析 webp 的解析类;

    @protocol SCNImageParserProtocol <NSObject>
    
    @required;
    + (UIImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;
    
    @end
    
    ///默认支持png 和 jpg;可通过注册的方式扩展!
    @interface SCNImageResponseParser : SCNHTTPResponseParser
    
    
    /**
     注册新的图片解析器和对应的类型
    
     @param parser 解析器
     @param mime 支持的类型
     */
    + (void)registerParser:(Class<SCNImageParserProtocol>)parser forMime:(NSString *)mime;
    
    @end
    

这种注册器的方式优雅地扩展了网络库的功能,就像插件一样,插上就能用,只需符合我协议中规定的要求即可!反过来,如果你不需要解析 webp 或无需 JSON 转换 Model,就无需添加对应的模块!

如果没有这种良好的实践,达到同样的扩展效果可能就很难了!如果你有其他意见请联系我。

SCNetworkService

如图所示,SCNetworkService 主要负责发起网络请求,处理 Request、task 和 delegate 对象的一一对应关系!

  • 为了方便使用,还提供了可用于整个 App 的共享 SCNetworkService 对象,用于发送普通网络请求;当然,你有必要为不同的业务创建不同的 Service;一个 Service 内部对应一个 NSURLSession 对象!

SCNetworkRequest

NSURLSession 管理的网络请求结束后,在 SCNetworkRequest 中处理响应数据,根据配置的 ResponseParser 异步解析,最终在主线程中安全着陆;

  • SCNetworkRequest 从 start 开始被 Service 拥有,直到着陆后 Service 不再拥有,因此上层不需要持有 SCNetworkRequest 对象!如果要拥有 SCNetworkRequest 对象的指针,通常使用 weak 即可;
  • SCNetworkRequest 支持添加多个回调,回调顺序与添加顺序相同;
  • 请注意,在添加回调时,不要让 SCNetworkRequest 持有你的对象,否则 SCNetworkRequest 一直持有,直到着陆。虽然这不会导致循环引用导致的内存泄漏,但会“延长”被持有对象的生命周期;
  • 该类默认发送 GET 请求,也可以修改 method 发送 POST 请求,但只能发送不包含 body 的 POST 请求。

SCNetworkPostRequest

继承自 SCNetworkRequest,专门用于发送包含 body 的 POST 请求,body 内容支持四种编码方式

  • SCNPostDataEncodingURL : application/x-www-form-urlencoded;
  • SCNPostDataEncodingJSON : application/json;
  • SCNPostDataEncodingPlist : application/x-plist;
  • SCNPostDataEncodingFormData : multipart/form-data;

只有使用 SCNPostDataEncodingFormData 方式的请求会采用 HTTPBodyStream!

版本

  • 1.0.5 : 支持 stream HTTPBody,轻松处理大文件上传
  • 1.0.6 : 支持一次上传多个文件,配套 Node 上传文件服务器
  • 1.0.7 : 修复直接使用二进制上传失败问题(重复计算长度,导致Content Length计算偏大)
  • 1.0.8 : 支持 macOS 平台 (暂不支持图片解析)
  • 1.0.9 : 整理目录,POST 请求可添加 Query 参数
  • 1.0.10 : 修改默认 UA 格式
  • 1.0.11 : 抽取解析过程,可完全自定义;支持 JSONUtil 的动态映射
  • 1.0.12 : 解除并发数为 2 的限制,使用系统配置
  • 1.0.13 : 支持自定义请求body体
  • 1.0.14 : 修改默认UA
  • 1.0.15 : 下载文件支持断点续传
  • 1.0.16 : 将下载逻辑抽取为单独的类
  • 1.0.17 : 移除自动取消支持,这一功能抽取为了单独的 MRDeallocSubscriber 模块,可通过block形式完成,使得网络库更加纯粹
  • 1.0.18 : 整理下载文件逻辑,使用单独的属性控制断点续传,避免404等情况的响应数据写入文件
  • 1.0.19:基类支持发送不带 body 体的 POST 请求
  • 1.0.20:修复断点续传 Range 请求 416 问题
  • 1.0.21:修复下载类重写 getter 导致的调用堆栈溢出问题
  • 1.0.22:修复使用 Model 解析默认将 JSON 里的值转成 String 问题
  • 1.0.23: 下载文件时自动创建父级目录,避免下载出错
  • 1.0.24: 支持通过 NSURLRequest 初始化请求对象
  • 1.0.25: 修复参数字典为空时会在请求后面拼接?或者&的问题

由于该网络库是完全为自己业务服务的,因此不是所有的功能都很完善,而是用到时再加,发现不合理就改,所以如果你使用了 SCNetworkKit,发现功能缺失,可以提交 PR 或者 Issue 给我!