23

I am porting an App from Objective-C to Swift and I need to use the following method:

CFStreamCreatePairWithSocketToHost(alloc: CFAllocator!, host: CFString!, port: UInt32, \
readStream: CMutablePointer<Unmanaged<CFReadStream>?>, \
writeStream: CMutablePointer<Unmanaged<CFWriteStream>?>)

The old logic looks like this (which several web sites seem to agree on):

CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), port, \
                                   &readStream, &writeStream);

NSInputStream inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream outputStream = (__bridge_transfer NSOutputStream *)writeStream;

Which works fine thanks to toll-free bridging. However, ARC does not exist in "Swift-space", and the type system has changed.

How do I turn my streams into instances of

CMutablePointer<Unmanaged<CFReadStream>?>, and
CMutablePointer<Unmanaged<CFWriteStream>?>

And then convert them back into NSStream subclasses after the CFStreamCreatePairWithSocketToHost call?

Ephemera
  • 8,672
  • 8
  • 44
  • 84
  • 1
    If you have the luxury of targeting iOS 8 or OS X 10.10 as a minimum, you can shed your CFStream calls using a new NSStream API as well. http://stackoverflow.com/questions/24461520/swift-cannot-convert-the-expressions-type-void-to-type-string/24461594#24461594 – wjl Jun 29 '14 at 19:25
  • 2
    ARC most definitely does exist in Swift. In fact, it's _the_ memory management model of Swift. It's just that there are certain places where an explicit cast to ARC space used to be necessary in objc and isn't needed in Swift. – radex Jul 25 '14 at 06:55

5 Answers5

42

I got it to work, here's my code: Make sure you keep a reference of the connection class somewhere :-)

class Connection : NSObject, NSStreamDelegate {
    let serverAddress: CFString = "127.0.0.1"
    let serverPort: UInt32 = 8443

    private var inputStream: NSInputStream!
    private var outputStream: NSOutputStream!

    func connect() {
        println("connecting...")

        var readStream:  Unmanaged<CFReadStream>?
        var writeStream: Unmanaged<CFWriteStream>?

        CFStreamCreatePairWithSocketToHost(nil, self.serverAddress, self.serverPort, &readStream, &writeStream)

        // Documentation suggests readStream and writeStream can be assumed to
        // be non-nil. If you believe otherwise, you can test if either is nil
        // and implement whatever error-handling you wish.

        self.inputStream = readStream!.takeRetainedValue()
        self.outputStream = writeStream!.takeRetainedValue()

        self.inputStream.delegate = self
        self.outputStream.delegate = self

        self.inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
        self.outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)

        self.inputStream.open()
        self.outputStream.open()
    }

    func stream(stream: NSStream, handleEvent eventCode: NSStreamEvent) {
        println("stream event")
    }
}
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
Vincenzo
  • 1,158
  • 11
  • 18
  • 1
    How do you perform comparison of the event code in stream function? – fnc12 Jun 17 '14 at 02:51
  • 2
    @fnc12 You need to use NSStreamEvent.xxx now, the structure of NSStreamEvent has changed slightly in Swift. – Ephemera Jun 17 '14 at 06:43
  • This does compile, but it crashes on execution. See my comment for a better solution using NSSTream.getStreamsToHostWithName, which is the new equivalent – Scott D Jul 14 '14 at 11:56
  • 1
    You are using `takeUnretainedValue`. According to the documentation, "This is useful when a function returns an unmanaged reference and you know that you're _not_ responsible for releasing the result." (Emphasis added.) The `takeRetainedValue` documentation says that it should be used where you are responsible for releasing the result, which seems analogous to `CFBridgingRelease`/`__bridge_transfer`. – Rob Aug 31 '14 at 04:18
  • I updated the code to be more correct. It has proper memory management now, it stops trying to use `if let` incorrectly (you weren't using the bound variable), and some other miscellaneous tweaks. – Lily Ballard Sep 22 '14 at 23:11
  • I also marked the streams as `private` because I expect this `Connection` class might want to control how they're exposed. For this reason I made them implicitly-unwrapped. But if you just want them to be public vars, then you should go back to using `?` and explicit unwrapping. – Lily Ballard Sep 22 '14 at 23:13
  • I keep getting EXC_BAD_ACCESS on self.inputStream = readStream!.takeRetainedValue() line. What's wrong? – fnc12 Dec 23 '14 at 12:10
  • This question is related: http://stackoverflow.com/questions/29048826/takeunretainedvalue-vs-takeretainedvalue – Michael Mar 14 '15 at 12:21
28

I wasn't able to get the examples others have provided in this thread to work. Sure, they compiled, but they crashed as soon as the connection was open.

However, I noticed in the WWDC 2014 discussions (and iOS 8 release notes) that there is a new method for initializing an NSStream for creating a bound pair of in/out streams.

See below:

var inputStream: NSInputStream?
var outputStream: NSOutputStream?

NSStream.getStreamsToHostWithName("localhost", port: 1234, inputStream: &inputStream, outputStream: &outputStream)

This removes the need for the awkward CFStreamCreatePairWithSocketToHost call as well as removing the need for Unmanaged resources.

Scott D
  • 1,394
  • 1
  • 13
  • 17
  • 3
    Yes, this is more beautiful way – fnc12 Dec 23 '14 at 12:17
  • From the docs: "Note: The getStreamsToHost:port:inputStream:outputStream: method of NSNetService is not available on iOS, and is discouraged on OS X for performance reasons. Specifically, NSNetService requires you to create an instance of NSHost. When you create the object, the lookup is performed synchronously. Thus, it is unsafe to construct an NSHost object on your main application thread. See NSNetService and Automatic Reference Counting (ARC) for details." – pshah Feb 15 '16 at 20:37
2

I worked out how to do it. A few important notes:

  1. CMutablePointers will be automatically created if you use the & operator.
  2. You can get at the T in an Unmanaged<T> with .getUnretainedValue() and getRetainedValue() (Seems .getUnretainedValue() is analogous to __bridge_transfer)
  3. Optionals are automatically initialised to nil.
  4. If an optional is nil it will translate into a false condition.

So far I have (untested):

var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?

CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, \
&readStream, &writeStream)

if (readStream && writeStream) {
    inputStream = readStream!.takeUnretainedValue();
    outputStream = writeStream!.takeUnretainedValue();
}
Ephemera
  • 8,672
  • 8
  • 44
  • 84
  • 2
    You say, "Seems `.getUnretainedValue()` is analogous to `__bridge_transfer`". I believe `takeRetainedValue` is analogous to `__bridge_transfer`, not `takeUnretainedValue`. – Rob Aug 31 '14 at 04:22
1

I am using getStreamsToHostWithName function of NSStream class. It is more easy and beeter than CFStreamCreatePairWithSocketToHost

func initNetworkCommunication() {

print("connecting...")

let serverAddress = "gzoa.vps.infomaniak.com"
let serverPort = 1234

NSStream.getStreamsToHostWithName(serverAddress, port: serverPort, inputStream: &inputStream, outputStream: &outputStream)

self.inputStream!.delegate = self
self.outputStream!.delegate = self

self.inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
self.outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)

self.inputStream!.open()
self.outputStream!.open()

}

Mr. Bean
  • 4,221
  • 1
  • 19
  • 34
1

Swift 3 version of CF and NS code. Both work for me.

CF:

class Connection: NSObject, StreamDelegate {

private var inputStream: InputStream!
private var outputStream: OutputStream!

var connected = false

func connect(host: String, port: UInt32) {
    var readStream:  Unmanaged<CFReadStream>?
    var writeStream: Unmanaged<CFWriteStream>?

    CFStreamCreatePairWithSocketToHost(nil, host as CFString, port, &readStream, &writeStream)

    self.inputStream = readStream!.takeRetainedValue()
    self.outputStream = writeStream!.takeRetainedValue()

    if let inputStream = inputStream, let outputStream = outputStream {
        inputStream.delegate = self
        outputStream.delegate = self

        inputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
        outputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)

        inputStream.open()
        outputStream.open()

        connected = true
    }
}

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    print("stream event, \(eventCode)")
}

}

NS:

class NSConnection: NSObject, StreamDelegate {

private var inputStream: InputStream?
private var outputStream: OutputStream?

var connected = false

func connect(host: String, port: Int) {
    Stream.getStreamsToHost(withName: host, port: port, inputStream: &inputStream, outputStream: &outputStream)

    if let inputStream = inputStream, let outputStream = outputStream {
        inputStream.delegate = self
        outputStream.delegate = self

        inputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
        outputStream.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)

        inputStream.open()
        outputStream.open()
    }
}

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    print("stream event, \(eventCode)")
}

}
Ivan Rep
  • 369
  • 5
  • 8