
//
// ViewController.swift
// tableview
//
// Created by comsoft on 2026/04/13.
//
import UIKit // UIKit 프레임워크 가져오기 (UI 구성 요소 사용을 위해 필수)
var name = ["학식","분식","짜장","왜안되","양고기"] // 테이블 뷰 각 셀에 표시할 문자열 배열 (전역 변수)
// ViewController 클래스 선언
// UIViewController를 상속하고, UITableViewDelegate와 UITableViewDataSource 프로토콜을 채택
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var Table: UITableView! // 스토리보드에서 연결된 테이블 뷰 아울렛 (weak: 강한 순환 참조 방지)
// [필수 메서드] 각 섹션에 표시할 행(row)의 개수를 반환
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5 // 각 섹션마다 5개의 행을 표시
}
// [선택 메서드] 사용자가 특정 셀을 탭했을 때 호출
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.description) // 선택된 셀의 섹션과 행 번호를 콘솔에 출력
}
// [필수 메서드] 각 행(indexPath)에 표시할 셀을 구성하고 반환
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 재사용 큐에서 "myCell" 식별자를 가진 셀을 꺼내오고 MyTableViewCell 타입으로 강제 캐스팅
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
cell.MyLabel.text = name[indexPath.row] // 셀의 레이블에 name 배열의 값을 행 번호 기준으로 설정
// ⚠️ 섹션이 7개이고 행이 5개이므로 총 35개 셀이 생성되지만
// name 배열은 5개뿐이라 indexPath.row(0~4)만 반복 사용됨
print(indexPath.row) // 현재 셀의 행 번호를 콘솔에 출력
return cell // 구성된 셀 반환
}
// [선택 메서드] 테이블 뷰의 섹션(구역) 개수를 반환
func numberOfSections(in tableView: UITableView) -> Int {
return 7 // 테이블 뷰를 7개의 섹션으로 나눔
// ⚠️ 섹션이 7개, 행이 5개이므로 name 배열(5개)과 행 수는 맞지만
// 섹션 구분 의미가 없는 상태 (모든 섹션이 동일하게 표시됨)
}
// 뷰가 메모리에 로드된 직후 호출되는 생명주기 메서드
override func viewDidLoad() {
super.viewDidLoad() // 부모 클래스(UIViewController)의 viewDidLoad 먼저 실행
Table.delegate = self // 테이블 뷰의 delegate를 현재 ViewController로 지정 (이벤트 처리 담당)
Table.dataSource = self // 테이블 뷰의 dataSource를 현재 ViewController로 지정 (데이터 제공 담당)
}
}
문제내용
| 배열 범위 초과 위험 | name 배열은 5개인데 섹션이 7개 × 행 5개 = 35셀 → indexPath.row는 0~4라서 현재는 괜찮지만, 행 수가 5를 넘으면 크래시 발생 |
| 섹션 의미 없음 | 7개 섹션이 모두 동일한 데이터를 보여줌 → 섹션 헤더나 다른 데이터 분기가 없으면 의미 없는 구조 |
| return 5 하드코딩 | name.count로 바꾸면 배열 크기 변경 시 자동 반영되어 더 안전함 |
스위프트의 옵셔널 체이닝을 최대한 쉽게 12살에게 설명해줘
옵셔널 체이닝 쉽게 이해하기 🍎
먼저 "옵셔널"이 뭔지부터!
친구한테 선물 상자를 받았다고 상상해봐.
그 상자 안에는...
- 🎁 선물이 있을 수도 있고
- 📦 아무것도 없을 수도 있어 (빈 상자)
스위프트에서 옵셔널이 딱 이거야.
"값이 있을 수도 있고, 없을 수도 있는 것"
문제 상황
var 학생이름: String? = "민준"
// ↑
// 물음표 = "있을 수도, 없을 수도!"
근데 이 이름을 그냥 쓰려고 하면...
print(학생이름.count) // ❌ 오류!!
스위프트가 이렇게 말해:
"야, 그 안에 진짜 값이 있는지 없는지 확인도 안 하고 쓰면 어떡해!" 😤
옵셔널 체이닝 등장! ?
상자를 열어보지 않고도 "있으면 써, 없으면 그냥 넘어가" 하는 방법이야.
print(학생이름?.count) // ✅ 물음표 하나로 해결!
// ↑
// "값이 있으면 .count 해줘,
// 없으면 그냥 nil(빈값) 줘"
실생활 예시로 이해하기 🏫
class 학교 {
var 교장선생님: 선생님? // 교장이 있을 수도, 없을 수도
}
class 선생님 {
var 이름: String = "김선생"
var 핸드폰: 핸드폰? // 핸드폰이 있을 수도, 없을 수도
}
class 핸드폰 {
var 번호: String = "010-1234-5678"
}
교장선생님 핸드폰 번호를 알고 싶어!
let 우리학교 = 학교()
// 😱 옵셔널 체이닝 없이 하면 이렇게 복잡해:
if let 교장 = 우리학교.교장선생님 {
if let 폰 = 교장.핸드폰 {
print(폰.번호)
}
}
// 😎 옵셔널 체이닝 쓰면 한 줄로 끝!
print(우리학교.교장선생님?.핸드폰?.번호)
// ↑ ↑
// "교장 있어?" "폰 있어?"
체이닝이 뭔 뜻이냐면... ⛓️
체인(chain) = 쇠사슬 이잖아.
우리학교 → 교장선생님? → 핸드폰? → 번호
🏫 ⛓️ 👨🏫 ⛓️ 📱 ⛓️ 📞
이렇게 ?로 연결연결 하는 게 옵셔널 체이닝이야!
핵심 정리 ✅
| 중간에 값이 있으면 | 쭉쭉 진행해서 최종 값 반환 |
| 중간에 값이 없으면 | 거기서 멈추고 nil 반환 |
// 교장선생님이 없는 경우
우리학교.교장선생님 = nil
print(우리학교.교장선생님?.핸드폰?.번호)
// 결과: nil <- 교장이 없으니 거기서 그냥 멈춤!
// 오류가 나지 않아! 그냥 "없어요~" 하고 끝 😊
💡 한 줄 요약: 옵셔널 체이닝은 "혹시 없을 수도 있는 것들을 안전하게 꺼내 쓰는 방법" 이야. 없으면 오류 대신 그냥 nil을 돌려줘서 앱이 터지지 않아! 🛡️
스위프트에서 옵셔널 체이닝을 사용하는 이유는 다음과 같습니다.
---
### 1. 안전한 값 접근
옵셔널 체이닝을 사용하면 옵셔널 값이 `nil`인지 매번 확인하지 않고도 해당 값의 속성이나 메서드에 안전하게 접근할 수 있습니다. 만약 중간에 옵셔널 값이 `nil`이라면, 전체 표현식이 자동으로 `nil`이 되어 런타임 오류 없이 안전하게 처리됩니다.
### 2. 코드 간결성 및 가독성 향상
일일이 옵셔널 바인딩(`if let` 또는 `guard let`)을 여러 번 사용하는 대신, `?.` 연산자를 연결하여 한 줄로 표현 가능하기 때문에 코드가 훨씬 간결해지고 읽기 쉬워집니다.
### 3. 런타임 크래시 예방
옵셔널 체이닝 없이 강제로 값을 추출하려 할 때 `nil`이면 앱이 크래시됩니다. 옵셔널 체이닝은 이러한 위험을 줄여주어 앱 안정성을 높여줍니다.
---
### 요약
옵셔널 체이닝은 옵셔널인 값 안에 있는 속성, 메서드, 서브스크립트에 안전하고 간단하게 접근할 수 있게 하여 코드를 깔끔하게 만들고 예외 상황 발생을 예방하는 중요한 문법입니다.
필요하시면 예제 코드와 함께 더 자세히도 설명 가능하니 말씀해 주세요.
프로그래밍언어에서 예외처리하는 방법을 표로 정리해줘
프로그래밍 언어별 예외처리 비교 🛡️
탭을 클릭하면 각 언어별 상세 설명과 코드 예제를 볼 수 있어요!
핵심 차이점 요약:
- finally 없는 언어 → Swift는 defer, **C++**는 RAII 패턴으로 대체
- Python만 except 키워드 사용, 나머지는 모두 catch
- Python만 raise로 예외 던지기, 나머지는 모두 throw
- Swift만 예외 던지는 함수에 throws를 반드시 명시
- Kotlin은 try를 값처럼 쓸 수 있는 유일한 언어
- Java는 Checked / Unchecked 예외를 엄격하게 구분
프로그래밍 언어의 예외처리 방법을 비교하는 표를 작성하기 위해 필요한 주요 요소는 다음과 같습니다.
1. 언어 이름 (예: Swift, Java, Python 등)
2. 예외 처리 구문 및 키워드 (try, catch, throw 등)
3. 예외 던지기 방법
4. 예외 잡기 및 처리 방법
5. 특이사항 또는 특징 (예: 옵셔널과 연계, checked/unchecked 예외 등)
이제 위 내용을 바탕으로 한국어로 정중하고 간결한 설명과 함께 표를 만들어 드리겠습니다.
---
| 프로그래밍 언어 | 예외 처리 구문 및 키워드 | 예외 던지기 방법 | 예외 잡기 및 처리 방법 | 특이사항 및 특징 |
|---|------------------------------|--------------------------|----------------------------|------------------------------------------|
| Swift | `do`, `try`, `catch`, `throw` | `throw` 키워드 사용 | `do` 블록 내 `try` 사용, `catch`로 처리 | `try?` 옵셔널 반환, `try!` 강제 처리 가능 |
| Java | `try`, `catch`, `finally`, `throw`, `throws` | `throw` 키워드 사용 | `try` 블록에 `catch` 및 `finally` 추가 | Checked 예외와 Unchecked 예외 구분 |
| Python | `try`, `except`, `finally`, `raise` | `raise` 키워드 사용 | `try` 블록 내 `except`로 처리 | 다양한 예외 클래스, 다중 예외 처리 가능 |
| C# | `try`, `catch`, `finally`, `throw` | `throw` 키워드 사용 | `try` 블록에 `catch` 및 `finally` 추가 | LINQ 등과 연계된 예외 처리 지원 |
| JavaScript | `try`, `catch`, `finally`, `throw` | `throw` 키워드 사용 | `try` 블록에서 `catch`로 처리 | 비동기 처리시 `Promise`와 함께 사용 |
| Kotlin | `try`, `catch`, `finally`, `throw` | `throw` 키워드 사용 | `try` 블록 내 `catch`로 처리 | Checked 예외 없음, 간결한 구문 지원 |
---
### 간략 설명
- 대부분 언어가 `try`-`catch` 구조를 기본으로 하여 예외를 잡고 처리합니다.
- 예외를 던질 때는 `throw` 혹은 `raise` 키워드를 쓰며, 처리하지 않는 예외는 상위 호출부로 전파합니다.
- Swift는 옵셔널과 연계된 `try?` 등 특이한 문법을 제공하여 예외 처리와 값 추출을 쉽게 합니다.
- Java는 Checked 예외(강제 처리 필요)와 Unchecked 예외를 구분해 예외 관리에 엄격함을 더합니다.
- Python은 문법이 간단하고 다양한 예외 타입과 다중 예외 처리를 지원합니다.
- JavaScript는 비동기 작업과 함께 예외 처리를 조합해 활용할 수 있습니다.
필요하시면 특정 언어별 자세한 예제도 함께 준비해 드릴 수 있습니다.
Python 예외처리
구성요소키워드설명
| 예외 처리 블록 | try: | 예외 발생 가능한 코드 작성 |
| 예외 잡기 | except 예외타입: | 특정 예외 처리, 여러 개 나열 가능 |
| 성공 시 실행 | else: | 예외 없이 성공했을 때만 실행 (선택) |
| 항상 실행 | finally: | 예외 여부 관계없이 항상 실행 |
| 예외 던지기 | raise | 예외 발생시키기 |
| 예외 정의 | class ... (Exception) | Exception 상속으로 커스텀 예외 생성 |
Swift 예외처리
구성요소키워드설명
| 예외 정의 | enum ... : Error | Error 프로토콜을 채택한 열거형으로 정의 |
| 예외 던지기 | throw | 함수 내에서 예외 발생시킴 |
| 던지는 함수 선언 | throws | 함수 시그니처에 반드시 명시 |
| 예외 처리 블록 | do { try } catch | try 앞에 do 블록 필수 |
| 정리 코드 | defer | finally 대신 사용, 블록 종료 시 실행 |
| 강제 실행 | try! | 예외 없다고 확신 시 사용 (없으면 크래시) |
| 옵셔널 변환 | try? | 실패 시 nil 반환 |

AVAudioPlayer
class AVAudioPlayer
Mentioned in
Overview
Use an audio player to:
- Play audio of any duration from a file or buffer
- Control the volume, panning, rate, and looping behavior of the played audio
- Access playback-level metering data
- Play multiple sounds simultaneously by synchronizing the playback of multiple players
For more information about preparing your app to play audio, see Configuring your app for media playback.
Important
For more advanced playback capabilities, like playing streaming or positional audio, use AVAudioEngine instead.
Topics

스위프트에서 do catch 블럭을 이용해서 예외처리를하는법을 알려줘
Swift do-catch 예외처리 완벽 가이드
기본 구조
do {
try 예외가_발생할_수_있는_함수() // 실행 시도
} catch 특정에러 {
// 특정 에러 처리
} catch {
// 나머지 모든 에러 처리
}
Step 1 - 에러 타입 정의
// Error 프로토콜을 채택한 열거형으로 에러 종류 정의
enum 주문에러: Error {
case 재고없음
case 결제실패
case 배달불가(지역: String) // 연관값으로 추가 정보 전달 가능
}
Step 2 - throws 함수 만들기
// throws 키워드 = "나는 에러를 던질 수 있어요!" 선언
func 주문하기(메뉴: String, 재고: Int) throws -> String {
if 재고 <= 0 {
throw 주문에러.재고없음 // 에러 던지기
}
if 메뉴 == "금지메뉴" {
throw 주문에러.결제실패 // 에러 던지기
}
return "\(메뉴) 주문 완료!" // 성공 시 반환
}
Step 3 - do-catch로 처리하기
do {
let 결과 = try 주문하기(메뉴: "파스타", 재고: 5)
print(결과) // "파스타 주문 완료!"
} catch 주문에러.재고없음 {
// 특정 에러만 잡기
print("재고가 없습니다!")
} catch 주문에러.결제실패 {
print("결제에 실패했습니다!")
} catch 주문에러.배달불가(let 지역) {
// 연관값 꺼내기
print("\(지역)은 배달이 안 돼요!")
} catch {
// 위에서 못 잡은 나머지 모든 에러
// 'error' 키워드로 에러 접근 가능
print("알 수 없는 에러: \(error)")
}
defer - finally 대신 쓰는 정리 코드
func 파일열기() throws {
defer {
print("무조건 마지막에 실행!") // 에러가 나도 항상 실행
// 파일 닫기, 네트워크 연결 해제 등 정리 코드 여기에
}
// 에러가 발생해도 defer는 반드시 실행됨
throw 주문에러.재고없음
}
try? - 에러를 nil로 변환
// 에러가 나면 nil 반환, 성공하면 Optional 값 반환
let 결과 = try? 주문하기(메뉴: "파스타", 재고: 0)
print(결과) // nil (에러 발생)
// if let 과 함께 자주 사용
if let 성공결과 = try? 주문하기(메뉴: "파스타", 재고: 5) {
print(성공결과) // "파스타 주문 완료!"
}
try! - 강제 실행 (⚠️ 위험!)
// 에러가 절대 안 난다고 확신할 때만 사용
// 에러 발생 시 앱이 즉시 크래시!!
let 결과 = try! 주문하기(메뉴: "파스타", 재고: 5)
// ❌ 이러면 앱 터짐!
let 위험 = try! 주문하기(메뉴: "파스타", 재고: 0)
실전 예제 - 전체 코드
import Foundation
// 1. 에러 정의
enum 로그인에러: Error {
case 아이디없음
case 비밀번호틀림
case 계정잠김(남은시간: Int)
}
// 2. throws 함수
func 로그인(아이디: String, 비번: String) throws -> String {
guard 아이디 == "user123" else {
throw 로그인에러.아이디없음
}
guard 비번 == "pass123" else {
throw 로그인에러.비밀번호틀림
}
return "환영합니다, \(아이디)님!"
}
// 3. do-catch 처리
do {
let 메시지 = try 로그인(아이디: "user123", 비번: "틀린비번")
print(메시지)
} catch 로그인에러.아이디없음 {
print("존재하지 않는 아이디입니다")
} catch 로그인에러.비밀번호틀림 {
print("비밀번호가 틀렸습니다") // ← 이게 출력됨
} catch 로그인에러.계정잠김(let 시간) {
print("계정 잠김. \(시간)분 후 재시도하세요")
} catch {
print("오류: \(error.localizedDescription)")
}
한눈에 정리
| do-catch | do { try } catch { } | 직접 처리 | 에러별로 다르게 처리할 때 |
| try? | let x = try? func() | nil 반환 | 실패해도 괜찮을 때 |
| try! | let x = try! func() | 앱 크래시 | 100% 성공 확신할 때만 |
| defer | defer { } | 항상 실행 | 자원 정리가 필요할 때 |
💡 실무 팁:
- try!는 최대한 피하기 → 앱이 터질 수 있어요!
- 에러 종류가 많으면 enum으로 깔끔하게 정리하기
- defer는 함수 시작부분에 써두면 정리코드를 까먹지 않아요
Call can throw, but it is not marked with 'try' and the error is not handled 이런 에러는 왜뜨니?
"Call can throw, but it is not marked with 'try'" 에러
이 에러는 throws 함수를 호출할 때 try를 안 붙였기 때문이에요!
원인 한눈에 보기
func 주문하기() throws -> String {
throw 주문에러.재고없음
}
// ❌ 에러 발생!
let 결과 = 주문하기()
// ~~~~~~~~
// 🚨 Call can throw, but it is not marked with 'try'
Swift는 이렇게 말하는 거예요:
"이 함수는 에러를 던질 수 있는데, 너 try 안 붙였잖아! 에러 처리는 어떻게 할 거야?" 😤
해결 방법 3가지
방법 1 - do-catch로 제대로 처리 ✅ (권장)
do {
let 결과 = try 주문하기() // try 붙이기
print(결과)
} catch {
print("에러 발생: \(error)")
}
방법 2 - try? 로 nil 반환
// 에러나면 그냥 nil로 처리해도 될 때
let 결과 = try? 주문하기()
print(결과 ?? "실패")
방법 3 - try! 강제 실행 ⚠️
// 절대 에러 안 난다고 100% 확신할 때만!
// 에러 나면 앱 즉시 크래시!
let 결과 = try! 주문하기()
자주 하는 실수 패턴
// ❌ 실수 1 - do 없이 try만 씀
let 결과 = try 주문하기() // do-catch 없으면 또 에러!
// ❌ 실수 2 - try 위치가 잘못됨
do {
let 결과 = 주문하기() // try를 깜빡!
} catch { }
// ✅ 올바른 방법
do {
let 결과 = try 주문하기() // do 안에 try!
} catch { }
한 줄 요약
throws 함수 호출 = 반드시 try 붙이기 + do-catch 로 감싸기! Swift는 에러를 무시하는 걸 절대 허용하지 않아요 🛡️
[]은 배열 ,{} 블럭(어떤것의 시작과끝), () 함수의 매개변수를 나열할때 쓰는것,<> 제네릭
[],{},(),<> 프로그래밍언어에서 각 괄호들의 용도를 설명해줘
각 괄호를 클릭하면 언어별 상세 용도와 코드 예제를 볼 수 있어요!
핵심만 한 줄 요약:
- [ ] 대괄호 → 배열, 인덱스, 딕셔너리 접근
- { } 중괄호 → 코드 블록, 객체, 클로저
- ( ) 소괄호 → 함수 호출, 매개변수, 연산 우선순위
- < > 꺽쇠괄호 → 제네릭, HTML 태그, 비교 연산자
같은 괄호도 언어마다 역할이 조금씩 달라서, Swift 기준으로 보면 [ ]는 배열과 딕셔너리 둘 다 쓰고, { }는 클로저에도 많이 쓰는 게 특징이에요!
로그래밍언어에서 꺽쇠괄호의 용도를 예를 들어 설명해줘
꺽쇠괄호 < > 완벽 가이드
1. 제네릭 (Generic) — 가장 많이 쓰는 용도
제네릭 = "어떤 타입이든 받을 수 있는 틀"
// ❌ 제네릭 없이 → 타입마다 함수를 따로 만들어야 함
func 정수출력(값: Int) { print(값) }
func 문자출력(값: String) { print(값) }
func 실수출력(값: Double) { print(값) }
// ✅ 제네릭 사용 → 함수 하나로 모든 타입 처리!
func 출력<T>(값: T) { // <T> = "T 자리에 어떤 타입이든 와도 돼"
print(값)
}
출력(값: 123) // T = Int
출력(값: "안녕") // T = String
출력(값: 3.14) // T = Double
2. 컬렉션 타입 선언
// Swift
var 숫자목록: Array<Int> = [1, 2, 3] // [Int] 와 동일
var 이름목록: Array<String> = ["민준"]
var 딕셔너리: Dictionary<String, Int> = ["나이": 20]
var 집합: Set<Int> = [1, 2, 3]
// Java / Kotlin
List<String> names = new ArrayList<>();
Map<String, Int> dict = new HashMap<>();
// TypeScript
let numbers: Array<number> = [1, 2, 3];
3. 제네릭 클래스 / 구조체
// 어떤 타입이든 담을 수 있는 상자
struct 상자<T> {
var 내용물: T
func 꺼내기() -> T {
return 내용물
}
}
let 정수상자 = 상자(내용물: 42)
let 문자상자 = 상자(내용물: "선물")
let 배열상자 = 상자(내용물: [1, 2, 3])
print(정수상자.꺼내기()) // 42
print(문자상자.꺼내기()) // "선물"
4. 타입 제약 (where, :)
// T는 아무 타입이나 안 되고, 숫자(Numeric)만 허용!
func 합계<T: Numeric>(배열: [T]) -> T {
return 배열.reduce(0, +)
}
print(합계(배열: [1, 2, 3])) // 6 (Int)
print(합계(배열: [1.1, 2.2, 3.3])) // 6.6 (Double)
// print(합계(배열: ["a", "b"])) // ❌ 컴파일 에러! String은 Numeric이 아님
5. HTML / XML 태그
<!-- 여는 태그 <태그명> 닫는 태그 </태그명> -->
<html>
<body>
<h1>제목</h1>
<p>문단</p>
<img src="사진.jpg" /> <!-- 단독 태그 -->
</body>
</html>
6. C++ 템플릿 & 스트림
#include <vector>
#include <iostream>
// 템플릿 - Swift 제네릭과 같은 개념
template<typename T>
T 더하기(T a, T b) {
return a + b;
}
더하기<int>(1, 2); // → 3
더하기<double>(1.1, 2.2); // → 3.3
// << >> 스트림 연산자 (꺽쇠 2개)
std::cout << "출력" << std::endl; // 화면에 출력
std::cin >> 변수; // 입력 받기
// 컨테이너 타입
std::vector<int> v = {1, 2, 3};
std::map<std::string, int> m;
7. 비교 연산자 (꺽쇠의 또 다른 얼굴)
// 꺽쇠는 괄호 역할도 하고, 비교 연산자 역할도 해요!
let a = 5
let b = 10
if a < b { print("a가 더 작다") } // 미만
if a > b { print("a가 더 크다") } // 초과
if a <= b { print("a가 이하") } // 이하
if a >= b { print("a가 이상") } // 이상
언어별 꺽쇠 용도 한눈에 보기
| 제네릭 | Array<Int> | List<String> | vector<int> | Array<number> | - |
| 템플릿 | - | - | template<T> | - | - |
| HTML 태그 | - | - | - | - | <div> |
| 비교 연산자 | a < b | a < b | a < b | a < b | - |
| 타입 단언 | - | - | - | <Type>값 | - |
| 스트림 | - | - | cout << | - | - |
💡 핵심 정리:
- 제네릭에서 <T> = "T 자리에 어떤 타입이든 넣을 수 있어요"
- HTML에서 <태그> = "이 안에 있는 내용을 이렇게 표시해줘"
- 비교 연산자 < > = "왼쪽이 더 작다 / 크다"
- C++에서 << >> = "데이터를 이쪽 방향으로 흘려보내" 🌊
대괄호 — Square Brackets
배열, 인덱스, 딕셔너리 접근에 주로 사용
중괄호 — Curly Brackets
코드 블록, 객체, 딕셔너리, 클로저에 사용
소괄호 — Parentheses
함수 호출, 매개변수, 연산 우선순위에 사용
꺽쇠괄호 — Angle Brackets
제네릭, HTML 태그, 비교 연산자에 사용
<div style="background-colo
var number : [Int] = [] var odd = [Int]() var even : Array = Array()
Swift 배열 선언 3가지 방법 분석
코드 뜯어보기
var number : [Int] = []
// ↑ ↑ ↑
// 변수명 타입선언 빈배열
//
// [Int] = "Int 타입만 담는 배열"
// [] = "지금은 비어있어"
// 가장 많이 쓰는 기본 방법 ✅
var odd = [Int]()
// ↑ ↑ ↑
// 변수명 타입 ()=생성자 호출
//
// [Int]() = "Int 배열을 새로 만들어줘"
// 타입 추론으로 odd의 타입이 [Int]로 자동 결정
// 두 번째로 자주 쓰는 방법 ✅
var even : Array<Int> = Array<Int>()
// ↑ ↑
// 제네릭 문법! 생성자 호출
//
// Array<Int> = 위에서 배운 제네릭 클래스!
// Array = Swift 내장 제네릭 구조체
// [Int] 는 사실 Array<Int> 의 축약형
세 줄이 완전히 동일한 이유
// 이 세 줄은 100% 동일해요!
var number : [Int] = []
var odd : [Int] = [Int]()
var even : Array<Int> = Array<Int>()
// [Int] == Array<Int>
// ↑ ↑
// 축약형 원래 제네릭 형태
// Swift가 [] 문법을 제공해서 짧게 쓸 수 있는 것!
배열에 값 넣고 사용하기
var number: [Int] = []
var odd = [Int]()
var even : Array<Int> = Array<Int>()
// 값 추가
number.append(1)
number.append(2)
number.append(3) // [1, 2, 3]
odd = [1, 3, 5, 7, 9]
even = [2, 4, 6, 8, 10]
// 접근
print(odd[0]) // 1
print(even.first!) // 2
print(number.count) // 3
// ❌ 타입이 다르면 에러!
number.append("안녕") // 컴파일 에러!
// ↑
// Int 배열에 String 넣으면 안됨!
선언 방법 총정리
// 방법 1 - 타입 명시 + 빈 배열
var a: [Int] = []
// 방법 2 - 타입 추론 + 초기값
var b = [1, 2, 3] // [Int] 자동 추론
// 방법 3 - 생성자 호출
var c = [Int]()
// 방법 4 - 제네릭 원형
var d: Array<Int> = Array<Int>()
// 방법 5 - 초기값으로 채우기
var e = [Int](repeating: 0, count: 5) // [0, 0, 0, 0, 0]
한눈에 비교
| 기본형 | var a: [Int] = [] | 가장 많이 쓰는 방법 |
| 추론형 | var b = [1, 2, 3] | 초기값으로 타입 자동 결정 |
| 생성자형 | var c = [Int]() | 생성자 호출 방식 |
| 제네릭 원형 | var d: Array<Int> | [Int]의 원래 모습 |
| 반복 초기화 | [Int](repeating:count:) | 같은 값으로 n개 채우기 |
💡 핵심 포인트:
- [Int]는 Array<Int>의 축약형이에요
- 아까 배운 Box<T> 처럼 Array<T>도 제네릭 구조체예요
- Swift의 Array, Dictionary, Set 모두 내부적으로 제네릭으로 만들어졌어요! 🎯
let number : [Int] = [] //빈 배열을 let으로 만들 수는 있지만 초기값에서 변경 불가이니 배열의 의미 없음 var odd = [Int]() var even : Array = Array() print(number) //[] print(number[0]) //오류, 빈 배열을 값을 넣은 다음에 접근 number.append(100) //let으로 선언한 불변형 배열이라 추가 불가능 //error: cannot use mutating member on immutable value: 'number' is a 'let' constant print(number[0]) number.append(200) print(number[0], number[1],number) 가변형(mutable) var animal = ["dog", "cat","cow"] 불변형 (immutable) 초기화 후 변경 불가 let animal1 = ["dog", "cat","cow"]
Swift 배열 var vs let 완벽 정리
핵심 개념 먼저
var = 변수 (Variable) → 변경 가능 ✅
let = 상수 (Constant) → 변경 불가 ❌
코드 한 줄씩 분석
let number: [Int] = []
// ^^^
// let = 불변! 빈 배열로 고정됨
// 처음부터 [] 이고 영원히 []
// 배열인데 값을 못 넣으니 사실상 의미없음 🤷
var odd = [Int]() // 변경 가능한 빈 Int 배열
var even: Array<Int> = Array<Int>() // 위와 동일
print(number) // [] ← 빈 배열 출력은 됨
print(number[0]) // 💥 런타임 에러!!
// ↑
// 빈 배열에 인덱스 접근 → 앱 크래시!
// "Index out of range"
number.append(100)
// 💥 컴파일 에러!!
// error: cannot use mutating member on
// immutable value: 'number' is a 'let' constant
// ↑
// let으로 선언한 배열은 append 자체가 불가능!
// 컴파일 단계에서 막아버림 (앱 실행도 안됨)
print(number[0]) // 위 에러 때문에 실행 안됨
number.append(200) // 위 에러 때문에 실행 안됨
print(number[0], number[1], number) // 위 에러 때문에 실행 안됨
var 배열 vs let 배열 비교
// ✅ var - 가변형 (mutable)
var animal = ["dog", "cat", "cow"]
animal.append("bird") // ✅ 추가 가능
animal.remove(at: 0) // ✅ 삭제 가능
animal[0] = "lion" // ✅ 수정 가능
print(animal) // ["lion", "cat", "bird"]
// ❌ let - 불변형 (immutable)
let animal1 = ["dog", "cat", "cow"]
animal1.append("bird") // ❌ 컴파일 에러!
animal1.remove(at: 0) // ❌ 컴파일 에러!
animal1[0] = "lion" // ❌ 컴파일 에러!
print(animal1) // ✅ 읽기는 가능
print(animal1[0]) // ✅ "dog" 읽기는 가능
에러 종류 2가지 구분
// 🔴 컴파일 에러 - 아예 실행조차 안됨
let nums = [1, 2, 3]
nums.append(4)
// error: cannot use mutating member on immutable value
// ↑ 코드 작성 중에 Xcode가 빨간줄로 바로 알려줌
// 🟠 런타임 에러 - 실행은 되지만 도중에 터짐
var empty = [Int]()
print(empty[0])
// Fatal error: Index out of range
// ↑ 앱 실행 중에 갑자기 크래시 발생!
안전하게 배열 접근하는 방법
var animal = ["dog", "cat", "cow"]
// ❌ 위험한 방법
print(animal[5]) // 크래시!!
// ✅ 안전한 방법 1 - 범위 체크
if animal.count > 5 {
print(animal[5])
}
// ✅ 안전한 방법 2 - first, last 사용
print(animal.first ?? "없음") // "dog"
print(animal.last ?? "없음") // "cow"
// ✅ 안전한 방법 3 - 옵셔널 확장 (커스텀)
// 인덱스가 범위 밖이면 nil 반환
extension Array {
subscript(safe index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
print(animal[safe: 5] ?? "없음") // "없음" 크래시 없음!
전체 정리
| 선언 | var arr = [1,2,3] | let arr = [1,2,3] |
| 읽기 | ✅ 가능 | ✅ 가능 |
| 추가 append | ✅ 가능 | ❌ 컴파일 에러 |
| 삭제 remove | ✅ 가능 | ❌ 컴파일 에러 |
| 수정 arr[0]= | ✅ 가능 | ❌ 컴파일 에러 |
| 빈 배열 접근 | 🟠 런타임 에러 | 🟠 런타임 에러 |
💡 실무 팁:
- 변경이 필요없는 배열은 let으로 선언 → 실수로 수정하는 걸 컴파일러가 막아줌
- 배열 접근 전엔 항상 isEmpty 또는 count 확인하는 습관 들이기
- first, last 는 빈 배열이면 nil 반환이라 안전해요 🛡️

var num = [1, 2, 3, 4] print(num[0], num[3]) print(num.first!) for i in 0...num.count-1{ print(num[i]) } print(num[1...2]) num[0...2] = [10,20,30] print(num)
Swift 배열 순회 & 슬라이싱 코드 분석
한 줄씩 완전 분석
var num = [1, 2, 3, 4]
// 인덱스: 0 1 2 3
// 배열 생성, 타입 추론으로 [Int] 자동 결정
print(num[0], num[3])
// num[0] = 1 (첫 번째)
// num[3] = 4 (마지막)
// 출력: 1 4
print(num.first!)
// ↑
// first = Optional(1) 을 반환
// ! = 강제 언래핑 → 1
// 빈 배열이면 ! 때문에 💥 크래시!
// 출력: 1
for 반복문 분석
for i in 0...num.count-1 {
print(num[i])
}
// 0...num.count-1
// num.count = 4
// 4 - 1 = 3
// 0...3 = 0, 1, 2, 3
//
// i = 0 → num[0] = 1
// i = 1 → num[1] = 2
// i = 2 → num[2] = 3
// i = 3 → num[3] = 4
//
// 출력:
// 1
// 2
// 3
// 4
⚠️ 더 안전한 for문 작성법
// ❌ 위험 - num이 빈 배열이면 0...(-1) 크래시!
for i in 0...num.count-1 { }
// ✅ 안전 방법 1 - indices 사용
for i in num.indices {
print(num[i])
}
// ✅ 안전 방법 2 - 직접 순회 (가장 Swift다운 방법)
for n in num {
print(n)
}
// ✅ 안전 방법 3 - 인덱스도 필요할 때
for (index, value) in num.enumerated() {
print("\(index)번째: \(value)")
}
// 0번째: 1
// 1번째: 2
// 2번째: 3
// 3번째: 4
슬라이싱 분석
print(num[1...2])
// ^^^^^
// 1...2 = 인덱스 1부터 2까지
// num[1] = 2
// num[2] = 3
// 출력: [2, 3]
//
// ⚠️ 결과 타입은 Array가 아니라
// ArraySlice<Int> 임!
// 원본 배열의 일부를 "참조"하는 방식
num[0...2] = [10, 20, 30]
// ^^^^^ ^^^^^^^^^^^^
// 인덱스 0~2 범위를 새 값으로 교체
//
// 전: [1, 2, 3, 4]
// ↑ ↑ ↑
// [10, 20, 30] 으로 교체
// 후: [10, 20, 30, 4]
// ↑
// 3번 인덱스는 그대로!
print(num) // [10, 20, 30, 4]
범위 연산자 종류
var num = [1, 2, 3, 4, 5]
print(num[1...3]) // [2, 3, 4] 1이상 3이하 (닫힌 범위)
print(num[1..<3]) // [2, 3] 1이상 3미만 (반열린 범위)
print(num[2...]) // [3, 4, 5] 2부터 끝까지
print(num[...2]) // [1, 2, 3] 처음부터 2까지
전체 실행 결과
var num = [1, 2, 3, 4]
print(num[0], num[3]) → 1 4
print(num.first!) → 1
for i in 0...num.count-1
print(num[i]) → 1
2
3
4
print(num[1...2]) → [2, 3]
num[0...2] = [10,20,30]
print(num) → [10, 20, 30, 4]
배열 접근 방법 총정리
| 인덱스 접근 | num[0] | 1 |
| 첫 번째 (안전) | num.first | Optional(1) |
| 첫 번째 (강제) | num.first! | 1 (빈배열이면 크래시) |
| 마지막 (안전) | num.last | Optional(4) |
| 닫힌 범위 슬라이싱 | num[1...2] | [2, 3] |
| 반열린 범위 슬라이싱 | num[1..<2] | [2] |
| 범위 치환 | num[0...2] = [10,20,30] | 해당 범위 교체 |
| 전체 순회 | for n in num | 가장 Swift다운 방법 ✅ |
💡 핵심 팁:
- first, last 는 옵셔널 반환 → ! 대신 ?? 나 if let 사용 권장
- 0...num.count-1 대신 num.indices 쓰면 빈 배열 크래시 예방
- 슬라이싱 결과는 ArraySlice 타입 → 다시 배열로 쓰려면 Array(num[1...2]) 로 변환!
Swift 배열 max() min() 완벽 분석
코드 한 줄씩 분석
var num = [1, 2, 3, 10, 20]
// 인덱스: 0 1 2 3 4
print(num)
// 배열 전체 출력
// 결과: [1, 2, 3, 10, 20]
print(num.min())
// ^^^
// min() = 가장 작은 값 반환
// 반환 타입이 Optional(Int) ← 중요!
// 왜냐면 빈 배열이면 최솟값이 없으니까
// 결과: Optional(1)
print(num.max())
// ^^^
// max() = 가장 큰 값 반환
// 이것도 Optional(Int) 반환
// 결과: Optional(20)
print(num.min()!)
// ^
// ! = 강제 언래핑
// Optional(1) → 1 껍데기 벗기기
// 결과: 1
print(num.max()!)
// ^
// ! = 강제 언래핑
// Optional(20) → 20
// 결과: 20
왜 Optional을 반환할까?
// ✅ 값이 있는 배열 → 정상
var num = [1, 2, 3, 10, 20]
print(num.min()) // Optional(1)
print(num.max()) // Optional(20)
// ❌ 빈 배열 → 최댓값/최솟값이 존재하지 않음!
var empty = [Int]()
print(empty.min()) // nil ← 없으니까 nil!
print(empty.max()) // nil
// 만약 Optional이 아니었다면?
print(empty.min()!) // 💥 크래시!!
// 그래서 Swift가 Optional로 감싸서 돌려주는 것!
안전하게 사용하는 방법 3가지
var num = [1, 2, 3, 10, 20]
// 방법 1 - ! 강제 언래핑 (값 있다고 확신할 때만)
print(num.min()!) // 1
print(num.max()!) // 20
// ⚠️ 빈 배열이면 앱 크래시!
// 방법 2 - ?? 기본값 지정 (권장 ✅)
print(num.min() ?? 0) // 1 (없으면 0)
print(num.max() ?? 0) // 20 (없으면 0)
// ✅ 빈 배열이어도 0 출력, 크래시 없음!
// 방법 3 - if let 안전하게 꺼내기 (권장 ✅)
if let 최솟값 = num.min() {
print("최솟값: \(최솟값)") // 최솟값: 1
}
if let 최댓값 = num.max() {
print("최댓값: \(최댓값)") // 최댓값: 20
}
전체 실행 결과
var num = [1, 2, 3, 10, 20]
print(num) → [1, 2, 3, 10, 20]
print(num.min()) → Optional(1)
print(num.max()) → Optional(20)
print(num.min()!) → 1
print(num.max()!) → 20
min() max() 활용 예제
var scores = [85, 92, 78, 96, 61]
let 최고점 = scores.max()!
let 최저점 = scores.min()!
let 범위 = 최고점 - 최저점
print("최고점: \(최고점)") // 최고점: 96
print("최저점: \(최저점)") // 최저점: 61
print("점수범위: \(범위)") // 점수범위: 35
정리
| min() | Optional(T) | nil | 가장 작은 값 |
| max() | Optional(T) | nil | 가장 큰 값 |
| min()! | T | 💥 크래시 | 확실히 값 있을 때 |
| max()! | T | 💥 크래시 | 확실히 값 있을 때 |
| min() ?? 0 | T | 0 반환 | 안전한 방법 ✅ |
| max() ?? 0 | T | 0 반환 | 안전한 방법 ✅ |
💡 핵심 정리:
- min(), max() 는 항상 Optional 반환
- 빈 배열일 가능성이 있으면 ?? 또는 if let 사용
- 값이 반드시 있다고 확신할 때만 ! 사용 🛡️