Swift NSSet & CoreData
Asked Answered
M

2

6

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 !

Moneybag answered 23/11, 2014 at 16:32 Comment(0)
E
13
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.

Endstopped answered 23/11, 2014 at 16:37 Comment(5)
Great thanks Matt ... I am getting closer to kissing the ground swift walks on already...Moneybag
Is it possible to set the type in the for loop? Something like "for aresponse: Response in self.response.question.responses { .." ?Thievish
@Thievish Things are completely different now from when this question was asked and answered. Swift now has a native Set type, with an element type. And Objective-C now has the ability to mark the element type of an NSSet.Endstopped
@Endstopped following your last comment, could you check my answer?, I've just found it out and I would like to confirm it.Bluebonnet
For me in swift 3 I had to do something like this (aresponse as! Response).score.integerValueOrton
B
3

Actually I've discovered something that might be useful. As of version Xcode 7.2 and Swift 2.0 the following declaration works:

@NSManaged var responses: Set<Response>

I think it might be related with the fact that Swift automatically bridges between NSSet and NSArray. I haven't found this documented but it's working for me and I would appreciate if someone confirms it.

Bluebonnet answered 29/1, 2016 at 21:7 Comment(2)
I just modified my CoreDataProperties backers to say @NSManaged var relationship: Set<MyClass>? and can confirm it works - this is a bit risky as you'll lose it if you regenerate those files via Xcode.Ermine
I agree that this method is risky since I regenerate the files often, the answer by Matt I think is safer answer, still this answer helped me understand Matts answer better.Orton

© 2022 - 2024 — McMap. All rights reserved.