Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Language Atlas (mini-courses for more languages)  Module 7 – Objective-C for maintainers and Swift developers

Objective-C ARC, blocks and Swift interop

Find retain cycles under ARC, write blocks with __block and a weak self, annotate headers for Swift, and catch format-string and other C hazards.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Clang 21.0.0 and Swift 6.4
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 3 interview questions.

What you will learn

  • Explain strong, weak and copy references under ARC and find the retain cycle that keeps objects alive
  • Write blocks that capture values, change a __block variable and use a weak self to break a cycle
  • Annotate a header with nullability and lightweight generics so that Swift imports optionals and typed arrays
  • Find format-string bugs and other C-level hazards in Objective-C code and build with the flags that catch them

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain strong, weak and copy references under ARC and find the retain cycle that keeps objects alive
  • Write blocks that capture values, change a __block variable and use a weak self to break a cycle
  • Annotate a header with nullability and lightweight generics so that Swift imports optionals and typed arrays
  • Find format-string bugs and other C-level hazards in Objective-C code and build with the flags that catch them

Before you start

On this page

cycle.m, a recorded program of this lesson, creates seven objects that it counts, and four of them are still alive when it ends: each is held, through a property or a block, in a ring of references that ARC (automatic reference counting): Objective-C memory management in which the compiler inserts the retain and release calls, so that an object lives while a strong reference to it exists and is freed after the last one goes. It does not collect reference cycles. never breaks. clang warned about one of those rings and said nothing about another that leaks in exactly the same way.

This lesson covers the parts of Objective-C where Swift habits help least. You will follow ARC as it frees objects, find and break a retain cycle, write blocks and see exactly what they capture, annotate a header so that Swift imports optionals and typed arrays instead of crashing, and spot the C hazards Objective-C keeps: format strings from outside the program, formats too narrow for their values, and integer overflow.

ARC frees an object when its last strong reference goes

lifetimes.m creates five tickets, each of which prints a line when it is created and when it is freed, and lets go of them in different ways. Apple’s ARC release notes describe the mechanism as code the compiler adds, so that each object lives for as long as something needs it and no longer. Clang’s specification makes it exact: storing an object in a strong reference: A reference that keeps its object alive: under ARC an object lives while at least one strong variable, property or captured block points at it. Object pointers are strong unless declared otherwise. variable or property retains it, the value it replaced is released, and an object is freed when the last strong reference to it goes. Strong is the default for object pointers, so plain Ticket *a is strong.

lifetimes.m, five tickets and when each is freed M · arc/lifetimes.m
// lifetimes.m: when ARC frees an object, and what a weak reference holds afterwards. Every ticket says when it is
// created and when it is freed.
#import <Foundation/Foundation.h>
#include <stdio.h>

@interface Ticket : NSObject
@property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
@end

@implementation Ticket
- (instancetype)initWithName:(NSString *)name {
    self = [super init];
    if (self) {
        _name = [name copy];
        printf("  + %s created\n", name.UTF8String);
    }
    return self;
}
- (void)dealloc {
    printf("  - %s freed\n", self.name.UTF8String);
}
@end

int main(void) {
    @autoreleasepool {
        printf("1. one strong reference, then none\n");
        Ticket *first = [[Ticket alloc] initWithName:@"T1"];
        printf("   first holds %s\n", first.name.UTF8String);
        first = nil;
        printf("2. two strong references to one ticket\n");
        Ticket *a = [[Ticket alloc] initWithName:@"T2"];
        Ticket *b = a;
        a = nil;
        printf("   a is nil, b still holds %s\n", b.name.UTF8String);
        b = nil;
        printf("3. a weak reference\n");
        Ticket *strong = [[Ticket alloc] initWithName:@"T3"];
        __weak Ticket *weak = strong;
        printf("   weak sees %s\n", weak.name.UTF8String);
        strong = nil;
        printf("   weak is now %s\n", weak == nil ? "nil" : "still an object");
        printf("4. a new ticket stored only in a weak variable\n");
        __weak Ticket *lost = [[Ticket alloc] initWithName:@"T4"];
        printf("   lost is %s\n", lost == nil ? "nil" : "an object");
        printf("5. a strong variable at the end of its scope\n");
        Ticket *last = [[Ticket alloc] initWithName:@"T5"];
        printf("   last holds %s\n", last.name.UTF8String);
    }
    printf("after the autorelease pool\n");
    return 0;
}
When each ticket was freed Python · lifetimes_session.py
from session import run

# When ARC frees an object: build lifetimes.m, read its one warning, and run it.
run("""
cd arc
clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation lifetimes.m -o lifetimes
./lifetimes
""")

Output

$ cd arc
$ clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation lifetimes.m -o lifetimes
lifetimes.m:44:24: warning: assigning retained object to weak variable; object will be released after assignment [-Warc-unsafe-retained-assign]
   44 |         __weak Ticket *lost = [[Ticket alloc] initWithName:@"T4"];
      |                        ^      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
$ ./lifetimes
1. one strong reference, then none
  + T1 created
   first holds T1
  - T1 freed
2. two strong references to one ticket
  + T2 created
   a is nil, b still holds T2
  - T2 freed
3. a weak reference
  + T3 created
   weak sees T3
  - T3 freed
   weak is now nil
4. a new ticket stored only in a weak variable
  + T4 created
  - T4 freed
   lost is nil
5. a strong variable at the end of its scope
  + T5 created
   last holds T5
  - T5 freed
after the autorelease pool

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lifetimes_session.py

Every - … freed line sits exactly where the last strong reference went. T2 survived a = nil because b still held it, and went at b = nil. A weak reference: A reference, declared __weak or with the weak attribute, that does not keep its object alive. ARC sets it to nil when the object is freed, so it never points at freed memory. reference does not count: T3 was freed as soon as strong let go, and weak then read nil. Step through those two cases with the counts beside them:

Interactive · step by step Two strong references, then a weak one Step through cases 2 and 3 of lifetimes.m and watch the count of strong references decide when each ticket is freed.
Two strong references, then a weak one, step 8 of 8. Showing a (nil), b (nil), strong (nil), weak (nil, set by ARC). In focus: weak.anilbnilstrongnilweaknil, set by ARC

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. A new ticket. a = [[Ticket alloc] initWithName:@"T2"]. The new object has one strong reference, a, and the run printed + T2 created.
  2. A second strong reference. b = a. ARC retains the object for b, so two strong references now keep it alive.
  3. a lets go. a = nil releases one reference. One is left, so the ticket lives on, and the run printed a is nil, b still holds T2.
  4. The last strong reference goes. b = nil releases the last one. The count reaches 0, ARC frees the ticket, and its dealloc printed - T2 freed.
  5. A ticket with a weak reference. Case 3: strong holds a new ticket, and weak points at it too. A weak reference does not count, so the ticket still has one strong reference.
  6. Reading through weak. While the ticket lives, weak reads it like any reference: the run printed weak sees T3.
  7. The strong reference goes. strong = nil releases the only strong reference. ARC frees the ticket, and the run printed - T3 freed.
  8. weak is nil. Clang's specification says a weak reference to an object being freed is set to nil, so weak never points at freed memory: the run printed weak is now nil.

The two other lines are the edges of the rule. T4 was stored only in a weak variable, so it had no strong reference from its first moment; clang warned at build time, assigning retained object to weak variable; object will be released after assignment, and the run printed + T4 created and - T4 freed back to back. T5 was freed at the closing brace of the @autoreleasepool block, where its variable’s scope ended, before after the autorelease pool.

Key idea

Under ARC an object lives exactly as long as at least one strong reference points at it; weak references do not count, and ARC sets them to nil when the object goes.

Checkpoint

Check: When ARC frees an object

  1. Question 1 of 2 In case 2 of lifetimes.m, a and b both pointed at T2. Which line freed it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: b = nil

    Each strong reference keeps the object alive; ARC frees it when the count of strong references reaches zero, which here happened at b = nil.

  2. Question 2 of 2 __weak Ticket *lost = [[Ticket alloc] initWithName:@"T4"]; What does lost hold on the next line?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: nil: the new ticket had no strong reference and was freed at once

    clang's warning said it: assigning retained object to weak variable; object will be released after assignment. Keep a new object in a strong variable for as long as you need it.

A ring of strong references is never freed

A strong reference keeps its object alive, so two objects that hold each other strongly keep each other alive for ever. Clang’s specification says it plainly: ARC does not provide a cycle collector, and breaking cycles is the programmer’s job, by hand or with weak references. Apple’s release notes call such a ring a strong reference cycle, the name for what was earlier known as a retain cycle: A ring of strong references, such as two objects that hold each other, which keeps every object in it alive after the rest of the program lets go. Apple also calls it a strong reference cycle; ARC never collects one.. cycle.m counts its live objects: each init adds one and each dealloc subtracts one, the dealloc counter a debug build can keep:

cycle.m, three cycles and their fixes M · arc/cycle.m
// cycle.m: three ways to keep objects alive by accident, each beside its fix. A counter of live objects says how many
// are left at the end of each case.
#import <Foundation/Foundation.h>
#include <stdio.h>

static int live = 0;

@class Order;

@interface Customer : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) Order *order;
@end

@interface Order : NSObject
@property (nonatomic, strong) Customer *buyer;      // a strong reference back: the cycle
@property (nonatomic, weak) Customer *weakBuyer;    // a weak reference back: the fix
@end

@implementation Customer
- (instancetype)init { if ((self = [super init])) { live++; } return self; }
- (void)dealloc { live--; printf("  - customer %s freed\n", self.name.UTF8String); }
@end

@implementation Order
- (instancetype)init { if ((self = [super init])) { live++; } return self; }
- (void)dealloc { live--; printf("  - order freed\n"); }
@end

@interface Feed : NSObject
@property (nonatomic, copy) NSString *label;
@property (nonatomic, copy) void (^onUpdate)(void);
@property (nonatomic, strong) NSMutableArray *handlers;
- (void)watch;
- (void)watchWeakly;
- (void)watchThroughAList;
@end

@implementation Feed
- (instancetype)init { if ((self = [super init])) { live++; _handlers = [NSMutableArray array]; } return self; }
- (void)watch {
    self.onUpdate = ^{
        printf("    update for %s\n", self.label.UTF8String);
    };
}
- (void)watchWeakly {
    __weak Feed *weakSelf = self;
    self.onUpdate = ^{
        Feed *strongSelf = weakSelf;
        if (strongSelf) {
            printf("    update for %s\n", strongSelf.label.UTF8String);
        }
    };
}
- (void)watchThroughAList {
    NSMutableArray *list = self.handlers;
    [list addObject:^{
        printf("    update for %s\n", self.label.UTF8String);
    }];
}
- (void)dealloc { live--; printf("  - feed %s freed\n", self.label.UTF8String); }
@end

int main(void) {
    @autoreleasepool {
        printf("1. customer and order hold each other strongly\n");
        {
            Customer *c = [[Customer alloc] init];
            c.name = @"Asha";
            c.order = [[Order alloc] init];
            c.order.buyer = c;
        }
        printf("   live objects: %d\n", live);
        printf("2. the order points back weakly\n");
        {
            Customer *c = [[Customer alloc] init];
            c.name = @"Ravi";
            c.order = [[Order alloc] init];
            c.order.weakBuyer = c;
        }
        printf("   live objects: %d\n", live);
        printf("3. a block property that uses self\n");
        {
            Feed *f = [[Feed alloc] init];
            f.label = @"news";
            [f watch];
            f.onUpdate();
        }
        printf("   live objects: %d\n", live);
        printf("4. the block uses a weak self\n");
        {
            Feed *f = [[Feed alloc] init];
            f.label = @"sport";
            [f watchWeakly];
            f.onUpdate();
        }
        printf("   live objects: %d\n", live);
        printf("5. the block reaches self's array through a local variable\n");
        {
            Feed *f = [[Feed alloc] init];
            f.label = @"weather";
            [f watchThroughAList];
        }
        printf("   live objects: %d\n", live);
    }
    return 0;
}
Five cases, with the live objects after each Python · cycle_session.py
from session import run

# Three cycles and their fixes: build cycle.m, read which of the cycles clang warned about, and run it.
run("""
cd arc
clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation cycle.m -o cycle
./cycle
""")

Output

$ cd arc
$ clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation cycle.m -o cycle
cycle.m:43:39: warning: capturing 'self' strongly in this block is likely to lead to a retain cycle [-Warc-retain-cycles]
   43 |         printf("    update for %s\n", self.label.UTF8String);
      |                                       ^~~~
cycle.m:42:5: note: block will be retained by the captured object
   42 |     self.onUpdate = ^{
      |     ^~~~
1 warning generated.
$ ./cycle
1. customer and order hold each other strongly
   live objects: 2
2. the order points back weakly
  - customer Ravi freed
  - order freed
   live objects: 2
3. a block property that uses self
    update for news
   live objects: 3
4. the block uses a weak self
    update for sport
  - feed sport freed
   live objects: 3
5. the block reaches self's array through a local variable
   live objects: 4

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cycle_session.py

Case 1 ended with 2 live objects: the customer held its order and the order held the customer, so when the variable c went at the closing brace, each still had one strong reference, from the other. Case 2 is the same pair with one change, @property (nonatomic, weak) Customer *weakBuyer, and both were freed: the customer first, because only c held it, then the order, which the customer had held. The count stayed at 2, the leak of case 1.

The rule that prevents most cycles is about ownership: a parent holds its children strongly, and a child points back to its parent weakly. Choosing the attribute of a property is mostly answering that question, and two more:

Interactive · answer and compare Which attribute should an object property have? Answer three questions about the property you are declaring. The table marks the attributes that fit and says why the others do not.
Does this object own the other one, or does it point back to the object that owns it?
Is the value a string, a collection or a block that the caller could change or reuse later?
Can the other object be freed while this one still exists?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Four ownership attributes of a property against the four things that decide between them
Attribute Keeps the object alive After the object is freed If the caller changes the object Typical use
strong Written as: @property (strong) Good yes: each strong reference counts, so the object lives while this one holds it Good cannot happen while this property holds it Poor sees the change: nickname became Asha Rao in the second lesson Good objects this one owns, such as a customer's order
weak Written as: @property (weak) Mixed no: a weak reference does not count Good reads nil: ARC sets it, as weak and weakSelf showed Mixed sees the change: it is the same object Good a reference back to an owner, or self inside a block
copy Written as: @property (copy) Good yes, its own copy, which is strong Good its copy lives as long as the property holds it Good keeps the value it was given: owner stayed Asha Good NSString, NSArray, NSDictionary and block properties
unsafe_unretained Written as: @property (unsafe_unretained) Poor no: it does not count Poor still points at the freed memory; reading it is undefined Mixed sees the change: it is the same object Poor rare: code from before weak references, where nothing else works
How the answers decide
  • “Does this object own the other one, or does it point back to the object that owns it?” It points back to its owner: rules out strong and copy. A strong reference back to the owner closes a cycle, like the customer and order of case 1, which were never freed.
  • “Does this object own the other one, or does it point back to the object that owns it?” It owns the other object: rules out weak and unsafe_unretained. Nothing else keeps an owned object alive, so a reference that does not count would let it be freed at once, like ticket T4.
  • “Is the value a string, a collection or a block that the caller could change or reuse later?” Yes, such as an NSMutableString: rules out strong and adds a caveat to weak. A string or collection passed in may be mutable and change later, as nickname did; copy keeps the value it was given.
  • “Can the other object be freed while this one still exists?” Yes, it can go first: rules out unsafe_unretained. If the other object can be freed first, unsafe_unretained points at freed memory, while weak reads nil.

In short: strong for what an object owns, weak for a reference back to its owner or anything that may go first, copy for strings, collections and blocks that a caller might change, and unsafe_unretained almost never, because it keeps pointing at freed memory.

Checkpoint

Check: Retain cycles and weak references

  1. Question 1 of 2 Case 1 of cycle.m made a customer and an order that hold each other strongly. How many live objects did it leave behind?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 2 objects

    When c went out of scope, each object still had one strong reference, from the other, so neither dealloc ran and the counter stayed at 2. ARC has no cycle collector.

  2. Question 2 of 2 A Department keeps an NSArray of its Employee objects, and each Employee has a property pointing back to its Department. Which attribute should that property have?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: weak

    Owners hold what they own strongly, and what is owned points back weakly. Then letting go of the owner frees the whole group.

A block captures values when it is created

A block: A piece of code written ^(arguments) { … } that can be stored and called later, with the variables it uses captured: the Objective-C and C counterpart of a Swift closure. Blocks are Objective-C objects. is a piece of code with the variables it uses, written ^(int price) { … }, the Objective-C form of a Swift closure. Clang’s Blocks specification fixes what it captures: local variables are copied into the block as constants at the moment the block literal is evaluated, and a variable marked __block variable: A local variable declared with __block, which the blocks that use it share with its scope instead of copying, so that a block can change it and the code around it sees the change. is shared between the block and its scope instead, so that both see changes. Blocks are also Objective-C objects, which can be stored in collections and properties:

blocks.m, four things a block does with what it captures M · blocks/blocks.m
// blocks.m: what a block captures and when, a __block variable, a block that keeps an object alive, and blocks
// stored in a collection.
#import <Foundation/Foundation.h>
#include <stdio.h>

@interface Ticket : NSObject
@end

@implementation Ticket
- (void)dealloc { printf("  - ticket freed\n"); }
@end

int main(void) {
    @autoreleasepool {
        int discount = 10;
        int (^priceAfter)(int) = ^(int price) {
            return price - discount;
        };
        discount = 50;
        printf("1. priceAfter(200) = %d, with discount now %d\n", priceAfter(200), discount);

        __block int sum = 0;
        [@[@3, @1, @2] enumerateObjectsUsingBlock:^(NSNumber *mark, NSUInteger index, BOOL *stop) {
            (void)index;
            (void)stop;
            sum += mark.intValue;
        }];
        printf("2. sum of 3, 1 and 2 with __block: %d\n", sum);

        printf("3. a block that captured a ticket\n");
        void (^show)(void);
        {
            Ticket *ticket = [[Ticket alloc] init];
            show = ^{
                printf("   the block still has %s\n", ticket == nil ? "nothing" : "the ticket");
            };
            ticket = nil;
            printf("   the variable is nil\n");
        }
        show();
        show = nil;
        printf("   the block is gone\n");

        NSArray *steps = @[^{ printf("4. step one\n"); }, ^{ printf("   step two\n"); }];
        for (void (^step)(void) in steps) {
            step();
        }
    }
    return 0;
}
Captures, a __block sum, a block that keeps a ticket, and an error Python · blocks_session.py
from session import run

# What blocks capture: build and run blocks.m, then compile a block that assigns to a variable without __block.
run("""
cd blocks
clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation blocks.m -o blocks
./blocks
!clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation blocks_bad.m -o blocks_bad
""")

Output

$ cd blocks
$ clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation blocks.m -o blocks
$ ./blocks
1. priceAfter(200) = 190, with discount now 50
2. sum of 3, 1 and 2 with __block: 6
3. a block that captured a ticket
   the variable is nil
   the block still has the ticket
  - ticket freed
   the block is gone
4. step one
   step two
$ clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation blocks_bad.m -o blocks_bad
blocks_bad.m:10:17: error: variable is not assignable (missing __block type specifier)
   10 |             sum += mark.intValue;
      |             ~~~ ^
1 error generated.
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 blocks_session.py

priceAfter(200) gave 190, not 150: the block had copied discount when it was 10, and changing the variable afterwards did not reach the copy. The sum of 3, 1 and 2 reached 6 only because sum was __block; without it, blocks_bad.m did not compile: variable is not assignable (missing __block type specifier). The third case is the one that matters for memory: the ticket survived ticket = nil, because the block captured the object strongly, and it was freed only when the block itself went at show = nil. Apple’s guide states the rule: blocks keep strong references to any objects they capture.

What the block uses What it captures Can it change it?
a local int a constant copy, made when the block is created no
a __block local the shared variable itself yes, and the scope sees it
an object pointer a strong reference to the object the object, yes; the variable, no
an instance variable or self.label self, strongly through self

The last row hides a trap: the specification says that a block using an instance variable, such as _label, imports self, so the block holds the whole object even though the word self never appears in it.

Checkpoint

Check: What a block captures

  1. Question 1 of 2 int tax = 5; int (^withTax)(int) = ^(int price) { return price + tax; }; tax = 18; What does withTax(100) return?

    Type a number.

    A hint

    When does a block take its copy of a local variable?

    Show the answer: Question 1 of 2

    Answer: 105 returned

    The block copied tax when it was created, with the value 5, as priceAfter copied discount when it was 10; changing tax afterwards does not reach the copy. Only a __block variable is shared.

  2. Question 2 of 2 In case 3 of blocks.m the ticket survived ticket = nil and was freed at show = nil. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The block had captured the ticket, and blocks keep strong references to the objects they capture

    Apple's guide says blocks maintain strong references to captured objects, including self. That is what makes a stored block able to keep its owner alive.

A block that uses self can keep its object alive

Cases 3 to 5 of cycle.m are the same ring as case 1, with a block in it. In case 3 the feed keeps the block in its onUpdate property, and the block uses self.label, so it keeps the feed. Step through it, and then through the fix:

Interactive · step by step A feed and the block it keeps Step through case 3 of cycle.m, where the block uses self, then case 4, where it uses a weak self, and see which feed is freed.
A feed and the block it keeps, step 8 of 8. Showing f (gone), weakSelf (nil, set by ARC). In focus: weakSelf.fgoneweakSelfnil, set by ARC

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. A feed. Case 3 creates a Feed and keeps it in the local variable f: one strong reference.
  2. The block uses self. watch creates a block that prints self.label. Apple's guide says a block keeps strong references to what it captures, self included, so the block holds the feed.
  3. The feed keeps the block. self.onUpdate = ^{ … } stores the block in a copy property, so the feed holds the block. clang warned here: capturing self strongly in this block is likely to lead to a retain cycle.
  4. The scope ends. f goes away at the closing brace and releases its reference. The feed still has one, from the block, and the block one, from the feed.
  5. A cycle. Nothing outside the pair points at it, yet each keeps the other alive. ARC has no cycle collector, so neither is ever freed: the run counted live objects: 3.
  6. Case 4, a weak self. watchWeakly first stores self in weakSelf, a __weak variable, and the block captures weakSelf instead of self. The feed again has one strong reference, f.
  7. The scope ends again. f goes away. Now the feed has no strong reference at all, because the block holds only the weak one.
  8. Freed in order. ARC frees the feed, which printed - feed sport freed; freeing the feed releases its block, and weakSelf would read nil. Inside the block, strongSelf keeps the feed alive while one update runs.

The fix of case 4 is the pattern Apple’s guide recommends. The method first stores self in a weak variable, __weak Feed *weakSelf = self;, and the block uses weakSelf, so it holds nothing strongly. Inside the block, Feed *strongSelf = weakSelf; takes a strong reference for the length of one run and tests it for nil, because the feed may already be gone by the time the block runs; Apple’s guide says the weak pointer is then simply nil. The run printed - feed sport freed, and the live count did not grow.

Case 5 is the reason not to rely on the compiler. clang flagged case 3 with capturing 'self' strongly in this block is likely to lead to a retain cycle, but case 5, where the block reaches the feed’s array through a local variable, NSMutableArray *list = self.handlers;, made the same ring with no warning at all, and the count rose to 4. The warning sees a block stored straight into self; it cannot follow every path to it.

Using self, or an instance variable, in a stored block

A block kept by self, in a property, an array or a handler that self owns, must not hold self strongly: self.onUpdate = ^{ [self reload]; }; leaks, and so does ^{ [_table reload]; }, which captures self too. Write __weak typeof(self) weakSelf = self; before the block and use weakSelf inside it, testing a strong copy of it for nil before use.

Checkpoint

Check: Blocks that use self

  1. Question 1 of 2 Of the three block cases of cycle.m, which one leaked without any warning from clang?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Case 5, where the block reached self's array through a local variable

    A compiler warning is a help, not a proof. The counter of live objects, or a memory tool, is what shows a leak.

  2. Question 2 of 2 Inside the block of watchWeakly, the code copies weakSelf into a local strongSelf and tests it before use. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The feed may be freed before the block runs, and strongSelf keeps it alive while one run lasts

    Apple's guide says the weak pointer is simply nil if the object has gone. Taking one strong local and testing it gives the block either the whole object or nothing.

Nullability and generics decide what Swift sees

Swift imports every Objective-C header, and what it sees depends on what the header says. Apple’s page on designating nullability lists the rules: a pointer marked nonnull comes in as a plain type, one marked nullable as an optional, and one with no annotation as an implicitly unwrapped optional: A Swift type written with !, such as String!, that may hold nil but can be used without unwrapping; if it is nil when used, the program stops. Swift imports unannotated Objective-C pointers this way., a value Swift lets you use as if it could not be nil. Lightweight generics, such as NSArray<NSString *> *, keep the type of a collection’s elements; without them, Swift sees [Any]. Between NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END every declaration without an annotation counts as nonnull, so only the nullable ones need a word.

Shelf.h is written the old way. TypedShelf.h declares the same class with annotations, one nullable result and an element type, plus a method that reports a failure through NSError **:

The same class, unannotated and annotated

swift/Shelf.h

// Shelf.h: a header written the old way, with no nullability annotations and no element types.
#import <Foundation/Foundation.h>

@interface Shelf : NSObject
@property (nonatomic, copy) NSArray *titles;
- (NSString *)titleAtIndex:(NSUInteger)index;   // nil when there is no book at that index
- (void)addTitle:(NSString *)title;
@end

swift/TypedShelf.h

// TypedShelf.h: the same class, annotated: nonnull unless marked, one nullable result, the element type of the
// array, and a method that reports a failure through NSError.
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TypedShelf : NSObject
@property (nonatomic, copy) NSArray<NSString *> *titles;
- (nullable NSString *)titleAtIndex:(NSUInteger)index;
- (void)addTitle:(NSString *)title;
- (BOOL)removeTitle:(NSString *)title error:(NSError **)error;
@end

NS_ASSUME_NONNULL_END

swift/plain.swift

// plain.swift: Swift using the unannotated Shelf. Both lines with String compile.
let shelf = Shelf()
shelf.addTitle("Gitanjali")
print("titles:", shelf.titles.count)
let first: String = shelf.title(at: 0)
print("first:", first)
let sixth: String = shelf.title(at: 5)
print("sixth:", sixth)

swift/typed_fixed.swift

// typed_fixed.swift: TypedShelf used as Swift wants it: the optional unwrapped, and the NSError ** method called
// with try.
let shelf = TypedShelf()
shelf.addTitle("Gitanjali")
for title in shelf.titles {
    print(title.uppercased())
}
if let sixth = shelf.title(at: 5) {
    print("sixth:", sixth)
} else {
    print("no book at index 5")
}
do {
    try shelf.removeTitle("Kabir")
} catch {
    print("removeTitle failed:", error.localizedDescription)
}

The session compiles each class with clang and builds Swift programs against its header with -import-objc-header, which hands swiftc a header to import, the job a bridging header does in an Xcode target, as Apple’s page on importing Objective-C describes it:

Swift against an unannotated and an annotated header Python · swift_session.py
from session import run

# Swift and Objective-C in one program: compile each Objective-C class to an object file, then build Swift programs
# that import its header (-import-objc-header, the command-line form of a bridging header) and link the object file.
run("""
cd swift
swiftc --version
clang -fobjc-arc -Wall -Wextra -c Shelf.m -o Shelf.o
clang -fobjc-arc -Wall -Wextra -c TypedShelf.m -o TypedShelf.o
swiftc -import-objc-header Shelf.h plain.swift Shelf.o -o plain
!./plain
!swiftc -import-objc-header Shelf.h plain_loop.swift Shelf.o -o plain_loop
!swiftc -import-objc-header TypedShelf.h typed.swift TypedShelf.o -o typed
swiftc -import-objc-header TypedShelf.h typed_fixed.swift TypedShelf.o -o typed_fixed
./typed_fixed
""")

Output

$ cd swift
$ swiftc --version
swift-driver version: 1.168.6 Apple Swift version 6.4 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)
Target: arm64-apple-macosx26.0
$ clang -fobjc-arc -Wall -Wextra -c Shelf.m -o Shelf.o
$ clang -fobjc-arc -Wall -Wextra -c TypedShelf.m -o TypedShelf.o
$ swiftc -import-objc-header Shelf.h plain.swift Shelf.o -o plain
$ ./plain
titles: 1
first: Gitanjali
plain/plain.swift:7: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
[exit status 133]
$ swiftc -import-objc-header Shelf.h plain_loop.swift Shelf.o -o plain_loop
plain_loop.swift:5:17: error: value of type 'Any' has no member 'uppercased'
3 | shelf.addTitle("Gitanjali")
4 | for title in shelf.titles {
5 |     print(title.uppercased())
  |                 |- error: value of type 'Any' has no member 'uppercased'
  |                 `- note: cast 'Any' to 'AnyObject' or use 'as!' to force downcast to a more specific type to access members
6 | }
7 |
[exit status 1]
$ swiftc -import-objc-header TypedShelf.h typed.swift TypedShelf.o -o typed
typed.swift:7:27: error: value of optional type 'String?' must be unwrapped to a value of type 'String'
5 |     print(title.uppercased())
6 | }
7 | let sixth: String = shelf.title(at: 5)
  |                           |- error: value of optional type 'String?' must be unwrapped to a value of type 'String'
  |                           |- note: coalesce using '??' to provide a default when the optional value contains 'nil'
  |                           `- note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
8 | print("sixth:", sixth)
9 |
[exit status 1]
$ swiftc -import-objc-header TypedShelf.h typed_fixed.swift TypedShelf.o -o typed_fixed
$ ./typed_fixed
GITANJALI
no book at index 5
removeTitle failed: no book called Kabir on the shelf

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 swift_session.py

Objective-C declaration Swift sees What happened
- (NSString *)titleAtIndex: String! let sixth: String = … compiled, then stopped with exit status 133
- (nullable NSString *)titleAtIndex: String? the same line did not compile: the optional must be unwrapped
NSArray *titles [Any] title.uppercased() did not compile: Any has no member
NSArray<NSString *> *titles [String] the loop compiled and printed GITANJALI
- (BOOL)removeTitle:error: func removeTitle(_:) throws try caught no book called Kabir on the shelf

The first row is the expensive one. With no annotation, the Swift compiler had no way to know that the method can return nil, accepted the program, and the program stopped at run time with Unexpectedly found nil while implicitly unwrapping an Optional value. The annotated header moved the same mistake to compile time, where it costs nothing. The last row shows Apple’s rule for error parameters at work: the NSError ** became throws.

Key idea

Annotate every header Swift will read: NS_ASSUME_NONNULL_BEGIN and END around it, nullable where nil can come back, and element types on collections, so that Swift sees optionals and typed arrays instead of String! and [Any].

Checkpoint

Check: How Swift imports a header

  1. Question 1 of 2 How does Swift import - (NSString *)titleAtIndex:(NSUInteger)index; from a header with no nullability annotations?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: As returning String!, an implicitly unwrapped optional

    nonnull gives a plain type, nullable an optional, and nothing at all an implicitly unwrapped optional, which crashes at run time when it is nil.

  2. Question 2 of 2 Swift code loops over shelf.titles and calls title.uppercased(). It compiled with TypedShelf and not with Shelf. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: TypedShelf declares NSArray<NSString > , so Swift sees [String]; Shelf's plain NSArray * gives [Any]

    Lightweight generics are for Swift and the compiler: NSArray<NSString > imports as [String], NSArray * as [Any].

C’s hazards stay: format strings, widths and overflow

Objective-C is C underneath, so C’s ways to go wrong come with it. The most serious is a format string that comes from outside the program. note_unsafe.m passes a note from its command line to stringWithFormat: as the format itself; note_safe.m passes it as the argument of @"%@":

The unsafe and the safe way to print a note

format/note_unsafe.m

// note_unsafe.m: prints a note that comes from outside the program, using the note itself as the format.
#import <Foundation/Foundation.h>
#include <stdio.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        if (argc != 2) {
            fprintf(stderr, "usage: note_unsafe <note>\n");
            return 2;
        }
        NSString *note = @(argv[1]);
        NSString *line = [NSString stringWithFormat:note];
        printf("note: %s\n", line.UTF8String);
    }
    return 0;
}

format/note_safe.m

// note_safe.m: the same program with a fixed format; the note is only ever an argument.
#import <Foundation/Foundation.h>
#include <stdio.h>

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        if (argc != 2) {
            fprintf(stderr, "usage: note_safe <note>\n");
            return 2;
        }
        NSString *note = @(argv[1]);
        NSString *line = [NSString stringWithFormat:@"%@", note];
        printf("note: %s\n", line.UTF8String);
    }
    return 0;
}
A note used as a format, then as an argument, and the build check Python · format_session.py
from session import run

# A note from outside the program used as a format, then used as an argument; and the check that tells the two apart
# at build time, -Werror=format-security.
run("""
cd format
clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation note_unsafe.m -o note_unsafe
./note_unsafe 'Paid in full: 100%'
!./note_unsafe 'Refund %@ %@'
clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation note_safe.m -o note_safe
./note_safe 'Paid in full: 100%'
./note_safe 'Refund %@ %@'
!clang -fobjc-arc -Wall -Wextra -Werror=format-security -framework Foundation note_unsafe.m -o note_unsafe
clang -fobjc-arc -Wall -Wextra -Werror=format-security -framework Foundation note_safe.m -o note_safe
""")

Output

$ cd format
$ clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation note_unsafe.m -o note_unsafe
note_unsafe.m:12:53: warning: format string is not a string literal (potentially insecure) [-Wformat-security]
   12 |         NSString *line = [NSString stringWithFormat:note];
      |                                                     ^~~~
note_unsafe.m:12:53: note: treat the string as an argument to avoid this
   12 |         NSString *line = [NSString stringWithFormat:note];
      |                                                     ^
      |                                                     @"%@",
1 warning generated.
$ ./note_unsafe 'Paid in full: 100%'
note: Paid in full: 100
$ ./note_unsafe 'Refund %@ %@'
[exit status 139]
$ clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation note_safe.m -o note_safe
$ ./note_safe 'Paid in full: 100%'
note: Paid in full: 100%
$ ./note_safe 'Refund %@ %@'
note: Refund %@ %@
$ clang -fobjc-arc -Wall -Wextra -Werror=format-security -framework Foundation note_unsafe.m -o note_unsafe
note_unsafe.m:12:53: error: format string is not a string literal (potentially insecure) [-Werror,-Wformat-security]
   12 |         NSString *line = [NSString stringWithFormat:note];
      |                                                     ^~~~
note_unsafe.m:12:53: note: treat the string as an argument to avoid this
   12 |         NSString *line = [NSString stringWithFormat:note];
      |                                                     ^
      |                                                     @"%@",
1 error generated.
[exit status 1]
$ clang -fobjc-arc -Wall -Wextra -Werror=format-security -framework Foundation note_safe.m -o note_safe

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 format_session.py

clang warned at once: format string is not a string literal (potentially insecure), with the fix spelled out, treat the string as an argument. The two runs of the unsafe program show why. A note ending in 100% lost its percent sign without a word, because % started a conversion. A note with two %@ made stringWithFormat: read two objects that were never passed, and the program died with exit status 139, 128 plus signal 11, a segmentation fault. The safe program printed both notes exactly. MITRE lists this weakness as CWE-134, use of an externally controlled format string, and its mitigations include passing format functions only fixed strings and building with high warning levels.

Make the check fail the build

-Werror=format-security turns the warning into an error: the session’s build of note_unsafe.m failed with [-Werror,-Wformat-security], and note_safe.m built. Put the flag in the build script of every target, so a format string that is not a literal can never reach a release, and write NSLog(@"%@", text), never NSLog(text).

Two quieter hazards come from C’s integer types. A format must match the size of its argument, and an NSInteger is 8 bytes on a 64-bit Mac while %d reads 4, so 5,000,000,000 paise printed as 705,032,704; clang’s -Wformat warning even offered the fix, %ld with a (long) cast. And signed overflow is undefined in C: 2147483647 + 1 in an int printed −2,147,483,648 with no complaint, until the program was built with -fsanitize=undefined, whose check reported the line and the values: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int':

widths.m, a narrow format and an overflow M · format/widths.m
// widths.m: two C hazards that Objective-C keeps: a format that is too narrow for its argument, and a signed
// overflow, which C leaves undefined.
#import <Foundation/Foundation.h>
#include <stdio.h>

int main(void) {
    @autoreleasepool {
        NSInteger paise = 5000000000;
        NSString *wrong = [NSString stringWithFormat:@"%d paise", paise];
        NSString *right = [NSString stringWithFormat:@"%ld paise", (long)paise];
        printf("with %%d:  %s\nwith %%ld: %s\n", wrong.UTF8String, right.UTF8String);
        int count = 2147483647;
        count = count + 1;
        printf("2147483647 + 1 in an int: %d\n", count);
    }
    return 0;
}
The warning, the wrong numbers, and the sanitizer's report Python · widths_session.py
from session import run

# A format too narrow for its argument and a signed overflow: the warning clang gives, what the program prints, and
# what the undefined-behaviour sanitizer reports when the same program is built with -fsanitize=undefined.
run("""
cd format
clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation widths.m -o widths
./widths
clang -fobjc-arc -Wall -Wextra -Wno-format -fsanitize=undefined -framework Foundation widths.m -o widths_checked
./widths_checked
""")

Output

$ cd format
$ clang -fobjc-arc -Wall -Wextra -Wformat-security -framework Foundation widths.m -o widths
widths.m:9:67: warning: values of type 'NSInteger' should not be used as format arguments; add an explicit cast to 'long' instead [-Wformat]
    9 |         NSString *wrong = [NSString stringWithFormat:@"%d paise", paise];
      |                                                        ~~         ^~~~~
      |                                                        %ld        (long)
1 warning generated.
$ ./widths
with %d:  705032704 paise
with %ld: 5000000000 paise
2147483647 + 1 in an int: -2147483648
$ clang -fobjc-arc -Wall -Wextra -Wno-format -fsanitize=undefined -framework Foundation widths.m -o widths_checked
$ ./widths_checked
with %d:  705032704 paise
with %ld: 5000000000 paise
widths.m:13:23: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior widths.m:13:23
2147483647 + 1 in an int: -2147483648

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 widths_session.py

String Escape / Unescape Turn a note with quotes, backslashes or line breaks into a C string literal for an Objective-C string in your source; a note from outside the program still goes in as an argument.

Checkpoint

Check: Format strings and other C hazards

  1. Question 1 of 2 For the note "Paid in full: 100%", note_unsafe printed "note: Paid in full: 100". Why did the percent sign disappear?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The note was used as the format, and % starts a conversion, which swallowed the sign

    A string from outside the program must only ever be an argument. As a format it is code, and it lost a character here and crashed the program with two %@.

  2. Question 2 of 2 Which format prints an NSInteger of 5000000000 correctly on a 64-bit Mac?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: %ld, with the value cast to long

    NSInteger is 8 bytes here. A format must match the size of its argument, and the warning flags of the build catch a mismatch when the format is a literal.

Practise: predict which objects ARC never frees

The exercise turns the counting rule of this lesson into a function. Given the strong and weak references between a program’s objects and the variables that hold some of them, it returns the objects that are still alive after those variables let go, which is what cycle.m’s counter measured. Its sample tests are the five cases of cycle.m, the lost ticket T4, the ticket T2 that two variables held, and a chain that ends in a ring:

Exercise · Medium · Python

Predict which objects ARC never frees

cycle.m counted its live objects to find leaks. Write survivors(objects, strong, weak, held) in survivors.py, which predicts the same count before the program runs: it returns the names of the objects that are still alive once the program's variables have let go, sorted alphabetically.

  • objects lists every object by name, such as ["customer", "order"].
  • strong lists the strong references between objects as pairs: ("customer", "order") means the customer holds the order strongly, through a property, an array or a captured block.
  • weak lists the weak references the same way. They never keep an object alive.
  • held lists the objects that local variables hold strongly; a name may appear twice when two variables hold the same object. All of these variables let go at the end.

Count the way ARC does, not the way a garbage collector does. An object's count is the number of strong references to it, from held and from strong. When the variables let go, each count falls by one for each of them, and an object whose count reaches 0 is freed. Freeing an object releases every strong reference it held, which may free more objects in turn. An object that no strong reference holds from the start is freed at once, like the ticket T4. What is left at the end is the answer.

The sample tests are the five cases of cycle.m, the lost ticket of lifetimes.m, a ticket held by two variables, and a chain that ends in a ring.

Starter code · survivors.py

def survivors(objects, strong, weak, held):
    """Return, sorted, the names of the objects still alive after every variable in held lets go."""
    # A garbage collector frees everything that no variable can reach any more, so this first try returns nothing.
    # ARC counts references instead: replace this with the counting the prompt describes.
    return []
The sample tests · test_survivors.py
from survivors import survivors


def test_case_1_strong_both_ways():
    """case 1: customer and order hold each other strongly, so both stay"""
    assert survivors(["customer", "order"], [("customer", "order"), ("order", "customer")], [], ["customer"]) == ["customer", "order"]


def test_case_2_weak_back_reference():
    """case 2: the order points back weakly, so both are freed"""
    assert survivors(["customer", "order"], [("customer", "order")], [("order", "customer")], ["customer"]) == []


def test_cases_3_and_4_block_and_self():
    """case 3: the feed keeps its block and the block keeps the feed; case 4: the block holds a weak self"""
    assert survivors(["block", "feed"], [("feed", "block"), ("block", "feed")], [], ["feed"]) == ["block", "feed"]
    assert survivors(["block", "feed"], [("feed", "block")], [("block", "feed")], ["feed"]) == []


def test_case_5_ring_through_an_array():
    """case 5: feed, its array and the block in it form a ring of three"""
    strong = [("feed", "list"), ("list", "block"), ("block", "feed")]
    assert survivors(["block", "feed", "list"], strong, [], ["feed"]) == ["block", "feed", "list"]


def test_object_held_by_nothing():
    """T4: an object stored only in a weak variable is freed at once"""
    assert survivors(["T4"], [], [], []) == []


def test_two_variables_one_object():
    """T2: two variables hold one ticket, and both let go"""
    assert survivors(["T2"], [], [], ["T2", "T2"]) == []


def test_chain_into_a_ring():
    """a holds b, b holds c, and c and d hold each other: a and b are freed, the ring of c and d stays"""
    strong = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "c")]
    assert survivors(["a", "b", "c", "d"], strong, [], ["a"]) == ["c", "d"]
A hint

Keep a dictionary of counts, one entry per object, and start it from strong and held. Then subtract one for each name in held, and put every object whose count is 0 on a list of objects to free. Freeing one means subtracting one from each object it holds strongly; any that reach 0 join the list.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then take the self-check on the six ideas of the lesson. Ideas you have answered wrongly come back first, and every option you choose is explained straight away:

Practice that adapts to you

Practise this lesson

12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 Under ARC, what ownership does a variable such as Ticket *a have when no qualifier is written?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: Strong

    Plain object pointers are strong, which is why case 1 of lifetimes.m freed T1 only at first = nil.

  2. Question 2 of 12 When was T5 of lifetimes.m freed?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: At the closing brace of the @autoreleasepool block, where its variable went out of scope

    The end of a strong variable's scope releases its reference, so local objects need no nil at the end.

  3. Question 3 of 12 What does ARC do with two objects that hold each other strongly when nothing else points at them?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: Nothing: they stay alive, because ARC has no cycle collector

    Reference counting cannot see that a group is unreachable; each member's count stays above zero. Breaking the ring is the code's job.

  4. Question 4 of 12 A controller owns a view, and the view has a delegate property that points back to the controller. Which attribute should delegate have, and why?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: weak, because the controller owns the view, so a strong reference back would make a ring

    A delegate usually owns, directly or indirectly, the object that calls it, so the call-back reference is the weak link of the pair.

  5. Question 5 of 12 Which of these is true of blocks in Objective-C?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: They are objects, so an NSArray or a property can hold them

    Clang's Blocks specification makes every block an Objective-C object, which is why blocks can be retained, stored, and also part of a retain cycle.

  6. Question 6 of 12 Which attribute does Apple's guide give a property that holds a block, and why?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: copy, because a block must be copied to keep its captured state beyond the scope that made it

    onUpdate in cycle.m is declared (nonatomic, copy) for this reason. The cycle problem is solved inside the block, not by the property's attribute.

  7. Question 7 of 12 A block stored in self.onDone uses the instance variable _count, and never the word self. Does it capture self?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: Yes: an instance variable is reached through self, so the block imports self

    Write weakSelf before the block and reach the value through a tested strongSelf, or read it into a local first if the block only needs its current value.

  8. Question 8 of 12 A colleague's change adds [self.timers addObject:^{ [self tick]; }]; where self owns the timers array. clang gave no warning. What do you ask for?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: A weak self captured by the block, with a tested strong copy inside, because self holds the array that holds the block

    Look for every place a block is stored where self can reach it, directly or not, and make the block's reference to self weak.

  9. Question 9 of 12 Inside NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END, how is an unannotated NSString * parameter imported into Swift?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: As String, not optional

    The macros let a header mark only the exceptions: everything is nonnull unless it says nullable.

  10. Question 10 of 12 Inside NS_ASSUME_NONNULL_BEGIN, a method returns nil when a lookup finds nothing. Which annotation does its result need?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: nullable

    An annotation is a promise to Swift. Mark nullable exactly where nil can come back, and leave the rest to the audited region.

  11. Question 11 of 12 With which exit status did note_unsafe end for the note "Refund %@ %@"?

    Type a number.

    Show the answer: Question 11 of 12

    Answer: 139 exit status

    139 is 128 plus 11, the number of SIGSEGV: stringWithFormat: read two objects that were never passed and touched memory it could not read. note_safe printed the same note unchanged.

  12. Question 12 of 12 How do you make sure that a format string from outside the program cannot reach a release build again?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: Build every target with -Werror=format-security and write fixed formats such as @"%@"

    CWE-134's mitigations are fixed format strings and high warning levels; turning the warning into an error is how a team keeps both.

Organisations named on this page

MySmartCoPilot is independent of Apple, the LLVM Project and MITRE. Apple and the LLVM Project are named for their compilers and documentation, MITRE for its list of software weaknesses.

Interview questions

Warm-up (fresher to mid-level): What is a retain cycle, and how does a weak reference break it?

A retain cycle is a ring of strong references: object A holds B strongly and B holds A, directly or through other objects. Under ARC an object is freed when its last strong reference goes, so when the rest of the program lets go of the ring, every object in it still has one strong reference, from its neighbour, and none is ever freed: Clang’s ARC specification says ARC has no cycle collector. A weak reference does not keep its object alive, and ARC sets it to nil when the object goes, so making one link weak, usually the one from a child back to its parent or from a block back to self, lets the whole ring be freed. In the lesson, a customer and an order that held each other strongly stayed alive; with a weak reference back, both were freed.

Three questions of the kind asked about memory, Swift boundaries and safe printing:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    In an app with Objective-C screens, memory grows by a few hundred kilobytes every time a user opens and closes the order screen, and its controller's dealloc never runs. The controller keeps blocks for a timer and for network callbacks. How do you find the cycle and fix it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to senior

    Swift code that calls an Objective-C method from your module crashes with "Unexpectedly found nil while implicitly unwrapping an Optional value", though it compiled cleanly. What does the crash tell you about the header, and how do you fix it so that the compiler catches the next one?

    Model answer, Pro. First step and follow-ups

  3. Mid to staff

    Reading a colleague's change, you find NSLog(message) in a module that logs text received from a server, and the same pattern with stringWithFormat: in a few other files. The author says the messages are always plain text. What do you say, and what do you change in the code and in the build?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Under ARC, when is an object freed?

    As soon as its last strong reference goes. Object pointers are strong unless declared otherwise; weak references do not count and read nil once the object is gone.

  2. What becomes of a new object that is stored only in a __weak variable?

    It never has a strong reference, so it is freed straight away. clang warned that it will be released after assignment, and the run printed T4 created and T4 freed back to back.

  3. A customer and an order hold each other strongly. Why are they never freed?

    Each keeps one strong reference from the other when the program lets go, and ARC has no cycle collector. A weak reference from the order back to its customer freed both.

  4. A block uses a local int discount, 10 at the time. The variable is then set to 50. What does the block see?

    Still 10: a block copies the local values it uses, as constants, at the moment it is created. Only a variable marked __block is shared with the scope around it.

  5. Why can a block that only uses the instance variable _label keep its object alive?

    Using an instance variable captures self, so the block holds the whole object strongly although self is never written. If self stores that block, the two form a ring.

  6. How should a block that self keeps refer to self?

    Through __weak typeof(self) weakSelf = self;, written before the block. Inside, take a strong copy of weakSelf for one run and test it for nil, since the object may already be gone.

  7. What does Swift see for an unannotated method returning NSString *, and for the same method marked nullable?

    String!, an implicitly unwrapped optional, which stopped with exit status 133 on nil; and String?, which the compiler makes you unwrap. NSArray<NSString *> * comes in as [String], a plain NSArray as [Any].

  8. Why is [NSString stringWithFormat:note] dangerous when note comes from outside the program?

    The note becomes the format: a % swallowed text, and a note holding two %@ made it die with exit status 139 (CWE-134). Pass the note as an argument of @"%@", and build with -Werror=format-security.

Key takeaways

  • Under ARC an object is freed when its last strong reference goes; weak references do not count and read nil afterwards, and an object stored only in a weak variable is freed at once.
  • ARC has no cycle collector, so two objects that hold each other strongly are never freed; a child’s reference back to its parent is weak, and properties are strong, weak or copy by ownership.
  • A block copies the local values it uses when it is created, shares only __block variables, and keeps every object it captures alive, self included, even when it only uses an instance variable.
  • A block stored by self must use a weak self; clang’s warning caught the direct case and missed one that went through a local variable, so the live count is the real test.
  • Unannotated headers give Swift String! and [Any]; NS_ASSUME_NONNULL_BEGIN, nullable and element types give it optionals and typed arrays, and an NSError ** method becomes one that throws.
  • A format string from outside the program crashed it with exit status 139 and silently dropped a %; build with -Werror=format-security, match formats to their types, and use -fsanitize=undefined to catch overflow.

Terms in this lesson

__block variable
A local variable declared with __block, which the blocks that use it share with its scope instead of copying, so that a block can change it and the code around it sees the change.
ARC (automatic reference counting)
Objective-C memory management in which the compiler inserts the retain and release calls, so that an object lives while a strong reference to it exists and is freed after the last one goes. It does not collect reference cycles.
block
A piece of code written ^(arguments) { … } that can be stored and called later, with the variables it uses captured: the Objective-C and C counterpart of a Swift closure. Blocks are Objective-C objects.
implicitly unwrapped optional
A Swift type written with !, such as String!, that may hold nil but can be used without unwrapping; if it is nil when used, the program stops. Swift imports unannotated Objective-C pointers this way.
retain cycle
A ring of strong references, such as two objects that hold each other, which keeps every object in it alive after the rest of the program lets go. Apple also calls it a strong reference cycle; ARC never collects one.
strong reference
A reference that keeps its object alive: under ARC an object lives while at least one strong variable, property or captured block points at it. Object pointers are strong unless declared otherwise.
weak reference
A reference, declared __weak or with the weak attribute, that does not keep its object alive. ARC sets it to nil when the object is freed, so it never points at freed memory.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.