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 !
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.