NSLog file noise


#1

I was afraid I had messed something up.

Then I went and looked at the code and finally I loaded the saved application in the solutions.

When you CREATE a new item WITHOUT a picture the code STILL attempts to find it in the cache and the file system.

So you will see this noise in your logs:

[quote]2014-03-11 12:31:28.465 HomePwner[4318:60b] Error: unable to find /Volumes/Drobo/Users/sincerus/Library/Application Support/iPhone Simulator/7.1/Applications/B43D6187-0E2A-4386-92DC-4DA361674C92/Documents/018242F1-230E-47BF-8AB0-2EE83E66EE6D
2014-03-11 12:31:46.229 HomePwner[4318:60b] Error: unable to find /Volumes/Drobo/Users/sincerus/Library/Application Support/iPhone Simulator/7.1/Applications/B43D6187-0E2A-4386-92DC-4DA361674C92/Documents/635BC2D5-AB39-4587-A1C4-0712DE6E5E72
2014-03-11 12:31:49.470 HomePwner[4318:60b] Error: unable to find /Volumes/Drobo/Users/sincerus/Library/Application Support/iPhone Simulator/7.1/Applications/B43D6187-0E2A-4386-92DC-4DA361674C92/Documents/118E7080-9267-45AC-872B-3B5483F61F58
2014-03-11 12:31:51.833 HomePwner[4318:60b] Error: unable to find /Volumes/Drobo/Users/sincerus/Library/Application Support/iPhone Simulator/7.1/Applications/B43D6187-0E2A-4386-92DC-4DA361674C92/Documents/93F56A2C-8663-4D49-B167-6411A39DD289
2014-03-11 12:32:02.685 HomePwner[4318:60b] Saved all of the BNRItems[/quote]

I added the following to the code below the NSLog so I could see the call stack. Quicker than setting a break point and debugging. :wink:

if (result) {
            self.dictionary[key] = result;
        } else {
            NSLog(@"Error: unable to find %@", [self imagePathForKey:key]);
            @throw [NSException exceptionWithName:@"Signal flare"
                                           reason:@"Wanting to see teh call stack and how I got here]"
                                         userInfo:nil];
        }

This reveals…

So after digging I found my controller needed to know the STATE of the record.

I first put a hack at the controller like so in the BNRDetailViewController:

...
// prevent trying to load image on newly created record
@property (nonatomic, getter = isNew) Boolean newRecord;

@end

@implementation BNRDetailViewController
...
-(instancetype)initForNewItem:(BOOL)isNew {
    self = [super initWithNibName:nil bundle:nil];
    
    // set state var
    _newRecord = isNew;
    
    if(self) {
        if (isNew) {

The problem with THIS hack is it solved my ‘new’ button problem but it does NOT solve drilling down for a record that NEVER received an image.

Using a little OO process of isA and hasA the ‘item’ record HAS a state. So I refactored my hack above to be against the BNRItem record.

So the archival code with NSCoder needs to get updated to handle this extra property:

@property (nonatomic, getter = hasImage) Boolean imageFlag;
@property (nonatomic, copy) NSString *itemKey;

I changed the getter to make it a little easier to read. So you can just call it via ‘item.hasImage’ for example.

Didn’t mean for this to turn into a new book or essay. =)

Just an FYI.


#2

My final code changes and it worked with no issue. It also removed the log noise for new records. Second to last line added Boolean imageFlag.

@interface BNRItem : NSObject <NSCoding>

@property (copy, nonatomic) NSString *itemName;
@property (copy, nonatomic) NSString *serialNumber;
@property (nonatomic) int valueInDollars;
@property (strong, nonatomic, readonly) NSDate *dateCreated;

@property (nonatomic, getter = hasImage) Boolean imageFlag;
@property (nonatomic, copy) NSString *itemKey;

Then I update my NSCoder methods to handle the new object property correctly.

#pragma mark - Archival methods

-(void)encodeWithCoder:(NSCoder *)aCoder {
    
    [aCoder encodeObject:self.itemName forKey:@"itemName"];
    [aCoder encodeObject:self.serialNumber forKey:@"serialNumber"];
    [aCoder encodeObject:self.dateCreated forKey:@"dateCreated"];
    [aCoder encodeObject:self.itemKey forKey:@"itemKey"];  // used for image store lookup
    
    [aCoder encodeBool:self.imageFlag forKey:@"imageFlag"];
    
    [aCoder encodeInt:self.valueInDollars forKey:@"valueInDollars"];
    
}

-(instancetype)initWithCoder:(NSCoder *) aDecoder {
    self = [super init];
    if (self) {
        _itemName = [aDecoder decodeObjectForKey:@"itemName"];
        _serialNumber = [aDecoder decodeObjectForKey:@"serialNumber"];
        _dateCreated = [aDecoder decodeObjectForKey:@"dateCreated"];
        _itemKey = [aDecoder decodeObjectForKey:@"itemKey"];
        
       _imageFlag = [aDecoder decodeBoolForKey:@"imageFlag"];
        
        _valueInDollars = [aDecoder decodeIntForKey:@"valueInDollars"];
    }
    
    return self;
}

#pragma mark -

Object properties are memset to binary zero and default to NO or FALSE when created. Not true for a LOCAL methods but it does work for ivars. FYI
So it ALWAYS defaults to FALSE. So it only needs to be flipped to TRUE or YES below.

Set the BNRItem picture flag in the BNRDetailViewController here:

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {
    
    // Get picked image from info dictionary
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    
    // Store the image in the STImage store using the STItem key
    [[STImageStore sharedStore] setImage:image forKey:self.item.itemKey];
    
    // set this items pic flag to true
    _item.imageFlag = TRUE;
    
    // drop the new pic into our imageView container
    self.imageView.image = image;

Then just put the last normal line called in the ‘viewWillAppear’ event to check the image flag. Notice the very bottom that has the if(hasImage) check below:

- (void)viewWillAppear:(BOOL)animated {
    
    [super viewWillAppear];
    
    UIInterfaceOrientation io = [[ UIApplication sharedApplication] statusBarOrientation];
    [self prepareViewsForOrientation:io];
    
    STItem *item = self.item;
    
    self.nameField.text = item.itemName;
    self.serialNumberField.text = item.serialNumber;
    self.valueField.text = [NSString stringWithFormat:@"%d", item.valueInDollars];
    
    // create dataFormatter to display date correctly
    static NSDateFormatter *df = nil;
    if (!df) {
        df = [[NSDateFormatter alloc] init];
        df.dateStyle = NSDateFormatterMediumStyle;
        df.timeStyle = NSDateFormatterNoStyle;
    }
    
    // now format the date object using the formmater
    self.dateLabel.text = [df stringFromDate:item.dateCreated];
    
    // load image into imageView control
    NSString *imageKey = self.item.itemKey;
    
    // attempt to load ONLY if we have an image
    if (item.hasImage) {
        UIImage *imageToDisplay = [[STImageStore sharedStore] imageForKey];
        self.imageView.image = imageToDisplay;
    }
    
}

Funny how documenting something give the ‘smart and elegant’ part of your brain a chance to talk to you. =)

After doing ALL of this it came to me that a cleaner and better option.

Set the UUID to ‘nil’ unless we create an image. Then just do a nil check for the imageKey and use it’s ‘nil’ value as the boolean flag.

We could even keep the helper method and have it just wrap the imageKey value.
The only heavy lifting change would be were we set it to ‘YES’. Refactor the code that creates the UUID key out of the CTOR.
Move it into a helper method that gets called if you set it to a YES.
You could even have it purge the image on setting it to a NO.

I hope this rant helps somebody. =)

Cheers


#3

Nice work. That “error” was messing with my head too. You saved me some digging.


#4

Yes, I’m grateful for your writing this up. In my opinion it’s an errata in the book. I too looked high and low to figure out what I had done wrong and just a few minutes ago realized (after looking at the imageForKey: code more closely that the operation isn’t actually in error at all: if (as you wrote) there is no image (either because it’s a new item that hasn’t had a chance to have an image assigned yet) or if it’s a pre-existing item that has never had an image associated with it, this NSLog message is triggered. What a wild goose chase!