iOS / Swift: Set timeout for opening Streams - ios

I am working on an iOS app (Swift 4) that connects to a server via
Stream.getStreamsToHost(withName: self.host!, port: self.port!, inputStream: &self.inputStream, outputStream: &self.outputStream)
if let instream = self.inputStream, let outstream = self.outputStream {
instream.delegate = self
outstream.delegate = self
instream.schedule(in: .current, forMode: .commonModes)
outstream.schedule(in: .current, forMode: .commonModes)
instream.open()
outstream.open()
}
In case I have a bad internet connection / no connection / server is not reachable for some reason, I want to show the user an error message. This already works but it takes almost a minute until the error "The operation couldn’t be completed. Operation timed out" happens.
Can I somehow reduce the timeout in my app or is this a system-wide timeout that cannot be changed?
The constants for Stream.setProperty do not seam to contain any timeout-related stuff: https://developer.apple.com/documentation/foundation/stream#1666775
A workaround I can imagine is to manually schedule a function after X seconds and if the connection is not established then, to cancel it. Is this the best-practice to achieve a custom timeout?

After reading a few threads about this problem I decided to use a Timer that checks for a successful connection after n seconds:
Stream.getStreamsToHost(withName: self.host!, port: self.port!, inputStream: &self.inputStream, outputStream: &self.outputStream)
if let instream = self.inputStream, let outstream = self.outputStream {
instream.delegate = self
outstream.delegate = self
instream.schedule(in: .current, forMode: .commonModes)
outstream.schedule(in: .current, forMode: .commonModes)
instream.open()
outstream.open()
self.connectionTimer = Timer.scheduledTimer(withTimeInterval: self.CONNECTION_TIMEOUT, repeats: false) { _ in
if !self.instreamReady || !self.outstreamReady {
self.errorHandler(NetworkingError.Timeout("Timeout \(self.CONNECTION_TIMEOUT)s exeeded"))
self.disconnect()
}
self.connectionTimer?.invalidate()
self.connectionTimer = nil
}
}
Works :)

var continue = true
var countin = 0
var inp :InputStream?
var out :OutputStream?
let testData = mesaj
Stream.getStreamsToHost(withName: addr, port: port, inputStream: &inp, outputStream: &out)
let stream = out!
print( stream.streamStatus.rawValue)
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
guard continue == true else{
timer.invalidate()
return
}
if (counting == 0){
DispatchQueue.global(qos: .utility).async() {
stream.open()
var position = 0
print( stream.streamStatus.rawValue)
while position < testData.count {
let length = min(4096, testData.count - position)
let amount = testData[position...].withUnsafeBytes {
stream.write($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: length)
}
if amount <= 0 {
// Error or EOF
break
}
position += amount
}
stream.close()
DispatchQueue.main.sync {
self.durumTF.text = "Send to PC"
self.durumTF.textColor = .blue
}
continue=false
}
}
if (counting == 3){
print(stream.streamStatus.rawValue)
if stream.streamStatus.rawValue == 7 {
stream.close()
self.durumTF.text = "Couldn't connect to PC"
self.durumTF.textColor = .red
}
if stream.streamStatus.rawValue == 1 {
stream.close()
self.durumTF.text = "Run server on pc"
self.durumTF.textColor = .red
}
continue = false
}
counting += 1
}

Related

CFStreamCreatePairWithSocketToHost - How to handle the network disconnect?

We have created socket for streaming using the API "CFStreamCreatePairWithSocketToHost".
func openStream() {
if let aUrl = url {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(
kCFAllocatorDefault,
aUrl as CFString,
port!,
&readStream,
&writeStream)
iStream = readStream!.takeRetainedValue()
oStream = writeStream!.takeRetainedValue()
if ssl == true {
let dict = [
kCFStreamSSLValidatesCertificateChain: kCFBooleanFalse, // allow self-signed certificate
kCFStreamSSLLevel: kCFStreamSSLValidatesCertificateChain // don't understand, why there isn't a constant for version 1.2
] as CFDictionary
let sslSetRead = CFReadStreamSetProperty(iStream, CFStreamPropertyKey(kCFStreamPropertySSLSettings), dict)
let sslSetWrite = CFWriteStreamSetProperty(oStream, CFStreamPropertyKey(kCFStreamPropertySSLSettings), dict)
if sslSetRead == false || sslSetWrite == false {
//print("SSL Configuration Failed")
}
}
commonStr = ""
open(iStream)
open(oStream)
print("Open Stream")
}
}
Used the below delegate for getting events.
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
// print("Stream : \(aStream.debugDescription) Event : \(eventCode.rawValue)")
switch eventCode {
case .endEncountered, .errorOccurred:
streamError()
break
case .hasBytesAvailable:
if let iStream = iStream {
if aStream == iStream {
//read data
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
var len: Int
while iStream.hasBytesAvailable {
len = iStream.read(buffer, maxLength: bufferSize)
if len > 0 {
let output = Data.init(bytes: buffer, count: len)
bufferTokenize(output: output)
}else {
break
}
}
buffer.deallocate()
}
}
break
case .hasSpaceAvailable:
if aStream == oStream! {
streamReady()
}
break
case .openCompleted:
if aStream == oStream! {
if let del = delegate {
del.connectionCompleted()
}
}
break
default:
break
}
}
Other Supporting functions are
func streamError() {
stopStream()
NSObject.cancelPreviousPerformRequests(withTarget:self)
perform(#selector(callReconnect), with: nil, afterDelay: 2)
}
func open(_ stream: Stream?) {
if let aStream = stream {
aStream.delegate = self
aStream.schedule(in: RunLoop.main, forMode: .common)
aStream.open()
}
}
func stopStream() {
print("Stop Stream")
close(iStream)
close(oStream)
}
func close(_ stream: Stream?) {
if nil != stream {
stream?.close()
stream?.remove(from: RunLoop.main, forMode: .common)
}
}
func resetStream() {
stopStream()
openStream()
}
It is working fine for below steps
Create connection - Open stream
get "openCompleted" event - then Create request
get "hasSpaceAvailable" event - then Send Request
get "hasBytesAvailable" event - then Handle Data
Disconnect connection from server for testing purpose
get "endEncountered" event - then Close the connection and Recreate the connection from step 1
But the below scenario not working properly
Follow the steps 1 - 4 then
Disconnect Internet connection for testing purpose - the event "hasBytesAvailable" stopped
Again reconnect the Internet connection within 30 seconds - now we get "hasBytesAvailable" event
But the reconnect time extent to 5 minute - We don't have any event, and don't get "endEncountered" or "errorOccurred" also. Then how to move to next stop.
Please Help, Thanks
Regards,
Elamvazhuthi K

Count-Up and Count-Down Timer with Date() in Swift 4 should stop after a certain time period

I am quite new to Swift and already learned a lot using the questions here.
In one of my first projects I try to write a soccer playtime timer app. The first timer is counting up after the whistle button is pressed showing the minutes played and the second timer is counting down to zero showing the minutes left to play. This works so far.
Now both timers should stop automatically when the halftime is over, so that I can start a third overtime timer. So far the invalidate statement of the timer is not working - both timers keep running. There seems to be something wrong with my if-statements, but at the moment I have no clue what. So any help would be very appreciated.
var countUpClock: Timer?
var countDownClock: Timer?
private var formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
func runPlaytimeClocks() {
let startTime = Date()
let countTime = Date() + 2700 //45min of playtime
if startTime <= countTime {
countUpClock = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.timePlayed.text = self?.formatter.string(from: startTime, to: Date())
}
}
else {
countUpClock?.invalidate()
}
if startTime <= countTime {
countDownClock = Timer.scheduledTimer(withTimeInterval: -1.0, repeats: true) { [weak self] _ in
self?.timetoPlay.text = self?.formatter.string(from: Date(), to: countTime)
}
}
else {
countDownClock?.invalidate()
}
Thank you very much for the replies.
I found exactly what I was looking for here: http://ioscake.com/swift-nstimer-in-background.html
I adapted the solution to my for clocks (CountUpClock, CountDownClock, OvertimeClock, HalftimeClock).
Do you have any suggestions what would be the best solution to start the second halftime of the soccer game?
So far the CountUpClock starts again at 0:00 when I press the whistle button after the halftime break. But it should keep running from minute 45:00 to 90:00 - while the CountDownClock should counting down from 45:00 to 0:00 again.
What would be the best solution for such a behaviour?
import UIKit
import UserNotifications
private let stopTimeKey = "stopTimeKey"
class ViewController: UIViewController {
//Outlets
#IBOutlet weak var timePlayed: UILabel!
#IBOutlet weak var timeToPlay: UILabel!
#IBOutlet weak var overtime: UILabel!
#IBOutlet weak var halftime: UILabel!
#IBOutlet weak var halftimeButton: UIButton!
private var stopTime: Date?
override func viewDidLoad() {
super.viewDidLoad()
registerForLocalNotifications()
stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
if let time = stopTime {
if time > Date() {
startTimers(time, includeNotification: false)
} else {
notifyTimerCompleted()
}
}
}
private func registerForLocalNotifications() {
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
guard granted && error == nil else {
// display error
print("\(String(describing: error))")
return
}
}
} else {
let types: UIUserNotificationType = [.badge, .sound, .alert]
let settings = UIUserNotificationSettings(types: types, categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
}
//Actions
#IBAction func whistleButtonTapped(_ sender: UIButton) {
overtimeClock?.invalidate()
overtimeClock = nil
halftimeClock?.invalidate()
halftimeClock = nil
overtime.text = "00:00"
halftime.text = "00:00"
halftimeButton.isHidden = true
//add 10 seconds per halftime to try out
let time = Date() + 10
if time > Date() {
startTimers(time)
} else {
timeToPlay.text = "error"
}
}
#IBAction func halftimeButton(_ sender: UIButton) {
halftimeButtoPressed()
}
// Code for different Timers
private var countDownClock: Timer?
private var countUpClock: Timer?
var overtimeClock: Timer?
var halftimeClock: Timer?
private func startTimers(_ stopTime: Date, includeNotification: Bool = true) {
// save `stopTime` in case app is terminated
UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
self.stopTime = stopTime
// start Timer
countDownClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountDownTimer(_:)), userInfo: nil, repeats: true)
countUpClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountUpTimer(_:)), userInfo: nil, repeats: true)
guard includeNotification else { return }
// start local notification (so we're notified if timer expires while app is not running)
if #available(iOS 10, *) {
let content = UNMutableNotificationContent()
content.title = "Overtime is starting soon"
content.body = "In 5 seconds the overtime will start"
content.sound = UNNotificationSound.default()
//5 seconds warning before overtime starts
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: stopTime.timeIntervalSinceNow - 5, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
} else {
let notification = UILocalNotification()
//5 seconds warning before overtime starts
notification.fireDate = stopTime - 5
notification.alertBody = "Overtime is starting soon"
UIApplication.shared.scheduleLocalNotification(notification)
}
}
private func stopTimer() {
countDownClock?.invalidate()
countDownClock = nil
countUpClock?.invalidate()
countUpClock = nil
}
private func halftimeButtoPressed() {
overtimeClock?.invalidate()
overtimeClock = nil
startHalftimeClock()
halftimeButton.isHidden = true
}
private let dateComponentsFormatter: DateComponentsFormatter = {
let _formatter = DateComponentsFormatter()
_formatter.allowedUnits = [.minute, .second]
_formatter.unitsStyle = .positional
_formatter.zeroFormattingBehavior = .pad
return _formatter
}()
#objc func handleCountDownTimer(_ timer: Timer) {
let now = Date()
if stopTime! > now {
timeToPlay.text = dateComponentsFormatter.string(from: now, to: stopTime!)
} else {
stopTimer()
notifyTimerCompleted()
startOvertimeClock()
halftimeButton.isHidden = false
}
}
#objc func handleCountUpTimer(_ timer: Timer) {
//add 10 seconds per halftime to try out
let now = Date() + 10
if now > stopTime! {
timePlayed.text = dateComponentsFormatter.string(from: stopTime!, to: now)
} else {
stopTimer()
notifyTimerCompleted()
}
}
//Overtime Clock
#objc func startOvertimeClock() {
let startOvertime = Date()
overtimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.overtime.text = self?.dateComponentsFormatter.string(from: startOvertime, to: Date())
}
}
//Halftime Clock
#objc func startHalftimeClock() {
let startHalftime = Date()
halftimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.halftime.text = self?.dateComponentsFormatter.string(from: startHalftime, to: Date())
}
}
private func notifyTimerCompleted() {
timeToPlay.text = "End"
timePlayed.text = "End"
}
}

StreamDelegate stops to receive events after a amount of readings in swift

I have an iOS chat application that receives messages over a socket connection.
When user open the app after a long time and there are more than 50 unread messages, the server send over socket a message telling the number of unread messages, at this point the application show an alert with a progress bar and then the server send each one message.
So, the application get each one message on the StreamDelegate method stream(_ stream: Stream, handle eventCode: Stream.Event) and update the progress bar until the end of messages.
The problem is, when I have a large amount of unread messages (about 300+) at some point the StreamDelegate stops to receive the events with the messages, and none error messages is displayed.
I call the connect method on a global queue:
DispatchQueue.global().async {
self.connect(host, port: port)
}
This is my socket connect code:
fileprivate func connect(_ host: String, port: Int) {
postStatus(.connecting)
self.host = NSString(string: host)
self.port = UInt32(port)
self.log("connect to \(host):\(port)")
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, self.host, self.port, &readStream, &writeStream)
self.inOk = false
self.outOk = false
self.inputStream = readStream!.takeRetainedValue()
self.outputStream = writeStream!.takeRetainedValue()
self.inputStream.delegate = self
self.outputStream.delegate = self
let mainThread = Thread.isMainThread;
let loop = mainThread ? RunLoop.main : RunLoop.current
self.inputStream.schedule(in: loop, forMode: RunLoopMode.defaultRunLoopMode)
self.outputStream.schedule(in: loop, forMode: RunLoopMode.defaultRunLoopMode)
self.inputStream.open()
self.outputStream.open()
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(connectionTimeout), userInfo: nil, repeats: false)
if(!mainThread) {
loop.run()
}
}
In the StreamDelegate method stream(_ stream: Stream, handle eventCode: Stream.Event) I get the message event and process it on the method read(String)
case Stream.Event.hasBytesAvailable:
if let timer = timer {
timer.invalidate()
self.timer = nil
}
let json = ChatLibSwift.readMessage(self.inputStream)
do {
if StringUtils.isNotEmpty(json) {
try self.read(json)
}
} catch let ex as NSError {
LogUtils.log("ERROR: \(ex.description)")
}
break
case Stream.Event.hasSpaceAvailable:
break
The method that read each one message:
static func readMessage(_ inputStream: InputStream) -> String {
do {
var lenBytes = [UInt8](repeating: 0, count: 4)
inputStream.read(&lenBytes, maxLength: 4)
// header
let i32: Int = Int(UInt32.init(lenBytes[3]) | UInt32.init(lenBytes[2]) << 8 | UInt32.init(lenBytes[1]) << 16 | UInt32.init(lenBytes[0]) << 24 )
var msg = [UInt8](repeating: 0, count: (MemoryLayout<UInt8>.size * Int(i32)))
let bytesRead = inputStream.read(&msg, maxLength: Int(i32))
if bytesRead == -1 {
print("<< ChatLib ERROR -1")
return ""
}
let s = NSString(bytes: msg, length: bytesRead, encoding: String.Encoding.utf8.rawValue) as String?
if let s = s {
if bytesRead == Int(i32) {
return s
}
else {
print("Error: readMessage \(s)")
}
return s
}
return ""
} catch {
return ""
}
}
Anyone has idea how to solve it?
The main idea is to force schedule the reading of the stream after a successful read operation:
let _preallocatedBufferSize = 64 * 1024
var _preallocatedBuffer = [UInt8](repeating: 0, count: MemoryLayout<UInt8>.size * Int(_preallocatedBufferSize))
var message : ....
func readMessage(_ inputStream: InputStream) {
if !inputStream.hasBytesAvailable || message.isCompleted {
return
}
var theBuffer : UnsafeMutablePointer<UInt8>?
var theLength : Int = 0
// try to get buffer from the stream otherwise use the preallocated buffer
if !inputStream.getBuffer(&theBuffer, length:&theLength) || nil == theBuffer
{
memset(&_preallocatedBuffer, 0, _preallocatedBufferSize)
let theReadCount = inputStream.read(&_preallocatedBuffer, maxLength:_preallocatedBufferSize)
if theReadCount > 0 {
theBuffer = _preallocatedBuffer;
theLength = theReadCount;
} else {
theBuffer = nil;
theLength = 0;
}
}
if nil != theBuffer && theLength > 0 {
_message.appendData(theBuffer, length:theLength)
self.perform(#selector(readMessage), with:inputStream, afterDelay:0.0, inModes:[RunLoopMode.defaultRunLoopMode])
}
}

Create a InputStream that receives data through a socket in Swift 3 (iOS)

So my issue is getting a InputStream to work within swift for the iOS platform. I wrote some code and will post it below, but basically I have an Android client that sends data to the IP of the iOS client. Unfortunately the Android client says the connection was refused to connect to iOS client. I'm really not too sure what I'm doing wrong here as I think the code looks solid, so I'm kind of confused. By the way this my first time working with sockets in swift(iOS) so please understand I'm new to this and am here to learn through example code.
ViewControler.swift
var inputstream: InputStream?
var outputstream: OutputStream?
var host: CFString?
var port: UInt32?
override func viewDidLoad() {
super.viewDidLoad()
initNetworkCommunication()
}
func initNetworkCommunication(){
host = "192.168.1.187" as CFString
port = 55555
var readstream : Unmanaged<CFReadStream>?
var writestream : Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host!, port!, &readstream, &writestream)
inputstream? = readstream!.takeRetainedValue()
outputstream? = writestream!.takeRetainedValue()
inputstream?.delegate = self as! StreamDelegate
outputstream?.delegate = self as! StreamDelegate
inputstream?.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
outputstream?.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
inputstream?.open()
Status.text = "Input Stream Opened"
outputstream?.open()
Status.text = "Output Stream Opened"
}
func stream(_ aStream: Stream, handleEvent eventCode: Stream.Event) {
switch (eventCode){
case Stream.Event.errorOccurred:
print("Can not connect to the host!")
Status.text = "Cannot connect to: \(host ?? "Null Host" as CFString)"
break
case Stream.Event.endEncountered:
outputstream?.close()
outputstream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
outputstream = nil
break
case Stream.Event.hasBytesAvailable:
Status.text = "Recieving data..."
NSLog("HasBytesAvaible")
var buffer = [UInt8](repeating: 0, count: 4096)
if ( aStream == inputstream){
while (inputstream?.hasBytesAvailable)!{
var len = inputstream?.read(&buffer, maxLength: buffer.count)
if(len! > 0){
Status.text = "Collected data..."
var data1 = NSData(bytes: buffer, length: buffer.count)
Status.text = "Converted Data to NSData..."
var str64 = data1.base64EncodedData(options: .lineLength64Characters)
Status.text = "Decode Base64..."
let data: NSData = NSData(base64Encoded: str64 , options: .ignoreUnknownCharacters)!
// turn Decoded String into Data
Status.text = "Turning Base64 data into image..."
let dataImage = UIImage(data: data as Data)
Status.text = "Done"
// pass the data image to image View.:)
Display.image = dataImage
Status.text = "Image Displayed"
}
}
}
break
case Stream.Event.openCompleted:
NSLog("OpenCompleted")
break
case Stream.Event.hasSpaceAvailable:
NSLog("HasSpaceAvailable")
break
default:
print("Unknown event")
}
}
It sounds like the Android device is trying to connect to the iOS device, and from the code you've posted, the iOS device is trying to connect to the Android device. That's not how socket networking works.
One of the devices (or both, if this a peer to peer setup) needs to listen for connections on a particular port. See this guide (https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html#//apple_ref/doc/uid/CH73-SW8) for information about creating a listening socket from iOS.

swift OutputStream could not find member "write"

I'm trying for a while to send data to an OutputStream to communicate with another device. (For this i use the External Accessory Framework) But the function does not work. I have the error Could not find member "write". I hope somebody can help me to find the solution. thank you.
func _writeData() {
while (_session.outputStream.hasSpaceAvailable && _write.length > 0) {
var bytesWritten: Int = _session?.outputStream.write(_write.bytes, _write.length);
if(bytesWritten == -1){
println("write error");
break;
}
else if (bytesWritten > 0){
_write.replaceBytesInRange(NSMakeRange(0, bytesWritten), withBytes: nil, length: 0);
}
}
}
//high level write data method
func writeData(data: NSData) {
if(_write == nil) {
_write = NSMutableData.alloc();
}
_write.appendData(data);
self._writeData();
}
The Error --------> var bytesWritten: Int = _session?.outputStream.write(_write.bytes, _write.length);
There are the function where i open and close the session
//open a session with the accessory and set up the input and output stream on the default run loop
func openSession(accessory: EAAccessory, withProtocolString protocolString: String) -> ObjCBool{
_session = EASession(accessory: accessory, forProtocol: protocolString);
if(_session != nil) {
_session.inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode);
_session.inputStream.open()
_session.outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode);
_session.outputStream.open();
} else {
println("Creating session failed");
}
return (_session != nil);
}
//close the session with the accessory
func closeSession() {
_session.inputStream.close();
_session.inputStream.removeFromRunLoop(NSRunLoop(), forMode: NSDefaultRunLoopMode);
_session.outputStream.close();
_session.outputStream.removeFromRunLoop(NSRunLoop(), forMode: NSDefaultRunLoopMode);
}
after testing many things, I have been able to write to the socket, here is my code:
var inputStream: NSInputStream?
var outputStream: NSOutputStream?
NSStream.getStreamsToHostWithName("localhost", port: 8443, inputStream: &inputStream, outputStream: &outputStream)
outputStream?.open()
inputStream?.open()
//while(true){
//get input
//}
let data: NSData = "this is a test string".dataUsingEncoding(NSUTF8StringEncoding)!
outputStream?.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
you can test it using net cat
nc -l 8443

Resources