-1

I know this is a common error people post here but I can't find a post that matches to what I'm doing even if its the same fundamentally. I'm new to Swift and just trying to find my way, thank you.

The first time I open my app, a blog reader app that reads from a MYSQL database, it works as intended, I can follow the blogs that I chose and unfollow. When I follow a blog/cell it saves to User Defaults using KeyArchiver but when I double tap the home button to clear the app from memory and reopen the app, it crashes.

Something wrong is going on in my loadUserDefaults because I set up breakpoints and it crashes at this line self.followedIdentifiers = Set(UserDefaults.standard.stringArray(forKey: "followedID")!)

I know I have an optional but why is it crashing/ coming back nil if I saved it with saveUserDefaults. Is it not saving? or am I not loading it correctly?

The error is this

fatal error: unexpectedly found nil while unwrapping an Optional value

Code: This is MainController.swift

var mainArray = [Blog]()
var followedArray = [Blog]()
var filteredArray = [Blog]()
var followedIdentifiers = Set<String>()

override func viewDidLoad() {
    super.viewDidLoad()

    // Receiving Data from Server
    retrieveDataFromServer()

    // NSCoding - Unarchiving Data (followedID)
    loadUserDefaults()
}

// NSCoding: Archiving UserDefaults
func saveUserDefaults() {

    // Saving to UserDefaults
    let encodedData = NSKeyedArchiver.archivedData(withRootObject: self.followedIdentifiers)
    UserDefaults.standard.setValue(encodedData, forKey: "followedID")
    UserDefaults.standard.synchronize()
}

// NSCoding: Unarchiving UserDefaults
func loadUserDefaults() { // --- Crash is Here ---

    // Unarchiving Data
    if let data = UserDefaults.standard.data(forKey: "followedID"), let myFollowedList = NSKeyedUnarchiver.unarchiveObject(with: data) as? Set<String> {

        self.followedIdentifiers = myFollowedList
        self.followedIdentifiers = Set(UserDefaults.standard.stringArray(forKey: "followedID")!) // CRASH

    } else {
        print("Error/ Empty: (Loading UserDefaults (followedID))")
    }
}

// Retrieving Data from Server
func retrieveDataFromServer() {

    let getDataURL = "http://example.com/receiving.php"
    let url: NSURL = NSURL(string: getDataURL)!

    do {
        let data: Data = try Data(contentsOf: url as URL)
        let jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray

        // Clear the arrays
        self.followedArray = [Blog]()
        self.mainArray = [Blog]()

        // Looping through jsonArray
        for jsonObject in jsonArray {

            if let blog = Blog(jsonObject:jsonObject as! [String : Any]) {

                // Check if identifiers match
                if followedIdentifiers.contains(blog.blogID) {
                    self.followedArray.append(blog)
                } else {
                    self.mainArray.append(blog)
                }
            }
        }
    } catch {
        print("Error: (Retrieving Data)")
    }
    myTableView.reloadData()
}

This is Blog.swift which handles all the blogs objects and NSCoding

class Blog: NSObject, NSCoding {

var blogName: String
var blogStatus1: String
var blogStatus2: String
var blogURL: String
var blogID: String
var blogType: String
var blogDate: String
var blogPop: String


private init (name: String,status1: String,status2: String,url: String,id: String,type: String,date: String,pop: String) {
    blogName = name
    blogStatus1 = status1
    blogStatus2 = status2
    blogURL = url
    blogID = id
    blogType = type
    blogDate = date
    blogPop = pop
    super.init()
}

convenience init?(jsonObject: [String:Any]) {

    guard let bID = jsonObject["id"] as? String,
        let bName = jsonObject["blogName"] as? String,
        let bStatus1 = jsonObject["blogStatus1"] as? String,
        let bStatus2 = jsonObject["blogStatus2"] as? String,
        let bURL = jsonObject["blogURL"] as? String,
        let bType = jsonObject["blogType"] as? String,
        let bDate = jsonObject["blogDate"] as? String,
        let bPop = jsonObject["blogPop"] as? String

        else {
            print("Error: (Creating Blog Object)")
            return nil
    }

    self.init(name: bName, status1: bStatus1, status2: bStatus2, url: bURL, id: bID, type: bType, date: bDate, pop: bPop)

}

convenience required init?(coder aDecoder: NSCoder) {
    guard let blogName = aDecoder.decodeObject(forKey: "blogName") as? String,
        let blogStatus1 = aDecoder.decodeObject(forKey: "blogStatus1") as? String,
        let blogStatus2 = aDecoder.decodeObject(forKey: "blogStatus2") as? String,
        let blogURL = aDecoder.decodeObject(forKey: "blogURL") as? String,
        let blogID = aDecoder.decodeObject(forKey: "blogID") as? String,
        let blogType = aDecoder.decodeObject(forKey: "blogType") as? String,
        let blogDate = aDecoder.decodeObject(forKey: "blogDate") as? String,
        let blogPop = aDecoder.decodeObject(forKey: "blogPop") as? String else {
            print("Error: (Creating Blog Object)")
            return nil
    }
    self.init(name: blogName, status1: blogStatus1, status2: blogStatus2, url: blogURL, id: blogID, type: blogType, date: blogDate, pop: blogPop)
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(blogName, forKey: "blogName")
    aCoder.encode(blogStatus1, forKey: "blogStatus1")
    aCoder.encode(blogStatus2, forKey: "blogStatus2")
    aCoder.encode(blogURL, forKey: "blogURL")
    aCoder.encode(blogID, forKey: "blogID")
    aCoder.encode(blogType, forKey: "blogType")
    aCoder.encode(blogDate, forKey: "blogDate")
    aCoder.encode(blogPop, forKey: "blogPop")
 }
}
WokerHead
  • 947
  • 2
  • 15
  • 46
  • 1
    It seems that you save *data* and then try to read that back as a *string array.* – Martin R Jul 03 '17 at 19:40
  • 1
    And why do you assign to `self.followedIdentifiers` twice? The first assignment makes sense, the second doesn't. – Martin R Jul 03 '17 at 19:42
  • @MartinR So do I read it back as `data` or do I save as `string array` instead of `data`? and for 2nd comment, no idea why I did that so I use `myFollowedList` instead of `self.followedIdentifiers`? – WokerHead Jul 03 '17 at 19:45
  • 1
    You have an ! Mark in your retrieval, so you are going to get a crash if the retrieval fails (which it does because you used inconsistent types between save and fetch as Martin R pointed out). Code defensively and use a conditional assignment. Also, don't call synchronise; it isn't necessary. Also, don't use UserDefaults as a data store. – Paulw11 Jul 03 '17 at 20:56
  • What else can I use to save the archived data? Doesn't NSCoding archive it into an object and I'm just saving that object. How would I code defensively, using a guard? Also what Martin R stated is correct but do I unarchive as data then convert it to strings? – WokerHead Jul 03 '17 at 21:05
  • 1
    You don't need a guard, you can use `if let foo = bar {`. And you can save archived data to file, which is generally where `FileManager` comes in to play. – Guy Kogus Jul 03 '17 at 21:52
  • @GuyKogus can you demonstrate how to use FileManager, never used it, only have used UserDefaults. – WokerHead Jul 03 '17 at 22:43
  • 1
    Start by checking this https://stackoverflow.com/questions/11053842/how-to-save-file-in-the-documents-folder. There's a lot of info available on this, look for it yourself before asking people to help you. – Guy Kogus Jul 04 '17 at 08:29
  • @GuyKogus I did it thanks to you, so much easier to use File Manager with no hassle. Add it as an answer and I'll give it to you if not I'll add it but thank you guys again! Also, just to learn whats the difference between UserDefaults and FileManager? – WokerHead Jul 04 '17 at 17:36
  • 1
    `UserDefaults` is (meant to be) a lightweight `plist` file where you can easily save and retrieve values relating to the state of the user's usage of the app, e.g. a boolean to indicate if the user has seen a tooltip, or the name of the last article they read. `FileManager` is for heavier manipulation of files, such as getting all the contents of a folder or saving images to file, things like that. – Guy Kogus Jul 05 '17 at 07:01

1 Answers1

0

I used File Manager to save my archived array and it was the best choice, so easy and simple. Using Swift 3. Credit to @GuyKogus, @Paulw11 & @MartinR

Used to save my object

NSKeyedArchiver.archiveRootObject(myObject, toFile: filePath)

Used to load my object

if let myFollowedList = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? Set<String> {

    myObject = myFollowedList
}
WokerHead
  • 947
  • 2
  • 15
  • 46