1

Swift 3 iOS 10, Trying to save array with custom objects in NSKeyedArchiver, basically trying to save the table view after the user uses the buttons to switch between sections. I've tried several post to solve the issue but no luck, now I'm trying to do it myself NSCoding and NSKeyedArchiver.

Error:

Cannot convert value of type '[Blog]' to expected argument type 'NSCoder'

Error is in code in Blog.swift, Code that handles NSCoding and my Blog Objects

import UIKit

class BlogsCoding: NSObject, NSCoding {

var blogList : [Blog]

init(blogList : [Blog]) {
    self.blogList = blogList
}

convenience required init?(coder aDecoder: NSCoder) {

    guard let blogList = aDecoder.decodeObject(forKey: "blogs") as? [Blog]
        else {
            return nil
    }
    self.init (blogList : blogList)
}

func encode(with aCoder: NSCoder) {

    aCoder.encode(blogList, forKey: "blogs")
 }
}

class Blog: NSObject, NSCoding { // To conform to NSCoding

// Strings
var blogName: String?
var blogStatus1: String?
var blogStatus2: String?
var blogURL: String?
var blogID: String?
var blogType: String?
var blogDate: String?
var blogPop: String?
var blogList : [Blog] // To conform to NSCoding

override init() {

}
// Converting Strings into Objects
init(blogName bName: String,
     andBlogStatus1 bStatus1: String,
     andBlogStatus2 bStatus2: String,
     andBlogURL bURL: String,
     andBlogID bID: String,
     andBlogType bType: String,
     andBlogDate bDate: String,
     andBlogPop bPop: String,
     blogList : [Blog]) // To conform to NSCoding
{
    super.init()

    self.blogName = bName
    self.blogStatus1 = bStatus1
    self.blogStatus2 = bStatus2
    self.blogURL = bURL
    self.blogID = bID
    self.blogType = bType
    self.blogDate = bDate
    self.blogPop = bPop
    self.blogList = blogList // To conform to NSCoding
 }

 // To conform to NSCoding
 convenience required init?(coder aDecoder: NSCoder) {

    guard let blogList = aDecoder.decodeObject(forKey: "blogs") as? [Blog]
        else {
            return nil
    }
    self.init (coder : blogList) // *---* Error is here *---*
}

func encode(with aCoder: NSCoder) {

    aCoder.encode(blogList, forKey: "blogs")
 }

}

In MainController.swift - Where my tableview is

override func viewDidLoad() {

var path : String {
        let manager = FileManager.default
        let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first! as NSURL
        return url.appendingPathComponent("blogs")!.path // I have a blogs.plist for this, doing it right?
    }
}

Follow Button

// Follow Button
@IBAction func followButtonClick(_ sender: UIButton!) {

// After Updating Table, Save Arrays
        var success = false
            // mainArray is array holding custom objects from json
        success = NSKeyedArchiver.archiveRootObject(mainArray, toFile: "path") // If I dont use "" I get undeclared 'path'

        if success {
            print("Saved Blogs")
        } else  {
            print("Didn't Save Blogs")
        }
}

Receiving Data from Server

// Retrieving Data from Server
func retrieveData() {

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

    do {

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

        // Looping through jsonArray
        for i in 0..<jsonArray.count {

            // Create Blog Object
            let bID: String = (jsonArray[i] as AnyObject).object(forKey: "id") as! String
            let bName: String = (jsonArray[i] as AnyObject).object(forKey: "blogName") as! String
            let bStatus1: String = (jsonArray[i] as AnyObject).object(forKey: "blogStatus1") as! String
            let bStatus2: String = (jsonArray[i] as AnyObject).object(forKey: "blogStatus2") as! String
            let bURL: String = (jsonArray[i] as AnyObject).object(forKey: "blogURL") as! String
            // New
            let bType: String = (jsonArray[i] as AnyObject).object(forKey: "blogType") as! String
            let bDate: String = (jsonArray[i] as AnyObject).object(forKey: "blogDate") as! String
            let bPop: String = (jsonArray[i] as AnyObject).object(forKey: "blogPop") as! String
            // NSCoding
            let blogList: NSObject = ((jsonArray[i]) as! NSObject).value(forKey: "blogList") as! NSObject

            // Add Blog Objects to Main Array
            mainArray.append(Blog(blogName: bName, andBlogStatus1: bStatus1, andBlogStatus2: bStatus2, andBlogURL: bURL, andBlogID: bID, andBlogType: bType, andBlogDate: bDate, andBlogPop: bPop, blogList: blogList as! [Blog]))

        }
    }
    catch {
        print("Error: (Retrieving Data)")
    }

Also, is 'path' defined and used right? Thank you!

WokerHead
  • 947
  • 2
  • 15
  • 46
  • 1
    You are decoding an object to `[Blog]` but then passing the object in the `init(coder` method which expects `NSCoder`. That's a classical type mismatch. Btw: Your `retrieveData()` method is pretty ugly. Don't use `NSMutableArray` and `.mutableContainers`. Will you ever mutate the result? Omit the `options` parameter and cast the result to `[[String:Any]]`. Then replace the loop with `for item in jsonArray` and the lines in the body for example to `let bID = item["id"] as! String`. That's much cleaner and more efficient. – vadian Jun 11 '17 at 06:46
  • How do I fix the type mismatch, replacing the blogList with NSCoder, it gives the same error if thats what you meant. For retrieveData() thank you I've been meaning to clean that code,I just plan on populating a tableview with that info. How should I update the code. – WokerHead Jun 11 '17 at 06:50
  • The `encoder / decoder` methods are for encoding / decoding each property of the class not for the class itself. – vadian Jun 11 '17 at 06:56
  • So I include the blog objects instead of coder: blogList? I just started with NSKeyedArchiver mind helping me out. I can give you the correct answer checkmark – WokerHead Jun 11 '17 at 07:01

1 Answers1

3

The purpose of NSCoding is to convert each single property of the class to a property list compliant format.

First of all declare all String properties as non-optional because the custom initializer passes only non-optional parameters.

Then add the lines in the init(coder and encode(with methods (edited)

class Blog: NSObject, NSCoding { // To conform to NSCoding

    // Strings
    var blogName: String
    var blogStatus1: String
    var blogStatus2: String
    var blogURL: String
    var blogID: String
    var blogType: String
    var blogDate: String
    var blogPop: String
    var blogList : [Blog] // To conform to NSCoding

    // Converting Strings into Objects
    init(blogName bName: String,
         andBlogStatus1 bStatus1: String,
         andBlogStatus2 bStatus2: String,
         andBlogURL bURL: String,
         andBlogID bID: String,
         andBlogType bType: String,
         andBlogDate bDate: String,
         andBlogPop bPop: String,
         blogList : [Blog]) // To conform to NSCoding
    {
        self.blogName = bName
        self.blogStatus1 = bStatus1
        self.blogStatus2 = bStatus2
        self.blogURL = bURL
        self.blogID = bID
        self.blogType = bType
        self.blogDate = bDate
        self.blogPop = bPop
        self.blogList = blogList // To conform to NSCoding
        super.init()
    }

    // To conform to NSCoding
    convenience required init?(coder aDecoder: NSCoder) {
        self.blogName = aDecoder.decodeObject(forKey: "blogName") as! String
        self.blogStatus1 = aDecoder.decodeObject(forKey: "blogStatus1") as! String
        self.blogStatus2 = aDecoder.decodeObject(forKey: "blogStatus2") as! String
        self.blogURL = aDecoder.decodeObject(forKey: "blogURL") as! String
        self.blogID = aDecoder.decodeObject(forKey: "blogID") as! String
        self.blogType = aDecoder.decodeObject(forKey: "blogType") as! String
        self.blogDate = aDecoder.decodeObject(forKey: "blogDate") as! String
        self.blogPop = aDecoder.decodeObject(forKey: "blogPop") as! String
        self.blogList = aDecoder.decodeObject(forKey: "blogs") as! [Blog]
        super.init (coder : aDecoder)
    }

    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")
        aCoder.encode(blogList, forKey: "blogs")
    }
}

However there is a potential caveat:

Encoding / decoding an array of objects of the target class may cause unexpected behavior for example an infinite loop. Consider that. You might encode the array separately.


The second issue is a typo, you pass "path" as a literal string rather than a variable path:

NSKeyedArchiver.archiveRootObject(mainArray, toFile: path)
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thank you for the detailed response, no errors. What can happen in the potential caveat in the infinite loop, what can trigger that? Also, for 'path' I get an error saying "Use of unresolved identifier 'path'. – WokerHead Jun 11 '17 at 07:45
  • Are you sure you want to have a property containing an array of objects of the enclosing class? You might add the file extension `blogs.plist` to the path. – vadian Jun 11 '17 at 07:51
  • So I built the project and I got this error "fatal error: unexpectedly found nil while unwrapping an Optional value" in retrieveData "let blogList: NSObject = ((jsonArray[i]) as! NSObject).value(forKey: "blogList") as! NSObject". I tried adding an if let but still error. Also, should I not have a property containing objects? Little lost there. For the 'path'I add the 'blogs.plist' to "var path : String" ? – WokerHead Jun 11 '17 at 08:08
  • **Once again:** Is this intended that you have an array `[Blog]` in the `Blog` class. Each item in that array contains a `[Blog]` array which contains a `[Blog]` array (... and so on). And as mentioned in the first comment `let blogList: NSObject = ((jsonArray[i]) as! NSObject).value(forKey: "blogList") as! NSObject` is horrible code. Clean it up. – vadian Jun 11 '17 at 08:13
  • It's not intended, I just dont know any other method in which to get the same results. I've been trying to clean it up. – WokerHead Jun 11 '17 at 08:22
  • To clean it up would it just be the line that you said `let blogList: NSObject = ((jsonArray[i]) as! NSObject).value(forKey: "blogList") as! NSObject` . How would I rewrite this to clean it up. – WokerHead Jun 11 '17 at 19:06
  • No offense, did you read my first comment of the question entirely? – vadian Jun 11 '17 at 19:09
  • I did, NSCoder converts objects to a plist format but I do not understand on how to fix this error. Very new to NSCoding – WokerHead Jun 11 '17 at 19:11