Gold challenge Major issue


#1

I have spent 3 whole days trying to crack this one but i think this particular problem is just not solvable for me… I have tried different ways… This is my last attempt-
So first of all i add a new method and a couple of new ivars to BNRItemStore…

BNRItemStore.h

@property NSMutableArray *assets;
@property NSManagedObject *currentAssetType;
-(NSMutableArray *)filteredSetOfAllItemsIn:(NSManagedObject *)assetType usingCurrentAsset:(BNRItem *)item;

BNRItemStore.m

-(NSMutableArray *)filteredSetOfAllItemsIn:(NSManagedObject *)assetType usingCurrentAsset:(BNRItem *)item{    
    if(!assets || (currentAssetType!=assetType)){
        currentAssetType= assetType;
        NSLog(@"assets array initialising");
        assets= [[NSMutableArray alloc] init];
    }
    if (item.assetType==currentAssetType) {
        NSLog(@"item- %@",item);
        [assets addObject:item];
    }
    return assets;
}

Then i send this method above as a message to BNRItemStore object in AssetTypePicker-
AssetTypePicker.h (2 new properties)

NSManagedObject *selectedAssetType;
@property NSMutableArray *assets;

AssetTypePicker.m

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSLog(@"Test2");
    if (section==0) {
        return [[[BNRItemStore sharedStore] allAssetTypes] count]; //3 Asset Types
    }
    else{
        NSLog(@"asset count %d",self.assets.count);
        return self.assets.count;
    }
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    NSLog(@"Selecting a row in AssetTypePicker");
    UITableViewCell *cell= [tableView cellForRowAtIndexPath:indexPath];
    
    [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
    
    NSArray *allAssets=[[BNRItemStore sharedStore] allAssetTypes];
    selectedAssetType= [allAssets objectAtIndex:indexPath.row]; //each assetType is of type NSManagedObject
    
    [item setAssetType:selectedAssetType];
    
    self.assets=[[BNRItemStore sharedStore] filteredSetOfAllItemsIn:selectedAssetType usingCurrentAsset:item];
    NSLog(@"assets- %@",self.assets);
    
    NSLog(@"popping off modal controller's view");
    [self.navigationController popViewControllerAnimated:YES];   
}

In the above scenario, i have added filteredSetOfAllItemsIn: usingCurrentAsset: for adding all the various assets (BNRItems) to their appropriate AssetTypes…
The code in this method only runs for only single case though wherein if i add 2 items and group them under AssetType- “Furniture” (for instance) consecutively,
the corresponding 2 assets(BNRItems) get added in one array object. Similarly for the next 2 items, if grouped under AssetType- “Jewelry”, the corresponding assets will be added to a new array object and so on…
But i want this particular code to also include the case wherein when i add the first item under AssetType-“Furniture” and second item under AssetType-“Jewelry” (thus creating 2 separate array objects with those items added individually in each one of them),the third item, if mapped again to AssetType-“Furniture”, ought to be added to the first array rather than creating a separate third array… The Array object creation should be per AssetType and not per Asset itself (BNRItem). However sadly, this isn’t the case currently (in the code above) and i haven’t been able to tune the code in the method to make it work for this particular case… Is there any way to get this working??
I can’t believe i spent 3 days and still couldn’t get the damn thing solved… Earlier i tried other things including using a dictionary and soon hit a dead end. Please do not suggest NSPredicate… Truth be told i do not want to solve this any other way using other techniques unless absolutely necessary i.e if there is no way i can solve using this technique above…i had to think real hard before i was able to come up with this… And still not solved it… Pls assist :confused: :confused: :confused: I am so frustrated with this chapter… Have spent 12 days over it still things aren’t clear… This chapter really ruined my experience… I want to move to the next chapter…


#2

I suggest you take a step back and reconsider the problem at hand. The Gold challenge is simply asking you to create a second section in the table view, and then show all the assets (i.e. items) that belong to the currently selected asset type (i.e. the asset type that is currently check marked in the table view).

At a high level, the problem involves obtaining an array of items that are ‘related’ to the currently selected asset type, and displaying those items in a newly created area in the view. So using the design pattern MVC, you’re view Controller, BNRAssetTypeViewController, will get some data (Model), and then display it in the table View.

  1. Prepare the View by telling the table view that you’d like to add a second section. Also add some clarity by titling the sections.

In BNRAssetTypeViewController

[code]- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 2;
}

  • (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    {
    if (section == 0)
    return @“Asset Types”;

    return @“Items for Selected Asset Type”;
    }

// Indicate how many rows you need for each section

  • (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    NSManagedObject *assetType = self.item.assetType;
    NSSet *items = [assetType valueForKey:@“items”];

    if (section == 1) {
    return [items count];
    }

    return [[[BNRItemStore shareStore] allAssetTypes] count];
    }

[/code]

  1. Obtain the data and display it in the second section of the table view:

In BNRAssetTypeViewController

[code]- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@“UITableViewCell” forIndexPath:indexPath];

NSUInteger section = [indexPath section];

NSArray *allAssets = [[BNRItemStore shareStore] allAssetTypes];

if (section == 0) { // This section shows all possible asset types
    NSManagedObject *assetType = allAssets[indexPath.row]; // The asset type to show
    // Use key-value coding to get the asset type's label
    NSString *assetLabel = [assetType valueForKey:@"label"];
    cell.textLabel.text = assetLabel;
    
    // Checkmark the one that is currently selected
    if (assetType == self.item.assetType) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    }
    else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
}
else if (section == 1) { // This section shows all items that belong to the currently selected asset type
    // Currently checkmarked Asset Type
    NSManagedObject *assetType = self.item.assetType;
    // Get the set of items that are related to the currently selected asset type.
    // No need to fetch the items since we already have a handle on it.
    NSSet *items = [assetType valueForKey:@"items"];
    // Place the items in an array and sort them according to their orderingValue
    NSArray *sortedItems = [items sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"orderingValue" ascending:YES]]];
    // Get the item that corresponds to this particualar row
    BNRItem *anyItem = sortedItems[indexPath.row];
    
    cell.textLabel.text = anyItem.itemName;
}

return cell;

}
[/code]

Hope that clarified it.


#3

Wow I hadn’t thought of it this way at all. But where did we set the values for key @“items”. Does it take that from the model file coz we had items as one of the ivars of BNRAssetType entity. I’m confused. I had tried fetching objects from a dictionary before but i didn’t have a clue as to how because I couldn’t figure out an appropriate key on basis of which the relevant assets could be picked out. So where did we configure the assets on under @“items” key.
Thanks for the assistance though. It means a lot given how lost I was trying to solve it using an array. I guess using an array was a wrong turn to begin with. What do u think?


#4

Learning Core Data has a steep curve so don’t be discouraged. As for your following thought, you are correct:

If you go back to the book (sorry can’t give you a page number but it’s right after Figure 23.5 in the Core Data Chapter) where it explains “to-one” vs “to-many” relationships, it explains what you figured out above. Namely “When an entity has a to-may relationship, each instance of that entity has a pointer to an NSSet. This set contains [a pointer to] the instance of the entity that it has a relationship with”.

Core Data allows you to use information that is stored in SQLite data base like they are Objective-C objects (abstracts it away for ease of use). Every NSManagedObject you create with ‘attributes’ and ‘relationships’, is treated as an Objective-C object. So the attributes and relationship you specify will end up being iVars. Either primitive iVars (such as int, float, char etc), or pointers to objects. For a ‘relationship’ you have a choice of two types. If you specify ‘to-one’ relationship, then you will simply have a single pointer that can point to a different NSManagedObject. If you specify a ‘to-many’, then Core Data creates an NSSet. The set will contain one or more pointers that can point to a different NSManagedObject (a type that you specified in your object graph).

The good thing about Core Data that it does a lot of things for you. The bad thing…well, it does a lot of things for you, so some things will seem to be happening by ‘magic’.

Allow me to explain. In BNRItemStore.m, you created BNRItems (NSManagedObjects), in - (BNRItem *)createItem. In there you do not connect/relate the item to any Asset Type. In BNRAssetTypeViewController.m, in - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath, if a user taps on any table row containing an asset type, you set BNRItem’s property like so, self.item.assetType = assetType. This is where the ‘magic’ happens. Because in the Data Model file you made BNRItem point to a BNRAssetType and an BNRAssetType point to a BNRItem, any time you set one side of a relationship, Core Data will set the other side automatically for you. So, as soon as you said self.item.assetType --> assetType, Core Data will go to assetType object and do this from behind the scenes:

NSMutableSet *itemsSet = [assetType mutableSetValueForKey:@"items"]; [itemsSet addObject:self.item];

… The connection between BNRItem and BNRAssetType, being a two way relationship, both objects end up pointing at each other now. To test this in your code, simply replace “self.item.assetType = assetType” with the above code to confirm it. So, to set relationships, you can set it on either end, and Core Data will set it on the side that you did not set.

Makes sense?


#5

It is starting to make sense just a bit but this behind the scenes thing that Core Data does, makes it hard for a newbie to understand the mechanism in which objects are talking to each other… It feels like i have so very little control over my own code… Ok, now tell me if i’m wrong about this…
So i already understand that for instance i set the object pointer assetType of BNRItem object to point to BNRAssetType and immediately reverse linking happens (items pointer of BNRAssetType points to NSSet object that now contains all the assets (BNRItems) i.e owns them.
Now you say that key-value pairing (wherein the key is @“items”) happens on its own which is done by Core-Data… As u described using the following–

[quote] NSMutableSet *itemsSet = [assetType mutableSetValueForKey:@"items"]; [itemsSet addObject:self.item];[/quote]

So this “magic” must be happening after we have set the model file with the object graph. Otherwise it couldn’t have happened during when a message is passed to an object for executing specific SQL queries under the hood . Also, can we change this particular key string… I hope so.
There is one more confusion i’m having with the above code snippet-. On passing the mutableSetValueForKey: to assetType we are actually returned an NSSet object (though the message is capable of returning a Mutable version)… So we are having an NSMutableSet (subclass of NSSet) type pointer pointing to it. Now in second line we are basically passing addObject message to an NSSet object…But is NSSet capable to handle addObjects message??..We are letting a superclass object be pointed at by a subclass type pointer…Does this work because it returns an object of type id??
I’m not sure…
Anyways besides this side issue, i believe that this line of code-

is also returning an entity object (BNRAssetType in this case) through under the hood Core-Data activities i.e by setting up model file and not because we set up a value for the key-@“BNRAssetType”… I could be terribly wrong but i’m curious…


#6

For your first question:

There’s no way to know for certain what happens exactly when, as how Apple implements those APIs are closed. However, it might help to understand that the Data Model file defines the objects and their relationships. It’s a template. When you save to disk or retrieve from disk, Core Data uses the data Model to construct the NSManagedObjects according to their definitions in the data Model. The NSManagedContext object is the one that keeps track of the state of all these objects that are retrieved from the database. So, Core Data retrieves rows from a SQLite database and construct NSManagedObjects out of them, and tracks their state through this ‘context’. When you set a relationship for one of the managed objects to point to another one, the context is most likely the one establishing the reverse relationship. Now if the other object that is being pointed to is in the context, then Core Data (through faulting) will retrieve if from the database and establish the reverse relationship. Once you save the context, then all changes in the context are committed to disk.

For your second question:

[quote]Also, can we change this particular key string…
[/quote]

The string for this key is determined by the name that you assign to your attributes and relationships in the data model. If you change them after the data base file has been created, then you will get an error message, as you changed the template but the content of disk is different. That’s ok during development as you can simply delete the file on disk and re-create a new one based on the new data model. However the proper way for doing that would be through data migration.

For your third question:

If you place a breakpoint at “NSMutableSet itemSet = [assetType mutableSetValueForKey@“items”]", and check out “itemSet” in the debugger, you’ll see that the class type is something called "_NSNotifyingWrapperMutableSet”. When you do the same for the line “NSSet items = [assetType valueForKey:@“items”];", then it’s something called "_NSFaultingMutableSet”. Regardless of what those are, you can treat them as a class that has the same interface as an NSMutableSet.

Even if you declared the variable “items” as an NSSet, the class that returns will respond to the same methods that NSMutableSet responds to. So for example you can do something like this without any errors:

NSSet *items = [assetType valueForKey:@"items"]; [(id)items addObject:self.item];

adding “id” to front of ‘items’ is not changing ‘items’ from an NSSet to an id type. All it’s doing is quieting the compiler to allow sending ‘items’ the message addObject, to which it will respond to.

For question 4:

‘e’ will be pointing to an NSEntityDescription, not an NSManagedObject, which what BNRAssetType is equivalent to. The method [model entitiesByName] will return a NSDictionary that contains keys made up of the names of the entities contained in the model, and the objects are composed of NSEntityDescription objects (and not NSManagedObjects).


#7

[quote]If you place a breakpoint at “NSMutableSet itemSet = [assetType mutableSetValueForKey@“items”]", and check out “itemSet” in the debugger, you’ll see that the class type is something called "_NSNotifyingWrapperMutableSet”. When you do the same for the line “NSSet items = [assetType valueForKey:@“items”];", then it’s something called "_NSFaultingMutableSet”. Regardless of what those are, you can treat them as a class that has the same interface as an NSMutableSet.
[/quote]

So that means my assumption that mutableSetValueForKey: in actuality returned NSSet was in fact wrong… It doesn’t return that, instead what it returns is some form of NSMutableSet object which then is pointed to by a NSMutableSet type object pointer-itemSet. If this is the case then its perfectly OK. Coz otherwise if the former was true we would have had a situation wherein we would be passing subclass type message to a superclass object which would then have given a runtime error…
I’m not saying anything about duck-typing using id type to suppress compiler warning related to uncertainty whether a given object is suitable enough to receive a subclass purposed message.


#8

Correct.

I was just making sure that point was clear.


#9

Thanks a lot for the insight. It really means a lot. I have been stuck with this chapter for a very long time now. Some of the things in the chapter are clear, some of them semi-clear, a lot of it totally unclear. Out of the three challenges, I think the gold challenge was more purposed toward CoreData concepts. I think it’s time I moved on to the next chapter. It’s already gotten so delayed. I really need to finish the book. I guess I will come back to Core Data later on as it is so damn important and a crucial framework that cannot be done without, if building any kind of apps- big or small. They all need a persistent store.Coz reading and writing incremental changes to the pre-existing data involving opening up the whole file will become so inefficient overtime due to performance costs. I guess I will study Core Data in greater detail in future. Right now I got other stuff too on my plate. The learning curve for this chapter is definitely very steep. There are a ton of things. I’ll tackle these later on I guess. There aren’t very many good Core Data related reading material, are there? First off, I’ll also have to take a look at apple’s core data guide. Maybe I’ll get some nuggets and then work my way up from there. What do u think? Again thanx a ton for being considerably detailed in your solutions and making them easy to follow.


#10

You’re welcome. Being relatively new to programming myself I know exactly how you feel. The best part is that the knowledge you gain will be like layers of an onion, you will never learn everything completely, but will accumulate as layers. It’s always best to learn as much as possible, move on, and then come back again.

As for reading recommendations, the only true first hand book that I can recommend is Pro Core Data for iOS, Second Edition by Warner, Robert and Privat , Michael (Nov 23 2011), however it’s for iOS5. However despite it’s age, many of the things that apply to Core Data are still there. Some things changed considerably especially when it comes to iCloud and Core Data integration (which this book does not discuss), but many of those things you can supplement through Apple’s documentation (which is very lacking when it comes to iCloud and Core data). There was a dramatic change with Core Data and iCloud with iOS7 however strangely enough Apple did not release any updated docs or sample code to show how to use it properly. There are rumours they might make that process even simpler for developers with iOS8. We’ll wait and see.

You can try reading Apple’s doc, however without much prior basic knowledge, you might find yourself lost very quickly. I recommend you start with a nice basic Core Data book and then revisit Apple’s docs to re-enforce or use as a reference. I may also try reading “Learning Core Data for iOS: A Hands-On Guide to Building Core Data Applications” by Tim Roadley which is recent, if I every get a chance.

Best of luck with your journey.