iOS - Swift : fetching data from database in main thread, not in background - ios

In my iOS App i'm able to download data from a database, but actually all the operations are made in background and the main thread is still active, even the GUI. I also tried to make a 'sleep' with
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { ... }
With this delay everthing works fine, but it's not a good solution. How can i change my code to do this in the main thread? Possibly with loadingIndicator.
This is my code (checking if username exists):
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String)
{
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
var _errorMsg = ""
var _psw = ""
var parameters : [String : Any]?
parameters = ["username": username,
"action": "login"]
print(parameters!)
let session = URLSession.shared
let url = "http://www.thetestiosapp.com/LoginFunctions.php"
let request = NSMutableURLRequest()
request.url = URL(string: url)!
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField:"Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters!, options: .sortedKeys)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
_psw = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
passwordFromDb = _psw
errorMsg = _errorMsg
}

You’re attempting to update passwordFromDb and errorMsg at the end of this method. But this is an asynchronous method and and those local variables _psw and _errorMsg are set inside the closure. Rather than trying to defer the checking of those variables some arbitrary three seconds in the future, move whatever “post request” processing you need inside that closure. E.g.
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let parameters = ...
let session = URLSession.shared
var request = URLRequest()
...
do {
request.httpBody = ...
let task = session.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse,
let statusCode = httpResponse.statusCode {
print ("status code = \(statusCode)")
}
guard let data = data else {
print (error ?? "Unknown error")
return
}
let password = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
DispatchQueue.main.async {
// USE YOUR PASSWORD AND ERROR MESSAGE HERE, E.G.:
self.passwordFromDb = password
self.errorMsg = _errorMsg
// INITIATE WHATEVER UI UPDATE YOU WANT HERE
}
}
task.resume()
} catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
}

Related

How to send api response to UIViewController using Combine

I am updating my collectionView with a response from my api using Combine to provide real time info. My api returns NSArray which is working but for some strange reasons inside my SearchAPI class I can receive the response and print it out in the console print("Our array ", searchArray) but can't sink to my UIViewController and update my collectionView accordingly. print("value ", value) Value is always empty
import Foundation
import Combine
class SearchAPI {
static let shared = SearchAPI()
func fetchData(url: String, category: String, queryString: String) -> Future<NSArray, Error>{
var searchArray: NSArray = []
let urlString = url
print("url come ", urlString)
guard let url = URL(string: urlString) else {
fatalError()
}
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
guard let data = data, error == nil else {
return
}
do{
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? NSArray
if let responseJson = json {
searchArray = responseJson
print("Our array", searchArray)
}
}
}
task.resume()
return Future { promixe in
DispatchQueue.main.async {
promixe(.success(searchArray))
}
}
}
}
//In my UIViewController
var observers: [AnyCancellable] = []
let action = PassthroughSubject<NSArray, Never>()
var category = "tv"
var queryString = ""
private var models: NSArray = []
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isMovieSelected {
btnMoviesBottomBorder.backgroundColor = .secondaryPink
btnAlbumsBottomBorder.backgroundColor = .systemGray
btnAlbumsBottomBorder.backgroundColor = .systemGray
}
txtSearch.searchTextField.addTarget(self, action: #selector(searchItem), for: .editingChanged)
}
#objc func searchItem(){
moviesView.alpha = 0
albumsView.alpha = 0
booksView.alpha = 0
lblSrchResults.alpha = 1
queryString = txtSearch.text!.replacingOccurrences(of: " ", with: "%20")
print("who is calling ", queryString)
let url = "https://endpoint?category=\(category)&query=\(queryString)"
SearchAPI.shared.fetchData(url: url, category: category, queryString: queryString)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("finished")
case .failure(let error):
print(error)
}
}, receiveValue: { [weak self] value in
print("value ", value)
self?.models = value
self?.searchCollectionView!.reloadData()
}).store(in: &observers)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("Fetched ", models.count)
return models.count
}
In your fetchData function, you create a dataTask to run the URL request. At some point later on that dataTask will complete and return an array.
Then you create a Future and send some code off to run on the main queue. That code has nothing inside of it to make it wait until the dataTask completes.
The code inside the Future is going to run "right away" and complete before a value has been assigned to searchArray.
You need to change your Future so that it only resolves (only completes) after the dataTask is done. Just tweaking your code a bit it looks something like:
func fetchData(url: String, category: String, queryString: String) -> Future<NSArray, Error>
{
return Future { promixe in
var searchArray: NSArray = []
let urlString = url
print("url come ", urlString)
guard let url = URL(string: urlString) else {
fatalError()
}
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data,
error == nil else {
promixe(.failure(error!))
return
}
do{
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? NSArray
if let responseJson = json {
searchArray = responseJson
print("Our array", searchArray)
promixe(.success(searchArray))
}
}
}
task.resume()
}
}
I typed this into a playground, but I did not run it, so additional changes may be necessary.
All of the action now happens inside the future. Once you are in a future block then you should call its resolution function (in your code called promixe) for all success or failure cases.
I changed your code so that promixe is called ONLY when the data task completes (successfully or unsuccessfully).
For your code you should probably also:
Be sure to call proximate, once, on every path through your Futures block. In particular you need to catch the error that JSONSerialization might give you and called promixe to report the error. Otherwise your future may not complete if data comes back from the server, but it cannot be parsed.
Change your code to use Swift style JSON instead of using JSONSerialization that way you will have an [SomeType] instead of NSArray. Use the type system to your advantage to reduce the possibility of errors.
Instead of doing queryString = txtSearch.text!.replacingOccurrences(of: " ", with: "%20") use
txtSearch.text!.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed). It will cover more cases if the user types
something unexpected.

How to Decode Apple App Attestation Statement?

I'm trying to perform the Firebase App Check validation using REST APIs since it's the only way when developing App Clips as they dont' support sockets. I'm trying to follow Firebase docs. All I'm having trouble with is the decoding of the App Attestation Statement.
So far I've been able to extract the device keyId, make Firebase send me a challenge to be sent to Apple so they can provide me an App Attest Statement using DCAppAttestService.shared.attestKey method.
Swift:
private let dcAppAttestService = DCAppAttestService.shared
private var deviceKeyId = ""
private func generateAppAttestKey() {
// The generateKey method returns an ID associated with the key. The key itself is stored in the Secure Enclave
dcAppAttestService.generateKey(completionHandler: { [self] keyId, error in
guard let keyId = keyId else {
print("key generate failed: \(String(describing: error))")
return
}
deviceKeyId = keyId
print("Key ID: \(deviceKeyId.toBase64())")
})
}
func requestAppAttestChallenge() {
guard let url = URL(string: "\(PROJECT_PREFIX_BETA):generateAppAttestChallenge?key=\(FIREBASE_API_KEY)") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { [self] data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
return
}
guard let data = data, error == nil else {
return
}
let response = try? JSONDecoder().decode(AppAttestChallenge.self, from: data)
if let response = response {
let challenge = response.challenge
print("Response app check challenge: \(challenge)")
print("Response app check keyID: \(deviceKeyId)")
let hash = Data(SHA256.hash(data: Data(base64Encoded: challenge)!))
dcAppAttestService.attestKey(deviceKeyId, clientDataHash: hash, completionHandler: {attestationObj, errorAttest in
let string = String(decoding: attestationObj!, as: UTF8.self)
print("Attestation Object: \(string)")
})
}
}
task.resume()
}
I tried to send the attestation object to Firebase after converting it in a String, although I wasn't able to properly format the String. I see from Apple docs here the format of the attestation, but it isn't really a JSON so I don't know how to handle it. I was trying to send it to Firebase like this:
func exchangeAppAttestAttestation(appAttestation : String, challenge : String) {
guard let url = URL(string: "\(PROJECT_PREFIX_BETA):exchangeAppAttestAttestation?key=\(FIREBASE_API_KEY)") else {
return
}
let requestBody = ExchangeAttestChallenge(attestationStatement: appAttestation.toBase64(), challenge: challenge, keyID: deviceKeyId)
let jsonData = try! JSONEncoder().encode(requestBody)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("Exchange App Attestation \(String(describing: response))")
return
}
guard let data = data, error == nil else {
return
}
print("Exchange App Attestation: \(data)")
}
task.resume()
}

Getting a value from web call in Swift [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
I am trying to get the returned value from a PHP script in Swift. However, it seems as though I keep getting the error:
Unexpectedly found nil while unwrapping an Optional value
Here is the class:
var value: String!
func run(idNumber: Int) {
let request = NSMutableURLRequest(url: URL(string: "https://mywebsite.com/file.php")!)
request.httpMethod = "POST"
let postString = "a=Hello"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
//answer = error;
}
let answerString = String(data: data!, encoding: String.Encoding.utf8)
self.value = answerString
}
task.resume()
}
func getValue() -> String{
return value
}
The error occurs when calling the getValue() function. However, when I print out the "answerString" as soon as it is created, it prints out successfully!
The functions are called here:
let access = ApiAccess()
access.run(idNumber: 0)
print(access.getValue())
Making a request is an asynchronous task. You need to wait the closure callback to be call before calling getValue.
You can add a closure callback to your run method. That way you will know when the request has finished and you can print the result:
var value: String!
func run(idNumber: Int, #escaping callback: () -> Void) {
let request = NSMutableURLRequest(url: URL(string: "https://mywebsite.com/file.php")!)
request.httpMethod = "POST"
let postString = "a=Hello"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
//answer = error;
}
let answerString = String(data: data!, encoding: String.Encoding.utf8)
self.value = answerString
callback()
}
task.resume()
}
func getValue() -> String{
return value
}
let access = ApiAccess()
access.run(idNumber: 0) {
print(access.getValue())
}
The issue is that the callback for URLSession.shared.dataTask() happens asynchronously, so you'll end up executing access.getValue() before your var value is ever set. This means that value is forcefully unwrapped before it has a value, which causes this error.
To workaround this, consider using promises, RxSwift, or similar async tools so that you only access values when available.
Refactor your run(idNumber:) function to take a completion handler:
func run(idNumber: Int, completion: (String?, Error?)-> Void ) {
let request = NSMutableURLRequest(url: URL(string: "https://mywebsite.com/file.php")!)
request.httpMethod = "POST"
let postString = "a=Hello"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
completion(nil, error)
}
let answerString = String(data: data!, encoding: String.Encoding.utf8)
self.value = answerString
completion(answerString, nil)
}
task.resume()
}
And call it like this:
let access = ApiAccess()
access.run(idNumber: 0) { result, error in
guard let result = result else {
print("No result. Error = \(error)")
return
}
print("result = \(result)")
}
(Or use Futures or Promises, as mentioned by #JohnEllmore in his answer)

Swift - Multiple Parameters to webservice

I have the following code that should send a username and password off to a webservice, in return I get a single integer back:
func attemptLogin() {
let url:URL = URL(string: endpoint+"/LoginNew")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let postString = "username="+txtUsername.text! + "; password="+txtPassword.text!
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest) {
(
data, response, error) in
guard let data = data, let _:URLResponse = response, error == nil else {
print("error")
return
}
let dataString = String(data: data, encoding: String.Encoding.utf8)
print(dataString)
}
task.resume()
}
In my function I need to add two parameters are I'm trying to do in this line:
let postString = "username="+txtUsername.text! + "; password="+txtPassword.text!
request.httpBody = postString.data(using: String.Encoding.utf8)
I am getting the following response from my web service when I run this however
Optional("Missing parameter: password.\r\n")
I am obviously not appending the parameters to the request properly but I'm not sure what I've done wrong.
It is good practice to avoid using explicit unwraps of optionals (using !), use guard let for text i UITextFields instead.
And why not separate into two methods, attemptLogin and login, which maybe can take a closure for code to execute when sign in completed? Maybe the closure can take an Result enum.
Like this:
typealias Done = (Result) -> Void
enum MyError: Error {
case unknown
}
enum Result {
case success(String)
case failure(MyError)
init(_ error: MyError) {
self = .failure(error)
}
init(_ dataString: String) {
self = .success(dataString)
}
}
func login(username: String, password: String, done: Done? = nil) {
let session = URLSession.shared
guard
let url = URL(string: endpoint+"/LoginNew"),
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let postString = "username=\(username)&password=\(password)"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request) {
(data, response, error) in
guard let data = data else { done?(Result(.unknown)); return }
let dataString = String(data: data, encoding: String.Encoding.utf8)
done?(Result(dataString))
}
task.resume()
}
func attemptLogin() {
guard
let username = txtUsername.text,
let password = txtPassword.text
else { return }
login(username: username, password: password) {
result in
swicth result {
case .success(let dataString):
print(dataString)
case .failure(let error):
print("Failed with error: \(error)")
}
}
}
Disclaimer: Have not tested the code above, but hopefully it compiles (at least with very small changes).

Making a re-useable function of JSON URL fetching function in SWIFT 2.0

I am stuck in a problem. I think it is all due to my weak basics. I am sure someone can help me easily and put me in the right direction.
I have different segues and all get the data from JSON via remote URL.
So in-short all segues need to open URL and parse JSON and make them into an ARRAY
I have made the first segue and it is working fine.
Now i plan to use the functions where it download JSON and turns it into ARRAY as a common function
I read in another page on stackoverflow that I can declare all common functions outside the class in ViewController
I hope everyone is with me this far.
now in ViewController i declare a function
getDataFromJson(url: String)
This function code looks like following
func getJsonFromURL(url: String)
{
// some class specific tasks
// call the common function with URL
// get an array
let arrJSON = getJsonArrFromURL(url)
for element in arrJSON
{
// assign each element in json to ur table
print("Element: \(element)")
}
// some class specific tasks
}
and this will call the common function declared outside the score of class
getArrFromJson(url: String) -> NSArray
This common function is just very generic.
Take a URL, call it, open it, parse its data into ARRAY and return it back.
The problem i am stuck is where to put the return
It returns empty array as the task is not finished and i am clueless
func getJsonArrFromURL(var url: String) -> NSArray
{
var parseJSON : NSArray?
if ( url == "" )
{
url = self.baseURLHomepage
}
print("Opening a JSON URL \(url)")
let myUrl = NSURL(string: url);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let postString = "";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if ( error != nil )
{
print("Error open JSON url \n\(error)")
return
}
do
{
parseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
}
catch
{
self.showAlert("Error", msg: "Error occurred while trying to process the product information data")
print("Error occured in JSON = \(error)")
}
}
task.resume()
return parseJSON!
}
You can probably add a method like below in any of your class
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
print("response String is nil")
completionHandler(nil, error)
return
}
if let dataNew = data {
completionHandler(NSString(data: (NSData(base64EncodedData: dataNew, options: NSDataBase64DecodingOptions([])))!, encoding: NSASCIIStringEncoding), nil)
}
}
}
task.resume()
return task
}
and access it anywhere like
let url = "your URL String"
let info = "The data you would like to pass"
yourClassName.post(url, info: info) { responseString, error in
guard responseString != nil else {
print("response String is nil")
print(error)
return
}
do {
if !(responseString as? String)!.isEmpty {
let json = try NSJSONSerialization.JSONObjectWithData((responseString as! String).data, options: NSJSONReadingOptions.init(rawValue: 0))
//process your json here
}
} catch {
print("Error\n \(error)")
return
}
}
Extend your string like follows
extension String {
var data:NSData! {
return dataUsingEncoding(NSUTF8StringEncoding)
}
}

Resources