TCP in Swift 3

Hello. I am implementing TCP connection in one of my applications. This is my working Swift 2 code:


func openNetworkCommunication()
    {
        if(self.inputStream != nil && self.outputStream != nil)
        {
            closeNetworkCommunication()
        }

        let ipAddress = delegate!.ipAddressString as CFStringRef

        let portNumber = delegate?.ipPort

        var readStream:  Unmanaged<CFReadStream>?
        var writeStream: Unmanaged<CFWriteStream>?
        CFStreamCreatePairWithSocketToHost(nil, ipAddress, portNumber!, &readStream, &writeStream)

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

        self.outputBuffer = delegate?.messageToServer.dataUsingEncoding(NSUTF8StringEncoding) as? NSMutableData

        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(theStream : NSStream, handleEvent streamEvent : NSStreamEvent)
    {
        switch (streamEvent)
        {
        case NSStreamEvent.None:
            print("NSStreamEventNone")

        case NSStreamEvent.OpenCompleted:
            ... code ...

        case NSStreamEvent.HasBytesAvailable:
            ... code ...

        case NSStreamEvent.HasSpaceAvailable:
            ... code ...

        case NSStreamEvent.ErrorOccurred:
            ... code ...

        case NSStreamEvent.EndEncountered:
            p... code ...
       
       default:
            print("Unknown event")
        }
    }


I have converted the code to Swift 3 and faced two issues:


Question 1:


func openNetworkCommunication()
    {
        if(self.inputStream != nil && self.outputStream != nil)
        {
            closeNetworkCommunication()
        }

        let ipAddress = delegate!.ipAddressString as CFString

        let portNumber = delegate?.ipPort

        var readStream:  Unmanaged<CFReadStream>?
        var writeStream: Unmanaged<CFWriteStream>?
        CFStreamCreatePairWithSocketToHost(nil, ipAddress, portNumber!, &readStream, &writeStream)

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

        self.outputBuffer = delegate?.messageToServer.data(using: String.Encoding.utf8) as? NSMutableData

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

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

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


I have a warning here:

self.outputBuffer = delegate?.messageToServer.data(using: String.Encoding.utf8) as? NSMutableData


saying that "Cast from Data? to unrelated type NSMutableData always fails".


How should I treat this situation correctly?


Question 2:


In method:

func stream(theStream : Stream, handleEvent streamEvent : Stream.Event)


the following line:

case Stream.Event.none:


gives an error: "'none' is unavailable, use [] to construct an empty option set". Using Stream.Event.[]: or Stream.Event[]: does not help either. What is the proper way?


Thank you so much!

Accepted Reply

1) Documentation says that Data is available in "iOS (10.0 and later), macOS (10.12 and later), tvOS (10.0 and later), watchOS (3.0 and later)". Does it mean that it will not be available with iOS 9 and 8?

The glue that creates

Data
out of
NSData
is part of the Swift libraries that get embedded within your app. As such, you shouldn’t have problems deploying back to older OS releases. However, I strongly recommend that you test this for yourself; Apple is spinning a lot of plates right now and if there’s something broken in this backward compatibility story it’d be great to know about it now rather than later.

2) If I stick to Data: What would be the correct conversion?

[finishes install of Xcode 8.0b3]

I’ve covered each case below, with explanatory notes.

self.inputBuffer = Data(capacity: 32768)!

You need the forced unwrap because the

Data(capacity:)
call is a failable initialiser (because
capacity
could be huge). The alternative is to make your initialiser failable (or errorable).
let bytesWritten = self.outputBuffer!.withUnsafeBytes {
    self.outputStream!.write($0, maxLength: self.outputBuffer!.count)
}
Data
has nothing equivalent to
-[NSData bytes]
because that’s inherently unsafe; the returned pointer references the data within the buffer and that reference gets invalidated in various cases (for example, if you mutate the buffer). Instead it provides
withUnsafeBytes(body:)
, which follows the standard Swift pattern of calling a ‘noescape’ block with the pointer. This works out nicely because the Swift compiler can infer a whole bunch of stuff for you (for example, the right type for
$0
in the snippet above).
self.outputBuffer!.replaceBytes(in: 0..<bytesWritten, with: Data())

You’re in Swift Land™ now, so you use Swift types (

Range
instead of
NSRange
). Moreover, you can construct
Range
with the fancy
..<
operator.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Q1: We don't know what 'self' is in "self.outputBuffer", which means we don't know what type 'outputBuffer' has. We don't know what type 'messageToServer' returns, but mixing Data and NSData/NSMutableData in Swift 3 isn't going to work, AFAIK.


Q2: Just use:


     case []:


[] is the empty set literal. The type should be inferrable from context (the switch expression type), but if it could not, you could say "[] as Stream.Event" to make it explicit.


Note that it's an option set (and it always was), so there may be 0, 1 or more options in the value you're switching on. It's not clear whether failing to allow for this is a bug in your original code, or if there's an API contract that says you'll never get more than one.


Conversely, it's possible that you'll never get 0 options, in which situation you never needed the 'none' case at all. (The reason the old "none" convention was taken away was that it suggested a 0-option possibility that didn't really exist in most APIs that used Int-based masks under Obj-C.)

Thank you!


Regarding Question 1.


At the moment, my outputBuffer is declared like this:


var outputBuffer : NSMutableData?


Method messageToServer returns String.


But I am wondering whether I should use Data instead of NSMutableData now. I cannot find a good example of TCP connection in Swift 3.


Best.

But I am wondering whether I should use Data instead of NSMutableData now.

Yes you should. The Swift migrator tends to leave mutable objects alone because it’s hard to get a global view of their behaviour. This means that half of your code (the code that generates the data) has been migrated to Data but the other half (the code that deals with that data) has not. You just need to complete that migration.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you, Eskimo. I got a couple of more questions then.


1) Documentation says that Data is available in "iOS (10.0 and later), macOS (10.12 and later), tvOS (10.0 and later), watchOS (3.0 and later)". Does it mean that it will not be available with iOS 9 and 8? I did want to make the application compatible with earlier versions, especially now as the new iOS has not been released. In this case, should I stick to NSMutableData?


2) If I stick to Data: What would be the correct conversion?


       private var inputBuffer : Data!

     override init()
    {
        super.init()
   
        let bufferSize : Int!
   
        if delegate?.bufferCapacity != nil
        {
            bufferSize = delegate?.bufferCapacity
        }
   
        else
        {
            bufferSize = 4096
        }
   
        // PROBLEM 1: THIS LINE HAS PROBLEMS WITH CONVERSION!!!!!!!
        self.inputBuffer = NSMutableData(capacity: bufferSize!)
    }


func sendToServer()
    {
    
        if (self.outputBuffer!.count != 0)
        {
        
            // PROBLEM 2: VALUE OF TYPE 'DATA' HAS NO MEMBER BYTES in 'self.outputBuffer!.bytes'. WHICH MEMBER SHOULD I PUT??
            let bytesWritten : Int = self.outputStream!.write(UnsafePointer<UInt8>(self.outputBuffer!.bytes), maxLength: self.outputBuffer!.length)
        
            if (bytesWritten <= 0)
            {
                self.error = self.outputStream?.streamError
            }
            
            else
            {
                // PROBLEM 3: NOT CLEAR HOW TO MAKE ARGUMENT TYPE RANGE<INDEX> FROM THIS NSMAKERANGE EXPRESSION.
                //0...bytesWritten IS OBVIOUSLY NOT WORKING
                self.outputBuffer!.replaceBytes(in: NSMakeRange(0, bytesWritten), with: nil)
            }
        }
    }



3) If Data is absolutely not backward compatible and I have to stick to NSMutableData, then I to repeat my original question #1 regarding "Cast from Data? to unrelated type NSMutableData always fails".


Thank you very for your help. Sorry for too many questions; I did extensive googling prior to asking them. ))

1) Documentation says that Data is available in "iOS (10.0 and later), macOS (10.12 and later), tvOS (10.0 and later), watchOS (3.0 and later)". Does it mean that it will not be available with iOS 9 and 8?

The glue that creates

Data
out of
NSData
is part of the Swift libraries that get embedded within your app. As such, you shouldn’t have problems deploying back to older OS releases. However, I strongly recommend that you test this for yourself; Apple is spinning a lot of plates right now and if there’s something broken in this backward compatibility story it’d be great to know about it now rather than later.

2) If I stick to Data: What would be the correct conversion?

[finishes install of Xcode 8.0b3]

I’ve covered each case below, with explanatory notes.

self.inputBuffer = Data(capacity: 32768)!

You need the forced unwrap because the

Data(capacity:)
call is a failable initialiser (because
capacity
could be huge). The alternative is to make your initialiser failable (or errorable).
let bytesWritten = self.outputBuffer!.withUnsafeBytes {
    self.outputStream!.write($0, maxLength: self.outputBuffer!.count)
}
Data
has nothing equivalent to
-[NSData bytes]
because that’s inherently unsafe; the returned pointer references the data within the buffer and that reference gets invalidated in various cases (for example, if you mutate the buffer). Instead it provides
withUnsafeBytes(body:)
, which follows the standard Swift pattern of calling a ‘noescape’ block with the pointer. This works out nicely because the Swift compiler can infer a whole bunch of stuff for you (for example, the right type for
$0
in the snippet above).
self.outputBuffer!.replaceBytes(in: 0..<bytesWritten, with: Data())

You’re in Swift Land™ now, so you use Swift types (

Range
instead of
NSRange
). Moreover, you can construct
Range
with the fancy
..<
operator.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Eskimo. Thank you so much! Very informative answer. Greatly appreciated!


Igor

Just upgraded xCode and the following:


self.outputBuffer!.replaceBytes(in: 0..<bytesWritten, with: Data())


gives error that type Data has no member replaceBytes...


Huh?


Thanks!

Try one of the replaceSubrange(_:with:) methods.

Thank you very much, TheCD. No errors now. I will test it as soon as the rest of the code is converted.


Best regards.