Connecting to Socket using Swift 5
Connecting to Socket using Swift 5

Connecting to Socket using Swift 5

Pierre Janineh
SwiftCommmunity
Published in
5 min readMar 16, 2021

--

While working on some project, I went through a difficult time trying to create a socket connection with my Java server side built on Ubuntu server.
So here it is, how to connect to a Socket with Swift 5, and how to read Strings and Ints.

First, we need to create a Socket class:

Create a class (I called it SocketServer), this class is going to connect to the desired socket using host & port.
Later on, we’re going to declare a protocol (SocketDelegate) that this class calls its methods when InputStream has available response.

You can visit the Github gist for better view of the code.

Methods explanations follow every code block.

So we start off by declaring the class, host and port of the server.

class SocketServer: NSObject{

static private let HOST = "000.000.000.000"
static private let PORT: UInt32 = 3000

//Server action code
static public let SOME_ACTION = 100

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

public var isOpen: Bool = false
weak var delegate: SocketDelegate?

override init(){
super.init()
}

/**
Connects to Socket by setting the delegate to itself,
and opening InputStream & OutputStream.
*/
public func connect(){
//set up uninitialized socket streams
without automatic memory management
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?

//bind read and write socket streams
together and connect them to the socket of the host
CFStreamCreatePairWithSocketToHost(
kCFAllocatorDefault,
SocketServer.HOST as CFString,
SocketServer.PORT,
&readStream,
&writeStream)


//store retained references
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()

inputStream.delegate = self
//run a loop
inputStream.schedule(in: .current, forMode: .common)
outputStream.schedule(in: .current, forMode: .common)

//open flood gates
inputStream.open()
outputStream.open()

isOpen = true
}

/**
This method closes InputStream and OutputStream after reading data from InputStream.
*/
private func closeNetworkConnection(){
inputStream.close()
outputStream.close()
isOpen = false
}

/**
A private global method to use to communicate with the server.
- Parameter serverActionCode: The static field (server action code) for this function.
- Parameter int: The other int willing to write to outputStream.
*/
private func serverRequest(serverActionCode: UInt8, int: UInt8){
writeToOutputStream(int: serverActionCode)
writeToOutputStream(int: int)
}

/**
Calls serverRequest(serverActionCode:int:) where the code is the action's code.
- Parameter someParam: an example parameter to pass to server after passing the action code number.
*/
public func someAction(someParam: UInt8!){
serverRequest(serverActionCode: SocketServer.SOME_ACTION, int: someParam)
}
}

Implements NSObject in advance to implement StreamDelegate Later on.

Second, write an extension to implement StreamDelegate:

We need to create an extension that implements StreamDelegate to override delegate method and to get response from InputStream.

extension SocketServer: StreamDelegate{

//Delegate method override
func stream(_ aStream: Stream, handle eventCode: Stream.Event){
switch eventCode{
case .hasBytesAvailable:
//inputStream has something to pass
let s = self.readStringFrom(stream: aStream as! InputStream)
self.closeNetworkConnection()
if let s = s{
self.delegate?.socketDataReceived(result: Data(s.utf8))
}else{
self.delegate?.receivedNil()
}
case .endEncountered:
print("end of inputStream")
case .errorOccurred:
print("error occured")
case .hasSpaceAvailable:
print("has space available")
case .openCompleted:
isOpen = true
print("open completed")
default:
print("StreamDelegate event")
}
}

/**
This Method gets an UnsafeMutablePointer<UInt8> buffer of the InputStream bytes of size.
- Parameter stream: InputStream to read from.
- Parameter size: The size of bytes to read.
- Returns: UnsafeMutablePointer<UInt8> buffer of bytes.
*/
private func getBufferFrom(stream: InputStream, size: Int) -> UnsafeMutablePointer<UInt8> {
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: size)

while (stream.hasBytesAvailable) {
let numberOfBytesRead = self.inputStream.read(buffer, maxLength: size)
if numberOfBytesRead < 0, let error = stream.streamError {
print(error)
break
}
if numberOfBytesRead == 0{
//EOF
break
}
}
return buffer
}

/**
Reads data from InputStream with size.
Calls getBufferFrom(stream:size:) and gets data from buffer.
- Parameter stream: InputStream to read from.
- Parameter size: The size of bytes to read.
- Returns: Data of bytes from InputStream.
*/
private func readDataFrom(stream: InputStream, size: Int) -> Data?{
let buffer = getBufferFrom(stream: stream, size: size)
return Data(bytes: buffer, count: size)
}

/**
Reads String from InputStream by calling readDataFrom(stream:size:).
- Parameter stream: InputStream to read from.
- Parameter withSize: The size of bytes to read.
- Returns: String of bytes from InputStream.
*/
private func readStringFrom(stream: InputStream, withSize: Int) -> String?{
let d = readDataFrom(stream: stream, size: withSize)!
return String(data: d, encoding: .utf8)
}

/**
Reads String from InputStream by first reading the Int of length of the String sent by server, and then calling readStringFrom(stream:withSize:).
- Parameter stream: InputStream to read from.
- Returns: String of bytes from InputStream.
*/
private func readStringFrom(stream: InputStream) -> String?{
let len: Int = Int(Int32(readIntFrom(stream: inputStream)!))
return readStringFrom(stream: stream, withSize: len)
}

/**
Reads UInt32 from InputStream by calling getBufferFrom(stream:size:4) and converting bytes into an Int.
- Parameter stream: InputStream to read from.
- Returns: UInt32 a full int.
*/
private func readIntFrom(stream: InputStream) -> UInt32?{
let buffer = getBufferFrom(stream: stream, size: 4)
var int: UInt32 = 0
let data = NSData(bytes: buffer, length: 4)
data.getBytes(&int, length: 4)
int = UInt32(bigEndian: int)
buffer.deallocate()
return int
}

/**
Writes a String to OutputStream by sending it as bytes.
- Parameter string: The String to write to OutputStream.
*/
private func writeToOutputStream(string: String){
let data = string.data(using: .utf8)!
data.withUnsafeBytes {
guard let pointer = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
else {
print("Error joining chat")
return
}
outputStream.write(pointer, maxLength: data.count)
}
}

/**
Writes an Int to OutputStream by sending it as bytes.
- Parameter int: The Int to write to OutputStream.
*/
private func writeToOutputStream(int: UInt8){
let data = int.data
data.withUnsafeBytes {
guard let pointer = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
else {
print("Error joining chat")
return
}
outputStream.write(pointer, maxLength: data.count)
}
}
}

My server writes bytes’ length first as an Int and then writes all the bytes, so we first need to read 4 bytes (an Int), then read bytes from InputStream with the size we got from the server.
So reading a string from InputStream would go:
readStringFrom(stream)readStringFrom(stream, withSize: readIntFrom(stream))

Third, create a protocol:

Finally, after we’re done with Socket class, we need to create a protocol as we mentioned earlier, to call its methods when needed.

protocol SocketDelegate: class{
/**
This method is called when StreamDelegate calls stream(_:eventCode:) with .hasBytesAvailable after all bytes have been read into a Data instance.
- Parameter result: Data result from InputStream.
*/
func socketDataReceived(result: Data?)

/**
Called when StreamDelegate calls stream(_:eventCode:) with .hasBytesAvailable after all bytes have been read into a Data instance and it was nil.
*/
func receivedNil()
}

Implements class to avoid reference cycle.

Lastly, using SocketServer is as simple as:

class SomeClass: UIViewController{
var result: SomeObj
func viewDidLoad(){
let socket: SocketServer = SocketServer()
socket.delegate = self
socket.connect()
socket.someAction(someParam: 5)
}
}
extension SomeClass: SocketDelegate{
func socketDataReceived(result: Data?){
if result == nil {return}
let result: SomeObj = processData(result)
if let result: SomeObj = result{
self.result = result
}
}
func receivedNil(){}
}

That’s it, have fun programming, and feel free to contact me if anything goes wrong :)

--

--