ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [사전캠프 TIL 3일차] ARC / 클로저 / OOP
    iOS💖 2025. 2. 5. 18:22

    키워드

    🗂️ ARC (strong, weak, unowned, optional unowned 타입) ➡️ 복잡한 데이터 구조 설정시 적용해보기! 🔴

    클로저

    OOP

     

     

    ARC

    ARC (Automatic Reference Counting) ?

    1. 참조 변수 (클래스 instance, 클로저 등) 가 더이상 참조 되지 않을때, Heap 영역에 할당된 메모리를 자동으로 해제

    2. RC 계산 개념으로 메모리를 자동 해제

    3. Automatic Reference Counting 이므로, reference 타입에만 적용 (struct, enum 등 X)

     

     

    모든 instance 는 자신의 RC 값이 있음

    (instance 생성시, Heap에 같이 저장됨)

     

     

    RC (Reference Count)

    : instance 를 참조하고 있는 횟수 계산

     

     

    RC 가 +1 되는 경우

    1. instance 를 새로 생성 시 ⭐️ 

    2. instance 의 address 를 변수에 할당 시

     

     

    RC 가 -1 되는 경우

    1. instance 를 가리키던 변수가 메모리 해제 됐을 시

    2. instance 본인 마저도 nil 로 지정될 시 (RC == 0)
    (instance 가 Heap영역에 메모리 할당되고,  Stack영역에 해당 instance 를 참조하는 지역변수로 메모리 할당 됨)

    3. instance의 property의 경우, 

        해당 property가 다른 클래스 instance를 참조할 때;

     

     

    RC에 대해..

    - 참조 계산 시점

    Compile Time : 컴파일 시점에서 언제 참조/해제 될 지 결정되어, 런타임 때 그대로 실행됨

    - 장점

    개발자가 참조 해제 시점 파악 가능

    Run Time 시, 추가 리소스 발생 X

    - 단점

    1. 순환 참조 발생시, 영구적으로 메모리 해제되지 않을 수 있음

    2. 사용중인 instance 메모리를 해제하여, 해당 instance 의 속성/메서드 호출시 app crush(충돌) 일어날 수 있음

     

     

    * ARC 가 메모리 해제 할 수 없는 참조

    1. strong 참조

    2. strong 참조 사이클이 생성된 경우

     

     

    * ARC 의 메모리 해제를 막을 수 없는 참조

    : weak, unowned

     

     

     

    [RC 계산 방법 & 메모리 해제] 그림으로 이해하기 ❤️

     

    RC 계산 방법 & 메모리 해제

     

     

    그림의 코드에서와 같이, 

    B instance 의 property a 는 해제됐어도, 해당 property (= a) 를 다른 곳에서 참조하고 있었다면

    A instance 의 RC는 -1 일 뿐, 메모리 해제되지 않는다!

     

    위의 경우에서, B instance 의 메모리가 해제되어 A instance 메모리도 해제 된 것이 아니다!! ❌

    예제에서 B instance 에서만 참조 중이었던 것이 전제였으므로, A instance 의 기존 RC값 (=1) 이 -1 되어, RC = 0이 되었으므로,

    메모리가 해제 된 것이다!! 

     

     

    메모리 할당 / 해제 출력

    Class XXX {
       init { print("XXX Init") }
       deinit { print("XXX Deinit") }
    }

     

     

     

     

     

    순환 참조 문제

    : instance 를 확실하게 유지 / strong 참조가 유지되는 한, ARC는 메모리 해제 불가!

     

    ARC 에서 메모리 해제 하지 않는 이유?

    1. RC != 0

    즉, 활성화 되고있는 참조가 있는 경우

    2. strong 참조 사이클 (= 순환 참조 문제  = Circular Reference) 가 생성된 경우

     

     

    순환 참조 문제? (= Circular Reference) 🔴

    : 두 개 이상의 객체가 서로 참조하면서 메모리에서 해제되지 않는 상황

    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { print("\(name) is being deinitialized") }
    }
    
    
    class Apartment {
        let unit: String
        init(unit: String) { self.unit = unit }
        var tenant: Person?
        deinit { print("Apartment \(unit) is being deinitialized") }
    }
    
    // 🔴 1
    var john: Person?
    var unit4A: Apartment?
    
    john = Person(name: "John Appleseed")
    unit4A = Apartment(unit: "4A")
    
    // 🔴 2
    // linking these two instances(john,unit4A) creates a strong reference cycle between them.
    john!.apartment = unit4A
    unit4A!.tenant = john
    
    // 🔴 3
    // The strong reference cycle 는 nil 이 되어도 메모리 할당이 끊어지지 않음 
    john = nil
    unit4A = nil

     

     

     

     

     

     

    순환 참조 해결

     

    순환 참조 해결 방법

    : 클래스 간의 관계 중, 일부를 weak / unowned 참조로 정의

    1. weak 참조

    - 다른 instance 보다 lifetime이 짧은 (= 메모리 할당 해제될 수 있는) 경우 사용

     

    2. unowned 참조 (= non-optional unowned 참조)

    - 다른 instance 와 lifetime이 같거나, 더 긴 경우 사용

    - unowned 참조는, 항상 값을 갖는 것 (= 종속적인 관계 (상대가 있어야 나도 존재)  = non-optional 타입) 를 전제

    - unowned 참조는, 항상 참조가 할당 해제되지 않는 instance를 참조할 때만 사용 

     

    3. unowned optional 참조

    - unowned 참조와 같이, 종속적인 관계 이지만 & 항상 참조를 해야만 하는 것은 아닌 (= 그래서 optional   = 참조안하고 있을때는 nil 일 수도 있다는 말) 

    - 코드 예시)

    class Course의 nextCourse property (🔴 부분)

    class Department {
        var name: String
        var courses: [Course]
        init(name: String) {
            self.name = name
            self.courses = []
        }
    }
    
    
    class Course {
        var name: String
        unowned var department: Department
        unowned var nextCourse: Course? // 🔴
        init(name: String, in department: Department) {
            self.name = name
            self.department = department
            self.nextCourse = nil
        }
    }
    
    let department = Department(name: "Horticulture")
    
    
    let intro = Course(name: "Survey of Plants", in: department)
    let intermediate = Course(name: "Growing Common Herbs", in: department)
    let advanced = Course(name: "Caring for Tropical Plants", in: department)
    
    
    intro.nextCourse = intermediate
    intermediate.nextCourse = advanced
    department.courses = [intro, intermediate, advanced]

     

     

     

     

     

    직접 구현해보기

    ARC와 순환 참조 해결

    1. 순환 참조 문제 구현

    • 두 클래스를 생성하세요:
      • Person 클래스: 이름을 저장하는 속성(name)과 애완동물(pet) 속성을 가짐.
      • Pet 클래스: 주인(owner) 속성을 가짐.
    • 서로를 참조하는 인스턴스를 생성하고 순환 참조 문제를 확인하세요.
    class Person {
        var name: String
        var pet: Pet?
        
        init(name: String) {
            self.name = name
        }
        deinit { print("\(name) deinitialized") }
    }
    
    class Pet {
        var owner: Person?
        deinit { print("Owner \(owner) deinitialized") }
    }
    
    var mimi: Person?
    var toto: Pet?
    
    mimi = Person(name: "mimi")
    toto = Pet()
    
    mimi!.pet = toto
    toto!.owner = mimi
    
    mimi = nil 
    toto = nil

     

    [실행결과] 
    // 아무것도 안찍힘. deinit 실행되지 않음 >>>>> 메모리 해제되지 않음!! 🔴

     

     

     

     

     

    2. 순환 참조 해결

    • weak 키워드를 사용해 순환 참조 문제를 해결하세요.
    • 해결 후, 두 인스턴스가 정상적으로 메모리에서 해제되는지 확인하세요.
    class Person {
        var name: String
        var pet: Pet?
        
        init(name: String) {
            self.name = name
        }
        deinit { print("\(name) deinitialized") } // 🔴 실행됨
    }
    
    class Pet {
        weak var owner: Person?
        deinit { print("Owner \(owner) deinitialized") } // 🔴 실행됨
    }
    
    var mimi: Person?
    var toto: Pet?
    
    mimi = Person(name: "mimi")
    toto = Pet()
    
    mimi!.pet = toto
    toto!.owner = mimi
    
    mimi = nil 
    toto = nil
    [실행 결과]
    mimi deinitialized
    Owner nil deinitialized

     

     

     

     

     

     

    회고

    이전에도 ARC 개념을 공부했었지만, 정해주신 학습 순서대로 공부하니 이번엔 제대로 이해가 됐다. 

    내가 이전에 ARC 가 알긴알겠는데 확실하게 모르겠었던 이유는, 메모리 구조 때문이었다.

    Swift 에서 많이 쓰이는 Heap, Stack 개념을 확실하게 이해한 후 ARC 를 이해하니 완전 이해된다. 야호~💝

     

     

     

     

     

     

    출처

    https://babbab2.tistory.com/26

     

    iOS) 메모리 관리 (1/3) - ARC(Automatic Reference Counting)

    안녕하세요~~ 소들입니다 👀 오늘은 지난 시간 메모리 구조에 이어 Swift를 사용할 때 메모리 관리가 어떤 식으로 되는지에 대해 공부해볼 거예요 :) ARC 면접 단골 질문이라죠? 깔깔 iOS 개발자라

    babbab2.tistory.com

     

    https://hortenssiaa.tistory.com/37

     

    (Swift) ARC (Automatic Reference Counting, 자동 참조 계산)

    @escaping 을 공부하다가 여기 까지 오게 되었음 ㅠㅠ ㅎ ARC (Automatic Reference Counting, 자동 참조 계산)앱의 메모리 사용량을 추적, 관리class instance 가 더 이상 필요하지 않을 때, 해당 instance 에서

    hortenssiaa.tistory.com

    https://hortenssiaa.tistory.com/38  🔴🔴🔴

     

    (Swift) 메모리 참조 속성 (strong, week, unowned, optional unowned)

    Apple Documentation 을 보고 정리하였음 1. strong 참조default reference type 이다.  var / let 앞에 week, unowned 가 붙지 않으면 strong reference type 임!!instance 를 확실히 유지 & strong 참조가 유지되는 한, 할당 해

    hortenssiaa.tistory.com

     

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/

     

    Documentation

     

    docs.swift.org

     

     

     

     


     

    클로저 🩷

    클로저란?

    • 코드에서 독립적으로 전달/사용 할 수 있는 기능을 가진 코드블록
      • 비동기 처리 / UI 업데이트에서 자주 사용
    • 변수/상수에 저장 가능
    • 함수 인자로 전달 가능
    • 이름있는 클로저 (= 함수)일 경우, 함수처럼 () 를 붙여 사용
    • 이름없는 클로저 (= 익명함수)

     

    클로저 문법

    { (매개변수) -> 반환타입 in
        실행 코드
    }
    
    
    // 매개변수 O
    { (name: String) -> String in
        print("Hello, \(name)!")
    }
    
    // 매개변수 X
    { () -> Int in
        print(30)
    }

     

     

     

    캡쳐 (Capture)

    클로저는 외부 변수/상수의 값을 "캡쳐" 하여 저장한다. 

    var number = 10
    let closure = { number += 5 }
    closure()
    print(number) // 15

     

     

     

    클로저 예제

    // 기본 클로저
    let greet = { (name: String) -> String in 
        return "Hello, \(name)!")
    }
    print(greet("Alice") // "Hello, Alice!"
    
    // 🔴함수의 인자로 클로저 전달
    func performAction(action: () -> Void) {
        action() // 전달받은 클로저 실행
    }
    
    performAction {
        print("Action Performed")
    }

     

     

     

    직접 구현해보기

    1. 간단한 클로저 구현:

    숫자 배열 [1, 2, 3, 4, 5]를 생성하고, 클로저를 사용해 배열의 모든 값을 2배로 만든 결과를 출력하세요.

    let numbers = [1, 2, 3, 4, 5]
    let doubled = numbers.map { $0 * 2 }
    print(doubled)

     

     

     

    2. 클로저 캡처 이해:

    아래 코드를 완성하여 클로저가 외부 변수 값을 캡처하는 동작을 확인하세요.

    var counter = 0
    let incrementCounter = {
        counter += 1
    }
    incrementCounter() // 1
    incrementCounter() // 2
    print(counter) // 출력: 2

     

     

     

     


     

    객체지향 프로그래밍 (OOP)

     

    객체지향 프로그래밍 (OOP) ?

    객체 (Object) 기반인 프로그램 설계 방식

     

     

     

    주요 원칙

    1. 캡슐화 (Encapsulation) : 데이터를 숨기고, 외부에는 필요한 인터페이스만 제공

    2. 상속 (Inheritance) : 기존 클래스를 확장하여 새로운 클래스 생성

    3. 다형성 (Polymorphism) : 같은 메서드를 다양한 방식으로 동작하게 함

    4. 추상화 (Abstraction) : 불필요한 세부 사항을 숨기고, 중요한 부분만 표현

     

     

     

    클래스와 객체

    클래스 : 객체를 정의하기 위한 청사진

    객체 : 클래스에서 생성된 실제 instance

     

     

     

    OOP 기본 예제

    class Animal {
        var name: String
    
        init(name: String) {
            self.name = name
        }
    
        func makeSound() {
            print("Some generic sound")
        }
    }
    
    class Dog: Animal {
        override func makeSound() {
            print("Bark!")
        }
    }
    
    let dog = Dog(name: "Buddy")
    dog.makeSound() // "Bark!"

     

     

     

     

    직접 구현해보기

    1. 동물 클래스 설계:

    다음 요구 사항에 맞는 클래스를 구현하세요:

    Animal 클래스: name 속성과 makeSound() 메서드를 포함.

    Dog 클래스: Animal을 상속받고 makeSound() 메서드를 오버라이드하여 "Bark!" 출력.

    Cat 클래스: Animal을 상속받고 makeSound() 메서드를 오버라이드하여 "Meow!" 출력.

    class Animal {
        var name: String
        
        init(name: String) {
            self.name = name
        }
        
        func makeSound() { }
    }
    
    class Dog: Animal {
        override func makeSound() {
            print("Bark!")
        }
    }
    
    class Cat: Animal {
        override func makeSound() {
            print("Meow!")
        }
    }
    
    
    var toto = Dog(name: "toto")
    var nani = Cat(name: "nani")
    
    toto.makeSound() // Bark!
    nani.makeSound() // Meow!

     

     

     

     

    2. 다형성 확인:

    Animal 타입 배열에 Dog와 Cat 객체를 저장하고, 배열의 각 객체에서 makeSound() 메서드를 호출하세요.

    예상 출력:

    Bark!
    Meow!

     

    코드

    var animalArr: [Animal] = [toto, nani]
    var makeSound = animalArr.map { $0.makeSound() }
    // Bark!
    // Meow!

     

     

     

     

     

     

    댓글

Designed by black7375.