目录
- 简介
- 一、日志记录集成[LogManager]
- 二、使用【AFNetworking】集成接口
- 1、get请求
- 2、Post请求
- 3、图片上传
- 4、文件上传
- 5、请求头/错误处理
- 三、错误h5集成
- 参考链接
简介
本篇文章主要目的是为了将用户操作习惯记录到本地文件,然后定期或者根据实际需要打包压缩上传到服务器,用以处理用户在闪退的时候,或需要详细了解具体某个用户在这一段时间的操作习惯。由于要压缩上传本地日志,顺道集成了AFNetWorking了post和get的接口请求,以及请求是接口失败后,错误信息显示,这个在开发的时候特别方便,后台可以在根据这些错误日志查询对应的问题。
点击下载集成的Demo:
github:【RequestAndLogManager 】
gitee:【RequestAndLogManager】
一、日志记录集成[LogManager]
在文件中,对每个方法和属性都做了注释;有写入日志方法,也有打印写入日志的方法,以便于检查日志是否成功
#import <Foundation/Foundation.h>
记录本地日志
#define LLog(module,...) {\
[[LogManager sharedInstance] logInfo:module logStr:__VA_ARGS__,nil];\
}
// 日志保留最大天数
static const int LogMaxSaveDay = 7;
// 日志文件保存目录
static const NSString* LogFilePath = @"/Documents/ZMLog/";
// 日志压缩包文件名
static NSString* ZipFileName = @"ZMLog.zip";
@interface LogManager : NSObject
/**
* 获取单例实例
*
* @return 单例实例
*/
+ (instancetype) sharedInstance;
#pragma mark - Method
/**
* 写入日志
*
* @param module 模块名称
* @param logStr 日志信息,动态参数
*/
- (void)logInfo:(NSString*)module logStr:(NSString*)logStr, ...;
///清空过期的日志
- (void)clearExpiredLog;
/**
*
*/
///检测日志是否需要上传
- (void)checkLogNeedUpload;
/**
读取文件信息
@param filePath 文件路径
*/
- (NSString *)readFile:(NSString *)filePath;
/**
获取对应日期做为文件名
@param dateStr 自定义日期【格式:yyyy-MM-dd】
@return 返回文件路径
*/
-(NSString *)getLogPathWithDate:(NSString *)dateStr;
@end
#import "LogManager.h"
#import <ZipArchive.h>
#import "ZMEngine.h"
@interface LogManager()
// 日期格式化
@property (nonatomic,retain) NSDateFormatter* dateFormatter;
// 时间格式化
@property (nonatomic,retain) NSDateFormatter* timeFormatter;
// 日志的目录路径
@property (nonatomic,copy) NSString* basePath;
@end
@implementation LogManager
/**
* 获取单例实例
*
* @return 单例实例
*/
+ (instancetype) sharedInstance{
static LogManager* instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!instance) {
instance = [[LogManager alloc]init];
}
});
return instance;
}
// 获取当前时间
+ (NSDate*)getCurrDate{
NSDate *date = [NSDate date];
NSTimeZone *zone = [NSTimeZone systemTimeZone];
NSInteger interval = [zone secondsFromGMTForDate: date];
NSDate *localeDate = [date dateByAddingTimeInterval: interval];
return localeDate;
}
#pragma mark - Init
- (instancetype)init{
self = [super init];
if (self) {
// 创建日期格式化
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
// 设置时区,解决8小时
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
self.dateFormatter = dateFormatter;
// 创建时间格式化
NSDateFormatter* timeFormatter = [[NSDateFormatter alloc]init];
[timeFormatter setDateFormat:@"HH:mm:ss"];
[timeFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
self.timeFormatter = timeFormatter;
// 日志的目录路径
self.basePath = [NSString stringWithFormat:@"%@%@",NSHomeDirectory(),LogFilePath];
}
return self;
}
#pragma mark - Method
/**
* 写入日志
*
* @param module 模块名称
* @param logStr 日志信息,动态参数
*/
- (void)logInfo:(NSString*)module logStr:(NSString*)logStr, ...{
#pragma mark - 获取参数
NSMutableString* parmaStr = [NSMutableString string];
// 声明一个参数指针
va_list paramList;
// 获取参数地址,将paramList指向logStr
va_start(paramList, logStr);
id arg = logStr;
@try {
// 遍历参数列表
while (arg) {
[parmaStr appendString:arg];
// 指向下一个参数,后面是参数类似
arg = va_arg(paramList, NSString*);
}
} @catch (NSException *exception) {
[parmaStr appendString:@"【记录日志异常】"];
} @finally {
// 将参数列表指针置空
va_end(paramList);
}
#pragma mark - 写入日志
// 异步执行
dispatch_async(dispatch_queue_create("writeLog", nil), ^{
NSString* filePath = [self getLogPathWithDate:nil];
// [时间]-[模块]-日志内容
NSString* timeStr = [self.timeFormatter stringFromDate:[LogManager getCurrDate]];
NSString* writeStr = [NSString stringWithFormat:@"[%@]-[%@]-%@\n",timeStr,module,parmaStr];
// 写入数据
[self writeFile:filePath stringData:writeStr];
NSLog(@"写入日志:%@",filePath);
});
}
/**
读取文件信息
@param fileName 文件路径
*/
- (NSString *)readFile:(NSString *)fileName{
NSString *filePath = [self getLogPathWithDate:fileName];
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSString *logStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
return logStr;
}
/**
获取对应日期做为文件名
@param dateStr 自定义日期【格式:yyyy-MM-dd】
@return 返回文件路径
*/
-(NSString *)getLogPathWithDate:(NSString *)dateStr{
NSString* fileName = nil;
if(dateStr||dateStr > 0){
fileName = dateStr;
}else{
fileName = [self.dateFormatter stringFromDate:[NSDate date]];
}
NSString* filePath = [NSString stringWithFormat:@"%@%@",self.basePath,fileName];
return filePath;
}
///清空过期的日志
- (void)clearExpiredLog{
// 获取日志目录下的所有文件
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.basePath error:nil];
for (NSString* file in files) {
NSDate* date = [self.dateFormatter dateFromString:file];
if (date) {
NSTimeInterval oldTime = [date timeIntervalSince1970];
NSTimeInterval currTime = [[LogManager getCurrDate] timeIntervalSince1970];
NSTimeInterval second = currTime - oldTime;
int day = (int)second / (24 * 3600);
if (day >= LogMaxSaveDay) {
// 删除该文件
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@/%@",self.basePath,file] error:nil];
NSLog(@"[%@]日志文件已被删除!",file);
}
}
}
}
///检测日志是否需要上传
- (void)checkLogNeedUpload{
// 发起请求,从服务器上获取当前应用是否需要上传日志
[kZMEngine checkUploadLogWithResponse:^(id response) {
if ([kZMEngine getResultCode:response] == 1) {
NSDictionary *dic = [kZMEngine getResultData:response];
if (dic&&[dic isKindOfClass:[NSDictionary class]]&&dic.allKeys>0) {
[self uploadLog:dic];
}else{
NSLog(@"请求失败,data没有数据!");
}
}else{
NSLog(@"检测日志失败!");
}
}];
}
#pragma mark - Private
/**
* 处理是否需要上传日志
*
* @param resultDic 包含获取日期的字典
*/
- (void)uploadLog:(NSDictionary*)resultDic{
if (!resultDic) {
return;
}
// 0不拉取,1拉取N天,2拉取全部
int type = [resultDic[@"type"] intValue];
// 压缩文件是否创建成功
BOOL created = NO;
if (type == 1) {
// 拉取指定日期的
// "dates": ["2017-03-01", "2017-03-11"]
NSArray* dates = resultDic[@"dates"];
// 压缩日志
created = [self compressLog:dates];
}else if(type == 2){
// 拉取全部
// 压缩日志
created = [self compressLog:nil];
}
if (created) {
// 上传
[self uploadLogToServer:^(BOOL boolValue) {
if (boolValue) {
NSLog(@"日志上传成功---->>",nil);
// 删除日志压缩文件
[self deleteZipFile];
}else{
NSLog(@"日志上传失败!!",nil);
}
}];
}
}
/**
* 压缩日志
*
* @param dates 日期时间段,空代表全部
*
* @return 执行结果
*/
- (BOOL)compressLog:(NSArray*)dates{
// 先清理几天前的日志
[self clearExpiredLog];
// 获取日志目录下的所有文件
NSArray* files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.basePath error:nil];
// 压缩包文件路径
NSString * zipFile = [self.basePath stringByAppendingString:ZipFileName] ;
ZipArchive* zip = [[ZipArchive alloc] init];
// 创建一个zip包
BOOL created = [zip CreateZipFile2:zipFile];
if (!created) {
// 关闭文件
[zip CloseZipFile2];
return NO;
}
if (dates) {
// 拉取指定日期的
for (NSString* fileName in files) {
if ([dates containsObject:fileName]) {
// 将要被压缩的文件
NSString *file = [self.basePath stringByAppendingString:fileName];
// 判断文件是否存在
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
// 将日志添加到zip包中
[zip addFileToZip:file newname:fileName];
}
}
}
}else{
// 全部
for (NSString* fileName in files) {
// 将要被压缩的文件
NSString *file = [self.basePath stringByAppendingString:fileName];
// 判断文件是否存在
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
// 将日志添加到zip包中
[zip addFileToZip:file newname:fileName];
}
}
}
// 关闭文件
[zip CloseZipFile2];
return YES;
}
/**
* 上传日志到服务器
*
* @param returnBlock 成功回调
*/
- (void)uploadLogToServer:(void(^)(BOOL boolValue))returnBlock{
NSString *filePath = [self getLogPathWithDate:@""];
[kZMEngine updateFileWithfileName:ZipFileName filePath:filePath response:^(id response) {
if ([kZMEngine getResultCode:response] == 1) {
returnBlock(YES);
}else{
returnBlock(NO);
}
}];
}
/**
* 删除日志压缩文件
*/
- (void)deleteZipFile{
NSString* zipFilePath = [self.basePath stringByAppendingString:ZipFileName];
if ([[NSFileManager defaultManager] fileExistsAtPath:zipFilePath]) {
[[NSFileManager defaultManager] removeItemAtPath:zipFilePath error:nil];
}
}
/**
* 写入字符串到指定文件,默认追加内容
*
* @param filePath 文件路径
* @param stringData 待写入的字符串
*/
- (void)writeFile:(NSString*)filePath stringData:(NSString*)stringData{
// 待写入的数据
NSData* writeData = [stringData dataUsingEncoding:NSUTF8StringEncoding];
// NSFileManager 用于处理文件
BOOL createPathOk = YES;
if (![[NSFileManager defaultManager] fileExistsAtPath:[filePath stringByDeletingLastPathComponent] isDirectory:&createPathOk]) {
// 目录不存先创建
[[NSFileManager defaultManager] createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
}
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]){
// 文件不存在,直接创建文件并写入
[writeData writeToFile:filePath atomically:NO];
}else{
// NSFileHandle 用于处理文件内容
// 读取文件到上下文,并且是更新模式
NSFileHandle* fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
// 跳到文件末尾
[fileHandler seekToEndOfFile];
// 追加数据
[fileHandler writeData:writeData];
// 关闭文件
[fileHandler closeFile];
}
}
@end
打印记录日志的页面:
#import <UIKit/UIKit.h>
@interface ZMLogView : UIView
///初始化
+(instancetype)initLogView;
///打印日志信息
-(void)logInfo:(NSString *)str;
@end
#import "ZMLogView.h"
//大小尺寸
#define kLogViewFrame CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)
@interface ZMLogView()
///标签
@property(nonatomic,strong)UITextView *textView;
///标题
@property(nonatomic,strong)UILabel *titleLbl;
@end
@implementation ZMLogView{
CGRect _frame;
}
#pragma mark - 赋值
-(void)logInfo:(NSString *)str{
if (str) {
self.textView.text = str;
}else{
self.textView.text = @"暂无日志信息";
}
}
#pragma mark -Methods
#pragma mark - Intial
+(instancetype)initLogView{
return [[self alloc]initWithFrame:CGRectZero];
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder: aDecoder]) {
self.frame = kLogViewFrame;
[self setUpBaseData];
[self setUpUI];
}
return self;
}
-(instancetype)initWithFrame:(CGRect)frame{
frame = kLogViewFrame;
if (self = [super initWithFrame:frame]) {
[self setUpBaseData];
[self setUpUI];
}
return self;
}
///基本数据配置
-(void)setUpBaseData{
}
///控件添加
-(void)setUpUI{
[self textView];
}
#pragma mark - 布局
-(void)layoutSubviews{
[super layoutSubviews];
}
#pragma mark - lazyload
-(UILabel *)titleLbl{
if (!_titleLbl) {
_titleLbl = [UILabel new];
_titleLbl.frame = CGRectMake(10, 20, [UIScreen mainScreen].bounds.size.width - 20, 20);
_titleLbl.textAlignment = NSTextAlignmentCenter;
_titleLbl.textColor = [UIColor purpleColor];
if (@available(iOS 8.2, *)) {
_titleLbl.font = [UIFont systemFontOfSize:18 weight:1];
} else {
_titleLbl.font = [UIFont systemFontOfSize:18];
}
_titleLbl.text = @"打印日志";
[self addSubview:_titleLbl];
}
return _titleLbl;
}
-(UITextView *)textView{
if (!_textView) {
_textView = [UITextView new];
_textView.font = [UIFont systemFontOfSize:14];
_textView.textColor = [UIColor darkGrayColor];
CGRect frame = CGRectMake(10, CGRectGetMaxY(self.titleLbl.frame),[UIScreen mainScreen].bounds.size.width - 20, [UIScreen mainScreen].bounds.size.height - 50);
_textView.frame = frame;
_textView.showsHorizontalScrollIndicator = NO;
[_textView setEditable:NO];
[self addSubview:_textView];
}
return _textView;
}
-(void)dealloc{
}
@end
打印日志应用实例,在还没引入上传请求接口时,【LogManager.m】文件中可以先将请求接口其注释避免报错:
#import "LogManager.h"
#import "ZMLogView.h"
///显示日志
@property(nonatomic,strong)ZMLogView *logView;
- (void)viewDidLoad {
[super viewDidLoad];
[self testForLocalLog];
}
///写入数据到本地文件并显示
-(void)testForLocalLog{
//写入数据到本地文件
LLog(@"错误信息",@"五哦粗");
//获取日志信息并显示
NSString *str = [[LogManager sharedInstance] readFile:@"2018-09-11"];
NSLog(@"%@",str);
//渲染
[self.logView logInfo:str];
}
#pragma mark - lazyload
-(ZMLogView *)logView{
if (!_logView) {
_logView = [ZMLogView initLogView];
[self.view addSubview:_logView];
}
return _logView;
}
二、使用【AFNetworking】集成接口
1、get请求
/**
get请求
@param url 链接
@param params 参数
@param success 成功代码块
@param failure 失败代码块
*/
- (void)base_GetWithUrl:(NSString *)url parameters:(NSDictionary *)params success:(void(^)(id success))success failure:(void(^)(id failure))failure{
url = [NSString stringWithFormat:@"%@%@",kURLMain,url];
// 创建请求对象
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer]; //设置请求数据
manager.responseSerializer = [AFHTTPResponseSerializer serializer];//设置返回数据
//请求头设置
[ZMBaseEngine setHttpHeaderWithManager:manager];
// 设置超时时长
manager.requestSerializer.timeoutInterval = 10.0f;
//NSString *strUrl = [NSString stringWithFormat:@"%@%@",kURLMain,url];
[manager GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//json解析
NSDictionary *dic = [self jsonToDic:responseObject];
success(dic);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self handlingError:error failure:failure];
}];
}
2、Post请求
/**
post请求
@param url 链接
@param params 参数
@param success 成功代码块
@param failure 失败代码块
*/
- (void)base_PostWithUrl:(NSString *)url parameters:(id)params success:(void(^)(id success))success failure:(void(^)(id failure))failure{
url = [NSString stringWithFormat:@"%@%@",kURLMain,url];
// 创建请求对象
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer]; //设置请求数据
manager.responseSerializer = [AFHTTPResponseSerializer serializer];//设置返回数据
//请求头设置
[ZMBaseEngine setHttpHeaderWithManager:manager];
// 设置超时时长
manager.requestSerializer.timeoutInterval = 10.0f;
[manager POST:url parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//json解析
NSDictionary *dic = [self jsonToDic:responseObject];
success(dic);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self handlingError:error failure:failure];
}];
}
3、图片上传
/**
图片上传
@param imgData 图片data
@param fileName 图片名称
@param type 类型
@param url 链接
@param params 参数
@param progress 进度代码块
@param success 成功代码块
@param failure 失败代码块
*/
- (void)base_UpLoadImage:(NSData *)imgData fileName:(NSString *)fileName imgType:(NSString *)type PostWithURL:(NSString *)url parameters:(id)params progress:(void(^)(id progress))progress success:(void(^)(id success))success failure:(void(^)(id failure))failure{
url = [NSString stringWithFormat:@"%@%@",kURLMain,url];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 设置超时时长
manager.requestSerializer.timeoutInterval = 30.0f;
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer =[AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html",@"image/png",@"image/jpeg",nil];
[manager POST:url parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
//上传数据,域名为fileName
[formData appendPartWithFileData:imgData name:@"imageFile"fileName:fileName mimeType:[NSString stringWithFormat:@"image/%@",type]];
} progress:^(NSProgress * _Nonnull uploadProgress) {
progress(uploadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//json解析
NSDictionary *dic = [self jsonToDic:responseObject];
success(dic);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self handlingError:error failure:failure];
}];
}
4、文件上传
/**
上传文件
@param url 链接
@param params 参数
@param fileName 文件名
@param upName 上传文件名
@param filePath 路径
@param progress 进度代码块
@param success 成功代码块
@param failure 失败代码块
*/
- (void)base_UpdateFileWithUrl:(NSString*)url parameters:(NSMutableDictionary*)params fileName:(NSString*)fileName upName:(NSString *)upName filePath:(NSString *)filePath progress:(void(^)(id progress))progress success:(void(^)(id success))success failure:(void(^)(id failure))failure{
url = [NSString stringWithFormat:@"%@%@",kURLMain,url];
if (!upName) {
upName = fileName;
}
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//请求头设置
[ZMBaseEngine setHttpHeaderWithManager:manager];
[manager POST:url parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
/*
第一个参数:文件的URL路径
第二个参数:参数名称 file
第三个参数:在服务器上的名称
第四个参数:文件的类型
*/
if (filePath&&fileName) {
NSData*fileData = [NSData dataWithContentsOfFile:filePath];
// [formData appendPartWithFileURL:[NSURL fileURLWithPath:@""] name:fileName error:nil];
[formData appendPartWithFileData:fileData name:upName fileName:fileName mimeType:@"application/zip"];
}
} progress:^(NSProgress * _Nonnull uploadProgress) {
progress(uploadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//json解析
NSDictionary *dic = [self jsonToDic:responseObject];
success(dic);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self handlingError:error failure:failure];
}];
}
5、请求头/错误处理
///接口请求头
+(void)setHttpHeaderWithManager:(AFHTTPSessionManager *)manager{
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html", nil];
#pragma mark --- 请求头的设置自Header
//app应用相关信息的获取
NSDictionary *dicInfo = [[NSBundle mainBundle] infoDictionary];
//App版本
NSString *strAppVersion = [dicInfo objectForKey:@"CFBundleShortVersionString"];
//系统名称
NSString *strSysName =[[UIDevice currentDevice]systemName];
//系统版本
NSString *strSysVersion = [[UIDevice currentDevice]systemVersion];
//设备模式
NSString *strModel=[[UIDevice currentDevice]model];
//设备本地模式
NSString *strLocModel =[[UIDevice currentDevice]localizedModel];
//设备的平台
[manager.requestSerializer setValue:strSysName forHTTPHeaderField:@"platform"];
//设备的型号(模式)
[manager.requestSerializer setValue:[NSString stringWithFormat:@"model:%@\t locModel:%@",strModel,strLocModel] forHTTPHeaderField:@"type"];
//系统版本
[manager.requestSerializer setValue:strSysVersion forHTTPHeaderField:@"sysVersion"];
//App的版本
[manager.requestSerializer setValue:strAppVersion forHTTPHeaderField:@"appVersion"];
// --------- other test -----------
[manager.requestSerializer setValue:@"19644" forHTTPHeaderField:@"MEMBERID"];
[manager.requestSerializer setValue:@"1" forHTTPHeaderField:@"X-MUYAN-MECHINE"];
[manager.requestSerializer setValue:@"xx" forHTTPHeaderField:@"X-MUYAN-SIGN"];
[manager.requestSerializer setValue:@"43" forHTTPHeaderField:@"X-MUYAN-VERSION"];
}
#pragma mark - other methods
///接口请求错误处理
-(void)handlingError:(NSError * _Nonnull)error failure:(void(^)(id failure))failure{
// 取得错误信息
NSData *data = error.userInfo[@"com.alamofire.serialization.response.error.data"];
#if DEBUG
NSLog(@"%@",error.userInfo);
#else
#endif
NSString *strError = @"网络无连接:请检查!";
NSString *errDesStr = error.userInfo[@"NSLocalizedDescription"];
NSArray *arr = [errDesStr componentsSeparatedByString:@" "];
NSString *lastStr = arr.lastObject;
NSString *errCodeStr;
if ([self engineValidateStr:lastStr belongToStr:@"()1234567890."]) {
errCodeStr = [lastStr substringWithRange:NSMakeRange(1, lastStr.length - 2)]?:@"";
}else{
errCodeStr = lastStr;
}
//打印错误信息
NSString *dataStr = nil;
if (data.length > 0) {
dataStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
}else{
dataStr = @"错误日志为空";
}
NSString *errorResponse = error.userInfo[@"com.alamofire.serialization.response.error.response"];
NSString *errorUrl = error.userInfo[@"NSErrorFailingURLKey"];
NSString *errorStr = [NSString stringWithFormat:@"--------- 开始 --------<br><br>NSErrorFailingURLKey:<br>%@<br><br>NSLocalizedDescription:<br>%@<br><br>com.alamofire.serialization.response.error.response:<br>%@<br><br>com.alamofire.serialization.response.error.data:<br>%@<br><br>--------- 结束 --------",errorUrl,errDesStr,errorResponse,dataStr];
data = [errorStr dataUsingEncoding:NSUTF8StringEncoding];
[self openHtmlError:data target:[self engineGetCurrentVC]];
NSDictionary *dic = @{Req_resultCode:errCodeStr,Req_errorMessage:strError,Req_errorData:data?:@""};
// 回调失败代码块
failure(dic);
}
- (NSDictionary *)jsonToDic:(NSData *)jsonData{
NSError *error = nil;
NSData *data = jsonData;
NSDictionary *dicData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
if (error) {
return @{};
} else {
return dicData;
}
}
/**
判断某个字符是否属于设置字符串
@param str 字符
@param toStr 字符串
*/
- (BOOL)engineValidateStr:(NSString*)str belongToStr:(NSString *)toStr{
BOOL res = YES;
NSCharacterSet* tmpSet = [NSCharacterSet characterSetWithCharactersInString:toStr];
int i = 0;
while (i < str.length) {
NSString * string = [str substringWithRange:NSMakeRange(i, 1)];
NSRange range = [string rangeOfCharacterFromSet:tmpSet];
if (range.length == 0) {
res = NO;
break;
}
i++;
}
return res;
}
///打印错误信息
-(void)openHtmlError:(NSData *)data target:(UIViewController *)target{
//#ifdef Debug
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (str&&str.length>0) {
ZMWKWebViewVC *vc = [ZMWKWebViewVC new];
[vc.wkWebView loadHTMLString:str baseURL:[NSURL URLWithString:@"www://baidu.com"]];
[target presentViewController:vc animated:YES completion:nil];
}else{
}
//#else
//#endif
}
///获取当前屏幕显示的viewcontroller
- (UIViewController *)engineGetCurrentVC{
// 定义一个变量存放当前屏幕显示的viewcontroller
UIViewController *result = nil;
// 得到当前应用程序的关键窗口(正在活跃的窗口)
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
// windowLevel是在 Z轴 方向上的窗口位置,默认值为UIWindowLevelNormal
if (window.windowLevel != UIWindowLevelNormal)
{
// 获取应用程序所有的窗口
NSArray *windows = [[UIApplication sharedApplication] windows];
for(UIWindow * tmpWin in windows)
{
// 找到程序的默认窗口(正在显示的窗口)
if (tmpWin.windowLevel == UIWindowLevelNormal)
{
// 将关键窗口赋值为默认窗口
window = tmpWin;
break;
}
}
}
// 获取窗口的当前显示视图
UIView *frontView = [[window subviews] objectAtIndex:0];
// 获取视图的下一个响应者,UIView视图调用这个方法的返回值为UIViewController或它的父视图
id nextResponder = [frontView nextResponder];
// 判断显示视图的下一个响应者是否为一个UIViewController的类对象
if ([nextResponder isKindOfClass:[UIViewController class]]) {
result = nextResponder;
} else {
result = window.rootViewController;
}
return result;
}
三、错误h5集成
在请求接口报错时,会直接调用该类方法
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
@interface ZMWKWebViewVC : UIViewController
@property (nonatomic, strong) WKWebView *wkWebView;
-(void)zm_WKLoadUrl:(NSString *)url;
@end
@interface ZMWKWebViewVC ()<WKUIDelegate,WKNavigationDelegate>
@property (nonatomic, strong) UIProgressView *progressView;
///错误标题
@property(nonatomic,strong)UILabel *titleLbl;
///退出按钮
@property(nonatomic,strong)UIButton *dismissBtn;
@end
@implementation ZMWKWebViewVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
//进度条初始化
self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0, 20, [[UIScreen mainScreen] bounds].size.width, 2)];
_progressView.backgroundColor = [UIColor blueColor];
//设置进度条的高度,下面这句代码表示进度条的宽度变为原来的1倍,高度变为原来的1.5倍.
_progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
[self.view addSubview:_progressView];
[self dismissBtn];
}
-(void)zm_WKLoadUrl:(NSString *)url{
[self.wkWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"estimatedProgress"]) {
_progressView.progress = self.wkWebView.estimatedProgress;
if (_progressView.progress == 1) {
/*
*添加一个简单的动画,将progressView的Height变为1.4倍,在开始加载网页的代理中会恢复为1.5倍
*动画时长0.25s,延时0.3s后开始动画
*动画结束后将progressView隐藏
*/
[UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^{
_progressView.transform = CGAffineTransformMakeScale(1.0f, 1.4f);
} completion:^(BOOL finished) {
_progressView.hidden = YES;
}];
}
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
//开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
//开始加载网页时展示出progressView
_progressView.hidden = NO;
//开始加载网页的时候将progressView的Height恢复为1.5倍
_progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
//防止progressView被网页挡住
[self.view bringSubviewToFront:_progressView];
}
//加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
//加载完成后隐藏progressView
_progressView.hidden = YES;
}
//加载失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
//加载失败同样需要隐藏progressView
//_progressView.hidden = YES;
}
static NSString *static_url = @"";
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
NSLog(@"%@",webView.URL.absoluteString);
decisionHandler(WKNavigationActionPolicyAllow);
}
- (void)dealloc {
[self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress"];
}
//设备宽高
#define kWebIphone_W [UIScreen mainScreen].bounds.size.width
#define kWebIphone_H [UIScreen mainScreen].bounds.size.height
#pragma mark - lazyload
- (WKWebView *)wkWebView{
if (_wkWebView == nil){
//创建网页配置对象
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 创建设置对象
WKPreferences *preference = [[WKPreferences alloc]init];
// 设置字体大小(最小的字体大小)
preference.minimumFontSize = 40;
// 设置偏好设置对象
config.preferences = preference;
// 创建WKWebView
_wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0,CGRectGetMaxY(self.titleLbl.frame), kWebIphone_W, kWebIphone_H - CGRectGetMaxY(self.titleLbl.frame) - 10) configuration:config];
_wkWebView.scrollView.bounces = NO;
_wkWebView.UIDelegate = self;
_wkWebView.navigationDelegate = self;
[self.view addSubview:_wkWebView];
[self.wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
return _wkWebView;
}
-(UILabel *)titleLbl{
if (!_titleLbl) {
_titleLbl = [UILabel new];
_titleLbl.frame = CGRectMake(0, 20, kWebIphone_W, 30);
_titleLbl.textColor = [UIColor purpleColor];
_titleLbl.textAlignment = NSTextAlignmentCenter;
_titleLbl.text = @"错误信息";
if (@available(iOS 8.2, *)) {
_titleLbl.font = [UIFont systemFontOfSize:18 weight:1.0];
} else {
_titleLbl.font = [UIFont systemFontOfSize:18];
}
[self.view addSubview:_titleLbl];
UILabel *lineLbl = [UILabel new];
lineLbl.frame = CGRectMake(_titleLbl.bounds.origin.x,CGRectGetMaxY(_titleLbl.bounds) - 0.5, _titleLbl.bounds.size.width, 0.5);
lineLbl.backgroundColor = [UIColor lightGrayColor];
[_titleLbl addSubview:lineLbl];
}
return _titleLbl;
}
-(UIButton *)dismissBtn{
if (!_dismissBtn) {
_dismissBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_dismissBtn.frame = CGRectMake(10, CGRectGetMinY(self.titleLbl.frame), 50, 20);
_dismissBtn.layer.masksToBounds = YES;
_dismissBtn.layer.cornerRadius = 5;
_dismissBtn.titleLabel.font = [UIFont systemFontOfSize:14];
[_dismissBtn setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal];
[_dismissBtn setTitle:@"返回" forState:UIControlStateNormal];
[_dismissBtn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_dismissBtn];
}
return _dismissBtn;
}
-(void)btnAction:(UIButton *)sender{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
参考链接