Toll-free bridging and pointer access in Swift
Asked Answered
A

5

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?

Algology answered 4/6, 2014 at 4:26 Comment(2)
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. #24462020Becharm
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.Lanceted
I
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")
    }
}
Ie answered 4/6, 2014 at 17:46 Comment(8)
How do you perform comparison of the event code in stream function?Ownership
@Ownership You need to use NSStreamEvent.xxx now, the structure of NSStreamEvent has changed slightly in Swift.Algology
This does compile, but it crashes on execution. See my comment for a better solution using NSSTream.getStreamsToHostWithName, which is the new equivalentGarratt
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.Divergence
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.Eckenrode
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.Eckenrode
I keep getting EXC_BAD_ACCESS on self.inputStream = readStream!.takeRetainedValue() line. What's wrong?Ownership
This question is related: #29049326Judaist
G
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.

Garratt answered 2/7, 2014 at 19:21 Comment(2)
Yes, this is more beautiful wayOwnership
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."Hulbard
A
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();
}
Algology answered 5/6, 2014 at 3:14 Comment(1)
You say, "Seems .getUnretainedValue() is analogous to __bridge_transfer". I believe takeRetainedValue is analogous to __bridge_transfer, not takeUnretainedValue.Divergence
B
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()

}

Bootee answered 2/5, 2016 at 10:20 Comment(0)
R
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)")
}

}
Regularize answered 5/2, 2017 at 14:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.