Reputation: 660
I am fairly new to Swift programming, please forgive the dummy questions I have got to ask below.
In my app, I am trying to schedule to call a function, which will receive some data from my server and will be called every second. The communication needs to be achieved via a TCP socket. After doing some research, it seems to me that I need to have a way to properly use threads to call that function. So here comes my questions:
I tried looking for tutorials and examples about threads and socket communications, but I could not find information that is applicable to my app. So any help or insight regarding the design would be greatly appreciated!
Thanks in advance!
Upvotes: 4
Views: 1202
Reputation: 660
After some digging, I have found the way to implement stream programming on the Swift side (using native Swift capabilities). It turns out that the way in Swift is very much like the way in Objective C.
The two functions required to enable stream programming in Swift are func connect()
, which I had to write by myself, and func stream(_ aStream: Stream, handle eventCode: Stream.Event)
, which is provided as a method in StreamDelegate
.
Here is what func connect()
looks like:
func connect() {
Stream.getStreamsToHost(withName: <serverIP>, port: <serverPort>, inputStream: &inputStream, outputStream: &)
guard let inputStream = inputStream, let outputStream = outputStream else {
print(" ->\tNetworkControllerError: Cannot open inputstream/outputstream.")
return
}
// Set delegate
inputStream.delegate = self
outputStream.delegate = self
let socketWorkQueue = DispatchQueue(label: "socketWorkQueue", attributes: .concurrent)
CFReadStreamSetDispatchQueue(inputStream, socketWorkQueue)
CFWriteStreamSetDispatchQueue(outputStream, socketWorkQueue)
inputStream.open()
outputStream.open()
}
And here is what the func stream(_ aStream: Stream, handle eventCode: Stream.Event)
looks like:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if aStream === inputStream {
switch eventCode {
case Stream.Event.errorOccurred:
streamEventQueue.async {
// do something here
}
break
case Stream.Event.openCompleted:
streamEventQueue.async {
// do something here
}
break
case Stream.Event.hasBytesAvailable:
streamEventQueue.async {
// do something here
let output = read()
}
break
case Stream.Event.endEncountered:
streamEventQueue.async {
// do something here
}
break
default:
break
}
}
else if aStream === outputStream {
switch eventCode {
case Stream.Event.errorOccurred:
streamEventQueue.async {
// do something here
}
break
case Stream.Event.openCompleted:
streamEventQueue.async {
// do something here
}
break
default:
break
}
}
}
So the stream()
function should be the only place in my app to handle state transitions caused by network connection/disconnection. Note that I also used a serial event queue to process the stream event. This is to prevent any potential race condition which might lead to state corruption.
For those who are not very familiar with stream programming, this is a server-client communication pattern which keeps the connection/socket alive. The connection is required to stay alive mostly due to there is a constant communication between the server and the client. Communication patterns like RESTful API would burden the server significantly by clients' requests and hence not recommended.
Upvotes: 0
Reputation: 5695
According to me, the following can be done for the scenarios presented in the question
If you are going to make connection to your server as soon as you launch the app. I believe it's better to do that in AppDelegate
in didFinishLaunchingWithOptions
given below
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
}
It depends on how you are going to switch to other ViewController
. In the process of switching if you are going to deallocate your current ViewController
, then your thread will die.
Considering that you're making a socket based app and you will be receiving data from server every second. Then, NSURLSession
might not be of much help to you. For socket communication, normally NSInputStream
and NSOutputStream
will be used. The following example from objective-C may help you to get started:
- (void)connectWithHostFromUrl: (NSURL *)hostUrl {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)[hostUrl host], 80, &readStream, &writeStream);
_inputStream = (__bridge_transfer NSInputStream *)readStream;
[_inputStream setDelegate:self];
[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
_outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[_outputStream setDelegate:self];
[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream open];
}
// Delegate methods
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventNone:
{
// handle it according to your need
}
break;
case NSStreamEventOpenCompleted:
{
// handle it according to your need
}
break;
case NSStreamEventHasBytesAvailable:
{
if (_receivedData == nil) {
_receivedData = [NSMutableData new];
}
uint8_t buffer[1024];
NSInputStream *inputStream = (NSInputStream *)aStream;
NSInteger bytesReceived = [inputStream read:buffer maxLength:1024];
if (bytesReceived > 0) {
[_receivedData appendBytes:(const void *)buffer length:bytesReceived];
}
}
break;
case NSStreamEventHasSpaceAvailable:
{
if (_dataToSend != nil) {
// _dataToSend is NSMutableData/NSData object
NSOutputStream *outputStream = (NSOutputStream *)aStream;
const uint8_t *mutableBytes = (const uint8_t *)[_dataToSend mutableBytes];
NSInteger length = [_dataToSend length]/sizeof(uint8_t);
[outputStream write:(const uint8_t *)mutableBytes maxLength:length];
}
}
break;
case NSStreamEventErrorOccurred:
{
// handle it according to your need
}
break;
case NSStreamEventEndEncountered:
{
// handle it according to your need
}
break;
default:
break;
}
}
Since there are a lot of cases to handle here. Most of the time, I suggest using a tested third party library like SocketRocket.
Please suggest edits to make this answer better :)
Upvotes: 1