If you try ChatGPT on the official OpenAI website, you may notice that you do not have to wait for the neural network answer to be completely generated. Instead, you see beautiful word-by-word appearing of the answer dynamically in real-time. Even when it has not yet been fully received, you may already find some information you need or understand that your question is poorly formulated, and it is better to change the request and ask it again.
This behavior is much more user-friendly but somewhat more technically complicated. So, if you want to implement it in your iOS app, you have to write a little non-obvious code.
First, let’s write a regular network request, which simply waits for the server’s full response and only then returns it. In order to keep the article short enough, I omit nuances of forming a typical request and working with OpenAI API. I also do not pay sufficient attention to handling standard and typical network and server errors.
let urlSession = URLSession(configuration: .default)
let request: URLRequest! // A little mock code
urlSession.dataTask(with: request) { data, response, error in
guard let data, let string = String(data: data, encoding: .utf8) else {
print(error) // Handle network errors and basic parsing problems
return
}
print(string) // A response to our request in JSON format
}
OK, we can send a request and receive a response, but only when it is completely generated. Now, let’s try to fix this nuance. In the beginning, we should add to the body of our URLRequest
the stream
parameter and set it to the true
value (see OpenAI API documentation for details). Then remove the completionHandler
closure and add a delegate for our URLSession
.
let urlSession = URLSession(
configuration: .default,
delegate: self,
delegateQueue: nil
)
urlSession.dataTask(with: request)
After that, we have to implement the necessary methods of the delegate. But be careful — the type of the delegate
parameter in the URLSession
initializer is URLSessionDelegate
. It does not contain all the methods we need, so instead, we should implement its subprotocol URLSessionDataDelegate
.
OK, let’s start with the first necessary delegate method. Our URLSession
object will call it whenever it receives a chunk of a ChatGPT response.
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data
) {
guard let response = dataTask.response as? HTTPURLResponse else {
print("Some network error has occured. Internet connection, timeout, etc.")
return
}
guard (200 ..< 300).contains(response.statusCode) else {
print("Some HTTP error has occured, the code is \(response.statusCode)")
return
}
guard let string = String(data: data, encoding: .utf8) else {
print("Some parsing error has occured")
return
}
// A chunk of the response to our request, in JSON format with some meta
print(string)
}
Great, we have the working code and are ready to get ChatGPT response chunks!
But how to fully process the completion of a network request? Especially if the request is ended with an error. For example, if it times out or cannot reach the OpenAI server at all. Actually, we just need one more method in our URLSession
delegate.
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?
) {
if let error {
print(error)
return
}
guard let response = task.response as? HTTPURLResponse else {
print("Some network error has occured")
return
}
guard (200 ..< 300).contains(response.statusCode) else {
print("Some HTTP error has occured, the code is \(response.statusCode)")
return
}
print("The request is completed successfully")
}
That is all. Now, you just need to complete these drafts to a fully-designed iOS app — parse incoming JSON strings to your own models, correctly handle possible errors, and implement some user interface. I am sure you will succeed!
Thank you for your attention.