iOS输出重定向

stdout和stderr

我们知道iOS上的日志打印函数有printf和NSLog两种,其中printf对应标准输出stdoutNSLog对应标准错误stderr
在默认情况下,stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出。举例来说就是fprintf(stdout, "xxxx")fprintf(stdout, "xxxx\n"),前者不会立即输出,直到遇到新行才会一起输出。而fprintf(stderr, "xxxxx"),不管有没有\n都输出。这也是为什么使用NSLog不需要在最后添加\n的原因。

输出重定向

在iOS上,Debug时stdoutstderr默认都是直接输出到Console, 非Debug时会保存在Systemlog里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#define stdout __stdoutp
#define stderr __stderrp
extern FILE *__stdoutp;
extern FILE *__stderrp;
typedef struct __sFILE {
unsigned char *_p; /* current position in (some) buffer */
int _r; /* read space left for getc() */
int _w; /* write space left for putc() */
short _flags; /* flags, below; this FILE is free if 0 */
short _file; /* fileno, if Unix descriptor, else -1 */
struct __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */
int _lbfsize; /* 0 or -_bf._size, for inline putc */
/* operations */
void *_cookie; /* cookie passed to io functions */
int (* _Nullable _close)(void *);
int (* _Nullable _read) (void *, char *, int);
fpos_t (* _Nullable _seek) (void *, fpos_t, int);
int (* _Nullable _write)(void *, const char *, int);
/* separate buffer for long sequences of ungetc() */
struct __sbuf _ub; /* ungetc buffer */
struct __sFILEX *_extra; /* additions to FILE to not break ABI */
int _ur; /* saved _r when _r is counting ungetc data */
/* tricks to meet minimum requirements even when malloc() fails */
unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */
unsigned char _nbuf[1]; /* guarantee a getc() buffer */
/* separate buffer for fgetln() when line crosses buffer boundary */
struct __sbuf _lb; /* buffer for fgetln() */
/* Unix stdio files get aligned to block boundaries on fseek() */
int _blksize; /* stat.st_blksize (may be != _bf._size) */
fpos_t _offset; /* current lseek offset (see WARNING) */
} FILE;

以上是 stdoutstderr 在iOS中的定义,FILE这个结构包含了文件操作的基本属性,对文件的操作都要通过这个结构的指针来进行。其中_file是文件描述符,stdoutstderr的文件描述符已经在系统中定义:

1
2
#define STDOUT_FILENO 1 /* standard output file descriptor */
#define STDERR_FILENO 2 /* standard error file descriptor */

输出到文件

使用freopen函数将输出重定向到文件。freopen详细介绍可见http://www.cplusplus.com/reference/cstdio/freopen/
具体实现如下:

1
2
3
4
5
NSString *documentpath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [NSString stringWithFormat:@"%@.log",[NSDate date]];
NSString *logFilePath = [documentpath stringByAppendingPathComponent:fileName];
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);

输出到UITextView

1、通过NSPipe创建一个管道,pipe有读端和写端。
2、通过dup2将标准输入重定向到pipe的写端。
3、通过NSFileHandle监听pipe的读端,然后将读出的信息显示在UITextview上。

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)redirectNotificationHandle:(NSNotification *)nf{
    NSData *data = [[nf userInfo] objectForKey:NSFileHandleNotificationDataItem];
    NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    textView.text = [NSString stringWithFormat:@"%@\n%@",textView.text, str];
    NSRange range;
    range.location = [textView.text length] - 1;
    range.length = 0;
    [textView scrollRangeToVisible:range];
    
    [[nf object] readInBackgroundAndNotify];
}
- (void)redirectSTD:(int )fd{
    NSPipe * pipe = [NSPipe pipe] ;
    NSFileHandle *pipeReadHandle = [pipe fileHandleForReading] ;
    dup2([[pipe fileHandleForWriting] fileDescriptor], fd) ;
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(redirectNotificationHandle:) name:NSFileHandleReadCompletionNotification object:pipeReadHandle] ;
    [pipeReadHandle readInBackgroundAndNotify];
}
[self redirectSTD:STDOUT_FILENO];
[self redirectSTD:STDERR_FILENO];

本文作者: ctinusdev
本文链接: https://ctinusdev.github.io/2016/04/01/PrintRedirect/
转载请注明出处!

坚持原创技术分享,您的支持将鼓励我继续创作!