티스토리 뷰

Swift

Swift [Closure 3부]

이도형 2023. 3. 4. 15:14

지금까지 배운것들을 복습하는 느낌

INDEX

1. 객체내에서 클로저 사용

2.  강한 참조 사이클로 메모리 누수 사례

3. @autoclosure

4.  마무리

 

1. 객체내에서 클로저 사용

 

함수내부에 있는 클로저는

객체의 속성 및 메서드에 접근하기 위해서는 반드시 self키워드를 사용해야 했다.

이유는? why??

실제로 클로저는 오래동안 객체를 사용해야 하고, 힙영역에 저장하고 사용할 객체의 주소를 저장한다.

따라서 클로저와 객체(클래스 인스턴스) 모두 힙영역에 존재하는 건 맞지만

둘다 힙영역에 재각각 따로 존재한다.

(둘의 공간은 분리되어 있다. 아래코드를 예시로들면 sayMyName클로저들은 힙영역에 잇지만 모두 Person외부에 있음) 

따라서 self키워드를 통해 객체를 강하게 참조를 하고 있다는 것을 표시해줘야 한다.(Strong References)

하지만 클로저외부에서 참조타입(Person)의 메모리 주소(참조)를 캡쳐해 

강한참조사이클의 발생 가능성이 있기에,

지난 포스팅에서 강한 참조 순환 해결 방법으로 weak/owned + 캡쳐리스트의 방법이 있음을 정리하였다.

아래 2가지 방법으로 강한 참조 사이클 문제를 해결할 수 있다.

1. self.name

2. [self]  =====> Swift 5.3이후

 

class Person {
    let name = "홍길동"
    
    //1) 1번째 스택에서 일어나는 일반적인 함수
    func myName() {
        print("나의 이름은 \(name)입니다.")
    }
    
    //2) 2번째 쓰레드의 스택에서 동작.클로저가 print로 출력!해야하기에
    // 클래스 인스턴스를 강하게 참조함(strong reference).사이클은 발생X // RC == 2
        func sayMyName1() {
            DispatchQueue.global().async {
                print("나의 이름은 \(self.name)입니다.")
            }
        }
    
    //3) weak참조 + 캡쳐리스트 => 강한 참조 방지, 옵셔널 형태로 출력함 // RC == 1
    func sayMyName2() {
        DispatchQueue.global().async { [weak self] in
            print("나의 이름은 \(self?.name)입니다.")
        }
    }
    
    //4) guard let바인딩으로 weak참조 사용시 값이 있을때만 벗겨 사용하겠다 // RC == 1
    // (실제로 이 코드가 제일 많이 사용된다. why? 값이 없다면 출력할 이유도 없기때문)
    func sayMyName3() {
        DispatchQueue.global().async { [weak self] in // weak self였다는 걸 암시하기위해 weakSelf
            guard let weakSelf = self else { return }   // 가드문 처리 ==> 객체없으면 일종료
            print("나의 이름은 \(weakSelf.name)입니다.(가드문)")
        }
    }
}

let person = Person()

person.MyName() //나의 이름은 홍길동입니다.
person.sayMyName1() //나의 이름은 홍길동입니다.
person.sayMyName2() //나의 이름은 Optional("홍길동")입니다.
person.sayMyName3() //나의 이름은 홍길동입니다.(가드문)

 

 

strong 참조할때

 

 

weak 참조를 할때

 

 

2.  강한 참조 사이클로 메모리 누수 사례

 

인스턴스가 생성되고(RC == 1) 사라져(RC ==1) 소멸자 호출되는 형태

class Dog {
    var name = "초코"
    
    var run: (() -> Void)?
    
    func walk() {
        print("\(self.name)가 걷는다.")
    }
    
    func saveClosure() {
        // 클로저를 인스턴스의 변수에 저장
        run = { print("\(self.name)가 뛴다.") }
    }
    
    deinit {
        print("\(self.name) 메모리 해제")
    }
}

func doSomething() {
    let choco: Dog? = Dog()
}

doSomething() //초코 메모리 해제

 

인스턴스가 생성되고(RC == 1) 클로저와 클래스 인스턴스간 강한 참조 싸이클이 발생하는 현상

class Dog {
    var name = "초코"
    
    var run: (() -> Void)?
    
    func walk() {
        print("\(self.name)가 걷는다.")
    }
    
    func saveClosure() {
        // 클로저를 인스턴스의 변수에 저장
        run = { print("\(self.name)가 뛴다.") }
    }
    
    deinit {
        print("\(self.name) 메모리 해제")
    }
}

func doSomething() {
    let choco: Dog? = Dog()
    choco?.saveClosure()       // 강한 참조사이클 일어남 (메모리 누수가 일어남)
}

doSomething() //소멸자 호출X

 

이해를 위하여 choco를 var로 선언후 nil대입

class Dog {
    var name = "초코"
    
    var run: (() -> Void)?
    
    func walk() {
        print("\(self.name)가 걷는다.")
    }
    
    func saveClosure() {
        // 클로저를 인스턴스의 변수에 저장
        run = { print("\(self.name)가 뛴다.") }
    }
    
    deinit {
        print("\(self.name) 메모리 해제")
    }
}

func doSomething() {
    var choco: Dog? = Dog()
    choco?.saveClosure()       // 강한 참조사이클 일어남 (메모리 누수가 일어남)
    choco = nil
}



doSomething() //소멸자 호출 X

 

문제 코드

    func saveClosure() {
        // 클로저를 인스턴스의 변수에 저장
        run = { print("\(self.name)가 뛴다.") }
    }

 

 

해결책: 캡쳐리스트 + weak/owned 참조

class Dog {
    var name = "초코"
    
    var run: (() -> Void)?
    
    func walk() {
        print("\(self.name)가 걷는다.")
    }
    
    func saveClosure() {
        // 클로저를 인스턴스의 변수에 저장
        run = { [weak self] in
            print("\(self?.name)가 뛴다.")
        }
    }
    
    deinit {
        print("\(self.name) 메모리 해제")
    }
}

func doSomething() {
    let choco: Dog? = Dog()
    choco?.saveClosure()      
}

doSomething() //초코 메모리 해제 (정상적으로 소멸)

 

3. @autoclosure

함수의 파라미터 중 클로저 타입에 @autoclosure 키워드를 붙이는 이유는

파라미터로 전달된 일반 구문 & 함수를 클로저로 래핑(Wrapping) 하기 위해서다

 일반적으로 클로저 형태로 써도되지만, 너무 번거로울때 사용하며,

실제 코드가 명확해 보이지 않을 수 있으므로 사용은 지양(🍎 공식 문서)

읽기위한 문법(UIKit에서 종종 등장) Escaping이 더 중요하다

주의점 - 파라미터가 없는 클로저만 가능

// 클로저 앞에 @autoclosure 키워드 사용(파라미터가 없는 클로저만 가능)

func someFuction(closure: @autoclosure () -> Bool) {
    if closure() {
        print("참입니다.")
    } else {
        print("거짓입니다.")
    }
}

var num = 1

// 실제로 함수를 사용하려고 하면

//someFuction(closure: Bool)
someFuction(closure: num == 1) //참입니다.

// autoclosure는 기본적으로 non-ecaping 특성을 가지고 있음

func someAutoClosure(closure: @autoclosure @escaping () -> String) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        print("소개합니다: \(closure())")
    }
}


someAutoClosure(closure: "제니") //3초후 ... => 소개합니다: 제니

 

 

4.  마무리

이번 포스팅에서는 지금까지 배운 클로저와 관련된 개념들(reference counting, 캡쳐리스트, weak,escaping...)이

어떻게 쓰이는지 메모리 누수 사례를 통해 묶어 정리하였다.

이부분을 공부할때에는그림을 그려가며 객체간의 reference counting이 얼마나 올라가는지

알아보는 것도 나름 도움이 되었다.

완전한 이해를 위해서는 더많은 공부와 실제로 앱을 만들면서 수없이 부딪혀보는 수밖에 없다고 생각했다.

지금은 텍스트만으로 이해는 되지만  와닿지는 않는 개념들도 앱을 만들어보고

클로저를 다루다보면 어느새 이해하는 순간이 올거라 믿는다. 

클로저 끝~~

 

반응형

'Swift' 카테고리의 다른 글

Swift [Generic]  (0) 2023.03.08
Swift [Error Handling(throw, try, do-catch, rethrows)]  (0) 2023.03.07
Swift [Closure 2부]  (0) 2023.03.04
Swift [ARC- strong, memory leak, weak, unowned]  (0) 2023.03.02
Swift [Closure 1부]  (0) 2023.03.01
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함