【iOS开发】iOS10 Log调试小工具

一个解决iOS10在发布环境下无法查看调试log的小工具

出发点

由于iOS10系统,在发布环境下(打成ipa包安装测试或者发布之后从App Store下载安装的包),使用Xcode已经无法查看我们自己打印的log。所以就做了一个小工具,查看log,便于调试。
楼主的需求是,在安装了APP之后查看log,验证程序是否正常运行。除了在调试的时候用到,在线上包出问题之后,也能通过查看log来定位问题。

效果

楼主将该工具分成了3种模式

  • 不显示log模式
    这个是默认的模式。默认初始化之后是不显示任何log的。也就是说不做任何操作。
  • Xcode控制台显示log模式
    这个模式只在Xcode控制台显示log。这就是我们iOS10出来之前用的模式。通过控制台查看log。但是由于iOS10的时候,真机查看log时候会冒出一大坨没用的log,然后我们自己的log又看不见(好像Xocde8.2.1可以看见,但是还是会有很多无用的log),所以在iOS10下这个模式不好用
  • 将log写入到文件并显示(悬浮窗模式)
    这个模式将log显示在Xcode控制台,同时将log写入到沙盒中。我们可以通过读取沙盒的log文件来查看log。并且log文件还可以通过邮件、QQ、微信等方式发送。这个模式就是我们这个小工具主要实现的功能了。值的一说的是,这个模式我们会有一个悬浮窗,用来随时查看沙盒的log文件内容。

废话少说,上图:

悬浮窗模式
预览界面
分享界面
  • 第一幅图就是悬浮窗模式。图中为浮窗展开时的界面。3个按钮分别对应着上面说的悬浮窗模式、Xcode控制台显示log模式、不显示log模式。
  • 第二幅图是点击在浮窗上的【预览】之后显示的界面,这些就是我们在程序中打印的log。
  • 第三幅图是点击第二幅图右上角的分享之后的界面。如果手机安装了QQ微信Facebook等软件,可以通过这些软件来分享这个log文件。
效果示例
  • 默认情况下,我们的小工具什么都不做。
  • 当我们手动触发某个开关之后,悬浮窗就显示出来。并自动在沙盒创建log文件,之后就可以使用log写入功能。
  • 悬浮窗是可以一直漂浮在APP的上方的,便于我们随时点击浮窗,查看log
  • 点击悬浮窗上的【预览】按钮就可以以界面的形式显示沙盒文件的log,并且可以选择已邮件QQ微信等形式发送log文件
  • 点击悬浮窗上的【Xcode】按钮,悬浮窗会消失,并且只会在Xcode控制台显示log。不会写入到文件。
  • 点击悬浮窗上的【关闭】按钮,悬浮窗会消失,并且log不会再打印。
  • 重启APP之后,会根据之前的模式来决定是否显示浮窗和打印log。

使用

//需要导入头文件  #import "SQLogToolManager.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
    [[SQLogToolManager shareManager] logIntial];
    return YES;
}
  • 在应用的某个界面上设置一个开关,点击这个开关就将log工具设置到悬浮窗模式(楼主设置的是在登录界面上连续点击10次就触发方法)
    代码示例:
- (IBAction)onClickWriteToTextAndShowFloatWindow:(UIButton *)sender
{
    [SQLogToolManager shareManager].logLevel = SQLogToolManagerLevelText;
}
  • 在需要打印Log的地方使用 NSLogD()来替换平常用的NSLog()
    代码示例:
- (IBAction)onClickTest:(UIButton *)sender
{
    for (int i = 0; i < 10; ++i)
    {
        NSLogD(@"这里是测试。%@:%d",@"第一个参数",i);
    }
}

不足之处&需改进的地方

这个只是一个小工具,所以只做了几个我认为比较重要的功能。。主要缺陷:

  • 界面长得不是很好看(管他呢)
  • 不能显示系统的log。只能显示使用NSLogD()方法打印的log。也就是说,苹果的警告log无法通过这个小工具得到。(当前,这也避免了iOS10上那些恶心的无用log)
  • 无法显示崩溃log。楼主认为这个功能不是那么重要,就没做了。

项目解析

介绍下项目用到的几个类和主要的技术点

项目文件结构
  • SQFloatWindow 悬浮窗功能类。
    继承UIWindow,将windowLevel设置为UIWindowLevelAlert + 1 来使window浮窗悬浮在APP上方。
        UIWindow *preKeyWindow = [UIApplication sharedApplication].keyWindow;
        
        self.backgroundColor = [UIColor clearColor];
        self.windowLevel = UIWindowLevelAlert + 1;
        self.rootViewController = [UIViewController new];
        [self makeKeyAndVisible];
        
        //还回keyWindow
        if (preKeyWindow) {
            [preKeyWindow makeKeyWindow];
        }

值得注意的是,我们的悬浮窗不需要作为keyWindow(不然会带来很多麻烦的),所以需要将keyWindow还给之前的window。

给悬浮窗加了一个主按钮和若干个子按钮,用来点击

//添加按钮
[self setupButtons];
//主按钮
[self setupMainBtnWithName:mainBtnName];

定义一个block属性来处理子按钮的点击事件,具体的子按钮由按钮的tag来区分

/**
 点击事件block
 */
@property (nonatomic,copy) void(^clickBlocks)(NSInteger i);

浮窗还做了一些点击展开/收回,拖拽,靠边隐藏 等操作,具体请查看源代码

  • SQLog log打印和写入文件工具类。
    当调用 SQLogD(...)时,会将log显示在Xcode控制台,并且将log写入到沙盒文件中。
//目前只用到了SQLogD
//Debug
#define SQLogD(...) [SQLog logD:[NSString stringWithFormat:__VA_ARGS__],@""];

调用logIntial初始化方法时,会在沙盒创建log.txt文件(如果已创建就清空文件内容)。

当调用SQLogD(...)打印log时,会创建一个异步的串行队列来进行打印并写入log。

+ (void)logvLevel:(SQLogLevel)level Format:(NSString *)format VaList:(va_list)args
{
    __block NSString *formatTmp = format;
    
    dispatch_async(k_operationQueue, ^{//异步串行队列
        
        if (level >= LogLevel)//只有大于当前LogLevel级别的log才会进行操作
        {
            formatTmp = [[SQLog SQLogFormatPrefix:level] stringByAppendingString:formatTmp];
            //写入log文件的同时,在控制台打印出来
            NSLog(@"%@",formatTmp);
            
            NSString *contentStr = [[NSString alloc] initWithFormat:formatTmp arguments:args];
            NSString *contentN = [contentStr stringByAppendingString:@"\n"];
            NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
            [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
            
            //设置为北京时间
            NSTimeZone *timeZone = [NSTimeZone timeZoneForSecondsFromGMT:8];//直接指定时区,这里是东8区
            NSInteger seconds = [timeZone secondsFromGMTForDate: [NSDate date]];
            NSDate *beiJingDate = [NSDate dateWithTimeInterval: seconds sinceDate: [NSDate date]];
            
            //这里是最终打印出来的字符串,可以根据需要加一些参数进去
            NSString *content = [NSString stringWithFormat:@"%@ %@", [dateFormatter stringFromDate:beiJingDate], contentN];
            
            //使用NSFileHandle来写入数据
            NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:logFilePath];
            [file seekToEndOfFile];
            [file writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
            [file closeFile];
            
            formatTmp = nil;
        }
        
    });
}

  • SQLogToolManager 本小工具的管理类。用来整合SQLog和SQFloatWindow 并处理对外的接口和使用逻辑。
#import <Foundation/Foundation.h>
#import "SQLog.h"

/**
 log显示宏定义。
 
 @param ... 可变参数,跟NSLog一致
 */
#define NSLogD(...) do{\
                        switch ([SQLogToolManager shareManager].logLevel) {\
                            case SQLogToolManagerLevelNone:{} break;\
                            case SQLogToolManagerLevelLog:{\
                                NSLog(__VA_ARGS__);} break;\
                            case SQLogToolManagerLevelText:{\
                                SQLogD(__VA_ARGS__);} break;\
                            default: break;}\
                    } while (0);


typedef enum
{
    SQLogToolManagerLevelNone = 0,     //不打印log
    SQLogToolManagerLevelLog = 1,      //只在控制台显示log
    SQLogToolManagerLevelText = 2      //在控制台显示log及在本地写入log
} SQLogToolManagerLevel;

@interface SQLogToolManager : NSObject

/**
 logLevel
 */
@property (nonatomic, assign) SQLogToolManagerLevel logLevel;

/**
 单例
 */
+ (instancetype)shareManager;

/**
 初始化
 */
- (void)logIntial;

@end

NSLogD(...)宏定义中,使用logLevel属性来控制log的类型。用户改变了logLevel就改变了打印的类型。

悬浮窗子按钮的点击事件,分为三个。【预览】,【Xcode】和【关闭】。

//点击事件处理
        _floatWindow.clickBolcks = ^(NSInteger i){
            
            switch (i)
            {
                case 0:
                {
                    //显示沙盒本地log
                    [weakSelf displayLocalLog];
                }
                    break;
                case 1:
                {
                    //Xcode显示log
                    weakSelf.logLevel = SQLogToolManagerLevelLog;
                }
                    break;
                case 2:
                {
                    //隐藏log
                    weakSelf.logLevel = SQLogToolManagerLevelNone;
                }
                    break;
                default:
                    break;
            }
            
        };

其中,【Xcode】【关闭】只是设置了logLevel,而【预览】使用了iOS提供的预览和分享的类UIDocumentInteractionController

//显示沙盒本地log
- (void)displayLocalLog
{
    if (![SQLog getLogFilePath])
    {
        return;
    }
    
    //由文件路径初始化UIDocumentInteractionController
    UIDocumentInteractionController *documentInteractionController  = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:[SQLog getLogFilePath]]];
    self.documentInteractionController  = documentInteractionController ;
    documentInteractionController.delegate = self;
    
//    //显示分享文档界面
//    [documentInteractionController  presentOptionsMenuFromRect:[UIScreen mainScreen].bounds inView:[UIApplication sharedApplication].keyWindow.rootViewController.view animated:YES];
    
    //直接显示预览界面
    [documentInteractionController  presentPreviewAnimated:YES];
}

#pragma mark - UIDocumentInteractionControllerDelegate

//在哪个控制器显示预览界面
-(UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller
{
    return [UIApplication sharedApplication].keyWindow.rootViewController;
}

参考

谦言万语

蓝天白云,嫩草明湖,小屋人家,大雁南飞……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,412评论 25 707
  • 在店里待了一整个上午,是又没生意也没什么客人,那些朋友也都不知道跑哪去了,我算体验了“开店容易守店难!”了。...
    爱情味阅读 306评论 0 1
  • 时间: 2017年11月18日 地点:天津豆丁公寓 作者:阮博杰 1.我怎么如此幸运,跟大家在一起的感觉依然那么地...
    阮博杰阅读 236评论 0 0
  • 如何站在不同的角度看待问题?当用不同的思维考虑问题,你会获得很大的收获。当我是做推广专员时,我天天只知道关键词,出...
    sherrytx阅读 251评论 0 0