一:介绍
React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域。
在React Native移动平台项目开发中,除了React Native 提供的封装好的部分插件和原声组建外,在实际的项目中还需要使用到很多其他的插件,比如网络请求、数据库、相机、相册、通讯录、视频播放器、浏览器、蓝牙连接、图片处理、消息推送、地图、统计、埋点等等APP开发中需要用到的功能,都为IDE开发平台提供封装好的插件,以便项目开发使用。
另外,这些博文都是来源于我日常开发中的技术总结,在时间允许的情况下,我会针对技术点分别分享iOS、Android两个版本,如果有其他技术点需要,可在文章后留言,我会尽全力帮助大家。这篇文章重点介绍网络请求插件的开发与使用
二:实现思路分析
网络请求插件是需要实现前端与服务端的数据交互,其中包括GET请求、POST请求、文件上传、单/多张图片上传、文件下载等功能。这些功能将通过封装后的方法暴漏出来,通过RN接口提供给Javascript开发使用。
具体的实现思路如下:
新建NetWorkPlugin类,实现RCTBridgeModule协议
添加RCT_EXPORT_MODULE()宏
添加React Native跟控制器
声明被JavaScript 调用的方法
导入AFNetworking请求库
新建NetworkHelper类,封装实现网络请求功能
实现GET请求
实现POST请求
实现文件上传
实现单/多张图片上传
实现文件下载
Javascript调用浏览器方法
三:实现源码分析
1. 新建NetWorkPlugin类,实现RCTBridgeModule协议
新建继承NSObject的NetWorkPlugin类,并实现RCTBridgeModule协议1
2
3
4
5
6// NetWorkPlugin.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <UIKit/UIKit.h>
@interface NetWorkPlugin : NSObject<RCTBridgeModule>
@end
2. 添加RCT_EXPORT_MODULE()宏
为了实现RCTBridgeModule协议,NetWorkPlugin的类需要包含RCT_EXPORT_MODULE()宏。
并在这个宏里面添加一个参数“NetWorkPlugin”用来指定在 JavaScript 中访问这个模块的名字。
如果你不指定,默认就会使用这个 Objective-C 类的名字。
如果类名以 RCT 开头,则 JavaScript 端引入的模块名会自动移除这个前缀。1
2
3
4
5// NetWorkPlugin.m
#import "NetWorkPlugin.h"
@implementation NetWorkPlugin
RCT_EXPORT_MODULE(NetWorkPlugin);
@end
3. 添加React Native跟控制器
如果不添加React Native跟控制器,view将不能正常显示出来,实现方法如下:1
2// NetWorkPlugin.m
#import <React/RCTUtils.h>
引入1
UIViewController *vc = RCTPresentedViewController();
4. 声明被JavaScript 调用的方法
React Native需要明确的声明要给 JavaScript 导出的方法,否则 React Native 不会导出任何方法。下面通过举例来展示声明的方法,通过RCT_EXPORT_METHOD()宏来实现:
1 | // NetWorkPlugin.m |
5. 导入AFNetworking请求库
网络请求使用的第三方库是AFNetworking,这个库很常见,也比较常用,就不做过多的描述,可手动导入也可使用cocoapods自动导入,导入之后在.m文件中引入头文件。
6. 新建NetworkHelper类,封装实现网络请求功能
新建继承NSObject的NetworkHelper类,定义枚举类型来判断网络状态:1
2
3
4
5
6
7
8
9
10typedef NS_ENUM(NSUInteger, NetworkStatusType) {
/** 未知网络*/
NetworkStatusUnknown,
/** 无网络*/
NetworkStatusNotReachable,
/** 手机网络*/
NetworkStatusReachableViaWWAN,
/** WIFI网络*/
NetworkStatusReachableViaWiFi
};
定义网络状态的Block1
typedef void(^NetworkStatus)(NetworkStatusType status);
实时获取网络状态,通过Block回调实时获取(此方法可多次调用)1
+ (void)networkStatusWithBlock:(NetworkStatus)networkStatus;
7. 实现GET请求
声明GET请求方法:1
2
3
4
5
6
7
8
9
10
11
12
13/**
*
* @param URL 请求地址
* @param parameters 请求参数
* @param success 请求成功的回调
* @param failure 请求失败的回调
*
* @return 返回的对象可取消请求,调用cancel方法
*/
+ (__kindof NSURLSessionTask *)GET:(NSString *)URL
parameters:(id)parameters
success:(HttpRequestSuccess)success
failure:(HttpRequestFailed)failure;
8. 实现POST请求
声明POST请求方法:1
2
3
4
5
6
7
8
9
10
11
12
13/**
*
* @param URL 请求地址
* @param parameters 请求参数
* @param success 请求成功的回调
* @param failure 请求失败的回调
*
* @return 返回的对象可取消请求,调用cancel方法
*/
+ (__kindof NSURLSessionTask *)POST:(NSString *)URL
parameters:(id)parameters
success:(HttpRequestSuccess)success
failure:(HttpRequestFailed)failure;
POST请求具体的方法实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28+ (NSURLSessionTask *)POST:(NSString *)URL
parameters:(id)parameters
success:(HttpRequestSuccess)success
failure:(HttpRequestFailed)failure {
NSString *AllReplaceURL = [self replaceURL:URL];
[self setAFHTTPSessionManagerProperty:^(AFHTTPSessionManager *sessionManager) {
[sessionManager.requestSerializer setQueryStringSerializationWithBlock:^NSString * _Nonnull(NSURLRequest * _Nonnull request, id _Nonnull parameters, NSError * _Nullable __autoreleasing * _Nullable error) {
return parameters;
}];
}];
NSURLSessionTask *sessionTask = [_sessionManager POST:AllReplaceURL parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (_isOpenLog) {NSLog(@"responseObject = %@",[self jsonToString:responseObject]);}
[[self allSessionTask] removeObject:task];
success ? success(responseObject) : nil;
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (_isOpenLog) {NSLog(@"error = %@",error);}
[[self allSessionTask] removeObject:task];
failure ? failure(error) : nil;
}];
sessionTask ? [[self allSessionTask] addObject:sessionTask] : nil ;
return sessionTask;
}
9. 实现文件上传
声明文件上传方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
*
* @param URL 请求地址
* @param parameters 请求参数
* @param names 文件对应服务器上的字段
* @param filePaths 文件本地的沙盒路径
* @param progress 上传进度信息
* @param success 请求成功的回调
* @param failure 请求失败的回调
*
* @return 返回的对象可取消请求,调用cancel方法
*/
+ (__kindof NSURLSessionTask *)uploadFileWithURL:(NSString *)URL
parameters:(id)parameters
names:(NSArray<NSString *> *)names
filePaths:(NSArray<NSString *> *)filePaths
progress:(HttpProgress)progress
success:(HttpRequestSuccess)success
failure:(HttpRequestFailed)failure;
文件上传具体的方法实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38+ (NSURLSessionTask *)uploadFileWithURL:(NSString *)URL
parameters:(id)parameters
names:(NSArray<NSString *> *)names
filePaths:(NSArray<NSString *> *)filePaths
progress:(HttpProgress)progress
success:(HttpRequestSuccess)success
failure:(HttpRequestFailed)failure {
NSString *AllReplaceURL = [self replaceURL:URL];
NSURLSessionTask *sessionTask = [_sessionManager POST:AllReplaceURL parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
NSError *error = nil;
for (NSUInteger i = 0; i < filePaths.count; i++) {
NSString *name = names[i];
NSString *filePath = filePaths[i];
[formData appendPartWithFileURL:[NSURL fileURLWithPath:filePath] name:name error:&error];
}
(failure && error) ? failure(error) : nil;
} progress:^(NSProgress * _Nonnull uploadProgress) {
//上传进度
dispatch_sync(dispatch_get_main_queue(), ^{
progress ? progress(uploadProgress) : nil;
});
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (_isOpenLog) {NSLog(@"responseObject = %@",[self jsonToString:responseObject]);}
[[self allSessionTask] removeObject:task];
success ? success(responseObject) : nil;
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (_isOpenLog) {NSLog(@"error = %@",error);}
[[self allSessionTask] removeObject:task];
failure ? failure(error) : nil;
}];
sessionTask ? [[self allSessionTask] addObject:sessionTask] : nil ;
return sessionTask;
}
10. 实现单/多张图片上传
声明单/多张图片上传方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**
*
* @param URL 请求地址
* @param parameters 请求参数
* @param name 图片对应服务器上的字段
* @param images 图片数组
* @param fileNames 图片文件名数组, 可以为nil, 数组内的文件名默认为当前日期时间"yyyyMMddHHmmss"
* @param imageScale 图片文件压缩比 范围 (0.f ~ 1.f)
* @param imageType 图片文件的类型,例:png、jpg(默认类型)....
* @param progress 上传进度信息
* @param success 请求成功的回调
* @param failure 请求失败的回调
*
* @return 返回的对象可取消请求,调用cancel方法
*/
+ (__kindof NSURLSessionTask *)uploadImagesWithURL:(NSString *)URL
parameters:(id)parameters
name:(NSString *)name
images:(NSArray<UIImage *> *)images
fileNames:(NSArray<NSString *> *)fileNames
imageScale:(CGFloat)imageScale
imageType:(NSString *)imageType
progress:(HttpProgress)progress
success:(HttpRequestSuccess)success
failure:(HttpRequestFailed)failure;
图片经过等比压缩后得到的二进制文件,默认图片的文件名, 若fileNames为nil就使用,单/多张图片上传具体的方法实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48+ (NSURLSessionTask *)uploadImagesWithURL:(NSString *)URL
parameters:(id)parameters
name:(NSString *)name
images:(NSArray<UIImage *> *)images
fileNames:(NSArray<NSString *> *)fileNames
imageScale:(CGFloat)imageScale
imageType:(NSString *)imageType
progress:(HttpProgress)progress
success:(HttpRequestSuccess)success
failure:(HttpRequestFailed)failure {
NSString *AllReplaceURL = [self replaceURL:URL];
NSURLSessionTask *sessionTask = [_sessionManager POST:AllReplaceURL parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
for (NSUInteger i = 0; i < images.count; i++) {
NSData *imageData = UIImageJPEGRepresentation(images[i], imageScale ?: 1.f);
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *imageFileName = NSStringFormat(@"%@%ld.%@",str,i,imageType?:@"jpg");
[formData appendPartWithFileData:imageData
name:name
fileName:fileNames ? NSStringFormat(@"%@.%@",fileNames[i],imageType?:@"jpg") : imageFileName
mimeType:NSStringFormat(@"image/%@",imageType ?: @"jpg")];
}
} progress:^(NSProgress * _Nonnull uploadProgress) {
dispatch_sync(dispatch_get_main_queue(), ^{
progress ? progress(uploadProgress) : nil;
});
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (_isOpenLog) {NSLog(@"responseObject = %@",[self jsonToString:responseObject]);}
[[self allSessionTask] removeObject:task];
success ? success(responseObject) : nil;
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (_isOpenLog) {NSLog(@"error = %@",error);}
[[self allSessionTask] removeObject:task];
failure ? failure(error) : nil;
}];
sessionTask ? [[self allSessionTask] addObject:sessionTask] : nil ;
return sessionTask;
}
11. 实现文件下载
声明文件下载方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
*
* @param URL 请求地址
* @param fileDir 文件存储目录(默认存储目录为Download)
* @param progress 文件下载的进度信息
* @param success 下载成功的回调(回调参数filePath:文件的路径)
* @param failure 下载失败的回调
*
* @return 返回NSURLSessionDownloadTask实例,可用于暂停继续,暂停调用suspend方法,开始下载调用resume方法
*/
+ (__kindof NSURLSessionTask *)downloadWithURL:(NSString *)URL
fileDir:(NSString *)fileDir
progress:(HttpProgress)progress
success:(void(^)(NSString *filePath))success
failure:(HttpRequestFailed)failure;
在下载过程中可以获取到下载进度,下载流程为:缓存目录拼接完成,打开文件管理器,创建Download目录,拼接文件路径,返回文件位置的URL路径。文件下载具体的方法实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34+ (NSURLSessionTask *)downloadWithURL:(NSString *)URL
fileDir:(NSString *)fileDir
progress:(HttpProgress)progress
success:(void(^)(NSString *))success
failure:(HttpRequestFailed)failure {
NSString *AllReplaceURL = [self replaceURL:URL];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:AllReplaceURL]];
__block NSURLSessionDownloadTask *downloadTask = [_sessionManager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
dispatch_sync(dispatch_get_main_queue(), ^{
progress ? progress(downloadProgress) : nil;
});
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
NSString *downloadDir = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:fileDir ? fileDir : @"Download"];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createDirectoryAtPath:downloadDir withIntermediateDirectories:YES attributes:nil error:nil];
NSString *filePath = [downloadDir stringByAppendingPathComponent:response.suggestedFilename];
return [NSURL fileURLWithPath:filePath];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
[[self allSessionTask] removeObject:downloadTask];
if(failure && error) {failure(error) ; return ;};
success ? success(filePath.absoluteString /** NSURL->NSString*/) : nil;
}];
[downloadTask resume];
downloadTask ? [[self allSessionTask] addObject:downloadTask] : nil ;
return downloadTask;
}
12. Javascript调用浏览器方法
现在从 Javascript 里可以这样调用这个方法:1
2
3
4
5
6
7
8import { NativeModules } from "react-native";
const NetWorkPlugin = NativeModules.NetWorkPlugin;
NetworkPlugin.post({url:"http://192.168.1.1:8080/ApiSystem/login",params:{name:"15842137500",login_type:"0",password:"000000"},headers:{}},(msg) => {
Alert.alert(JSON.stringify(msg));
},(err) => {
Alert.alert(JSON.stringify(err));
});
希望可以帮助大家,如有问题可加QQ技术交流群: 668562416
如果哪里有什么不对或者不足的地方,还望读者多多提意见或建议
如需转载请联系我,经过授权方可转载,谢谢