Al Martin
Al Martin

Reputation: 179

NSMutableArray in playground causing errors with Xcode 7

I have a class that gets all the country codes and is supposed to sort them alphabetically. The class runs fine in Xcode for ios but when I copy it to test it in playground I get an error. See picture.

Playground code

So I stripped back all the code to find the NSMutableArray wasn't declared properly so it changed them to this.

var countries:NSMutableArray = NSMutableArray()
var countryKeys:NSMutableArray = NSMutableArray()
var countryNames:NSMutableArray = NSMutableArray() 

It took away the error but the code wont run. No errors and no output.

Anyone know why this code won't run in playground? Is it a bug?

Full code here

struct countryCodes{
    var countries:NSMutableArray = NSMutableArray()
    var countryKeys:NSMutableArray = NSMutableArray()
    var countryNames:NSMutableArray = NSMutableArray()

    init(){
        for code in NSLocale.ISOCountryCodes() {
            let id = NSLocale.localeIdentifierFromComponents([NSLocaleCountryCode: code])
            let name = NSLocale(localeIdentifier: "en_UK").displayNameForKey(NSLocaleIdentifier, value: id) ?? "Country not found for code: \(code)"
            self.countries.addObject(["key":id,"value":name])
        }
        self.sortByValue()
    }

    private func sortByValue(){
        let descriptor: NSSortDescriptor = NSSortDescriptor(key: "value", ascending: true)
        extractToArrays(self.countries.sortedArrayUsingDescriptors([descriptor]))
    }

    private func extractToArrays(sortedCountries:NSArray){
        for item in self.countries{
            self.countryKeys.addObject(item["key"] as! String)
            self.countryNames.addObject(item["value"] as! String)
        }
    }
}

I tried opening a new playground and Immediately I get an error

//: Playground - noun: a place where people can play

import UIKit

var str = "Hello, playground"

The error

file:///Volumes/External/Xcode%20Projects/MyPlayground3.playground/: error: Playground execution aborted: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x8).

Upvotes: 1

Views: 247

Answers (1)

Aaron Brager
Aaron Brager

Reputation: 66242

A few comments.

First, value names should begin with capital letters:

struct CountryCodes {

Steer clear of untyped collections like NSArray and NSDictionary. Just use the native Swift collection types. So countries will be an array of [String : String]:

    var countries = [[String : String]]()

In initialization, we need to create the array, and sort it.

    init() {
        for code in NSLocale.ISOCountryCodes() {
            let id = NSLocale.localeIdentifierFromComponents([NSLocaleCountryCode : code])

Swift Optionals represent whether or not data is there. So in the case that data is not there, it's a bad practice to insert a string saying so in your data model. Instead, use guard to handle that case:

            guard let name = NSLocale(localeIdentifier: "en_UK").displayNameForKey(NSLocaleIdentifier, value: id) else {
                debugPrint("Country not found for code: \(code)")
                break;
            }

Once you get the keys and values, append them to the array of countries:

            countries += [["key":id, "value":name]]
        }

Now that you have all of them, sort by value:

        countries = countries.sort() { $0["value"] < $1["value"] }
    }

Calculating keys and country names can quickly be done using map - no need to store them in separate arrays:

    var keys : [String] {
        return countries.map { $0["key"]! }
    }

    var names : [String] {
        return countries.map { $0["value"]! }
    }
}

Here's the full code if you want to try it in a playground:

struct CountryCodes {
    var countries = [[String : String]]()

    init() {
        for code in NSLocale.ISOCountryCodes() {
            let id = NSLocale.localeIdentifierFromComponents([NSLocaleCountryCode : code])

            guard let name = NSLocale(localeIdentifier: "en_UK").displayNameForKey(NSLocaleIdentifier, value: id) else {
                debugPrint("Country not found for code: \(code)")
                break;
            }

            countries += [["key":id, "value":name]]
        }

        countries = countries.sort() { $0["value"] < $1["value"] }
    }

    var keys : [String] {
        return countries.map { $0["key"]! }
    }

    var names : [String] {
        return countries.map { $0["value"]! }
    }
}

let codes = CountryCodes()
codes.countries
codes.keys
codes.names

Notice that this code uses no forced casting (as!) which causes crashes due to logical errors. The only force operator (!) being used is in the keys and names getters.

Upvotes: 1

Related Questions