% arc-ownership.tex — ARC, strong/weak/unowned, retain cycles, capture lists,
% the dealloc test, memory tools.
% Sources: docs/memos/swift-arc-memory.md, docs/memos/swift-closures-capture.md,
% docs/memos/ios-memory.md, docs/memos/ios-memory-tools-debugging.md,
% docs/school/teacher/plan.md (ios Q2 PARTIAL, Q12 INCORRECT, Q17 PARTIAL,
% Q18 INCORRECT, playground 01: the "T? is non-owning" misconception).
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/ios-swift/arc-ownership.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=ios-swift kind=concept level=core platform=apple new=no round=round1-2026-09-23 topic=memory,debugging
% @tags: arc, strong, weak, unowned, retain-cycle, capture-list, weak-self, delegate, deinit, memory-graph-debugger, leaks-instrument, allocations
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}

\lstdefinelanguage{SwiftSheet}{
  morekeywords={protocol,class,final,struct,enum,func,var,let,weak,unowned,init,
    if,else,return,guard,self,nil,private,true,false,deinit,in,
    AnyObject,Void,String,Bool,Int},
  sensitive=true, morecomment=[l]{//}, morestring=[b]"}

\tikzset{
  obj/.style={box, minimum width=15mm, minimum height=6mm},
  strong/.style={->, very thick, draw=sheetBlue},
  weakr/.style={->, thick, dashed, draw=sheetGreen},
  cyc/.style={->, very thick, draw=sheetRed},
  lbl/.style={font=\scriptsize, text=black!75, inner sep=1pt},
  ttl/.style={font=\bfseries\small},
  hdr/.style={font=\bfseries\scriptsize, inner sep=1pt},
  mc/.style={draw=sheetGrey, minimum width=25mm, minimum height=8.5mm,
             font=\scriptsize, align=center, inner sep=1pt},
}

\begin{document}

\sheettitle{ARC · strong / weak / unowned · capture lists}{ios-swift · memo}

\oneliner{\textbf{ARC} = the compiler inserts \texttt{retain}/\texttt{release};
an object is freed \emph{deterministically} the instant its \textbf{strong}
count hits 0 (\texttt{deinit} runs). No tracing GC $\Rightarrow$ \textbf{no cycle
detection}: two objects that strongly own each other leak forever, silently.
You break cycles by making the back-edge \texttt{weak} or \texttt{unowned}.}

\begin{multicols}{2}

\section{How it works}
\begin{itemize}
  \item ARC manages \textbf{class instances} (+ actors, closure contexts).
        Structs/enums are copied, not counted — but copying a struct that
        holds a class ref retains that object.
  \item \textbf{strong} (default) — owns: +1. \textbf{weak} — non-owning, must be
        \texttt{var} + Optional; the runtime (side table) sets it to
        \texttt{nil} when the object dies. \textbf{unowned} — non-owning, not
        auto-nilled; access after death = \textbf{crash} (\texttt{unowned(unsafe)}
        = dangling pointer, UB).
  \item \textbf{Ownership $\neq$ nil-ability.} Two independent dials:
        \texttt{?} answers \emph{``can it be nil?''};
        \texttt{strong/weak/unowned} answers \emph{``does it keep the object
        alive?''}. \texttt{var x: Foo?} retains exactly as hard as
        \texttt{var x: Foo}.
  \item \textbf{weak vs unowned:} can the referent die while I still hold the
        ref? \emph{Maybe} $\to$ \texttt{weak} (delegates, async callbacks —
        the safe default). \emph{Never — it outlives me} $\to$
        \texttt{unowned} (child that cannot exist without its parent:
        in \texttt{CreditCard}, \texttt{unowned let customer: Customer}).
  \item \textbf{Cycle shapes:} (1) \textbf{delegate} — make the protocol
        \texttt{: AnyObject} and the property \texttt{weak var delegate};
        (2) \textbf{closure} stored on \texttt{self} that captures
        \texttt{self} (completion handlers, \texttt{lazy var}, Combine
        \texttt{sink}); (3) \textbf{parent/child back-refs} — parent owns
        children strongly, \texttt{child.parent}, \texttt{list.prev} are
        \texttt{weak}; (4) \textbf{Timer} — the run loop retains it: also
        \texttt{invalidate()}.
  \item \textbf{Capture list \texttt{[x]}} = a \textbf{by-value snapshot taken
        when the closure is created}. Without a list the closure captures the
        \emph{variable} (sees later writes). For a \textbf{reference type} the
        snapshot copies the \emph{pointer}: same object, so later mutations of
        the object \emph{are} visible. \texttt{[weak self]} is the same
        snapshot, just non-owning. Mix: \texttt{[weak self, count]}.
  \item Only \textbf{escaping} closures stored (directly or transitively) on
        \texttt{self} need \texttt{[weak self]}; \texttt{map}/\texttt{forEach}
        are non-escaping.
\end{itemize}

\section{Example — break the cycle, prove it}
\begin{lstlisting}[language=SwiftSheet]
final class VM {
  var onChange: (() -> Void)?         // VM owns the closure
  func bind() {
    onChange = { [weak self] in       // closure does NOT own VM
      guard let self else { return }  // unwrap once
      self.reload() }
  }
  func reload() {}
}
func test_VM_deallocates() {
  var sut: VM? = VM(); sut?.bind()
  weak var weakSut = sut              // observer, not an owner
  sut = nil                           // drop the last strong ref
  XCTAssertNil(weakSut, "VM leaked - retain cycle?")
}
\end{lstlisting}

\section{Memory tools — name them}
\begin{itemize}
  \item \textbf{Xcode Memory Graph Debugger} (debug-bar button): live objects +
        \emph{who holds whom}; purple \textbf{!} = leak/cycle. Turn on
        \emph{Malloc Stack Logging} to see where it was allocated.
  \item \textbf{Instruments — Leaks}: finds \emph{unreachable} blocks (true leaks, cycles).
  \item \textbf{Instruments — Allocations}: heap growth over time;
        \emph{Mark Generation} exposes \emph{abandoned} memory (reachable but
        never freed, e.g. an unbounded cache).
  \item \textbf{Zombies}: use-after-free / \texttt{EXC\_BAD\_ACCESS}.
\end{itemize}

\columnbreak

\section{Picture — the two dials}
\begin{tikzpicture}[sheet]
  \node[hdr] at (1.3,0.55) {\texttt{Foo} (non-optional)};
  \node[hdr] at (3.9,0.55) {\texttt{Foo?} (optional)};
  \node[hdr, anchor=east] at (-0.05,0) {strong};
  \node[hdr, anchor=east] at (-0.05,-0.9) {weak};
  \node[hdr, anchor=east] at (-0.05,-1.8) {unowned};
  \node[mc, fill=sheetBlue!12] at (1.3,0) {\textbf{owns} (+1)\\never nil};
  \node[mc, fill=sheetRed!15, draw=sheetRed, very thick] at (3.9,0) {\textbf{still owns} (+1)\\may be nil — \textbf{not weak!}};
  \node[mc, fill=black!6, text=sheetGrey] at (1.3,-0.9) {compile error\\(weak must be \texttt{?})};
  \node[mc, fill=sheetGreen!12] at (3.9,-0.9) {non-owning\\\textbf{auto-nil} on dealloc};
  \node[mc, fill=sheetOrange!12] at (1.3,-1.8) {non-owning\\\textbf{crash} if dead};
  \node[mc, fill=sheetOrange!6] at (3.9,-1.8) {non-owning (Swift 5+)\\not auto-nilled};
  \node[note, anchor=west] at (5.25,-0.9) {\texttt{?} = can\\be nil};
  \node[note, anchor=west] at (5.25,-1.6) {keyword =\\keeps alive?};
\end{tikzpicture}

\section{Picture — cycles and their fix}
\begin{tikzpicture}[sheet]
  \tikzset{obj/.append style={minimum width=12mm}}
  % closure: leak
  \node[ttl, anchor=west] at (-0.3,1.0) {closure — leak};
  \node[obj] (vc) at (0.4,0) {VC};
  \node[obj, draw=sheetOrange, fill=sheetOrange!8] (cl) at (2.6,0) {closure};
  \draw[cyc] (vc.north east) to[bend left=35] node[lbl, above]{stores} (cl.north west);
  \draw[cyc] (cl.south west) to[bend left=35] node[lbl, below]{captures \texttt{self}} (vc.south east);
  % closure: fixed
  \node[ttl, anchor=west] at (3.8,1.0) {closure — fixed};
  \node[obj] (vc2) at (4.5,0) {VC};
  \node[obj, draw=sheetOrange, fill=sheetOrange!8] (cl2) at (6.7,0) {closure};
  \draw[strong] (vc2.north east) to[bend left=35] node[lbl, above]{stores} (cl2.north west);
  \draw[weakr] (cl2.south west) to[bend left=35] node[lbl, below]{\texttt{[weak self]}} (vc2.south east);
  % parent / child
  \node[ttl, anchor=west] at (-0.3,-1.25) {tree / list};
  \node[obj] (p) at (0.4,-2.15) {Parent};
  \node[obj] (c1) at (2.6,-1.75) {Child};
  \node[obj] (c2) at (2.6,-2.55) {Child};
  \draw[strong] (p.east) -- (c1.west);
  \draw[strong] (p.east) -- (c2.west);
  \draw[weakr] (c2.south) to[bend left=30] node[lbl, below=1.5pt]{\texttt{weak var parent}} (p.south);
  % delegate
  \node[ttl, anchor=west] at (3.8,-1.25) {delegate};
  \node[obj] (own) at (4.5,-2.15) {VC};
  \node[obj] (tv) at (6.7,-2.15) {tableView};
  \draw[strong] (own.north east) to[bend left=35] node[lbl, above]{owns} (tv.north west);
  \draw[weakr] (tv.south west) to[bend left=35] node[lbl, below]{\texttt{weak var delegate}} (own.south east);
  % legend
  \draw[strong] (-0.3,-3.45) -- ++(0.5,0) node[lbl, right]{strong (+1)};
  \draw[weakr] (1.8,-3.45) -- ++(0.5,0) node[lbl, right]{weak / unowned};
  \draw[cyc] (4.2,-3.45) -- ++(0.5,0) node[lbl, right]{strong loop = leak};
\end{tikzpicture}

\section{Interview traps}
\begin{itemize}
  \trap{\textbf{``Optional means non-owning''} — your playground mistake.
        \texttt{var parent: Node?} is \emph{strong}; write \texttt{weak}.}
  \trap{\textbf{\texttt{[x]} is by value}, not by reference (Q12): snapshot at
        creation. Reference type $\to$ the \emph{pointer} is copied.}
  \trap{Dealloc test (Q17): \texttt{weak} ref $\to$ set strong to \texttt{nil}
        $\to$ \texttt{XCTAssertNil}. A \texttt{deinit} print is only the manual version.}
  \trap{Tools (Q18, you did not know): Memory Graph, Leaks, Allocations.}
  \trap{``No leaks'' but memory grows $\to$ abandoned memory $\to$ Allocations.}
  \trap{\texttt{[unowned self]} in a network callback that outlives the VC = crash.}
  \trap{\texttt{Timer} + \texttt{[weak self]}: the run loop still holds the timer — \texttt{invalidate()}.}
\end{itemize}

\section{Remember}
\textbf{``?'' asks \emph{may it be nil}; the keyword asks \emph{do I keep it
alive}.} Arrows point \emph{down} the tree strong, \emph{back up} weak.
Tools: \textbf{G}raph, \textbf{L}eaks, \textbf{A}llocations — ``GLA''.

\section{Likely questions}
\begin{enumerate}
  \item ARC vs GC? — compile-time counting, deterministic, no pauses, no cycle collection.
  \item When \texttt{unowned}? — referent provably outlives the reference.
  \item \texttt{var n=1; f=\{[n] in print(n)\}; n=99; f()}? — prints \textbf{1}.
  \item Why \texttt{weak} must be \texttt{var} + \texttt{?} — ARC writes \texttt{nil} into it.
  \item Does \texttt{self.x} in a closure capture \texttt{self}? — yes, strongly.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} value vs reference · closures (\texttt{@escaping}) · delegate pattern · Timer / NotificationCenter tokens · Combine \texttt{AnyCancellable} · Zombies / ASan}

\end{document}
