Video streaming from an IP camera to an Android phone

I recently picked up an IP camera to play around with and build an app that displays the camera’s live video stream. I chose the D-Link DCS-942L camera because it supports streaming video to a UDP socket by leveraging the RTSP and RTP protocols, and the consumer reviews indicated that it was easy to set up and use. The camera supports two video encodings, H.264 and MPEG4, both of which are supported by iOS (to a certain extent; more on that later).

I wanted to build an iOS app that worked with the IP camera, but ended up building an Android app instead. I’ll explain why later. There already are iOS and Android apps available for viewing video from the DCS-942L but I thought it would be fun to build my own and add a few custom features.

Background

In case you are not familiar with the basics of video streaming, here’s a brief overview of the terms I mentioned above. When I started this pet project I didn’t know much about these things, but that’s what pet projects are good for!

UDPUser Datagram Protocol is a transport protocol that can deliver “datagrams” (data with some networking metadata attached to it) from one computer to another. The data in a datagram might be just a small chunk of the full data object being transferred between two computers, so each datagram has a sequence number that aids in properly reconstructing the original data object. Unlike TCP, the UDP does not require a connection to be established and kept alive between two computers. One computer informs  another computer of its IP address and the port on which it is expecting to receive datagrams, and as long as a socket is listening on that port the datagrams can keep flowing in. Another difference between UDP and TCP is that the former does not make any guarantees that datagrams will arrive in the correct order (or arrive at all). It is the application developer’s job to deal with reassembling the data and accommodating any missing datagrams. UDP is commonly used for applications that do not benefit from the strong delivery guarantees of TCP, such as video streaming where dropping an occasional video frame or two is not a noticeable problem.

RTSPReal-Time Streaming Protocol is like HTTP but it is built specifically for setting up, configuring, and tearing down streaming media sessions, such as a video chat session or a movie being streamed to a computer from streaming media server. RTSP is not involved with the actual video streaming, but just managing the streaming session. It has a set of directives like DESCRIBE, which asks the streaming media source to list the available media types and their capabilities, and SETUP, which is how the client app tells the server which media type it wants to consume and gets a session ID back in the response.

RTPReal-time Transport Protocol is the low-level protocol used to break a video into packets that can be streamed between computers. Those packets of video data are often put into UDP datagrams. To get a feel for the kind of processing involved with RTP video streams, check out this high-level overview of the process on StackOverflow.

H.264 and MJPEG – These are commonly used compression techniques for audio/video files. Most normal humans do not ever need to know or care about how such things work or can be decoded, since companies like Apple build support for them into their products. iOS uses hardware acceleration to decode H.264 video (by processing it on the GPU instead of the CPU). I’m not sure if they hardware accelerate MPEG4 but I assume they do. For a taste of what’s involved with decoding H.264 formatted for RTP, check out RFC 6184.

There are myriad other protocols and formats and terribly geeky lingo involved with the strange world of streaming media, but the ones above are supported by my D-Link DCS942L IP camera and are thus the ones that I care about in this project.

So close, yet so far…

It was a lot of fun writing an  RTSP protocol processor in C++ and getting it to communicate with my IP camera. My code was able to dance the RTSP dance over a TCP connection and initiate a video streaming session on a port with a UDP socket ready to get busy. All I needed to do next was find a way to make iOS decode the H.264 or MPEG4 video stream and show it. Should be easy, right?

Not right. Not even close to right. I should not have left that out of my technical research before deciding to drop $100 on an IP camera that I don’t actually need. The only form of video streaming that iOS supports is some oddball called HTTP Live Streaming invented by, you guessed it, Apple. I searched the InterWebs high and low for a way to leverage the existing support in iOS for showing H.264 or MP4 videos, which works great if the videos are saved as a file in local storage, but apparently Apple locked that down pretty tight to make sure you can’t do what I wanted to do. Namely, display those videos from a stream sent to a UDP socket, using industry-standard protocols.

I should point out that there is a way to implement this in iOS by using an open-source library called FFmpeg. After reading several rants about what a headache it is to set that library up and use it, I asked a buddy of mine about it who has experience using FFmpeg. He confirmed that it’s a massive pain to use, so I dropped that plan.

Android to the rescue (?!)

I’ve never been a big Android fan, truth be told. However, I do own a relatively new Nexus 4 phone that I use when learning about Android programming to test out my code on a real device, since the Android emulator is just awful. It turns out that as of API level 12, Android has had support for showing a video stream sent via RTP. It even handles all of the RTSP communication for you! Wow, take that Apple.

After about half an hour of Web searching and coding I put together an Android app (running on Android 4.3) that shows the video stream from my IP camera. That was easy.

Normally this blog is about iOS development, but since Apple really let me down this time, my blog will have a cameo appearance by that green Android robot.

Your project needs to support at least API level 12, since that is when Google added support for RTP video streaming, but my project’s minimum SDK version is 18. To allow your app to use the video streaming feature put these in the AndroidManifest.xml file:

<!-- Required permissions for RTP video streaming. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Next we need to create the UI element that will display video being streamed from an IP camera. Open your activity’s layout file and add a SurfaceView element, like so:

<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <SurfaceView 
    android:id="@+id/surfaceView"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" />
</RelativeLayout>

The rest of the implementation is in the activity subclass that displays the layout created above. Creating the activity’s UI and configuring it to be full-screen is shown below. If you use this code, be sure to update the RTSP_URL string to your camera’s local IP address.

public 
class      MainActivity 
extends    Activity 
implements MediaPlayer.OnPreparedListener, 
           SurfaceHolder.Callback {
  final static String USERNAME = "admin";
  final static String PASSWORD = "camera";
  final static String RTSP_URL = "rtsp://10.0.1.7:554/play1.sdp";

  private MediaPlayer _mediaPlayer;
  private SurfaceHolder _surfaceHolder;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Set up a full-screen black window.
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    Window window = getWindow();
    window.setFlags(
      WindowManager.LayoutParams.FLAG_FULLSCREEN, 
      WindowManager.LayoutParams.FLAG_FULLSCREEN);
    window.setBackgroundDrawableResource(android.R.color.black);

    setContentView(R.layout.activity_main);

    // Configure the view that renders live video.
    SurfaceView surfaceView = 
      (SurfaceView) findViewById(R.id.surfaceView);
    _surfaceHolder = surfaceView.getHolder();
    _surfaceHolder.addCallback(this);
    _surfaceHolder.setFixedSize(320, 240);
  }

  // More to come...
}

Note that the SurfaceHolder object is given the activity as a callback. This enables the activity to know when the rendering surface is ready for use. When it is ready we begin setting up the MediaPlayer object that handles the RTSP communication and RTP video streaming work. When the SurfaceView is destroyed, perhaps due to the device orientation being changed or the app terminating, the media player is disposed of via the release method.

/* 
SurfaceHolder.Callback 
*/

@Override
public void surfaceChanged(
  SurfaceHolder sh, int f, int w, int h) {}

@Override
public void surfaceCreated(SurfaceHolder sh) {
  _mediaPlayer = new MediaPlayer();
  _mediaPlayer.setDisplay(_surfaceHolder);

  Context context = getApplicationContext();
  Map<String, String> headers = getRtspHeaders();
  Uri source = Uri.parse(RTSP_URL);

  try {
    // Specify the IP camera's URL and auth headers.
    _mediaPlayer.setDataSource(context, source, headers);

    // Begin the process of setting up a video stream.
    _mediaPlayer.setOnPreparedListener(this);
    _mediaPlayer.prepareAsync();
  } 
  catch (Exception e) {}
}

@Override
public void surfaceDestroyed(SurfaceHolder sh) {
  _mediaPlayer.release();
}

The IP camera that I own requires all RTSP requests to contain an ‘Authorization’ header with a Basic auth value, which is a very insecure way of passing my ever-so-confidential username and password for the camera (which I typed in when using the camera’s setup wizard software). The code above uses a helper method to get the headers needed to communicate with the RTSP server, in this case just an Authorization header. Here’s how I implemented that.

private Map<String, String> getRtspHeaders() {
  Map<String, String> headers = new HashMap<String, String>();
  String basicAuthValue = getBasicAuthValue(USERNAME, PASSWORD);
  headers.put("Authorization", basicAuthValue);
  return headers;
}

private String getBasicAuthValue(String usr, String pwd) {
  String credentials = usr + ":" + pwd;
  int flags = Base64.URL_SAFE | Base64.NO_WRAP;
  byte[] bytes = credentials.getBytes();
  return "Basic " + Base64.encodeToString(bytes, flags);
}

Last but not least we have the “on prepared” listener implementation for the MediaPlayer object. This is invoked some time after the call to prepareAsync seen previously. All that it needs to do is start the media playback.

/*
MediaPlayer.OnPreparedListener
*/
@Override
public void onPrepared(MediaPlayer mp) {
  _mediaPlayer.start();
}

A few seconds after that code executes I begin to see the video stream displayed on my Android phone. Very cool!

Recursive Selfies

Here is a shot of my IP camera and my Android phone, which is showing the video being streamed from the camera.

camera1

When several cameras are being used at the same time, you can start getting into some weird recursive situations, such as this picture of me taking a picture of myself taking a picture…

camera2

If you really want to get freaky with it, view the camera’s internal Web site to have a large screen full of streaming video and then hold the Android phone up next to it. This starts to get pretty confusing after a while!

camera3

Well, I hope you learned a thing or two out of all this. I know that I did. Happy programming!

Posted in Android, Streaming Media | 10 Comments

Base64 encoding in iOS 7

Over the years Apple has slowly but surely filled in some pretty conspicuous gaps in their application platform for iOS developers. They didn’t even support working with JSON data until iOS 5 introduced NSJSONSerialization!  Another small, but important, gap that was filled in by iOS 7 is proper support for encoding and decoding data in Base64; such as base64EncodedStringWithOptions: and initWithBase64EncodedString:options:.

NSHipster has a good, quick overview of what Base64 is all about with an example here.

StackOverflow shows how to both encode and decode in Base64 here.

Learn more about Base64 encoding on the Wikipedia page here.

Thanks Apple! It’s about time…

Posted in iOS7 | 3 Comments

A custom Terminal command to show/hide hidden files in the Mavericks Finder

In my previous blog post I showed the Terminal commands that allow you to cause Finder to display hidden files and directories.

finder-show-all-files

By default Finder does not show a file or directory if its name begins with a period, such as the .gitignore file commonly edited by Git users. In this post I show how to simplify this task by adding a custom command that Terminal will recognize and execute, so that you don’t need to copy and paste the command every time you need to use it (or, dare I say, memorize it!).

Please note, the Terminal command seen in this post works starting in OS X Mavericks. Previous versions of OS X used a slightly different command to show/hide hidden items (Google it if you care).

First, fire up Terminal. If this is your first time using the Mac’s command line, it is available via Applications > Utilities > Terminal.  Once the command line window is up and running, open up one of the configuration files used by Terminal (which is actually a Bash command line interface inherited from Unix) by typing the bold text here:

user$ open ~/.bashrc

After pressing the Return key you should see a TextEdit window showing the contents of that file. If the file does not exist for some reason, you can create it with this command touch ~/.bashrc and then open it.

Now we will add the code needed to introduce a custom command in Terminal, named showallfiles. Copy and paste this code into the .bashrc file and then save it and close the editor window.

showallfiles() {
 defaults write com.apple.finder AppleShowAllFiles -boolean $1
 killall Finder
}

In order to load this new command into Terminal, either quit the Terminal session and reopen it, or type this command that causes the edited file to be reprocessed by the active Terminal session:

user$ source ~/.bashrc

Now you can easily toggle between showing all hidden files and directories…

user$ showallfiles true

…and hiding them when you no longer want to see them in Finder…

user$ showallfiles false

Happy programming!

Posted in Tips and Tricks | 1 Comment

View hidden files and directories with Finder in OS X Mavericks

With the release of OS X Mavericks the old Terminal command that causes Finder to show hidden files and directories no longer works. If you want to see hidden files, such as a .gitignore file in your repository, open a Terminal window and run this command:

defaults write com.apple.finder AppleShowAllFiles -boolean true

Then refresh all Finder windows so that they honor the new configuration setting:

killall Finder

To revert back to hiding these files and directories, run the first command but change true to false.

Check out my next blog post to see how this task can be automated by writing a custom Terminal command!

Posted in Tips and Tricks | 3 Comments

The reliability of Reachability

Mike Ash’s blog post about Apple’s networking API, called Reachability, has spawned a very interesting and eye-opening discussion in its comments. The post can be found here Friday Q&A 2013-06-14: Reachability

The two main points I took away from this are:

  1. Make an initial network request attempt before asking Reachability if the network is available, in case, for example, the device’s radio transmitter is off due to the device being in a low-power state and needs to be turned on to make a network request.
  2. If you rely on Reachability to inform your code when the network becomes available again, also provide the user with a means of manually attempting to connect to the server (in case Reachability cannot detect when a problem is fixed, such as the server being temporarily offline).

I typically rely on the Reachability wrapper API included in the AFNetworking library to manage an app’s connections to the outside world. Perhaps I have been relying on it a bit too much…

Posted in Networking | Leave a comment

iOS questions from a WPF developer

While vacationing in Hawaii recently I was contacted by a developer named Peter who had some iOS programming questions for me. Peter’s background is in WPF, just like me, and he is now moving into iOS programming (welcome to the club!). I replied letting him know that I would answer his questions once I returned to the mainland, what Hawaiians call the rest of the U.S.

Peter’s questions resonated with me. They were things that I, too, was unsure about when I first got into iOS programming, years ago. I assume that these questions must arise for many WPF developers getting into iOS, so I am sharing my reply to Peter here, for the benefit of others.

Hey Peter,

Our trip to Hawaii was great. The sunshine must have fried a few neurons, I forgot about your email. Thanks for the reminder. :)

I’ll start off by saying that my book iOS Programming for .NET Developers addresses what you’re asking about (and a whole lot more), so I’ll summarize here. You can read the book for more detail and background.

“The first thing I’m having trouble with is understanding how Apple wants us to style the controls. I’ve been doing some searching online and so far what I’ve found is that a lot of effects (table view cell dropshadow, gradients) are done through static images as the UIView’s background. This feels very wrong to me, you know after being used to having the “luxuries” of XAML borders and such. I found another way which is manipulating the Layers in code; this still feels kinda wrong. So, in short what is the recommended way of styling your UI? How do you do it? Do you use the designer?”

Unlike XAML technologies, which rely heavily on vector-based UI elements to replace a control’s UI, iOS views are quite often styled/customized with images. Styling can be done in code by directly setting properties on a UIView, or via appearance proxies, which is conceptually similar to the WPF styling system. That approach is somewhat limited in terms of what can be adjusted and how drastically. For example, you can use an appearance proxy to set a tintColor property on all UIButtons, but that just introduces a custom color into Apple’s color computation code. If you want to radically change the visual style of a UIButton, an image is the way to go.

I also felt the use of images was “wrong” at first, after so many years working with custom data/control templates, but I quickly got used to it. I don’t see the reliance on images to be a shortcoming, it’s actually a lot easier, assuming someone on your team is capable of producing attractive images.

I do most of an app’s styling in code, since app-specific styles use shared colors and resizable images defined in code. For one-off images in some UIView there is no reason not to apply them using Interface Builder, unless you prefer to keep all styling in code.

“The second issue I’m having trouble wrapping my head around is yet again with UI design. Basically as far as I understand UIView is the general container (I guess the closest thing to a border?). So, does this mean a page will be littered with a whole bunch of UIViews? For example if my page had 2 sections I would have 1 UIView for each section and a third in the middle to have some sort of divider?”

UIView is more akin to WPF’s UIElement than Border. UIView is the base class for all UIKit objects shown on an iPhone or iPad screen. It does not have a default appearance, but it does support having children (known as subviews) whose frames are relative to the top-left corner of the parent UIView. This is very similar to the visual tree concept in WPF. I wouldn’t say a screen/page is “littered” with UIViews. That’s like saying that a forest is littered with trees. Read Apple’s View Programming Guide for iOS to get a quick introduction.

“Lastly, layouts! Coming from WPF I’m used to percentages (Grid); things resizing based on the parent container’s size etc etc. I kind of cringe having to set fixed sizes, am I doing it wrong?”

Layout was based on frames (a.k.a. rectangles) and auto-resizing (a.k.a. springs and struts) until iOS 6 came out. Now we have AutoLayout, which I wrote an introduction about here. AutoLayout uses a system of constraints to define the layout logic for a set of UIViews, and reduces/eliminates the need for hard-coded frames. The downside of AutoLayout is that it can get pretty complicated for non-trivial views, and throws errors if the set of constraints you specify is incomplete or invalid.

Personally, I still use auto-resizing with frames, mostly because the projects I’m working on need to support iOS 5.1 and greater, so AutoLayout is not an option. I find working with AutoLayout in Interface Builder to be something like building a house of cards; it’s too easy to accidentally mess it up with a flick of the wrist. Some people think AutoLayout is great, though, so maybe they know something I don’t.

I hope this has been helpful for you. It takes a while, but eventually the iOS way of doing things feels just as natural as the WPF way.

Posted in iOS Programming for .NET Developers, Tips and Tricks | 5 Comments

Objective-C workflow for iOS apps

This article reviews a simple, lightweight Objective-C workflow component for iOS applications. The source code and demo app can be downloaded from GitHub and freely used under the permissive MIT license.

Repository: https://github.com/ijoshsmith/iOS-Workflow

Workflow as a conceptual tool

Workflow diagrams are often used to investigate and document complex problems. They consist of boxes connected by arrows. A box can represent various things, depending on its shape, but for our purposes here it represents a decision or a work item.

Decision boxes have two or more arrows pointing away from them, each arrow representing a distinct result for whatever decision was made. A work item box, on the other hand, has no more than one arrow pointing away from it. That arrow shows what happens after the task is completed. If the box does not have an arrow pointing at another box, it is a final node in the workflow. A workflow can have more than one final node, if necessary.

Daunting problems can be decomposed, rationalized, discussed, and solved using this modest set of tools. However, solving a problem in the abstract is not the same as implementing that solution in code. Converting the solution depicted by a workflow diagram into robust code that can deal with the realities of software, such as asynchronous Web service calls that might fail, is no small job.

Workflow as a design pattern

I have learned a simple truth through experience. If the solution to a problem is complicated enough that you need a workflow diagram to understand it, you should structure that solution in code as a workflow, if possible. The conceptual tools used to create and understand the solution to a difficult problem can, and should, be leveraged when implementing and troubleshooting that solution in source code.

The biggest benefit of structuring your solution as a workflow is that the plumbing needed to coordinate disparate, asynchronous decisions and work items does not interfere with the code that solves a complex problem. Domain logic is complicated enough by itself! Another major advantage of having direct correspondence between a workflow diagram and class files is that the diagram turns into a map of your code base, which can be very helpful (especially if you add the diagram file to your Xcode project).

After using this approach in two large iOS projects, both of which worked out very well, I decided to create a component that can be reused in any iOS app to model code as a workflow consisting of decisions and work items.

Introducing JASWorkflow

I set out to create a lightweight, yet useful, workflow implementation. Nothing too fancy, just the bare essentials for what I believe is needed by most apps that benefit from using a workflow to solve a complicated problem. Here is a list of features in JASWorkflow:

  • A workflow node can represent a binary decision (yes/no) or a work item.
  • When executed, a node can immediately return its final result, or indicate that its result is “pending” and report the real result later. This is useful for async processing, such as calling a Web service.
  • You can provide the workflow with a completion block that will be invoked when it finishes executing its nodes.
  • You can explain to a workflow how to translate the result of a final node into a semantically relevant outcome value, which is passed to your completion block.
  • Your completion block and every executed node runs on the same thread from which your code started executing the workflow.
  • A workflow object is self-preserving. It prevents itself from being deallocated while executing, so that your code does not need to hold a strong reference to the workflow object until it completes.
  • An executing workflow supports cancellation.
  • A completed workflow supports reuse (e.g. reset and executed again).
  • Nodes can share data with each other via the workflow’s userInfo property.

The rest of this article shows how to put JASWorkflow to use.

Workflow example

For the sake of clarity I present a simplistic workflow example. In reality, the trivial problem solved here would most likely not beckon to be implemented as a workflow, but that is irrelevant for our purposes. It merely provides context for a discussion about implementing a workflow. Without further ado, I give you the Health Workflow:

The workflow diagram above, created with the Grafio app on my iPad, shows the steps needed to determine if a person is healthy. First the test subject is asked if he is willing to exercise. If the answer is No, he is asked if he is injured. An uninjured person who is unwilling to exercise is deemed unhealthy. An injured person’s health status cannot be determined because an injury is a valid reason for not exercising. If the test subject says he is willing to exercise, and then proves it by lifting weights and doing sit-ups, he is considered healthy. I’m not a doctor, but I play one on the Internet!

The demo iPhone app, which is part of the source code download available on GitHub, has a button and label for each possible outcome.

The goal is to execute a Health Workflow when the user taps a button. The workflow must simulate predefined decisions made by the test subject, and then display the workflow’s outcome value next to the button. This allows us to verify that our code is functioning properly for every route through the workflow.

Programmatic workflow representation

Our goal it so convert the workflow diagram seen at the top of the previous section into Objective-C classes, enums, and so forth. Let’s begin that process by creating a diagram equivalent to the one above, but this time each box is labelled with a programmatic identifier.

Three rounded boxes at the bottom represent the possible outcomes of a completed workflow, labelled with enum value names. All other boxes are labelled with node class names. Next let’s turn our attention to the outcome values.

Defining outcomes

All three outcomes for the Health Workflow are listed in the DEMOOutcome enum. I also included a helper function that is used when displaying an outcome to the user.

A workflow’s outcome values do not need to be expressed as an enum. Strings, or any other kind of object, would be fine. Later on I show how these values are associated with nodes, so that a workflow can translate a final node’s result to some meaningful outcome value.

Implementing nodes

The real brains of a workflow is in its nodes. Each discrete decision and item of work should be encapsulated in a subclass of JASDecision or JASWorkItem. Both of those classes are direct subclasses of JASWorkflowNode, as shown in the abridged class declarations below:

Note that JASWorkflowNode does not have any awareness of other nodes. Its two subclasses introduce properties that enable nodes to be linked together. The names of those properties are closely tied to values in the JASWorkflowNodeResult enum seen previously. For example, JASDecision’s yes property references the next node to execute if the decision node’s result is JASWorkflowNodeYes.

Below is the node that represents the decision of whether or not the test subject is willing to exercise. The mockResult property is needed for testing purposes, since this node does not actually know how to do anything aside from return a mocked result value.

The implementation of this decision node is seen below.

Keep in mind, in a real app each node would add value to a workflow, not simply return an externally determined result. I won’t bother showing the DEMODecisionIsInjured class, since it is very similar to the code seen above. Instead, let’s now see how a work item node is declared:

This node does not immediately produce its final value. The node returns ‘Pending’ from the execute method, and two seconds later reports back to the workflow a result of ‘Complete’. During those two seconds the workflow sleeps, allowing the thread on which it runs to do other work. This delayed completion emulates a node that must wait for a Web service to respond, or any other asynchronous event needed by iOS apps.

The DEMOWorkItemLiftWeights class is very similar to this, so I won’t bore you with it.

Executing a workflow

Now that we have defined outcomes and nodes it is time to compose them into a workflow. Before diving into the code that creates and executes a workflow, first let’s review an abridged declaration of the JASWorkflow class. The complete class declaration is full of informative, yet verbose, API documentation.

In a previous section I showed the user interface for the demo app, which displays a button and label for each of the three possible Health Workflow outcomes. Here is the action method for a button that tests which outcome is produced if the test subject will not exercise because he is injured (which, I remind you, should be ‘Inconclusive’):

That method calls a helper method responsible for creating a workflow with predefined decision values, executing the workflow, and updating a label with the result.

The first thing the above method does is call into yet another helper method to create a JASWorkflow and return, via an out parameter, the first node to execute. We will visit that method shortly, but for now turn your focus to the use of the startExecutingWithNode:completion: method.

The first point of interest is that a workflow can start executing at any node, there is no arbitrary enforcement of a “first” node. This increases the flexibility of a workflow, since there may be several viable entry points, determined at run-time.

Although the code shown above does not make it explicit, the startExecutingWithNode:completion: method is a non-blocking call. In other words, the flow of execution does not wait until the workflow completes before moving past a call to that method. When the workflow eventually finishes executing nodes, it will invoke your completion handler block.

The completion handler block accepts three parameters. The first is named outcome and points to a semantically relevant value based on the result of the final node to execute, or nil if no such value exists. In the next method I explain more about this very useful and powerful feature.

The second parameter to the completion handler block is named finalNode and it points to the last node to execute before the workflow completed. If the workflow encountered an error, this node’s error property will point to an NSError object.

The last parameter is named cancelled and it will be YES if the workflow was cancelled via the cancel method. If the workflow was cancelled, the outcome and finalNode arguments will be nil.

Next up is the method that creates and configures a workflow and its nodes. This is where the magic happens.

I annotated each part of that method with a letter to assist with explaining how it works.

Part A is equivalent to drawing boxes in a workflow diagram. This is where the four workflow nodes are created. Note that a node can be passed data via its initializer, such as the initWithCount: method for the sit-ups node, or via properties, accessor methods, etc. Alternatively, nodes can access and share data by using the workflow’s userInfo property, which exposes a mutable dictionary.

Part B uses Key-Value Coding to set up mock return values for both decision nodes. I use KVC instead of a direct property assignment because those variables are typed as JASDecision pointers, and that class does not declare the mockResult property (both of its subclasses declare that property).

Part C is equivalent to drawing arrows between boxes in a workflow diagram. This is where the order in which nodes execute is determined.

Part D creates an instance of JASWorkflow that manages four nodes. The workflow object owns (i.e. retains) the nodes passed into its initializer, and only those nodes. All other pointers to nodes are weak, meaning that a node not passed into the workflow’s initializer will be immediately deallocated unless your code explicitly keeps a strong reference to it. All nodes used in a workflow should be passed to its initializer.

Part E shows how to map the result of a final node to an outcome value. There are three uses of the ifFinalNode:producesResult:evaluateTo: method, one for each value of the DEMOOutcome enum. This is how the workflow knows what value to pass for the outcome parameter of your completion handler block. When a node produces a result, such as JASWorkflowNodeResultNo, for which it does not have a node to execute next, the workflow is complete. At that time, the workflow tries to translate the [finalNode, result] combination into an outcome value. It can only produce an outcome value if your code supplied one via the ifFinalNode:producesResult:evaluateTo: method.

Part F points the rootNode out parameter at the first node to execute, which in this demo is the node that asks if the test subject is willing to exercise.

Summary

I have had nothing but success when applying this approach to implementing complex, asynchronous aspects of production iOS apps. If you are the kind of person who finds workflow diagrams useful, like I do, be sure to consider giving it try next time you’re faced with a gnarly problem whose solution can be clearly expressed with boxes and arrows.

All of the code reviewed here, and more, is available on GitHub.

Happy coding!

Posted in iOS 6, Objective-C | 2 Comments