Mutating function inside structure - swift

I'm using Swift 4, I have a structure that I initialize with default values.
I made a function inside that is supposed to read a JSON and change those default values with what it gets but it doesn't seem to work.
Error: Closure cannot implicitly capture a mutating self parameter
Code:
struct Workspace: Decodable {
var guid: String
var name: String
private enum CodingKeys : String, CodingKey {
case guid = "guid"
case name = "name"
}
init(){
self.guid = "blbl"
self.name = "oops"
}
mutating func getUserWorkspace(base: String, completed: #escaping () -> ()){
let url = URL(string: "some url")!
var request = URLRequest(url: url)
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data!)
self.guid = res.guid //Error here
self.name = res.name //Error here
DispatchQueue.main.async {
completed()
}
}catch {
print ("JSON error")
}
}
}.resume()
}
I changed let to var, but I guess there's still something I don't understand..

//Try this one and let me know
struct Workspace: Decodable {
static var shared = Workspace()
var guid: String
var name: String
private enum CodingKeys : String, CodingKey {
case guid = "guid"
case name = "name"
}
init(){
self.guid = "blbl"
self.name = "oops"
}
mutating func getUserWorkspace(base: String, completed: #escaping () -> ()){
let url = URL(string: "some url")!
var request = URLRequest(url: url)
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data!)
Workspace.shared.guid = res.guid //Error here
Workspace.shared.name = res.name //Error here
DispatchQueue.main.async {
completed()
}
}catch {
print ("JSON error")
}
}
}.resume()
}
}

I would suggest you to use class instead of struct. Anyway if you like to use your code, then capture the self inside your mutation method like below:
mutating func getUserWorkspace(base: String, completed: #escaping () -> ()){
let url = URL(string: "some url")!
var request = URLRequest(url: url)
var myself = self
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data!)
myself.guid = res.guid //Error here
myself.name = res.name //Error here
DispatchQueue.main.async {
completed()
}
}catch {
print ("JSON error")
}
}
}.resume()
}

I actually found the problem.. and it was pretty stupid.. I just had to change the "completed" part of the function, like this:
mutating func getUserWorkspace(base: String, completed: #escaping (_ arr:[Workspace]?) -> ()){
let url = URL(string: "SOME URL")!
var request = URLRequest(url: url)
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let rep = try JSONDecoder().decode([Workspace].self, from: data!)
DispatchQueue.main.async {
completed(rep)
}
}catch {
print(error)
}
}
}.resume()
}

Related

Mutating Struct property with asynchronous function

I have the following Struct that I want to initialize, and then use its method query() to mutate its result property.
Query() sends and fetches JSON data, then decodes it to a String. When I declare query() as a mutating function, I receive the error "Escaping closure captures mutating 'self' parameter" in my URLSession.
What do I need to change?
The call:
var translation = Translate(string: "hello", base: "en", target: "de", result: "")
translation.query()
let translated = translation.result
The struct:
struct Translate {
let string: String, base: String, target: String
var result: String
mutating func query() {
let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
let bodyData = try? JSONSerialization.data(withJSONObject: body)
guard let url = URL(string: "https://libretranslate.com/translate") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = bodyData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
DispatchQueue.main.async {
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
if responseJSON["translatedText"] != nil {
self.result = responseJSON["translatedText"] as! String
}
}
}
return
}
.resume()
}
}
Xcode error:
There are many issues in the code.
The most significant issue is that the URLRequest is asynchronous. Even if no error occurred result will be always empty.
You have to add a completion handler – it fixes the errors you got by the way – and it's highly recommended to handle all errors.
Instead of JSONSerialization the code uses JSONDe/Encoder
struct Translation : Decodable { let translatedText : String }
struct Translate {
let string: String, base: String, target: String
func query(completion: #escaping (Result<String,Error>) -> Void) {
let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
do {
let bodyData = try JSONEncoder().encode(body)
let url = URL(string: "https://libretranslate.com/translate")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = bodyData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(Translation.self, from: data!).translatedText} )
}
.resume()
} catch {
completion(.failure(error))
}
}
}
let translation = Translate(string: "hello", base: "en", target: "de")
translation.query() { result in
DispatchQueue.main.async {
switch result {
case .success(let translated): print(translated)
case .failure(let error): print(error)
}
}
}
Both exclamation marks (!) are safe.

Swift used Codable, but the type is not correct

I know that Codable = Decodable & Encodable but when calling json from xcode,
Codable was given as a struct, but an error saying
Argument type'login.Type' does not conform to expected type'Encodable' appears.
json code
struct login: Codable {
var userId: String?
var userPw: String?
class func LoginBoard(_ completeHandler: #escaping (login) -> Void) {
let loginboard: String = MAIN_URL + "/member/login"
guard let url = URL(string: loginboard) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.httpBody = try? JSONEncoder().encode(login) // ERROR [Argument type 'login.Type' does not conform to expected type 'Encodable']
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
print("error calling Post on /todos/1")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
let decoder = JSONDecoder.init()
let LoginList = try decoder.decode(login.self, from: responseData)
completeHandler(LoginList)
}
catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
There is no error in try decoder.decode
but only in urlRequest.httpBody = try? JSONEncoder().encode(login) what is the problem?
You need to have something like this to set the values.
let loginvalues = login(userId: "john", userPw: "adfadfa")
urlRequest.httpBody = try? JSONEncoder().encode(loginvalues)
If you place this inside a play ground and run it you will see that you get the json data.
struct Login: Codable {
var userId: String?
var userPw: String?
}
let loginvalues = Login(userId: "john", userPw: "adfadfa")
let test = try? JSONEncoder().encode(loginvalues)
print(String(data: test!, encoding: .utf8)!)

Sending String Array via Alamofire - Swift

I want to send more than one "davetiyeID". For this, I send the DAVMED_ID array as a parameter. But when printing, I get blank output. What do I need to do to send more than one value? So I want to send more than one davetiyeID. Is it possible for me to do this? Can I send 3-4 invitation IDs instead of one davetiyeID?
extension Array {
/// Convert the receiver array to a `Parameters` object.
func asParameters() -> Parameters {
return [arrayParametersKey: self]
}
}
var DAVMED_ID : [String] = []
public struct ArrayEncoding: ParameterEncoding {
public let options: JSONSerialization.WritingOptions
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters,
let array = parameters[arrayParametersKey] else {
return urlRequest
}
do {
let data = try JSONSerialization.data(withJSONObject: array, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
#objc func favKontrol(){
let url = URL(string: "...")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
Alamofire.request(
url,
method: .post,
parameters: DAVMED_ID.asParameters(),
encoding: ArrayEncoding(),
headers: nil).responseJSON { (responseData) -> Void in
switch (responseData.result) {
case .success(let result):
...
}

URLSession.shared.dataTask block not running

I'm trying to make a request to an API. Without the completion handler, the request proceeds without issue. However, now that I've added in a completion handler, the URLSession.shared.dataTask block doesn't appear to run at all; the second print statement never prints. How should I adjust my code to fix this issue?
func makeRequestToApi(word: String, completionHandler: #escaping ([String]) -> Void) {
var array = [String]()
let appId = ""
let appKey = ""
let language = "en-us"
let unwrappedURL = URL(string: "https://od-api.oxforddictionaries.com:443/api/v2/entries/\(language)/\(word)")!
var request = URLRequest(url: unwrappedURL)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue(appId, forHTTPHeaderField: "app_id")
request.addValue(appKey, forHTTPHeaderField: "app_key")
print("THIS STATEMENT PRINTS...")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
print("AND THIS STATEMENT")
if let data = data,
let _ = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) {
do {
let root = try JSONDecoder().decode(Root.self, from: data)
let results = root.results
for result in results {
for lexical in result.lexicalEntries {
for entry in lexical.entries {
for sense in entry.senses {
for example in sense.examples {
array.append(example.text)
}
}
}
}
}
} catch {
print(error)
}
completionHandler(array)
}
}
dataTask.resume()
}
This is the function that I used on another view controller that returned data for the same words:
func makeRequestToApi() {
let appId = ""
let appKey = ""
let language = "en-us"
let strictMatch = "false"
var word = search
word = word.lowercased()
let oxDicURL = URL(string: "https://od-api.oxforddictionaries.com:443/api/v2/entries/\(language)/\(word)?strictMatch=\(strictMatch)")
if let unwrappedURL = oxDicURL {
var request = URLRequest(url: unwrappedURL)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue(appId, forHTTPHeaderField: "app_id")
request.addValue(appKey, forHTTPHeaderField: "app_key")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data,
let _ = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) {
do {
let root = try JSONDecoder().decode(Root.self, from: data)
let results = root.results
for result in results {
for lexical in result.lexicalEntries {
for entry in lexical.entries {
for sense in entry.senses {
for example in sense.examples {
print(example.text)
self.array.append(example.text)
}
}
}
}
}
self.search = word
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "goToDetail", sender: nil)
}
} catch {
print(error)
}
}
}
dataTask.resume()
}
}

Get value from callback function swift

Question I want to get the value returned from my ApiToken function so I can use it in another function. For some reason I can not get the value from this function it will not return anything. How could I return the value from my ApiToken function and use it in another function.
Here is my GetApiToken class with the ApiToken function
class GetApiToken {
public func ApiToken(link: String, completionBlock: #escaping (String) -> Void) -> Void
{
let url = URL(string: link)!
let jsonDict = ["username": "snow", "password": "ssssssssss"]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonDict, options: [])
var request = URLRequest(url: url)
request.httpMethod = "post"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error:", error)
return
}
do {
guard let data = data else { return }
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] else { return }
//self.token = json["access_token"] as? String ?? "x"
completionBlock((json["access_token"] as? String)!)
} catch {
print("error:", error)
}
}
task.resume()
}
}
Here is where I am trying to get the value
func getData(_ link:String)
{
let url = URL(string: link)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 20)
request.httpMethod = "GET"
var output = ""
GetApiToken().ApiToken(link: "http://localhost:5000/auth", completionBlock: { str in
output = str
})
request.addValue("JWT \(output)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type") ..........
It's an asynchronous call, so you need to put everything that will happen once the data has been retrieved in the completion callback
func getData(_ link:String)
{
let url = URL(string: link)!
var request = URLRequest(url: url,
cachePolicy: .reloadIgnoringCacheData,
timeoutInterval: 20)
request.httpMethod = "GET"
GetApiToken().ApiToken(link: "http://localhost:5000/auth",
completionBlock:
{ output in
request.addValue("JWT \(output)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
.......
})