Adoption Curve Dot Net

A Better Way of Managing Xcode Scripts

| Comments

I can’t remember where, but I recently came across a neat way of managing Xcode scripts. Usually, you’d add a script into a Run Script build phase for your target. That’s fine, but it does mean that the script is ‘buried’ inside the Xcode project. The effect of this is to hide any changes away inside the mess of XML that is a .xcproject or .xcworkspace file.

The alternative is to create the script as a separate external file in your project folder; and link to this from the Run Script build phase. The process goes like this:

  • Create your script file in the root of your project’s directory structure, and give it a .sh file type
  • Chmod it so that it’s executable with chmod +x myscript.sh
  • In Xcode’s Build Phases section, add a new Run script build phase
  • Add your new script to the Run script phase with ./myscript.sh

Then when you build the project, Xcode will execute the contents of your myscript.sh file.

You can change the name of the Run Script build phase by double-clicking it. This is an example of how I include the script that automagically bumps the version and build number based on the git tag and commit numbers:

The advantage of this is that myscript.sh will be included under source control as a discrete file - so any changes that are made (or more importantly, any merge conflicts) will be much easier to identify than they would be if they were wrapped up inside Xcode’s XML.

How a Navigation-based App Fits Together

| Comments

Something I found less-than-obvious when starting out with iOS development was how the different types of ap generated by the project templates available in Xcode actually worked. Depending on which you choose what happens in appDelegate varies in subtly-different ways.

What follows is the first of a series of posts that summarise the main app types, and how these start up. It starts with a basic single-view app, and steps through the process of converting this into an app which is navigation-based.

What AppDelegate does

The App Delegate class does what it says on the tin - it’s the delegate for the app that is run by main.m. When the app is up and running, the app delegate receives the application:didFinishLaunchingWithOptions: call. This is the point where the app’s main window is created and configured.

The single-view app

The “single view” is the simplest kind of app that it’s possible to create (I’m deliberately ignoring the “empty app” template, which I’ll come back in a later post). It has a single view controller which creates and manages a single view.

The application:didFinishLaunchingWithOptions: method looks like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = self.viewController;
    [self.window makeKeyAndVisible];
    return YES;
}

Stepping through this, the first line grabs a reference to the window into which the view is going to be displayed. It’s an instance of UIWindow created with the bounds of the main screen - normally this is the screen of the device, but there are situations where you can have more than one screen (connected to an external display, for example).

Then an instance of a UIViewController is created and assigned to the viewController property, using the ViewController xib file.

This UIViewController instance is assigned to the rootViewController property of the app’s window, and the makeKeyAndVisible method brings this window to the front and starts intercepting user interactions.

Finally, the method returns YES, and the rootViewController takes over.

This is the basic pattern for all iOS apps - starting a different style, a navigation-based app for example, is just a case of changing this method so that the right kind of class becomes the rootViewController.

Creating a Paged Photo Gallery With a UICollectionView

| Comments

The “traditional” way of creating a swipeable gallery of images uses a UIScrollView and a series of views containing images which are placed in a line inside the scrollView. It’s a technique that works - but it’s fiddly to set up, and it doesn’t scale well. With more than a very few images in the gallery, you need to start implementing lazy loading to prevent the gallery gobbling up memory.

If you think of a gallery as being a UITableView laid on its side, then it’s pretty obvious that we could use something similar and take advantage of the lazy loading of cells that a table view offers. Prior to iOS6, sideways-scrolling tableViews were tricky to set up - but iOS6 came to the rescue with UICollectionView.

This is a worked example of setting up a full-screen paged image gallery using UICollectionView that lazily-loads images, and supports dynamic resizing of images during device rotation.

Setting up the project

Create a Single View Application project in Xcode. You should end up with an AppDelegate and a single view controller and xib file.

Wiring up the collection view

In the main view controller’s .h file, add the UICollectionViewDelegate and UICollectionViewDatasource protocols:

@interface CMFViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate>

Then switch to the .m file and create two new properties:

@interface CMFViewController ()
@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@property (nonatomic, strong) NSArray *dataArray;
@end

In the main view controller’s ‘.xib’, drag and drop a UICollectionView object into the view, and then connect it to the collectionView property. You’ll also need to connect the UICollectionView object’s dataSource and delegate outlets to the File's Owner object.

Creating the data

For the purposes of this demo, I’ve assumed that there’s an Assets directory in the project’s folder structure, and that contains a series of images.

The first step is to load the names of the files in this directory into the dataArray property - I’ve done this with a seperate method:

-(void)loadImages {
    NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Assets"];
    self.dataArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:sourcePath error:NULL];
}

This method gets called in the view controller’s viewDidLoad method:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadImages];
}

Feeding the collection view with data

There are three methods that need to be implemented to feed the collection view with data:

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

Taking these in turn, the first is to tell the collection view how many sections it has. This being a simple example, there’s only one:

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

Next, the collection view needs to know how many items it’s going to be displaying in total - which is the number of images that live in the Assets directory:

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [self.dataArray count];
}

The last third method is where the real work takes place, and the collection view cells are created. Before we can do this, though, we need to go ahead and create the cells.

Creating the UICollectionViewCells

There’s a couple of ways of creating UICollectionViewCells - the nib-based route, or creating a custom subclass. The latter is the more flexible, so this is the route we’ll take here.

Create a new UICollectionViewCell subclass called CMFGalleryCell. This is going to need one public property declared in the .h file:

@property (nonatomic, strong) NSString *imageName;

and one public method:

-(void)updateCell;

Next we’re going to need a .xib file for the cell, so create a new that and give it a name - mine’s CMFGalleryCell.xib.

By default, the new view comes into life with a UIView inside it - we don’t need this, so delete it and replace it with a UICollectionViewCell object. The canned object is 50 points x 50 points, which isn’t what we need - change the dimensions to 320 x 548 (for a 4-inch device) or 320 x 440 (for a 3.5” device).

While you’re there, change the color of the background to white, and drop a UILabel to the center of the view. This label won’t show anything useful, but will help to show the effect of paging through the collection view when it runs for the first time.

Next, we need to tell this UICollectionViewCell that it’s actually an instance of our custom class. Select the object, and in the Identity inspector change the custom class field to CMFGalleryCell.

Once the object knows what it’s supposed to be, we need to update the CMFGalleryCell class so that it can extract the UICollectionViewCell from the nib file when the need arises. There’s several ways of doing this - here’s the way I tend to do it, in the initWithFrame: method:

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:@"CMFGalleryCell" owner:self options:nil];

        if ([arrayOfViews count] < 1) {
            return nil;
        }

        if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]]) {
            return nil;
        }

        self = [arrayOfViews objectAtIndex:0];
    }

    return self;
}

Stepping through this, the first task is to call the superclass’s initWithFrame: method. Assuming that this succeeds, we can then grab the contents of the xib file into the arrayOfViews array. This should have the UICollectionViewCell object as the first item in the array - but if that isn’t the case, the two if statements will allow things to fail gracefully.

Assuming things are where they’re supposed to be, the penultimate line grabs the UICollectionViewCell from the arrayOfViews, and the last line returns it.

Feeding the collection view with cells

Now we’re ready to send the UICollectionView some cells to display. Switch back to the view controller, and import the custom cell class:

#import "CMFGallleryCell.h"

Now we’re in a position to register this class for use. This needs to be done before the collectionView is displayed, so I tend to stick this in a configureCollectionView method which is called from viewDidLoad::

- (void)viewDidLoad {
    [super viewDidLoad];

    [self loadImages];
    [self setupCollectionView];
}

The setupCollectionView method is going to expand as we go along, but for the moment it needs to register the cell subclass for use:

-(void)setupCollectionView {
    [self.collectionView registerClass:[CMFGalleryCell class] forCellWithReuseIdentifier:@"cellIdentifier"];
}

That’s pretty self-explanatory - it tells the collectionView that there’s a class called CMFGalleryCell that will be associated with the reuse identifier cellIdentifier

Now we can start to implement the method that does the heavy lifting of cell creation:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    CMFGalleryCell *cell = (CMFGalleryCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];

    return cell;
}

A lot takes place behind the scenes here. Every time the collection view needs a cell, it will call the collectionView:cellForItemAtIndexPath: method on its dataSource, and expect a UICollectionViewCell object to be returned.

The method first attempts to dequeue an existing cell to save the memory overhead of creating a new one. If for any reason there isn’t a reusable cell available in the queue (for example, if it’s the first cell being created) then behind the scenes, the collection view’s dataSource object will create a new instance of one. As far as we’re concerned, we’re guaranteed to get a cell returned - either a shiny new one, or one that’s been used prevously and is ready for recycling.

Configuring the collection view at runtime

We’ve got to the point where there’s a collection view and collection view cells (albeit ones that don’t do anything yet). In order to get the cells displayed correctly, there’s a bit more configuration required.

This means expanding the existing setupCollectionView method:

-(void)setupCollectionView {
    [self.collectionView registerClass:[CVGGallleryCell class] forCellWithReuseIdentifier:@"cellIdentifier"];

    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
    [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
    [flowLayout setMinimumInteritemSpacing:0.0f];
    [flowLayout setMinimumLineSpacing:0.0f];
    [self.collectionView setPagingEnabled:YES];
    [self.collectionView setCollectionViewLayout:flowLayout];
}

In order to know how to display content, the collection view relies on a `UICollectionViewLayout` object which provides the necessary information to allow the collection view to draw the cells into the user interface.

The linear grid-based layout is sufficiently generic that Apple have provided a `UICollectionViewLayout` subclass called `UICollectionViewFlowLayout`.  Out of the box, this will draw lines of cells in a grid arrangement.  You don't need to understand too much about the detail at this stage, but these are the important characteristics that we need to implement in order to create the gallery view:

Creating the flow layout

The flow layout is created with

UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];

Configuring the scroll direction

Flow layouts can scroll in either vertical (up-and-down table view style) or horizontal (side-to-side gallery style) directions. We’re going to use horizontal:

[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];

Setting the cell spacing

To get the seamless gallery effect, we need to setup the collection view to display the cells without any spacing:

[flowLayout setMinimumInteritemSpacing:0.0f];
[flowLayout setMinimumLineSpacing:0.0f];

Setting up paging

Getting each image to “snap” to the screen as it scrolls is a nice touch, and UICollectionView provides that functionality out of the box:

[self.collectionView setPagingEnabled:YES];

Setting the item size

The collection view needs be informed of the size of the item, which can be set statically or dynamically via a delegate protocol. If you’re only ever going to have one size of cell, then you can simple set the item size with:

[flowLayout setItemSize:CGSizeMake(320, 548)];

However, it can also be set dynamically through the UICollectionViewDelegateFlowLayout protocol. To use this method, first switch to the view controller’s header file and add the declaration for the protocol:

@interface CMFViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>

Then switch back to the implementation file and add the collectionView:layout:sizeForItemAtIndexPath: method:

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(320, 548);
}

Applying the layout to the collection view

Once the UICollectionViewFlowLayout is setup and configured, it needs to be applied to the collection view itself:

[self.collectionView setCollectionViewLayout:flowLayout];

At this point, if you build and run the project, you’ll see a working collection view with paging cells:

Configuring the cells at runtime

Functional as this is, it doesn’t yet look much like a gallery. To get the images requires a bit more work, firstly in the view controller:

Feeding the cells with data

Thinking back to the data that we’re working with, we’ve got an NSArray of NSStrings containing image filenames. We’re going to pass that filename to the cell, and then load the image into the cell.

Passing the filename to the cell can be done when the cell is dequeued (or created) in the collectionView:cellForItemAtIndexPath: method:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    CMFGalleryCell *cell = (CMFGalleryCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];

    NSString *imageName = [self.dataArray objectAtIndex:indexPath.row];
    [cell setImageName:imageName];

    [cell updateCell];

    return cell;
}

Loading the image into the cell

We’ll do this in the cell’s updateCell method

-(void)updateCell {
    NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Assets"];
    NSString *filename = [NSString stringWithFormat:@"%@/%@", sourcePath, self.imageName];

    UIImage *image = [UIImage imageWithContentsOfFile:filename];

    [self.imageView setContentMode:UIViewContentModeScaleAspectFit];
    [self.imageView setImage:image];
}

One final tweak - switch to the cell’s .xib file, remove the UILabel and change the background colour of the UICollectionViewCell to ‘default’.

Run the app again, and now you’ve got a paging gallery:

Handling device rotation

At the moment, if you rotate the simulator (or device) you’ll see an error in the console:

UICollectionGallery[26606:c07] the behavior of the UICollectionViewFlowLayout is not defined because:
UICollectionGallery[26606:c07] the item height must be less that the height of the UICollectionView minus the section insets top and bottom values.

That’s occuring because when the device rotates, the collection view cell is taller than the collection view itself. To fix this, we need to do two things:

Making the collection view cell size dynamic

We want the collection view cell to be the same size as the collection view, regardless of whether the device is in portrait or landscape orientation. If we’d set the collection view cell size statically (in the setupCollectionView method) that would be tricky - but we future-proofed ourselves by making this a dynamic method.

The collection view cell size is controlled by the collectionView:layout:sizeForItemAtIndexPath: method - so we can update this to return whatever the current size of the UICollectionView is:

-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.collectionView.frame.size;
}

Forcing the collection view to update

If you run the app again, you’ll see that the error still occurs. That’s because having changed the size of the collection view cells, we need to force the collection view to redraw itself. We can do this while the device is rotating by hooking into the willRotateToInterfaceOrientation:duration: method:

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [self.collectionView reloadData];
}

But this now creates another glitch - when you rotate into a different orientation, the paging will be slightly off. To fix this, we need to extend the willRotateToInterfaceOrientation:duration: method:

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    CGPoint currentOffset = self.collectionView.contentOffset;

    float newOffsetX;

    if ((toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)) {
        // currently in portrait
        float offsetIndex = (int)currentOffset.x / [[UIScreen mainScreen] bounds].size.width ;
        newOffsetX = offsetIndex * [[UIScreen mainScreen] bounds].size.height;
    } else {
        // currently in landscape
        float offsetIndex = (int)currentOffset.x / [[UIScreen mainScreen] bounds].size.height;
        newOffsetX = offsetIndex * [[UIScreen mainScreen] bounds].size.width;
    }

    CGPoint newOffset = CGPointMake(newOffsetX, 0);
    [self.collectionView setContentOffset:newOffset];

    [self.collectionView reloadData];
}

This checks the current offset, figures out how many images along the Y axis that offset equates to, then calculates a new offset to move the collectionView to the appropriate location.

Going further

Although this example is based on a full-screen gallery, there’s no reason why it can’t be adapted to fit into a pre-existing interface. It’s just a case of amending the sizes of the collection view and cells, and tweaking the paging increments accordingly.

Source code

A complete project with source code is downloadable from https://github.com/timd/UICollectionViewGallery.git

Hacking the Government’s Kebabs

| Comments

It’s a truth universally acknowledged that a man or women in possession of a skinful of beer must be in search of a kebab.

But the path to kebab nirvana is often paved with the slings and arrows of outrageous fortune.

Not any more - there’s an app for that.

Kebabbage is an app that I slung together at this weekend’s National Hack The Government Day with the help of Shaun McDonald. There were about 9,000 separate datasets on offer of varying degrees of interest and complexity, but one that caught my eye was the API offered by the Food Standards Agency. This aggregates the results of local authority Trading Standards inspections of food retailers, and allows searching by a variety of parameters such as location and authority.

There’s also data on hygiene scores, which suggested an approach - having donned beer googles it seems the dodgier the establishment, the better the kebab. So rather than go for the obvious “where’s the cleanest and nicest” approach, I decided to turn it on its head, and sort and highlight the lowest scores.

Overall, it’s a bit silly, and not especially useful. But then not every hackday creation can be about saving the world.

The how

The app relies on the core iOS MapKit library, and two external APIs. The uk-postcodes.com API converts the geolocated latitude and longitude values to UK postcodes; and the Food Standards Agency api provides a JSON feed of rated premises based on the postcode.

Functionally, the app has a single view controller that manages the main interface, and acts as the delegate for two network client classes. These are subclasses of AFNetworking’s AFHTTPClient - each one spins up with a custom init method and then uses an AFJSONRequestOperation to retrieve the relevant data.

Getting the data and plotting it onto the map has four stages:

  • converting the lat/long position of the device to a UK postcode
  • using the postcode to get a list of premises from the FSA API
  • for each premises, converting the postcode to a lat/long pair
  • plotting each premises on the map

Getting the initial lat/long-to-postcode isn’t any big issue, because it’s a single asynchronous call-and-response to the uk-postcodes.com API. Neither is getting the list of premises - again, that’s a single call-and-response which results in a blob of JSON.

Converting the postcode for each premises is a bit more involved, though. There can be over 100 premises returned in a single query, and converting each postcode takes time. Firing these off as a long series of individual requests would mean reliniquishing the chance of knowing when all had been processed.

The approach I used instead was to create a series of individual AFJSONRequestOperation objects, and then batching these up with AFHTTPClient’s enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock: method. As the signature suggests, this takes an array of multiple AFJSONRequestOperation operations, and a completion block which is fired after all the individual requests have been successfully processed (or have failed, whichever is the case). That allows code to be scheduled to run once everything has completed - which in this case was the placement of the MKAnnotation objects onto the map.

-(void)getLatLongForArrayOfObjects:(NSArray *)outletsArray {

    NSMutableArray *operationsArray = [[NSMutableArray alloc] init];

    for (KBBOutlet *outlet in outletsArray) {

        // GET http://uk-postcodes.com/postcode/<postcode>.json
        NSString *postcode = [outlet postCode];

        NSString *urlString = [NSString stringWithFormat:@"%@postcode/%@.json", kPostcodeApiUrl, postcode];
        NSString *encodedURLString = [urlString stringByAddingPercentEscapesUsingEncoding:NSStringEncodingConversionAllowLossy];
        NSURL *url = [NSURL URLWithString:encodedURLString];

        NSURLRequest *request = [NSURLRequest requestWithURL:url];

        AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
            // SUCCESS
            NSDictionary *geoDictionary = [JSON objectForKey:@"geo"];
            NSString *latString = [geoDictionary objectForKey:@"lat"];
            NSString *lngString = [geoDictionary objectForKey:@"lng"];
            [outlet setLatitude:[latString floatValue]];
            [outlet setLongitude:[lngString floatValue]];
        } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
            // FAILURE
            NSLog(@"Error in PostCode: %@", error);
        }];
        [operation setShouldExecuteAsBackgroundTaskWithExpirationHandler:nil];
        [operationsArray addObject:operation];
    }

    AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:kPostcodeApiUrl]];
    [client enqueueBatchOfHTTPRequestOperations:operationsArray progressBlock:nil completionBlock:^(NSArray *operations) {
        [self.delegate plotOutlets:outletsArray];
    }];

}

The code is sitting up on GitHub at https://github.com/timd/Kebbabage

Some other post-hackday thoughts

I was somewhat suprised prior to the Hackday when I found out that it was partly sponsored by the Taxpayers Alliance. I’ve got a major problem with them as an opaquely-funded, right-wing blowhard pressure group that have an simplistic “everything private sector good, anything public sector bad” philosophy. Hackdays like this attract a large number of participants from all areas of the public sector, people who the TPA clearly look down their noses at - so at first glance there didn’t seem to be a natural fit.

I’m a tax-payer - in fact I’m pretty sure that I pay more UK tax than most of their funders - and they don’t speak for me. Tax is the price I pay for living in a civilised society, and the TPA espouse a set of small-minded, selfish, begger-thy-neighbour attitudes that grate on me badly. They know the price of everything, and the value of nothing.

Paul Clark puts it far more eloquently than I can.

The data they supplied was largely culled from Freedom of Information requests to local authorities - and the effort that has gone into compiling and consolidating this is worthy enough in and of itself. But there were some less-than-subtle hints that we might make want to spend the day digging through it in search of numbers that could later be turned into lazy Daily Mail-friendly “OMG councils spends bazillions on flowers for lesbian single parent playgroups” headlines.

That kind of political point scoring is not what I do hackdays for. I loathe and detest pretty much everything that our current government stands for; I’m not much more enamoured of the opposition alternatives. But there are better things that I can do with the skills that I’ve got available than digging up more mud for politicians to sling at each other. Doing that for unaccountable pressure groups is definitely off my agenda.

Having said all that, I don’t have a problem with involving the TPA in these kind of events. Who knows, perhaps some exposure to people who will willingly give up a day of their weekend to hack with data for no immediate reward than the inherent satisfaction might go some way to expose the shallowness of the TPA’s profit-centric philosophy. And looking at it from the point of view of Rewired State, they’re a) a source of data and b) are a fixture in the political playground, so they have a part to play.

But I found it quietly satisfying that if the TPA planned to buy the evening’s beer in the hope of someone finding them some dirt to sling, they probably left disappointed.

More on Blocks in Objective-C (but Still Without Tears)

| Comments

This is the second part of a two-part look at blocks in Objective-C - you can find the first part here.

Blocks in detail

Apple provides some pretty detailed documentation on blocks with as complete a definition as I’ve ever seen – so let’s look at that and use it as a starting point.

According to Apple, a block is:

…an anonymous inline collection of code that:

  • Has a typed argument list just like a function

  • Has an inferred or declared return type

  • Can capture state from the lexical scope within which it’s defined

  • Can optionally modify the state of the lexical scope

  • Can share the potential for modification with other blocks defined within the same lexical scope

  • Can continue to share and modify state defined within the lexical scope (the stack frame) after the lexical scope (the stack frame) has been destroyed.

OK, let’s do this again in English.