
Using blocks in iOS. Part 2
- Tutorial
In the first part of our tutorial, we used the Storyboard to customize the view. In the second and final part, we finally got to the blocks themselves. We’ll talk about what blocks are, what their syntax is, how to use them, and a bunch of examples. It will show how to use blocks with NSArray, UIView animations, Grand Central Dispatch, and more.
Blocks were first introduced in iOS 4.0 and Mac OSX 10.6 to simplify the code. They can help reduce it, reduce delegate dependency, and write clean, readable code. But, despite the obvious advantages, many developers do not use blocks due to an incomplete understanding of the principle of their action.
Let's look at all the “what, where, when and why” of the blocks.
From the inside, a block is a piece of code that can be called at some point in the future. A block is a first class function , so we can say that blocks are ordinary Objective-C objects. And objects can be passed as parameters, returned from functions and methods, and assigned to variables. Units are often called closures (closures) in languages such as Python, Ruby and Lisp, because after the announcement they encapsulate their state. The block creates a constant copy of any local variable referenced.
When you need to invoke some code that returns something later, you probably would use delegates or NSNotificationCenter, not blocks. And it would work fine, but pieces of code would be scattered everywhere - the task starts from one point, and the result is processed to another.
Blocks are better because they keep the task in one place, which we will soon see.
For you! But seriously, blocks are for everyone and everyone uses blocks. Blocks are the future, so you can learn them right now. Many methods of built-in frameworks are rewritten or supplemented by blocks based on existing functionality.
This is a picture from the iOS Developer Library that explains block syntax well.

The format of the description of the blocks is as follows:
If you have already programmed in any other C-language, this construction is completely familiar to you except for this ^ symbol. ^ and means block. If you understand that ^ means "I am a block," congratulations, you just understood the most difficult thing about blocks. ;]
Please note that names for parameters are not required at the moment, but you can include them if you wish.
Below is a sample block declaration.
Next, a block description.
Actually, this is how the block is created. Note that the description of the block is different from its declaration. It starts with the ^ character and is accompanied by parameters that can be named and which must correspond to the type and order of parameters in the block declaration. The following is the code itself.
In the block definition, the type of the return value is optional; it can be identified from the code. If there are several return values, they must be of the same type.
Sample block description:
If you combine the description and declaration of the block, you get the following code:
You can also use blocks in this way:
Now let's look at a couple of examples of using blocks in contrast with the same code without blocks.
Let's see how the blocks change the principle of performing some operations on the array. For starters, here is the usual code for the loop:
You may not have to set the value of the “stop” variable. But everything will become clear after you see the implementation of this method using the block. The block method contains the “stop” variable, which allows you to stop the cycle at any time, and therefore we simply duplicate this feature here for code similarity.
Now let's look at the same code using fast enumeration.
And now with the block:
In the code above, you may wonder what the “stop" variable is. This is just a variable that can be assigned YES inside the block to stop the process. This parameter is defined as the part of the block that will be used in the enumerateObjectsUsingBlock method .
The above code is trivial, and to see the advantages of blocks in it is quite difficult. But there are two things that I would like to note:
Let's look at a simple animation that operates on a single UIView. It changes the alpha parameter to 0 and moves the view down and right by 50 points, then removes the UIView from the superview. Simple, right? Code without blocks:
Block method:
If you look closely at these two methods, you will notice three advantages of the block method:
I think the best advice is to use the blocks where they are needed. Perhaps you want to continue to use the old methods to maintain backward compatibility, or simply because they are more familiar with them. But every time you come to a similar decision point, think about whether the blocks can make your life easier for writing code, and whether it is worth using methods with a block approach.
Of course, over time, you may notice that you are increasingly in need of blocks simply because most of the frameworks are written and rewritten using blocks. Therefore, start using the blocks now to meet them in the future fully equipped.
We continue from the same place where we stopped in the first part . If you have not read the first part, or just want to refresh everything in your memory, you can download the project here .
Open the project in Xcode and switch to Project Navigator . Right-click on the iOSDiner folder and select New Group . Let's call it Models.

Now right-click on the created Models folder and select the New File → Objective-C class (or Cocoa Touch Class). Let's call it “IODItem” and select NSObject as the parent class .

Repeat the above steps to create the IODOrder class .

Now we have all the necessary classes. The time has come for the code.
Open IODItem.h . First of all, you need to add the NSCopying protocol to the class . Protocols
are a kind of “convention” about the methods that the class will implement. If a protocol is declared in a class, then in the same class it is necessary to define the mandatory, or optional methods that are described in the protocol. The protocol declaration is as follows:
Next, add the properties for the object. Each object will have a name, price and image file name. IODItem.h will look like this:
Now let's switch to IODItem.m . Here we see a warning.

This warning applies to the NSCopying protocol that we added earlier. Remember that protocols can describe binding methods? So NSCopying requires you to define
That's all, the warning has disappeared. This code creates a new IODItem object, copies the properties of an existing object, and returns this new instance.
Additionally, an initializer method is required. It allows you to easily and quickly set default property values when an object is initialized. Write in IODItem.m :
Go back to IODItem.h and add a prototype method before ending the file (@end):
Next, let's work on another class - IODOrder . This class will be an order and the operations associated with it - adding an object, deleting, calculating the total cost of the order and displaying the contents of the order on the screen.
Open IODOrder.h and add the header file import before the interface :
And under interface we will write the property:
This is a dictionary in which objects selected by the user will be stored.
Switch to ViewController.m , add properties:
Property currentItemIndex will store the index to view objects in the inventory ( inventory a ). The meaning of inventory is easy to explain - it is an array of IODItem elements that we get from the web service. order - an object of class IODOrder , which stores objects selected by the user.
Next, in the viewDidLoad method, you need to reset currentItemIndex and order .
Now the start of ViewController.m looks like this:
Build a project. There should be no warnings.
The method of the retrieveInventoryItems class , which we will add now, will load and process the inventory downloaded from the web service.
Note . Class methods begin with "+", and class instance methods with "-".
In IODItem.m, immediately after the #import directives, add the following:
Note . Change the address if you hosted the web service yourself.
Next, add the retrieveInventoryItems method .
And here is our first block. Let's take a closer look at the code.
Now open IODItem.h and add a prototype method:
Another thing you need to know is dispatch queue . Switch to ViewController.m and add a property.
At the end of the ViewDidLoad method, write the line:
The first parameter to the dispatch_queue_create method is the name of the queue. You can name it however you want, but this name must be unique. Therefore, Apple recommends a reverse DNS style for this name.
Now let's use this queue. Write in viewDidAppear :
Run the project. Something is wrong, right? We use the retrieveInventoryItems method that is defined in the IODItem to call the web service, return the inventory objects and put them into an array.
Remember the five second delay in the PHP script from the last part ? But, when we run the program, we do not see “Loading inventory ...”, then wait five seconds, and see “Inventory Loaded”.
The problem is this: a call to a web service blocks and “freezes” the main queue, and does not allow it to change the label text. If only we had a separate line, which could be used for such operations, without going to the main line ... Stop! We already have it. That's where the Grand Central Dispatchand blocks can help us quite easily. With Grand Central Dispatch, we can put data processing in a different queue without blocking the main one. Replace the last two lines in viewDidAppear with the following:
Notice that here we use two different blocks that return nothing and have no parameters.
Run the project again. Now it works as it should.
You have not wondered why we call dispatch_async to assign text to the label? When you put the text in the label, you update the UI element, and everything related to the UI should be processed in the main queue. Therefore, we once again call dispatch_async , but only take the main queue and execute the block in it.
This method is quite common when the operation takes a lot of time and you need to update the UI elements.
Grand Central Dispatch is a rather complex system, the role of which is impossible to understand in this simple lesson. If you are interested, readMultithreading and Grand Central Dispatch on iOS for Beginners .
We use a web service to download and store inventory. Now we need three helper methods that will display the stored inventory to the user.
The first method, findKeyForOrderItem:, we will add to IODOrder.m . It will be useful for accessing an object from a dictionary.
Let's see what this method does. But before doing this, I must explain why it is needed at all. The IODOrder object contains the dictionary orderItems (key-value pairs). The key is IODItem , and the value is NSNumber , which shows how many such IODItem have been ordered.
In theory, everything is fine, but a peculiar quirk of the NSDictionary class is that when you want to assign an object as a key, it does not assign this object, but creates a copy of it and uses it as a key. This means that the object you use as a key must comply with the NSCopying protocol (which is why we are in IODItemannounced the NSCopying protocol ).
The key in the dictionary orderItems and IODItem in the inventory array are not the same object (although they have the same properties), so we can’t do a simple key search. Instead, we will have to compare the name and price of each object to determine if they match. This is what the above method does: it compares the properties of the keys to find the right one.
And here is what the code does:
Remember to add the prototype method to IODOrder.h.
Go to ViewController.m and add the following method:
Using currentItemIndex and the inventory array , this method displays the name and image for each inventory object.
We write down one more method:
This method is the longest of the three helper methods, but rather simple. It checks the status of the program and determines whether to make a button active.
For example, if currentItemIndex is zero, ibPreviousItemButton should not be active, because there are no elements before the first. If there are no elements in orderItems , then the ibTotalOrderButton button should not be active, because there is no order for which the amount could be calculated.
So, with these three methods you can now do magic. Let's go back to viewDidAppear in ViewController.m and add at the very beginning:
Then, replace the block with this:
Build and run.
Oh, here comes the hamburger. But we want to see the rest of the food, so let's make the buttons work.
The ibaLoadNextItem: and ibaLoadPreviousItem: methods we already have in ViewController.m . Add some code.
Thanks to these helper methods, switching between objects with changing currentItemIndex and updating the UI has become very easy. Compile and run. Now you can see the entire menu.
We have a menu, but no waiter to accept the order. In other words, the add and remove buttons do not work. Well, the time has come.
We need another helper method in the IODOrder class . In I ODOrder.m we write the following:
This is a simple getter method for the orderItems property . If there is already something in orderItems , the method will return an object. And if not, it will create a new dictionary and assign it orderItems , and then return it.
Next, we ’ll work on the orderDescription method . This method will give us lines for output on a chalk board. In IODOrder.m we write:
A little explanation:
We go to IODOrder.h and add prototypes of these two methods.
Now that we can get the order description line from the object, switch to ViewController.m and add a method to call.
This method checks the number of objects in the order. If there are none, the string “No Items. Please order something! ". If otherwise, the method uses the orderDescription method from the IODOrder class to display an order description string.
Now we can update the board in accordance with the current order. Updating our block from viewDidAppear :
I understand that this may seem pointless, as we just rewrote the source code a couple of lines below, but for the sake of accuracy, this can be done.
The following method will add an object to the order. We go to IODOrder.m .
I explain:
Method removeItemFromOrder: almost the same as addItemToOrder: . In IODOrder.m we write:
Note that when removing an object from an order, we need to do something only if the object is found in the order. If it is found, we look at its quantity, decrease by 1, delete the object from the dictionary and insert a new object there, if the number is greater than 0.
Go to IODOrder.h and add the prototypes:
Now in ViewController.m you can write code for the buttons for deleting and adding objects:
All we need to do in both methods is to get the current object from the inventory array, pass it to addItemToOrder: or removeItemFromOrder:, and update the UI.
Build and build a project. Now objects can be added and deleted, and the board is updated.
Let's revive our program a bit. Change ibaRemoveItem: and ibaAddItemMethod:
There is a lot of code, but it is simple. The first part creates a UILabel and exposes its properties. The second is the animation that moves the created label. This is an example of a UIView animation with a block, which we described at the beginning of the lesson.
Run the project. Now you can see the animation when you click on the buttons to add and remove an object.
The last method that we need to add to IODOrder.m is the method of calculating the order amount.
Let's go through the code:
Go back to IODOrder.h and add the prototype.
The last thing left to do is add the amount calculation to the application. All the dirty work will be done using the totalOrder method , we only need to show the amount to the user by pressing the Total button . Fill in the ibaCalculateTotal method :
Here we consider the order amount and display it on the screen.
That's all! Run the project.

Before finishing, I will give the list of blocks which can be useful.
Below are code samples for creating custom blocks.
Since a block is an Objective-C object, it can be stored in a property for future reference. This is convenient when you want to call a method after completing some asynchronous task (network-related, for example).
Finally, you can simplify the code using typedef .
Last tip. When you use a method that takes a block, Xcode can autocomplete the block for you, saving you time and preventing possible errors.
For example, enter:
The autocomplete program will find enumerateObjectsUsingBlock - press Enter to autocomplete. Then press Enter again to autocomplete the block. The result is this:
You can write your code instead of code , close the method call, and voila - much easier than typing everything manually.
The project code can be downloaded here . If you are familiar with git, you can take the project from here with commits at every step. I hope that in the process of creating this simple application you could feel the simplicity and power of the blocks, and you got some ideas for using them in your projects.
If this was your acquaintance with the blocks - you have taken a huge step. Blocks need to be learned to work with multithreading, network and more.
More information about the blocks can be found here:
Introduction
Blocks were first introduced in iOS 4.0 and Mac OSX 10.6 to simplify the code. They can help reduce it, reduce delegate dependency, and write clean, readable code. But, despite the obvious advantages, many developers do not use blocks due to an incomplete understanding of the principle of their action.
Let's look at all the “what, where, when and why” of the blocks.
What are these “Blocks” and why are they so important?
From the inside, a block is a piece of code that can be called at some point in the future. A block is a first class function , so we can say that blocks are ordinary Objective-C objects. And objects can be passed as parameters, returned from functions and methods, and assigned to variables. Units are often called closures (closures) in languages such as Python, Ruby and Lisp, because after the announcement they encapsulate their state. The block creates a constant copy of any local variable referenced.
When you need to invoke some code that returns something later, you probably would use delegates or NSNotificationCenter, not blocks. And it would work fine, but pieces of code would be scattered everywhere - the task starts from one point, and the result is processed to another.
Blocks are better because they keep the task in one place, which we will soon see.
Who needs blocks?
For you! But seriously, blocks are for everyone and everyone uses blocks. Blocks are the future, so you can learn them right now. Many methods of built-in frameworks are rewritten or supplemented by blocks based on existing functionality.
How to use blocks?
This is a picture from the iOS Developer Library that explains block syntax well.

The format of the description of the blocks is as follows:
return_type (^block_name)(param_type, param_type, ...)
If you have already programmed in any other C-language, this construction is completely familiar to you except for this ^ symbol. ^ and means block. If you understand that ^ means "I am a block," congratulations, you just understood the most difficult thing about blocks. ;]
Please note that names for parameters are not required at the moment, but you can include them if you wish.
Below is a sample block declaration.
int (^add)(int,int)
Next, a block description.
^return_type(param_type param_name, param_type param_name, ...) { ... return return_type; }
Actually, this is how the block is created. Note that the description of the block is different from its declaration. It starts with the ^ character and is accompanied by parameters that can be named and which must correspond to the type and order of parameters in the block declaration. The following is the code itself.
In the block definition, the type of the return value is optional; it can be identified from the code. If there are several return values, they must be of the same type.
Sample block description:
^(int number1, int number2){ return number1+number2 }
If you combine the description and declaration of the block, you get the following code:
int (^add)(int,int) = ^(int number1, int number2){
return number1+number2;
}
You can also use blocks in this way:
int resultFromBlock = add(2,2);
Now let's look at a couple of examples of using blocks in contrast with the same code without blocks.
Example: NSArray
Let's see how the blocks change the principle of performing some operations on the array. For starters, here is the usual code for the loop:
BOOL stop;
for (int i = 0 ; i < [theArray count] ; i++) {
NSLog(@"The object at index %d is %@",i,[theArray objectAtIndex:i]);
if (stop)
break;
}
You may not have to set the value of the “stop” variable. But everything will become clear after you see the implementation of this method using the block. The block method contains the “stop” variable, which allows you to stop the cycle at any time, and therefore we simply duplicate this feature here for code similarity.
Now let's look at the same code using fast enumeration.
BOOL stop;
int idx = 0;
for (id obj in theArray) {
NSLog(@"The object at index %d is %@",idx,obj);
if (stop)
break;
idx++;
}
And now with the block:
[theArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSLog(@"The object at index %d is %@",idx,obj);
}];
In the code above, you may wonder what the “stop" variable is. This is just a variable that can be assigned YES inside the block to stop the process. This parameter is defined as the part of the block that will be used in the enumerateObjectsUsingBlock method .
The above code is trivial, and to see the advantages of blocks in it is quite difficult. But there are two things that I would like to note:
- Simplicity . Using blocks, we can access the object, the index of the object in the array, and the stop variable without writing code. This leads to a decrease in code, which in turn reduces the number of errors.
- Speed . The block method has a slight speed advantage over the fast enumeration method. In our case, this advantage is perhaps too small to mention, but in more complex cases it becomes significant.
Example: UIView animations
Let's look at a simple animation that operates on a single UIView. It changes the alpha parameter to 0 and moves the view down and right by 50 points, then removes the UIView from the superview. Simple, right? Code without blocks:
- (void)removeAnimationView:(id)sender {
[animatingView removeFromSuperview];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[UIView beginAnimations:@"Example" context:nil];
[UIView setAnimationDuration:5.0];
[UIView setAnimationDidStopSelector:@selector(removeAnimationView)];
[animatingView setAlpha:0];
[animatingView setCenter:CGPointMake(animatingView.center.x+50.0,
animatingView.center.y+50.0)];
[UIView commitAnimations];
}
Block method:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[UIView animateWithDuration:5.0
animations:^{
[animatingView setAlpha:0];
[animatingView setCenter:CGPointMake(animatingView.center.x+50.0,
animatingView.center.y+50.0)];
}
completion:^(BOOL finished) {
[animatingView removeFromSuperview];
}];
}
If you look closely at these two methods, you will notice three advantages of the block method:
- Simplifies the code . With a block, we do not need to define a completely separate method to complete the callback, or call beginAnimations / commitAnimations .
- All code is in one place . Using a block, you don’t need to start the animation in one place, and the callback method in another. All animation related code is in one place, which makes it easier to write and read.
- So says Apple . Apple rewrote existing functionality that did not use blocks, so Apple officially recommends switching to block-based methods, if possible.
When to use blocks?
I think the best advice is to use the blocks where they are needed. Perhaps you want to continue to use the old methods to maintain backward compatibility, or simply because they are more familiar with them. But every time you come to a similar decision point, think about whether the blocks can make your life easier for writing code, and whether it is worth using methods with a block approach.
Of course, over time, you may notice that you are increasingly in need of blocks simply because most of the frameworks are written and rewritten using blocks. Therefore, start using the blocks now to meet them in the future fully equipped.
Returning to iOS Diner: customizing model classes
We continue from the same place where we stopped in the first part . If you have not read the first part, or just want to refresh everything in your memory, you can download the project here .
Open the project in Xcode and switch to Project Navigator . Right-click on the iOSDiner folder and select New Group . Let's call it Models.

Now right-click on the created Models folder and select the New File → Objective-C class (or Cocoa Touch Class). Let's call it “IODItem” and select NSObject as the parent class .

Repeat the above steps to create the IODOrder class .

Now we have all the necessary classes. The time has come for the code.
Configure IODItem
Open IODItem.h . First of all, you need to add the NSCopying protocol to the class . Protocols
are a kind of “convention” about the methods that the class will implement. If a protocol is declared in a class, then in the same class it is necessary to define the mandatory, or optional methods that are described in the protocol. The protocol declaration is as follows:
@interface IODItem : NSObject
Next, add the properties for the object. Each object will have a name, price and image file name. IODItem.h will look like this:
#import
@interface IODItem : NSObject
@property (nonatomic,strong) NSString* name;
@property (nonatomic,strong) float price;
@property (nonatomic,strong) NSString* pictureFile;
@end
Now let's switch to IODItem.m . Here we see a warning.

This warning applies to the NSCopying protocol that we added earlier. Remember that protocols can describe binding methods? So NSCopying requires you to define
— (id)copyWithZone:(NSZone *)zone
. Until this method is added, the class will be considered incomplete. Add the following to IODItem.m before @end :- (id)copyWithZone:(NSZone *)zone {
IODItem *newItem = [[IODItem alloc] init];
newItem.name = _name;
newItem.price = _price;
newItem.pictureFile = _pictureFile;
return newItem;
}
That's all, the warning has disappeared. This code creates a new IODItem object, copies the properties of an existing object, and returns this new instance.
Additionally, an initializer method is required. It allows you to easily and quickly set default property values when an object is initialized. Write in IODItem.m :
- (id)initWithName:(NSString *)name
andPrice:(NSNumber *)price
andPictureFile:(NSString *)pictureFile {
if(self = [self init])
{
_name = name;
_price = price;
_pictureFile = pictureFile;
}
return self;
}
Go back to IODItem.h and add a prototype method before ending the file (@end):
- (id)initWithName:(NSString*)inName andPrice:(float)inPrice andPictureFile:(NSString*)inPictureFile;
Configure IODOrder
Next, let's work on another class - IODOrder . This class will be an order and the operations associated with it - adding an object, deleting, calculating the total cost of the order and displaying the contents of the order on the screen.
Open IODOrder.h and add the header file import before the interface :
#import "IODItem.h"
And under interface we will write the property:
@property (nonatomic,strong) NSMutableDictionary* orderItems;
This is a dictionary in which objects selected by the user will be stored.
ViewController setup
Switch to ViewController.m , add properties:
#import "ViewController.h"
#import "IODOrder.h"
@interface ViewController ()
@property (assign, nonatomic) NSInteger currentItemIndex;
@property (strong, nonatomic) NSMutableArray *inventory;
@property (strong, nonatomic) IODOrder *order;
Property currentItemIndex will store the index to view objects in the inventory ( inventory a ). The meaning of inventory is easy to explain - it is an array of IODItem elements that we get from the web service. order - an object of class IODOrder , which stores objects selected by the user.
Next, in the viewDidLoad method, you need to reset currentItemIndex and order .
- (void)viewDidLoad {
[super viewDidLoad];
self.currentItemIndex = 0;
self.order = [[IODOrder alloc] init];
}
Now the start of ViewController.m looks like this:
#import "ViewController.h"
#import "IODOrder.h"
@interface ViewController ()
@property (assign, nonatomic) NSInteger currentItemIndex;
@property (strong, nonatomic) NSMutableArray *inventory;
@property (strong, nonatomic) IODOrder *order;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.currentItemIndex = 0;
self.order = [[IODOrder alloc] init];
}
Build a project. There should be no warnings.
Loading inventory
The method of the retrieveInventoryItems class , which we will add now, will load and process the inventory downloaded from the web service.
Note . Class methods begin with "+", and class instance methods with "-".
In IODItem.m, immediately after the #import directives, add the following:
#define INVENTORY_ADDRESS @"http://adamburkepile.com/inventory/"
Note . Change the address if you hosted the web service yourself.
Next, add the retrieveInventoryItems method .
+ (NSArray *)retrieveInventoryItems {
// 1 — создаем переменные
NSMutableArray *inventory = [[NSMutableArray alloc] init];
NSError *error = nil;
// 2 — получаем данные об инвентаре
NSArray *jsonInventory = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:INVENTORY_ADDRESS]]
options:kNilOptions
error:&error];
// 3 — смотрим все объекты инвентаря
[jsonInventory enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSDictionary *item = obj;
[inventory addObject:[[IODItem alloc] initWithName:item[@"Name"]
andPrice:item[@"Price"]
andPictureFile:item[@"Image"]]];
}];
// 4 — возвращаем копию инвентаря
return [inventory copy];
}
And here is our first block. Let's take a closer look at the code.
- First, we declared an array in which we will store objects for return, and a pointer to a possible error.
- Мы используем объект NSData для загрузки данных с веб-сервера, и затем передаем этот NSData-объект в JSON-сервис, чтобы декодировать исходные данные в Objective-C типы (NSArray, NSDictionary, NSString, NSNumber, и т. д.).
- Далее нам необходим enumerateObjectsUsingBlock: метод, которые мы обсуждали ранее, для «конвертирования» объектов из NSDictionary в IODItem. Мы вызываем этот метод для массива jsonInventory, и перебираем его с помощью блока, который передает элемент массива как NSDictionary в метод инициализации IODItem-объекта. Затем он добавляет этот новый объект в возвращаемый массив.
- In conclusion, it returns an array of equipment inventory a . Note that we are returning a copy of the array, not the array itself, because we do not want to return its mutable version. The copy method creates an immutable copy.
Now open IODItem.h and add a prototype method:
+ (NSArray*)retrieveInventoryItems;
Dispatch Queue and Grand Central Dispatch
Another thing you need to know is dispatch queue . Switch to ViewController.m and add a property.
@property (strong, nonatomic) dispatch_queue_t queue;
At the end of the ViewDidLoad method, write the line:
self.queue = dispatch_queue_create("com.adamburkepile.queue",nil);
The first parameter to the dispatch_queue_create method is the name of the queue. You can name it however you want, but this name must be unique. Therefore, Apple recommends a reverse DNS style for this name.
Now let's use this queue. Write in viewDidAppear :
self.ibChalkboardLabel.text = @"Loading inventory...";
self.inventory = [[IODItem retrieveInventoryItems] mutableCopy];
self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?";
Run the project. Something is wrong, right? We use the retrieveInventoryItems method that is defined in the IODItem to call the web service, return the inventory objects and put them into an array.
Remember the five second delay in the PHP script from the last part ? But, when we run the program, we do not see “Loading inventory ...”, then wait five seconds, and see “Inventory Loaded”.
The problem is this: a call to a web service blocks and “freezes” the main queue, and does not allow it to change the label text. If only we had a separate line, which could be used for such operations, without going to the main line ... Stop! We already have it. That's where the Grand Central Dispatchand blocks can help us quite easily. With Grand Central Dispatch, we can put data processing in a different queue without blocking the main one. Replace the last two lines in viewDidAppear with the following:
dispatch_async(self.queue, ^{
self.inventory = [[IODItem retrieveInventoryItems] mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^{
self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?";
});
});
Notice that here we use two different blocks that return nothing and have no parameters.
Run the project again. Now it works as it should.
You have not wondered why we call dispatch_async to assign text to the label? When you put the text in the label, you update the UI element, and everything related to the UI should be processed in the main queue. Therefore, we once again call dispatch_async , but only take the main queue and execute the block in it.
This method is quite common when the operation takes a lot of time and you need to update the UI elements.
Grand Central Dispatch is a rather complex system, the role of which is impossible to understand in this simple lesson. If you are interested, readMultithreading and Grand Central Dispatch on iOS for Beginners .
Additional methods
We use a web service to download and store inventory. Now we need three helper methods that will display the stored inventory to the user.
The first method, findKeyForOrderItem:, we will add to IODOrder.m . It will be useful for accessing an object from a dictionary.
- (IODItem *)findKeyForOrderItem:(IODItem *)searchItem {
//1 - находим соответствующий индекс объекта
NSIndexSet *indexes = [[self.orderItems allKeys] indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
IODItem *key = obj;
return ([searchItem.name isEqualToString:key.name] && searchItem.price == key.price);
}];
//2 - возвращаем первый совпавший объект
if([indexes count] >=1) {
IODItem *key = [self.orderItems allKeys][[indexes firstIndex]];
return key;
}
//3 - если ничего не нашли
return nil;
}
Let's see what this method does. But before doing this, I must explain why it is needed at all. The IODOrder object contains the dictionary orderItems (key-value pairs). The key is IODItem , and the value is NSNumber , which shows how many such IODItem have been ordered.
In theory, everything is fine, but a peculiar quirk of the NSDictionary class is that when you want to assign an object as a key, it does not assign this object, but creates a copy of it and uses it as a key. This means that the object you use as a key must comply with the NSCopying protocol (which is why we are in IODItemannounced the NSCopying protocol ).
The key in the dictionary orderItems and IODItem in the inventory array are not the same object (although they have the same properties), so we can’t do a simple key search. Instead, we will have to compare the name and price of each object to determine if they match. This is what the above method does: it compares the properties of the keys to find the right one.
And here is what the code does:
- We iterate over the keys of the orderItems dictionary using the indexesOfObjectsPassingTest: method to find name and price matches, is another example of a block. Pay attention to BOOL after ^. This is the return type. This method processes the array using a block to compare two objects, returns the indices of all objects that pass the test described in the block.
- Here we take the first index of the returned
- 3. Return nil if the matching key was not found.
Remember to add the prototype method to IODOrder.h.
- (IODItem*)findKeyForOrderItem:(IODItem*)searchItem;
Go to ViewController.m and add the following method:
- (void)updateCurrentInventoryItem {
if (self.currentItemIndex >=0 && self.currentItemIndex < [self.inventory count]) {
IODItem* currentItem = self.inventory[self.currentItemIndex];
self.ibCurrentItemLabel.text = currentItem.name;
self.ibCurrentItemLabel.adjustsFontSizeToFitWidth = YES;
self.ibCurrentItemImageView.image = [UIImage imageNamed:[currentItem pictureFile]];
}
}
Using currentItemIndex and the inventory array , this method displays the name and image for each inventory object.
We write down one more method:
- (void)updateInventoryButtons {
if (!self.inventory || ![self.inventory count]) {
self.ibAddItemButton.enabled = NO;
self.ibRemoveItemButton.enabled = NO;
self.ibNextItemButton.enabled = NO;
self.ibPreviousItemButton.enabled = NO;
self.ibTotalOrderButton.enabled = NO;
} else {
if (self.currentItemIndex <= 0) {
self.ibPreviousItemButton.enabled = NO;
} else {
self.ibPreviousItemButton.enabled = YES;
}
if (self.currentItemIndex >= [self.inventory count]-1) {
self.ibNextItemButton.enabled = NO;
} else {
self.ibNextItemButton.enabled = YES;
}
IODItem* currentItem = self.inventory[self.currentItemIndex];
if (currentItem) {
self.ibAddItemButton.enabled = YES;
} else {
self.ibAddItemButton.enabled = NO;
}
if (![self.order findKeyForOrderItem:currentItem]) {
self.ibRemoveItemButton.enabled = NO;
} else {
self.ibRemoveItemButton.enabled = YES;
}
if (![self.order.orderItems count]) {
self.ibTotalOrderButton.enabled = NO;
} else {
self.ibTotalOrderButton.enabled = YES;
}
}
}
This method is the longest of the three helper methods, but rather simple. It checks the status of the program and determines whether to make a button active.
For example, if currentItemIndex is zero, ibPreviousItemButton should not be active, because there are no elements before the first. If there are no elements in orderItems , then the ibTotalOrderButton button should not be active, because there is no order for which the amount could be calculated.
So, with these three methods you can now do magic. Let's go back to viewDidAppear in ViewController.m and add at the very beginning:
[self updateInventoryButtons];
Then, replace the block with this:
dispatch_async(queue, ^{
self.inventory = [[IODItem retrieveInventoryItems] mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateInventoryButtons];
[self updateCurrentInventoryItem];
self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?";
});
});
Build and run.
Oh, here comes the hamburger. But we want to see the rest of the food, so let's make the buttons work.
The ibaLoadNextItem: and ibaLoadPreviousItem: methods we already have in ViewController.m . Add some code.
- (IBAction)ibaLoadPreviousItem:(UIButton *)sender {
self.currentItemIndex--;
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
}
- (IBAction)ibaLoadNextItem:(UIButton *)sender {
self.currentItemIndex++;
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
}
Thanks to these helper methods, switching between objects with changing currentItemIndex and updating the UI has become very easy. Compile and run. Now you can see the entire menu.
Do delete and add an object
We have a menu, but no waiter to accept the order. In other words, the add and remove buttons do not work. Well, the time has come.
We need another helper method in the IODOrder class . In I ODOrder.m we write the following:
- (NSMutableDictionary *)orderItems{
if (!_orderItems) {
_orderItems = [[NSMutableDictionary alloc] init];
}
return _orderItems;
}
This is a simple getter method for the orderItems property . If there is already something in orderItems , the method will return an object. And if not, it will create a new dictionary and assign it orderItems , and then return it.
Next, we ’ll work on the orderDescription method . This method will give us lines for output on a chalk board. In IODOrder.m we write:
- (NSString*)orderDescription {
// 1 - Создаем строку
NSMutableString* orderDescription = [[NSMutableString alloc] init];
// 2 - Сортируем объекты по имени
NSArray* keys = [[self.orderItems allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
IODItem* item1 = (IODItem*)obj1;
IODItem* item2 = (IODItem*)obj2;
return [item1.name compare:item2.name];
}];
// 3 - перебираем объекты и добавляем имя и количество в строку
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
IODItem* item = (IODItem*)obj;
NSNumber* quantity = (NSNumber*)[self.orderItems objectForKey:item];
[orderDescription appendFormat:@"%@ x%@\n", item.name, quantity];
}];
// 4 - возвращаем строку с описанием заказа
return [orderDescription copy];
}
A little explanation:
- This is a line to describe the order. Each order object will be added to it.
- This piece of code takes an array from the keys of the dictionary orderItems and uses the sortedArrayUsingComparator: block to sort the keys by name.
- This code uses an already sorted array of keys and is already familiar enumerateObjectsUsingBlock: . We transform each key into IODItem , we receive value (quantity), and we add a line in orderDescription .
- Finally, we return the string orderDescription , but again only a copy of it.
We go to IODOrder.h and add prototypes of these two methods.
- (void)updateCurrentInventoryItem;
- (void)updateInventoryButtons;
Now that we can get the order description line from the object, switch to ViewController.m and add a method to call.
- (void)updateOrderBoard {
if (![self.order.orderItems count]) {
self.ibChalkboardLabel.text = @"No Items. Please order something!";
} else {
self.ibChalkboardLabel.text = [self.order orderDescription];
}
}
This method checks the number of objects in the order. If there are none, the string “No Items. Please order something! ". If otherwise, the method uses the orderDescription method from the IODOrder class to display an order description string.
Now we can update the board in accordance with the current order. Updating our block from viewDidAppear :
dispatch_async(self.queue, ^{
self.inventory = [[IODItem retrieveInventoryItems] mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateOrderBoard]; //<----Добавить эту строку
[self updateInventoryButtons];
[self updateCurrentInventoryItem];
self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?";
});
});
I understand that this may seem pointless, as we just rewrote the source code a couple of lines below, but for the sake of accuracy, this can be done.
The following method will add an object to the order. We go to IODOrder.m .
- (void)addItemToOrder:(IODItem*)inItem {
// 1 - ищем объект в заказе
IODItem* key = [self findKeyForOrderItem:inItem];
// 2 - если объект не существует - добавляем
if (!key) {
[self.orderItems setObject:[NSNumber numberWithInt:1] forKey:inItem];
} else {
// 3 - если существует - обновляем количество
NSNumber* quantity = self.orderItems[key];
int intQuantity = [quantity intValue];
intQuantity++;
// 4 - Обновляем заказ с новым количеством
[self.orderItems removeObjectForKey:key];
[self.orderItems setObject:[NSNumber numberWithInt:intQuantity] forKey:key];
}
}
I explain:
- We use the previously added method to find the key for orderItem . Remember that if the object is not found, it will simply return nil .
- If the object was not found in the order, add it in an amount equal to 1.
- If the object was found, look at its number and increase it by 1.
- Finally, delete the original record and record the new version with the updated quantity.
Method removeItemFromOrder: almost the same as addItemToOrder: . In IODOrder.m we write:
- (void)removeItemFromOrder:(IODItem*)inItem {
// 1 - ищем объект в заказе
IODItem* key = [self findKeyForOrderItem:inItem];
// 2 - удаляем объект, только если он существует
if (key) {
// 3 - берем количество, и уменьшаем на единицу
NSNumber* quantity = self.orderItems[key];
int intQuantity = [quantity intValue];
intQuantity--;
// 4 - удаляем объект
[[self orderItems] removeObjectForKey:key];
// 5 - добавляем новый объект с обновленным количеством только если количество больше 0
if (intQuantity)
[[self orderItems] setObject:[NSNumber numberWithInt:intQuantity] forKey:key];
}
}
Note that when removing an object from an order, we need to do something only if the object is found in the order. If it is found, we look at its quantity, decrease by 1, delete the object from the dictionary and insert a new object there, if the number is greater than 0.
Go to IODOrder.h and add the prototypes:
- (void)addItemToOrder:(IODItem*)inItem;
- (void)removeItemFromOrder:(IODItem*)inItem;
Now in ViewController.m you can write code for the buttons for deleting and adding objects:
- (IBAction)ibaRemoveItem:(UIButton *)sender {
IODItem* currentItem = self.inventory[self.currentItemIndex];
[self.order removeItemFromOrder:currentItem];
[self updateOrderBoard];
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
}
- (IBAction)ibaAddItem:(UIButton *)sender {
IODItem* currentItem = self.inventory[self.currentItemIndex];
[self.order addItemToOrder:currentItem];
[self updateOrderBoard];
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
}
All we need to do in both methods is to get the current object from the inventory array, pass it to addItemToOrder: or removeItemFromOrder:, and update the UI.
Build and build a project. Now objects can be added and deleted, and the board is updated.
UIAnimation
Let's revive our program a bit. Change ibaRemoveItem: and ibaAddItemMethod:
- (IBAction)ibaRemoveItem:(UIButton *)sender {
IODItem* currentItem = [self.inventory objectAtIndex:self.currentItemIndex];
[self.order removeItemFromOrder:currentItem];
[self updateOrderBoard];
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
UILabel* removeItemDisplay = [[UILabel alloc] initWithFrame:self.ibCurrentItemImageView.frame];
removeItemDisplay.center = self.ibChalkboardLabel.center;
removeItemDisplay.text = @"-1";
removeItemDisplay.textAlignment = NSTextAlignmentCenter;
removeItemDisplay.textColor = [UIColor redColor];
removeItemDisplay.backgroundColor = [UIColor clearColor];
removeItemDisplay.font = [UIFont boldSystemFontOfSize:32.0];
[[self view] addSubview:removeItemDisplay];
[UIView animateWithDuration:1.0
animations:^{
removeItemDisplay.center = [self.ibCurrentItemImageView center];
removeItemDisplay.alpha = 0.0;
}
completion:^(BOOL finished) {
[removeItemDisplay removeFromSuperview];
}];
}
- (IBAction)ibaAddItem:(UIButton *)sender {
IODItem* currentItem = [self.inventory objectAtIndex:self.currentItemIndex];
[self.order addItemToOrder:currentItem];
[self updateOrderBoard];
[self updateCurrentInventoryItem];
[self updateInventoryButtons];
UILabel* addItemDisplay = [[UILabel alloc] initWithFrame:self.ibCurrentItemImageView.frame];
addItemDisplay.text = @"+1";
addItemDisplay.textColor = [UIColor whiteColor];
addItemDisplay.backgroundColor = [UIColor clearColor];
addItemDisplay.textAlignment = NSTextAlignmentCenter;
addItemDisplay.font = [UIFont boldSystemFontOfSize:32.0];
[[self view] addSubview:addItemDisplay];
[UIView animateWithDuration:1.0
animations:^{
[addItemDisplay setCenter:self.ibChalkboardLabel.center];
[addItemDisplay setAlpha:0.0];
}
completion:^(BOOL finished) {
[addItemDisplay removeFromSuperview];
}];
}
There is a lot of code, but it is simple. The first part creates a UILabel and exposes its properties. The second is the animation that moves the created label. This is an example of a UIView animation with a block, which we described at the beginning of the lesson.
Run the project. Now you can see the animation when you click on the buttons to add and remove an object.
Getting the amount (Total)
The last method that we need to add to IODOrder.m is the method of calculating the order amount.
- (float)totalOrder {
// 1 - Объявляем и инициализируем переменную для суммы
__block float total = 0.0;
// 2 - Блок для полсчета
float (^itemTotal)(float,int) = ^float(float price, int quantity) {
return price * quantity;
};
// 3 - Перебираем объекты и суммируем стоимость каждого
[self.orderItems enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
IODItem* item = (IODItem*)key;
NSNumber* quantity = (NSNumber*)obj;
int intQuantity = [quantity intValue];
total += itemTotal([item.price floatValue], intQuantity);
}];
// 4 - Возвращаем сумму
return total;
}
Let's go through the code:
- Объявляем и инициализируем переменную, которая будет хранить сумму. Обратите внимание на __block. Мы будем использовать эту переменную внутри блока. Если мы не напишем __block, блок, который мы создали ниже, создаст константную копию этой переменной и использует ее внутри себя. Это означает, что мы не сможем изменить ее внутри блока. Путем добавления этого ключевого слова мы можем читать И менять значение переменной внутри блока.
- Теперь мы определяем блок, который просто берет цену и количество, и возвращает стоимость объекта.
- This piece of code through the enumerateKeysAndObjectsUsingBlock block : iterates over all the objects in the orderItems dictionary and uses the previous block to find the sum of each object, then adds this value to the sum of the entire order (which is why we need the __block keyword for the order variable - we change it inside the block).
- We return the total amount of the order.
Go back to IODOrder.h and add the prototype.
- (float)totalOrder;
The last thing left to do is add the amount calculation to the application. All the dirty work will be done using the totalOrder method , we only need to show the amount to the user by pressing the Total button . Fill in the ibaCalculateTotal method :
- (IBAction)ibaCalculateTotal:(UIButton *)sender {
float total = [self.order totalOrder];
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:@"Total"
message:[NSString stringWithFormat:@"$%0.2f",total]
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelButton = [UIAlertAction
actionWithTitle:@"Close"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:cancelButton];
[self presentViewController:alert animated:YES completion:nil];
}
Here we consider the order amount and display it on the screen.
That's all! Run the project.

Block cheat sheet
Before finishing, I will give the list of blocks which can be useful.
NSArray
- enumerateObjectsUsingBlock - perhaps the most often used block for me to iterate over objects.
- enumerateObjectsAtIndexes: usingBlock: - the same as enumerateObjectsUsingBlock , only you can iterate over objects in a certain interval.
- indexesOfObjectsPassingTest: - the block returns the set of indexes of objects that pass the test described by the block. Useful for finding a specific group of objects.
NSDictionary
- enumerateKeysAndObjectsUsingBlock: - iterates over the elements of a dictionary.
- keysOfEntriesPassingTest: - returns a set of keys whose objects pass the test described by the block.
Uiview
- animateWithDuration: animations: - block for UIAnimation, useful for simple animations.
- animateWithDuration: completion: - another UIAnimation block. Contains the second block for calling the code at the end of the animation.
Grand central dispatch
- dispatch_async is the main function for asynchronous GCD code.
Create your own blocks
Below are code samples for creating custom blocks.
// Некоторый метод, который принимает блок
- (void)doMathWithBlock:(int (^)(int, int))mathBlock {
self.label.text = [NSString stringWithFormat:@"%d", mathBlock(3, 5)];
}
// вызываем метод с блоком
- (IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
Since a block is an Objective-C object, it can be stored in a property for future reference. This is convenient when you want to call a method after completing some asynchronous task (network-related, for example).
// объявляем свойство
@property (strong) int (^mathBlock)(int, int);
// храним блок для вызова в дальнейшем
- (void)doMathWithBlock:(int (^)(int, int))mathBlock {
self.mathBlock = mathBlock;
}
// Вызываем метод с блоком
- (IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
// Позже...
- (IBAction)button2Tapped:(id)sender {
self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}
}
Finally, you can simplify the code using typedef .
//typedef для блока
typedef int (^MathBlock)(int, int);
// Создаем свойство, используя typedef
@property (strong) MathBlock mathBlock;
// Метод для хранения блока
- (void)doMathWithBlock:(MathBlock) mathBlock {
self.mathBlock = mathBlock;
}
// Вызываем метод с блоком
- (IBAction)buttonTapped:(id)sender {
[self doMathWithBlock:^(int a, int b) {
return a + b;
}];
}
// Позже...
- (IBAction)button2Tapped:(id)sender {
self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)];
}
Blocks and completion
Last tip. When you use a method that takes a block, Xcode can autocomplete the block for you, saving you time and preventing possible errors.
For example, enter:
NSArray * array;
[array enum
The autocomplete program will find enumerateObjectsUsingBlock - press Enter to autocomplete. Then press Enter again to autocomplete the block. The result is this:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
code
}
You can write your code instead of code , close the method call, and voila - much easier than typing everything manually.
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// Do something
}];
Where to go next?
The project code can be downloaded here . If you are familiar with git, you can take the project from here with commits at every step. I hope that in the process of creating this simple application you could feel the simplicity and power of the blocks, and you got some ideas for using them in your projects.
If this was your acquaintance with the blocks - you have taken a huge step. Blocks need to be learned to work with multithreading, network and more.
More information about the blocks can be found here: