Swift NSSet & CoreData

Ian33 picture Ian33 · Nov 23, 2014 · Viewed 8.3k times · Source

I am trying to move an objective C & CoreData app to Swift and iOS and hit a brick wall with iterating through NSSet objects:

Xcode has generated these classes:

class Response: NSManagedObject {
    @NSManaged var responseText: String
    @NSManaged var score: NSNumber
    @NSManaged var answers: NSSet
    @NSManaged var question: Question
}

class Question: NSManagedObject {
    @NSManaged var questionText: String
    @NSManaged var questionType: String
    @NSManaged var qualifier: Qualifier
    @NSManaged var responses: NSSet
}

then in the 3rd class I am adding a method that needs to iterate though an NSSet..

class Answer: NSManagedObject {

    @NSManaged var evaluation: Evaluation
    @NSManaged var response: Response

    func scoreGap() -> Int {
        var max = 0
        for aresponse in self.response.question.responses {

            // this next line throws an error
            var scoreVal = aresponse.score.integerValue

            if scoreVal > max { max = scoreVal }
        }
        return max - self.response.score.integerValue
    }
}

The line after the comment above gives and error "AnyObject does not have a member named score"

The Objective-C method looks like this and works fine:

// returns the gap between the score of this answer and the max score
-(NSNumber *)scoreGap
{
    long max = 0;
    for(Response *aresponse in self.response.question.responses) {
        if(aresponse.score.longValue > max) max = aresponse.score.longValue;
    }   
    max = max - self.response.score.longValue;
    return @(max);
}

This is about day three on Swift so I may well be missing something really obvious... but at the moment I really don't get it !

Answer

matt picture matt · Nov 23, 2014
for aresponse in self.response.question.responses {
    // this next line throws an error
    var scoreVal = aresponse.score.integerValue

The problem is that for aresponse in ... responses is iterating through an NSSet. Swift doesn't know what is in the NSSet, so it treats each value aresponse as an AnyObject, which has no properties. Thus, the compiler complains when you try to get the score property of aresponse.

You, on the other hand, know that each value aresponse is a Response. So you need to tell Swift this, by casting (with as). Note that you were in fact doing that in your Objective-C code:

for(Response *aresponse in self.response.question.responses)

But you have forgotten to do the same thing in Swift. You can do it in various ways; for example you could write this sort of thing:

for aresponse in self.response.question.responses {
    var scoreVal = (aresponse as Response).score.integerValue

That gets us past the compiler, which now shares in our knowledge of what this variable really is.

Once you get used to this, you will be kissing the ground that Swift walks on. All this time, in Objective-C, you were making assumptions about what kind of object you had, but in fact what you had was an id and the compiler was letting you send any message at all to it - and so, you would often crash with "unrecognized selector". In Swift, an "unrecognized selector" crash is basically impossible because the type of everything is specified and the compiler won't let you send an inappropriate message to an object in the first place.