I have a question related to properly managing intensive tasks, like connecting to a server for an iOS client app. I have a server running in python. Both client and server code are below.
With the current code, I recieve the following warning:
Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a thread without a QoS class specified (base priority 33). Investigate ways to avoid priority inversions
PID: 31921, TID: 17707895
Backtrace
=================================================================
3 CFNetwork 0x000000019baeca08 estimatedPropertyListSize + 37652
4 CFNetwork 0x000000019b9573a8 cfnTranslateCFError + 2688
5 libdispatch.dylib 0x00000001019e67bc _dispatch_client_callout + 20
6 libdispatch.dylib 0x00000001019e834c _dispatch_once_callout + 140
7 CFNetwork 0x000000019b95737c cfnTranslateCFError + 2644
8 CFNetwork 0x000000019baec8c8 estimatedPropertyListSize + 37332
9 libdispatch.dylib 0x00000001019e67bc _dispatch_client_callout + 20
10 libdispatch.dylib 0x00000001019e834c _dispatch_once_callout + 140
11 CFNetwork 0x000000019baec790 estimatedPropertyListSize + 37020
12 CFNetwork 0x000000019baec81c estimatedPropertyListSize + 37160
13 libdispatch.dylib 0x00000001019e67bc _dispatch_client_callout + 20
14 libdispatch.dylib 0x00000001019e834c _dispatch_once_callout + 140
15 CFNetwork 0x000000019baec804 estimatedPropertyListSize + 37136
16 CFNetwork 0x000000019b9ac498 _CFStreamErrorFromCFError + 248272
17 CoreFoundation 0x000000019a7bd568 3A5F992A-D1CD-312E-BD2E-F7C66343A417 + 406888
18 LFA QUANTIFICATION 0x000000010090e490 $s18LFA_QUANTIFICATION6ClientC7connectyyF + 1648
19 LFA QUANTIFICATION 0x0000000100914294 $s18LFA_QUANTIFICATION4HomeC12logOutButtonyySo8UIButtonCF + 132
20 LFA QUANTIFICATION 0x000000010091440c $s18LFA_QUANTIFICATION4HomeC12logOutButtonyySo8UIButtonCFTo + 52
21 UIKitCore 0x000000019d807f78 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 14884728
22 UIKitCore 0x000000019d19c698 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 8152728
23 UIKitCore 0x000000019d19ca10 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 8153616
24 UIKitCore 0x000000019d199f0c 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 8142604
25 UIKitCore 0x000000019d19ba78 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 8149624
26 UIKitCore 0x000000019cbd74c0 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 2102464
27 UIKitCore 0x000000019cbd6e64 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 2100836
28 UIKitCore 0x000000019cbd60e4 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 2097380
29 UIKitCore 0x000000019cb98970 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 1845616
30 UIKitCore 0x000000019cb97010 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 1839120
31 UIKitCore 0x000000019cb95a1c 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 1833500
32 UIKitCore 0x000000019ca80d78 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 699768
33 UIKitCore 0x000000019ca80468 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 697448
34 UIKitCore 0x000000019ca80524 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 697636
35 CoreFoundation 0x000000019a79162c 3A5F992A-D1CD-312E-BD2E-F7C66343A417 + 226860
36 CoreFoundation 0x000000019a7908a8 3A5F992A-D1CD-312E-BD2E-F7C66343A417 + 223400
37 CoreFoundation 0x000000019a78f058 3A5F992A-D1CD-312E-BD2E-F7C66343A417 + 217176
38 CoreFoundation 0x000000019a78dd88 3A5F992A-D1CD-312E-BD2E-F7C66343A417 + 212360
39 CoreFoundation 0x000000019a78d968 CFRunLoopRunSpecific + 608
40 GraphicsServices 0x00000001dea834e0 GSEventRunModal + 164
41 UIKitCore 0x000000019cc00edc 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 2272988
42 UIKitCore 0x000000019cc00518 UIApplicationMain + 340
43 UIKitCore 0x000000019ce39734 7BF01CFC-23F1-326A-AFD8-AD967FFECE28 + 4601652
44 LFA QUANTIFICATION 0x000000010090ca10 $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 120
45 LFA QUANTIFICATION 0x000000010090c988 $s18LFA_QUANTIFICATION11AppDelegateC5$mainyyFZ + 44
46 LFA QUANTIFICATION 0x000000010090ca8c main + 28
47 dyld 0x00000001bdcaed84 7BE2B757-3B3D-3E91-8CB7-74F3887660C7 + 23940
I did quite a bit of reading into it and experimenting, and I tried to solve it with using DispathQueues to run in the background around the following code that calls the client class (from another view controller):
@IBAction func logOutButton(_ sender: UIButton) {
DispatchQueue.global(qos: .background).async {
self.clientSession.connect()
self.clientSession.send(message: "IMAGE")
self.clientSession.send(message: "Leaving")
self.clientSession.send(message: "!")
}
}
Using the .async flag, I was able to make the warnings go away, but then the client was unable to recieve input from my server (at least, I was unable to print the message that was recieved). I check the server-side, and the server was both receiving and sending data back to the iOS client as usual. Generally speaking, running with async always made the warning to away but the client seemed unable to respond correctly to the server (i.e seemed to hang/not recieve server messages)
My main question is this: how can I make these warnings go away without affecting the client’s ability to recieve messages from the server? It doesn’t seem like the warnings mess with the app’s functionality, but I would like to know why these issues arise and what is the recommended fix? My hunch is that I am not using DispatchQueues correctly.
Any guidance in any way would be helpful.
Current Code:
//
// Client.swift
// LFA QUANTIFICATION
import UIKit
class Client: NSObject {
var inputStream: InputStream!
var outputStream: OutputStream!
weak var delegate: ChatRoomDelegate?
var username = ""
let maxBufferLength = 4096
func connect() {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, ("ADDR" as CFString), PORT, &readStream, &writeStream) // placeholder for ADDR and PORT, filled in correctly when running
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
inputStream.delegate = self
outputStream.delegate = self
inputStream.schedule(in: .current, forMode: .common)
outputStream.schedule(in: .current, forMode: .common)
inputStream.open()
outputStream.open()
}
func byteArray<T>(from value: T) -> [UInt8] where T: FixedWidthInteger {
withUnsafeBytes(of: value.bigEndian, Array.init)
}
func send(message: String) {
let lengthStr : Int32 = Int32(message.count)
let bytes : [UInt8] = byteArray(from: lengthStr)
outputStream.write(bytes, maxLength: bytes.count) // send length of the message
// send the message
if let _ = message.data(using: .ascii) {
outputStream.write(message, maxLength: Int(lengthStr))
}
}
}
extension Client: StreamDelegate {
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case .openCompleted:
print("Stream Opened")
case .hasBytesAvailable:
print("here")
if aStream == inputStream {
recieveData()
}
case .errorOccurred:
print("Error")
case .endEncountered:
print("END")
default:
break
}
}
func recieveData() {
var buffer = [UInt8](repeating: 0, count: 1024)
let bytesRead = inputStream.read(&buffer, maxLength: buffer.count)
if bytesRead > 0 {
let recievedData = Data(buffer.prefix(bytesRead))
guard let recievedString = String(data: recievedData, encoding: .utf8) else { return }
self.delegate?.recieved(message: recievedString)
}
}
}
protocol ChatRoomDelegate: AnyObject {
func recieved(message: String)
}
// Server.py
import threading
import socket
UNKNOWN_BUFFER_LENGTH = 1024
PORT = 5050
SERVER = "localhost"
ADDR = (SERVER, PORT)
FORMAT = "utf-8"
DISCONNECT_MESSAGE = "!"
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADDR)
clients = set()
# List of all current client connnections
clients_lock = threading.Lock()
def receive_message(conn):
# Receive 4 bytes from the socket
data = conn.recv(4)
# Convert the received bytes to an integer using big-endian byte order
received_integer = int.from_bytes(data, byteorder='big')
msg = conn.recv(received_integer).decode(FORMAT)
return msg
def process_image(conn, addr):
print("Here")
message = f"[{addr}] Recieved Incoming Message"
conn.sendall(message.encode(FORMAT))
print("Sent message")
def handle_client(conn, addr):
print(f"[NEW CONNECTION] {addr} Connected")
try:
connected = True
while connected:
msg = receive_message(conn)
if msg == "IMAGE":
process_image(conn, addr)
if not msg:
break
if msg == DISCONNECT_MESSAGE:
connected = False
print(f"[{addr}] {msg}")
finally:
with clients_lock:
clients.remove(conn)
conn.close()
def start():
print('[SERVER STARTED]')
server.listen()
while True:
conn, addr = server.accept()
with clients_lock:
clients.add(conn)
print(f'[CONNENCTED CLIENTS] {len(clients)}')
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.start()
start()
\ View Controller that Calls the Client class, with the code snippet discussed above (where I tried to fix with async)
//
// Home.swift
// LFA QUANTIFICATION
import UIKit
class Home: UIViewController {
let clientSession = Client()
@IBOutlet weak var cameraPhotoButton: UIButton!
@IBOutlet weak var homeText: UILabel!
var user: String!
override func viewDidLoad() {
super.viewDidLoad()
clientSession.delegate = self
homeText.text = "Hello " + user + "! How are you feeling today?"
homeText.numberOfLines = 5
// Button Appearance properties
cameraPhotoButton.titleLabel!.textAlignment = .center
cameraPhotoButton.titleLabel!.numberOfLines = 0
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
@IBAction func capturePhoto(_ sender: UIButton) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "cam") as? Camera else {
return
}
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true, completion: nil)
}
@IBAction func logOutButton(_ sender: UIButton) {
self.clientSession.connect()
self.clientSession.send(message: "IMAGE")
self.clientSession.send(message: "Leaving")
self.clientSession.send(message: "!")
}
//clientSession.send(message: "hello")
//clientSession.send(message: "!")
}
extension Home: ChatRoomDelegate {
func recieved(message: String) {
print("Chatroom: ", message)
}
}