0

I have a class called Movie, which as of now, only has a string property called movieTitle.

I have an array of Movie, and using the .contains method returns false even when an object with the same title is in the array. Interestingly enough, .contains works in a playground I made but not in an app setting.

Thanks for the help! I'm fairly new to the programing game so if you and ELI5 things, that would be great!

Here's a snippet of the code I have. What ends up happening, is it just keeps adding the same 10 entries onto the array.

do{
    let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String: AnyObject]
        if let movieSearch = json["Search"] as? [[String: AnyObject]] {
            for movie in movieSearch {
                if let title = movie["Title"] as? String {
                    let newMovie = Movie(movieTitle: title)!

                    if (!self.movieList.contains(newMovie)) {
                        self.movieList.append(newMovie)
                    }
                    self.tableView.reloadData()
                }
            }
        }
    }catch {
        print("Error with Json: \(error)")
    }

Movie Class

import UIKit

class Movie: NSObject, NSCoding {
    // MARK: Properties

    struct PropertyKey {
        static let movieTitleKey = "title"
    }

    // MARK: Archiving Paths

    static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("Movies")

    var movieTitle: String

    // MARK: Initialization

    init?(movieTitle: String) {
        // Initialize stored properties.
        self.movieTitle = movieTitle

        super.init()

        // Initialization should fail if there is no itemName
        if movieTitle.isEmpty {
            return nil
        }
    }

    // MARK: NSCoding

    func encode(with aCoder: NSCoder) {
        aCoder.encode(movieTitle, forKey: PropertyKey.movieTitleKey)
    }

    required convenience init?(coder aDecoder: NSCoder) {
        let title = aDecoder.decodeObject(forKey: PropertyKey.movieTitleKey) as! String

        //Must call designated initializer.
        self.init(movieTitle: title)

    }
}


// MARK: Equatable
func ==(lhs: Movie, rhs: Movie) -> Bool { // Implement Equatable
    return lhs.movieTitle == rhs.movieTitle
}

What works in playgrounds

class Movie: NSObject {
    var movieTitle: String

    init?(movieTitle: String) {
        // Initialize stored properties.
        self.movieTitle = movieTitle

        super.init()

        // Initialization should fail if there is no itemName
        if movieTitle.isEmpty {
            return nil
        }

    }
}

var movieList = [Movie]()

var movie1 = Movie(movieTitle: "Batman")
var movie2 = Movie(movieTitle: "Batman")
var movie3 = Movie(movieTitle: "Superman")

movieList.append(movie1!)
movieList.append(movie2!)

movieList.contains(movie1!) // Returns True
movieList.contains(movie3!) // Returns False

4 Answers4

1

Because your Movie class (why is it a class?) inherits from NSObject (why?), it inherits NSObject's conformance of the Equatable protocol, with the NSObject implementation of ==. By default, this does identity comparison (comparing references), rather than value comparison.

Here's an example:

let movie1 = Movie(movieTitle: "Batman")
let movie2 = Movie(movieTitle: "Batman")

var movieList = [Movie1]

movieList.contains(movie1!) // True, because movie1 was added in
movieList.contains(movie2!) // False, movie2 was never added

Since Movie doesn't override == with an implementation that compares its value(s) (such as movieTitle), it defers to the default implementation, which is comparing the references. Even though movie2 has the same value, it's a distinct object with its own (separate) memory location. Thus, the identity comparison fails, and it's not found.

To solve this implement == to return true iff all the fields of Movie match up. What you're trying to do may be better off being implemented with structs, however.

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Probably, that's because what's happening is a "references comparison" because func ==(lhs: Movie, rhs: Movie) -> Bool { // Implement Equatable return lhs.movieTitle == rhs.movieTitle } – Ahmad F Oct 12 '16 at 07:03
  • @Alexander you are asking why it is a class... Would a `struct` be better for this case? I'm just curious – gasparuff Oct 12 '16 at 11:38
  • @Alexander As to why it's a class, I'm expanding on a tutorial created by apple, and they happened to use a class. I am curious as to why a struct would be better as well – Andrew Timosca Oct 12 '16 at 13:55
1

you should try with this way.

var filtered = [Movie]()
filtered = movieList.filter({$0.movieTitle == "Superman"})
if filtered.count == 1 {
//so,"Superman" movie contained in array..
}

let me know the results... thanks.

Vatsal Shukla
  • 1,274
  • 12
  • 25
0

Try overriding isEqual method of NSObject since it is already conforming Equatable protocol. You can test the code below in a playground. Hope it helps.

class Movie: NSObject {
    var movieTitle: String

    init?(movieTitle: String) {
        // Initialize stored properties.
        self.movieTitle = movieTitle

        super.init()

        // Initialization should fail if there is no itemName
        if movieTitle.isEmpty {
            return nil
        }

    }

    override func isEqual(_ object: Any?) -> Bool {
        guard let theMovie = (object as? Movie) else { return false }
        return movieTitle == theMovie.movieTitle
    }
}

var movieList = [Movie]()

func appendToList(newMovie: Movie) {
    if (!movieList.contains(newMovie)) {
        movieList.append(newMovie)
    }
}

var movie1 = Movie(movieTitle: "Batman")
var movie2 = Movie(movieTitle: "Batman")
var movie3 = Movie(movieTitle: "Superman")

appendToList(newMovie: movie1!)
movieList.count             // count is 1

appendToList(newMovie: movie2!)
movieList.count             // count is still 1 not incremented

movieList.contains(movie1!) // Returns true
movieList.contains(movie2!) // Returns true
movieList.contains(movie3!) // Returns false
Zafer Sevik
  • 191
  • 1
  • 5
0

Just try this code it works perfectly.

do{
    let json = try JSONSerialization.jsonObject(with: data!, options:.allowFragments) as! [String: AnyObject]
        if let movieSearch = json["Search"] as? [[String: AnyObject]] {
            for movie in movieSearch {
                if let title = movie["Title"] as? String {
                    let newMovie = Movie(movieTitle: title)!

                    let movieTitles = (self.movieList as NSArray).value(forKeyPath: "movieTitle") as? [String]
                    if movieTitles == nil || movieTitles!.contains(title) == false {
                        self.movieList.append(newMovie)
                    }
                    self.tableView.reloadData()
                }
            }
        }
    }catch {
        print("Error with Json: \(error)")
    }