iOS--React Native網絡請求插件

一:介紹

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開發使用。

具體的實現思路如下:

  1. 新建NetWorkPlugin類,實現RCTBridgeModule協議

  2. 添加RCT_EXPORT_MODULE()宏

  3. 添加React Native跟控制器

  4. 聲明被JavaScript 調用的方法

  5. 導入AFNetworking請求庫

  6. 新建NetworkHelper類,封裝實現網絡請求功能

  7. 實現GET請求

  8. 實現POST請求

  9. 實現文件上傳

  10. 實現單/多張圖片上傳

  11. 實現文件下載

  12. Javascript調用瀏覽器方法

三:實現源碼分析

1. 新建NetWorkPlugin類,實現RCTBridgeModule協議

新建繼承NSObject的NetWorkPlugin類,並實現RCTBridgeModule協議

// 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 端引入的模塊名會自動移除這個前綴。

// NetWorkPlugin.m
#import "NetWorkPlugin.h"
@implementation NetWorkPlugin
RCT_EXPORT_MODULE(NetWorkPlugin);
@end
3. 添加React Native跟控制器

如果不添加React Native跟控制器,view將不能正常顯示出來,實現方法如下:

// NetWorkPlugin.m
#import <React/RCTUtils.h>

引入<React/RCTUtils.h>之後,在視圖初始化或者顯示的時候,按照如下方法調用即可

UIViewController *vc = RCTPresentedViewController();
4. 聲明被JavaScript 調用的方法

React Native需要明確的聲明要給 JavaScript 導出的方法,否則 React Native 不會導出任何方法。下面通過舉例來展示聲明的方法,通過RCT_EXPORT_METHOD()宏來實現:

// NetWorkPlugin.m
#import "NetWorkPlugin.h"
#import <React/RCTUtils.h>
@implementation NetWorkPlugin
RCT_EXPORT_MODULE(NetWorkPlugin);
RCT_EXPORT_METHOD(post:(NSDictionary *)arguments
                  withCompletionHandler:(RCTResponseSenderBlock)completion
                  failureHandler:(RCTResponseSenderBlock)failure)
{
    NSLog(@"POST網絡請求執行方法");
}
@end
5. 導入AFNetworking請求庫

網絡請求使用的第三方庫是AFNetworking,這個庫很常見,也比較常用,就不做過多的描述,可手動導入也可使用cocoapods自動導入,導入之後在.m文件中引入頭文件。

6. 新建NetworkHelper類,封裝實現網絡請求功能

新建繼承NSObject的NetworkHelper類,定義枚舉類型來判斷網絡狀態:

typedef NS_ENUM(NSUInteger, NetworkStatusType) {
    /** 未知網絡*/
    NetworkStatusUnknown,
    /** 無網絡*/
    NetworkStatusNotReachable,
    /** 手機網絡*/
    NetworkStatusReachableViaWWAN,
    /** WIFI網絡*/
    NetworkStatusReachableViaWiFi
};

定義網絡狀態的Block

typedef void(^NetworkStatus)(NetworkStatusType status);

實時獲取網絡狀態,通過Block回調實時獲取(此方法可多次調用)

+ (void)networkStatusWithBlock:(NetworkStatus)networkStatus;
7. 實現GET請求

聲明GET請求方法:

/**
 *
 *  @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請求方法:

/**
 *
 *  @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請求具體的方法實現如下:

+ (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. 實現文件上傳

聲明文件上傳方法:

/**
 * 
 *  @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;

文件上傳具體的方法實現如下:

+ (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. 實現單/多張圖片上傳

聲明單/多張圖片上傳方法:

/**
 *
 *  @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就使用,單/多張圖片上傳具體的方法實現如下:

+ (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. 實現文件下載

聲明文件下載方法:

/**
 *
 *  @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路徑。文件下載具體的方法實現如下:

+ (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 裏可以這樣調用這個方法:

import { 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

如果哪裏有什麼不對或者不足的地方,還望讀者多多提意見或建議

如需轉載請聯繫我,經過授權方可轉載,謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章