How to accept any type of data from sockets in SwiftUI - swift

I'm fairly new to swift programming. I am playing with sockets and am trying to understand this snippet:
socket.on("Hello") { [weak self] (data, ack) in
if let data = data[0] as? [String: String],
let rawMessage = data["msg"]
{
DispatchQueue.main.async {
self?.messages.append(rawMessage)
}
}
}
I can understand the first bit; socket.on("Hello"). It declares what to do when the socket receives "Hello". And I also know that self?.messages.append(rawMessage) is appending the message to a list. However, the format of the other parts is confusing to me. I am trying to modify it so I can accept any type of data. I think that if let data = data[0] as? [String:String] is filtering the data. But when I remove it and its parentheses, XCode throws error's at me. Could you please explain to me this code and how I should go about modifying it?

Related

Using data from a URLSession and understanding Completion Handlers in Swift 5 [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 9 months ago.
This post was edited and submitted for review 6 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have code where I am creating a URLSession and trying to return the data obtained from this so it can be used outside of the function that the URLSession is in. My code looks like:
var nameStr = ""
var ingStr = ""
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
let str = String(data: data, encoding: .utf8)!
let str2 = convertStringToDictionary(text: str)
nameStr = "Name: \(str2!["title"] as! NSString)"
ingStr = "Ingredients: \((str2!["ingredientList"] as! NSString).components(separatedBy: ", "))"
print("First ", nameStr)
print(ingStr)
}
task.resume()
print("Second ", nameStr)
print(ingStr)
The first print statement works as expected, but the second only prints "Second ". I looked at Swift return data from URLSession and although I realize it does have a viable solution, I do not understand the whole completion block part and have a feeling others might be on the same boat. Any suggestions?
You said:
The first print statement works as expected, but the second only prints "Second ". I looked at Swift return data from URLSession …
The accepted answer to that question is the correct answer here, too.
What you’ve labeled as “first” and “second” in your code snippet are actually backwards. If you look carefully, you will see that “second” print statement (with no results yet) is actually happening before you see your “first” print statement! More to the point, your execution path will reach that “second” print statement before you’ve received and parsed the response from the network call.
That is the root of the issue, namely that the code inside the closure passed to dataTask method happens “asynchronously”, i.e. later. You can’t print your strings right after the resume statement (your “second” print statement) because those strings have not been set yet! Likewise, obviously cannot return these strings, either, because, again, they are not populated until well after you’ve returned from this function. This is all because the dataTask closure has not run yet! The completion handler closure, described in that other answer, is the solution.
Consider this code snippet:
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
guard
let data = data,
let string = String(data: data, encoding: .utf8),
let dictionary = convertStringToDictionary(text: string),
let title = dictionary["title"] as? String,
let ingredientsString = dictionary["ingredientList"] as? String
else { return }
let ingredients = ingredientsString.components(separatedBy: ", ")
// you’ve got `title` and `ingredients` here …
}
task.resume()
// … but not here, because the above runs asynchronously
This is basically your code, refactored to eliminate the ! forced unwrapping operators and the NSString references. But that’s not the relevant piece of the story. It is the those two comments, where I’m showing where you have results (i.e., in the closure) and where you do not (i.e., immediately after the resume). If you’re not familiar with asynchronous programming, this might look deeply confusing. But it is the important lesson, to understand exactly what “asynchronous” means.
… but I do not understand the whole completion block part. Any suggestions?
Yep, the answer to your question rests in understanding this “completion handler” pattern. The dataTask method has a completion handler closure parameter, a pattern that you have to repeat in your own method.
So, going back to my code snippet above, how precisely do you “return” something that is retrieved later? The answer: You technically do not return anything. Instead, add a completion handler closure parameter to your method and call that method with a Result, which is either some model object upon success (e.g., perhaps a Recipe object), or an Error upon failure. And to keep it simple for the caller, we will call that completion handler on the main queue.
Thus, perhaps:
func fetchRecipe(with url: URL, completion: #escaping (Result<Recipe, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
guard
let data = data,
let string = String(data: data, encoding: .utf8),
let dictionary = convertStringToDictionary(text: string),
let title = dictionary["title"] as? String,
let ingredientsString = dictionary["ingredientList"] as? String
else {
DispatchQueue.main.async {
completion(.failure(error ?? URLError(.badServerResponse)))
}
return
}
let ingredients = ingredientsString.components(separatedBy: ", ")
let recipe = Recipe(title: title, ingredients: ingredients)
DispatchQueue.main.async {
completion(.success(recipe))
}
}
task.resume()
}
This is basically the same as my above code snippet, except that I:
Wrapped it in a fetchRecipe method;
I don’t return anything;
I gave that method a completion parameter, which is a completion handler closure;
I call this completion closure when the network request completes, passing back either a .failure() or a .success(), obviously depending upon whether it failed or succeeded.
For the sake of completeness, this is the Recipe model object that I used, to wrap everything returned from the server in a nice, simple object:
struct Recipe {
let title: String
let ingredients: [String]
}
And you would call it like so:
fetchRecipe(with: url) { result in
// use `result` here …
switch result {
case .failure(let error):
print(error)
case .success(let recipe):
print(recipe)
// update your model and UI using `result` here …
}
}
// … but not here, because, again, the above runs asynchronously
I have to say, it feels deeply wrong to convert the Data to a String and then to a dictionary and then use dictionary key string literals. Usually, for example, we’d have a server that returned JSON and we would just parse the Data directly with JSONDecoder. But you did not share this convertStringToDictionary, so I wasn’t able to offer any meaningful advice on that score. But that’s something for you to tackle once you have your immediate problem behind you.

Calling function in Swift 5 causing error "Type of expression is ambiguous without more context"

When calling my function to fetch some locations from an API I'm getting the error "Type of expression is ambiguous without more context".
As I'm relatively new to iOS development and swift I have to ask you guys. Hope its just some simple thing I've overseen. I already searched the web, but nothing helped.
Thank you in advance!
Here is how I'm calling the function:
fetchLocation(latitude: latitude, longitude: longitude, locationsCompletionHandler: {
shopSites in
ForEach (shopSites){ }
})
And here is the function:
func fetchLocation(latitude: Double, longitude: Double, locationsCompletionHandler: #escaping ([ShopSite]) -> Void) {
let semaphore = DispatchSemaphore (value: 0)
let domain = "http://e2cadda8d178.ngrok.io/api/v1/"
let urlString = domain + "public/location/" + String(latitude) + "/" + String(longitude) + "/50"
//let url = URL(string: "e2cadda8d178.ngrok.io/api/v1/public/location/66.68994/10.249066/50")!
let url = URL(string: urlString)!
var request = URLRequest(url: url,timeoutInterval: Double.infinity)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { json, response, error in
guard let json = json else {
print(String(describing: error))
semaphore.signal()
locationsCompletionHandler([])
return
}
let str = String(decoding: json, as: UTF8.self)
print(str)
let shopSites: [ShopSite] = try! JSONDecoder().decode([ShopSite].self, from: json)
print(shopSites.count)
semaphore.signal()
locationsCompletionHandler(shopSites)
}
task.resume()
semaphore.wait()
Let me now if you need further details.
So, the debug information you get in Swift is based on the output from the compiler. Unfortunately, that means sometimes you get really terrible error messages like this when the compiler cannot figure out what you're trying to do.
I think your problem boils down to the fact that you're using ForEach which is a type of view from SwiftUI when you really want a simple for loop like:
for site in shopSites.
You could also do shopSites.forEach { }, which would be another way of iterating over that array.
Anyways, I think using a view that looks like it is named like it should be a loop where you really want some kind of actual loop construct is your problem here.
In general, when you get an error like that there's no one answer. Take a step back, try to add more specific type annotations, or rephrase what you're trying to say and usually a better error message will shake out.

accessing data returned from socket in swift using socket.io

I've got the following to listen for new 'message' events with the use of Socket.IO
self.socket.on("message") {data, ack in
print("received data: \(data)")
// do stuff
}
Data I'm getting back in data looks as follows:
[{
conversationid = 1;
message = hello world;
senderid = 1;
}]
It's an array holding a single object but I'm struggling to access the individual elements in there. For example:
let message = ???
Many thanks!
Your data's type is [[String : Any]], if you wanna get the message try this:
Swift 5.x:
if let data = data as? [[String : Any]] {
let message = data[0]["message"] as? String ?? ""
print(message)
}
In this repo you have more info about how to implement socket.io
Played around with it a bit more.
The following works:
let socketResponse = data[0] as? NSDictionary
let message = socketResponse!["message"]
While that solution makes sense to me, it still doesn't make sense why e.g. the solution that #0x0010b suggested doesn't. That one seemed perfectly sound to me as well. Grateful for anyone input that can shed some light on this one ;-)

Casting from Any to anything else fails

API gives me back a variable that has type Any. It looks like this when I print it.
{
"sender" : "Kira",
"created" : "08.05.2018",
"text" : "Cncncm"
}
I tried to use SwiftyJSON to cast it like this let mydata = JSON(data) but it failes. I tried to use Swift 4 decoding technique but that failed as well. I tried to do this let myData = data as? Dictionary<String, String> but it fails again.
I am clueless what to do here. Any tips or solutions?
Finally a chance to demonstrate one of the Codable protocols hidden gems. Please run the following in a Playground:
import Cocoa
let jsonData = """
{
"sender" : "Kira",
"created" : "08.05.2018",
"text" : "Cncncm"
}
""".data(using: .utf8)!
struct SenderText: Codable {
let sender: String
let created: Date
let text: String
}
let dayFormatter = DateFormatter()
dayFormatter.dateFormat = "dd.MM.yyyy"
let date = dayFormatter.date(from:"08.05.2018")
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dayFormatter)
do {
let sendText = try decoder.decode(SenderText.self, from: jsonData)
print(sendText)
} catch {
print(error)
}
The sheer elegance of how easy it is to define such an intricate parser mapping a messy JSON-string to your favourite struct will hardly ever stop to amaze me. No matter how weird your date format looks, it is hardly more than 3 lines away from being parsed during the process.
There is something in regard to casting you should note though: In Swift, as in most object oriented languages, you can only cast something to something else if (and only if) it already is something else in the first place (but that knowledge has been lost somewhere). Since your String is "just" a String (in disguise of an Any maybe) you won't be able to cast it to anything else. However the Codable protocol provides you with a terrific means to decode from the Strings Data with astonishing ease. This process should not be mistaken as a cast, even if it looks largely the same. It is the creation and initialisation of another, more fittingly structured object from a simple piece of Data that you are likely to have gotten from your average web service of choice.
Great so far, at least in my book.
You can parse it like this as it's a json string
let trd = yourVar as? String
if let data = trd?.data(using: String.Encoding.utf8) {
do {
var content = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:String]
print(content)
}
catch let error as NSError {
print(error)
}
}

How to send message struct containing a string as NSData for Game Center

I am trying to create a multiplayer game that will send moves between players using Game Center. I'm still learning a lot about programming, so please excuse me if my question is ill-formed. Also, I am not very familiar with Obj-C, so a Swift answer would be great.
In my toy program to try and teach myself, I am trying to follow the strategy used by Shayne Meyer using the GameKitHelper class here: https://github.com/shaynemeyer/SwiftCircuitRacer/tree/master/SwiftCircuitRacer
Using this approach, Shayne sends messages to other players online using structs sent as NSData. I am able to send integers (e.g., the ILoveYou message) but not messages that carry a string property (e.g., the Thanks message). In this latter case I get "Thread 1: EXC_BAD_ACCESS(code=1, address=0x78674100)" at the line "var messageThanks = UnsafePointer,MesssageThanks>(data.bytes).memory"
Eventually, I would like to send game moves that provide both strings and integers together. How does one send a message struct as NSData when properties also include a string? Secondly, I would be appreciative if someone could help me understand fundamentally what is going on when the data is packaged and how what UnsafePointer is doing as it related to sending data via Game Center.
Thank you.
Cliff
enum MessageType: Int {
case ILoveYou, Thanks
}
struct Message {
let messageType: MessageType
}
struct MessageILoveYou {
let message: Message
let messageSenderNumber: UInt32
}
struct MessageThanks {
let message: Message
let messageSenderName: String
let messageSenderNumber: UInt32
}
func sendILoveYou() {
println("sendILoveYou:")
let nameNumber = UInt32(56)
var message = MessageILoveYou(message: Message(messageType: MessageType.ILoveYou), messageSenderNumber: nameNumber)
let data = NSData(bytes: &message, length: sizeof(MessageILoveYou))
sendData(data)
}
func sendThanks() {
println("sendThanks:")
let nameString = "Don J"
let senderNumberInt = UInt32(88)
var message = MessageThanks(message: Message(messageType: MessageType.Thanks), messageSenderName: nameString, messageSenderNumber: senderNumberInt)
let data = NSData(bytes: &message, length: sizeof(MessageThanks))
sendData(data)
}
func matchReceivedData(match: GKMatch, data: NSData, fromPlayer player: String) {
println("matchReceivedData:")
var message = UnsafePointer<Message>(data.bytes).memory
if message.messageType == MessageType.ILoveYou {
println("messageType == ILoveYou")
let messageILoveYou = UnsafePointer<MessageILoveYou>(data.bytes).memory
iLoveYouThanksDelegate?.iLoveYouReceived(from: messageILoveYou.messageSenderNumber)
} else if message.messageType == MessageType.Thanks {
println("messageType == Thanks")
var messageThanks = UnsafePointer<MessageThanks>(data.bytes).memory
iLoveYouThanksDelegate?.thanksReceived(from: messageThanks.messageSenderName)
}
}
func sendData(data: NSData) {
var sendDataError: NSError?
let gameKitHelper = GameKitHelper.sharedInstance
if let multiplayerMatch = gameKitHelper.multiplayerMatch {
let success = multiplayerMatch.sendDataToAllPlayers(data, withDataMode: .Reliable, error: &sendDataError)
if !success {
if let error = sendDataError {
println("Error:\(error.localizedDescription)")
matchEnded()
}
}
}
}
The problem here is that when you create a String in Swift, it allocates a bit of memory itself, and then uses that memory to store the actual characters of the string. All that the string value really holds is some data representing a pointer to that memory and some other info (like how much memory has been allocated, so that it can be freed properly.
You can see this here:
let str = "This is quite a long string, certainly more than 24 bytes"
sizeofValue(str) // and yet this only returns 24
When you stuff variables into an NSData object, the initializer takes a pointer to the memory of the string variable that is holding those pointers, not the characters itself:
// only storing those 24 bytes, not the actual string
let data = NSData(bytes: &str, length: sizeofValue(str))
Note, the type of the bytes argument is UnsafePointer<Void>. This is an indication that you are heading into tricky territory.
Then, when you unmarshal the data at the other end, all your receiver is going to get is some pointers to random memory (sadly, memory on the other user’s device!)
If you want to put string values into an NSData object, you are going to need to marshal them first into raw data. For example, you could encode them into an array:
let data = Array(str.utf8).withUnsafeBufferPointer { buf in
NSData(bytes: buf.baseAddress, length: buf.count)
}
As it happens, since this is a common thing to want to do, there’s a method to do this directly:
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
Then, to unpack the data, you can use NSString’s constructor from an NSData object:
let newStr = NSString(data: data, encoding: NSUTF8StringEncoding)
edit: if you wanted to encode more than just a string in a single NSData, you could do something along these lines… I should say, I’ve never had to do this myself so I’m in no way familiar with the standard practices for this, there could be much better techniques or helper classes/functions. Hopefully someone with more experience can edit to show how to do this properly :)
var type = MessageType.Thanks
// start the data with the type
let data = NSMutableData(bytes: &type, length: sizeofValue(type))
// then append the string
data.appendData(Array(str.utf8).withUnsafeBufferPointer { buf in
NSMutableData(bytes: buf.baseAddress, length: buf.count)
})
switch UnsafePointer<MessageType>(data.bytes).memory {
case .ILoveYou:
// ...
case .Thanks:
let str = NSString(data: data.subdataWithRange(NSMakeRange(1, data.length-1)), encoding: NSUTF8StringEncoding)
}