Swift 문법 심화 목차
1. 프로퍼티 옵저버
2. 타입 캐스팅
3. 접근제한자
4. 클로저
5. 고차함수
6. 예외처리
7. ARC와 메모리 누수
8. 프로토콜
9. 확장
10. 제네릭
11. 비동기와 네트워킹
12. Combine 맛보기
13. RxSwift 맛보기
Swift 문법 심화 정리 (1) 은 여기로: 1. 프로퍼티 옵저버 / 2. 타입 캐스팅 / 3. 접근 제한자
Swift 문법 심화 정리 (2) 는 여기로: 4. 클로저 / 5. 고차함수 / 10. 제네릭
오늘은 예외처리, ARC와 메모리 누수, 비동기와 네트워킹을 다뤄 볼 예정
예외처리에 대한 자세한 내용은 여기로
ARC는 스위프트의 메모리 관리 기법으로, 개발자가 객체의 메모리를 명시적으로 관리하지 않아도 되도록 한다. 하지만 잘못된 참조 관계를 만들면 메모리 누수가 발생할 수 있다.
스위프트는 객체의 메모리를 자동으로 관리하기 위해 ARC(Automatic Reference Counting)를 사용한다. ARC는 각 객체가 얼마나 많은 참조를 받고 있는지 카운트하고, 더 이상 참조되지 않는 객체를 자동으로 해제한다.
스위프트에서 클래스는 참조 타입으로, 클래스 인스턴스는 여러 변수나 상수에서 참조될 수 있다. ARC는 이러한 참조를 카운트해서, 객체가 더 이상 사용되지 않을 때 메모리를 해제한다.
❗️ 참고: 열거형과 구조체는 값 타입이다.
메모리 누수는 객체가 더 이상 필요 없지만, 메모리에서 해제되지 않고 남아있는 상황을 의미한다. 주로 강한 참조 순환(Strong Reference Cycle) 때문에 발생.
두 객체가 서로를 강하게 참조하면, 서로를 해제하지 못하는 상황이 발생하는데 이를 강한 참조 순환이라고 한다.
class Person {
var name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
var unit: String
var tenant: Person?
init(unit: String) {
self.unit = unit
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person? = Person(name: "John Appleseed")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil
unit4A = nil
// 여기서 Person과 Apartment 인스턴스는 메모리에서 해제되지 않음
class Person {
var name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
var unit: String
weak var tenant: Person?
init(unit: String) {
self.unit = unit
}
deinit {
print("Apartment \(unit) is being deinitialized")
}
}
var john: Person? = Person(name: "John Appleseed")
var unit4A: Apartment? = Apartment(unit: "4A")
john?.apartment = unit4A
unit4A?.tenant = john
john = nil
unit4A = nil
// 이제 Person과 Apartment 인스턴스는 메모리에서 해제됨
class Customer {
var name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class CreditCard {
var number: String
unowned var customer: Customer
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit {
print("Card #\(number) is being deinitialized")
}
}
var john: Customer? = Customer(name: "John Appleseed")
john?.card = CreditCard(number: "1234-5678-9012-3456", customer: john!)
john = nil
// 이제 Customer와 CreditCard 인스턴스는 메모리에서 해제됨
ARC는 스위프트에서 객체의 메모리를 자동으로 관리해주지만, 강한 참조 순환을 방지하기 위해 약한 참조와 미소유 참조를 적절히 사용해야 한다. 이렇게 하면 메모리 누수를 방지하고, 메모리를 효율적으로 관리할 수 있다.
: 프로그램의 작업을 병렬로 수행하기 위한 기본 단위이다. 스레드를 사용하면 동시에 여러 작업을 처리할 수 있다. 스레드는 프로세스 내에서 실행되며, 프로세스의 자원을 공유한다. 여러 스레드를 사용하면 CPU 코어를 효율적으로 활용할 수 있지만, 스레드 간의 자원 공유와 동기화 문제로 인해 복잡성이 증가할 수 있다.
Swift의 스레드 종류
메인 스레드(Main Thread) - 애플리케이션의 주요 인터페이스 및 UI 업데이트를 담당하는 스레드입니다. UI 요소의 변경은 메인 스레드에서 수행되어야 합니다.
백그라운드 스레드(Background Threads) - 메인 스레드 이외에 동시에 작업을 수행하기 위해 생성되는 스레드들을 일컫습니다. 주로 작업을 분산하거나 병렬로 처리할 때 사용됩니다.
동기 (Synchronoun): 요청과 결과가 순차적으로 발생하며, 호출한 작업이 완료될 때까지 다음 작업을 기다린다. 동기 작업은 코드의 흐름이 직관적이지만, 긴 작업을 실행할 때 메인 스레드를 차단할 수 있다.
비동기 (Asynchronous): 비동기 작업은 요청과 결과가 독립적으로 발생하며, 호출한 작업이 완료되기 전에 다음 작업을 계속 진행할 수 있다. 비동기 작업은 UI 응답성을 유지하는 데 유리하지만, 콜백이나 클로저를 사용하여 작업 완료를 처리해야 하므로 코드가 복잡해질 수 있다.



GCD(Grand Central Dispatch)는 멀티스레딩을 쉽게 관리하기 위한 저수준 API이다. GCD를 사용하면 비동기 작업을 간편하게 관리할 수 있다. DispatchQueue를 사용하여 작업을 직렬 또는 동시로 실행할 수 있다. GCD는 시스템 수준에서 최적화되어 있어 성능이 뛰어나다.
DispatchQueue.main은 메인 스레드에서 실행되는 직렬 큐이다. 주로 UI 업데이트 작업에 사용된다. 메인 스레드는 앱의 UI를 담당하므로, 메인 큐에서 긴 작업을 실행하면 UI가 응답하지 않게 될 수 있다. 따라서 메인 큐에서는 짧고 빠른 작업만 실행하는 것이 좋다.
DispatchQueue.global은 시스템에서 제공하는 동시 큐이다. 여러 가지 QoS(Quality of Service) 레벨로 제공되며, 백그라운드 작업, 네트워크 호출 등 다양한 용도로 사용된다. DispatchQueue.global(qos: .background)와 같이 특정 QoS를 지정하여 작업의 우선순위를 조절할 수 있다.
예제:
DispatchQueue.global(qos: .background).async {
// 백그라운드에서 실행될 작업
print("Performing task in background")
DispatchQueue.main.async {
// 메인 스레드에서 UI 업데이트
print("Updating UI on main thread")
}
}
네트워킹은 인터넷이나 로컬 네트워크를 통해 데이터 통신을 하는 것이다. 스위프트에서는 주로 URLSession을 사용하여 네트워크 요청을 처리한다. 데이터를 가져오거나, 보내거나, 파일을 다운로드하고 업로드할 수 있다. 네트워크 요청은 비동기적으로 처리하여 UI 응답성을 유지할 수 있다.
예제:
import Foundation
func fetchData(from url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// 에러 처리
if let error = error {
print("Error: \(error)")
return
}
// 데이터 확인
guard let data = data else {
print("No data received")
return
}
// 데이터 처리 (예: JSON 파싱)
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print("JSON response: \(json)")
} catch {
print("Failed to parse JSON: \(error)")
}
}
task.resume()
}
if let url = URL(string: "https://api.example.com/data") {
fetchData(from: url)
}