Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using pipe() in Swift App to redirect stdout into a textView (only runs in simulator, not native)

I created an iOS app for iPhone using Swift under the Xcode IDE. For debugging purposes I would like to have everything what print (and printf in C code) prints to the Xcode console normally, redirected to a UItextView. My code (mainly derived from here) follows:

//
//  ViewController.swift
//  Scroll View Demo
//

//  
//

import UIKit

class ViewController: UIViewController {


    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var writeButton: UIButton!
    var pipe = Pipe()
    var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // dup2() makes newfd (new file descriptor) be the copy of oldfd (old file descriptor), closing newfd first if necessary.
        openConsolePipe()
        print("\npipe started")

    }

    @IBAction func buttonPressed(_ sender: Any) {
        print("\(count). Hello world")
        count += 1
    }
    public func openConsolePipe () {
        dup2(pipe.fileHandleForWriting.fileDescriptor, 
            STDOUT_FILENO)
        // listening on the readabilityHandler
        pipe.fileHandleForReading.readabilityHandler = {
         [weak self] handle in
        let data = handle.availableData
        let str = String(data: data, encoding: .ascii) ?? "<Non-ascii data of size\(data.count)>\n"
        DispatchQueue.main.async {
            self?.textView.text += str
        }
      }
    }

}

The code works in simulation mode on the physical device and in the virtual iphone as well. But when I run it natively with the cord to Xcode cut, the pipe() doesn't work. I'm clueless at the moment why the pipe() doesn't work in the standalone app.

like image 268
Krischu Avatar asked Dec 30 '18 13:12

Krischu


1 Answers

The code change that made the example finally work is to modify the file handle of stdout to unbuffered (setvbuf(stdout, nil, _IONBF, 0)):

//
//  ViewController.swift
//  Scroll View Demo
//

//  
//



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var textView: UITextView!
@IBOutlet weak var writeButton: UIButton!
var pipe = Pipe()
var count = 0

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    // dup2() makes newfd (new file descriptor) be the copy of oldfd (old file descriptor), closing newfd first if necessary.
    openConsolePipe()
    print("\npipe started")

}

@IBAction func buttonPressed(_ sender: Any) {
    print("\(count). Hello world")
    count += 1
}
public func openConsolePipe () {
    **setvbuf(stdout, nil, _IONBF, 0).** //<--------- !
    dup2(pipe.fileHandleForWriting.fileDescriptor, 
        STDOUT_FILENO)
    // listening on the readabilityHandler
    pipe.fileHandleForReading.readabilityHandler = {
     [weak self] handle in
    let data = handle.availableData
    let str = String(data: data, encoding: .ascii) ?? "<Non-ascii data of size\(data.count)>\n"
    DispatchQueue.main.async {
        self?.textView.text += str
    }
  }
}

That's because with the app running disconnected from Xcode stdout is redirected to something like /dev/null with buffering set to "buffered" and thus never appears in the pipe(). Setting it to unbuffered made things work.

like image 108
Krischu Avatar answered Oct 15 '22 17:10

Krischu