Saturnboy
 10.23
Code
off

Since Flex is Dead, I decided to rebuild my fancy Flex Clock using the html5 canvas element. Here is the result:

(click to see it in action)

Lots of very vanilla drawing code that is fairly boring, but one interesting part is how the new canvas-based clock uses svg paths. The old flex code used svg paths liberally as simple way to draw shapes, but since canvas doesn’t support drawing svg path data directly I had to write my own:

function drawPath(ctx,path,fill,ang) {
    ctx.save();
    ctx.rotate(ang == undefined ? 0 : ang  * Math.PI / 180.0);
    ctx.beginPath();
 
    var parts = path.split(' ');
    while (parts.length > 0) {
        var part = parts.shift();
        if (part == 'M') {
            coords = parts.shift().split(',');
            ctx.moveTo(coords[0], coords[1]);
        } else if (part == 'L') {
            continue;
        } else if (part == 'Z') {
            break;
        } else if (part.indexOf(',') >= 0) {
            coords = part.split(',');
            ctx.lineTo(coords[0], coords[1]);
        }
    }
 
    ctx.closePath();
    ctx.fillStyle = (fill == undefined ? '#000' : fill);
    ctx.fill();
    ctx.restore();
}

The drawPath() function takes svg path data as input (with the restriction that it contains only M (move to), L (line to), and Z (close path) elements), and executes moveTo() and lineTo() calls in the canvas element instead. It does zero error checking, so the incoming svg path data must be well formatted.

If you call it like this:

drawPath(ctx, 'M 0,0 L 0,10 10,10 10,0 Z');

You’d get a nice 10px by 10px square as expected.

Conclusion

It’s no surprise that canvas is just as powerful as Flex or SVG as far as drawing shapes and gradients, but it would definitely make canvas extra cool if it had more intergrated svg path support. Yes, of course, I could have used pure svg if I wanted to draw a bunch of svg shapes, but I had set my mind on using canvas only.

Files

 3.24

Adobe killed this blog when they killed Flex. Well I guess Flex is not all the way killed, more like stumbing around with a knife in the neck. So what’s next? I’m breaking out my crystal ball to see what the future holds…

Flex’s Future

With a singular, spectacular blog post Adobe eviscerated Flex and any hope of ever using Flex again. Thanks Adobe! Flex is dead dead, not just a little dead. No one in their right mind should ever start development on a Flex app. Period. I’ll make a nice pull quote just so you understand my opinion on the matter:

“Flex is dead!” — Justin

Spoon is not dead, nor is the open sourcing of Flex on Apache (as Apache Flex), but alas, no amount of open sourcing or intense community love will change things in this particular case. But since everyone is in love with zombies nowadays, I’m sure there are plenty of people and companies out there that see the simulacrum of life in Flex and call it life.

Flex Mobile’s Future (aka Adobe AIR)

Flex Mobile, aka Adobe’s cross-compiler that take AS3 code as input and outputs iOS binaries (as a fully formed IPA) and also Android binaries (APK), is decidedly not dead yet. But the most unfortunate part is that it certainly should be. In general, cross-platform mobile solutions (particularly Flex Mobile) really suffer when the make contact with reality (a real app with real functionality and real users). Sure they can be made to look pretty, sure they make great demos, and sure they lower the initial bar for developer. But you will never ever have as much control as you will when you go native. It is still the case today (March 24 2013) that the only good answer is go native, and write it twice. Twice being once for iOS and once for Android.

Flash Player’s Future

Banned from iOS, unsupported by Chrome Mobile, officially discontinued on all interesting mobile platforms, click to install on desktop Safari, no 64-bit flash player on linux, and now click to run on Firefox. Hmmm…I wonder what is next? How about dead. Yep, my crystal ball says that the flash player itself is on the gallows.

Let’s think about how Adobe makes money from Flash (this is coming from a developer that actually knows nothing)… From what I can tell, they sell really nice authoring tools, they sell some server-side tools (mainly Flash MediaServer, aka FMS), and they have a bunch of game stuff. I love games and I love watching video online, but does the flash-related parts of any of those mostly desktop businesses look healthy and awesome to you, especially with the rapid growth of mobile and tablets?

Adobe’s Future

Adobe will be certainly fine (but again, I’m just some guy who writes code). I always think of them as a tools company. Photoshop is a sweet piece of software (Photoshop 1.0 source is now available in the Computer History Museum). But the whole platform play that is Flash and Flex? I always felt that was Macromedia’s idea, and Adobe just went along for the ride after the aquistion. That ride isn’t much fun anymore.

And who just left? Kevin Lynch is moving to Apple (aka they guy who was chief software architect at Macromedia).

My Future

I know I’ll be doing some more speaking. And I’m trying hard to figure out how to create more teaching opportunities. I love teaching, but for whatever reason it’s hard to find the right teaching opportunity with the right students. I’ll always be learning new feameworks and languages, because that is a constant. But writing more content for this blog, probably not so much…

I’m working on couple of app ideas right now, and still writing lots of code, but only a little bit of that work will make it all the way to this blog. Watch my my github for some new projects.


 9.19

Many ways to do the same thing…but which is right? It’s an issue developers face every single day.

Here’s one of those issues that I faced this past week: rotating an object in Flex. I was playing with the camera in Flex Mobile, and I quickly learned that it is landscape only even if the device is being held in portrait. Really, Adobe?

“…a Video object attached to the camera will only show upright video in a landscape-aspect orientation. Thus, mobile apps should use a landscape orientation when displaying video and should not auto-rotate.”
     – official Adobe docs for flash.media.Camera

Sad but true. So, if you wanted to build a video chat app and use the front camera you either always hold the iPhone in landscape or you hold it portrait and rotate the video in code. (Why did I say iPhone?…Alas, Flex Mobile does not provide front camera support for Android…period…none!)

Option 1: rotation Attribute

The first thing to try is to rotate the easy way, like this:

camera.rotation = 90;

Using actionscript, I just set the rotation to 90°. Of course, this rotates about (0,0), the upper left corner in Flex, and not what we need.

Option 2: rotation, Plus transformX and transformY

To rotate an object about its center, you can set transformX and transformY (and transformZ too), like this:

<s:Button id="btn" label="My Button"
    rotation="90"
    transformX="{btn.width * 0.5}"
    transformY="{btn2.height * 0.5}" />

Using MXML, I set rotation to 90°, plus transformX and transformY to half the width and height of the object (in this case, a button). By moving the rotation point from the top left to the center of the button, I get what I want: an object rotating about its center. [I had forgotten how this works...thanks go to Bindu for reminding me!]

Option 3: Matrix Rotation

Option #2 works because every component in Flex has a full affine transformation matrix behind it (as of Flash Player v9, I think, but only exposed to MXML in Flex 4). In MXML, when you set attributes like rotation, transformX, and transformY, you are just setting the underlying transformation matrix.

You can also set the matrix directly, like this:

var mat:Matrix = new Matrix();
mat.translate(-W/2, -H/2);
mat.rotate(Math.PI/2);
mat.translate(+W/2, +H/2);
mat.concat(myObj.transform.matrix);
myObj.transform.matrix = mat;

To get a rotation about the center of our object, we first translate it, then rotate, then translate it back. Note the concat() operation, which just “adds” the current matrix to our new rotation matrix, and thus preserves any previous transforms (maybe our object had scaleX or scaleY applied).

Option 4: Rotate Animation

Lastly, if we want to watch the rotation happen, we can use a Rotate animation, like this:

var rot:Rotate = new Rotate();
rot.angleBy = 90;
rot.duration = 1000;
rot.autoCenterTransform = true;
rot.target = myObj;
rot.play();

Here we just instantiate a new spark Rotate effect, set its various properties, and start it with a call to play(). The key property to force rotation about the center is autoCenterTransform, just set it to true.

Conclusion

When I need total control, I find myself doing the full matrix solution. Otherwise for anything simple, the MXML attributes are best.

Here’s an example project showing all options (view source enabled):

Flash is required. Get it here!
Files

 9.9

Did a little bit of vanilla Flex work recently, and I needed a Tree component to display an object hierarchy. Everyone, by now, hopefully knows that mx:DataGrid and mx:Tree are two of the crappiest, bug ridden, worst performing components from Flex 3. And, everyone by now, has left the buggy world of Flex 3 behind and entered the world Flex 4 and the vastly improved Spark components. With the arrival of Flex 4.5 this summer, Adobe finally gave us a rewritten Spark-based DataGrid. Alas, no updated Tree yet, so I had to write my own. So once again, I turned to the trusty combo of List plus custom ItemRenderer to make pure-Spark custom TreeList component that doesn’t suck.

Alex Harui is the guru of turning a Flex 4 Spark List into a look-alike for the old Flex 3 component using skins and a custom ItemRenderer. Alex has used List + ItemRenderer to make a DataGrid, DateField, ColorPicker, Menu + MenuBar, and even an XML-based TreeList. Unfortunately, Alex’s TreeList assumes incoming XML, and I needed a TreeList that could display a simple object hierarchy (root node with children, and those children have children, etc.). Since I couldn’t find exactly what I wanted, I decided to build it myself.

Flattener

The key step to getting a hierarchy of objects to display as a list is: flatten the list, duh! Or at least flatten the part of the tree you wish to display. So, I built a simple adapter class that turns an object hierarchy into an ArrayList that can be given directly to a List‘s dataProvider.

Here’s the actual flattener, but with just the comments not the code:

public class MyObjFlattenedList extends ArrayList {
 
    //the root object
    private var _root:MyObj;
 
    //list of open nodes
    private var _openList:Array;
 
    public function MyObjFlattenedList(root:MyObj) {
        _root = root;
        _openList = [];
        reset();
        ...
    }
 
    public function reset(openList:Array = null):void {
        //init the flattened list, starting at root
        _openList = (openList == null ? [] : openList);
        var a:Array = [];
        addItemToList(_root, a);
        this.source = a;
    }
 
    private function addItemToList(obj:MyObj, a:Array):void {
        //recursively walk obj and all of its "open" children to build
        //a flattened list that is returned in array a
    }
 
    public function isItemOpen(obj:MyObj):Boolean {
        //true if obj has children and is "open"
    }
 
    public function openItem(obj:MyObj):void {
        //add all of obj's children (if any) to the list
    }
 
    public function closeItem(obj:MyObj):void {
        //remove all of obj's children (if any) from the list
    }
    ...
}

There’s really not much to it. When instantiated with a root object, the object hierarchy is walked recursively to build a flattened list of all the open nodes. Once the initial list is built, openItem() can be called to open a node, and add all its children to the list. Alternately, closeItem() can be called to close a node, and remove all its children from the list.

Design

I used some basic styling and skinning, but the ItemRenderer does the majority of the work. Here’s the abbreviated version of MyObjRenderer.mxml with all the boring stuff edited out:

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark"
    width="100%"
    autoDrawBackground="false">
 
    <fx:Script>
        <![CDATA[
            [Bindable] private var _obj:MyObj;
            [Bindable] private var _hasChildren:Boolean = false;
 
            private var _list:MyObjFlattenedList;
 
            override public function set data(val:Object):void {
                super.data = val;
 
                if (val != null) {
                    _obj = val as MyObj;
 
                    var ownerList:List = owner as List;
                    _list = ownerList.dataProvider as MyObjFlattenedList;
 
                    btn.selected = _list.isItemOpen(_obj);
                    _hasChildren = (_obj.children != null && _obj.children.length > 0);
                }
            }
 
            private function toggleHandler():void {
                if (btn.selected) {
                    _list.openItem(_obj);
                } else {
                    _list.closeItem(_obj);
                }
            }
        ]]>
    </fx:Script>
 
    <s:states>
        <s:State name="normal" />
        <s:State name="hovered" />
        <s:State name="selected" />
    </s:states>
 
    <s:Rect ...>
 
    <s:HGroup ...>
        <s:ToggleButton id="btn" click="toggleHandler()" visible="{_hasChildren}" ... />
 
        <s:Group id="dot" visible="{!_hasChildren}" ... />
 
        <s:Label ... />
    </s:HGroup>
</s:ItemRenderer>

Each rendered item has a background Rect and a Label. But most importantly, each row has either a ToggleButton (if the object has children) or some non-interactive visuals (if the object doesn’t have children it just gets a dot). The toggle button is the key interactive element used to open or close the node, everything else is part of the visual gravy added to make the list look good.

Focusing on the functional stuff, if you look in the script block, you’ll see two functions: a data() setter and a toggleHandler() to handle toggle button clicks. As expected, clicking the toggle button calls openItem() or closeItem() on the flattener adapter which adds or removes children from the flattened list, respectively. The setter mostly sets up the local variables, but it notably computes if the object has children or not, and also sets the initial state of the toggle button.

Conclusion

With just a little effort, we can have a nice usable Spark TreeList component that looks decent. More importantly, we have total control and can make the TreeList look like anything our designer can throw at us. As is always the case, the combo of List + ItemRenderer proves to be awesome. I tried to cover all the interesting pieces, but for the details, you’ll need to check out the source code.

Here’s the finished product (view source enabled):

Flash is required. Get it here!

Use it and enjoy.

Files

 8.16

Sometimes I feel like I write more test code than real code. For unit tests on iOS our stack is OCHamcrest, OCMock, and GHUnit. For functional tests, there’s nothing better than FoneMonkey. For this post, I’m going to focus on OCHamcrest.

Hamcrest was born in the Java world as the matcher framework in jMock. It was quickly extracted into its own framework and has become somewhat of a monster in the testing world. It’s now included directly in JUnit (since v4.4), and has been ported to many languages (OCHamcrest in Objective-C, Hamcrest-AS3 in Actionscript, PyHamcrest in Python, etc.). Additionally, the matcher concept is generally useful, and Hamcrest is used is lots of different places (my favorite is collection filtering with Hamcrest in LambdaJ).

When writing unit tests, OCHamcrest offers lots of advantages over the vanilla SenTest assertions. First, there’s a ton of matchers that really make life easy, especially when testing collections like NSArray. Second, OCHamcrest matchers are very readable in code, almost self-documenting. Lastly, OCHamcrest automatically provides excellent failure messages when actual is not equal to expected.

Matching Strings

Some string matching examples:

  • is – match the complete string
  • startsWith – match the beginning of a string
  • endsWith – match the end of a string
  • containsString – match part of the string
  • equalTo – match the complete string
  • equalToIgnoringCase – match the complete string but ignore case
  • equalToIgnoringWhiteSpace – match the complete string but ignore extra whitespace (new line, tab, or double spaces)
NSString *s = @"FooBar";
 
assertThat(s, is(@"FooBar"));
 
assertThat(s, startsWith(@"Foo"));
assertThat(s, endsWith(@"Bar"));
assertThat(s, containsString(@"oo"));
assertThat(s, equalToIgnoringCase(@"foobar"));
 
assertThat(@" X \n  Y \t\t  Z \n", equalToIgnoringWhiteSpace(@"X Y Z"));

NOTE: Technically, is isn’t really a matcher, it’s a matcher decorator that implicity converts to the equalTo matcher. [thanks Jon!]

Combining Matchers

You can combine multiple matchers with:

  • allOf – AND together all matchers
  • anyOf – OR togehter all matches
NSString *s = @"FooBar";
 
assertThat(s, allOf(startsWith(@"Foo"), endsWith(@"Bar"), nil));
assertThat(s, anyOf(startsWith(@"Foo"), startsWith(@"Bar"), nil));
assertThat(s, anyOf(endsWith(@"Foo"), endsWith(@"Bar"), nil));

NOTE: The list of matchers must be nil terminated.

You can invert a matcher, or multiple matchers, with:

  • isNot – negate the matcher
NSString *s = @"FooBar";
 
assertThat(s, isNot(@"foo"));
assertThat(s, isNot(endsWith(@"Baz")));
assertThat(s, isNot(allOf(startsWith(@"Baz"), endsWith(@"Baz"), nil)));
assertThat(s, isNot(anyOf(startsWith(@"Baz"), startsWith(@"Baz"), nil)));

Matching nil

You can match nil with:

  • nilValue() – stands in for nil
  • notNilValue() – stands in for !nil
NSObject *o = nil;
assertThat(o, nilValue());
 
NSString *s = @"FooBar";
assertThat(s, notNilValue());

Matching Classes

You can match an instance’s class with:

  • instanceOf – match the class
NSString *s = @"FooBar";
assertThat(s, instanceOf([NSString class]));

Matching Numbers

One of the great pains of Objective-C is typing numbers from primitive types to objects and back again. OCHamcrest has a variety of matchers the help make life easy.

  • assertThatInt – typed assert that expects an int (other types too: assertThatFloat, assertThatDouble, etc.)
  • equalToInt – typed equals that takes an int (other types too: equalToFloat, equalToDouble, equalToBool, etc.)
  • closeTo – match a number with a target number plus or minus a delta (both params are double)
  • lessThan – match a number less than the given number (param is NSNumber), also lessThanOrEqualTo
  • greaterThan – match a number greater than the given number (param is NSNumber), also greaterThanOrEqualTo
assertThatInt(5, equalToInt(5));
assertThatFloat(3.14, equalToFloat(3.14f));
assertThatBool( false, equalToBool(NO) );
 
NSNumber *i = [NSNumber numberWithInt:5];
assertThat(i, equalToInt(5));
assertThat(i, is([NSNumber numberWithInt:5]));
 
NSNumber *f = [NSNumber numberWithFloat:3.14f];
assertThat(f, equalToFloat(3.14f));
assertThat(f, is([NSNumber numberWithFloat:3.14f]));

The easiest cleanest approach is to use assertThatInt with equalToInt, the next best option is to use the vanilla assertThat with equalToInt, the most verbose option is to use NSNumber everywhere.

It’s easy to make rough number comparisons too:

NSNumber *f = [NSNumber numberWithFloat:3.14f];
assertThat(f, closeTo(3.0f, 0.25f));
 
assertThat(f, lessThan([NSNumber numberWithInt:4]));
assertThat(f, greaterThan([NSNumber numberWithInt:3]));

NOTE: It is a little weird, but closeTo takes double params, but everything else expects NSNumber params.

Numeric comparisons also work great on dates too:

NSDate *now = [NSDate date];
 
//now minus 1000 seconds
NSDate *beforeNow = [NSDate dateWithTimeIntervalSinceNow:-1000]; 
 
assertThat(now, greaterThan(beforeNow));

Matching Arrays

Easily the best part of OCHamcrest is its ability to match lists of objects. Array matchers are every powerful, but don’t forget to add the terminating nil to all lists.

  • hasItem – match if given item appears in the list
  • hasItems – match if all given items appear in the list (in any order)
  • contains – match exactly the entire array
  • containsInAnyOrder – match entire array, but in any order
  • hasCountOf – match the size of the array
  • empty – match an empty array

Here some basic array examples:

NSArray *a = [NSArray array];
 
assertThat(a, is(empty()));
assertThat(a, hasCountOf(0));

Here some hasItem examples:

NSArray *a = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
 
assertThat(a, hasItem(@"a"));
assertThat(a, isNot(hasItem(@"X")));
assertThat(a, hasItem(equalToIgnoringCase(@"A")));

The last matcher may look a little weird, but remember matchers expect a matcher as their input param, and only default to equalTo if none is given. Thus, the first matcher hasItem(@"a") can be rewritten as hasItem(equalTo(@"a")).

We repeat the above example, but this time using numbers in our NSArray. As you can see below, all the number matchers require us to explicitly use equalToInt everywhere:

NSArray *a = [NSArray arrayWithObjects:
    [NSNumber numberWithInt:2],
    [NSNumber numberWithInt:3],
    [NSNumber numberWithInt:5],
    nil];
 
assertThat(a, hasItem(equalToInt(2)));
assertThat(a, isNot(hasItem(equalToInt(13))));
 
assertThat(a, contains(equalToInt(2), equalToInt(3), equalToInt(5), nil));

Here are some more complex array matchers:

NSArray *a = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
 
assertThat(a, hasItems(@"b", @"a", nil));
 
assertThat(a, contains(@"a", @"b", @"c", nil));
assertThat(a, containsInAnyOrder(@"c", @"b", @"a", nil));
 
assertThat([a componentsJoinedByString:@","], is(@"a,b,c"));

And as I show in the last matcher, you can always dump to a string, and just match strings.

Matching Dictionaries

The dictionary matchers build on the array matchers:

  • hasKey – match a key
  • hasValue – match a value
  • hasEntry – match a key-value pair
  • hasEntries – match a list of k-v pairs
NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:
    @"valA", @"keyA", @"valB", @"keyB", @"valC", @"keyC", nil];
 
assertThat(d, hasCountOf(3));
assertThat(d, isNot(empty()));
 
assertThat(d, hasKey(@"keyA"));
assertThat(d, isNot(hasKey(@"keyX")));
 
assertThat(d, hasValue(@"valA"));
 
assertThat(d, hasEntry(@"keyA", @"valA"));
 
assertThat(d, hasEntries(@"keyA", @"valA", @"keyC", @"valC", nil));

Matcher Error Messages

When a matcher fails, you get a standardize error message of: Expected "foo", but was "bar". This default message is easy to modify by using the describedAs() matcher in place of the typical is() matcher.

NSString *s = @"bar";
 
assertThat(s, is(@"foo"));
//Expected "foo", but was "bar"
 
assertThat(s, describedAs(@"doh! this should be 'foo'", equalTo(@"foo"), nil));
//Expected doh! this should be 'foo', but was "bar"
 
assertThat(s, describedAs(@"doh! this should be foo, %0, %1", equalTo(@"foo"), @"baz", [NSNumber numberWithInt:42], nil));
//Expected doh! this should be foo, "baz", <42>, but was "bar"

NOTE: The argument list for describedAs() MUST end with nil or your tests will crash instantly with no useful error message.

Building a Custom Matcher

Writing your own custom matchers is relatively easy. Here’s an example of a matcher that matches the value of some property on an object:

#import <OCHamcrestIOS/OCHamcrestIOS.h>
#import <objc/objc-api.h>
 
@interface HasProperty : HCBaseMatcher {
    NSString *property;
    id<HCMatcher> valueMatcher;
}
 
+ (id) hasProperty:(NSString *)aProperty value:(id<HCMatcher>)aValueMatcher;
- (id) initWithProperty:(NSString *)aProperty value:(id<HCMatcher>)aValueMatcher;
 
@end
 
OBJC_EXPORT id<HCMatcher> hasProperty(NSString *property, id valueMatcher);

We extends HCBaseMatcher with our custom HasProperty class. We store the name of the property and a value matcher.

And the implementation:

#import "HasProperty.h"
#import <OCHamcrestIOS/HCDescription.h>
#import <OCHamcrestIOS/HCWrapInMatcher.h>
 
@implementation HasProperty
 
+ (id) hasProperty:(NSString *)aProperty value:(id<HCMatcher>)aValueMatcher {
    return [[[self alloc] initWithProperty:aProperty value:aValueMatcher] autorelease];
}
 
- (id) initWithProperty:(NSString *)aProperty value:(id<HCMatcher>)aValueMatcher {
    self = [super init];
    if (self != nil) {
        property = [aProperty copy];
        valueMatcher = [aValueMatcher retain];
    }
    return self;
}
 
- (void) dealloc {
    [property release];
    [valueMatcher release];
    [super dealloc];
}
 
- (BOOL)matches:(id)item {
    SEL propertyGetter = NSSelectorFromString(property);
 
    if ([item respondsToSelector:propertyGetter]) {
        if ([valueMatcher matches:[item performSelector:propertyGetter]])
            return YES;
    }
    return NO;
}
 
- (void) describeTo:(id<HCDescription>)description {
    [[[description appendText:[NSString stringWithFormat:@"an object with a property named '%@' with a value of {", property]]
        appendDescriptionOf:valueMatcher]
        appendText:@"}"];
}
@end
 
OBJC_EXPORT id<HCMatcher> hasProperty(NSString *property, id valueMatcher) {
    return [HasProperty hasProperty:property value:HCWrapInMatcher(valueMatcher)];
}

When we write a custom matcher, we must implement two methods, matches: to do the matching and describeTo: to provide feedback in case of match failure. In the above code, we first construct a selector from the given property name, then call the selector to get the actual property value, and finally check if actual matches the expected value (given by the valueMatcher).

Usage looks like this:

Person *p = [Person personWithFirstName:@"Joe" andLastname:@"Doe"];
 
assertThat(p, hasProperty(@"firstName", @"Joe"));

Or more importantly, we can now use our custom hasProperty matcher to match an arrays of objects:

NSArray *a = [NSArray arrayWithObjects:
    [Person personWithFirstName:@"Joe" andLastname:@"Doe"],
    [Person personWithFirstName:@"Joe" andLastname:@"Smith"],
    [Person personWithFirstName:@"Jane" andLastname:@"Allen"],
    nil];
 
assertThat(a, contains(
    hasProperty(@"firstName", @"Joe"),
    hasProperty(@"firstName", @"Joe"),
    hasProperty(@"firstName", @"Jane"),
    nil));

That’s it. Go forth and match.

UPDATE: I put the above hasProperty matcher into a pull request, and Jon Reid accepted it into OCHamcrest v1.6. He even wrote a nice post about it. Get the lastest OCHamcrest from github.

Links


© 2014 saturnboy.com