NSLog打印到屏幕小工具

需求

主要考虑到测试员测试app时候发现bug,但是不知道是移动端的bug还是后台数据的bug。如果可以把数据实时的打印到屏幕上方便测试员看到。测试员分析数据即可知道是哪边出现的问题。也方便把测试机给后台伙伴。方便后台伙伴观看数据入参出参,以便更容易发现问题。

效果

1. 日志按钮有拖动功能,防止遮挡屏幕的主要部分。

2. 长按日志按钮,清除文字。

3. 点击日志按钮弹出或者消失文字窗口,方便测试与查看日志的切换。

4. github地址:https://github.com/FreeBaiShun/BSLogWindow

用法

1. pod 'BSLogWindow'

2. 编写代码

#import "BSLogWindow.h"

#define ShowLogWindow 1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    self.window = [UIWindow new];
    self.window.rootViewController = [ViewController new];
    [self.window makeKeyAndVisible];

    #if DEBUG && ShowLogWindow
    BSLogWindow *logWindow = [BSLogWindow sharedInstance];
    [logWindow setHidden:NO];
    logWindow.printBlock = ^(NSString *str) {
  
    };
    #endif

    return YES;
}

代码分析

1. 代码入口

- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:CGRectMake(0, 20, BSLogWindowSereenWidth, BSLogWindowSereenHeight/2.0+BSLogWindowNAVTOP)];
    if (self) {
        if ([UIApplication sharedApplication].keyWindow) {
            [[UIApplication sharedApplication].keyWindow addObserver:self forKeyPath:@"rootViewController" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
            [self setUpUI];
            [self redirectSTD:STDERR_FILENO];
        }
    }
    
    return self;
}
  1. 如果window存在就完成UI的初始化以及NSLog的处理
  2. 添加window的KVO监听rootViewController属性,考虑到app中途改变rootViewController导致视图销毁问题。

2. UI初始化部分   

  1. 首先初始化self自身
  2. 完成textView初始化
  3. 接下来利用三方WMDragView初始化viewWindowBtn
  4. 添加日志按钮的点击事件,以及长按事件

3. NSLog部分的处理

  1. 编写方法redirectSTD
  2. 该方法创建了NSPipe,初始化读pipeReadHandle,写pipeFileHandle,再利用dup2函数把fd与pipeFileHandle进行重定向。这样在用NSLog时候就会重定向到pipeFileHandle,就会想NSPipe对象写。之后监听pipeReadHandle读端口即可。
  3. 这样当我们用NSLog时候其实就会向NSPipe的写端口写数据,这时候我们就会监听到读端口有数据流出。
- (void)redirectSTD:(int)fd{
    NSPipe * pipe = [NSPipe pipe];
    NSFileHandle *pipeReadHandle = [pipe fileHandleForReading];
    int pipeFileHandle = [[pipe fileHandleForWriting] fileDescriptor];
    dup2(pipeFileHandle, fd);
    [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(redirectNotificationHandle:)name:NSFileHandleReadCompletionNotification
                                                   object:pipeReadHandle];
    [pipeReadHandle readInBackgroundAndNotify];
}

4. 之后在监听到数据后读出数据,并用printf函数把数据打印到控制台(不能用NSLog,会造成死循环)。然后调用[BSLogWindow printLog:str];方法完成界面上textView的文字输出。最后进行文本的输出回调。这个回调可以拦截到NSLog的内容,这些内容可以写在文件里,或者上传到服务器,或者是存到数据库等等。

- (void)redirectNotificationHandle:(NSNotification *)nf{
    NSData *data = [[nf userInfo] objectForKey:NSFileHandleNotificationDataItem];
     NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        printf("%s", [str UTF8String]);
        [BSLogWindow printLog:str];
    if(self.printBlock){
        self.printBlock(str);
    }
    [[nf object] readInBackgroundAndNotify];
}

4. 屏幕输出部分

  1. printLog方法的编写

  2. 该方法使用@synchronized (self)进行加锁处理避免了多线程访问self防止数据竞争问题。限制做多打印最新的20条日志信息。

    - (void)printLog:(NSString*)newLog {
        if (newLog.length == 0) {
            return;
        }
        
        @synchronized (self) {
            newLog = [NSString stringWithFormat:@"%@\n", newLog]; // add new line
            BSLogModel* log = [BSLogModel logWithStr:newLog];
            
            // data
            if (!log) {
                return;
            }
            [self.logsArrM addObject:log];
            if (self.logsArrM.count > 20) {
                [self.logsArrM removeObjectAtIndex:0];
            }
            
            // view
            [self refreshLogDisplay];
        }
    }
  3. refreshLogDisplay方法的编写

  4. 这里判断最后两天数据显示为黄色,其他数据显示为白色。这里主要考虑到如果是网络请求,有入参有出参情况。

    - (void)refreshLogDisplay {
        // attributed text
        NSMutableAttributedString* attributedString = [NSMutableAttributedString new];
        
        [self.logsArrM enumerateObjectsUsingBlock:^(BSLogModel *log, NSUInteger idx, BOOL * _Nonnull stop) {
            if (log.strLog.length == 0) {
                return;
            }
            
            NSMutableAttributedString* logString = [[NSMutableAttributedString alloc] initWithString:log.strLog];
            UIColor* logColor = (idx == self.logsArrM.count - 1 || idx == self.logsArrM.count - 2) ? [UIColor yellowColor] : [UIColor whiteColor]; // yellow if new, white if more than 0.1 second ago
            [logString addAttribute:NSForegroundColorAttributeName value:logColor range:NSMakeRange(0, logString.length)];
            
            [attributedString appendAttributedString:logString];
        }];
        
        self.textView.attributedText = attributedString;
        
        // scroll to bottom
        if(attributedString.length > 0) {
            NSRange bottom = NSMakeRange(attributedString.length - 1, 1);
            [self.textView scrollRangeToVisible:bottom];
        }
    }

    参考:https://blog.csdn.net/wtdask/article/details/81906236

猜你喜欢

转载自blog.csdn.net/qq_17190231/article/details/83097678
今日推荐