Swift app stops running after an error - ios

My iOS app stops running after a run-time error occurs. I'm catching the error as an exception. I would like the app to continue running to the next steps after error handling. Any advises how to do this?
do {
guard let ps: Element = try! docu.getElementById("product-name")! else { ide = "" }
if ps != nil {
ide = (try ps.text())
}
} catch {
print("error")
ide = ""
}

I think you are overusing the ! (force unwrap) symbol here. It does not deal gracefully with nil values, in fact, it crashes.
I think what you might want to be doing here is
guard
let ps: Element = try? doc.getElementById("product-name"),
let ide = try? ps.text()
else {
print("error")
ide = ""
}
// ide is guaranteed to be valid here
...
Note how if you use try? you do not need to "catch" the error, it will simply return an optional value, nil if the call would raise an exception.
Alternatively you could simply
let ps: Element = try? doc.getElementById("product-name")
let ide = try? ps?.text()
// ide will be an optional value here
If you really don't want to guard/if let...

Related

Swift: how to detect the error type using the debugger?

I'm new in iOS development, so maybe I'm thinking in the wrong way. I coded a view model with a function that calls an API, and everything works fine.
class SearchCityViewModel : ViewModelProtocol {
//OBSERVABLES
var cities = PublishSubject<[City]>()
var networkError = PublishSubject<Void>()
var generalError = PublishSubject<Void>()
init(){
print("Init SearchCityViewModel")
reinit()
}
func reinit(){}
func searchCity(stringToSearch: String){
async {
do {
if stringToSearch.count>=2 {
let cities = try await(api.getCities(cityToSearch: stringToSearch)).payload!
self.cities.onNext(cities)
}
else {
self.cities.onNext([])
}
}
catch {
self.generalError.onNext(Void())
}
}
}
Now I want to handle errors. In the catch block I want to distinguish all the errors I want to handle gracefully, and for the other ones I just want to emit a general error. To do that, firstly I need to know which error is thrown when the situation I want to handle occurs. I usually do this with the debugger. For instance, I disable the internet connection, and i create a breakpoint inside the catch block. The idea is to check which error is thrown when the internet connection is disabled, in order to create a catch block for that kind of error.
Image of the debugger
I'm struggling because with the debugger I only see that is an AFError instance, but it's not telling me nothing more that can help me to catch it.
What is wrong with my workflow? Do I really need to read all the docs every time? For each library I use?
Thank you!
Perhaps you can read the articles and then you will know how to do it better, you can use the framework -oslog instead of using print function.
debugging your logging info
I found the way. What I was missing is casting the error as NSError. In this way, with the debugger is possible to see the domain and the code of the error. In the case of Alamofire, the real error is wrapped, and it's accessible through the underlyingError attribute. Once I had the domain and the code of the error, I wrote the following code:
class SearchCityViewModel : ViewModelProtocol {
//OBSERVABLES
var cities = PublishSubject<[City]>()
var networkError = PublishSubject<Void>()
var generalError = PublishSubject<Void>()
init(){
print("Init SearchCityViewModel")
reinit()
}
func reinit(){}
func searchCity(stringToSearch: String){
async {
do {
if stringToSearch.count>=2 {
let cities = try await(api.getCities(cityToSearch: stringToSearch)).payload!
self.cities.onNext(cities)
}
else {
self.cities.onNext([])
}
}
catch {
if let afError = asAFError, let underlyingError = afError.underlyingError as NSError?, underlyingError.domain == NSURLErrorDomain, underlyingError.code == NSURLErrorNotConnectedToInternet || underlyingError.code == NSURLErrorTimedOut {
self.networkError.onNext(Void())
}
else {
self.generalError.onNext(Void())
}
}
}
}

Errors using PFUser.getCurrentUserInBackground()

I am using Parse and PFUser in a Swift iOS app, and find myself in a case where PFUser.current() does not do exactly what I want, due to synchronisation issues.
For that reason I am trying to use: PFUser.getCurrentUserInBackground().
I got started with the code below, inspired from what can be found here: https://github.com/BoltsFramework/Bolts-ObjC.
But this document probably being a bit outdated, it does not quite work.
let userCheckTask = PFUser.getCurrentUserInBackground()
userCheckTask.continueWith {
(task: BFTask!) -> BFTask<AnyObject> in
if task.isCancelled() { // Error-1.
// the save was cancelled.
} else if task.error != nil {
// the save failed.
} else {
// the object was saved successfully.
var object = task.result() as PFObject // Error-2.
}
}
The compiler gives me two errors, this one on the line marked "Error-1"
Cannot invoke 'isCancelled' with no arguments
And this other one on the line marked "Error-2"
Expression type 'PFUser?' is ambiguous without more context
I have no idea what kind of argument 'isCancelled' is expecting.
Does anyone know how to fix those?
let userCheckTask = PFUser.getCurrentUserInBackground()
userCheckTask.continueWith {
(task: BFTask) -> BFTask<AnyObject> in
if let e = task.error {
return BFTask(error: e)
} else {
return BFTask(result: task.result)
}
}

aws dynamodb how to use object mapper with batch get in ios swift

Thanks in advance for any help. I am trying to get Batch items (Load multiple) items from one DynamoDb table using the AWS iOS SDK (Swift). I can load one item using the Block syntax, but I need to load 10 or more than that. I don't want to use 10 Block calls to load them individually. I tried to follow the attach stackoverflow Link (where the similar solution is given) but I am getting the following compiler error message. I come from Java background, hence could also be a syntax issue. Is it the right way to load multiple items? I don't want to use low level API. Any help, where I am going wrong. Thanks.
aws dynamodb how to use object mapper with batch get in ios
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...10 {
tasksList.append(dynamoDBObjectMapper.load(AWSCards.self, hashKey: "SH_"+String(i), rangeKey: nil))
}
AWSTask.init(forCompletionOfAllTasksWithResults: tasksList).continueWithBlock { (task) -> AnyObject? in
if let cards = task.result as? [AWSCards] {
print(cards.count)
}
else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
Have a try with the following codes (Swift 4.1, Feb 9th, 2018):
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...10 {
tasksList.append(dynamoDBObjectMapper.load(AWSCards.self, hashKey: "SH_"+String(i), rangeKey: nil))
}
AWSTask<AnyObject>.init(forCompletionOfAllTasksWithResults: tasksList).continueWith { (task) -> Any? in
if let cards = task.result as? [AWSCards] {
print(cards.count)
}
else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
Your question is "how to use the object mapper" but it might be more efficient for you to not use it.
However, there is a way to use it. See Niklas's answer here and here (he copy & pasted), but something about it strikes me as fishy. I want to make the assertion that it is not as fast as the built-in batch-get function, but I am unsure. I suspect that this does not complete the items in parallel, or at least not as efficiently as in BatchGetItem.
See the docs: "In order to minimize response latency, BatchGetItem retrieves items in parallel."
According to Yosuke, "Currently, AWSDynamoDBObjectMapper does not support the batch get item. You need to load one item at a time if you want to use the object mapper" as of 2016. This still seems to be the case. I am using a version a couple versions behind, but not too far behind. Someone check.
In conclusion, if you are loading one item at a time, you are likely missing out on the whole purpose of BatchGetItem (low latency).
Pulling from various sources, including John Davis's question here, I have tested and ran this BatchGetItem result. Here ya go.
import AWSDynamoDB
let primaryKeyToSortKeyDict : [String : String] = .... // Your stuff
var keys = [Any]()
for key in primaryKeyToSortKeyDict.keys {
let partitionKeyValue = AWSDynamoDBAttributeValue()
partitionKeyValue?.s = String(key)
let sortValue = AWSDynamoDBAttributeValue()
sortValue?.s = String(primaryKeyToSortKeyDict[key]!)
keys.append(["partitionKeyAttributeName": partitionKeyValue, "sortKeyAttributeName": sortValue])
}
let keysAndAttributesMap = AWSDynamoDBKeysAndAttributes()
keysAndAttributesMap?.keys = keys as? [[String : AWSDynamoDBAttributeValue]]
keysAndAttributesMap?.consistentRead = true
let tableMap = [table : keysAndAttributesMap]
let request = AWSDynamoDBBatchGetItemInput()
request?.requestItems = tableMap as? [String : AWSDynamoDBKeysAndAttributes]
request?.returnConsumedCapacity = AWSDynamoDBReturnConsumedCapacity.total
guard request != nil else {
print("Handle some error")
return
}
AWSDynamoDB.default().batchGetItem(request!) { (output, error) in
print("Here is the batchgetitem output")
if error == nil {
// do output stuff
} else {
// handle error
}
}

Can someone explain what why this code is wrong?

var input = AVCaptureDeviceInput(device: backCamera)
The error message i get says call can throw, but it is not marked with 'try' and the error is not handled.
also I have this line of code
if ((error == nil && captureSession?.canAddInput(input)) != nil)
Optional type 'Bool' cannot be used as a boolean; test for "!=nil" instead.
Auto Correct is on but for the second line when i click fix it just rewrites the same line of code and adds another "!=nil".
You are getting the error that says
this call can throw, but it is not marked with try and the error is not handled.
because you did not catch the exceptions that could be thrown. Instead, you should be using this
var error: NSError?
do{
let input = try AVCaptureDeviceInput(device: backCamera)
}
catch let myError as NSError{
error = myError
}
The second error is because captureSession?.canAddInput(input) returns an optional Bool value. To fix this, you should be using (captureSession?.canAddInput(input) ?? false), which effectively turns nil values into false.
if(error == nil && (captureSession?.canAddInput(input) ?? false))
A better way to do this would be to put it all in the do-catch statement
do{
let input = try AVCaptureDeviceInput(device: backCamera)
if((captureSession?.canAddInput(input) ?? false)){
//your code
}
}
catch var error as NSError{
//handle the NSError 'error'
}
For your first line, I believe all you have to do is surround it with a
do{
try //statement
}catch {
}
For the second statement, I believe the reason why the compiler is complaining is because you are trying to evaluate a type Bool for nil or not, try simply having
(error == nil && captureSession?.canAddInput(input))

handling error using xcode 7.0 beta in asynchronous block

I am trying to validate different errors while downloading text files from AWS S3, and with the next piece of code:
... above here function receiving String parameters ruta, archivo, archivoLocal
let directorioURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL
let archivoURL = directorioURL.URLByAppendingPathComponent("b\(archivoLocal)")
let downloadRequest = AWSS3TransferManagerDownloadRequest()
downloadRequest.bucket = ruta
downloadRequest.key = archivo
downloadRequest.downloadingFileURL = archivoURL
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
let task = BFTask()
let executor = BFExecutor.mainThreadExecutor()
transferManager.download(downloadRequest).continueWithExecutor(executor, withBlock: { (task) -> AnyObject! in
if task.error != nil {
if task.error.domain == AWSS3TransferManagerErrorDomain {
self.processDomainErrorType(AWSS3TransferManagerErrorType(rawValue: task.error.code))
} else {
self.processError(task.error)
}
} else if task.result != nil {
do {
let mytext = try String(contentsOfURL: archivoURL, encoding: NSUTF8StringEncoding)
self.processResult(mytext)
} catch let urlerror as? NSError {
self.processError(urlerror)
}
}
...
I am getting the error:
Invalid conversion from throwing function of type '(_) throws -> AnyObject!' to non-throwing function type '#convention(block) (BFTask!) -> AnyObject!'
I obtained the "do { try } catch" syntax from https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10
I can remove the error by replacing the catch clause with:
} catch _ {
self.processError(NSError(domain: "String-ContentsOfURL Error", code: 100, userInfo: nil))
}
Of course this way I will never know the real cause why ContentsOfURL could be failing.
All I can figure out why this error happens is because this syntax is valid only for OS X apps and for iOS the error handling guide at
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42
allows only the second syntax unless you own the object throwing the errors from an enum structure of ErrorType type, which is not the case since I want to catch the NSError from String object, contentsOfURL function.
I hope someone could guide me through this, maybe being XCode 7 a beta, the catch syntax is still incomplete or maybe I should not matter about the reason why this function fails, but I think it is important to determine what is making this function fail and if it could be traced and fixed before reaching the do-try-catch clause.
Additionally to the above error, I am getting a warning in the task variable assignation line to BFTask() saying that "Initialization of immutable value 'task' was never used". I think this is a bug with this beta version that it doesn't include the pattern to acknowledge that the variable task is being used in the asynchronous block. I'd appreciate a lot some confirmation about this and if I just need to ignore it.
By the way, the only reason I am using XCode 7 beta is because my client wants to evaluate the app before acquiring their apple membership.
Apple replaced NSError with ErrorType in Swift 2.
Replace your own explicit usage of NSError with ErrorType and you don't get this type of compiler errors.

Resources