Classes

Basics

As we wrote in Introduction Objective-C classes, as in other object-oriented programming language, provide the blueprint for creating objects.

Objective-C separates class’s description from its internals details. Description is about the public properties and methods of a class and is known as interface. Details defines the code that actually makes properties and methods work and are known as implementation.

In this tutorial we cover the following topics


Experiment 1: how to start

  1. Create a new project class_001 as we did in Introduction. You should see autogenerated main.m file with the following content
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
  2. Select File | New | New… to create a new class Foo with the following steps

    1. Select File | New | New… from main menu
      class_001_create_new_class_001_main_menu
    2. Select type of a new file
      class_001_create_new_class_002_select_type_01

      class_001_create_new_class_003_select_type_02

    3. Add the name for the class
      class_001_create_new_class_004_add_name_01

      class_001_create_new_class_006_add_name_02

    4. Select destination folder
      class_001_create_new_class_007_select_destination_folder
    5. Enjoy the result
      class_001_create_new_class_008_done

  3. Practise simple project’s files navigation

    1. Find some files related with curently edited file.
      class_001_simple_project_navigation_001
    2. Change the file curently beeing edited.
      class_001_simple_project_navigation_002
    3. class_001_simple_project_navigation_003

    4. Open Assistant editor to edit more than one file at once.
      class_001_simple_project_navigation_switch_to_assistant_editor_001
    5. Again find some related files.
      class_001_simple_project_navigation_switch_to_assistant_editor_002
    6. class_001_simple_project_navigation_switch_to_assistant_editor_003

    7. Open more edit panels.
      class_001_simple_project_navigation_switch_to_assistant_editor_004
    8. class_001_simple_project_navigation_switch_to_assistant_editor_005

  4. At the end of the previous step you should be able to see all three files: main.m — main project file, Foo.m — implementation of Foo class and Foo.h — definition of Foo class
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject
    
    @end
    
    #import "Foo.h"
    
    @implementation Foo
    
    @end
    

    As we can see an interface is created with the @interface directive, after which come the class and the superclass name, separated by a colon. It is possible to define protected variables inside of the curly braces just after superclass name, but it is not recommended.

    @interface Foo : NSObject{
        // It's not recommended but you can
        // put here an instance variables
    }
    
    @end
    

    Simple explanation for this is that instance variables are often treated as implementation details and therefore should be stored in the .m file instead of the interface.

    The @implementation directive is similar to @interface, except we don’t need to include the super class.

  5. Add some variables to the class and create a class instace.
    @implementation Foo{
        int instanceVariablePrivate;
    }
    
    int instanceVariablePublic;
    
    @end
    
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo = [[Foo alloc] init];
        }
        return 0;
    }
    

    Next in main.m try to get an access to one of the variables either by

            Foo *foo = [[Foo alloc] init];       
            NSLog(@"%d", foo.instanceVariablePrivate);
    

    or

            Foo *foo = [[Foo alloc] init];        
            NSLog(@"%d", foo.instanceVariablePublic);
    

    You cen even try -> operator like

    NSLog(@"%d", foo->instanceVariablePublic);
    

    All options without success as you can compare with the following screen
    class_001_no_access_to_public_variable
    We are not surprised that there is no access to private variables. The question is: Is is possible to have a public variables in Objective-C? Well, yes it is, but it is strongly not recommended. You can do this with @public keyword but you shouldn’t. Instead properties should be used. Just to satisfy ones curiosity, please take a look into the following example but try not to use this approach.

    @interface Foo : NSObject{
        @public int instanceVariablePublic;
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo = [[Foo alloc] init];
            
            NSLog(@"public %d", foo->instanceVariablePublic);
        }
        return 0;
    }
    

    class_001_how_to_create_public_variable

    Let’s focus now on private variables. To get an access to them we need some methods — this would be a topic of the next step.

    The conclusions from above are as follows

    1. You can simply create private variables (instance variables).
    2. There is no access to private variables other than with the help of methodhs from the class.
    3. You can not simply create public variable. You can do this with use @public keyword but it is strongly not recommended.
  6. Simple methods.
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject{
     
    }
    
    - (int) getValueOfPrivateVariable;
    - (void) setValueOfPrivateVariable : (int) newValue;
    
    @end
    
    #import "Foo.h"
    
    @implementation Foo{
        int instanceVariablePrivate;
    }
    
    - (int) getValueOfPrivateVariable {
        return instanceVariablePrivate;
    }
    
    - (void) setValueOfPrivateVariable : (int) newValue {
        instanceVariablePrivate = newValue;
    }
    
    @end
    
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo = [[Foo alloc] init];
            
            [foo setValueOfPrivateVariable:123];
            
            NSLog(@"public %d", [foo getValueOfPrivateVariable]);
        }
        return 0;
    }
    

    and test if it works (should work)
    class_001_first_methods

    Now there is a time for an explanations.

    1. Methods naming conventions.

      Objective-C methods are designed to protect from any ambiguities. As a result, method names are terrible verbose, but undeniably descriptive. You can follow this pattern with the following rules for naming Objective-C methods:

      1. Don’t abbreviate anything.
      2. Explicitly state parameter names in the method itself.
      3. Be descriptive.
    2. Methods declaration.

      Lines started from sequence of the pattern - (return_type) method_name declares (in .h file) a method called method_name which returns value of type return_type

      General method declaration pattern takes the form

      - (return_type) method_name

      for method which takes no parameters, and the form

      - (return_type) method_name:(argument_1_type)argument_1_name
      label_for_argument_2:(argument_2_type)argument_2_name
      label_for_argument_3:(argument_3_type)argument_3_name

      for method which takes one or more parameters (three in this case). The minus sign prepended to the method marks it as an instance method (opposed to a class method marked with plus sign — see next section tutu).

    3. Calling methods.

      To invoke a method we place the object and the desired method in square brackets, separated by a space. Arguments are separated from the method name using a colon:

      [foo getValueOfPrivateVariable]
      [foo setValueOfPrivateVariable:123]
      

      If there is more than one parameter, it comes after the initial argument, following the same pattern. Each parameter is paired with a label, separated from other arguments by a space, and set off by a colon:

      [object method_name: argument_1_value label_for_argument_2:argument_2_value label_for_argument_3:argument_3_value]
      

      It’s a lot easier to see the purpose behind the above naming conventions when you approach it from an invocation perspective. They make method calls read more like a human language than a computer one. For example, compare the following method call from Simula-style languages to Objective-C’s version:

    4. Nested method calls

      Nesting method calls is a common pattern in moder programming languages (Java) or libraries (jQuery). It’s a natural way to pass the result of one call to another. An example of this is a two-step proces of instantiation an object

      Foo *foo = [[Foo alloc] init];        
      

      First, the [Foo alloc] method is called, then the init method is invoked on its return value.

    5. Method overloading
      Objective-C does not support method overloading, so we have to use different method names. The reason for this is that in a dynamically typed language, type information is very often not known until runtime. At runtime, all objects are statically typed as id in Objective-C. Additionally, a core idea in dynamically typed object oriented languages is that we should not care what type an object is as long as it responds to the messages we want to send.

      Luckily there is hope to solve this problem. Remember that the method name includes the method signature keywords (the parameter names that come before the “:”s), so the following are two different methods, even though they both begin with saveToFile:

      -(void) saveToFile:(NSString *)path valueInt:(int)anInt;
      -(void) saveToFile:(NSString *)path valueString:(NSString *)aString;
      

      (the names of the two methods are saveToFile:valueInt: and saveToFile:valueString:).

    6. Private mehods
      The simplest method to get private methods is to add them to the implementation but not the interface. Actuall private methods we can get with extensions: see Classes, part 2: Extensions
  7. Excercise 2.1

    Add minimum three more instance variables. Add minimum three diferent methods (with differnt number of arguments) to operate on these variables and use them.



More details


Experiment 2: properties


In object-oriented world, an object’s properties let other objects inspect or change this object state. Sometimes, when we are extremely lazy, we do this with public variables. In a well-designed object-oriented code, it shouldn’t happen. It shouldn’t be possible to directly access the internal state of an object. Instead, accessor methods (so called getters and setters) are used as an abstraction for interacting with the object’s internal data.

The goal of the @property directive is to make it easy to create and configure properties by automatically generating these accessor methods. What is more important, properties behaves like public instance variables.

  1. Change our code to have a form presented in the following snippets
         
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject{
    }
    
    @property int x;
    
    @end
    
      
    #import "Foo.h"
    
    @implementation Foo{
    }
    
    @end   
    
      
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo = [[Foo alloc] init];
            
            foo.x = 123;
            NSLog(@"public %d", foo.x);
            
            [foo setX:456];
            NSLog(@"public %d", [foo x]);
        }
        return 0;
    }   
    

    and run it
    class_002_properties_001

    Lines

      
            foo.x = 123;
            NSLog(@"public %d", foo.x);
    

    are clear for us and shouldn’t surprise us. We use well known dot notation to get an access to a public member of the class (or it looks like we exactly do this what is the same thing from our point of view).

    On the other side, lines

      
            [foo setX:456];
            NSLog(@"public %d", [foo x]);  
    

    looks strange. There is a call to undefined method setX as well as x and what is more surprising, both calls works. The answer is very simple. The compiler generates a getter and a setter for the x property. The default naming convention is to use the property name as the getter, prefix it with set for the setter, and prefix it with an underscore for the instance variable.

    Say it again: under the hood the compiler (or preprocessor?) generates for us the following changes for every property declared with the @property directive:

    • Introduce instance variable using the property name and underscore prefix as its name. In our case this would be
        
      @interface Foo : NSObject{
          int _x;
      }
      
    • Generates getter using the property name as its name. In our case this would be
        
      - (int)x {
          return _x;
      }
      
    • Generates setter using the property name and set prefix as its name. In our case this would be
        
      - (int)setX:(int)x {
          _x = x;
      }
      

    After declaring the property with the @property directive, we can call these methods as if they were included in our class’s interface and implementation files. Properties accessed with dot notation are translated to the above accessor methods behind the scenes, so for example

      
            foo.x = 123;
    

    code actually calls

      
            [foo setX:456];  
    
  2. The getter= and setter= attributes.

    If we don’t like @property’s default naming conventions, we can change the getter/setter method names with the getter= and setter= attributes. Test the followig code.

         
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject{
    }
    
    @property (getter=myGet, setter=mySet:)int x;
    
    @end
    
      
    #import "Foo.h"
    
    @implementation Foo{
    }
    
    @end   
    
      
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo = [[Foo alloc] init];
            
            [foo mySet:456];
            NSLog(@"public %d", [foo myGet]);
        }
        return 0;
    } 
    

    class_002_properties_custom_accessors_name

  3. We defer explanation of other attributes like nonatomic, strong, weak and copy to the next tutorials. You can find some information here
  4. Excercise 2.2

    The readonly attribute is an easy way to make a property read only. Write a code to test and show how it works.



Experiment 3: constructor methods


There are no constructor methods in Objective-C. Instead, an object is initialized by calling the init method immediately after it’s allocated. This explains why instantiation is always a two-step process: allocate followed by initialize.

  
        Foo *foo = [[Foo alloc] init];

init is the default initialization method, but we can also define our own “versions”. There’s nothing special about custom initialization methods as they are just normal instance methods, except the method name should always begin with init.

  1. Use code with a default initialization method
       
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject{
    }
    
    @property int x;
    @property int y;
    
    - (id)init;
    
    @end
    
     
    #import "Foo.h"
    
    @implementation Foo{
    }
    
    - (id)init{
        self = [super init];
        if (self) {
            _x = arc4random_uniform(100);
        }
        return self;
    }
    
    @end  
    
       
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo = [[Foo alloc] init];
            
            NSLog(@"x=%d y=%d", foo.x, foo.y);
        }
        return 0;
    }
    

    class_003_constructor_default_init

    id type used above is a generic type, something like void but it can hold any kind of object.

  2. Use code with our own initialization method
      
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject{
    }
    
    @property int x;
    @property int y;
    
    - (id)initWithRandomXValue:(int)from toValue:(int)to;
    
    @end 
    
       
    #import "Foo.h"
    
    @implementation Foo{
    }
    
    - (id)initWithRandomXValue:(int)from
                       toValue:(int)to{
        self = [super init];
        if (self) {
            _x = arc4random_uniform((to - from) + 1) + from;
        }
        return self;
    }
    
    @end
    
     
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo = [[Foo alloc] initWithRandomXValue: 1000 toValue:1100];
            
            NSLog(@"x=%d y=%d", foo.x, foo.y);
        }
        return 0;
    }  
    

    class_003_constructor_custom_constructor

  3. If you want you can even try to use init name instead of initWithRandomXValue
       
    ...
    - (id)init:(int)from toValue:(int)to;
    ...
    
     
    ...
    - (id)init:(int)from
       toValue:(int)to{
    ...
    }  
    ...
    
       
            Foo *foo = [[Foo alloc] init: 1000 toValue:1100];
    
  4. Class-level initialization method — see next part of this tutorial.


Experiment 4: class methods and variables


Sometimes we need some properties to be common for all instances of a class or need a method which belongs to the class but not to any particular instances. These are very commonly called “static” properties or methods in other programming languages.

Class method declarations look like instance methods, except they are prefixed with a plus sign instead of a minus sign.

Technically there is no such thing as a class-level variable in Objective-C, but we can “emulate” one by declaring a static variable before defining the implementation.

  1. Copy, paste and analyze the following code
       
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject{
    }
    
    @property int x;
    @property int y;
    
    - (void)setRandomXValue;
    - (void)setRandomXValue:(int)from toValue:(int)to;
    
    + (void)setDefaultRange;
    
    @end
    
       
    #import "Foo.h"
    
    static int _defaultFrom;
    static int _defaultTo;
    
    @implementation Foo{
    }
    
    - (void)setRandomXValue:(int)from
                    toValue:(int)to{
        _x = arc4random_uniform((to - from) + 1) + from;
    }
    
    - (void)setRandomXValue{
        _x = arc4random_uniform((_defaultTo - _defaultFrom) + 1) + _defaultFrom;
    }
    
    + (void)setDefaultRange {
        _defaultFrom = 2000;
        _defaultTo = 2200;
    }
    
    @end
    
       
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [Foo setDefaultRange];
            
            Foo *foo1 = [[Foo alloc] init];
            
            [foo1 setRandomXValue];
            
            NSLog(@"x=%d y=%d", foo1.x, foo1.y);
            
            Foo *foo2 = [[Foo alloc] init];
            
            [foo2 setRandomXValue: 100 toValue: 200];
            
            NSLog(@"x=%d y=%d", foo2.x, foo2.y);
        }
        return 0;
    }
    

    The result should be similar to presented below
    [fulmanp type=”terminal”]

    2016-02-18 23:27:57.779 class_001[5134:544764] x=2024 y=0
    2016-02-18 23:27:57.781 class_001[5134:544764] x=186 y=0
    Program ended with exit code: 0

    [/fulmanp]



Experiment 5: class-level initialization method


If we have to make some initializations before the first class usage, the initialize method is designed for this.

  1. Copy, paste and analyze the following code
       
    #import <Foundation/Foundation.h>
    
    @interface Foo : NSObject{
    }
    
    @property int x;
    @property int y;
    
    - (id)initWithRandomXValue:(int)from toValue:(int)to;
    
    @end
    
       
    #import "Foo.h"
    
    static int _defaultFrom;
    static int _defaultTo;
    
    @implementation Foo{
    }
    
    - (id)initWithRandomXValue:(int)from
       toValue:(int)to{
        self = [super init];
        if (self) {
            _x = arc4random_uniform((to - from) + 1) + from;
        }
        return self;
    }
    
    - (id)init {
        return [self initWithRandomXValue:_defaultFrom
                                  toValue:_defaultTo];
    }
    
    + (void)initialize {
        if (self == [Foo class]) {
            // Makes sure this isn't executed more than once
            _defaultFrom = 2000;
            _defaultTo = 2200;
        }
    }
    
    @end
    
       
    #import <Foundation/Foundation.h>
    #import "Foo.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Foo *foo1 = [[Foo alloc] init];
            
            NSLog(@"x=%d y=%d", foo1.x, foo1.y);
            
            Foo *foo2 = [[Foo alloc] initWithRandomXValue: 1000
                                                  toValue: 1100];
            
            NSLog(@"x=%d y=%d", foo2.x, foo2.y);
        }
        return 0;
    }
    

    The result should be similar to presented below
    [fulmanp type=”terminal”]

    2016-02-18 15:05:19.941 class_001[4937:519806] x=2143 y=0
    2016-02-18 15:05:19.944 class_001[4937:519806] x=1072 y=0
    Program ended with exit code: 0

    [/fulmanp]




Excercise 2.0


Create a project according to the following rules

  1. Write a class to cipher documents.
  2. You can implement any cipher algorithm — it’s up to you which one you want to implement.
  3. As a document we understand simple strings and files.
  4. For example this class should allow to
    1. read plain text file, cipher it and immediately save;
    2. read encrypted text file and return a decrypted string we can use in our program.

Leave a Reply

Your email address will not be published. Required fields are marked *