Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

NSCopying Protocol

I have a factory object for creating new core data entities based on a predicate. This makes it really easy to get the objects that I need, using a single method. I have an instance where I want to duplicate the existing object that I retrieved, so that I can use the existing data in the object, while retreiving the same entity from core data and then making subsequent updates to it. Before, I was just asking core data to retrieve the object again. But why not use the NSCopying protocol?

Let’s get set up

To start with, I have a method in my WordFactory class to obtain a word entity from core data using a simple predicate. The name of the method is getWordFromString:.

-(Word *) getWordFromString:(NSString *)string {
  //get out of here if the string is empty
  if ([string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length == 0){
    return nil;
  }

  //get word from persistence store
  Word *word = [Word objectWithPredicate:[NSPredicate predicateWithFormat:@"word = %@", string]];

  if (word == nil) {
    //create a new word
    //I am not going to show this code to make this simpler
  }
  return word;
}

You can see that I am using RestKit’s ActiveRecord style NSManagedObject extension. This makes it really easy to use a predicate to query core data, cutting down on the ceremony necessary to establish a core data connection and set the predicate. This will return a nil object if there are no results found.

I have reference to a Word, which is called currentWord. This is the word that the user just played. I want to randomly pull another word from an array of Words that I previously created. The NSArray of words that I created previously is called wordsDistributed. The catch is that I want to ensure that the next random word is not the same word that the user just played. So, I still want reference to the old word so I can compare it to the next random word.

Here is the code snippet for getting the next word:

Word *previousWord = [wordFactory getWordFromString:currentWord.word];
  int random;
  while ([currentWord.word isEqualToString:previousWord.word]) {
  random = arc4random() % [wordsDistributed count];
    currentWord = [wordsDistributed objectAtIndex:random];
}

The previousWord was retrieved from Core Data — each time. This seems to be a lot of work for core data to go and fetch another word object. So, while it worked, why not just make a copy of the currentWord and save it as previousWord? To do this I had to implement the NSCopying protocol in my Word class.

Implementing NSCopying

The first thing to do is to tell the Word class that we are going to implement the NSCopying protocol. To do this, we add NSCopying inside a set of brackets after the @interface declaration in the header file (Word.h in this case):

@interface Word : NSManagedObject <RKRequestDelegate, NSCopying>

You can see above that I am also implementing the RKRequestDelegate protocol, so we just specify the protocols in the brackets using a comma-separated list. Next, we look up the Apple documentation for the NSCopying protocol to see what methods are required. As we can see, there is only a single method that is required, called copyWithZone:. We will need to implement this in our, you guessed it, implementation file (Word.m):

#pragma mark - NSCopying protocol methods

- (Word *) copyWithZone: (NSZone *)zone {
  Word *word = [[Word alloc] init];
  [word setWord:[self word]];
  [word setWordID:[self wordID]];
  [word setValid:[self valid]];
  [word setProbability:[self probability]];
  [word setLength:[self length]];
  [word setLastUpdated:[self lastUpdated]];
  [word setFrontHooks:[self frontHooks]];
  [word setBackHooks:[self backHooks]];
  [word setDefinition:[self definition]];
  [word setAttempts:[self attempts]];
  [word setDifficulty:[self difficulty]];
  [word setQueued:[self queued]];
  return word;
}

Here, I am allocating and initializing a new Word object, and storing that as a variable named word. I am then setting the values for all of the properties for the word object. Then, I return this new object, which has all of the same data as the old one. Pretty easy!