본 도구에서 사용하는 오픈소스 라이브러리

본 도구의 코드에는 1개의 오픈소스 라이브러리가 포함되어 있습니다.

Swift 치트시트 — 간결한 참조

Swift 5.9 문법, 값 타입, 프로토콜과 가장 자주 쓰이는 표준 라이브러리를 다루는 치트시트로, 일상 작업의 약 80%를 커버합니다.

Sw

Swift Swift 5.9

LLVM (swiftc / Swift toolchain) · 다중 패러다임 (프로토콜 지향 · OO · 함수형) · 정적 · 강타입

추천 학습 경로

먼저 print와 변수 바인딩을 익히세요 → 값 타입(struct/enum)과 옵셔널 Optional을 익히세요 → 함수, 클로저와 컬렉션을 깊이 다루세요 → 프로토콜 지향 설계와 확장을 이해하세요 → do-catch와 throws로 오류를 처리하세요 → async/await과 DispatchQueue로 동시성을 작성하세요 → 필요에 따라 URLSession, 날짜, 빌드, 테스트를 배우세요. FAQ 섹션은 돌아와서 함정을 피할 때 적합합니다.

1.Hello World 및 빌드 환경

Swift 프로그램, SwiftPM 프로젝트 및 툴체인 실행.

최소 프로그램

최상위 코드가 프로그램 진입점입니다. print는 출력합니다. import로 모듈을 가져옵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 새 파일 main.swift:
import Foundation
print("Hello, world!")
// 실행:
// swift main.swift
// 컴파일 후 실행:
// swiftc main.swift -o app
// ./app
// 포인트:
// 최상위 문은 직접 실행
// main 함수 불필요 (스크립트 모드)
// print 는 자동 줄바꿈
// import Foundation 은 자주 사용

실행과 빌드

swift는 스크립트를 직접 실행합니다. swiftc는 컴파일합니다. swift run으로 패키지를 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 스크립트 직접 실행:
// swift script.swift
// 실행 파일로 컴파일:
// swiftc main.swift -o app
// ./app
// SwiftPM 패키지 실행:
// swift run
// 실행 타겟 지정:
// swift run MyTool
// 최적화 컴파일:
// swiftc -O main.swift
// 모든 소스 파일 컴파일:
// swiftc *.swift
// 디버그 정보:
// swiftc -g main.swift
// 버전 확인:
// swift --version

SwiftPM 프로젝트

swift package init로 초기화합니다. Package.swift 매니페스트입니다. Sources는 소스 코드 디렉터리입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 실행 패키지 생성:
// swift package init --type executable
// 라이브러리 패키지 생성:
// swift package init --type library
// 구조:
// Package.swift 매니페스트
// Sources/ 소스 코드
// MyTool/ 타겟 디렉터리
// Tests/ 테스트
// Package.swift:
// swift-tools-version:5.9
// import PackageDescription
// let package = Package(
// name: "MyTool",
// targets: [.executableTarget(
// name: "MyTool")]
// )
// 빌드: swift build
// 실행: swift run
// 테스트: swift test

import 가져오기

import로 모듈을 가져옵니다. Foundation이 자주 사용됩니다. 필요에 따라 하위 모듈을 가져옵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 표준 모듈 가져오기:
import Foundation
import UIKit // iOS 인터페이스
import SwiftUI // SwiftUI 인터페이스
// Foundation 제공:
// 문자열, 날짜, 파일, JSON
// 숫자, 컬렉션의 확장
// 서브모듈:
import Foundation.NSURL
// 멀티플랫폼 조건부 임포트:
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// 자신의 라이브러리 가져오기:
// import MyLibrary
// import 문은 파일 최상단

출력과 보간

print/print(items:). 문자열 보간 \(값). 구분 기호와 종료 기호.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let name = "Nick"
let age = 30
// 문자열 보간:
print("\(name) 今年 \(age) 岁")
// 다중값 출력:
print(1, 2, 3) // 공백 구분
print("a", "b", separator: "-") // a-b
print("x", terminator: "") // 줄바꿈 없음
// 디버그 출력:
debugPrint(name)
// 형식 제어:
let pi = 3.14159
print(String(format: "%.2f", pi)) // 3.14
// 배열/딕셔너리 직접 출력:
print([1, 2, 3])
print(["a": 1])
// 사용자 정의 타입은 CustomStringConvertible 구현

명령줄 인수

CommandLine.arguments로 인수를 가져옵니다. 첫 번째는 프로그램 경로입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Foundation
// 전체 매개변수:
let args = CommandLine.arguments
print("共 \(args.count) 个参数")
for (i, a) in args.enumerated() {
print("\(i): \(a)")
}
// 비즈니스 매개변수:
// args[0] 은 프로그램 경로
// args[1] 부터가 매개변수
if args.count > 1 {
let name = args[1]
print("你好 \(name)")
}
// 간단한 파싱:
// 복잡한 매개변수는 ArgumentParser
import ArgumentParser // 의존성 필요
// 종료:
// exit(0) / exit(1)

다중 파일

동일 패키지의 여러 소스 파일이 타입을 공유합니다. internal 접근 수준.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Sources/App/main.swift
let greeter = Greeter(name: "Swift")
greeter.greet()
// Sources/App/Greeter.swift
// 동일 패키지의 타입 자동 접근:
struct Greeter {
let name: String
func greet() {
print("你好,\(name)!")
}
}
// 접근 수준:
// private 파일 내
// internal 모듈 내 (기본)
// public 모듈 외부
// 컴파일:
// swiftc main.swift Greeter.swift
// SwiftPM 은 Sources/ 자동 포함
// 파일명 무관, 타입 이름만 고유하면 됨

Xcode 통합

Xcode 프로젝트 구성. target, scheme. iOS 개발 워크플로.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 프로젝트 구조:
// MyApp.xcodeproj
// Sources/
// Resources/
// Tests/
// target: 빌드 단위
// iOS App / watchOS App
// Framework / Unit Test
// scheme: 빌드 + 실행 조합
// 명령줄 빌드:
// xcodebuild -scheme MyApp build
// 테스트 실행:
// xcodebuild test -scheme MyApp
// 시뮬레이터:
// xcrun simctl list devices
// IPA 내보내기:
// xcodebuild archive
// SwiftPM 패키지 직접 드래그 가능
// SwiftPM 과의 차이:
// Xcode 는 서명과 기기를 처리

2.변수와 상수

var/let 바인딩, 타입 추론, 스코프와 명명.

var와 let

let은 변경할 수 없는 상수입니다. var는 변경 가능한 변수입니다. let 사용을 우선하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 상수:
let maxCount = 100
// maxCount = 200 // 오류: 불변
// 변수:
var score = 0
score = 10 // 수정 허용
// 타입 추론:
let name = "Swift" // String
let number = 42 // Int
// 명시적 타입:
let count: Int = 5
var price: Double = 3.5
// 명명 원칙:
// let 가능하면 let
// 수정 필요 시에만 var
// 동시 환경에서 불변성이 더 안전
// 컬렉션은 var 가변, let 읽기 전용

타입 추론

컴파일러가 리터럴과 컨텍스트를 기반으로 타입을 추론합니다. 복잡한 경우에는 명시적으로 표기합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 리터럴 추론:
let a = 42 // Int
let b = 3.14 // Double
let c = "hi" // String
let d = true // Bool
// 컨텍스트 추론:
var array: [Int] = []
array.append(1) // 요소 Int 추론
// 리터럴의 다중 타입:
let i: Int = 42
let f: Double = 42 // 허용
let u: UInt8 = 42
// 명시적 주석이 필요한 경우:
// 제네릭, 옵셔널, 복잡한 표현식
// 컴파일러 오류 시 주석 추가
// 타입 별칭:
typealias Age = Int
let age: Age = 30
// 단축키 Option+Click 으로 타입 확인

명시적 타입

콜론 뒤에 타입을 표기합니다. 가독성을 높입니다. 변환과 리터럴이 호환됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 기본 주석:
let name: String = "Swift"
let count: Int = 10
let ratio: Double = 0.5
// 옵셔널 주석:
var optional: String? = nil
// 컬렉션 주석:
var list: [Int] = [1, 2]
var dict: [String: Int] = [:]
// 튜플 주석:
var pair: (Int, String) = (1, "a")
// 함수 타입 주석:
var handler: (Int) -> Void
// 필요한 경우:
// 빈 컬렉션은 추론 불가
// 프로토콜 타입, 제네릭
// 공개 API 는 주석 필수
// 문서화와 컴파일 검사에 유용

상수

let이 상수를 선언합니다. 참조 타입 상수는 변경할 수 없는 참조입니다. 컴파일 타임 상수.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 기본 상수:
let days = 7
// 상수 배열/딕셔너리:
let fixed = [1, 2, 3]
// fixed.append(4) // 오류: 불변
// 상수 참조 타입:
class Counter { var n = 0 }
let c = Counter()
// c 불변, 그러나 객체 속성은 변경 가능:
c.n += 1 // 허용
// 전역 상수:
let appName = "GuruToolkit"
// 컴파일 타임 상수:
// static let / enum 네임스페이스
enum Config {
static let maxRetry = 3
}
print(Config.maxRetry)
// 상수는 계산 가능:
let total = days * 24

섀도잉

내부 스코프의 같은 이름 변수가 외부를 가립니다. if/switch의 로컬 상수에서 흔합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let x = 10
// 내부 가림:
if true {
let x = 20 // 외부를 가림
print(x) // 20
}
print(x) // 10
// 흔한 패턴:
// 같은 이름의 옵셔널 바인딩:
var value: Int? = 5
if let value = value {
print(value) // 새 상수 5
}
// 루프 변수 가림:
for x in 1...3 {
print(x) // 1 2 3
}
// 주의:
// 가림은 가독성 저하
// 복잡한 함수에서 다층 가림 피하기
// 옵셔널 바인딩은 가림으로 안전 해제

스코프

{}가 스코프를 정의합니다. 내부에서 외부에 접근할 수 있습니다. 외부에서 내부에 접근할 수 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let outer = 1
// 블록 스코프:
{
let inner = 2
print(outer) // 내부에서 외부 접근
print(inner)
}
// print(inner) // 오류: 스코프 벗어남
// 함수 스코프:
func demo() {
let local = "函数内"
print(local)
}
// 루프 스코프:
for i in 1...3 {
let sq = i * i
print(sq)
}
// 같은 이름 규칙:
// 같은 스코프에 중복 선언 불가
// 중첩 스코프에서는 가림 가능
// 라이프타임:
// 스코프 끝나면 변수 소멸
// 전역 변수는 프로그램 수명 내 존재

명명 규칙

Swift 명명 규약. camelCase, 명확한 동사, 약어 회피.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 변수/상수: camelCase
let userName = "nick"
var totalCount = 0
// 타입: CamelCase
struct UserAccount {}
class NetworkManager {}
enum ColorChoice {}
// 프로토콜: 동사/형용사 명명
protocol CanGreet {}
protocol Named {}
// 불리언 속성: is/has 접두사
var isEnabled = true
var hasPermission = false
// 메서드: 동사로 시작
func saveData() {}
func loadConfig() {}
// 공식 API 설계 규칙:
// 이름을 문장으로 읽기
// 약어와 모호함 피하기
// 문서 주석 /// 으로 용도 설명

타입 변환

숫자 타입의 명시적 변환. init() 이니셜라이저. 문자열과 숫자 간 상호 변환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 숫자 변환 (명시적):
let i = 42
let d = Double(i) // 42.0
let u = UInt8(i) // 42
// 자동 암묵 변환 없음:
// let x = 1 + 2.0 // 오류
let x = Double(1) + 2.0 // 정답
// 문자열을 숫자로:
let s = "42"
let n = Int(s) // Int? 옵셔널
let f = Double("3.14") // Double?
// 숫자를 문자열로:
let str = String(42)
let str2 = "\(3.14)"
// 옵셔널 해제:
if let n = Int(s) {
print(n)
}
// 절단: Int(3.9) // 3

3.타입 시스템

기본 타입, 튜플, 구조체, 열거형과 옵셔널 값.

기본 타입

Int, Double, Bool, String, Character. 값 타입.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 정수:
let i: Int = -10
let u: UInt = 10
// 부동소수:
let f: Float = 1.5 // 32비트
let d: Double = 3.14159 // 64비트
// 불리언:
let t: Bool = true
let f2: Bool = false
// 문자열과 문자:
let s: String = "hello"
let c: Character = "A"
// 타입 별칭:
let whole: Int = 42
// 정수 범위:
print(Int.min, Int.max)
// 표준 라이브러리 타입:
// Int/Double 은 값 타입
// 할당은 복사본, 참조 아님
// 메서드 호출: i.description

정수 타입

Int8-Int64, UInt 시리즈. 플랫폼 Int. 오버플로 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 부호 있음: Int8 Int16 Int32 Int64
// 부호 없음: UInt8 UInt16 UInt32 UInt64
// 플랫폼 기본: Int (64비트)
let a: Int8 = -128
let b: UInt8 = 255
let c: Int64 = 9_223_372_036_854_775_807
// 언더스코어 구분:
let big = 1_000_000
// 오버플로:
// 리터럴의 기본 오버플로는 컴파일 오류
// 연산 오버플로는 오버플로 연산자 사용:
var x: UInt8 = 250
x = x &+ 10 // 래핑 4
// &- &* &/ 오버플로 연산
// 오버플로 확인:
// x.addingReportingOverflow(10)
// 우선순위 명확할 때 타입 주석

부동소수점

Float/Double 정밀도. CGFloat UI 좌표. 연산과 특수 값.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Double 64비트 / Float 32비트
let d = 3.141592653589793
let f: Float = 3.14
// 과학 표기:
let e = 1.5e3 // 1500.0
// 리터럴은 Double 로 추론
// 연산:
let sum = d + 1.0
let sqrt = d.squareRoot()
let pow = pow(2.0, 10.0)
// 특수 값:
let nan = Double.nan
let inf = Double.infinity
nan.isNaN // true
inf.isInfinite // true
// 비교:
// 부동소수 동등 비교는 오차 허용 필요
let a = 0.1 + 0.2 // 0.30000000000000004
// 형식화:
String(format: "%.2f", a)
// 반올림:
Int(3.7) // 3
3.7.rounded() // 4.0

튜플

다중 복합 값. 명명된 요소. 구조 분해. 경량 데이터 그룹화.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 기본 튜플:
let pair = (1, "one")
print(pair.0) // 1
print(pair.1) // "one"
// 명명 요소:
let user = (name: "Nick", age: 30)
print(user.name)
print(user.age)
// 분해:
let (code, message) = (200, "OK")
print(code)
// 일부 무시:
let (x, _) = (1, 2)
// 타입 주석:
let point: (x: Double, y: Double) = (0, 0)
// 함수 복수 값 반환:
func div(a: Int, b: Int) -> (q: Int, r: Int) {
(a / b, a % b)
}
let result = div(a: 10, b: 3)
print(result.q, result.r)
// 딕셔너리 순회는 튜플 분해

구조체

struct 값 타입. 자동 멤버 이니셜라이저. 속성과 메서드.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 구조체 정의:
struct Point {
var x: Double
var y: Double
// 메서드:
func distance() -> Double {
(x * x + y * y).squareRoot()
}
// 변경 가능 메서드:
mutating func moveBy(dx: Double) {
x += dx
}
}
// 자동 이니셜라이저:
let p = Point(x: 1, y: 2)
print(p.distance())
// 값 타입 복사:
var p2 = p
p2.x = 100
print(p.x) // 1, p 영향 없음
// 계산 속성은 OOP 절 참조
// class 보다 struct 우선

열거형

enum 관련 값 그룹화. 연결 값. 원시 값. 완전 매칭.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 단순 열거형:
enum Direction {
case north, south, east, west
}
// 연관값:
enum Result2 {
case success(Int)
case failure(String)
}
// 원시값:
enum Color: String {
case red = "#FF0000"
case green = "#00FF00"
}
// 사용:
let dir = Direction.north
switch dir {
case .north: print("北")
case .south: print("南")
case .east: print("东")
case .west: print("西")
}
// 연관값 추출:
let r = Result2.success(200)
if case .success(let v) = r {
print(v)
}
// 원시값:
print(Color.red.rawValue)
// 메서드는 열거형에 추가 가능

옵셔널

Optional은 비어 있을 수 있음을 나타냅니다. T? 구문. nil은 값이 없음을 나타냅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 옵셔널 선언:
var name: String? = nil
var age: Int? = 30
// 할당:
name = "Nick"
// 해제 방법:
// 1. 강제 해제 (위험):
// print(name!)
// 2. 옵셔널 바인딩:
if let n = name {
print(n)
}
// 3. nil 병합 연산자:
let display = name ?? "未知"
// 4. 옵셔널 체인:
// name?.count
// nil 판정:
if name == nil { print("无名字") }
// 실제 타입:
// Optional<String> 은 열거형
// .some 과 .none 포함

중첩 타입

타입 내에 타입을 정의합니다. 네임스페이스 구성. 열거형이 관련 타입을 담습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 중첩 타입:
struct Player {
// 구조체 내부의 열거형:
enum State {
case idle, running, dead
}
var state: State = .idle
// 중첩 타입 별칭:
typealias Health = Int
var health: Health = 100
}
// 사용:
let p = Player()
let s: Player.State = .running
print(s)
// 중첩 타입 접근:
// Player.State
// 중첩으로 분류:
enum HTTP {
enum Status {
static let ok = 200
static let notFound = 404
}
}
print(HTTP.Status.ok)
// 장점:
// 관련 타입을 가까이 정의
// 최상위 이름 충돌 감소
// "누구에 속하는지" 관계 표현
// 깊이 중첩은 적절히 유지

4.값 타입과 참조

값 시맨틱, 클래스 참조, copy-on-write와 메모리 레이아웃.

값 vs 참조

struct는 값 시맨틱으로 복사됩니다. class는 참조를 공유합니다. 언어의 핵심 차이입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 값 타입:
struct Point { var x = 0 }
var a = Point()
a.x = 10
var b = a // 복사
b.x = 99
print(a.x) // 10, 독립
// 참조 타입:
class Box { var value = 0 }
let c = Box()
c.value = 5
let d = c // 참조 공유
d.value = 100
print(c.value) // 100, 같은 객체
// 선택 원칙:
// 값 타입을 기본으로 우선
// 공유/상속 필요 시 class
// 같은지 의미의 비교가 필요하면 값 타입
// Swift 는 struct 위주

class 시맨틱

class 참조 타입. 정체성이 고유합니다. 가변성과 스레드 공유.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 클래스 정의:
class Counter {
var count = 0
}
// 참조 공유:
let c1 = Counter()
let c2 = c1
c2.count += 1
print(c1.count) // 1, 같은 객체
// 항등 비교:
// === 는 참조 동일 비교
if c1 === c2 { print("同一实例") }
// == 는 값 비교 (Equatable 필요)
// 참조 타입의 가변성:
// 상수 참조도 속성 수정 가능
// 멀티스레드 공유 시 동기화 주의
// 클래스는 상속 지원 (OOP 절)
// 클래스는 deinit 소멸자 있음

Copy-on-write

배열 등 값 타입은 내부 저장소를 공유합니다. 수정할 때만 실제로 복사됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 배열:
var a = [1, 2, 3]
var b = a // 내부 저장소 공유
print(a) // [1, 2, 3]
// 수정할 때만 복사:
b.append(4)
print(a) // [1, 2, 3] 유지
// 카피온라이트 최적화:
// 할당 시 거의 비용 없음
// 최초 수정 시 복사 할당
// 대 배열의 다중 할당에 효율적
// 사용자 정의 타입 최적화:
// isKnownUniquelyReferenced
// 참조 유일성 확인
// 문자열/딕셔너리도 동일 최적화
// 값 의미 + 고성능 양립

정체성과 동등성

=== 참조 정체성. == 값 동등성 (Equatable). 차이가 중요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 항등 ===:
class Person { var name: String
init(name: String) { self.name = name }
}
let p1 = Person(name: "Nick")
let p2 = p1
let p3 = Person(name: "Nick")
// 항등:
print(p1 === p2) // true 같은 객체
print(p1 === p3) // false 다른 객체
// 값 동등 (Equatable 필요):
struct Point: Equatable {
var x: Int, y: Int
}
let q1 = Point(x: 1, y: 2)
let q2 = Point(x: 1, y: 2)
print(q1 == q2) // true 값 같음
// 규칙:
// 참조 타입은 === 로 동일성 판정
// 값 타입은 == 로 동등성 판정
// 사용자 정의는 Equatable 구현
// 해시와 동등은 일관 (Hashable)

weak와 unowned

weak는 약한 참조로 카운트를 증가시키지 않습니다. unowned는 비소유 참조입니다. 순환 참조를 끊습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
var pet: Pet?
deinit { print("Person 释放") }
}
class Pet {
// weak 는 보유 안 함:
weak var owner: Person?
deinit { print("Pet 释放") }
}
var nick: Person? = Person()
var dog: Pet? = Pet()
nick?.pet = dog
dog?.owner = nick
// 순환 참조 해소:
nick = nil // 해제
print("nick 已释放")
// weak 는 var 와 옵셔널 필수
// unowned: 항상 값이 있다고 가정
// class Restaurant {
// unowned var chef: Chef
// }
// 이미 해제된 unowned 접근 시 크래시
// weak 우선

inout 매개변수

inout 매개변수를 참조로 전달합니다. 함수 내에서 원본 값을 수정합니다. &로 호출.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// inout 함수:
func swapValues(_ a: inout Int, _ b: inout Int) {
let tmp = a
a = b
b = tmp
}
// 호출:
var x = 1
var y = 2
swapValues(&x, &y)
print(x, y) // 2 1
// 배열 요소 수정:
func increment(_ n: inout Int) {
n += 1
}
var nums = [10, 20]
increment(&nums[0])
print(nums) // [11, 20]
// 제한:
// 리터럴/상수 전달 불가
// 같은 변수를 두 번 전달 불가
// 속성은 var 가변 필요
// 참조 타입 매개변수와 차이:
// inout 은 값의 출입 통로

포인터 상호 운용

UnsafePointer와 C 상호 운용. 메모리 관리 책임. 위험 영역.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 변수 포인터 획득:
var number = 42
// withUnsafePointer 스코프:
withUnsafePointer(to: &number) { ptr in
print(ptr.pointee) // 42
}
// 가변 포인터:
withUnsafeMutablePointer(to: &number) { ptr in
ptr.pointee = 100
}
print(number) // 100
// C 함수 상호 운용:
// 배열을 C 포인터로:
var arr = [1, 2, 3]
arr.withUnsafeBufferPointer { buf in
// buf.baseAddress 를 C 에게
}
// 주의:
// 포인터 수명 직접 보장 필요
// 스코프 밖으로 포인터 유출 금지
// 대부분 사용 피하기
// 성능 병목일 때 고려

메모리 레이아웃

타입 메모리 레이아웃. 바이트 정렬. 성능과 상호 운용에 영향.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// 타입 크기 확인:
print(MemoryLayout<Int>.size) // 8
print(MemoryLayout<Double>.size) // 8
print(MemoryLayout<Bool>.size) // 1
// 정렬:
print(MemoryLayout<Int>.alignment)
// 구조체 레이아웃:
struct Small {
let a: Int8 // 1바이트
let b: Int64 // 8바이트
}
print(MemoryLayout<Small>.size) // 패딩 포함
// 컴팩트 레이아웃:
// 필드 순서가 패딩에 영향
// 큰 필드를 앞에 두는 것이 컴팩트
// 최적화:
// 다수의 작은 구조체 회피
// 배열은 연속 저장
// C 상호 운용 시 레이아웃 중요
// 수동 최적화는 드물게 필요

5.제어 흐름

if, switch, guard, 반복과 옵셔널 바인딩.

if / else

if 조건 실행. else if 다중 분기. 조건에 괄호가 필요 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let score = 85
// 기본 if:
if score >= 60 {
print("及格")
}
// else if:
if score >= 90 {
print("优秀")
} else if score >= 60 {
print("及格")
} else {
print("不及格")
}
// 조건은 Bool 필수:
// if score { } // 오류
// 다중 조건:
if score >= 60 && score < 90 {
print("良好")
}
// 한 줄 if:
if score > 80 { print("高分") }
// 삼항 연산자:
let label = score >= 60 ? "过" : "不过"

switch

switch 완전 매칭. 범위, 튜플, 바인딩. break가 필요 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let value = 5
// 값 매칭:
switch value {
case 1, 2, 3:
print("小")
case 4...6:
print("中")
default:
print("大")
}
// 범위와 where:
let temp = 25
switch temp {
case 0...10: print("冷")
case 11...30 where temp >= 20: print("暖")
case 11...30: print("温")
default: print("热")
}
// 열거형 소진:
enum Grade { case a, b, c }
let g = Grade.a
switch g {
case .a: print("优秀")
case .b: print("良好")
case .c: print("及格")
}
// 튜플 매칭:
// switch (x, y) { case (0, 0): ... }

guard

guard 조기 종료. else는 반드시 스코프를 종료해야 합니다. else에 분기가 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// guard 조기 반환:
func validate(_ age: Int?) {
guard let age = age else {
print("年龄为空")
return
}
// 여기서 age 는 해제됨
guard age >= 18 else {
print("未成年")
return
}
print("成人,年龄 \(age)")
}
validate(nil)
validate(15)
validate(20)
// if let 과의 차이:
// guard 의 해제 값은 이후 사용 가능
// if let 은 분기 내에서만 사용 가능
// guard 의 else 는 반드시 종료:
// return/throw/break/continue
// 구조가 더 명확, 들여쓰기 얕음
// 주요 흐름 로직이 최상위 유지

for-in 반복

for-in으로 범위, 배열, 딕셔너리를 순회합니다. 인덱스를 사용한 순회.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 범위:
for i in 1...5 { // 5 포함
print(i)
}
for i in 1..<5 { // 5 제외
print(i)
}
// 배열:
let names = ["a", "b", "c"]
for name in names {
print(name)
}
// 인덱스 포함:
for (i, name) in names.enumerated() {
print("\(i): \(name)")
}
// 딕셔너리:
let scores = ["a": 90, "b": 80]
for (key, value) in scores {
print("\(key): \(value)")
}
// 역순:
for i in (1...3).reversed() {}
// 간격:
for i in stride(from: 0, to: 10, by: 2) {}
// 인덱스 배열:
for i in names.indices { print(names[i]) }

while 반복

while 조건 반복. repeat-while은 먼저 실행하고 나중에 판단합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var n = 3
// while:
while n > 0 {
print(n)
n -= 1
}
// repeat-while (최소 1회 실행):
var count = 0
repeat {
count += 1
print("第 \(count) 次")
} while count < 3
// 루프 종료:
var found = false
var i = 0
while !found && i < 10 {
i += 1
if i == 5 { found = true }
}
// 용도:
// 조건을 모르는 루프
// 폴링, 재시도 로직
// for-in 과의 차이:
// 횟수 모를 때 while
// 메뉴 상호작용은 repeat-while

break와 continue

break는 반복을 종료합니다. continue는 이번 회차를 건너뜁니다. 레이블을 사용해 중첩을 종료.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// continue:
for i in 1...10 {
if i % 2 == 0 {
continue // 짝수 건너뜀
}
print(i) // 1 3 5 7 9
}
// break:
for i in 1...10 {
if i > 5 {
break // 조기 종료
}
print(i)
}
// 레이블로 중첩 종료:
outer: for i in 1...3 {
for j in 1...3 {
if i * j == 6 {
print("命中 \(i) \(j)")
break outer // 전체 종료
}
}
}
// 레이블 이름 자유
// 복잡한 중첩 제어 흐름에 사용
// 적당히 사용, 과용은 가독성 저하

if let

옵셔널 바인딩 언래핑. guard let 조기 종료. 다중 값 동시 언래핑.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var name: String? = "Nick"
var age: Int? = 30
// 단일 해제:
if let name = name {
print("名字 \(name)")
}
// 다중 해제 (쉼표):
if let name = name, let age = age {
print("\(name) \(age) 岁")
}
// 조건 포함:
if let name = name, name.count > 2 {
print(name)
}
// while 에서 해제:
var nums = [1, 2, 3]
while let n = nums.popLast() {
print(n) // 3 2 1
}
// 값과 무관한 nil 판정:
if name != nil { print("有值") }
// 주의:
// if let 분기 밖에서 사용 불가
// 이후 사용 필요 시 guard let

패턴 매칭

switch/if의 패턴. case let, where. 타입 매칭.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 연관값 바인딩:
enum Result3 {
case success(Int)
case failure(String)
}
let r = Result3.success(200)
switch r {
case .success(let code):
print("成功 \(code)")
case .failure(let msg):
print("失败 \(msg)")
}
// 범위 매칭:
let grade = 85
switch grade {
case 90...100: print("A")
case 80..<90: print("B")
case 70..<80: print("C")
default: print("D")
}
// 타입 매칭:
let any: Any = 42
switch any {
case let i as Int: print("整数 \(i)")
case let s as String: print("字符串 \(s)")
default: print("其他")
}
// where 조건:
switch grade {
case let g where g % 5 == 0: print("整五分")
default: print("其他分")
}

6.함수

함수 정의, 매개변수 레이블, 반환값, 함수 타입과 클로저.

함수 정의

func로 함수 정의. 매개변수와 반환값. 호출 구문.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 基本函数:
func greet(name: String) -> String {
"你好,\(name)!"
}
// 无返回值:
func sayHello() {
print("hello")
}
// 无返回也可标 Void:
func log(_ msg: String) -> Void {}
// 调用:
let g = greet(name: "Nick")
print(g)
sayHello()
// 多参数:
func add(a: Int, b: Int) -> Int {
a + b
}
// 参数默认标签=参数名
// 调用需写标签
// 最后一个表达式是返回值

매개변수 레이블

매개변수 레이블과 내부 이름. _로 레이블 생략. 가독성 설계.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 自定义标签:
func move(from start: Int, to end: Int) {
print("从 \(start)\(end)")
}
move(from: 1, to: 10)
// 省略标签:
func sum(_ a: Int, _ b: Int) -> Int {
a + b
}
print(sum(1, 2))
// 混合:
func configure(_ name: String, debug: Bool = false) {
print(name, debug)
}
configure("app") // 省略标签
configure("app", debug: true)
// API 设计规范:
// 标签使调用读作句子
// 介词 with/for/in 常见
// 标签用词清晰避免歧义

기본 매개변수 값

매개변수 기본값. 호출 시 생략 가능. 마지막에 위치하거나 모두 기본값 필요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 默认值:
func greet2(_ name: String, times: Int = 1) {
for _ in 1...times {
print("你好 \(name)")
}
}
greet2("Nick") // 用默认 1
greet2("Nick", times: 3)
// 默认值常见用法:
func fetch(
url: String,
timeout: Double = 30,
retries: Int = 3
) {
print(url, timeout, retries)
}
// 调用任意组合:
fetch(url: "https://x")
fetch(url: "https://x", retries: 5)
// 规则:
// 默认值在参数列表最后
// 默认参数可不传
// 默认值在函数签名中体现
// 可读性与灵活性的平衡

반환값

반환값 타입. 다중 값은 튜플 사용. 단일 표현식 암시적 반환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 单一返回值:
func square(_ n: Int) -> Int {
n * n
}
// 元组多值返回:
func stats(_ nums: [Int]) -> (min: Int, max: Int, sum: Int) {
(nums.min() ?? 0, nums.max() ?? 0, nums.reduce(0, +))
}
let s = stats([3, 1, 4, 1])
print(s.min, s.max, s.sum)
// 可选值返回:
func findIndex(of value: Int, in arr: [Int]) -> Int? {
arr.firstIndex(of: value)
}
if let i = findIndex(of: 4, in: [1, 2, 4]) {
print(i) // 2
}
// 无返回值:
func noReturn() {}
// 隐式返回:
// 单表达式省略 return
// 多语句需显式 return

가변 인자

...로 여러 인자를 배열로 수집. 임의 개수. 마지막 매개변수.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 可变参数:
func average(_ numbers: Double...) -> Double {
let sum = numbers.reduce(0, +)
return numbers.isEmpty ? 0 : sum / Double(numbers.count)
}
print(average(1, 2, 3, 4)) // 2.5
print(average()) // 0
// 可变参数作为数组使用
// 打印任意数量:
func logAll(_ items: String...) {
for item in items {
print(item)
}
}
logAll("a", "b", "c")
// 规则:
// 可变参数放最后
// 一个函数最多一个
// 内部是 [T] 数组
// 调用时逗号分隔

inout 매개변수

inout으로 외부 변수 수정. &로 참조 전달. 값 타입 제자리 수정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 交换:
func swapTwo<T>(_ a: inout T, _ b: inout T) {
let tmp = a
a = b
b = tmp
}
var x = 5
var y = 10
swapTwo(&x, &y)
print(x, y) // 10 5
// 值类型修改:
func scale(_ v: inout Double, by factor: Double) {
v *= factor
}
var price = 100.0
scale(&price, by: 0.8)
print(price) // 80.0
// 规则:
// 传变量而非常量/字面量
// & 前缀
// 与引用类型区别:
// inout 是函数内外的值往返
// 不创建新实例
// 适合大值类型避免拷贝

함수 타입

함수를 타입으로 사용. 할당, 전달, 반환. 일급 시민.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 函数类型标注:
func add(a: Int, b: Int) -> Int { a + b }
// (Int, Int) -> Int
// 赋值:
let operation: (Int, Int) -> Int = add
print(operation(1, 2))
// 作为参数:
func apply(_ f: (Int) -> Int, to value: Int) -> Int {
f(value)
}
func double(_ n: Int) -> Int { n * 2 }
print(apply(double, to: 5)) // 10
// 返回函数:
func makeAdder(_ base: Int) -> (Int) -> Int {
{ n in base + n }
}
let addTen = makeAdder(10)
print(addTen(5)) // 15
// 可选函数类型:
var handler: (() -> Void)?
// 调用需处理可选
// 类型匹配严格(含标签)

클로저

클로저는 컨텍스트를 캡처. 트레일링 클로저. 축약 인자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 定义闭包:
let square2 = { (n: Int) -> Int in
n * n
}
print(square2(4))
// 捕获变量:
var counter = 0
let increment = {
counter += 1
}
increment()
increment()
print(counter) // 2
// 尾随闭包:
func perform(_ action: () -> Void) {
action()
}
perform {
print("尾随闭包")
}
// 排序简写:
let nums = [3, 1, 2]
let sorted = nums.sorted { $0 < $1 }
print(sorted)
// 简写:
// $0 $1 参数简写
// 捕获列表 [weak self]
// @escaping 逃逸闭包

7.문자열

String 연산, 보간, 부분 문자열, Unicode과 포맷팅.

String 기초

String 값 타입. 리터럴과 가변성. Character 구성.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 创建字符串:
let greeting = "Hello"
// 多行字符串:三引号字面量
// let multi = """
// 多行
// 字符串
// """
// 等价(\n 换行):
let multi = "多行\n字符串"
// 可变字符串:
var text = "start"
text += " end"
// 空字符串:
let empty = ""
let empty2 = String()
// 字符序列:
// String 由 Character 组成
// 遍历字符:
for c in text {
print(c)
}
// 计数(字符数):
print(text.count)
// 判断空:
text.isEmpty
// 字符串是值类型:
// 赋值拷贝
// 高效(写时复制)

보간

\(expression)으로 값 삽입. 타입 안전. 임의 표현식.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let name = "Nick"
let age = 30
let score = 95.5
// 基础插值:
let msg = "\(name) 今年 \(age) 岁"
print(msg)
// 表达式插值:
let calc = "\(age * 2)\(age) 的两倍"
// 调用方法:
let upper = "大写: \(name.uppercased())"
// 格式化值:
let formatted = "分数: \(String(format: "%.1f", score))"
// 嵌套插值:
let nested = "\(name)\("\(age)".count) 个字符"
// 转义反斜杠:
let backslash = "反斜杠:\\"
// 注意:
// 插值类型需可显示
// 自定义类型实现 CustomStringConvertible

연결

문자열 연결. append. join. += 연산자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// + 拼接:
let a = "hello"
let b = " world"
let c = a + b // 新字符串
// += 追加:
var s = "start"
s += " - more"
// append:
s.append("!")
// 字符追加:
s.append(Character("?"))
// 数组 join:
let parts = ["a", "b", "c"]
let joined = parts.joined(separator: ", ")
print(joined) // "a, b, c"
// 性能:
// 小字符串拼接开销小
// 大量拼接:
// 用数组 + joined
// 或可变字符串一次构建
// 字符串是值类型,拼接创建新值

여러 줄 문자열

삼중 따옴표 여러 줄. 들여쓰기 제거. 값 보간. 구분.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 多行字符串用三引号字面量:
// let poem = """
// 床前明月光,
// 疑是地上霜。
// """
// 等价实现(\n 换行):
let poem = "床前明月光,\n疑是地上霜。"
print(poem)
// 缩进规则:以闭合引号缩进为基准
// 包含引号:
// let quote = """
// He said "hi"
// """
// 多行插值:
let name = "Rust"
// let info = """
// 语言:\(name)
// 类型:静态
// """
// 反斜杠取消换行:
// let oneLine = """
// 一行内容 \
// 继续
// """

자주 쓰는 메서드

대소문자, 검색, 교체, 분할, 트림. 자주 쓰는 문자열 API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let s = " Hello, Swift "
// 大小写:
let lower = s.lowercased()
let upper = s.uppercased()
// 修剪:
let trimmed = s.trimmingCharacters(
in: .whitespacesAndNewlines)
// 判断:
s.contains("Swift")
s.hasPrefix(" ")
s.hasSuffix(" ")
// 查找:
s.firstIndex(of: ",")
// 替换:
let replaced = s.replacingOccurrences(
of: "Swift", with: "Go")
// 分割:
let parts = "a,b,c".split(separator: ",")
// 前缀/后缀:
let pre = s.prefix(5)
// 判断空:s.isEmpty
// 重复:
String(repeating: "ab", count: 3)

부분 문자열

Substring은 원본 문자열을 참조. 슬라이싱은 Substring 반환. String으로 변환하여 소유.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let full = "Hello, Swift World"
// 索引范围切片:
let start = full.index(full.startIndex, offsetBy: 7)
let end = full.index(start, offsetBy: 5)
let sub = full[start..<end] // "Swift"
print(sub)
// 注意:Substring 类型
// 与原字符串共享存储
// 转 String 独立持有:
let owned = String(sub)
// 前缀后缀是 Substring:
let p = full.prefix(5) // "Hello"
// 用 Range 切:
if let range = full.range(of: "Swift") {
print(full[range])
}
// 多字节安全:
// 索引按字符边界
// 不要用整数下标
// 频繁用需转 String

Unicode

문자는 Unicode 스칼라로 구성. 코드 포인트. 확장 자소 클러스터.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Unicode 字面量:
let heart = "\u{2764}"
print(heart) // ❤
// 扩展字位簇:
let e = "\u{E9}" // é
let combo = "e\u{301}" // é(组合)
print(e == combo) // true,同字位簇
// 字符(字位簇)计数:
let flag = "🇨🇳"
print(flag.count) // 1(区域指示符组合)
// 遍历字位簇:
for c in "你好👍" {
print(c)
}
// 码点:
for scalar in "中".unicodeScalars {
print(scalar.value) // 20013
}
// 规范化:
// .precomposedStringWithCanonicalMapping()
// 排序比较需规范化

포맷팅

String(format:) C 스타일 포맷. 숫자 패딩과 정밀도.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// 基础格式:
let pi = 3.14159
let f = String(format: "%.2f", pi)
print(f) // "3.14"
// 整数补零:
String(format: "%05d", 42) // "00042"
// 宽度对齐:
String(format: "%10s", "hi") // 右对齐
// 科学计数:
String(format: "%e", 1234.0)
// 十六进制:
String(format: "%x", 255) // "ff"
// 多占位:
String(format: "%d-%@", 2026, "08-02")
// 注意:
// %@ 对象、%d 整数、%f 浮点
// %lld 64 位整数
// 更 Swift 的方式:
// 优先用插值与描述
// 需要精确格式才用 format

8.컬렉션

Array, Dictionary, Set, 고차 함수와 범위.

Array

순서가 있는 값 컬렉션. 제네릭 [T]. 추가, 삭제, 수정, 조회.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 创建:
var nums = [1, 2, 3]
let empty: [Int] = []
let filled = [Int](repeating: 0, count: 5)
// 字面量类型推断:
let names = ["a", "b"]
// 访问:
print(nums[0])
print(nums.first ?? 0)
print(nums.last ?? 0)
// 增删:
nums.append(4)
nums.insert(0, at: 0)
nums.removeLast()
nums.remove(at: 1)
// 遍历:
for n in nums { print(n) }
// 其他:
nums.count
nums.isEmpty
nums.contains(2)
// 求和:
let sum = nums.reduce(0, +)
// 排序:
nums.sorted()

배열 연산

슬라이싱, 교체, 병합, 검색. 자주 쓰는 고차 메서드.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var arr = [1, 2, 3, 4, 5]
// 切片:
let slice = arr[1...3] // ArraySlice
print(slice)
// 范围替换:
arr.replaceSubrange(0..<2, with: [9, 9])
// 合并:
let combined = arr + [10]
// 查找:
arr.first { $0 > 3 }
arr.last { $0 > 3 }
arr.contains(5)
arr.firstIndex(of: 3)
// 批量转换:
let doubled = arr.map { $0 * 2 }
let evens = arr.filter { $0 % 2 == 0 }
// 分区:
let (small, big) = arr.partitioned {
$0 < 3
}
// 前缀后缀:
arr.prefix(2)
arr.suffix(2)
// 交换:
arr.swapAt(0, 1)

Dictionary

키-값 쌍 컬렉션. [String: T]. 순서 없음. O(1) 조회.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 创建:
var scores: [String: Int] = [:]
var config = ["env": "dev", "port": "8080"]
// 增改:
scores["alice"] = 90
scores["bob"] = 85
scores["alice"] = 95 // 覆盖
// 读取(可选):
let alice = scores["alice"] // Int?
let missing = scores["x"] // nil
// 默认值:
let v = scores["x"] ?? 0
// 删除:
scores["bob"] = nil
scores.removeValue(forKey: "alice")
// 遍历:
for (k, v) in scores {
print("\(k): \(v)")
}
// 键列表/值列表:
Array(scores.keys)
Array(scores.values)
// 合并:
config.merge(["port": "9090"]) { _, new in new }
// 计数与空:
scores.count

Set

순서 없는 유일 요소 컬렉션. 해시 집합. 집합 연산.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 创建:
var set: Set<Int> = [1, 2, 3]
let other: Set = [3, 4, 5]
// 增删查:
set.insert(4)
set.remove(1)
set.contains(2)
// 集合运算:
let union = set.union(other) // 并
let intersect = set.intersection(other) // 交
let diff = set.subtracting(other) // 差
let sym = set.symmetricDifference(other) // 对称差
// 判断关系:
set.isSubset(of: other)
set.isSuperset(of: other)
set.isDisjoint(with: other)
// 遍历:
for v in set { print(v) }
// 转数组:
let arr = Array(set)
// 用途:
// 去重、存在性检查
// 去重:
let dup = Array(Set([1, 1, 2, 3]))
print(dup)

순회

배열/딕셔너리/집합 순회. 인덱스 포함. 역순. 필터 조건 순회.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let items = ["a", "b", "c"]
// 基础遍历:
for item in items {
print(item)
}
// 带索引:
for (i, item) in items.enumerated() {
print("\(i): \(item)")
}
// 下标遍历:
for i in items.indices {
print(items[i])
}
// 倒序:
for item in items.reversed() {}
// 字典遍历:
let dict = ["a": 1, "b": 2]
for (k, v) in dict {
print("\(k): \(v)")
}
// 条件过滤遍历:
for n in 1...10 where n % 2 == 0 {
print(n) // 2 4 6 8 10
}
// 跳过与退出:
// continue / break
// forEach:
items.forEach { print($0) }

고차 함수

map, filter, reduce, compactMap, sorted. 함수형 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let nums = [1, 2, 3, 4, 5]
// map 映射:
let doubled = nums.map { $0 * 2 }
// filter 过滤:
let evens = nums.filter { $0 % 2 == 0 }
// reduce 归约:
let sum = nums.reduce(0, +)
// compactMap 去 nil:
let strs = ["1", "a", "3"]
let ints = strs.compactMap { Int($0) }
print(ints) // [1, 3]
// flatMap 展平:
let grid = [[1, 2], [3]]
let flat = grid.flatMap { $0 }
// sorted 排序:
let sorted = nums.sorted { $0 > $1 }
// 链式组合:
let result = nums
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.reduce(0, +)
print(result) // 4 + 16 = 20
// forEach 遍历
// 惰性:lazy.map

중첩 컬렉션

컬렉션의 조합. 다차원 배열, 딕셔너리 안의 배열, 구조체 컬렉션.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 二维数组:
var grid = [[Int]]()
grid.append([1, 2, 3])
grid.append([4, 5, 6])
print(grid[0][1]) // 2
// 重复初始化:
var matrix = [[Int]](
repeating: [Int](repeating: 0, count: 3), count: 3)
matrix[1][2] = 9
// 字典内数组:
var groups: [String: [String]] = [:]
groups["dev"].append("nick") // 错误
// 需先初始化:
groups["dev"] = ["nick"]
groups["dev", default: []].append("tom")
// 结构体集合:
struct Task { var title: String; var done = false }
var tasks: [Task] = [Task(title: "a")]
tasks[0].done = true
// 数组字典:
let scores: [[String: Int]] = [
["math": 90], ["eng": 85]
]
print(scores[0]["math"] ?? 0)

Range

반개방 범위 ..<, 폐쇄 범위 ... . 배열 슬라이싱과 순환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 范围类型:
let closed = 1...5 // 含 5
let halfOpen = 1..<5 // 不含 5
// 循环:
for i in 1...3 {
print(i)
}
// 数组切片:
let arr = [10, 20, 30, 40]
let sub = arr[1...2] // [20, 30]
// 单侧范围:
let head = arr[..<2] // [10, 20]
let tail = arr[2...] // [30, 40]
// 检查包含:
(1...5).contains(3) // true
// 步长:
for i in stride(from: 0, to: 10, by: 2) {
print(i) // 0 2 4 6 8
}
// 反转:
for i in (1...3).reversed() {}
// 边界:
// 空范围:1..<1
// 浮点范围(限制多)
// Range 用于下标与模式匹配

9.메모리 관리

ARC 참조 카운팅, 약한 참조, 순환 참조와 메모리 최적화.

ARC

자동 참조 카운팅으로 클래스 인스턴스 관리. 참조 카운트 0이 되면 해제.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Device {
var name = ""
deinit {
print("\(name) 释放")
}
}
// 引用计数:
var d1: Device? = Device() // count 1
d1?.name = "phone"
var d2 = d1 // count 2
var d3 = d1 // count 3
d1 = nil // count 2
d2 = nil // count 1
d3 = nil // count 0,释放
// 规则:
// 每强引用 +1
// 计数归零自动释放
// deinit 可观察释放
// 值类型(struct)不参与 ARC
// 无需手动管理
// 弱引用不计数

weak/unowned

약한 참조는 카운트 증가 안 함. 순환 참조 방지. 해제된 객체 접근 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Parent {
var child: Child?
}
class Child {
// weak 不持有父:
weak var parent: Parent?
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent
// 循环已打破:
parent = nil
// child 的 parent 自动变 nil
// weak 特性:
// 必须 var、必须可选
// 对象释放自动变 nil
// unowned:
// class CreditCard {
// unowned let owner: Person
// }
// unowned 假定一直存在
// 访问已释放 unowned 崩溃
// 优先级:weak > unowned

자동 해제 풀

autoreleasepool 지연 해제. 대량 루프 메모리 제어.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 大批量循环:
var results: [String] = []
// 循环外包池:
for i in 0..<1000 {
autoreleasepool {
// 临时对象在此释放:
let temp = "临时数据 \(i)"
results.append(temp)
// 其他临时对象...
}
}
// 每轮迭代结束释放
// 用途:
// 大循环峰值内存
// 处理大量图片/文件
// 耗时任务临时对象
// 注意:
// 现代 Swift 多自动管理
// 需要手动时才用
// 池内 return 提前释放
// ARC 下多数情况无需

복사 의미론

값 타입 복사 안전. 대용량 타입 최적화. 공유와 유일성.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 值类型拷贝:
var arr1 = [1, 2, 3]
var arr2 = arr1 // 写时复制
// 修改触发真拷贝:
arr2.append(4)
print(arr1) // [1, 2, 3] 不变
// 大字符串/数组/字典:
// 赋值廉价(共享存储)
// 修改才分配
// 自定义 struct 同样安全:
struct Config {
var theme = "dark"
var font = 14
}
var c1 = Config()
var c2 = c1
c2.theme = "light"
print(c1.theme) // dark
// 拷贝原则:
// 值语义防意外共享
// 线程安全(无共享状态)
// 需要共享用 class

순환 참조

두 클래스가 서로 강하게 참조하여 순환 형성. 메모리 누수. weak로 해결.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Owner {
var pet: Pet?
deinit { print("Owner 释放") }
}
class Pet {
// BAD:强引用父,形成环
// var owner: Owner?
// GOOD:weak 弱引用
weak var owner: Owner?
deinit { print("Pet 释放") }
}
var owner: Owner? = Owner()
var pet: Pet? = Pet()
owner?.pet = pet
pet?.owner = owner
// 无环时正常释放:
owner = nil
pet = nil
// 诊断:
// Instruments 泄漏检查
// deinit 未调用 = 泄漏
// 其他循环场景:
// 闭包捕获 self 强引用
// 用 [weak self] 打破
// Timer/Delegate 注意

스택과 힙

값 타입은 스택, 참조 타입은 힙. 할당과 성능.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 栈上分配:
struct Point { var x = 0 }
let p = Point() // 栈上(多数情况)
// 堆上分配:
class Box { var v = 0 }
let b = Box() // 堆上
// 大结构体优化:
// 超过一定大小也堆上
// 数组元素内联存储
// 性能影响:
// 栈分配快、自动释放
// 堆分配需 ARC 管理
// 大量小对象
// 用 struct 优于 class
// 结构体数组连续内存:
struct Item { var id = 0 }
let items = [Item(), Item()] // 连续
// 参考:
// 值类型默认栈
// 逃逸才上堆
// 无需过度关注

lazy 저장

lazy 지연 초기화. 사용 시 생성. 비용 큰 속성 최적화.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// lazy 属性:
class DataLoader {
// 用到才加载:
lazy var config: [String: String] = {
print("加载配置")
return ["env": "dev"]
}()
}
let loader = DataLoader()
// 未访问不执行:
print("已创建")
// 首次访问触发:
print(loader.config)
// 后续直接返回:
print(loader.config)
// 特点:
// 只初始化一次
// 线程不安全(并发需注意)
// 闭包内可用 self
// 适合:
// 昂贵构建
// 可能不使用的属性
// 依赖其他属性的初始化
// 全局变量也 lazy

메모리 최적화

힙 할당 감소, 버퍼 재사용, 대용량 복사 방지. 성능 튜닝.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 减少临时分配:
// 复用可变字符串:
var buffer = ""
for i in 0..<1000 {
// 追加而非拼接:
buffer.append("\(i),")
}
// 预分配容量:
var nums: [Int] = []
nums.reserveCapacity(1000)
for i in 0..<1000 {
nums.append(i)
}
// 避免不必要拷贝:
// 传 inout 修改大数组
// 用 ArraySlice 共享
// 值类型注意
// 大结构体传引用
// 用 withUnsafeBytes
// 检查工具:
// Instruments Allocations
// 时间分析优先
// 别过早优化

10.클래스와 프로토콜

class, 상속, 프로토콜, 확장과 프로토콜 지향 설계.

class 정의

클래스는 속성과 메서드 정의. 참조 타입. 이니셜라이저.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Animal {
var name: String
// 初始化器:
init(name: String) {
self.name = name
}
// 方法:
func speak() {
print("\(name) 叫了一声")
}
}
// 实例化:
let dog = Animal(name: "旺财")
dog.speak()
// 引用语义:
let same = dog
same.name = "小黑"
print(dog.name) // 小黑
// 属性默认值:
class Counter {
var count = 0
init() {}
}
// deinit 析构:
deinit {
print("实例释放")
}
// 类可继承、可扩展
// 与 struct 对比
// 类需要 init 保证所有存储属性

상속

서브클래스가 부모 클래스 상속. 저장 속성, 메서드. 최종 클래스 final.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Vehicle {
var speed = 0
func describe() -> String {
"速度 \(speed)"
}
}
// 子类:
class Car: Vehicle {
var wheels = 4
override func describe() -> String {
"汽车 \(wheels) 轮,\(super.describe())"
}
}
let car = Car()
car.speed = 100
print(car.describe())
// 继承规则:
// 单继承
// override 覆盖必须标注
// 访问父类用 super
// 不能继承的:
// final class 不可继承
// 私有属性不可见
// 指定/便捷初始化器
// 优先组合与协议

메서드 오버라이드

override로 메서드, 속성, 이니셜라이저 재정의. super로 부모 클래스 호출.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Base {
var label = "base"
func greet() {
print("Base: \(label)")
}
// 可重写属性:
var value: Int {
get { 10 }
}
}
class Sub: Base {
// 重写方法:
override func greet() {
super.greet() // 先调父类
print("Sub 额外处理")
}
// 重写属性:
override var value: Int {
get { super.value + 5 }
}
}
let s = Sub()
s.greet()
print(s.value)
// 规则:
// override 必写
// 可读性:
// final 阻止重写
// final func 防再覆写
// 属性重写用 get/set/willSet/didSet
// 静态方法可重写

프로토콜

protocol로 요구사항 정의. 타입 채택. 프로토콜 지향 설계의 핵심.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 定义协议:
protocol Greetable {
var name: String { get }
func greet() -> String
}
// 遵守协议:
struct Person: Greetable {
var name: String
func greet() -> String {
"你好,\(name)!"
}
}
struct Robot: Greetable {
var name: String
func greet() -> String {
"哔,\(name) 就绪"
}
}
// 协议类型使用:
let things: [Greetable] = [
Person(name: "Nick"),
Robot(name: "R2"),
]
for t in things {
print(t.greet())
}
// 协议可继承:
protocol NamedGreetable: Greetable {
var age: Int { get }
}
// 协议默认实现用扩展

프로토콜 확장

extension으로 기본 구현 제공. 제약 확장. 프로토콜 기본 메서드.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 协议:
protocol Describable {
var description: String { get }
}
// 默认实现:
extension Describable {
var description: String {
"描述:" + Self.self.description
}
}
// 约束扩展:
extension Describable where Self: Equatable {
func isSameAs(_ other: Self) -> Bool {
self == other
}
}
// 为类型添加协议:
struct Cat: Describable {
var name: String
}
// 使用默认实现:
let cat = Cat(name: "喵")
print(cat.description)
// 协议 + 扩展:
// 默认方法减少样板
// 协议继承组合
// where 约束细化
// 面向协议编程的基础

확장

extension은 기존 타입에 메서드를 추가한다. 코드를 분류해 정리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 扩展内置类型:
extension String {
// 加方法:
func isEmail() -> Bool {
self.contains("@")
}
// 计算属性:
var wordCount: Int {
self.split(separator: " ").count
}
}
print("[email protected]".isEmail())
print("hi there".wordCount)
// 扩展自己的类型:
struct Point { var x: Double }
extension Point {
func doubled() -> Point {
Point(x: x * 2)
}
}
// 遵守协议用扩展:
extension Point: Equatable {}
// 分组组织:
// 一个类型多个 extension
// 按功能分类
// 不能加存储属性
// 不能改既有实现

프로퍼티 옵저버

willSet/didSet으로 프로퍼티 변화를 감지한다. 화면 갱신, 검증에 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var user: String = "" {
willSet {
print("即将改为 \(newValue)")
}
didSet {
print("已从 \(oldValue) 改为 \(user)")
}
}
user = "Nick"
user = "Tom"
// 输出观察日志
// 应用场景:
// 界面绑定刷新
// 数据校验
// 记录变更
// 规则:
// 初始化不触发
// 类与 struct 都可
// 存储属性才能观察
// 计算属性用 get/set
// willSet/didSet 参数可命名
// 并发访问注意线程

연산 프로퍼티

get/set으로 값을 계산한다. 저장하지 않는다. 파생 데이터에 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct Circle {
var radius: Double
// 计算属性:
var area: Double {
// 只读:
Double.pi * radius * radius
}
// 读写:
var diameter: Double {
get {
radius * 2
}
set {
radius = newValue / 2
}
}
}
var c = Circle(radius: 5)
print(c.area) // 78.54
print(c.diameter) // 10
c.diameter = 20
print(c.radius) // 10
// 特点:
// 每次访问重新计算
// 不占存储
// 派生值始终一致
// get 必须有
// set 用 newValue
// 也可自定义参数名
// 与存储属性对比

11.오류 처리

throws, do-catch, try, defer와 사용자 정의 오류를 다룬다.

Error 프로토콜

Error 프로토콜로 오류 타입을 표시한다. 열거형으로 오류 분류를 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 错误枚举:
enum FileError: Error {
case notFound
case noPermission
case invalidData(String)
}
// 简单错误:
struct SimpleError: Error {
let message: String
}
// 分类错误:
enum LoginError: Error {
case emptyUsername
case wrongPassword
case tooManyAttempts
}
// 用途:
// 标记可抛错误
// 携带信息:
// case invalidData(String)
// 错误符合可比较:
// 可加 Equatable 派生
// LocalizedError 提供描述
// 错误设计清晰分类

throws

throws는 오류를 던질 수 있는 함수를 표시한다. throw로 오류를 던진다. 호출 시 처리해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 抛错函数:
func divide(_ a: Int, by b: Int) throws -> Int {
guard b != 0 else {
throw DivideError.zeroDivision
}
return a / b
}
enum DivideError: Error {
case zeroDivision
}
// 调用必须处理:
// 1. try! 危险
// let x = try! divide(1, by: 0)
// 2. try? 转可选
let x = try? divide(1, by: 0)
print(x) // nil
// 3. try + do-catch
// 4. 传播给调用者
func safeDivide() throws -> Int {
try divide(10, by: 2)
}
// 链式 throw:
// 函数标记 throws 即可透传

do-catch

do 블록 안에서 try를 쓴다. catch로 오류를 잡는다. 여러 갈래로 나눌 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum CalcError: Error {
case zeroDiv
case overflow
}
func compute() throws -> Int {
throw CalcError.zeroDiv
}
// 捕获:
do {
let result = try compute()
print(result)
} catch CalcError.zeroDiv {
print("除以零")
} catch CalcError.overflow {
print("溢出")
} catch {
print("其他错误: \(error)")
}
// 绑定错误:
// catch let e as CalcError
// 一个 do 多个 try
// catch 从具体到一般
// error 隐式变量
// 未捕获:
// 传播到调用者
// 没有 catch 需 throws

try 변형

try는 catch가 필요하다. try?는 옵셔널로 바꾼다. try!는 강제한다(크래시 위험).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func risky() throws -> Int { 42 }
// try:必须 do-catch 或传播
// 语法要求:
func caller() throws {
let v = try risky()
print(v)
}
// try?:失败转 nil
let a = try? risky() // Int?
print(a ?? 0)
// try!:失败崩溃
// 适合确定不失败:
let b = try! risky()
print(b)
// 例子:
// let data = try? Data(contentsOf: url)
// 强制解包的权衡:
// try! 失败直接崩溃
// 生产谨慎使用
// 测试/确定场景可用
// try? 适合“失败也可”场景

사용자 정의 오류

오류가 문맥 정보를 담는다. LocalizedError로 설명을 제공한다. 오류 코드를 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 带上下文:
enum NetworkError: LocalizedError {
case timeout(Int)
case serverError(code: Int, message: String)
// 本地化描述:
var errorDescription: String? {
switch self {
case .timeout(let s):
return "超时 \(s) 秒"
case .serverError(let code, let msg):
return "服务器 \(code): \(msg)"
}
}
}
// 使用:
func fetch() throws {
throw NetworkError.serverError(code: 500, message: "内部错误")
}
do {
try fetch()
} catch let e as NetworkError {
print(e.errorDescription ?? e)
}
// 捕获任意 Error:
// catch let err { print(err.localizedDescription) }
// 关联值提供上下文
// 协议组合:
// Equatable 比较错误

defer 정리

defer는 스코프가 끝날 때 실행된다. 리소스를 정리한다. 역순으로 실행된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func processFile() throws {
print("打开文件")
// 延迟清理:
defer {
print("关闭文件")
}
print("处理数据")
// 中途出错也会执行 defer
// throw SomeError()
print("正常结束")
}
try? processFile()
// 多个 defer 逆序:
defer { print("A") }
defer { print("B") }
// 输出 B 后 A
// 用途:
// 关闭文件/连接
// 解锁、恢复状态
// 清理临时资源
// 提前 return 也执行
// 注意:
// 不在 defer 中读写返回值

치명적 오류

fatalError는 복구 불가능한 크래시를 낸다. precondition으로 조건을 검사한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 不可恢复:
// fatalError("无法继续")
// precondition:
func process(_ n: Int) {
precondition(n > 0, "参数必须为正数")
print(n)
}
process(5)
// process(-1) // 崩溃(precondition 不满足)
// 调试断言:
assert(true, "仅调试检查")
// 其他:
// assert 调试构建生效
// precondition 都生效
// 适用场景:
// 不变量被破坏
// 程序无法继续的 bug
// 开发期尽早失败
// 生产用错误处理
// 不要滥用

Result 타입

Result<Success, Failure>는 성공 또는 실패를 명시한다. throws를 피할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Result 使用:
func fetchData() -> Result<[String], Error> {
let ok = true
if ok {
return .success(["a", "b"])
} else {
return .failure(NetworkError.timeout(30))
}
}
enum NetworkError: Error {
case timeout(Int)
}
// 处理:
let result = fetchData()
switch result {
case .success(let data):
print(data)
case .failure(let error):
print(error)
}
// 方法:
// result.map { ... }
// result.flatMap { ... }
// try result.get() 转 throws
// 场景:
// 异步回调返回
// 非异常错误流
// 与 Result<_, Never> 组合
// 存储错误结果数组

12.입출력

명령줄 입력, 파일 읽고 쓰기, FileManager와 Codable을 다룬다.

입력 읽기

readLine으로 명령줄 입력을 읽는다. 반복해서 상호작용한다. 숫자를 파싱한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// 读取一行:
print("请输入名字:", terminator: "")
if let name = readLine() {
print("你好,\(name)!")
}
// 读取数字:
print("输入年龄:", terminator: "")
if let input = readLine(), let age = Int(input) {
print("年龄 \(age)")
}
// 循环交互:
while let line = readLine(), !line.isEmpty {
print("收到: \(line)")
}
// 批量管道输入:
// echo "data" | swift main.swift
// readLine 返回 nil 表示 EOF
// 标准错误输出:
// FileHandle.standardError
// 输出:
// print 到标准输出

파일 읽기

String(contentsOf:)로 파일을 읽는다. Data는 바이너리를 담는다. 한 줄씩 읽을 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 读取为字符串:
let path = "data.txt"
do {
let content = try String(contentsOfFile: path, encoding: .utf8)
print(content)
} catch {
print("读取失败: \(error)")
}
// 读取为 Data:
let data = try? Data(contentsOf: URL(fileURLWithPath: path))
// 逐行处理:
let text = "line1\nline2\n"
for line in text.components(separatedBy: .newlines) {
print(line)
}
// 相对路径注意:
// 当前工作目录
// 沙盒环境需 Document 目录
// 编码:
// .utf8 / .unicode
// 大文件流式读取:
// FileHandle + read(upToCount:)

파일 쓰기

문자열과 Data를 쓴다. 추가 모드를 지원한다. 원자적 쓰기를 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
let content = "hello\nworld\n"
// 覆盖写入:
let url = URL(fileURLWithPath: "out.txt")
try? content.write(to: url, atomically: true, encoding: .utf8)
// Data 写入:
let data = Data(content.utf8)
try? data.write(to: url)
// 追加写入:
if let handle = try? FileHandle(forWritingTo: url) {
handle.seekToEndOfFile()
handle.write(Data("more\n".utf8))
try? handle.close()
}
// 路径处理:
let dir = URL(fileURLWithPath: "data")
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
// 错误处理:
// try? 忽略错误
// 生产用 do-catch
// 原子写入防损坏

FileManager

파일 시스템 작업을 한다. 디렉터리 생성, 삭제, 이동, 존재 여부 확인을 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
let fm = FileManager.default
// 判断存在:
let exists = fm.fileExists(atPath: "data.txt")
// 创建目录:
try? fm.createDirectory(
atPath: "data/sub", withIntermediateDirectories: true)
// 移动/重命名:
try? fm.moveItem(atPath: "a.txt", toPath: "b.txt")
// 复制:
try? fm.copyItem(atPath: "b.txt", toPath: "c.txt")
// 删除:
try? fm.removeItem(atPath: "c.txt")
// 列出目录:
if let items = try? fm.contentsOfDirectory(atPath: ".") {
print(items)
}
// 属性:
if let attrs = try? fm.attributesOfItem(atPath: "data.txt") {
print(attrs[.size] ?? "?")
}
// 目录遍历:
// enumerator(atPath:)
// 沙盒路径:
// fm.urls(for: .documentDirectory, in: .userDomainMask)

출력 제어

print 변형이 있다. stderr로도 출력한다. 구분자와 종결자를 지정한다. 문자열 표현을 다룬다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// 基础:
print("普通输出")
print(1, 2, 3) // 空格分隔
print("a", "b", separator: "-")
print("x", terminator: "") // 不换行
print() // 换行
// 标准错误:
FileHandle.standardError.write(
Data("错误信息\n".utf8))
// 调试描述:
debugPrint("debug")
// 自定义输出:
struct Item: CustomStringConvertible {
var name: String
var description: String {
"Item(\(name))"
}
}
print(Item(name: "a"))
// CustomDebugStringConvertible:
// 调试更详细
// dump 深度打印:
dump([1, 2, 3])

Data 처리

Data는 바이트 컨테이너다. 문자열과 서로 변환한다. base64로 인코딩하고 디코딩한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// 字符串转 Data:
let str = "hello"
let data = Data(str.utf8)
// Data 转字符串:
let back = String(data: data, encoding: .utf8)
print(back ?? "")
// base64:
let encoded = data.base64EncodedString()
print(encoded) // aGVsbG8=
let decoded = Data(base64Encoded: encoded)
// 字节操作:
var bytes: [UInt8] = [0x68, 0x69]
let d = Data(bytes)
print(Array(d)) // [104, 105]
// 追加:
var buffer = Data()
buffer.append(Data([1, 2]))
buffer.append(Data("a".utf8))
// 十六进制:
let hex = data.map { String(format: "%02x", $0) }.joined()
print(hex) // 68656c6c6f
// 读取子范围:
// data.subdata(in: 0..<2)

JSONSerialization

JSON을 딕셔너리나 배열로 바꾼다. 반대로 직렬화도 한다. JSONSerialization 클래스를 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// JSON 字符串:
let jsonStr = "{\"name\": \"Nick\", \"age\": 30, \"tags\": [\"dev\"]}"
let jsonData = Data(jsonStr.utf8)
// 解析为 Any:
let obj = try? JSONSerialization.jsonObject(
with: jsonData)
// 转字典:
if let dict = obj as? [String: Any] {
print(dict["name"] ?? "")
let age = dict["age"] as? Int ?? 0
let tags = dict["tags"] as? [String] ?? []
print(age, tags)
}
// 反向序列化:
let payload: [String: Any] = [
"name": "Tom", "age": 25
]
if JSONSerialization.isValidJSONObject(payload) {
let data = try? JSONSerialization.data(withJSONObject: payload)
let str = String(data: data!, encoding: .utf8)
print(str ?? "")
}
// 现代推荐 Codable

Codable

Codable로 자동 직렬화한다. JSONEncoder/Decoder를 쓴다. 프로토콜 기반이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 类型遵守 Codable:
struct User: Codable {
var name: String
var age: Int
var tags: [String]
}
// 编码:
let user = User(name: "Nick", age: 30, tags: ["dev"])
let encoder = JSONEncoder()
let data = try? encoder.encode(user)
let json = String(data: data!, encoding: .utf8)
print(json ?? "")
// 解码:
let jsonStr = "{\"name\": \"Tom\", \"age\": 25, \"tags\": []}"
let decoder = JSONDecoder()
if let u = try? decoder.decode(User.self, from: Data(jsonStr.utf8)) {
print(u.name)
}
// 字段映射:
// CodingKeys 重命名
// snake_case:
// decoder.keyDecodingStrategy = .convertFromSnakeCase
// 数组解码:[User].self
// 日期处理需策略

13.흔한 함정

Swift 입문자가 가장 많이 빠지는 함정과 올바른 작성법을 다룬다.

강제 언래핑

!로 nil을 언래핑하면 크래시가 난다. 옵셔널 바인딩과 nil 병합 연산자를 우선 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// BAD:强制解包
var name: String? = nil
// print(name!) // 崩溃
// GOOD:可选绑定
if let name = name {
print(name)
}
// GOOD:空合运算符
let display = name ?? "匿名"
print(display)
// GOOD:guard 提前解包
func show(_ v: String?) {
guard let v = v else {
print("无值")
return
}
print(v)
}
// 何时可安全 !:
// 确定非 nil(已赋值)
// 测试/原型
// 其余避免
// try! 同理谨慎
// 崩溃信息难看
// 生产代码克制 !

옵셔널 체이닝

?.로 연쇄 접근한다. 중간에 하나라도 nil이면 전체가 nil이다. 대입에도 쓸 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Address { var city = "北京" }
class Person {
var address: Address?
}
let p = Person()
// GOOD:可选链
let city = p.address?.city // String?
print(city ?? "无地址")
// BAD:假定中间非 nil
// p.address!.city // 可能崩溃
// 多层链:
// p.address?.city.count
// 赋值的可选链:
p.address?.city = "上海" // nil 时静默
// 赋值地址:
p.address = Address()
p.address?.city = "上海"
print(p.address?.city ?? "")
// 方法调用可选链:
// p.address?.method()
// 判断链中是否有值:
if let c = p.address?.city {
print(c)
}
// 常见误区:
// 链返回可选类型
// 不会自动解包
// 与强制链 !. 对比

클로저 순환 참조

클로저가 self를 강하게 캡처해 순환이 생긴다. 캡처 목록 [weak self]를 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Loader {
var data: String = ""
var completion: (() -> Void)?
func setup() {
// BAD:闭包强捕获 self
// completion = {
// self.data = "loaded"
// }
// GOOD:[weak self]
completion = { [weak self] in
self?.data = "loaded"
// self 释放后自动 nil
}
}
deinit { print("Loader 释放") }
}
// 使用:
var l: Loader? = Loader()
l?.setup()
// 执行闭包或释放:
l = nil // 正常释放
// 其他打破方式:
// [unowned self] 假定存在
// 闭包外强引用后手动 nil
// 常见于:
// 网络回调、Timer、动画

배열 범위 초과

인덱스가 범위를 벗어나면 크래시가 난다. first/last/prefix로 안전하게 접근한다. 경계를 확인한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let nums = [1, 2, 3]
// BAD:越界索引
// print(nums[5]) // 崩溃
// GOOD:安全访问
if nums.indices.contains(5) {
print(nums[5])
}
// 安全方法:
let first = nums.first ?? 0
let last = nums.last ?? 0
// 安全取元素扩展:
extension Array {
func safe(_ index: Int) -> Element? {
indices.contains(index) ? self[index] : nil
}
}
print(nums.safe(1) ?? 0) // 2
print(nums.safe(9) ?? 0) // 0
// 空数组注意:
// nums[0] 也崩溃
// 遍历用 indices 或 enumerated
// 动态索引先检查

mutating

struct 메서드가 프로퍼티를 바꾸려면 mutating이 필요하다. 값 타입은 불변 복사본이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct Counter {
var count = 0
// 需要修改自身:
mutating func increment() {
count += 1
}
// 只读方法不需要:
func current() -> Int {
count
}
}
var c = Counter()
c.increment()
c.increment()
print(c.current()) // 2
// BAD:常量实例调 mutating
// let cc = Counter()
// cc.increment() // 错误
// GOOD:var 变量实例
var cc2 = Counter()
cc2.increment()
// 原因:
// mutating 修改 self
// 常量实例不可变
// 规则:
// struct/enum 需要 mutating
// class 不需要
// 下标、协议方法也适用
// 值语义的体现

문자열 인덱스

문자열은 정수 첨자를 쓸 수 없다. 문자가 여러 바이트를 차지한다. index 메서드를 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let s = "你好 Swift"
// BAD:整数下标
// let c = s[0] // 错误
// 字符数不等于字节数
// GOOD:索引 API
let first = s.startIndex
let c = s[first] // 你
print(c)
// 偏移:
let second = s.index(after: first)
print(s[second])
// 指定偏移:
let idx = s.index(s.startIndex, offsetBy: 3)
print(s[idx])
// 字符数组转换:
let chars = Array(s)
print(chars[0]) // 你
// 遍历:
for c in s { print(c) }
// 切片:
// s[..<idx]
// 多字节安全
// String.Index 保证边界

프로토콜과 Self

프로토콜에서 Self 제약을 쓴다. associatedtype을 쓴다. 타입 소거를 활용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 协议 Self 约束:
protocol EquatableLike {
func isEqualTo(_ other: Self) -> Bool
}
// 遵守:
struct Box: EquatableLike {
var value: Int
func isEqualTo(_ other: Box) -> Bool {
self.value == other.value
}
}
// BAD:把含 Self 协议当类型
// func compare(a: EquatableLike, b: EquatableLike) // 错误
// GOOD:泛型约束
func compare<T: EquatableLike>(_ a: T, _ b: T) -> Bool {
a.isEqualTo(b)
}
// 关联类型:
// protocol Container {
// associatedtype Item
// }
// 类型擦除:
// AnyEquatable 包装
// 需要动态多态时
// 用泛型或类型擦除

암시적 언래핑

T!는 암시적 언래핑 옵셔널이다. 초기화되지 않은 채 쓰면 크래시가 난다. 신중히 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// BAD:隐式解包访问未赋值崩溃
var name: String! = nil
// 使用直接:
// print(name) // 崩溃(未赋值)
// 赋值后:
name = "Nick"
print(name) // 自动解包
// 用途:
// IBOutlet 连接
// 初始化后必然有值
// 两阶段初始化
// 风险:
// 访问未赋值时崩溃
// 丢失可选安全性
// 不建议新代码使用
// GOOD:普通可选
var safeName: String?
// 解包:
if let n = safeName {
print(n)
}
// 强制场景:
// 确定初始化后再访问
// 或改为 let 常量

14.동시성

GCD, async/await, Task와 actor 동시성 모델을 다룬다.

GCD 기초

DispatchQueue는 직렬/동시 큐를 제공한다. 전역 큐와 메인 큐가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// 主队列:
let main = DispatchQueue.main
// 全局并发队列:
let background = DispatchQueue.global(qos: .background)
// 创建串行队列:
let serial = DispatchQueue(label: "com.app.serial")
// 创建并发队列:
let concurrent = DispatchQueue(
label: "com.app.concurrent", attributes: .concurrent)
// 提交任务:
background.async {
// 后台执行...
print("后台任务")
}
// 同步提交:
// serial.sync { ... }
// 延迟执行:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("1 秒后")
}
// QoS 优先级:
// .userInteractive / .userInitiated
// .utility / .background

디스패치 그룹

DispatchGroup으로 여러 작업이 끝나기를 기다린다. 여러 동시 작업 결과를 모은다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
let group = DispatchGroup()
let queue = DispatchQueue.global()
// 进入组:
// 多次异步任务
for i in 0..<3 {
group.enter()
queue.async {
print("任务 \(i) 执行")
// 模拟耗时
Thread.sleep(forTimeInterval: 0.2)
group.leave()
}
}
// 全部完成回调:
group.notify(queue: .main) {
print("所有任务完成")
}
// 同步等待:
// group.wait()
// 超时等待:
// group.wait(timeout: .now() + 5)
// 注意:
// enter 与 leave 必须成对
// 多线程安全
// 适合并行下载/批量处理

async/await

현대 Swift 동시성 문법이다. 비동기 함수와 일시 중단을 다룬다. 구조적 동시성을 지원한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// 异步函数:
func fetchData() async -> String {
// 模拟网络延迟
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "数据"
}
// 调用:
func load() async {
let data = await fetchData()
print(data)
}
// 异步抛错函数:
func risky() async throws -> Int {
try await Task.sleep(nanoseconds: 100_000_000)
return 42
}
// 调用:
func caller() async {
if let v = try? await risky() {
print(v)
}
}
// 注意:
// 异步函数不可在同步上下文调用
// Task { } 包一层
// 与旧回调风格互转

async let

여러 비동기 호출을 동시에 실행한다. 독립된 하위 작업에 바인딩한다. 병렬로 대기한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 三个独立异步任务:
func loadUser() async -> String { "用户" }
func loadPosts() async -> String { "帖子" }
func loadComments() async -> String { "评论" }
// async let 并发:
func loadAll() async -> String {
async let user = loadUser()
async let posts = loadPosts()
async let comments = loadComments()
// 全部并行执行:
let result = await "\(user) \(posts) \(comments)"
return result
}
// 使用:
// 三个任务同时跑
// await 聚合所有结果
// 注意:
// async let 绑定后必须 await
// 依赖关系:
// 有依赖用顺序 await
// 无依赖用 async let
// 适合并行请求独立接口

Task

Task는 동시성 단위를 만든다. 컨텍스트를 상속한다. 취소와 우선순위를 다룬다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Foundation
// 创建任务:
func run() async {
let task = Task {
try await Task.sleep(nanoseconds: 500_000_000)
return "完成"
}
let result = await task.value // String
print(result)
}
// 派发到主线程:
// @MainActor func updateUI()
// Task { @MainActor in ... }
// 任务取消:
task.cancel()
// 检查取消:
try Task.checkCancellation()
// 结构化任务组:
await withTaskGroup(of: String.self) { group in
for i in 0..<3 {
group.addTask {
"任务\(i)"
}
}
for await r in group {
print(r)
}
}
// 优先级:Task(priority: .high)

actor

actor는 상태를 격리한다. 데이터 경쟁을 막는다. 비동기 메서드로 접근한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Foundation
// 定义 actor:
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func getBalance() -> Double {
balance
}
}
// 使用(必须 await):
func test() async {
let account = BankAccount()
// 访问隔离状态需 await:
await account.deposit(100)
let balance = await account.getBalance()
print(balance)
}
// 并发安全:
// 编译器保证串行访问
// 避免数据竞争
// 对比:
// 类需自己加锁
// actor 自动隔离
// 注意:
// actor 方法默认 async 化
// 需 await 调用
// 适合共享可变状态

메인 스레드

UI 작업은 반드시 메인 스레드에서 해야 한다. 스레드를 전환한다. 메인 스레드인지 확인한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 判断主线程:
if Thread.isMainThread {
print("在主线程")
}
// 回到主线程:
DispatchQueue.main.async {
// UI 更新
print("主线程执行")
}
// 等待主线程执行:
// DispatchQueue.main.sync { }
// 注意死锁:
// 主线程 sync 到自己会死锁
// 现代 Swift:
@MainActor
func updateUI() {
// 隔离到主线程
}
// 从异步上下文调用:
// await MainActor.run { ... }
// 规则:
// UI 更新、动画主线程
// 耗时任务放后台
// 后台回主线程更新

스레드 안전성

가변 상태의 동시 접근을 보호한다. 락, 큐, 원자적 연산을 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Foundation
// 用串行队列保护:
final class SafeCounter {
private let queue = DispatchQueue(label: "counter")
private var _count = 0
var count: Int {
queue.sync { _count }
}
func increment() {
queue.sync { _count += 1 }
}
}
let counter = SafeCounter()
for _ in 0..<100 {
DispatchQueue.global().async {
counter.increment()
}
}
// 锁:
// NSLock
let lock = NSLock()
lock.lock()
// 临界区...
lock.unlock()
// 原子:
// OSAllocatedUnfairLock
// 现代方案:
// actor 隔离最安全
// 只读常量无需保护
// 优先不可变数据

15.네트워크

URLSession, HTTP 요청, 비동기 네트워크와 JSON 연동을 다룬다.

URLSession

URLSession으로 네트워크 요청을 보낸다. 설정과 델리게이트를 다룬다. 세션 종류가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// 默认会话:
let session = URLSession.shared
// 自定义配置:
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.waitsForConnectivity = true
let custom = URLSession(configuration: config)
// 请求:
let url = URL(string: "https://api.example.com")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
// 会话类型:
// .default / .ephemeral / .background
// ephemeral 不存缓存 Cookie
// background 后台传输
// 数据任务:
let task = session.dataTask(with: request)
// 配合 async/await 使用

GET 요청

GET을 보낸다. async/await로 데이터를 받는다. 응답 상태를 처리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// 异步 GET:
func fetchContent() async throws -> String {
let url = URL(string: "https://api.example.com/data")!
let (data, response) = try await URLSession.shared.data(from: url)
// 检查状态码:
guard let http = response as? HTTPURLResponse,
http.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return String(data: data, encoding: .utf8) ?? ""
}
// 使用:
func load() async {
do {
let content = try await fetchContent()
print(content)
} catch {
print("请求失败: \(error)")
}
}
// 查询参数:
var comps = URLComponents(string: "https://api.example.com/search")!
comps.queryItems = [URLQueryItem(name: "q", value: "swift")]
// 超时:
// request.timeoutInterval = 10
// 旧回调式:
// dataTask(with:completionHandler:)

POST 요청

JSON 본문을 보낸다. 폼도 제출한다. Content-Type을 설정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// POST JSON:
func postJSON() async throws {
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// body:
let payload: [String: Any] = ["name": "Nick", "age": 30]
request.httpBody = try JSONSerialization.data(withJSONObject: payload)
let (data, response) = try await URLSession.shared.data(for: request)
// 处理响应...
print(response)
}
// 表单:
var form = URLRequest(url: URL(string: "https://x/login")!)
form.httpMethod = "POST"
form.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
form.httpBody = "user=nick&pwd=123".data(using: .utf8)
// 注意:
// 大 body 用流式
// 响应同样检查状态码
// Codable 直接编码

비동기 네트워크

async/await로 네트워크를 호출한다. 요청을 동시에 보낸다. 작업을 취소한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// 异步请求 + Codable:
struct User: Codable { var id: Int; var name: String }
func fetchUsers() async throws -> [User] {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([User].self, from: data)
}
// 并发多个请求:
func loadAll() async throws -> (User, [String]) {
async let user = fetchUsers()
async let tags: [String] = ["a", "b"]
return (try await user.first!, try await tags)
}
// 任务取消:
// Task 取消时网络请求自动中断
// 响应检查
func fetchWithTimeout() async throws -> Data {
let url = URL(string: "https://api.example.com")!
// 超时:
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForRequest = 10
let session = URLSession(configuration: config)
return try await session.data(from: url).0
}
// 错误处理:
// URLError 类型分类

URL 구성

URLComponents로 URL을 안전하게 구성합니다. 쿼리 매개변수 인코딩. 경로 결합.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// 基础 URL:
var comps = URLComponents()
comps.scheme = "https"
comps.host = "api.example.com"
comps.path = "/v1/search"
// 查询参数:
comps.queryItems = [
URLQueryItem(name: "q", value: "swift 并发"),
URLQueryItem(name: "limit", value: "10"),
URLQueryItem(name: "page", value: "2"),
]
// 自动编码:
let url = comps.url!
print(url.absoluteString)
// 解析 URL:
if let c = URLComponents(string: url.absoluteString) {
print(c.host ?? "")
for item in c.queryItems ?? [] {
print("\(item.name)=\(item.value ?? "")")
}
}
// 好处:
// 自动百分号编码
// 中文/特殊字符安全
// 避免字符串拼接错误
// 添加片段:
comps.fragment = "section"

파일 다운로드

다운로드 작업으로 파일을 저장합니다. 진행률 모니터링. 백그라운드 다운로드.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// 数据下载:
func downloadImage() async throws -> Data {
let url = URL(string: "https://example.com/img.png")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// 保存到文件:
func save() async throws {
let data = try await downloadImage()
let dir = FileManager.default.temporaryDirectory
let file = dir.appendingPathComponent("img.png")
try data.write(to: file)
print(file.path)
}
// 文件下载任务:
let config = URLSessionConfiguration.background(withIdentifier: "dl")
let session = URLSession(configuration: config)
// 进度监听:
// 委托代理 URLSessionDownloadDelegate
// 下载完成回调
// 断点续传
// background 支持
// 注意:
// 大文件用磁盘写入
// 沙盒目录选择正确
// 内存考虑

네트워크 JSON

JSON을 요청하고 Codable로 디코딩합니다. 날짜와 전략 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// 模型:
struct Post: Codable {
var id: Int
var title: String
var created: Date
}
// 日期解码策略:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// 蛇形命名:
// decoder.keyDecodingStrategy = .convertFromSnakeCase
// 请求:
func fetchPosts() async throws -> [Post] {
let url = URL(string: "https://api.example.com/posts")!
let (data, _) = try await URLSession.shared.data(from: url)
return try decoder.decode([Post].self, from: data)
}
// 编码上传:
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// 错误处理:
// DecodingError 分类
// .dataCorrupted / .keyNotFound
// 网络层错误分开处理
// 模型验证

파일 업로드

multipart 폼 업로드. 파일 본문. 경계 구분자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// multipart 上传:
func upload(data: Data, filename: String) async throws {
let url = URL(string: "https://api.example.com/upload")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue(
"multipart/form-data; boundary=\(boundary)",
forHTTPHeaderField: "Content-Type")
// 构建 body:
var body = Data()
body.append("--\(boundary)\r\n".data(using: .utf8)!)
let header = "Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n\r\n"
body.append(header.data(using: .utf8)!)
body.append(data)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = body
let (_, response) = try await URLSession.shared.data(for: request)
print(response)
}
// 注意:
// boundary 唯一
// 大文件流式上传
// 进度用 URLSessionUploadTask
// 服务器响应校验

16.시간과 날짜

Date, DateFormatter, 달력 연산과 타이머.

Date

Date는 시점을 나타냅니다. 시간대에 무관한 절대 시각. 생성과 비교.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 当前时间:
let now = Date()
// 时间戳(秒):
let ts = now.timeIntervalSince1970
print(ts)
// 从时间戳创建:
let fromTs = Date(timeIntervalSince1970: 1700000000)
// 偏移创建:
let inOneHour = Date().addingTimeInterval(3600)
// 比较:
let earlier = Date(timeIntervalSince1970: 0)
print(now > earlier) // true
// 时间间隔:
let diff = now.timeIntervalSince(earlier)
print(diff) // 秒
// 排序:
let dates = [now, earlier].sorted()
// 注意:
// Date 与地区无关
// 展示需 DateFormatter
// 运算用 Calendar
// 不要手动加减秒

날짜 형식화

DateFormatter로 날짜를 표시합니다. 현지화된 형식. 사용자 지정 형식.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
let now = Date()
let formatter = DateFormatter()
// 本地化:
formatter.locale = Locale(identifier: "zh_CN")
formatter.timeZone = .current
// 预设风格:
formatter.dateStyle = .medium // 2026年8月2日
formatter.timeStyle = .short // 上午10:30
print(formatter.string(from: now))
// 自定义格式:
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(formatter.string(from: now))
// 格式符号:
// yyyy 年 / MM 月 / dd 日
// HH 24时 / mm 分 / ss 秒
// EEEE 星期全名
formatter.dateFormat = "yyyy年MM月dd日 EEEE"
print(formatter.string(from: now))
// 注意:
// 固定格式时设置 locale
// 展示优先预设风格
// 12/24 小时制

날짜 파싱

문자열을 Date로 변환. 고정 형식 파싱. ISO8601 파싱.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 自定义格式解析:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = .current
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date = formatter.date(from: "2026-08-02 10:30:00") {
print(date)
}
// ISO8601 解析:
let iso = ISO8601DateFormatter()
if let d = iso.date(from: "2026-08-02T10:30:00Z") {
print(d)
}
// 松散解析:
formatter.dateFormat = "yyyy/M/d"
// 注意:
// 解析失败返回 nil
// 固定格式用 en_US_POSIX
// 避免歧义
// 输入格式不一致时
// 多个 formatter 尝试
// 服务器 ISO 优先 iso8601

날짜 구성 요소

DateComponents로 연월일을 추출합니다. 달력 구성 요소 연산.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
let calendar = Calendar.current
let now = Date()
// 提取组件:
let comps = calendar.dateComponents(
[.year, .month, .day, .weekday], from: now)
print(comps.year ?? 0, comps.month ?? 0, comps.day ?? 0)
print(comps.weekday ?? 0) // 1=周日...7=周六
// 单独提取:
let year = calendar.component(.year, from: now)
// 组件创建日期:
var c = DateComponents()
c.year = 2026
c.month = 8
c.day = 2
let date = calendar.date(from: c)
print(date ?? Date())
// 今天开始:
let start = calendar.startOfDay(for: now)
// 注意:
// 组件用日历语义
// 跨时区安全
// 代替手算日期

달력 연산

날짜에 일/월 더하기와 빼기. 다음 주/다음 달. Calendar 연산 안전.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
let calendar = Calendar.current
let now = Date()
// 加天数:
let tomorrow = calendar.date(byAdding: .day, value: 1, to: now)!
// 加月:
let nextMonth = calendar.date(byAdding: .month, value: 1, to: now)!
// 加年:
let nextYear = calendar.date(byAdding: .year, value: 1, to: now)!
// 月初:
let monthStart = calendar.date(from:
calendar.dateComponents([.year, .month], from: now))!
// 下一个周一:
let monday = calendar.nextDate(
after: now, matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime)!
// 日期间隔:
let days = calendar.dateComponents(
[.day], from: now, to: tomorrow).day!
print(days) // 1
// 判断同日:
calendar.isDate(now, inSameDayAs: tomorrow)
// 时区:
// calendar.timeZone

시간 간격

코드 소요 시간 측정. TimeInterval 초 단위. 성능 타이밍.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// 计时:
let start = Date()
// 执行任务...
Thread.sleep(forTimeInterval: 0.3)
let elapsed = Date().timeIntervalSince(start)
print("\(elapsed) 秒") // ~0.3
// 高精度计时:
let t0 = DispatchTime.now()
// 任务...
let t1 = DispatchTime.now()
let nanos = t1.uptimeNanoseconds - t0.uptimeNanoseconds
print("\(Double(nanos) / 1e6) ms")
// 休眠:
Thread.sleep(forTimeInterval: 0.5)
// 异步休眠:
// try await Task.sleep(nanoseconds: 500_000_000)
// TimeInterval = Double 秒
// 注意:
// 预热忽略首次
// 多次取平均
// 单位统一秒

Timer

타이머 반복 실행. 일회성 타이머. RunLoop 주의.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 重复定时器:
var count = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
count += 1
print("tick \(count)")
if count >= 3 {
timer.invalidate() // 停止
}
}
// 一次性:
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) {
print("2 秒后执行")
}
// 注意:
// scheduledTimer 加入当前 RunLoop
// 主线程可用
// 后台线程需 run loop
// 无效:
// timer.invalidate()
// 避免强引用:
// 闭包捕获 self 循环
// 用 [weak self]
// 现代替代:
// Task.sleep 循环

날짜 비교

날짜 순서 비교. 같은 날 판정. 정렬과 검증.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
let calendar = Calendar.current
let now = Date()
let past = Date(timeIntervalSinceNow: -3600)
let future = Date(timeIntervalSinceNow: 3600)
// 直接比较:
print(future > now) // true
// 同一天:
calendar.isDate(now, inSameDayAs: future)
// 今天/昨天/明天:
calendar.isDateInToday(now)
calendar.isDateInYesterday(past)
calendar.isDateInTomorrow(future)
// 区间包含:
let range = past...future
print(range.contains(now)) // true
// 排序:
let dates = [future, past, now].sorted()
// 差值天数:
let daysBetween = calendar.dateComponents(
[.day], from: past, to: future).day ?? 0
print(daysBetween)
// 验证过期:
if let expires = Calendar.current.date(byAdding: .hour, value: 2, to: now),
now > expires {
print("已过期")
}

17.프로세스와 시스템

명령행 인자, 환경 변수, 프로세스 작업과 파일 경로.

명령줄 인수

CommandLine으로 인자를 가져옵니다. 인자와 옵션 처리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// 获取参数:
let args = CommandLine.arguments
print(args) // ["程序名", "arg1", "arg2"]
// 参数个数:
let count = CommandLine.arguments.count
// 简单解析:
let arguments = CommandLine.arguments
var verbose = false
var inputFile = ""
var index = 1
while index < arguments.count {
switch arguments[index] {
case "-v", "--verbose":
verbose = true
case "-f", "--file":
index += 1
inputFile = arguments[index]
default:
print("未知参数: \(arguments[index])")
}
index += 1
}
print("verbose=\(verbose), file=\(inputFile)")
// 注意:
// 第一个是程序路径
// 参数用 CommandLine.arguments
// 生产可用 ArgumentParser 库

환경 변수

환경 변수 읽기. 설정과 전달. 민감 정보 주의.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// 读取环境变量:
if let home = ProcessInfo.processInfo.environment["HOME"] {
print(home)
}
// 检查存在:
let hasKey = ProcessInfo.processInfo.environment["API_KEY"] != nil
// 全部变量:
for (key, value) in ProcessInfo.processInfo.environment {
print("\(key)=\(value)")
}
// 默认值:
let port = ProcessInfo.processInfo.environment["PORT"] ?? "8080"
print(port)
// 系统信息:
let os = ProcessInfo.processInfo.operatingSystemVersionString
let cores = ProcessInfo.processInfo.activeProcessorCount
print(os, cores)
// 安全注意:
// 密钥放环境变量
// 不提交到代码库
// 敏感值避免打印

Process

Process로 자식 프로세스를 시작합니다. 명령 실행. 출력 캡처.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// 运行命令:
func run(_ cmd: String, _ args: [String]) throws -> String {
let process = Process()
process.executableURL = URL(fileURLWithPath: cmd)
process.arguments = args
// 捕获输出:
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8) ?? ""
}
// 使用:
try print(run("/usr/bin/env", ["date"]))
// 环境变量:
process.environment = ["PATH": "/usr/bin"]
// 工作目录:
process.currentDirectoryURL = URL(fileURLWithPath: "/tmp")
// 退出码:
let status = process.terminationStatus
// 注意:
// 路径要正确
// 长命令用字符串拆分
// 异步用 terminationHandler

경로 처리

URL 경로 작업. 결합, 확장자, 파일 이름. 샌드박스 디렉터리.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
let fileURL = URL(fileURLWithPath: "/tmp/data/file.txt")
// 文件名:
let name = fileURL.lastPathComponent // "file.txt"
// 去掉扩展名:
let base = fileURL.deletingPathExtension().lastPathComponent // "file"
// 扩展名:
let ext = fileURL.pathExtension // "txt"
// 父目录:
let dir = fileURL.deletingLastPathComponent
// 拼接:
let newFile = dir.appendingPathComponent("out.txt")
// 追加扩展名:
let md = fileURL.appendingPathExtension("md")
// 沙盒目录:
let docs = FileManager.default.urls(
for: .documentDirectory, in: .userDomainMask)[0]
let caches = FileManager.default.temporaryDirectory
// 判断标准:
// fileURL.hasDirectoryPath
// 相对路径转绝对:
// URL(fileURLWithPath: "a.txt").standardizedFileURL

종료 코드

프로그램 종료 코드. exit과 fatalError. 0 성공 비 0 실패.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// 正常退出:
exit(0)
// 失败退出:
// exit(1)
// 带错误信息:
// 输出到 stderr
// FileHandle.standardError.write(
// Data("错误\n".utf8))
// exit(2)
// 常用约定:
// 0 成功
// 1 一般错误
// 2 用法错误
// 127 命令未找到
// 判断子进程状态:
let process = Process()
// process.waitUntilExit()
// let code = process.terminationStatus
// print(code)
// 注意:
// exit 立即终止
// 不执行 defer
// 脚本中可用
// 正常结束不调 exit
// 返回 Int 主函数

시그널 처리

시스템 시그널 캡처. SIGINT 등. 우아한 종료.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
import Darwin
// 信号处理:
signal(SIGINT) { _ in
print("收到 Ctrl-C")
exit(0)
}
// 忽略信号:
// signal(SIGHUP, SIG_IGN)
// 信号列表:
// SIGINT 中断 (2)
// SIGTERM 终止 (15)
// SIGKILL 强杀 (9) 不可捕获
// 优雅退出模式:
var running = true
signal(SIGTERM) { _ in
running = false
}
// while running {
// // 主循环...
// sleep(1)
// }
// 注意:
// 信号处理器要简单
// 用原子变量通信
// DispatchSource 更现代:
// DispatchSource.makeSignalSource
// 异常退出不推荐

작업 디렉터리

현재 작업 디렉터리 가져오기와 전환. 파일 상대 경로 기준.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 当前工作目录:
let cwd = FileManager.default.currentDirectoryPath
print(cwd)
// 切换目录:
// FileManager.default.changeCurrentDirectoryPath("/tmp")
// 相对路径解析:
let rel = URL(fileURLWithPath: "data/config.json")
print(rel.path) // 基于当前目录
// 标准化:
let normalized = rel.standardizedFileURL
// 判断路径:
let isAbsolute = rel.isFileURL
// 常用位置:
let home = FileManager.default.homeDirectoryForCurrentUser
let temp = FileManager.default.temporaryDirectory
// 注意:
// 工作目录影响相对路径
// 沙盒环境不同
// GUI 应用注意:
// 工作目录可能是根
// 显式构建绝对路径更稳
// 配置用相对基准

프로세스 파일 I/O

표준 입력 읽기. 표준 출력 쓰기. 파이프 상호작용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// 读取标准输入:
if let line = readLine() {
print("收到: \(line)")
}
// 循环读取:
// while let line = readLine() {
// print(line)
// }
// 标准输出:
print("输出到 stdout")
// 标准错误:
FileHandle.standardError.write(
Data("错误信息\n".utf8))
// 从文件重定向:
// swift main.swift < input.txt
// 管道输入:
// echo "data" | swift main.swift
// 输出重定向:
// swift main.swift > out.txt 2>&1
// 注意:
// readLine 返回 nil 表示 EOF
// 大输入注意性能
// 二进制用 FileHandle.standardInput

18.정규 표현식

정규 리터럴, NSRegularExpression과 패턴 매칭.

정규식 기초

정규식 문법 개념. 문자 클래스, 수량자, 그룹.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// 基本语法:
// 字面量:/abc/ 匹配 abc
// 字符类:[abc] [0-9] \\d
// 量词:* + ? {n,m}
// 锚点:^ $ \\b
// 分组:(...) 捕获
// 或:a|b
// Swift 5.7+ 正则字面量:
let pattern = /\\d{4}-\\d{2}-\\d{2}/
let text = "日期 2026-08-02 结束"
if let match = text.firstMatch(of: pattern) {
print(match.0) // 2026-08-02
}
// 常用:
// \\d 数字 \\w 单词 \\s 空白
// \\D \\W \\S 反向
// 贪婪 vs 懒惰:
// a.*b 贪婪 / a.*?b 懒惰
// 转义:\\\\. 匹配点号

정규 리터럴

Swift 5.7 정규 리터럴 /.../. 타입화된 매칭. 캡처 이름 지정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 正则字面量:
let pattern = /h(\\d+)x/
// 类型化捕获:
let text = "h123x h99x"
if let match = text.firstMatch(of: pattern) {
print(match.0) // h123x
print(match.1) // 123 (Substring)
}
// 命名捕获:
let named = /(?<code>\\d{3})-(?<area>\\d{4})/
if let m = "123-4567".firstMatch(of: named) {
print(m.code) // 123
print(m.area) // 4567
}
// 选项:
let caseInsensitive = /abc/ .ignoresCase()
// 查找全部:
for m in text.matches(of: pattern) {
print(m.0)
}
// 注意:
// 类型安全编译期校验
// 与 NSRegularExpression 互转
// 需 macOS 13 / iOS 16+

NSRegularExpression

NSRegularExpression 정규 엔진. 범위 매칭. 구 시스템 호환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// 创建:
let pattern = "\\d+"
let regex = try! NSRegularExpression(pattern: pattern)
// 匹配:
let text = "价格 100 元,折扣 20 元"
let range = NSRange(text.startIndex..., in: text)
// 全部匹配:
let matches = regex.matches(in: text, range: range)
for m in matches {
if let r = Range(m.range, in: text) {
print(text[r])
}
}
// 捕获组:
let groupRegex = try! NSRegularExpression(pattern: "(\\d+)-(\\d+)")
// 首匹配:
if let first = groupRegex.firstMatch(in: text, range: range) {
// first.range(at: 1) 第一组
}
// 替换:
let replaced = regex.stringByReplacingMatches(
in: text, range: range, withTemplate: "#")
print(replaced)
// 选项:
// .caseInsensitive / .anchorsMatchLines

매칭

첫 매칭, 전체 매칭, 매칭 여부 판정. 범위 가져오기.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 判断是否匹配:
let email = "[email protected]"
// 完整匹配:
let full = /[a-z]+@[a-z]+\\.[a-z]+/
print(email.wholeMatch(of: full) != nil)
// 部分包含:
print(email.contains(/@/))
// 首次匹配:
let pattern = /\\d+/
if let m = "abc123def".firstMatch(of: pattern) {
print(m.0) // 123
}
// 全部匹配:
let all = "a1b2c3".matches(of: /\\d+/)
print(all.count) // 3
// 匹配位置:
// m.range 返回范围
// 前缀匹配:
if "2026-08".hasPrefix("2026") {}
// 注意:
// wholeMatch 完整匹配
// firstMatch 首次
// matches 全部
// 空匹配注意

캡처 그룹

캡처 그룹 내용 추출. 명명 캡처. 정규식 그룹 참조.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 捕获组:
let pattern = /(\\d{4})-(\\d{2})-(\\d{2})/
let text = "2026-08-02"
if let m = text.wholeMatch(of: pattern) {
print(m.0) // 2026-08-02
print(m.1) // 2026
print(m.2) // 08
print(m.3) // 02
}
// 命名捕获:
let named = /(?<year>\\d{4})-(?<month>\\d{2})/
if let m = "2026-08".wholeMatch(of: named) {
print(m.year) // 2026
print(m.month) // 08
}
// 可选捕获:
// (x)? 匹配则可选
// 非捕获组:
// (?:x) 不捕获
// NSRegularExpression:
// m.range(at: 1) 提取组
// 用途:
// 解析结构化文本
// 提取 key=value

치환

정규식으로 텍스트 치환. 템플릿이 캡처 그룹 참조. 조건부 치환.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// 字面替换:
let s = "Hello, world!"
let replaced = s.replacingOccurrences(of: "o", with: "0")
print(replaced) // Hell0, w0rld!
// 正则替换:
let text = "日期: 2026-08-02"
let regex = try! NSRegularExpression(pattern: "(\\d{4})-(\\d{2})-(\\d{2})")
let nsRange = NSRange(text.startIndex..., in: text)
// 模板引用组:
let formatted = regex.stringByReplacingMatches(
in: text, range: nsRange,
withTemplate: "$2/$3/$1")
print(formatted) // 日期: 08/02/2026
// 替换范围:
let trimmed = text.replacingOccurrences(
of: "\\s+", with: " ", options: .regularExpression)
// 模板特殊字符:
// $0 全匹配 $1 组1
// 条件替换需循环匹配

분할

정규식으로 문자열 분할. 구분자 유지와 삭제. 다중 구분자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// 字符串分割:
let csv = "a,b,c"
let parts = csv.split(separator: ",")
print(parts) // ["a", "b", "c"]
// 正则分割:
let text = "one two three"
let words = text.components(separatedBy: .whitespacesAndNewlines)
// 多分隔符:
let messy = "a;b,c|d"
let regex = try! NSRegularExpression(pattern: "[;,_|]+")
let splitParts = regex.split(messy)
// 保留空串:
// split 默认删空
// omittingEmptySubsequences
// 按行分割:
let lines = text.components(separatedBy: .newlines)
// 注意:
// 分隔符正则
// 拆分时去掉分隔符
// 大文本注意性能

일반 패턴

이메일, URL, 숫자, 휴대폰 등 일반 정규식 템플릿.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// 邮箱:
let email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/
// URL:
let url = /https?:\\/\\/[\\w.-]+(?:\\/[\\w._-]*)*/
// 整数:
let int = /^[+-]?\\d+$/
// 浮点数:
let float = /^[+-]?\\d+\\.\\d+$/
// 手机号:
let phone = /^1[3-9]\\d{9}$/
// 身份证:
let idCard = /^\\d{17}[0-9Xx]$/
// IP 地址:
let ip = /(?:\\d{1,3}\\.){3}\\d{1,3}/
// 中文:
let chinese = /[\\u4e00-\\u9fa5]+/
// 空格:
let spaces = /\\s+/
// 使用:
print("[email protected]".wholeMatch(of: email) != nil)
print("13800138000".wholeMatch(of: phone) != nil)
// 注意:
// 正则要经过测试
// 性能:复杂模式慎用
// 实际校验用专门的库

19.빌드와 도구

swiftc 컴파일, SwiftPM 빌드, 포맷과 검사 도구.

swiftc 컴파일

명령행에서 단일 파일 컴파일. 실행 파일 생성. 최적화 옵션.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 编译执行文件:
# swiftc main.swift -o app
# 运行:
# ./app
# 直接解释:
# swift main.swift
# 多个源文件:
# swiftc main.swift utils.swift -o app
# 生成模块:
# swiftc -emit-module
# 优化:
# -O 优化编译
# -Ounchecked 更快但不检查
# -Onone 调试
# 调试信息:
# -g 生成调试符号
# 链接框架:
# -framework Foundation
# 交叉编译:
# -target x86_64-unknown-linux-gnu
# 查看版本:
# swift --version
# 说明:
# SwiftPM 管理多文件
# 单文件开发用 swiftc

SwiftPM 빌드

swift build로 프로젝트 컴파일. 증분 빌드. 릴리스 모드.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 初始化项目:
# swift package init --type executable
# 构建:
# swift build
# 发布模式:
# swift build -c release
# 指定产品:
# swift build --product app
# 增量构建(默认)
# 清理:
# swift package clean
# 更新依赖:
# swift package resolve
# 查看依赖树:
# swift package show-dependencies
# 输出位置:
# .build/debug/ 和 .build/release/
# 运行可执行:
# .build/debug/myapp
# 说明:
# Package.swift 声明包
# 依赖用 git 地址/版本
# 构建产物在 .build/

테스트 실행

swift test 테스트 스위트. XCTest와 테스트 타깃.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 运行测试:
# swift test
# 指定测试:
# swift test --filter TestMath
# 单个用例:
# swift test --filter TestMath/testAdd
# 并行:
# swift test --parallel
# 代码覆盖:
# swift test --enable-code-coverage
# 生成覆盖报告:
# llvm-cov report .build/debug/*.xctest
# 测试文件:
import XCTest
final class TestMath: XCTestCase {
func testAdd() {
XCTAssertEqual(add(1, 2), 3)
}
}
# 断言:
# XCTAssertTrue / XCTAssertNil
# 异步测试:
# XCTestExpectation
# 说明:
# 测试目标在 Package.swift 声明
# 命名 testXxx 自动发现

Package.swift

SwiftPM 패키지 매니페스트. 타깃과 의존성 선언. 플랫폼 설정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyApp",
// 平台:
platforms: [
.macOS(.v13)
],
// 产品:
products: [
.executable(name: "app", targets: ["App"]),
.library(name: "Core", targets: ["Core"]),
],
// 依赖:
dependencies: [
.package(url: "https://github.com/xxx/yyy", from: "1.0.0"),
],
// 目标:
targets: [
.executableTarget(
name: "App",
dependencies: ["Core", "yyy"]),
.target(name: "Core"),
.testTarget(
name: "AppTests",
dependencies: ["App"]),
]
)
# 依赖版本:
# from: 1.0.0 向上兼容
# exact: 1.2.3 精确
# branch: main

SwiftLint

코드 스타일 검사. 규칙 설정. CI 통합.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 安装(Homebrew):
# brew install swiftlint
# 运行:
# swiftlint
# 修复:
# swiftlint autocorrect
# 指定目录:
# swiftlint lint --path Sources/
# 配置文件:
# 生成默认配置
# swiftlint generate-docs
# 常见规则:
# line_length 行长
# trailing_whitespace 尾随空格
# force_cast 强制转换
# force_unwrapping 强制解包
# 配置 .swiftlint.yml:
# disabled_rules:
# - force_cast
# opt_in_rules:
# - empty_count
# 忽略文件:
# excluded:
# - Generated/
# CI 集成:
# 失败则构建失败
# 保证风格一致

swift-format

공식 코드 포맷 도구. 포맷 규칙과 설정.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 格式化:
# swift-format format -i Sources/*.swift
# 检查:
# swift-format lint -r Sources/
# 配置:
# swift-format dump-configuration > .swift-format
# 与 Xcode 集成
# 配置示例:
# {
# "indentation": {
# "spaces": 4
# },
# "lineBreakBeforeEachArgument": false,
# "rules": {
# "AllPublicDeclarationsHaveDocumentation": false
# }
# }
# 使用方式:
# 编辑器保存时格式化
# pre-commit 钩子
# CI 检查格式
# 与 SwiftLint 区别:
# format 管格式
# lint 管风格规则
# 两者可配合

xcodebuild

Xcode 프로젝트 명령행 빌드. 내보내기와 아카이브. CI 사용.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 列出工程/工作区:
# xcodebuild -list
# 构建:
# xcodebuild -project App.xcodeproj \
# -scheme App -configuration Debug build
# 工作区构建:
# xcodebuild -workspace App.xcworkspace \
# -scheme App build
# 真机 SDK:
# -sdk iphoneos
# 模拟器:
# -destination 'platform=iOS Simulator,name=iPhone 15'
# 测试:
# xcodebuild test -scheme App
# 归档:
# xcodebuild archive -scheme App \
# -archivePath build/App.xcarchive
# 导出 IPA:
# xcodebuild -exportArchive \
# -exportOptionsPlist ExportOptions.plist
# CI 常用:
# 输出结果用 xcresulttool 分析

릴리스 절차

버전 관리, 빌드 릴리스, 배포. 릴리스 체크리스트.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 版本号规范:
# MAJOR.MINOR.PATCH
# 1.0.0 初始
# 2.1.0 新功能
# 2.1.3 修复
# Git 打标签:
# git tag v2.1.0
# git push --tags
# 发布流程:
# 1. 版本号 bump
# 2. CHANGELOG 更新
# 3. 测试全部通过
# 4. 构建发布版
# 5. 生成归档
# 6. 签名与公证
# 7. 上传分发
# 命令行工具分发:
# 静态编译
# tar 打包
# Homebrew formula
# 检查清单:
# 测试覆盖
# 文档更新
# 依赖锁定
# 产物验证

공식 링크

공식 문서와 리소스에 대한 직접 링크.

이 치트시트에 대해

이 페이지는 Swift 5.9의 자기 완결적인 치트시트로, 실제 프로젝트에서 언어 핵심과 가장 자주 쓰이는 표준 라이브러리의 약 80% 일반적인 사용법을 다룹니다. 내용은 현대적인 관용구를 지향합니다: 값 타입(struct/enum), 프로토콜 지향 설계, async/await 구조적 동시성, if let 패턴 매칭, 그리고 Codable 직렬화. 권위 있는 참고 자료는 Swift 공식 언어 가이드와 API 설계 명세를 참조하세요. 19개 섹션은 각각 하나의 주제에 집중합니다. 첫 번째 프로그램부터 옵셔널, 프로토콜, 동시성, 흔한 함정까지. 각 섹션은 예제가 있는 8개의 하위 섹션(각 5-20줄)으로 나뉘며, 총 약 150개의 주제를 다룹니다. 코드 스니펫은 의도적으로 짧고 자기 설명적입니다. 모든 처리는 브라우저에서 완료됩니다. 업로드 없음, 추적 없음. 이 페이지는 GuruToolkit 무료 개발자 도구 모음의 일부이며, 코드 스니펫은 어떠한 보증 없이 자유롭게 사용할 수 있습니다.

버전 2.1.0