Swift 의 ARC 와 GC(가비지 컬렉터)의 차이점

Swift의 ARC(Automatic Reference Counting)와 GC(Garbage Collection)는 메모리 관리를 자동화하는 두 가지 다른 방식입니다. 이 두 시스템은 프로그램이 더 이상 필요하지 않은 메모리를 자동으로 회수하여 메모리 누수를 방지하고 성능을 최적화합니다. 그러나 그 구현 방식과 동작 원리가 다릅니다.

Swift ARC(Automatic Reference Counting)

1. 동작 방식:

•	ARC는 컴파일 타임과 런타임에 참조 카운팅을 사용하여 메모리를 관리합니다.
•	각 객체는 참조 카운트를 가지고 있으며, 객체에 대한 참조가 생성되면 참조 카운트가 증가하고, 참조가 해제되면 참조 카운트가 감소합니다.
•	참조 카운트가 0이 되면 객체의 메모리가 해제됩니다.

2. 장점:

•	실시간 성능: 메모리가 즉시 회수되므로 일관된 성능을 보장합니다.
•	예측 가능성: 메모리 해제 시점을 정확하게 예측할 수 있습니다.
•	낮은 오버헤드: 주기적인 메모리 스캔이 필요 없으므로 오버헤드가 적습니다.

3. 단점:

•	순환 참조: 두 객체가 서로를 참조할 때 참조 카운트가 0이 되지 않아 메모리가 해제되지 않는 문제가 발생할 수 있습니다. 이 문제를 해결하기 위해서는 약한 참조(weak reference)나 미소유 참조(unowned reference)를 사용해야 합니다.
•	수동 관리 필요: 개발자가 약한 참조와 강한 참조를 명시적으로 관리해야 합니다.

GC(Garbage Collection)

1. 동작 방식:

•	GC는 런타임에 주기적으로 메모리 힙을 스캔하여 더 이상 사용되지 않는 객체를 찾아내어 메모리를 회수합니다.
•	여러 가지 알고리즘(예: Mark-and-Sweep, Generational GC 등)을 사용하여 객체가 더 이상 사용되지 않는 시점을 판단합니다.

2. 장점:

•	자동 순환 참조 해결: 순환 참조 문제를 자동으로 해결할 수 있습니다.
•	개발자 편의성: 메모리 관리에 대한 부담이 적습니다. 개발자가 메모리 해제에 대해 신경 쓸 필요가 없습니다.

3. 단점:

•	비예측성: 메모리 회수가 언제 일어날지 예측하기 어렵습니다. 이는 성능 지연을 초래할 수 있습니다.
•	오버헤드: 주기적인 메모리 스캔이 필요하여 런타임 오버헤드가 발생할 수 있습니다.
•	일시적인 성능 저하: GC가 실행되는 동안 애플리케이션의 성능이 일시적으로 저하될 수 있습니다.

요약

•	ARC는 컴파일 타임과 런타임에 참조 카운트를 사용하여 메모리를 관리하며, 실시간 성능과 예측 가능성을 제공합니다. 그러나 순환 참조 문제를 수동으로 관리해야 합니다.
•	GC는 런타임에 주기적으로 메모리 스캔을 통해 더 이상 사용되지 않는 객체를 회수하며, 자동으로 순환 참조 문제를 해결합니다. 그러나 성능 예측이 어렵고 주기적인 오버헤드가 발생합니다.

이 두 메모리 관리 방식은 각각의 장단점이 있으므로, 애플리케이션의 요구 사항에 따라 적합한 방식을 선택하는 것이 중요합니다.

Swift Vapor

brew install vapor

Init Example Project

vapor -n hello

Initialize Model

import Vapor
import Fluent

final class Entry: Model {
    static let schema: String = "entries"
    
    init() {}
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "title")
    var title: String
    
    @Field(key: "content")
    var content: String
    
    init(id: UUID? = nil, title: String, content: String) {
        self.id = id
        self.title = title
        self.content = content
    }
}

property wrapper _ @ID 는 Model 프로토콜에서 id 프로퍼티 앞에 붙인다. 데이터베이스 스키마는 static let 으로 정의한다.

Migrations 정의

**마이그레이션(Migration)**은 데이터베이스의 스키마를 관리하고, 데이터베이스 구조를 변경하기 위해 사용된다.

struct CreateEntry: AsyncMigration {
    func prepare(on database: any Database) async throws {
        try await database.schema("entries")
            .id()
            .field("title", .string, .required)
            .field("content", .string, .required)
            .create()
    }
    
    func revert(on database: any Database) async throws {
        try await database.schema("galaxies").delete()
    }
}

Controller

해당 CRUD 컨트롤러를 실행하는 라우트 파일에서 아래와 같이 호출해준다.

    let journalController = JournalController()

     app.get("entries", use: journalController.index)