EXC_BAD_ACCESS for arrays in Xcode function - ios

I have this function
func pickAttraction(attractionType: Array<Attraction>) -> Attraction {
let randAttr = attractionType[5]
if favoritesNames.contains(randAttr.attractionName) {
return pickAttraction(attractionType: attractionType)
} else {
return randAttr
}
}
and my program crashes (sometimes) on the line starting with "if favoritesNames". On the last time it crashed, the array favoritesNames had 1 string inside, and randAttr.attractionName had a different string. I expected it to return randAttr, but it crashed instead. Does anyone have any idea why?
I also tried
if favoritesNames.contains(randAttr.attractionName) || favoritesNames[0] == randAttr.attractionName {
and I got the same error
I've also tried
func pickAttraction(from attractions: [Attraction]) -> Attraction? {
attractions.filter { !favoritesNames.contains($0.attractionName) }
.randomElement()
}
instead of the other function and I still get the same error
Thank you

I believe let randAttr = attractionType[5] is accessing an index that doesn't exist. Can you verify that there is available data if you access the array at [5]?

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

Convert any object to boolean in swift?

I am getting a dictionary as JSON response from server.From that dictionary there is a key "error" now i want to get the value of "error" key.I know that error always will be either 0 or 1.So i tried to get it as boolean but it did not work for me.Please suggest how can i convert its value to boolean.
let error=jsonResult.objectForKey("error")
//this does not work for me
if(!error)
{
//proceed ahead
}
Bool does not contain an initializer that can convert Int to Bool. So, to convert Int into Bool, you can create an extension, i.e.
extension Bool
{
init(_ intValue: Int)
{
switch intValue
{
case 0:
self.init(false)
default:
self.init(true)
}
}
}
Now you can get the value of key error using:
if let error = jsonResult["error"] as? Int, Bool(error)
{
// error is true
}
else
{
// error does not exist/ false.
}
Very Simple Way: [Updated Swift 5]
Create a function:
func getBoolFromAny(paramAny: Any)->Bool {
let result = "\(paramAny)"
return result == "1"
}
Use the function:
if let anyData = jsonResult["error"] {
if getBoolFromAny(anyData) {
// error is true
} else {
// error is false
}
}
Also, if you want one line condition, then above function can be reduced like:
if let anyData = jsonResult["error"], getBoolFromAny(anyData) {
// error is true
} else {
// error is false
}
Updated
Considering jsonResult as NSDictionary and error value is Bool. you can check it by following way.
guard let error = jsonResult.object(forKey: "error") as? Bool else{
// error key does not exist / as boolean
return
}
if !error {
// not error
}
else {
// error
}
Your error is neither 0 or 1. It is an optional object. It is nil if there was no key "error" in your JSON, otherwise it's probably an NSNumber.
It is most likely that nil means "no error" and if it's not nil and contains a zero it means "no error" and if it's not nil and contains a one it means "error" and if it's not nil and contains anything else then something is badly wrong with your json.
Try this:
let errorObject = jsonResult ["error"]
if errorObject == nil or errorObject as? NSInteger == 0
When I say "try", I mean "try". You're the responsible developer here. Check if it works as wanted with a dictionary containing no error, with a dictionary containing an error 0 or 1, and a dictionary containing for example error = "This is bad".
Check equality between error and 0 instead:
let error = jsonResult.objectForKey("error")
if error == 0 {
// proceed
}
Another Important point here is, you're using Swift and parsing the repsonse as NSDictionary. I can say that because you're using objectForKey method. Best practice would be to parse it as Swift Dictionary.
It may look like Dictionary<String:AnyObject> or [String:AnyObject]
Then you can check for the error as :
if let error = jsonResult["error"] where error == 0 {
//your code
}
Below code worked for me
if let error = jsonResult["error"] as? Int where Bool(error)
{
// error is true
print("error is true")
}
else
{
// error does not exist/ false.
print("error is false")
}

CNContactVCardSerialization.dataWithContacts giving exception

I'm trying to convert a CNContact array to vCard using the method CNContactVCardSerialization.dataWithContacts(). But it is giving me the following error.
2016-07-25 14:05:00.115 AddressBook-ios9[902:28918] Exception writing contacts to vCard (data): A property was not requested when contact was fetched.
I made sure that I'm passing an valid array of CNContacts, but still it is giving this exception. Can anybody guide to me to what I've done wrong?
I'm attaching the source code below.
func getVcardFromSearchingName(name: String) -> NSData? {
do {
if let contacts = searchMultiContacts(name) {
print(contacts)
let vCard = try CNContactVCardSerialization.dataWithContacts(contacts)
return vCard
} else {
return nil
}
} catch {
return nil
}
}
I found out my mistake. On the keys to fetch contact, I was missing CNContactVCardSerialization.descriptorForRequiredKeys(). After adding it, the code is working flawlessly.

Swift: Unit tests with functions that throw

How can one write unit tests in Swift that communicate useful information when calling functions that can throw?
What I'd really like to be able to do is something like this:
class TestTestsTests: XCTestCase {
func doFoo() throws -> String {
// A complex operation that might throw in various places
return "foo"
}
func doBar() throws -> String {
// A complex operation that might throw in various places
return "bar"
}
func testExample() throws {
let foo = try doFoo()
let bar = try doBar()
XCTAssertNotEqual(foo, bar)
}
}
Ideally the unit test runner would stop on the line where an unhandled exception occurred, and let the user explore the stack and error message. Unfortunately, adding throws to the test function causes it to be silently skipped over (and there's a UI bug that makes it look as though the test is still being run, and getting the same result as before adding throws).
Of course it is also possible to do this:
func testExample() {
let foo = try! doFoo()
let bar = try! doBar()
XCTAssertNotEqual(foo, bar)
}
But now a failure doesn't really provide the context we need. Add a throw to doFoo, and we get a message like fatal error: 'try!' expression unexpectedly raised an error: TestTestsTests.Error(): file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.1.101.15/src/swift/stdlib/public/core/ErrorType.swift, line 50, which only gives us the line number within testExample, and not within doFoo where the error occurred. This also seems to get the debugger 'stuck' (clicking continue just returns us to the same line with the same error message) on that line, regardless of whether breakpoints are enabled, and prevents other tests from running.
So maybe we could try something like this?
func testExample() {
do {
let foo = try doFoo()
let bar = try doBar()
XCTAssertNotEqual(foo, bar)
} catch {
XCTFail("\(error)")
}
}
This runs as expected, however we can't determine which of doFoo or doBar threw the error, and also no line number information. At least we can get the error message and not prevent other tests from running.
I could go on, but the short of it is that I can't find a way to simultaneously not break the unit test running (like with try!), figure out which function threw the error, and get the error information -- Unless I do something ridiculous like:
func testExample() {
var foo: String? = nil
var bar: String? = nil
do {
foo = try doFoo()
} catch {
XCTFail("\(error)")
return
}
do {
bar = try doBar()
} catch {
XCTFail("\(error)")
return
}
if let foo = foo, bar = bar {
XCTAssertNotEqual(foo, bar)
}
}
And I still don't get to find out where in doFoo or doBar the error occurred.
Is this just the sad state of unit testing in Swift, or am I missing something?
This runs as expected, however we can't determine which of doFoo or doBar threw the error, and also no line number information.
Maybe this is just an old problem or I'm not understanding your problem, or maybe you're just kind of making a statement rather than asking a question. But with respect to the quoted statement above, you can add any information you like to the messages in XCTest Assertions.
Swift also provides the following literals:
#file String The name of the file in which it appears.
#line Int The line number on which it appears.
#column Int The column number in which it begins.
#function String The name of the declaration in which it appears.
func testExample() {
var foo: String? = nil
var bar: String? = nil
do {
foo = try doFoo()
} catch {
XCTFail("try doFoo() failed on line: \(#line) in file: \(#file) with error: \(error)")
return
}
do {
bar = try doBar()
} catch {
XCTFail("try doBar() failed on line: \(#line) in file: \(#file) with error: \(error)")
return
}
if let foo = foo, bar = bar {
XCTAssertNotEqual(foo, bar)
}
}
If you really want to go crazy with it you can add error handling to your doBar() method and that error can contain any internal information you'd like.
In fact... by implementing your own errors in your methods you might not even need to separate the methods into two blocks in your tests, just printing the error should be enough. You can put any information you like in the error message.
Anyway, I think this is an outdated issue, you can get all the information you need from the test logs - they list out all the methods that failed and even have little arrows that let you jump right to the test that failed. They then highlight the specific assertion that failed... from there it's quite easy to tell what is happening in most cases. Worst case scenario you have to set a breakpoint or two and run the test again.
You can do your own errors, using Error or LocalizedError protocols
enum Errors: Error, CustomStringConvertible {
case foo_param_is_null
case bar_param_is_null(paramIndex: Int)
var description: String {
switch self {
case .foo_param_is_null:
return "Param is null in foo"
case .bar_param_is_null(let paramIndex):
return "Param at index \(paramIndex) is null in bar"
}
}
}
func foo(_ param: Int) throws {
guard param != 0 else {
throw Errors.foo_param_is_null
}
print("foo = \(param)")
}
func bar(_ params: [Int]) throws {
if let index = params.firstIndex(where: {$0 == 0}) {
throw Errors.bar_param_is_null(paramIndex: index)
}
print("bar = \(params)")
}
do {
try foo(1)
try foo(0)
} catch {
print("\(error)")
}
do {
try bar([1,2,3])
try bar([1,0,3])
} catch {
print("\(error)")
}
Result:
foo = 1
Param is null in foo
bar = [1, 2, 3]
Param at index 1 is null in bar
And if you need even more information, you can use structures to define errors and error domains. Something like :
struct FooBarError: Error, CustomStringConvertible {
var string: String
var context: Any?
static func fooError() {
FooBarError(string: "Foo Error")
}
static func barError(context: BarErrorContext) { FooBarError(string: "Bar Error", context: context)
}
var description: String {
if let cox = context as? BarErrorContext {
return "\(string) - paramIndex: \(ctx.paramIndex) - \(ctx.insidiousReason)"
}
return string
}
}
Note:
As #ibrust proposed, you can pass #function, #line and other special parameters to your errors initialisers to provide this information
do {
try foo()
} catch {
throw(BarFooError.foo(line: #line))
}
You can also propagate the original error
do {
try bar()
} catch {
throw(BarFooError.bar(exception: error))
}
Edited:
Finally , you can also use print(Thread.callStackSymbols) in your error description, but at this point, there is a risk of confusion between debugging and testing. Just a personal thought.

Resources