klioop for iOS

ARC, weak, and weak self 본문

swift

ARC, weak, and weak self

klioop2@gmail.com 2021. 3. 17. 21:35

ARC 는 Automatic Reference Counting 의 약자이다. Swift 에서는 직접 메모리 관리를 하지 않고 ARC 가 메모리를 알아서 관리한다.

 

어떤 클래스의 instance 가 만들어질때 마다 ARC 는 메모리의 특정 영역을 할당해서 해당 instance 의 정보를 저장한다. 구체적으로 instance 의 type 과 함께 관련된 properties 의 값들을 저장한다.

 

instance 의 주소를 가리키는 변수가 nil 값을 부여받는 등 instance 가 필요없어지면 ARC 는 할당한 메모리를 회수한다. 

그런데 instance 가 아직 필요한데 ARC 가 해당 instance 에 할당한 메모리를 회수하면 안되므로 ARC 는 항상 어떤 properties, 변수 또는 상수가 해당 instance 를 참조하고 있는지 추적한다. 

 

하나의 참조라도 활성되어있으면 ARC 는 instance 에 할당한 메모리를 회수하지 않는다. 

이렇게 변수나 상수 등으로 특정 instance 를 참조 하는 것을 strong reference 라고 부른다.

instance 가 회수되지 않도록 꽉 잡고 있기 때문에 strong 이 붙는다고 한다.

 

이런 특성 때문에 swift 에서는 클래스 instance 사이에서 strong reference cycle 이 발생할 수 있다.

instance 끼리 서로 강하게 참조하고 있기 때문에 해당 instance 가 필요없어서 할당된 메모리를 회수해야 하는데도 그렇지 못한 상황을 의미한다. 메모리 누수가 발생하는 것이다.

 

struct 이나 enum 을 다룰 때는 이런 상황이 발생하지 않는다. 모두 value type 이기 때문이다.

따라서 swift 에서는 필요한 경우가 아니면 class 보다 struct 으로 코드를 작성하는 게 안전하다.

 

다음의 예시 하나를 살펴보자.

 

class Blog {
    let name: String
    let url: URL
    var owner: Blogger?

    init(name: String, url: URL) { self.name = name; self.url = url }

    deinit {
        print("Blog \(name) is being deinitialized")
    }
}

class Blogger {
	let name: String
    var blog: Blog?
    
    init(name: String) { self.name = name }
    
	deinit {
    	print("Blogger \(name) is being deinitialized")
   }
}

var blog: Blog? = Blog(name="Amor", url="klioop.tstory.com") // nil 을 받기 위해 optional type 으로 선언
var blogger: Blogger? = Blogger(name="sam")

blog?.owner = blogger
blogger?.blog = blog

blog = nil
blogger = nil

// nothing printed

 

blog 와 blogger 의 deinit 이 호출되지 않는 것을 알 수 있다. 

blog 에 nil 값을 부여했지만 blogger 가 blog 를 강하게 참조하고 있기 때문에 blog 는 사라지지 않는다.

마찬가지로 blogger 에 nil 을 부여해도 blog 가 강하게 참조하고 있기 때문에 blogger 는 사라지지 않는다.

 

blog 는 자신을 잡고있는 blogger 를 놓지 않는다. blogger 도 자신을 잡고있는 blog 를 놓지 않는다. 사이클이다!

 

Swift 에서는 이러한 문제를 해결 하기 위해 weak 개념이 있다.

한 쪽의 instance 가 다른 instance 를 약하게 참조하도록 만드는 것이다. 참조는 하고 있지만 해당 instance 가 사라지는 것을 방해하지 않는다는 의미에서 약하다.

 

실제 코드로는 var 앞에 weak 를 적어주면 된다. blog 가 blogger 를 약하게 참조하도록 만들어보자.

 

class Blog {
    let name: String
    let url: URL
    weak var owner: Blogger?

    init(name: String, url: URL) { self.name = name; self.url = url }

    deinit {
        print("Blog \(name) is being deinitialized")
    }
}

blog?.owner = blogger
blogger?.blog = blog

blog = nil
blogger = nil

// Blogger sam is being deinitialized
// Blog amor is being deinitialized

 

blog 에 nil 을 먼저 부여했지만 blogger 의 deinit 이 먼저 호출되는 것을 확인하자.

blog 는 blogger 를 약하게 참조하지만 blogger 는 blog 를 강하게 참조하기 때문에 blogger 가 사라져야 blog 도 사라질 수 있다.

 

강참조 사이클(Strong Reference Cycle)이 instances 사이뿐 아니라 instance 와 closure 사이에서도 발생할 수 있다.

Swift 에서 closure 도 reference type 이다. 

 

Post 클래스를 새로 정의하고 Blog 클래스에 출판기능을 더해보자.

 

class Post {

	let title: String
    var isPublished: Bool = false
    
    init(title: String) { self.title = title }
}

class Blog {
    let name: String
    let url: URL
    weak var owner: Blogger?
    
    var publishedPosts: [Post] = []
    var onPublish: ((_ post: Post) -> void)?

    init(name: String, url: URL) { 
    	self.name = name 
        self.url = url 
        
    	onPublish = { post in
        
        	self.publishedPosts.append(post)
            print("Published post count is now \(self.publishedPosts.count)")  
    }

    deinit {
        print("Blog \(name) is being deinitialized")
    }
    
    func publish(post: Post) {
    	// 가짜 네트워크 request 
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        	self.onPublish?(post)
            }
     }
}

class Blogger {
	let name: String
    var blog: Blog?
    
    init(name: String) { self.name = name }
    
	deinit {
    	print("Blogger \(name) is being deinitialized")
   }
}

var blog: Blog? = Blog(name="Amor", url="klioop.tstory.com") // nil 을 받기 위해 optional type 으로 선언
var blogger: Blogger? = Blogger(name="sam")

blog?.owner = blogger
blogger?.blog = blog

blog!.publish(Post(title: "weak self 알아보기"))

blog = nil
blogger = nil

// Blogger sam is being deinitialized

 

blog 가 onPublish closure 를 강하게 참조하고 있고 onPublish 도 blog 를 self 를 통해서 강하게 참조 하고 있다.

(정확히는 main.asyncAfter closure 를 강하게 참조하고 main.asyncAfter closure 가 onPublish closure 를 강하게 참조하고 있다. 또한 onPublish closure 는 blog 를 강하게 참조하고 있다) 

여기서도 사이클이 발생하고 있는 것이다.

 

이런 상황을 해결하기 위해 Swift closure 에는 capturing list 라는 개념이 존재한다. closure 영역 인자 앞에서 정의된다.

closure 가 영역바깥의 어떤 것을 참조할 때 약하게 참조하도록 하는 장치이다. 코드로 살펴보면 다음과 같다.

 

class Blog {
    let name: String
    let url: URL
    weak var owner: Blogger?
    
    var publishedPosts: [Post] = []
    var onPublish: ((_ post: Post) -> void)?

    init(name: String, url: URL) { 
    	self.name = name 
        self.url = url 
        
    	onPublish = { [weak self] post in
        
        	self?.publishedPosts.append(post)
            print("Published post count is now \(self?.publishedPosts.count)")  
    }

    deinit {
        print("Blog \(name) is being deinitialized")
    }
    
    func publish(post: Post) {
    	// 가짜 네트워크 request 
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        	self.onPublish?(post)
            }
     }
}

blog?.owner = blogger
blogger?.blog = blog

blog!.publish(Post(title: "weak self 알아보기"))

blog = nil
blogger = nil

// Blogger sam is being deinitialized
// Published post count is now: Optional(1)
// Blog SwiftLee is being deinitialized

 

onPublish closure 영역안, 인자를 써주는 부분에서 post 앞에 [weak self] 를 추가한다. capturing list 라는 이름답게 여러 참조 값에 적용할 수 있다.

e.g. [weak self, weak someProperty ...] 

 

weak self 는 self 의 nil 값을 허용한다는 의미도 되기 때문에 closure 영역 안에서 self? 로 참조해야 한다.

 

DispatchQueue.main.asyncAfter 의 closure 가 self.onPublish 를 호출한다. DispatchQueue.main.asyncAfter closure 는 blog 를 강참조한다.

onPublish closure 가 자신의 역할을 수행한다. 이 때 blog 를 약참조하고 있다.

onPublish 가 일을 끝내면 DispatchQueue.main.asyncAfter closure 가 사라진다.

자신을 강참조 하고 있던 closure 가 사라졌기 때문에 현재 blog 를 참조하는 것은 onPublish 밖에 없다. 

하지만 onPublish closure 의 참조는 약하기 때문에 blog 는 사라질 수 있고 사라진다.

deinit 이 호출되고 해당 statement 가 출력되는 것을 알 수 있다.

 

참조

docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

 

Automatic Reference Counting — The Swift Programming Language (Swift 5.4)

Automatic Reference Counting Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you don’t need to think about memory management your

docs.swift.org

www.avanderlee.com/swift/weak-self/

 

Weak self and unowned self explained in Swift - SwiftLee

When to use weak self? When to use unowned self? How to prevent retain cycles? Everything explained with code examples and detailed explanations.

www.avanderlee.com

www.quora.com/What-does-weak-self-mean-in-a-Swift-Closure

 

What does “weak self” mean in a Swift Closure?

Answer: [code ][weak self][/code] is “syntactic sugar” built into the Swift language and basically means that the closure is getting a weak rather than a strong reference to [code ]self[/code]. So, this code: [code]let myClosure = { [weak self] in self

www.quora.com

www.swiftbysundell.com/articles/swifts-closure-capturing-mechanics/

 

Swift’s closure capturing mechanics | Swift by Sundell

This week, let’s take a look at the various ways that Swift closures can capture the objects and values that they depend on, and how we can control those mechanics.

www.swiftbysundell.com

 

'swift' 카테고리의 다른 글

Static, Dynamic(table), 그리고 Message dispatch  (0) 2021.12.15
[Swift] Optional 이해하기  (0) 2021.06.09
value type vs reference type  (0) 2021.03.17