iOS开发:日志记录及AFNetworking请求

简介: 本篇文章主要目的是为了将用户操作习惯记录到本地文件,然后定期或者根据实际需要打包压缩上传到服务器,用以处理用户在闪退的时候,或需要详细了解具体某个用户在这一段时间的操作习惯。由于要压缩上传本地日志,顺道集成了AFNetWorking了post和get的接口请求,以及请求是接口失败后,错误信息显示,这个在开发的时候特别方便,后台可以在根据这些错误日志查询对应的问题。

目录


简介

一、日志记录集成[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


参考链接


1、本地日志记录

2、关于AFNetworking3.0+的使用



相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
小程序 前端开发 API
微信小程序全栈开发中的异常处理与日志记录
【4月更文挑战第12天】本文探讨了微信小程序全栈开发中的异常处理和日志记录,强调其对确保应用稳定性和用户体验的重要性。异常处理涵盖前端(网络、页面跳转、用户输入、逻辑异常)和后端(数据库、API、业务逻辑)方面;日志记录则关注关键操作和异常情况的追踪。实践中,前端可利用try-catch处理异常,后端借助日志框架记录异常,同时采用集中式日志管理工具提升分析效率。开发者应注意安全性、性能和团队协作,以优化异常处理与日志记录流程。
|
2月前
|
API 开发工具 Android开发
iOS 和 Android 平台的开发有哪些主要区别?
iOS与Android开发区别:iOS用Objective-C/Swift,App Store唯一下载渠道;Android用Java/Kotlin,多商店发布(如Google Play、华为市场)。设计上,iOS简洁一致,Android灵活可定制。开发工具,iOS用Xcode,Android用Android Studio。硬件和系统多样性,iOS统一,Android复杂。权限管理、审核流程及API各有特点,开发者需依据目标平台特性进行选择。
38 3
|
13天前
|
前端开发 Android开发 iOS开发
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
【4月更文挑战第30天】Flutter 框架实现跨平台移动应用,通过一致的 UI 渲染(Skia 引擎)、热重载功能和响应式框架提高开发效率和用户体验。然而,Android 和 iOS 的系统差异、渲染机制及编译过程影响性能。性能对比显示,iOS 可能因硬件优化提供更流畅体验,而 Android 更具灵活性和广泛硬件支持。开发者可采用代码、资源优化和特定平台优化策略,利用性能分析工具提升应用性能。
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
|
14天前
|
存储 Swift iOS开发
使用Swift开发一个简单的iOS应用的详细步骤。
使用Swift开发iOS应用的步骤包括:创建Xcode项目,设计界面(Storyboard或代码),定义数据模型,实现业务逻辑,连接界面和逻辑,处理数据存储(如Core Data),添加网络请求(必要时),调试与测试,根据测试结果优化改进,最后提交至App Store或其它平台发布。
32 0
|
14天前
|
安全 Swift iOS开发
【Swift 开发专栏】Swift 与 UIKit:构建 iOS 应用界面
【4月更文挑战第30天】本文探讨了Swift和UIKit在构建iOS应用界面的关键技术和实践方法。Swift的简洁语法、类型安全和高效编程模型,加上与UIKit的紧密集成,使开发者能便捷地创建用户界面。UIKit提供视图、控制器、布局、动画和事件处理等功能,支持灵活的界面设计。实践中,遵循设计原则,合理组织视图层次,运用布局和动画,以及实现响应式设计,能提升界面质量和用户体验。文章通过登录、列表和详情界面的实际案例展示了Swift与UIKit的结合应用。
|
14天前
|
存储 安全 Swift
【Swift 开发专栏】使用 Swift 开发一个简单的 iOS 应用
【4月更文挑战第30天】本文介绍了使用 Swift 开发简单 iOS 待办事项应用的步骤。首先,阐述了 iOS 开发的吸引力及 Swift 语言的优势。接着,详细说明了应用的需求和设计,包括添加、查看和删除待办事项的功能。开发步骤包括创建项目、界面搭建、数据存储、功能实现,并提供了相关代码示例。最后,强调了实际开发中需注意的细节和优化,旨在帮助初学者掌握 Swift 和 iOS 开发基础。
|
18天前
|
存储 Java 物联网
SpringBoo利用 MDC 机制过滤出单次请求相关的日志
SpringBoo利用 MDC 机制过滤出单次请求相关的日志
|
22天前
|
iOS开发 开发者 UED
利用SwiftUI构建动态列表:iOS开发的新范式
【4月更文挑战第22天】在本文中,我们将深入探讨如何使用SwiftUI来创建动态列表。SwiftUI是苹果最新推出的用户界面工具集,它允许开发者以声明式的方式描述用户界面,从而简化了代码的复杂性。我们将通过具体的代码实例,展示如何利用SwiftUI的List和ForEach视图来创建动态列表,并讨论其在实际开发中的应用。
20 2
|
26天前
|
API 定位技术 iOS开发
IOS开发基础知识:什么是 Cocoa Touch?它在 iOS 开发中的作用是什么?
【4月更文挑战第18天】**Cocoa Touch** 是iOS和Mac OS X应用的核心框架,包含面向对象库、运行时系统和触摸优化工具。它提供Mac验证的开发模式,强调触控接口和性能,涵盖3D图形、音频、网络及设备访问API,如相机和GPS。是构建高效iOS应用的基础,对开发者至关重要。
21 0
|
1月前
|
搜索推荐 iOS开发 开发者
利用SwiftUI构建动态用户界面:iOS开发新篇章
【4月更文挑战第10天】在移动应用的世界中,流畅的用户体验和引人注目的界面设计是至关重要的。随着SwiftUI的推出,iOS开发者被赋予了创造高度动态且响应式界面的能力。本文将深入探讨如何利用SwiftUI的强大特性来实现一个动态用户界面,包括其声明性语法、状态绑定以及视图更新机制。我们将通过一个天气应用案例,了解如何有效地运用这些工具来提升应用的交互性和视觉吸引力。