nsjsonserialization.jsonobjectwithdata truncating data received - swift

I'm using the NSURLSession delegate way of making http get request to get json data from a server. I end up passing in the NSData received in my NSURLSessionDataDelegate to a model object to update an array like so:
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
networkDelegate?.updateGameList(data)
dataTask.resume()
}
Once the data makes its way into the updateGameList function, weird stuff happens if I try to use NSJSONSerialization.JSONObjectWithData. If I don't try to use NSJSONSerialization, and merely convert the NSData to a string, it looks like the proper response I am expecting, something like this:
Optional([{"id":"a19610e4-675e-40b3-8335-085b61bfb0e3","name":"tttt","status":"PLAYING"},
{"id":"3be2e411-0086-46fd-8fc9-38d11831d0fb","name":"LWP TEST","status":"PLAYING"},
{"id":"b5d97d73-1ce2-4e5b-8b38-805835e2a21d","name":"asd","status":"PLAYING"},
{"id":"8a15575c-4c2c-4b46-ad5b-51b4b2680416","name":"MsGame","status":"WAITING"},
{"id":"b54531b8-5323-4630-929c-6eb2cfebde63","name":"423","status":"PLAYING"},
{"id":"5a7cfaa0-e2a4-41f6-bda2-a854a2d00a57","name":"4234","status":"PLAYING"}])
However, once I try to use the NSData with NSJSONSerialization.JSONObjectWithData to make an NSArray, it starts throwing errors because the data seems to be truncated either at the beginning or end, like this:
Optional([{"id":"a19610e4-675e-40b3-8335-085b61bfb0e3","name":"tttt","status":"PLAYING"},{"id":"3be2e411-0086-46fd-8fc9-38d11831d0fb","name":"LWP TEST","status":"PLAYING"},
{"id":"6d4e9731-61be-4191-bb9a-a30b6395a4a2","name":"RANDOMBOT","status":"PLAYING"},{"id":"c31b363c-f565-4f4a-a6f1-1ac219425f40","name":"Testament ","status":"PLAYING"},
{"id":"af14d8bc-37a0-4ec3-88de-ee364344d720","name":"Testament ","status":"PLAYING"},
{"id":"29439dd9-357d-445b-856c-39862e19c2fc","name":"Testament ","status":"PLAYING"},{"id":"cc29046f-4e80-422d-a103-5be175e799c9","name":"matt7","status":"PLAYING"},
{"id":"ff75c546-0e9b-4560-8efb-d0fa5be61cde","name":"u","status":"DONE"},{"id":"3d88df55-6f84-469c-a18e-27e463dc30eb","name":"test","status":"PLAYING"},{"id":"40eb1b13-21c3-4c8d-a379-e6b85329374b","name":"test","status":"PLAYING"},
{"id":"4e7519dd-79d3-4229-8d0e-47ca112dc08f","name":"test","status":"PLAYING"},{"id":"32ce49cc-17aa-47ca-8b9f-1c35dbdb78e6","name":"test","status":"PLAYING"},
{"id":"f5d5c961-17eb-421d-86b1-fbbadfb795da","name":"test","status":"PLAYING")
update game list error Error Domain=NSCocoaErrorDomain Code=3840
"Unexpected end of file while parsing object."
UserInfo={NSDebugDescription=Unexpected end of file while parsing object.}
Here is the updateGameList function, when I comment out the do-catch block with the JSONSerialization code in it, the dataString prints out the correct response, when I uncomment it out, it runs multiple times, almost like it's trying to process the data a chunk at a time instead of all at once. If I take out the .AllowFragments option, it tells me to put it in, when I put it in, it gets to the real error of the json data not starting or ending correctly.
func updateGameList(gameListData:NSData) {
let dataString = NSString(data: gameListData, encoding: NSUTF8StringEncoding)
print(dataString)
do {
let gameList:NSArray = try NSJSONSerialization.JSONObjectWithData(gameListData, options: [.AllowFragments]) as! NSArray
}
catch {
print("update game list error \(error)")
}
}

Quoting from the documentation:
This delegate method may be called more than once, and each call
provides only data received since the previous call. The app is
responsible for accumulating this data if needed.
So what you're observing is a feature: the data is received in chunks (most likely because the originating server uses Transfer-Encoding: chunked), and therefore you cannot expect to be able to parse each chunk individually - you'll have to accumulate all chunks to from one response, which you can then try to parse.

Related

How to parse error when api request fails using responsedecodable in swift alamofire

I am trying to do an API request to the backend using alamofire and responseDecodable.
AF.request(Router.registerFacebookUser(facebookToken: token)).validate().responseDecodable(of: UserConfig.self) { result in
switch result.result {
case let .success(userConfig):
onAuthentication(userConfig)
case let .failure(error):
print(error)
//somehow get the message from ERROR JSON and pass it here
onFailure(error.localizedDescription)
}
}
When call succeeds, it successfully parses JSON to the model. However, there as some special cases, when it should fail. For example if user is already registered, I get a response JSON:
{
"error":{
"message":"User already exist"
}
}
Is it possible to override the AF error that we receive? Or maybe it's possible to parse another object if request fails? Or are there other ways how I can access the error message?
There are several ways to approach this in Alamofire.
In the validate() method that takes a closure, parse the error body and produce a .failure result with a custom associated error:
.validate { request, response, data
// Check request or response state, parse data into a custom Error type.
return .failure(MyCustomErrorType.case(parsedError))
}
Then, in your response handler, you'll need to cast to your custom error type from the AFError's underlyingError property:
.responseDecodable(of: SomeType.self) { response in
switch response.result {
case let .success(value): // Do something.
case let .failure(error):
let customError = error.underlyingError as? MyCustomErrorType
// Do something with the error, like extracting the associated value.
}
Use a Decodable container type to parse your responses as either the type you expect or your error representation. You should be able to find examples elsewhere, but on Alamofire's side it would work like this:
.responseDecodable(of: ContainerType<SomeType>.self) { response in
// Do something with response.
}
Write a custom ResponseSerializer type that checks the response and parses the error type when a failure is detected, otherwise parsing the expected type. We have examples in our documentation.
Of these options I usually go with the wrapper type unless I'm already using my own custom Error type, in which case the validator is fairly easy. A custom serializer is the most work but gives you the most flexibility as well, especially if you need to customize other aspects of your response handling.

Prevent NSKeyedArchiver throwing exception without Objective-C

On iOS version lower than 11 the throwing archivedData(withRootObject:requiringSecureCoding:) is unavailable, so I have tried to do the equivalent on versions less than iOS 11:
let archiveData: NSData
if #available(iOS 11.0, *) {
archiveData = try NSKeyedArchiver.archivedData(
withRootObject: rootObject,
requiringSecureCoding: true
) as NSData
} else {
NSKeyedArchiver.archivedData(withRootObject: userActivity)
let mutableData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: mutableData)
archiver.requiresSecureCoding = true
archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
if let error = archiver.error {
throw error
}
archiver.finishEncoding()
archiveData = mutableData
}
However, when the rootObject calls NSCoder.failWithError(_:) in the encode(with:) function an NSInvalidUnarchiveOperationException exception is raised.
If I subclass NSKeyedArchiver as such:
final class KeyedArchiver: NSKeyedArchiver {
override var decodingFailurePolicy: NSCoder.DecodingFailurePolicy {
return .setErrorAndReturn
}
}
It instead raises an NSInternalInconsistencyException exception with the message Attempting to set decode error on throwing NSCoder.
Is there a way to do this kind of archiving without throwing an exception, short of writing an Objective-C function to catch the exception and throwing it as an error?
The reason you're still getting the exception at encode time is that the .decodingFailurePolicy is effective only when decoding (i.e., unarchiving via NSKeyedUnarchiver), not encoding. Any object that calls .failWithError(_:) on encode will still produce the exception.
Calling .failWithError(_:) at encode-time is relatively rare: usually, once you have a fully constructed object at runtime, it's not terribly likely that it should be in a state that's not encodable. There are of course cases where this is possible, so you really have two options:
If you're working with objects you know and can check ahead of time whether they're in a valid state to encode, you should do that and avoid encoding invalid objects altogether
If you're working with arbitrary objects which you can't validate up-front, you're going to have to wrap your callout to NSKeyedArchiver via an Objective-C function which can catch the exception (and ideally throw an Error containing that exception, like the newer NSKeyedArchiver API does on your behalf)
Based on your comment above, option 2 is your best bet.
As an aside, you can shorten up your fallback code to avoid having to construct an intermediate NSMutableData instance:
let archiver = NSKeyedArchiver()
archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
archiveData = archiver.encodedData
The default initializer on NSKeyedArchiver constructs a new archiver with an internal mutable data instance to use, and NSKeyedArchiver.encodedData property automatically calls -finishEncoding on your behalf.

After API Result, Result Not SavingTo Variable [duplicate]

I declare a string in the beginning
var testString: String?
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let xml = SWXMLHash.parse(data)
testString = xml["root"]["schedule"]["date"].element?.text
}
But outside the NSURLSession, testString is nil. How can I make it so that it does not become nil and I can actually use the value?
For example, I want to use
println (testString)
AFTER the method block. But it is nil
The reason your variable is nil is because closures are executed asynchronously. That means that the rest of the code after the network request will continue to be called as normal, but the code containing parameters data, response and error is only called when the network request is finished.
To work around this, try putting whatever you are trying to do with the variable inside the closure, so your println(testString) would be inside the curly brackets.

What is the way to get received data out of URLSession?

Recently, I attempted to write my own Telegram Bot API. However, the project has seem to have hit a brick wall with URLSession (formerly NSURLSession) issues.
The call structure is as follows:
getMe() -> getData() -> NSURLSession
Ideally, I would like to have the data returned from NSURLSession passed back to getMe() for the application to process. However, this has not proven possible with the methods I have tried.
Below is the code I have been using. synthesiseURL() generates the URL that the app should open the session to in order to perform the action on the Telegram Bot API. A template of the URL generated by synthesiseURL() is https://api.telegram.org/bot\(token)/\(tgMethod).
// NSURLSession getData: gets data from Telegram Bot API
func getData(tgMethod: String, arguments: [String] = [String](), caller: String = #function) {
let url = synthesiseURL(tgMethod: "getMe"), request = NSMutableURLRequest(url: url)
var receivedData = String()
let session = URLSession.shared.dataTask(with: request as URLRequest) { data, response, err in
if err != nil {print(err!.localizedDescription); return}
DispatchQueue.main.async {
receivedData = String(data: data!, encoding: String.Encoding.nonLossyASCII)!
print(receivedData)
}
}
session.resume()
}
I have been trying to get getData to pass receivedData, which contains the Bot API's response, back to the function getMe.
func getMe() -> String {
HTTPInterface(botToken: token).get(tgMethod: "getMe")
return [???] // here's where the data from getData() should come
}
I have tried completion handlers, callbacks, asynchronous calls to the main thread etc, but none seem to be working as expected (getMe() returns an empty string).
Why is this so, and can it be fixed?
The fundamental issue is that your getMe() function is declared as having an immediate String return type, but it depends on a delayed / asynchronous call to get that string. The timeline looks something like this:
getMe() is called by some client code
getMe() kicks of the method that launches a URLSession to get the data
getMe() moves to the next line of execution and returns a string (still empty at this point). The getMe() function has now returned and the client code execution has continued forward with the empty String result
The URLSession completes with data, but execution has already moved on so the data doesn't get used anywhere
The easiest fix is to make your getMe function not have a return type, but to also call back to a closure parameter when the URLSession data comes back, something like:
func getMe(callback:String->()) {
//getData and pass a closure that executes the callback closure with the String data that comes back
}
The less easy fix is to use a technique like dispatch semaphores to prevent getMe() from returning a result until the URLSession data comes back. But this sort of approach is likely to stall your main thread and is unlikely to be the right choice.

NSURLSession crashing from Swift

I've got a simple class that uses an NSURLSession.
class test {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration());
func f() {
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0), { () -> Void in
var task = self.session.dataTaskWithRequest(request, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if error != nil {
// cry
return;
}
var error: NSError? = nil;
var dict = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as! Dictionary<String, String>;
// use dict
});
task.resume();
});
}
When I try to deserialize the data as JSON, the application crashes.
I've determined that the data seems to be of the right length but the content looks like garbage in the debugger, so it seems to me that the data object passed in is broken. Furthermore, I suspect some stack smashing as I can step through this completion handler and see that it's the attempt to deserialize the data that's crashing, but when the actual crash occurs, the stack in the debugger mentions nothing about the completion handler or any of my code.
I've seen several samples of using NSURLSession that look pretty much exactly like mine that just work. I tried using the shared session instead of making a new one, but that did not help either.
What is causing this crash?
Seems that the JSON was not actually all strings- I brainfarted and one of them was actually a number.
The real problem in the question is that Swift is completely worthless when handling the problem of force casts failing. Not only do you not get any kind of useful error at runtime that you could handle or recover from, but the debugging information presented when it occurs is completely misleading and points to totally the wrong place. You simply get a trap in a function without symbols with the wrong callstack.