24
Aug
2008
 

Cocoa Tutorial: C# LINQ Or Cocoa Key Paths And NSPredicate

by Matt Long

I think it may be helpful to demonstrate how to perform certain tasks in Cocoa from the perspective of a Windows programmer. And considering that I was one, it seems that I may be able to help shine some light on some of the issues that programmers face when coming over to the Mac platform. In this post I want to highlight one of Objective-C’s coolest features, key-value coding, in light of a Microsoft technology that was introduced in the most recent edition of the .NET framework (v3.5).

Microsoft’s new technology called LINQ, which stands for Language Integrated Query provides a way for developers to massage data collections using a SQL-like syntax that is built right into the language. You can perform simple data queries like this.

List products = GetProductList();

var soldOutProducts =
    from p in products
    where p.UnitsInStock == 0
    select p;
    
Console.WriteLine("Sold out products:");
foreach (var product in soldOutProducts)
{
    Console.WriteLine("{0} is sold out!", product.ProductName);
}

Notice how it has a very SQL like syntax. It’s actually very powerful and robust. There are a lot of capabilities. Take a look at the 101 LINQ Samples page if you would like to see more examples. What’s most interesting to me though is that the ideas behind this technology are not new.

If you’re new to Cocoa, you’ve probably heard of key-value coding and have probably even worked through some examples of its use without really understanding it. While the usage is different between the two, these technologies provide similar capabilities. Objective-C just had them first.

Key Paths Rule

Since OS X, the first version, key paths have been an integral part of data access and manipulation. Given an array or dictionary of objects you can filter your data down to a more specific data set. Your array doesn’t need to know anything about your data objects within the array or dictionary as the fields can be accessed by name. Have you ever seen syntax like this in Objective-C and wondered what it is?

NSArray *lastNames = [customers valueForKeyPath:@"lastName"];

Say you have declared a Customer class like this:

@interface Customer : NSObject {
    NSString *firstName;
    NSString *lastName;
    NSString *emailAddress;
}

You declare some of the customer objects and add them to an array.

Customer *bob = [[Customer alloc] init];
[bob setValue:@"Bob" forKey:@"firstName"];
[bob setValue:@"Smith" forKey:@"lastName"];
[bob setValue:@"bob@somedomain.com" forKey:@"emailAddress"];
    
Customer *carol = [[Customer alloc] init];
[carol setValue:@"Carol" forKey:@"firstName"];
[carol setValue:@"Jones" forKey:@"lastName"];
[carol setValue:@"carol@somedomain.com" forKey:@"emailAddress"];
    
Customer *wilma = [[Customer alloc] init];
[wilma setValue:@"Wilma" forKey:@"firstName"];
[wilma setValue:@"Baker" forKey:@"lastName"];
[wilma setValue:@"wilma@somedomain.com" forKey:@"emailAddress"];
    
customers = [[[NSMutableArray alloc] initWithObjects:bob, carol, wilma, nil] retain];

Note: I could declare some field accessors for the Customer object which would allow you to get and set field values, but since this post is largely about key-value coding, I figured it would makes sense to demonstrate the full key-value coding way. There is no concept of private variable in Objective-C, so we can simply call – setValue: forKey: to set our fields.

So if you recall this code from above:

NSArray *lastNames = [customers valueForKeyPath:@"lastName"];

you can see that once -valueForKeyPath gets called on the customers array, the NSArray lastNames would now contain a list of all of the last names in the customers array.

(
    Smith,
    Jones,
    Baker
)

Ok, so that might only be slightly helpful, but keep in mind that you can filter this way as many times on the same array or a filtered array as you need to–not to mention that this capability has been around in OS X since 10.0. That’s right–ten dot oh; the first version of OS X. By time OS X 10.4 Tiger came around in 2005, this capability had improved significantly. Objective-C now had a way to use set and array operators that allow you to filter your results further and more efficiently.

Set and Array Operators

You can find all of the details that you might need to take advantage of these key path operators in the Key Value Coding Programming Guide. Read it. It’s very helpful and comprehensive. In essence what these operators provide is a way to get a count, sum, average, max, min, distinct arrays, and arrays of joined arrays based on simple key path syntax.

Say you were interested in the average string length of the email addresses of all of your customers (not terribly useful, but oh so demonstrative). You could get that using this call.

[customers valueForKeyPath:@"@avg.emailAddress.length"];

Keep in mind that the length field is not some special syntax but rather simply a key-value coding compliant variable in an NSString object which is what emailAddress is.

The result of the code above returns the average email string length as an NSNumber. In my example code from above, this returns 19 for the NSNumber value.

But Wait! There’s More! NSPredicate

If you need to filter your array even further, you can do this simply by using an NSPredicate. Scott Stevenson has a short and sweet blog post that demonstrates clearly how to use an NSPredicate on an array at his Theocacao: Using NSPredicate to Filter an NSArray. I’ll make his code conform to our example code here.

NSPredicate *predicate;
predicate = [NSPredicate predicateWithFormat:@"emailAddress.length > 18"];
NSArray  *longEmailAddresses = [customers filteredArrayUsingPredicate:predicate]; 

NSLog(@"Email Addresses Longer Than 18 Chars: %@", longEmailAddresses);

The array longEmailAddresses now contains two Customer objects for the two customers who have email address string lengths greater than 18 characters.

But really that’s just the beginning. You can find all you need to know about Predicates in the Predicate Programming Guide at the Apple Developer site. You’ll see that you can combine the set and array operator syntax with an NSPredicate to get pretty much any type of data query filter you can imagine. As an example, say you want to get an array of all of the customers who’s email addresses use the same domain name. The code would go like this.

predicate = [NSPredicate predicateWithFormat:@"emailAddress endswith '@somedomain.com'"];

This will return all of our customer objects in the array because they all have the same domain name, however, if you change one of the email addresses, say for Carol.

[carol setValue:@"carol@otherdomain.com" forKey:@"emailAddress"];

you will notice that the array will now only contain the two customer objects who’s domain names are somedomain.com.

So What About LINQ

LINQ is actually very interesting technology. It allows you to run these types of inline queries on different types of data sets including XML and SQL databases. To ask the question “which is better” is really just an invitation to argue, so I won’t claim that the Cocoa is necessarily better. It has, however, been around longer and has provided these capabilities since the early days. So the point is that if you’ve seen this “new-fangled technology” from Microsoft and are wondering if you can do the same in Cocoa, the answer is a resounding yes. Data filtering as part of the language is cool technology, but it is most certainly not a Microsoft invention. All props to Anders Hejlsberg, the creator of C# and LINQ. He’s an amazing engineer and has brought a lot of great insight to Microsoft on behalf of developers. If you have to develop applications on Windows, be thankful for this guy. Without him, things would be a lot worse.

If you are, however, free to code in Cocoa, then embrace the key-value coding methodology. It’s very powerful and gives you flexibility that helps you to reduce the amount of code you need to write.

Conclusion

As a Objective-C/Cocoa developer, you need to get your key-value coding game on. It’s a very powerful component of the language and once you understand it, you’ll be able to not only utilize it where its available in the API, but you’ll be able to put it to use in your own objects. Until next time.

Comments

Chris Hanson says:

Your array-KVC examples are incorrect: Since you’re working with Customer objects, you should be using a key path of @”lastName” and not @”customer.lastName”.

[…] Tutorial: C# LINQ Or Cocoa Key Paths And NSPredicate Cocoa Tutorial: C# LINQ Or Cocoa Key Paths And NSPredicate: “I think it may be helpful to demonstrate how to perform certain tasks in Cocoa from the […]

thsutton says:

While LINQ and Cocoa bindings do serve similar purposes, they are quite different from a technology point of view. Whereas LINQ is part of the language with a syntax and a semantics (grounded in some quite interesting theory) that the compiler can and does check, Cocoa bindings are just strings in so far as the language is concerned and are interpreted on an ad-hoc basis by whatever object they happen to get passed to.

Another point about bindings that caused me no little confusion (and a symptom of their ad-hoc interpretation) is that the set and array operators are not supported by all collection classes. Some collections returned by Core Data, in particular and for example, don’t seem to support *any* operators but @count so you wind up doing things like coding up a value transformer to fake it.

I’ve not yet used NSPredicate, so I won’t bother to comment on it.

Finally, a note that I’m a Cocoa and Objective-C beginner so responses highlighting obvious and not-so-obvious errors and misunderstandings would be appreciated.

[…] can read more about how Apple’s OS X handles this in Matt Long’s posting over at the interestingly named “Cocoa Is My Girlfriend” blog. Tags: cocoa, key paths, […]

Matt Long says:

@Chris Hanson

Yep. You’re right. I’ve fixed it. Was a typo.

Thanks.

-Matt

[…] Cocoa Is My Girlfriend > Cocoa Tutorial: C# LINQ Or Cocoa Key Paths And NSPredicate – How to achieve LINQ-like filtering in Cocoa […]

Nimi Peleg says:

I think a more appropriate comparison to LINQ should be Core Data, which allows you to match NSPredicate queries against XML, SQLite, or in-memory stores.