如何将标准输出重定向到NSTextView?

8

请问有人能向我展示如何将Stdout重定向到NSTextView吗?

另外,NSLog打印的信息是否属于std?

谢谢

4个回答

14
下面的代码使用dup2将stdout插入到NSPipe对象的写入端口。读取端口通过GCD调度源进行观察,该调度源从管道中读取数据并将其附加到文本视图中。
NSPipe* pipe = [NSPipe pipe];
NSFileHandle* pipeReadHandle = [pipe fileHandleForReading];
dup2([[pipe fileHandleForWriting] fileDescriptor], fileno(stdout));
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [pipeReadHandle fileDescriptor], 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_event_handler(source, ^{
    void* data = malloc(4096);
    ssize_t readResult = 0;
    do
    {
        errno = 0;
        readResult = read([pipeReadHandle fileDescriptor], data, 4096);
    } while (readResult == -1 && errno == EINTR);
    if (readResult > 0)
    {
        //AppKit UI should only be updated from the main thread
        dispatch_async(dispatch_get_main_queue(),^{
            NSString* stdOutString = [[NSString alloc] initWithBytesNoCopy:data length:readResult encoding:NSUTF8StringEncoding freeWhenDone:YES];
            NSAttributedString* stdOutAttributedString = [[NSAttributedString alloc] initWithString:stdOutString];
            [self.logView.textStorage appendAttributedString:stdOutAttributedString];
        });
    }
    else{free(data);}
});
dispatch_resume(source);
NSLog(@"...") 输出的不是 stdout,而是打印到了 stderr。如果你想将其重定向到你的文本视图中,请更改
dup2([[pipe fileHandleForWriting] fileDescriptor], fileno(stdout));
转化为
dup2([[pipe fileHandleForWriting] fileDescriptor], fileno(stderr));

这可能是一个愚蠢的问题,但是:如果NSPipe已经被缓冲(由其自己的实现或者由它包装的操作系统构造),那么为什么需要定义自己的数据缓冲区呢? - algal
你是对的,NSPipe默认是带缓冲区的(根据文档:“...管道是带缓冲区的;缓冲区的大小由底层操作系统确定...”)。但是,读取数据时分块可能仍然是个好主意,这样可以更细粒度地控制自己进程中的内存消耗。(听起来像是管道的缓冲区由操作系统维护) - Thomas Zoechling
此外,do 循环是否有可能错误地测试了错误或终止条件,而不是缺少这些条件? - algal
while循环不控制可用数据的迭代。它只处理发送中断信号的非常罕见的情况。当可用数据大于缓冲区大小时,调度源处理程序会被多次调用。 - Thomas Zoechling
由于 NSPipeNSFileHandledup2 在 iOS 中可用...我们可以安全地假设它也适用于 iOS,对吧! - Durai Amuthan.H
以上代码如果没有Xcode调试您的应用程序,则无法正常工作。本文介绍了为什么会出现这种情况以及如何解决它(即此处提到的缓冲问题)。https://dev59.com/bbDma4cB1Zd3GeqPBss4#54124239 - brant

1

我知道这个问题是关于Objective-C的,但我想发表一篇Swift的答案,以便帮助其他人。

let pipefd = UnsafeMutablePointer<Int32>.allocate(capacity: 8)
pipe(pipefd)

dup2(pipefd[1], fileno(stdout))

// Print something here

let buf = UnsafeMutableRawPointer.allocate(byteCount: 1024, alignment: 0)
read(pipefd[0], buf, 100)

close(pipefd[1])

let output = self.ptrToString(pointer: buf)
if output != "" {
    // Do something with output
}

buf.deallocate()
pipefd.deallocate()

这是我用来将指针转换为字符串的函数:
func ptrToString (pointer buf: UnsafeMutableRawPointer) -> String {
    let filteredArray = Array(UnsafeBufferPointer(start: buf.assumingMemoryBound(to: UInt8.self), count: 1024)).filter { item in
        return item != 0
    }

    return filteredArray
        .map { String(UnicodeScalar(UInt8($0))) }
        .joined()
        .components(separatedBy: "\n")[0]
}

适用于Swift 4


0
一个更简单的解决方案,这个例子使用了NSTask并生成了一个NSString。
NSError * hala;
NSTask * nettask = [[NSTask alloc] init];
nettask.executableURL = [NSURL URLWithString:@"file:/usr/sbin/networksetup"];
nettask.arguments = [NSArray arrayWithObject:@"-listlocations"];
NSPipe * piper = [NSPipe pipe];
nettask.standardOutput = piper;
NSFileHandle * pipereader = [piper fileHandleForReading];
[nettask launchAndReturnError:&hala];
if (hala) printf ("taskproblem %s\n", hala.description.UTF8String);
[nettask waitUntilExit];
if ([nettask terminationStatus]) printf ("task did not end successfully\n");
else {
    NSData * readout = [pipereader readDataToEndOfFileAndReturnError:&hala];
    if (hala) printf ("readproblem %s\n", hala.description.UTF8String);
    NSString * result = [[NSString alloc] initWithBytes:readout.bytes length:readout.length - 1 encoding:NSASCIIStringEncoding];  //skip trailing \n
}

-1

如果目标仅是处理您的NSLog输出,而不是系统生成的错误日志,则有另一种方法可以实现。以下是一个代码来覆盖NSLog。该代码仅将日志和一些额外信息打印到stderr,而不是通常的NSLog输出,但您可以在HyperLog函数内进行任何适合您需求的更改:

HyperLog.h

#import <Foundation/Foundation.h>

#ifdef HYPER_LOG
#define NSLog(args...) HyperLog(__FILE__,__LINE__,__PRETTY_FUNCTION__,args);
#else
#define NSLog(x...)
#endif

void HyperLog(const char *file, int lineNumber, const char *functionName, NSString *format, ...);

Hyperlog.m

#import "HyperLog.h"

void HyperLog(const char *file, int lineNumber, const char *functionName, NSString *format, ...)
{
    va_list ap;
    va_start (ap, format);
    if (![format hasSuffix: @"\n"])
    {
        format = [format stringByAppendingString: @"\n"];
    }

    NSString *body = [[NSString alloc] initWithFormat:format arguments:ap];
    va_end (ap);
     NSString *fileName = [[NSString stringWithUTF8String:file] lastPathComponent];
    char mesg[8192]="\0";
    NSDate *now =[NSDate date];
    NSString *dateString = [NSDateFormatter localizedStringFromDate:now dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterMediumStyle];
    if ( sprintf( mesg, "<%s.%03.0f> : %s\n<%s : %d - %s>\n", [dateString UTF8String],roundf(fmod( [now timeIntervalSinceReferenceDate], 1 ) * 1000), [body UTF8String], [fileName UTF8String],
                 lineNumber,
            functionName) < 0 ) printf("message creation failed\n");
    fprintf(stderr, "%s", mesg );
}

然后,您只需要将这两行代码放在任何一个程序文件的顶部即可使其工作。

#define HYPER_LOG
#import "HyperLog.h"

我尝试使用Thomas的上述代码,使用C函数将系统生成的错误日志结果数据写入文本文件,在其他上下文中正常工作,但它一直崩溃,并且错误原因在过程中丢失。有没有人知道为什么?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接