
Welcome to the Objective-C Intro Course.
We will be learning Objective-C by creating an Adventure game.
We'll see the differences and similarities between Objective-C and Swift.
Though Swift is the future, knowing Objective-C will help make you a much better developer since you may still need to deal with it when using third-party code or even some of Apple's frameworks.
Welcome to the Adventure Game. We will build this in Objective-C. This will give us a chance to become familiar with the language.
Create a new project: New Project-> iOS Application-> Single View Application
Product Name: Pirate Adventure Game
Language: Objective-C
Though we can mix both languages, it's better to stick to only one if possible. Sometimes you **have** to mix them, for example if you have a Swift app that uses a third-party Objective-C library.
Create a git repository when asked.
The first thing you will notice is that there are 2 files for both AppDelegate and ViewController whereas in Swift we have only 1 for each. This is because files in Objective-C always comprise a header file, with extension `.h` and an implementation file, with header `.m`.
The header file is public by default whereas the implementation file is private.
This means that any code we put into the header file will be accessible to all other files in our app, but code in the implementation file will only be accessible to that file.
You will also have to import files manually. In Swift, files get imported automatically, not so in Objective-C.
---
Final code can be found on the Github Page (https://github.com/BitFountain/Objective-C-AdventureGame/archive/master.zip).
In this lecture we will be setting up the storyboard.
Download the Assets and import them into our project by dragging the files into `Assets.xcassets`.
Set the size in the storyboard to Compact/Regular.
Change the background color to: RGB 238, 215, 161.
Add a UIImageView and resize it in the Size Inspector to 374 x 667.
Set the image in the Attributes Inspector to map.
Add 4 UIImageViews to the top of the storyboard. They should be around 56 x 50 in size.
The images should be heart, fist, sword and helmet, in that order.
Add 4 labels that will reside beneath each image. The default text will be Health, Damage, Weapon, Armor.
Add another label. Make it bigger and change its background color to white and Light Grey Color for the font.
Add a button with the title Action. Change the background color to a brown that looks good with the rest of the palette. Change the font color to white.
Add an image and place it next to the button. The image should be the same height of the button.
Add an image for the compass rose and 4 buttons, one for each North, East, South, West.
Add a button for reset. Red background, white text.
Add an image at the bottom for the Bitfountain whale.
In this lecture we will add `IBOutlets` for our UI elements.
We can make connections to both the header and implementation files. But the better way to do it is to add them to the implementation file so that only that class is responsible for its view. Remember that adding them to the header file will make them public, hence accessible from any other class in your app. This might also lead to potential naming conflicts.
Option-Click the ViewController.m file so it will show side by side with the storyboard.
To create the connections you need to Ctrl-Drag from the storyboard UI element to the ViewController.m file but only between the `@interface` and `@end` block. This is where we add private properties for that class.
Outlet syntax:
`@property` means we are declaring a property.
`weak` and `nonatomic` are options of this property. `weak` means the memory retention will be weak, it is a *weak pointer*, Xcode will not need to hold on to this property in memory once its parent is released.
`nonatomic` makes this property thread-safe.
`IBOutlet` is for the compiler to know this is coming from the storyboard.
`UILabel` is the type or class of the outlet.
`*` is a pointer, then the name of the property `healthLabel`. The pointer says that it points to where the label is stored in memory. In Swift we don't have to worry about pointers, they are handled automatically for us, but in Objective-C we have to tell Xcode when we want to use a pointer.
All lines in Objective-C end with a semicolon. If you don't add them, you will get an error.
Code:
@property (weak, nonatomic) IBOutlet UILabel *healthLabel;
@property (weak, nonatomic) IBOutlet UILabel *damageLabel;
@property (weak, nonatomic) IBOutlet UILabel *weaponLabel;
@property (weak, nonatomic) IBOutlet UILabel *armorLabel;
@property (weak, nonatomic) IBOutlet UILabel *storyLabel;
@property (weak, nonatomic) IBOutlet UIButton *actionButton;
@property (weak, nonatomic) IBOutlet UIButton *northButton;
@property (weak, nonatomic) IBOutlet UIButton *westButton;
@property (weak, nonatomic) IBOutlet UIButton *southButton;
@property (weak, nonatomic) IBOutlet UIButton *eastButton;
@property (weak, nonatomic) IBOutlet UIImageView *itemImageView;
In this lecture we will setup `IBActions` for our buttons.
We will add them to the implementation file.
Notice that the `IBActions` are added below the `IBOutlets`. We add them to the implementation section of our implementation file, the block of code between `@implementation` and `@end`.
We can add `IBActions` into the public section (the header file) if you wanted it to be called by other classes outside of this file.
IBAction syntax:
IBAction is a hint to the compiler that this is a connection from the storyboard. `(IBAction)` is the same as writing `(void)` meaning that the function does not return anything.
The method begins with a dash `-` then we specify the return type inside parenthesis, then the name of the method, a colon and then the parameter type inside parenthesis as well, the pointer character `*` (asterisk) if appropriate, and then the parameter name. So, it's almost the reverse of Swift.
Code:
-(IBAction)actionButtonPressed:(UIButton *)sender {
}
-(IBAction)northButtonPressed:(UIButton *)sender {
}
-(IBAction)westButtonPressed:(UIButton *)sender {
}
-(IBAction)southButtonPressed:(UIButton *)sender {
}
-(IBAction)eastButtonPressed:(UIButton *)sender {
}
-(IBAction)resetButtonPressed:(UIButton *)sender {
}
We've got our UI set up so now let's start delving into Objective-C. We'll start real easy, we'll create a class with two attributes.
File-> New File-> iOS Source-> Cocoa Touch Class
Unlike Swift, we usually want to subclass NSObject when we are creating our own classes.
Class: Weapon
Subclass of: NSObject
Language: Objective-C
Notice that creating a new class creates both the header and the implementation file for us.
Let's put these files into a new Group named *Model*.
We will create the properties in the header file so they are public and we can get access to them from other classes.
### The class declaration syntax:
@interface Weapon : NSObject
Apart from the `@interface`, the declaration is very similar to Swift, where we would replace `@interface` with `class`.
The first property we will create is the `name` for our weapon:
@property (strong, nonatomic) NSString *name;
Objective-C is a superset of C, meaning you get access to both languages simultaneously.
Let's now setup an integer property and see the difference.
@property (nonatomic) int damage;
Both property declarations inside the class:
Code:
@interface Weapon : NSObject
@property (strong, nonatomic) NSString *name;
@property (nonatomic) int damage;
@end
Let's not create the Armor class now.
File-> New File-> iOS Source-> Cocoa Touch Class
Class: Armor
Subclass of: NSObject
Language: Objective-C
`Armor.h` should look like this after we add the two properties:
Code:
@interface Armor : NSObject
@property (strong, nonatomic) NSString *name;
@property (nonatomic) int health;
@end
Let's create a Boss model.
File-> New File-> iOS Source-> Cocoa Touch Class
Class: Boss
Subclass of: NSObject
Language: Objective-C
Code:
@interface Boss : NSObject
@property (nonatomic) int health;
@property (nonatomic) int damage;
@end
Let's create a Character class now.
File-> New File-> iOS Source-> Cocoa Touch Class
Class: Character
Subclass of: NSObject
Language: Objective-C
The first thing we need to do is import the `Armor` class. We'll use quotes instead of angle brackets to import the file:
import "Armor.h"
The difference between the angle brackets and the quotes is that the look up paths are different. One is the *global include path* and the other is relative to the local file. The rule of thumb is that we use angle brackets `< >` for iOS Foundations and quotes `" "` for our own files.
Also note that we import only the header file, because we only care about the public declaration of `Armor` and `Weapon`.
Also note that the header gets imported by its corresponding implementation file. So the `Character.m` file implementation you will see the header import as:
import "Character.h"
Remember that, though Swift does these imports automatically for us (imports of class declarations since there are no header files), Objective-C does not and we have to do them manually.
Code:
#import <Foundation/Foundation.h>
#import "Armor.h"
#import "Weapon.h"
@interface Character : NSObject
@property (nonatomic) Armor *armor;
@property (nonatomic) Weapon *weapon;
@property (nonatomic) int damage;
@property (nonatomic) int health;
@end
`Armor` is an object type, it's an instance of the `Armor` class we created, so it requires the `*` pointer character.
Same for `Weapon`.
`damage` and `health` are primitives.
We now need to create the Tile class. This will represent the virtual tile that we will be able to move to.
File-> New File-> iOS Source-> Cocoa Touch Class
Class: Tile
Subclass of: NSObject
Language: Objective-C
Code:
import <Foundation/Foundation.h>
import <UIKit/UIKit.h>
import "Weapon.h"
import "Armor.h"
@interface Tile : NSObject
@property (strong, nonatomic) NSString *story;
@property (strong, nonatomic) UIImage *itemImage;
@property (strong, nonatomic) NSString *actionButtonName;
@property (strong, nonatomic) Weapon *weapon;
@property (strong, nonatomic) Armor *armor;
@property (nonatomic) int healthEffect;
@end
Note that we'll need to import `UIKit` in order for the file to recognize `UIImage`. Since it's an iOS API, we import it with the global path angle brackets.
Time now to create a factory to handle the creation of the different instances we'll need.
Create a new group named Factory.
File-> New File-> iOS Source-> Cocoa Touch Class
Class: Factory
Subclass of: NSObject
Language: Objective-C
Code:
import <Foundation/Foundation.h>
import "Boss.h"
@interface Factory : NSObject
+(Boss *)createBoss;
@end
Note the `+` in the `createBoss` method. `+` is for class methods (what Swift would call Type Methods) and `-` is for instance methods.
With a class method, we do not need to create an instance of `Factory` to call `createBoss`.
This method doesn't have an implementation yet, we haven't written anything in the `.m` file so we'll do that now. In the `Factory.m`:
Code:
+(Boss *)createBoss {
Boss *boss = [[Boss alloc] init];
boss.health = 65;
return boss;
}
Instantiation syntax:
Let's create a character instance now.
Code:
import "Character.h"
@interface Factory : NSObject
+(Character *)createCharacter;
@end
We'll now need to implement this method. In `Factory.m`:
Code:
+(Character *)character
{
Character *character = [[Character alloc] init];
character.health = 100;
Armor *armor = [[Armor alloc] init];
armor.name = @"Cloak";
armor.health = 5;
character.armor = armor;
Weapon *weapon = [[Weapon alloc] init];
weapon.name = @"Fists";
weapon.damage = 10;
character.weapon = weapon;
character.damage = 0;
return character;
}
When creating a string value for a variable, we need to prepend it with the `@` sign. In Swift, we only need the quotes, in Objective-C we need `@"string literal"`.
Note that you can also import files directly into the `.m` files but if you've already imported them in the `.h` there is no need to reimport them.
In this lecture we will create a class method that will create tiles. These will be instances of the Tile class we created earlier.
We'll use the tiles to encapsulate all the data necessary to update our UI.
In `Factory.h`
Code:
+(NSArray *)createTiles;
There are two array types in Objective-C, a changeable array (we can add and remove instances from it) and a non-changeable array. The changeable array is called `NSMutableArray`.
If you don't need to add or remove items from an array, use use `NSArray` instead of `NSMutableArray`.
In `Factory.m`
Code:
import "Tile.h"
+(NSArray *)createTiles {
Tile *tile1 = [[Tile alloc] init];
tile1.story = @"As the mightiest heroine in these parts, we need you to undertake a perilous journey and defeat the Mega Boss. How about a useless, rusty sword to get started?";
tile1.itemImage = [UIImage imageNamed:@"sword"];
Weapon *bluntedSword = [[Weapon alloc] init];
bluntedSword.name = @"Rusty Sword";
bluntedSword.damage = 12;
tile1.weapon = bluntedSword;
tile1.actionButtonName = @"Take the sword";
tile1.healthEffect = 0;
// to make the error go away
NSArray *tiles = [[NSArray alloc] init];
return tiles;
}
Note that we are importing `Tile.h` in the implementation (`.m`) file and not the header file. This is because we are only using the Tile class inside this implementation file. So you want to keep your imports as clean as possible, only in the files that actually use them.
Let's create some more tiles.
Code:
Tile *tile2 = [[Tile alloc] init];
tile2.story = @"You have come across an armorer. Your flimsy moth-eaten cloak won't be much use, how about a nice set of steel armor? Don't worry, it's free, cause you're so awesome.";
tile2.itemImage = [UIImage imageNamed: @"armor.png"];
Armor *steelArmor = [[Armor alloc] init];
steelArmor.name = @"Steel armor";
steelArmor.health = 8;
tile2.armor = steelArmor;
tile2.actionButtonName = @"Take armor";
tile2.healthEffect = 0;
Tile *tile3 = [[Tile alloc] init];
tile3.story = @"There is a mysterious looking old dude in the distance. He looks shifty and momma always said not to talk to strangers, but we’re lost and our medieval GPS is broken. Better stop and ask for directions. We have our rusty sword anyway, right?";
tile3.itemImage = [UIImage imageNamed:@"druid.png"];
tile3.healthEffect = 12;
tile3.actionButtonName = @"Ask Dude";
NSMutableArray *firstColumn = [[NSMutableArray alloc] init];
[firstColumn addObject:tile1];
[firstColumn addObject:tile2];
[firstColumn addObject:tile3];
// update the tiles array
NSArray *tiles = [[NSArray alloc] initWithObjects:firstColumn, secondColumn, nil];
The `firstColumn` array will be inside the `tiles` array.
`nil` at the end of the array tells `NSArray` to stop adding items.
Tiles 7 through 9 for our third column.
Code:
Tile *tile7 = [[Tile alloc] init];
tile7.story = @"What is that on the horizon? Looks like a walking Shark with a laser! Should we attack it?";
tile7.itemImage = [UIImage imageNamed: @"shark.png"];
tile7.healthEffect = 8;
tile7.actionButtonName = @"Turn It Into Sushi!";
Tile *tile8 = [[Tile alloc] init];
tile8.story = @"Oh no! The terror of the skies, the Wyvern, has seen us, it’s swooping for us. Run, run!";
tile8.itemImage = [UIImage imageNamed: @"wyvern.png"];
tile8.healthEffect = -46;
tile8.actionButtonName = @"Run Away";
Tile *tile9 = [[Tile alloc] init];
tile9.story = @"What is this, another treasure chest? Let's open that baby and reap the spoils.";
tile9.itemImage = [UIImage imageNamed: @"treasure.png"];
tile9.healthEffect = 20;
tile9.actionButtonName = @"Take Treasure";
NSMutableArray *thirdColumn = [[NSMutableArray alloc] init];
[thirdColumn addObject:tile7];
[thirdColumn addObject:tile8];
[thirdColumn addObject:tile9];
// update tiles array
NSArray *tiles = [[NSArray alloc] initWithObjects:firstColumn, secondColumn, thirdColumn, nil];
Let's create our last 3 tiles for our last column.
Code:
Tile *tile10 = [[Tile alloc] init];
tile10.story = @"A group of no-good bandits attempts to steal your hard-earned booty.";
tile10.itemImage = [UIImage imageNamed: @"bandit.png"];
tile10.healthEffect = -15;
tile10.actionButtonName = @"Show Them Who's Boss";
Tile *tile11 = [[Tile alloc] init];
tile11.story = @"You found a placid lake. Looks benign, but you never know what perils might be hiding ‘neath the quiet surface.";
tile11.itemImage = [UIImage imageNamed:@"lake"];
tile11.healthEffect = -7;
tile11.actionButtonName = @"Swim Lake";
Tile *tile12 = [[Tile alloc] init];
tile12.story = @"This is the culmination of your training and your exploring, the moment of truth, what you were made for: Face off against the Mega Chicken Boss!";
tile12.itemImage = [UIImage imageNamed: @"chicken.png"];
tile12.healthEffect = -15;
tile12.actionButtonName = @"Fight!";
NSMutableArray *fourthColumn = [[NSMutableArray alloc] init];
[fourthColumn addObject:tile10];
[fourthColumn addObject:tile11];
[fourthColumn addObject:tile12];
// update the tiles array one last time
NSArray *tiles = [[NSArray alloc] initWithObjects:firstColumn, secondColumn, thirdColumn, fourthColumn, nil];
Now that we have our tile grid set up, we will use to allow the user to navigate to these different tiles in search of adventure.
To start setting up our game, we will add a helper function to the project.
In `ViewController.m`.
Code:
import "Character.h"
import "Boss.h"
import "Factory.h"
@property (strong, nonatomic) NSArray *tiles;
@property (strong, nonatomic) Character *character;
@property (strong, nonatomic) Boss *boss;
-(void) setupGame {
self.tiles = [Factory createTiles];
self.character = [Factory createCharacter];
self.boss = [Factory createBoss];
}
We will call this method inside of `viewDidLoad` to start setting up the game.
Code:
// inside viewDidLoad()
[self setupGame];
The brackets indicate that we are sending a message, which is Objective-C's way of saying calling a method. We are sending the `setupGame` message to the `self` objects. I know this sounds confusing, but all we're really doing is calling the `setupGame` method from this instance of the view controller (`self`).
Note that we are using Class methods so that we don' have to create instances of the Factory class just to call these setup methods.
Two different ways to access properties
Note that we can call the properties with an underscore before their name (`_tile`) and this would avoid any *getters* or *setters* we might have set up for it. You are accessing the instance variable directly.
If we use `self` before the property (`self.tile`) then, if you had added *getters* and *setters*, they **would** be called.
99% of the time you want to use `self` before the variable name.
We'll use a `CGPoint` to track where the user is in our tile grid. This is because a `CGPoint` will allow us to store `X` and `Y` points so we can simulate a grid, so, for example, the grid starts at 0,0 and if the user moves 1 tile to the right that would be 1,0, etc.
Code:
@property (nonatomic) CGPoint currentPoint;
// inside setupGame()
/* Setup the initial point in our coordinate system as 0,0. */
self.currentPoint = CGPointMake(0, 0);
Note that `CGPoint` is a primitive type so we don't use a pointer to it because it doesn't create an object, it accesses the value directly in memory. `CGPoint` is actually a struct and, unlike Swift, they are not objects.
We will now add a method to Character that will take care of updating its attributes.
We will create the method inside de `Character` class because all it does is update the character instance so it makes more sense to put it in there as opposed to in ViewController or elsewhere.
In `Character.h`.
Code:
-(void) calculateAttributesForArmor:(Armor *)armor withWeapon:(Weapon *) weapon withHealthEffect:(int) healthEffect;
This method takes 3 parameters, `armor`, `weapon` and `healthEffect`. The first 2 are pointers to objects, the last one is a primitive (`int`) so it's not a pointer.
However, unlike Swift, this method's name is not `calculateAttributesForArmor`. In Objective-C, You read the method names with the parameter names. So this method would be called: `calculateAttributesForArmor withWeapon withHealthEffect`.
In `Character.m`.
Code:
-(void) calculateAttributesForArmor:(Armor *)armor withWeapon:(Weapon *) weapon withHealthEffect:(int) healthEffect {
if (armor != nil) {
self.health = self.health - self.armor.health + armor.health ;
self.armor = armor;
return;
}
if (weapon != nil) {
self.damage = weapon.damage;
self.weapon = weapon;
return;
}
if (healthEffect != 0) {
self.health = self.health + healthEffect;
return;
}
self.health = self.health + self.armor.health;
self.damage = self.damage + self.weapon.damage;
}
There are no optionals in Objective-C, we have to test for `nil`. But C-types or primitives don't have `nil` values, you need to use `null` for them, which is not the same a `nil`. So for `healthEffect` we use `0` instead of `nil`.
Also, note that we need to use parentheses to encapsulate the conditions of an if statement.
Don't be confused between `self.armor.health` and `armor.health`. The first one is the attribute or property we set up (hence the `self` keyword), and the second one is the argument passed in the method. Maybe we should have named them differently to avoid this confusion, for example, we could have named the second one `incomingArmor` or something like that.
In `ViewController.m`.
Code:
[self.character calculateAttributesForArmor:nil withWeapon:nil withHealthEffect:0];
Let's now create a helper method which will update the UI for a given tile.
Code:
import "Tile.h"
-(void) updateTile {
self.actionButton.enabled = YES;
Tile *tileModel = [[self.tiles objectAtIndex:self.currentPoint.x] objectAtIndex:self.currentPoint.y];
self.storyLabel.text = tileModel.story;
self.itemImageView.image = tileModel.itemImage;
self.healthLabel.text = [NSString stringWithFormat: @"%i", self.character.health];
self.damageLabel.text = [NSString stringWithFormat: @"%i", self.character.damage];
self.armorLabel.text = self.character.armor.name;
self.weaponLabel.text = self.character.weapon.name;
[self.actionButton setTitle:tileModel.actionButtonName forState: UIControlStateNormal];
}
// inside setupGame
[self updateTile];
In this line: `[[self.tiles objectAtIndex:self.currentPoint.x] objectAtIndex:self.currentPoint.y];` we are accessing a multidimensional array or arrays inside another array.
In this code: `[NSString stringWithFormat: @"%d", self.character.health];` we use `@"%d"` is a token. Think of it as a placeholder for the variable value that is coming next. So `%d` is for numbers. We could use `%i` which is for integers, but you'll see `%d` used more often because it will accept doubles or integers, so it's more convenient to use because we don't have to check if the value is an integer or a double.
Also note that, unlike Swift, `UIControlStateNormal` does not have a period.
Run the app and let's see what we have so far.
Let's create another helper method. We'll use this for the navigation buttons. We want our user to stay within the grid.
Code:
-(void) updateButtons {
}
// inside setupGame
[self updateButtons];
Take a stab, if you wish, as to how we could keep the user only inside our grid. We'll see how to do it in the next lecture.
Let's implement the `updateButtons` method.
We're going to create a new class to abstract our code and make it more modular.
Create a new group called Brain.
Create a new file called `GameBrain`.
File-> New File-> iOS Source-> Cocoa Touch Class
Class: GameBrain
Subclass of: NSObject
Language: Objective-C
Code:
import <UIKit/UIKit.h>
-(BOOL)tileExistsAtPoint:(CGPoint)point forTiles: (NSArray *) tiles;
// in GameBrain.m
-(BOOL)tileExistsAtPoint:(CGPoint)point forTiles: (NSArray *) tiles {
if (point.y >= 0 && point.x >= 0 && point.x < [tiles count] && point.y < [[tiles objectAtIndex:point.x] count]) {
return YES;
} else {
return NO:
}
}
This method will tell us whether a tile exists at the point we want, by returning `true` of `false`.
We use the array count to make sure we stay within the grid range.
Let's import the `GameBrain` class into `ViewController` and create a property for it.
Code:
import "GameBrain.h"
@property (strong, nonatomic) GameBrain *gameBrain;
// inside the setupGame method
self.gameBrain = [[GameBrain alloc] init];
// update the updateButtons method
-(void) updateButtons {
self.westButton.hidden = ![self.gameBrain tileExistsAtPoint:CGPointMake(self.currentPoint.x - 1, self.currentPoint.y) forTiles:self.tiles];
self.eastButton.hidden = ![self.gameBrain tileExistsAtPoint:CGPointMake(self.currentPoint.x + 1, self.currentPoint.y) forTiles:self.tiles];
self.northButton.hidden = ![self.gameBrain tileExistsAtPoint:CGPointMake(self.currentPoint.x, self.currentPoint.y + 1) forTiles:self.tiles];
self.southButton.hidden = ![self.gameBrain tileExistsAtPoint:CGPointMake(self.currentPoint.x, self.currentPoint.y - 1) forTiles:self.tiles];
}
The exclamation mark `!` negates the statement that follows it, so it's like saying: "do the opposite of this". In this case, since the `tileExistsAtPoint` method returns true or false, we will actually set the `hidden` property to that opposite of what it returns. So if the tile exists, we set hidden to false because we **don't** want to hide the button.
The tricky thing here is that we're setting the `hidden` property directly to the result of the `tileExistsAtPoint` method. We could have written the method to return false if the point existed instead of true but that would be even more confusing.
Run the app. If we've done everything correctly, the South and West buttons should be hidden because we can't go in those directions.
Let's figure out how we can navigate to different tiles.
Code:
-(IBAction)northButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x, self.currentPoint.y + 1);
[self updateButtons];
[self updateTile];
}
-(IBAction)westButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x - 1, self.currentPoint.y);
[self updateButtons];
[self updateTile];
}
-(IBAction)southButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x, self.currentPoint.y - 1);
[self updateButtons];
[self updateTile];
}
-(IBAction)eastButtonPressed:(UIButton *)sender {
self.currentPoint = CGPointMake(self.currentPoint.x + 1, self.currentPoint.y);
[self updateButtons];
[self updateTile];
}
Run the app and make sure you can navigate in the adventure.
Let's implement the `actionButtonPressed` now.
Code:
-(IBAction)actionButtonPressed:(UIButton *)sender {
Tile *tile = [[self.tiles objectAtIndex:self.currentPoint.x] objectAtIndex:self.currentPoint.y];
/* Call the method updateCharacterStatsForArmor so that we can use the armor, weapon or health effect on our tile. */
[self.character calculateAttributesForArmor:tile.armor withWeapon:tile.weapon withHealthEffect:tile.healthEffect];
/* After we perform an action we call the method updateTile so that our view will update with a new weapon, armor or health stat. */
[self updateTile];
self.actionButton.enabled = NO;
}
Set the lines in the Story label to 0 so that they are handled automatically. And enlarge and center the other labels as needed.
Let's implement the reset button.
Code:
-(IBAction)resetButtonPressed:(UIButton *)sender {
self.character = nil;
self.boss = nil;
[self setupGame];
}
We're resetting the character and boss also though it's not technically necessary because their value changes. It's still nice to be explicit with these types of things.
Run the app and make sure the reset button works.
Let's see if we can figure out the final boss fight!
We'll add a new method to `GameBrain`.
Code:
import "Character.h"
import "Boss.h"
-(void)declareWinnerForCharacter: (Character *) character andBoss: (Boss *) boss;
// in GameBrain.m
-(void)declareWinnerForCharacter: (Character *) character andBoss: (Boss *) boss {
while (boss.health > 0) {
int bonusDamage = arc4random_uniform(5);
boss.health = boss.health - character.damage - bonuseDamage;
// We keep whittling the character's health while the boss' health is greater than 0
character.health = character.health - boss.damage;
// If the character dies before we kill the boss, we return immediately with the boss as winner
if (character.health <= 0) {
break;
} else if (boss.health <= 0){
break;
}
}
}
In `Factory.m` we need to give our boss some initial damage.
Code:
// add this line to the createBoss method
boss.damage = 15;
Next we will figure out how to make this method work since it doesn't return anything.
Let's figure out how to declare a winner by using the information from the `declareWinnerForCharacter`.
We will do this with a protocol and a delegate.
Code:
@protocol GameBrainDelegate <NSObject>
@required
-(void)playerDidWin:(BOOL)playerWon;
@end
@property (nonatomic, weak) id <GameBrainDelegate> delegate;
We need the `@required` keyword because methods can be optional in Objective-C protocols.
Note that we add `weak` to the memory allocation of `delegate`. We don't want to create a pointer for it, we just want a reference.
Note: adding `strong` to a primitive does not create a pointer to it, it adds to the retain count.
In the video it states that "adding strong will create a pointer to the delegate id". This is wrong, it will increase the retain count but will not create a pointer. A pointer is created with the use of `*` for pointers to objects. It was a little slip of the tongue :)
In `GameBrain.m`.
Code:
// the if statement should now look like this
if (character.health <= 0) {
[self.delegate playerDidWin:NO];
break;
} else if (boss.health <= 0){
[self.delegate playerDidWin:YES];
break;
}
Let's not conform to the delegate we just created.
In `ViewController.m`.
Code:
// this says that the ViewController instance now conforms to the GameBrainDelegate protocol
@interface ViewController () <GameBrainDelegate>
// inside setupGame
// we set the delegate as this instance of ViewController
self.gameBrain.delegate = self;
-(void)playerDidWin:(BOOL)playerWon{
if (playerWon == YES) {
} else {
}
}
We'll now add a helper method to show an alert for when the player dies or wins the fight against the boss.
Code:
-(void) showAlertWithTextforHeader: (NSString *) header withMessage:(NSString *) message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:header message:message preferredStyle: UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:okAction];
[self presentViewController:alert animated:true completion:nil];
}
// update the playerDidWin method to look like this:
-(void)playerDidWin:(BOOL)playerWon{
NSLog(@"playerDidWin %i", playerWon);
if (playerWon == YES) {
[self showAlertWithTextforHeader:@"Victory Message" withMessage:@"You have defeated the evil boss"];
} else {
[self showAlertWithTextforHeader:@"Death Message" withMessage:@"You're dead! Please restart the game!"];
}
}
// inside actionButtonPressed
// if we're on the boss tile, then let's see who wins that fight
if (self.currentPoint.x == 3 && self.currentPoint.y == 2) {
[self.gameBrain declareWinnerForCharacter:self.character andBoss:self.boss];
} else {
Tile *tile = [[self.tiles objectAtIndex:self.currentPoint.x] objectAtIndex:self.currentPoint.y];
[self.character calculateAttributesForArmor:tile.armor withWeapon:tile.weapon withHealthEffect:tile.healthEffect];
/* After we perform an action we call the method updateTile so that our view will update with a new weapon, armor or health stat. */
[self updateTile];
self.actionButton.enabled = NO;
}
Run the app and let's test the game. See if you can defeat the evil boss!
// 1
In Objective-C, `nil` and `null` are not the same thing. In some languages, they are interchangeable or one of them simply doesn't exist. Objective-C is a superset of C so it has both nil and null and there is a difference between them.
// 2
The rule of thumb is that `nil` should be used for pointers to an object and `null` for non-object pointers. But what does this mean really?
`nil` is actually 0 as an `id` which is a special type in Objective-C that can be of any type, an id is like a wildcard for data type. Sort of the equivalent of AnyObject in Swift (except id can be applied to non-objects such as primitives as well).
`null` is 0 as a void return type.
// 3
(examples)
// 4
You can further distinguish them by thinking that:
`nil` is usually used for an Objective-C object type.
`NULL` is used for c-style pointers (void *).
Yet another difference is that you can send messages to `nil`, whereas you can't send messages to `null`. However the messages sent to nil will not have any effect but you won't get an error when sending them.
// 1
We used to have to manage memory retention and allocation not too long ago when writing programs. This is something that we don't need to do in Swift for example, it is done for us automatically.
With `strong` and `weak`, we are telling Xcode how we want it to handle the memory retention for a particular object.
In that past, if a method started with the keywords `init`, `new` or `create`, at some point we had to issue a `release` for this object or it would hang around forever and you would get memory leaks.
As I said, we don't have to worry about it these days.
// 2
A strong reference means Xcode will hold on to this pointer in memory as long as it is not set to nil. So the object is not destroyed in memory until set to nil, either explicitly by us or by any other means. But as long as this object has a value, other than nil, there will be a space for it in memory.
// 3
With weak, the compiler knows not to hold on to this object in memory. The object is only held when another object holds a `strong` reference to it. Once that is not the case, once all objects that held a strong reference are no longer in memory, then this object is automatically destroyed.
// [All paragraphs] (slide 2)
In Objective-C we have to specify what type of memory retention scheme we want to use whenever we create objects. For example in a this `property`, which is an instance of `NSString`, meaning it's an object, we are saying we want a `strong` reference to this object, we don't want the compiler to destroy this object until we say so or until its parent is destroyed, in this case, the parent would be wherever we created the instance of this object.
// 1 (slide 3)
By contrast, when we create IBOutlets for our UI elements in the storyboard, we create them as weak and let Xcode destroy them when their controller is deallocated from memory.
// 3
This outlet is only useful as long as we are in that particular controller which is managing a view. Once that controller gets destroyed from memory or *deallocated*, then there is no need to hold on to the memory for the UI element so we declare it as `weak` and let the compiler destroy it when appropriate.
// 1 (slide 4)
There is a great analogy to this on this StackOverflow post where they use balloons to explain the difference between `weak` and `strong`.
// 1
In programming, properties need to have methods to access them. They usually need a method to `set` their value and a method to `get` their value. These are known as `setters` and `getters` respectively. In the old days, we used to have to write these methods ourselves but nowadays they are created automatically, under the hood. This is true for both Swift and Objective-C.
`atomic` and `nonatomic` refer to *thread safety*. *Thread safety* simply means that if we are running multiple threads of our application, you can think of it as multiple internal copies of the application running at the same time, then `atomic` and `nonatomic` refer to whether these properties are safe to use while running multiple threads.
// 2
By default, properties are `atomic`, this means they are safe to run on multiple threads because a lock is created whenever they are being **set** or their value is being returned (**get**) so that another thread cannot override its value at the same time. This is a good thing when we are doing lots of operations on multiple threads because you don't want a property changing it's value when you're trying to see what that value is or, worse, when you're trying to change it yourself.
// 3
Why is this important? To us it's not, really. Not right now. But we need to know what it is. Because `nonatomic` provides better performance since the compiler doesn't have to check for locks, etc. It can simply set or get the value of the property. This leads to faster code. Apple uses `nonatomic` for almost all of their properties in the iOS Frameworks. And we should do the same.
// 4
If you're an expert in threads and know when to use `atomic`, go for it. If you don't then stick with `nonatomic` for your properties.
// 1
In Objective-C, there is a distinction between the types we create. Some are objects and some are primitives, or *scalar types*, as they are officially known.
We are talking about distinctions between integers and strings, etc. here, what we could call basic types. Swift doesn't have this distinction at all.
// 2
If you google the difference between objects and primitives in Objective-C, you'll often see discussions about memory allocation. And this is not necessarily false, it just doesn't paint the whole picture. I will explain in a moment, but the most important distinction between them is not memory allocation really, it is their use.
Primitives cannot be passed as objects without conversion first. Now, why would we care about this? Mainly because a lot of the methods in iOS expect objects and if you pass them a primitive, you will get an error.
For example, arrays in Objective-C only take objects, they cannot store primitives. So if you have a bunch of primitive integers that you want to put into an NSArray, you will get an error unless you cover them first.
What about the memory thing? OK, so the main distinction in memory allocation is that objects require a pointer to a location in the *heap* whereas primitives require no pointer because they are allocated directly in the *stack*. But the truth is that primitives can also be allocated in the *heap* if you use *malloc*. But, seriously, we don't care about any of that right now.
If all this talk of *heap* vs *stack* makes sense to you, great, if it doesn't then don't worry about it. The important thing to understand here is the syntax and the use of each type.
// 3
Primitives cannot receive messages either, whereas objects can.
// 2 (slide 3)
When we instantiate a class or struct we are actually creating an object of that type. A simple example is `NSString` to create a string. This is an instance of the NSString class in Objective-C, so it's an object.
// 3
The rule of thumb in Objective-C is that if it begins with `NS` then it’s definitely an object because it’s coming from NextStep’s internal classes.
NextStep is the unix-based operating system that Apple bought to base OS X and iOS on. Incidentally, it's also the name of the company that Steve Jobs set up when he was forced out of Apple. Part of the deal to bring him back was that Apple buy NextStep and use it as the basis for their future software.
(refer to notes on Xcode project)
Syntax and Memory allocation when declaring property.
// 1
What do we mean by "sending messages"?
In Objective-C, when you send information to an object, for example, you have a `bike` object and you want to set its `wheels` property to 2, this is called *sending a message*.
// 3
In Swift we would set the property with `dot syntax`:
bike.wheels = 2
// 5
In Objective-C we *send a message* to that object:
[bike wheels : 2]
// 1 (slide 2)
A couple of things to note here:
1. the brackets
The brackets here are used as parentheses, to group commands together, they have nothing to do with arrays, which is where we have seen them before. But we need to use them whenever we send messages or we will get errors.
// 3
2. colon
Instead of use the equals sign to set a property, we use the colon `:` because we are actually passing an argument, we are not setting a property directly.
But where's the message? OK. So in the example above, we are messaging the `bike` object with the `wheels` message and passing `2` as a value. So `bike` is the object and `wheels` is the message which, in this case, happens to be a setter which sets its value to `2`.
Don't worry if you don't quite understand all this. The important thing is that you understand what is happening in that bit of code: we are seeing the `wheels` property to 2. But I do want you to know that this is called sending messages in Objective-C because you will come across this term in lots of documentation, tutorials, etc.
// 4
We can chain multiple commands or send multiple messages if you will, by encapsulating brackets inside of each other. When doing this, the code is executed from the inside out, that is to say that the inside brackets will always be executed first and then it expands outwards like ripples in a pond.
So in this example:
[[bike alloc] init]
We are actually calling `bike alloc` first and then we are calling the `init` method on the result of `bike alloc`.
This is equivalent in Swift of doing something like `bike.alloc.init`. But note that Swift doesn't initialize objects in this manner.
Although Swift is the future of iOS development, Objective-C is still relevant. If you're trying to get a job as an iOS developer, knowing Objective-C is required. Most legacy libraries are written in the language, and you need to know how to interact with them. Learn everything you need to know by building a pirate adventure game through the expert, heartcrafted methods used in all Bitfountain courses.
Most iOS courses these days are teaching Swift. We teach Swift in our intro course too! But we also realize that the days of Objective-C are far from over. If you have a little experience coding, we'll get you building iOS 9 apps in Objective-C quickly.