Convert Data? containing bytes to Int in swift 5 - swift

I read from a characteristic of a ble peripheral on IoS some data, and when I try to print them, it just says 2 bytes. Looking at the type it is a optional Data type (Data?).
My question is, to convert that value to a readable integer, what is the best as fastest practice in swift 5?
If I try to print characteristic.value it just says "2 bytes". characteristic.value is an optional Data type.
switch characteristic.uuid:
case accelerometerUUID:
if characteristic.value != nil {
let accelValue = ???
print(accelValue)
}
Thank you very much!

Try this:
guard let value = characteristic.value else { return } // This makes it non-optional
let stringInt = String(data: value, encoding: .utf8)
let yourNumber = Int(stringInt)
This is much safer than the example below. The one above should be preferred.
In one line of code:
let yourNumber = Int(String(data: characteristic.value!, encoding: .utf8))
This is unsafe because you are unwrapping a value that might be optional, this can result in a crash.
Hope this helps!

Related

What does this base64 related function do [in Swift]?

Can someone explain the behavior of the function below? Some have suggested to not use NSData. Do you have better alternatives to mention? If the returned value is Base64Encoded can I decode on one of the online encoders/decoders? Thanks.
func stringToData(message: String) -> NSData? {
let strData = NSData(base64Encoded: message, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)
return strData
}
NSData(base64Encoded:options:) is documented to attempt to initialize a data object with the given Base64 encoded string—and return nil if it fails. In other words; it decodes a Base64 encoded string as an NSData object.
In Swift, you would likely use the base64EncodedString() function and the Data(base64Encoded:) initializer on the Data type to encode and decode data as Base64 strings, for example like this:
let originalData = Data(bytes: [1,2,3,4,5,6,7,8,9,10,11,12])
let encodedAsBase64String = originalData.base64EncodedString()
// "AQIDBAUGBwgJCgsM"
let decodedData = Data(base64Encoded: encodedAsBase64String) // is optional because the decoding can fail
// 12 bytes: <01020304 05060708 090A0B0C>

cannot invoke initializer for type 'int' with an argument list of type 'NSString?'

I am developing an app, where I have to read from web page. Or that is my solution for my app. It is reading from database, but I have errors when using JSON, so i tried this, because i have to read only one number. But when I download the webpage code, which is only "3" for example, just the number. In my program it is like (NSString?), is there any possibilities to convert it in INT?
let url = NSURL(string: "http://servis.dronysitmp.cz/cteni.php")
if url != nil {
let task = URLSession.shared.dataTask(with: url! as URL, completionHandler: { (data, response, error) -> Void in
//print(data)
if error == nil {
let urlContent = NSString(data: data!, encoding: String.Encoding.ascii.rawValue) as NSString!
let urlContent2: Int = Int(urlContent)
print(urlContent!)
}
}
task.resume()
}
Josef, you’re not using common Swift patterns correctly. Instead of
let url = ...
if url != nil { ...
you would boost legibility by using this pattern:
if let url = ... {
In this case url is non-optional and cannot be nil. This construct is called “safe-unwrapping an optional” in Swift.
The next part of code that doesn’t look too nice is this:
let urlContent = NSString(data: data!, encoding: String.Encoding.ascii.rawValue) as NSString!
If you use a NSStringconstructor then there’s no need to cast it to NSString. What you’re probably trying to achieve is to get around the fact that NSString(data:encoding:) returns an optional—which it does for good reason as it cannot guarantee to always be successful. You should therefore rather do something like this:
if let urlContent = NSString(data: data!, encoding: String.Encoding.ascii.rawValue) {
which makes sure that no crash will occur in such cases when NSString cannot convert the supplied data into an integer.
Finally, please consider these two general advices:
Never use force-unwrapping of an optional without prior testing for nil. I. e. never do this: variable! if you cannot be 100% sure that variable is non-nil. So NSString(data: data!,... in your example is not good.
Try to avoid Foundation classes (NS stuff like NSString, etc.) if pure Swift substitutes exist. In your example: if let urlContent = String(data: data!, encoding: String.Encoding.ascii.rawValue) {. This makes your code “swifter” which is what Swift coders prefer…

Swift 3: Converting Data to String returns a nil value

Converting Data to String returns a nil value.
Code:
// thus unwraps the image
if let image = image{
print("Saving image data")
// don't unwrap here
if let data = UIImagePNGRepresentation(image){
let str = String(data: data, encoding: .utf8)
print(str)
}
}
I don't know the reason.
Also, how do I convert the String back to Data?
This doesn't work because when you interpret the bytes of the Image as a String, the string is invalid. Not every jumble of data is a valid utf8 string. i.e. not every collection of n bits (8, sometimes 16) are a valid utf8 code point. The Swift String api loops through the data object you pass it to validate that it is a valid string. In your case, theres no reason to think that this Data is a valid string, so it doesn't work.
A good read on utf8:
https://www.objc.io/issues/9-strings/unicode/

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)
}

Swift NSData from base64encoded string returns nil

I'm communicating with a server in Swift retrieving image data. The incoming data is encoded as a base64 string. I am able to correctly receive and display the encoded strings. When I go to use the NSData class to decode the string back to binary data and display...
println(NSData(base64EncodedString: imageString, options: NSDataBase64DecodingOptions(0)))
The output is
nil
nil
nil
nil
nil
nil
One for each of the images received.
I've also tried
println(NSData(base64EncodedString: imageString, options: nil))
and the same results. Is there anything I am missing along the way?? I would put the image strings up but they are massively long...
For others that may be having this issue, make sure your Base64 encoded string has a length divisible by 4 (= should be used to pad the length).
See this StackOverflow answer here: https://stackoverflow.com/a/36366421/330494
Try to use IgnoreUnknownCharacters option.
Or try to use initWithBase64EncodedString from NSDataAdditions
This can also happen if the input is so-called "URL Safe" Base64 data. This data has the + symbol replaced by the - symbol, and the / symbol replaced by the _ symbol.
Fortunately it's straightforward to convert it:
inputString = [[inputString stringByReplacingOccurrencesOfString:#"-" withString:#"+"] stringByReplacingOccurrencesOfString:#"_" withString:#"/"];
A full list of variants is available on Wikipedia.
Based on Frank Schmitt's and Barlow Tucker's answers I've created an extension to Data to better handle base64 encoding:
extension Data {
static func decodeUrlSafeBase64(_ value: String) throws -> Data {
var stringtoDecode: String = value.replacingOccurrences(of: "-", with: "+")
stringtoDecode = stringtoDecode.replacingOccurrences(of: "_", with: "/")
switch (stringtoDecode.utf8.count % 4) {
case 2:
stringtoDecode += "=="
case 3:
stringtoDecode += "="
default:
break
}
guard let data = Data(base64Encoded: stringtoDecode, options: [.ignoreUnknownCharacters]) else {
throw NSError(domain: "decodeUrlSafeBase64", code: 1,
userInfo: [NSLocalizedDescriptionKey: "Can't decode base64 string"])
}
return data
}
}
so in your code, you can use it like this:
let baseEncodeText = "(.....)" //your base64 encoded string
let data = try Data.decodeUrlSafeBase64(baseEncodeText)