Objective-C Learning Notes

Data Types

Primitive Types

Standard C data types work too

int someInt = 42;
float someFloat = 3.2f;

Local variables are allocated on the stack, while objects are allocated on the heap

BOOL a = YES;

Non-Primitive Types

// Root type
NSObject

// Immutable types
NSString : NSObject
NSNumber : NSObject

// Mutable types
NSMutableString : NSString
NSMutableArray : NSArray
NSMutableDictionary : NSDictionary

Strings and Numbers

NSString *str = @"fuckyou";
NSString *str2 = @"yes";

NSUInteger len = [str length]; // 7
NSUInteger len2 = str.length; // 7

BOOL eq = [str isEqualToString:str2];
BOOL endsWithU = [str hasSuffix:@"u"];

// split
NSArray *components = [pair componentsSeparatedByString:@"="];
NSNumber *num = [[NSNumber alloc] initWithInt:42];

// Use the factory method, more convenient
NSNumber *num2 = [NSNumber numberWithInt:43];

// Use a boxed expression
NSNumber *num3 = @(84 / 2);

Objective-C requires manual boxing/unboxing

NSNumber *num1 = 10; // Won't work: no auto-boxing, you need the @
NSNumber *num1 = @10; // OK
int num2 = num1; // This only gets you the address of the num1 object
int num2 = [num1 intValue]; // OK, manual unboxing
NSLog(@"%@, %d", num1, num2);

intValue is a method of the NSValue class

Arrays

Mutable / immutable

NSArray *arr = @[ @1, @2, @3 ];

NSMutableArray *mutArr = [[NSMutableArray alloc] initWithArray:arr];
[mutArr addObject:@4];

// Iterate with an enumerator
NSEnumerator *enumerator = [arr objectEnumerator];
id thing = nil;
while (thing = [enumerator nextObject]) {
    NSLog(@"thing: %@", thing);
}

// The C way
for (int i = 0; i < arr.count; i++) {
    NSLog(@"for: %@", arr[i]);
}

// The for-in way
for (NSNumber *num in arr) {
    NSLog(@"for in: %@", num);
}

Dictionaries

Mutable / immutable

NSDictionary *myDict = @{
    @"name": @"John",
    @"age": @123,
    @"male": @YES,
};

NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:myDict];
[mutDict addEntriesFromDictionary:@{
            @"dynamic": @YES,
        }];

Logging

NSLog(@"I am a line of log");

NSLog(@"%@", greeting);

Methods

  • The call syntax is a bit different from C-like languages
  • Every parameter name is part of the method name (which ends up being roughly equivalent to overloading)

Calling

UIViewController *vc = [[UIViewController alloc] init];

In a C-like language this would be

UIViewController *vc = UIViewController.alloc().init()

Passing Arguments

UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor greenColor];
view.frame = CGRectMake(150, 150, 100, 100);
[self.view addSubview:view];

Roughly equivalent to

UIView *view = UIView.alloc().init();
view.backgroundColor = UIColor.greenColor();
view.frame = {
    x: 150,
    y: 150,
    width: 100,
    height: 100
};
self.view.addSubView(view);

An example passing a single argument

#import "Foundation/NSObjCRuntime.h"
#import "XYZPerson.h"

@implementation XYZPerson


- (void) saySomething:(NSString *)word {
    NSLog(@"%@", word);
}

- (void) sayHello{
    [self saySomething:@"Hello"];
}

@end

OOP

Declaration / Implementation / Header Files

A class declaration usually goes in a .h file

The class implementation goes in a .m file

Declaration

// MyViewController.h

@interface MyViewController : UIViewController

// Properties should go inside the interface
@property NSString *name;
@property NSNumber *id;
@property (readonly) NSString *readOnlyName;

// The minus sign means it's an instance method
- (void)someMethod;

@end

Implementation

#import "MyViewController.h"

@implementation MyViewController

- (instancetype)init{
    self = [super init];
    if (self) {
        
    }
    return self;
}

- (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
}


@end

Initialization

When initializing, alloc and init must be called back to back, and you keep the final return value

  • alloc clears out any garbage data in the memory region being allocated
  • init assigns appropriate initial values to the various properties
// Usage 1
MyViewController *myVc = [[MyViewController alloc] init];

// Usage 2, which is effectively the same as calling alloc and init with no arguments
MyViewController *myVc = [MyViewController new];

No Abstract Classes or Virtual Functions?

Properties

  • Declared in the header file with @property
  • In the implementation file, @synthesize automatically generates getter/setter functions for the variable (you can skip it; they’re generated automatically)
  • If you write custom getter/setter methods, they override the generated ones
  • Externally visible properties (public properties) are declared in the header file; private properties are declared in the implementation file
AspectPublic properties (.h)Private properties (class extension)
VisibilityVisible externally, directly accessibleVisible only inside the class
Subclass inheritanceSubclasses can inherit and access themNot visible to subclasses
Memory managementFollows property attribute rules (e.g. strong)Same as public properties
Use casesProperties that need outside access (e.g. UI configuration)Internal state (e.g. caches, counters)
// Person.h
@interface Person : NSObject
@property float age;
@property BOOL isMan;

-(void)somePublicMethod;
@end
// Person.m
@implementation Person

@end

Private properties go in the implementation file

// Person.h
@interface Person ()
@property float weight;
@end

A bit of history:

  • Originally there was no @property syntax. You had to write the getter/setter for every property yourself, and reading or setting a property always went through methods, e.g. [person age] and [person setAge:28]
  • Then @property and @synthesize arrived. Now you only had to declare a property in the header or implementation file, and the compiler would generate the getter/setter for you (not through plain code generation, though; Apple hides the implementation details). Reading and setting properties still went through methods at this point
  • Later came dot syntax, which finally let you read and set properties with person.age and person.age = 28. But dot syntax is just syntactic sugar too: on the left side of an assignment it implicitly calls the setter, and on the right side it calls the getter

Writing a Class

@interface + @implementation

@class creates a forward reference to speed up compilation

Categories

Used to add methods to an existing class

Create a header file with a plus sign in its name, NSString+SpecialFix.h

#import <Foundation/Foundation.h>

@interface NSString (SpecialFix)
-(NSNumber*)lengthAsNumber;
@end

Likewise, create an implementation file with a plus sign in its name, NSString+SpecialFix.m

#import "NSString+SpecialFix.h"

@implementation NSString (SpecialFix)
-(NSNumber*)lengthAsNumber {
    NSUInteger length = [self length];
    return [NSNumber numberWithUnsignedInt:length];
}
@end

Now anywhere else, you just import the header NSString+SpecialFix.h and you can use the new method on NSString

#import "NSString+SpecialFix.h"

NSNumber *len = [@"hello" lengthAsNumber];

Protocols

Basically Java’s interface

Protocols are declared in a header file, e.g. Encoder.h contains the following

@protocol Encoder <NSObject>

-(NSString*)encode;

@end

Class A conforming to protocol B is equivalent to class A implements B {} in Java

It takes two things:

  1. Declare in A.h that A conforms to protocol B
  2. Implement all of protocol B’s required methods in A.m (there are also optional methods, which you don’t have to implement)
@implementation A <B>

-(NSString*) encode {
    return @"encoded result";
}

@end

Geometry

  • CGFloat: a floating-point number
  • CGSize: width and height
  • CGPoint: a point on a 2D plane
  • CGRect: a rectangular region on a 2D plane

Shorthand constructors:

  • CGSizeMake
  • CGPointMake
  • CGRectMake
CGFloat w = 100;
CGFloat h = 200;
CGSize size = CGSizeMake(w, h);

CGFloat x = 10;
CGFloat y = 20;
CGPoint point = CGPointMake(x, y);

CGRect rect = CGRectMake(point.x, point.y, size.width, size.height);
NSLog(@"%f, %f, %f, %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
// 10 20 100 200

Header Files

C uses include; Objective-C supports both, but mostly uses import

Angle brackets mean a system header; quotes mean a header from within the project

#import <Foundation/Foundation.h>
#import "WRAIChatViewController.h"

KVC

This isn’t actually an Objective-C language feature; it’s provided by Cocoa.

// Shape.h
@interface Shape : NSObject <RYCopying, RYSerializable>

@property float age;

@end

// test.m
Shape *sh = [[Shape alloc] init];
[sh setAge:23.2];
float aggg = [[sh valueForKey:@"age"] floatValue];
NSLog(@"valueForKey: %f", aggg); // 23.20001
NSLog(@"valueForKey at: %@", [sh valueForKey:@"age"]); // 23.2
[sh setValue:@54.3 forKey:@"age"];
NSLog(@"valueForKey at: %@", [sh valueForKey:@"age"]); // 54.3

Pros:

  • You can read and set properties without getter/setter methods
  • Values returned by valueForKey are boxed automatically
  • For setValue:forKey you have to wrap the value with @ yourself

Cons: it’s not a good fit for pulling deeply nested values via key paths, because key paths are expressed as strings and the compiler can’t check whether they’re correct

Blocks

Definition

C function pointer syntax

int (*addFunc)(int num1, int num2);

int add(int a, int b) {
    return a + b;
}

addFunc = &add;

JS syntax

const add = (a, b) => a + b;

Objective-C syntax

int (^addFunc)(int num1, int num2) = ^(int a, int b){
    return num1 + num2;
};

int ress = addFunc(1,9);
NSLog(@"ress: %d", ress);

// Without a return value
void (^printFunc)(void) = ^{
    NSLog(@" I am printFunc result");
};

In short, the Objective-C syntax is:

return_type (^block_name)(args) = ^(args) {
    logic
}

IIFC

You can actually do an IIFC just like in JS: declare it and run it right away

In JS

const result = (() => {
    const a = "prefix";
    const b = "suffix";
    return a + b;
})(); // prefixsuffix

In Objective-C

NSString* result = (^{
    NSString *a = @"prefix";
    NSString *b = @"suffix";
    NSArray *components = @[a, b];
    return [components componentsJoinedByString:@""];
})();
NSLog(@"result = %d", result); // prefixsuffix

Now with parameters, in JS

const result = ((a, b) => a + b)("prefix", "suffix");

Objective-C

NSString* result = (^(NSString *a, NSString *b) {
    NSArray *components = @[a, b];
    return [components componentsJoinedByString:@""];
})(@"prefix", @"suffix"); // prefixsuffix

Variable Capture

Objective-C blocks automatically capture these kinds of variables:

  • Global static variables
  • Local variables: variables in the same scope as the block declaration
  • Parameter variables

My guess is that local variables are captured by copy, which is why you can’t assign to a captured outer local variable inside a block

If you need to assign to it, you have to manually add __block in front of the outer variable’s declaration

References

Official documentation