LoginSignup
4
4

More than 5 years have passed since last update.

iOS Memory Management and Multithreading

Last updated at Posted at 2014-08-14

This post contains my reading notes of the book <Pro Multithreading and Memory Management>, from the mechanisms of ARC to the usage of Blocks and Grand Central Dispatch.

Memory Management Actions in Objective-C -- Reference Counting

Action for Objective-C Object Objective-C Method
Create and have ownership of it alloc/new/copy/mutableCopy group
Take ownership of it retain
Relinquish it release
Dispose of it dealloc

+(id)alloc

implementation.m

+ (id) alloc {
    return [self allocWithZone: NSDefaultMallocZone()]; 
}

+ (id) allocWithZone: (NSZone*)z {
    return NSAllocateObject (self, 0, z); 
}

struct obj_layout { 
    NSUInteger retained;
};

inline id
NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone) {
    int size = /* needed size to store the object */
    id new = NSZoneMalloc(zone, size);
    memset(new, 0, size);
    new = (id)&((struct obj_layout *)new)[1];
}

The NSAllocateObject function calls NSZoneMalloc to allocate a memory area. After that, the area is filled with zero and the area pointer is returned.

Objective-C now ignores zones, which was used to prevent memory fragmentation. The alloc method can now be rewritten as:

demo.m
struct obj_layout { 
   NSUInteger retained;
};
+ (id) alloc {
   int size = sizeof(struct obj_layout) + size_of_the_object; 
   struct obj_layout *p = (struct obj_layout *)calloc(1, size); 
   return (id)(p + 1);
}

The alloc method returns a memory block filled with zero containing a struct obj_layout header, which has a variable “retained” to store the number of references(reference count).

retain

get reference count value by calling retainCount:

demo.m
id obj = [[NSObject alloc] init]; 
NSLog(@"retainCount=%d", [obj retainCount]);
/*
* retainCount=1 is displayed. */

Implementation of retain:

demo.m
- (id) retain{
    NSIncrementExtraRefCount(self);
    return self;
}
inline void NSIncrementExtraRefCount(id anObject) {
    if (((struct obj_layout *)anObject)[-1].retained == UINT_MAX - 1) 
    [NSException raise: NSInternalInconsistencyException
        format: @"NSIncrementExtraRefCount() asked to increment too far"];
    ((struct obj_layout *)anObject)[-1].retained++; }

when the variable "retained" overflows, it's incremented.

release

demo.m
- (void) release {
    if (NSDecrementExtraRefCountWasZero(self))
    [self dealloc]; 
}

BOOL
NSDecrementExtraRefCountWasZero(id anObject) {
    if (((struct obj_layout *)anObject)[-1].retained == 0) { 
        return YES;
    } else {
        ((struct obj_layout *)anObject)[-1].retained--; return NO;
    } 
}

"retained" decremented.

dealloc

demo.m
- (void) dealloc {
    NSDeallocateObject (self); 
}
inline void NSDeallocateObject(id anObject) {
    struct obj_layout *o = &((struct obj_layout *)anObject)[-1];
    free(o); 
}

a chunck of memory desposed.

autorelease

similar to "automatic variable" in C, which is disposed of automatically when the execution leaves the scope.
autorelease means when execution leaves a code block, the release method is called on the object automatically.

demo.m
- (id) autorelease {
    [NSAutoreleasePool addObject:self]; 
}

ARC(Automatic Reference Counting)

With ARC, "id" and object type variables must have one of the following ownership qualifiers:
__strong, __weak, __unsafe_unretained, __autoreleasing
Ownership is properly managed not only by variable scope, but also by assignments between variables, which are qualified with __strong.

Use __weak to avoid circular references:
A __weak ownership qualifier provides a weak reference. A weak reference does not have ownership of the object.

demo.m
id __strong obj0 = [[NSObject alloc] init]; 
id __weak obj1 = obj0;
/*
* variable obj1 has a weak reference of the created object */

__unsafe_unretained: don't use it unless you have to support prior to iOS5, it might leave a dangling pointer. use __weak instead.

However, __weak is exclusively for pointers that should be zeroed, not for primitive types.

__unsafe_unretained is useful for situations where you have an object pointer in a situation where ARC doesn't have enough control to safely manage memory, like inside of a regular C struct. That's one reason there's the "no object pointers in C struct" limitation, unless you decorate that point with __unsafe_unretained to let the compiler you know what you're doing. It's also used to break strong-reference cycles (a.k.a. retain cycles) with blocks under some specific circumstances.

But for 99.9% of daily work, weak works great and you can forget about __unsafe_unretained.
Source

Blocks

A Block is generated from the Block literal starting with “^”. And the Block is assigned to the variable “blk”. Of course you can assign the value to other variables of the Block type.

Blocks are Anonymous functions together with automatic(local)variables.

Declarationssource
As a local variable:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

As a property:
@property (nonatomic, copy) returnType (^blockName)(parameterTypes);

As a method parameter:
(void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;

As an argument to a method call:
[someObject someMethodThatTakesABlock: ^returnType (parameters) {...}];

As a typedef:
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

block.m
int (^blk1)(int) = blk;
int (^blk2)(int); blk2 = blk1;
/*Functions can take arguments of Block type.*/
void func(int (^blk)(int)) {
/*Also, functions can return a Block.*/
int (^func()(int)) {
return ^(int count){return count + 1;};
}

Using typedefs:

block.m
typedef int (^blk_t)(int);

Grand Central Dispatch

How to use: define tasks you want to execute and add them to an appropriate dispatch queue.

GCD.m
dispatch_async(queue_for_background_threads, ^{ 
/*
* Here, processing a time-consuming task
*
* The task is completed. Then, use the result on the main thread as follows. 
*/
    dispatch_async(dispatch_get_main_queue(), ^{
    /*
    * Here, tasks that work only on the main, such as         
    * updating user inteface, etc.
    */
    }); 
});

Dispatch Queues
A dispatch queue is a queue to store tasks to be executed (in the added, first in first out order).

Two kinds of dispatch queues:

  • serial dispatch queue: waits until the current running task finishes before starting another task, only one task runs at a time.
  • concurrent dispatch queue: whether the first task is finished or not, the second may start.It doesn't wait until the tasks are finished and multiple tasks can run concurrently. The number of tasks running concurrently depends on the status of the current system(on iOS the XNU kernel decides the number of threads).

When a concurrent dispatch queue is used, the execution order depends on the task themselves, the system status and so on. the order of the tasks isn't constant. You should use serial dispatch queue when the order is important or the tasks shouldn't be running concurrently.

Two ways to obtain dispatch queues:

  • dispatch_queue_create: a function for creating a new dispatch queue.
serialQueue.m
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);

Use serial dispatch queues only to avoid race condition that occurs because multiple threads update the same data. Otherwise avoid it, too many threads consume too much memoery and the system to slow down.

dispatch queues are not treated as Objective-C objects, you have to call dispatch_release when it's no longer needed. Dispatch queues are managed by dispatch_retain and dispatch_release functions with reference counting techniques.

concurrentQueue.m
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create( "com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue, ^{NSLog(@"block on myConcurrentDispatchQueue");});
dispatch_release(myConcurrentDispatchQueue);
  • main dispatch queue / global dispatch queue:

The main dispatch queue is a serial dispatch queue to execute tasks on the main thread.

The global dispatch queues are concurrent dispatch queues that are usable anywhere in the app. There are four global dispatch queues with different priority: high, default, low and background.

globalDispatchQueues.m
/*
* How to get the main dispatch queue */
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*
* How to get a global dispatch queue of high priority */
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/*
* How to get a global dispatch queue of default priority */
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* How to get a global dispatch queue of low priority */
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/*
* How to get a global dispatch queue of background priority */
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

Dispatch Groups: a group of queues

dispatchGroups.m
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"blk0");}); dispatch_group_async(group, queue, ^{NSLog(@"blk1");}); dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});
dispatch_release(group);
/*output:
blk1
blk2
blk0
done*/

GCD functions

  • dispatch_set_target_queue: to modify the priority of a dispatch queue after it is created.
  • dispatch_after:setting the timing to start tasks in the queue.
dispatch_after.m
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds."); });
  • dispatch_barrier_async:waiting for other tasks in a queue. You can add a task to a concurrent dispatch queue at the time all the tasks in the queue are finished.When the task added by the dispatch_barrier_async function is finished, the concurrent dispatch queue will be back to normal, which means that it executes the tasks concurrently.

  • dispatch_sync: adds the block to the dispatch queue synchronously. Use carefully since it may cause deadlocks.

  • dispatch_apply:add a block to a dispatch queue for a number of times.

  • dispatch_suspend/dispatch_resume:suspend or resume the execution of the queue.

  • dispatch_once:used to ensure that the specified task will be executed only once during the application's lifetime(oftenly used to initialize a singleton class).

singleton.m
+ (id)sharedManager {
    static MyManager *sharedMyManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedMyManager = [[self alloc] init];
    });
    return sharedMyManager;
}

Summary: this article serves as a quick reference to iOS memory management and multithreading related topics. In the future, if I find particular cases interesting, I will come back to update this article.

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4