UIKit 앱 뼈대(AppDelegate·SceneDelegate·ViewController)와 iOS 13+ 씬 생명주기 흐름을 코드로 직접 구성해 볼 것이다.
함수 타입·함수 이름 구분, 전달인자 레이블 규칙, #function, Xcode ⌥-클릭 Quick Help로 시그니처 읽는 법을 정리해 볼 것이다.
튜플 다중 반환·가변/inout·일급 함수·클로저(표현식·후행·단축 인자)와 클래스의 저장 프로퍼티/이니셜라이저 규칙을 예제로 점검해 볼 것이다.
UIKit 기반 iOS 앱의 최소 AppDelegate 예제
//
// AppDelegate.swift
// Example
//
// Created by 1 on 2025/09/30.
//
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// 앱이 시작될 때 한 번 호출된다.
// 공통 초기화, SDK 설정, 로그 설정 등을 이 지점에서 수행한다.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 앱 런치 후 커스터마이징 지점
return true
}
// MARK: - UISceneSession Lifecycle (iOS 13+)
// 새 씬(윈도우/탭)을 만들 때 어떤 설정을 사용할지 반환한다.
// 여러 씬을 지원하는 앱이라면 여기서 이름/역할에 맞는 설정을 선택한다.
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration",
sessionRole: connectingSceneSession.role)
}
// 사용자가 씬을 닫는 등 세션이 폐기되었을 때 호출된다.
// 해당 씬과 관련된 리소스를 정리하거나 저장되지 않은 데이터를 정리한다.
func application(_ application: UIApplication,
didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// 필요한 정리 작업 수행
}
}
didFinishLaunchingWithOptions에서 전역 초기화(로그, SDK, 테마)를 수행한다.
iOS 13+ 씬 라이프사이클 사용으로 화면 구성은 SceneDelegate가 맡는다.
configurationForConnecting에서 생성될 씬의 설정을 선택한다.
didDiscardSceneSessions에서 닫힌 씬의 리소스를 정리한다.
필요 시 런치 지점에 푸시, 분석, Appearance 설정 등을 추가해 확장한다.
UIKit iOS 13+의 씬 기반 생명주기를 사용하는 SceneDelegate 예제
//
// SceneDelegate.swift
// Example
//
// Created by 1 on 2025/09/30.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow? // 이 씬(Window)의 루트 컨테이너
// 앱이 이 씬에 연결될 때 한 번 호출된다.
// 스토리보드를 쓰면 window가 자동 연결되고, 코드 기반이면 여기서 직접 구성한다.
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
// (코드 기반 예시)
// let windowScene = scene as! UIWindowScene
// let win = UIWindow(windowScene: windowScene)
// win.rootViewController = UINavigationController(rootViewController: ViewController())
// win.makeKeyAndVisible()
// self.window = win
}
// 이 씬의 세션 연결이 끊겼을 때(닫힘/제거) 호출된다. 재생성 가능한 리소스를 정리한다.
func sceneDidDisconnect(_ scene: UIScene) { }
// inactive → active 로 전환 시 호출. 일시 중지했던 작업을 재개한다.
func sceneDidBecomeActive(_ scene: UIScene) { }
// active → inactive 직전 호출. 타이머/애니메이션 일시 중지 등 처리한다.
func sceneWillResignActive(_ scene: UIScene) { }
// background → foreground 진입 직전. UI 갱신/세션 복구 준비를 한다.
func sceneWillEnterForeground(_ scene: UIScene) { }
// foreground → background 진입 직후. 데이터 저장, 공유 리소스 해제 등을 수행한다.
func sceneDidEnterBackground(_ scene: UIScene) { }
}
AppDelegate가 전역 초기화를 맡고, SceneDelegate는 각 윈도우(화면) 단위의 상태와 전환을 관리한다.
scene(_:willConnectTo:)에서 스토리보드면 자동 연결, 코드 기반이면 윈도우·루트 뷰 컨트롤러를 직접 구성한다.
sceneDidBecomeActive/sceneWillResignActive는 활성화·비활성화 전환 시 작업 재개·일시중지를 처리한다.
sceneWillEnterForeground/sceneDidEnterBackground는 전경·배경 전환에 맞춰 UI 갱신, 저장, 리소스 해제를 수행한다.
sceneDidDisconnect는 씬이 닫힐 때 재생성 가능한 리소스를 정리한다.
UIKit 기반의 기본 ViewController 예제
//
// ViewController.swift
// Example
//
// Created by 1 on 2025/09/30.
//
import UIKit
class ViewController: UIViewController {
// 화면이 메모리에 로드된 직후 한 번 호출된다.
// UI 초기화, 데이터 바인딩, 의존성 주입 등을 여기서 수행한다.
override func viewDidLoad() {
super.viewDidLoad()
// 추가 설정 지점 (예: 배경색, 내비게이션 타이틀, 버튼 타깃 등)
// self.view.backgroundColor = .systemBackground
// self.title = "홈"
}
// 필요 시 viewWillAppear/viewDidAppear 등을 오버라이드해
// 화면 전환 시점에 맞춘 갱신·애니메이션을 처리한다.
}
UIViewController가 화면 한 장의 뷰와 로직을 관리한다.
viewDidLoad는 화면이 메모리에 로드된 직후 한 번 호출되며, UI 초기화·데이터 바인딩·의존성 주입을 수행한다.
코드 기반일 경우 이 지점에서 배경색, 내비게이션 타이틀, 서브뷰 추가·오토레이아웃을 구성한다.
화면 전환 시점 갱신과 애니메이션은 viewWillAppear/viewDidAppear에서 처리한다.
관찰자·타이머 등 리소스 해제는 viewWillDisappear/viewDidDisappear 또는 deinit에서 정리한다.
4가지 Swift 함수
// 호출 레이블만 다르고 타입은 모두 동일하다.
// 1) 외부레이블 = 내부이름
func add(x: Int, y: Int) -> Int { x + y }
print(add(x: 10, y: 20)) // 출력: 30
// 2) 외부레이블(first/second)과 내부이름(x/y) 분리
func add(first x: Int, second y: Int) -> Int { x + y }
print(add(first: 10, second: 20)) // 출력: 30
// 3) 외부레이블 생략(언더스코어)
func add(_ x: Int, _ y: Int) -> Int { x + y }
print(add(10, 20)) // 출력: 30
// 4) 첫 번째 생략, 두 번째만 레이블 지정
func add(_ x: Int, with y: Int) -> Int { x + y }
print(add(10, with: 20)) // 출력: 30
// 네 함수 모두 “함수 타입”은 동일하다: (Int, Int) -> Int
print(type(of: add(x:y:))) // 출력: (Int, Int) -> Int
print(type(of: add(first:second:))) // 출력: (Int, Int) -> Int
print(type(of: add(_:_:))) // 출력: (Int, Int) -> Int
print(type(of: add(_:with:))) // 출력: (Int, Int) -> Int
4가지 함수는 매개변수의 “외부 레이블(호출 시 보이는 이름)”만 다르고, 내부 구현과 반환 타입은 동일하다.
외부 레이블은 호출 가독성을 높이며 _로 생략할 수 있다. 내부 이름은 함수 본문에서만 사용한다.
with처럼 자연어 레이블을 섞어 읽기 좋은 호출문을 만들 수 있다.
함수의 타입은 레이블과 무관하게 매개변수/반환 타입으로만 결정되어 모두 (Int, Int) -> Int이다.
여러 오버로드 중 특정 함수를 참조할 때는 add(x:y:), add(_:with:)처럼 레이블까지 포함해 지칭한다.
함수 타입과 함수 이름을 구별하는 방법
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return items.count
}
// 함수 타입: (UITableView, Int) -> Int
// 함수 이름: tableView(_:numberOfRowsInSection:)
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cell
}
// 함수 타입: (UITableView, IndexPath) -> UITableViewCell
// 함수 이름: tableView(_:cellForRowAt:)
시그니처에서 매개변수의 타입만 왼쪽부터 순서대로 뽑아 괄호로 묶는다.
그 뒤에 -> 반환타입을 붙여 (Param1, Param2) -> Return 형태의 함수 타입을 적는다.
함수 이름은 기본 이름 뒤에 각 매개변수의 외부 레이블을 콜론과 함께 이어붙여 표기한다.
외부 레이블이 _이면 생략 레이블로 이름에 _를 그대로 넣어 쓴다(예: tableView(_:cellForRowAt:)).
오버로드 구분은 레이블+타입 조합으로 이루어지며, 프로토콜/익스텐션 메서드도 동일 규칙을 따른다.
식별자에 대해 도움말 보기

Xcode에서 식별자 위에 Option(⌥)+클릭하면 ‘Quick Help’ 팝오버가 열린다.
팝오버에는 해당 심볼의 선언 시그니처(타입·반환값)와 접근 수준이 표시된다.
문서 주석(///)이 있으면 요약 설명과 매개변수·반환값 설명이 함께 나온다.
‘Declared In’에서 정의된 파일/모듈을 보여 주며, 항목을 클릭하면 그 위치로 이동한다.
더 자세한 내용은 팝오버의 책 아이콘을 눌러 Developer Documentation에서 확인한다.
레이블이 붙은 튜플로 여러 값을 한 번에 반환하는 예제
import Foundation
// 두 정수 x, y를 받아 합·차·나눗셈 결과를 “레이블이 붙은 튜플”로 반환한다.
func sss(x: Int, y: Int) -> (sum: Int, sub: Int, div: Double) {
let sum = x + y // 덧셈
let sub = x - y // 뺄셈
let div = Double(x) / Double(y) // 같은 자료형끼리 연산 → Double로 변환
return (sum, sub, div) // (sum:…, sub:…, div:…) 형태로 반환
}
let result = sss(x: 10, y: 3)
print(type(of: sss)) // 출력: (Int, Int) -> (sum: Int, sub: Int, div: Double)
print(result.sum) // 출력: 13
print(result.sub) // 출력: 7
print(result.div) // 출력: 3.3333333333333335
let x = String(format: "%.2f", result.div) // 소수 둘째자리까지 반올림해 문자열로 변환
print(x) // 출력: 3.33
type(of:)는 함수의 시그니처(타입)를 보여준다.
부동소수는 이진 표현 특성상 3.3333333333333335처럼 길게 보일 수 있으며, 화면 표시는 String(format:)으로 필요한 자리수만 포맷한다.
y가 0이면 분모가 0이므로 실제 코드에서는 guard y != 0 같은 방어 로직을 두는 것이 안전하다.
가변 인자 예제
func add(numbers: Int...) {
var sum: Int = 0 // 누적 합
for num in numbers { // Int... 는 내부에서 [Int]처럼 순회 가능
sum += num
}
print(sum) // 결과 출력
}
add(numbers: 1, 2, 3) // 출력: 6

가변 인자 Int...는 호출부에서 1, 2, 3처럼 여러 값을 쉼표로 전달한다.
함수 내부에서는 numbers가 [Int]처럼 동작하여 for-in으로 순회한다.
sum에 모든 항목을 누적하여 최종 합을 print로 출력한다.
읽기 좋게 하려면 반환형을 Int로 바꾸고 return sum을 사용해 호출부가 활용하게 할 수 있다.
일급 객체
func up(num: Int) -> Int {
return num + 1
}
func down(num: Int) -> Int {
return num - 1
}
let toUp = up // 타입: (Int) -> Int — 함수 참조를 상수에 저장
print(up(num: 10)) // 출력: 11
print(toUp(10)) // 출력: 11 // 주의: 함수 참조 호출 시 argument label을 쓰지 않는다
let toDown = down // 타입: (Int) -> Int
print(down(num: 10)) // 출력: 9
print(toDown(10)) // 출력: 9
함수는 일급 객체라 변수/상수에 담을 수 있다.
let toUp = up처럼 참조로 저장하면 호출 시 라벨을 쓰지 않는다(예: toUp(10)).
함수 타입은 라벨과 무관하게 (Int) -> Int로 표현된다.
특정 구현을 지칭할 때는 up(num:)처럼 라벨까지 포함해 표시한다.
클로저 예제를 많이 사용하는 프로그래밍 언어
| 1 | JavaScript / TypeScript | 비동기·이벤트·함수형 유틸 전부 클로저 기반 | 콜백/Promise, map/filter/reduce | () => {} |
| 2 | Swift | iOS API가 클로저 중심 설계 | 콜백, completion, 고차함수 | { }, trailing closure |
| 3 | Kotlin | 컬렉션/코루틴이 람다에 의존 | DSL, scope 함수, flow | { }, it, inline |
| 4 | Python | 내부함수/데코레이터로 캡처 사용 | 함수형 유틸, 데코레이터 | def inner, nonlocal |
| 5 | Ruby | 블록/프로시저가 일상 | each/map, DSL | do … end, -> |
| 6 | C# | LINQ·이벤트에 람다 상시 | 쿼리, 이벤트 핸들러 | x => x+1 |
| 7 | Rust | 이터레이터·클로저 조합 강력 | map/filter, move 캡처 | `move |
| 8 | Java | 자바8 이후 보급, 범용성 ↑ | 스트림, 콜백 | y -> x+y |
| 9 | Go | 존재는 하지만 패턴화 적음 | 핸들러, 지연 실행 | func(...) {} |
| 10 | PHP | 가능하지만 주류 패턴은 아님 | 간단 콜백 | function() use($x){} |
클로저 표현식 예제
// 함수 버전 -------------------------------------------------
func mul(x: Int, y: Int) -> Int { // 명명된 함수
return x * y
}
let r1 = mul(x: 10, y: 20)
print(r1) // 출력: 200
// 클로저 표현식 버전 -----------------------------------------
// { (매개변수) -> 반환타입 in 본문 }
let multiply: (Int, Int) -> Int = { (x: Int, y: Int) -> Int in
return x * y
}
let r2 = multiply(10, 20) // 상수에 담긴 클로저를 함수처럼 호출
print(r2) // 출력: 200
// 타입 추론·축약 형태 ----------------------------------------
// 매개변수/반환타입은 좌변에서 추론 → 생략 가능
let multiply2: (Int, Int) -> Int = { x, y in x * y }
print(multiply2(3, 4)) // 출력: 12
클로저 기본형은 { (매개변수) -> 반환타입 in 본문 }이다.
in 앞은 시그니처, 뒤는 실행 코드이다.
좌변에 함수 타입을 명시하면 매개변수 타입과 반환타입을 생략해 축약한다.
함수 mul과 클로저 multiply의 타입은 (Int, Int) -> Int로 동일하다.
클로저는 상수/변수에 담아 함수처럼 호출한다.
후행 클로저
// trailing closure 기본 예제
func someFun(cl: () -> Void) { cl() }
// 괄호 안에 클로저
someFun(cl: {
print("A") // 출력: A
})
// trailing closure (마지막 인자가 클로저면 소괄호 밖으로)
someFun {
print("B") // 출력: B
}
// 다른 인자 + 마지막 클로저
func calc(_ a: Int, _ b: Int, op: (Int, Int) -> Int) -> Int { op(a, b) }
let r1 = calc(3, 4, op: { x, y in x + y })
print(r1) // 출력: 7
let r2 = calc(3, 4) { x, y in x * y } // trailing closure
print(r2) // 출력: 12
// 표준 라이브러리 예
let arr = [3, 1, 2]
print(arr.sorted(by: { $0 < $1 })) // 출력: [1, 2, 3]
print(arr.sorted { $0 < $1 }) // 출력: [1, 2, 3] // trailing
마지막 매개변수가 클로저면 클로저를 괄호 밖으로 뺄 수 있다.
인자가 클로저 하나뿐이면 () 자체도 생략 가능.
다른 인자가 있으면 ()는 유지하고 마지막 클로저만 밖으로.
가독성 때문에 Swift에서 권장되는 표기.
단축인자 (shorthand argument name)
let names = ["C", "A", "B"]
// 풀 버전
let s1 = names.sorted { (a: String, b: String) -> Bool in return a < b }
print(s1) // 출력: ["A", "B", "C"]
// 단축 인자($0, $1) 사용
let s2 = names.sorted { $0 < $1 }
print(s2) // 출력: ["A", "B", "C"]
// 더 축약: 비교 연산자 자체를 넘김
let s3 = names.sorted(by: <)
print(s3) // 출력: ["A", "B", "C"]
// 내림차순 예시
let s4 = names.sorted(by: >)
print(s4) // 출력: ["C", "B", "A"]
sorted는 정렬 기준을 클로저로 받는다.
$0, $1은 클로저의 첫 번째·두 번째 매개변수를 자동 참조한다.
비교만 하면 될 때는 { $0 < $1 } 대신 < 혹은 >를 바로 넘기면 된다.
Swift 클래스 선언
class Person {
var age: Int = 0 // stored
var nextAge: Int { age + 1 } // computed
static var species = "Human" // type property
func birthday() { age += 1 } // 인스턴스 메서드
class func make() -> Person { Person() } // 타입 메서드(오버라이드 가능)
}
let p = Person()
p.age = 42
print(p.nextAge) // 출력: 43
p.birthday()
print(p.age) // 출력: 43
print(Person.species) // 출력: Human
클래스는 class 이름: 부모 { 프로퍼티와 메서드 } 형태로 선언한다.
프로퍼티는 저장(stored)·계산(computed)·타입(type)으로 구분한다.
저장 프로퍼티는 값을 보관하고, 계산 프로퍼티는 get/set으로 값을 계산한다.
타입 프로퍼티는 static/class로 선언하며 모든 인스턴스가 공유한다.
메서드는 인스턴스 메서드(객체가 호출, 상태 변경 가능)와 타입 메서드(static func/class func, 타입이 호출)로 나뉜다.
저장 프로퍼티

stored property(age, weight)를 기본값 없이 선언했는데, 클래스에는 이를 채워줄 지정 이니셜라이저가 없어서 컴파일러가 init()을 만들어 줄 수 없다
오류 해결 방법
// 1) 지정 이니셜라이저 제공
class Man {
var age: Int
var weight: Double
init(age: Int, weight: Double) {
self.age = age
self.weight = weight
}
}
// 2) 기본값 부여
class Man {
var age = 0
var weight = 0.0
}
// 3) 옵션이 유효한 모델이면 Optional로
class Man {
var age: Int?
var weight: Double?
}
기본 생성자로 객체를 만들고 인스턴스 메서드를 호출하는 Swift 클래스 예제
class Man {
var age: Int = 1
var weight: Double = 3.5
init() {} // 기본 생성자(파라미터 없음)
func display() { // 인스턴스 메서드
print("나이=\(age), 몸무게=\(weight)")
}
}
var x: Int = 10
let kim: Man = Man() // 생성자 호출 → 인스턴스 생성
print(kim.age) // 출력: 1
kim.display() // 출력: 나이=1, 몸무게=3.5
Man()은 생성자를 호출한다. 메모리를 할당하고 age=1, weight=3.5로 초기화된 인스턴스를 만든다.
display는 인스턴스 메서드이다. 사용하려면 객체를 먼저 만든다.
init(){}를 명시했으므로 매개변수 없는 생성자가 존재한다. 속성에 기본값이 있어 바로 사용한다.
따라서 kim에 Man()으로 새 객체를 할당하고, 이후 kim.age와 kim.display()를 호출한다.
'iOS' 카테고리의 다른 글
| Swift in Control: Types, Flow, and Thoughtful API Design (1) | 2025.09.23 |
|---|---|
| Writing Safe Swift Code: Tuples, Optionals, and Type Inference (0) | 2025.09.16 |
| Xcode에서 겪게 되는 기초 문법 정리 (0) | 2025.09.09 |
| 아이폰 앱 만들기 전에 챙겨야 할 지식 한 스푼 (2) | 2025.09.02 |