iOS 개발 기록

Swift 의 메모리 관리 (ARC, weak, unowned, lazy) 본문

Swift

Swift 의 메모리 관리 (ARC, weak, unowned, lazy)

택꽁이 2022. 5. 31. 18:37
728x90

Swift의 메모리 관리 

  • ARC(Automatic Reference Counting) : ARC가 관리해주는 Reference Counting (참조 횟수 계산) 은 참조 타입인 클래스의 인스턴스에만 적용된다. 구조체나 열거형은 값 타입으로 다른 곳에서 참조하지 않기 때문에 ARC로 관리할 필요가 없다.

 

 

ARC의 로직

  • 생성 : 런타임 때 동적할당 되는 모든 Object를 Swift Runtime이라는 라이브러리의 HeapObject라는 struct로 표현되어 관리된다. 여기에는 Reference Count와 type meta data를 포함한다. 
  • 해제 : ARC 는 인스턴스가 참조하는 다른 프로퍼티의 변수, 상수의 갯수를 세고 1개 이상 존재하면 인스턴스를 메모리에서 없애지 않는다. 
  • 카운팅 시점 : 컴파일시 카운팅. default는 강한 참조 형태로 카운팅하게 된다. 그러나 복합적으로 강한 참조가 나타나는 상황이면 강한참조 순환 문제가 나타날 수 있음. 
// 강한 참조 순환 문제 예
class Hop { 
    let hopName: String 
    var beerName: Beer?

    init(hopName: String) {
        self.hopName = hopName
    }
    deinit {
        print("Hop: deinit")
    }
}

class Beer { 
    let beerName: String
    var hopName: Hop?
    
    init(beerName: String){
        self.beerName = beerName
    }
    deinit {
        print("beer: deinit")
    }
}


var hop: Hop? = Hop(hopName: "citra") 		// Hop reference count : 1 
var beer: Beer? = Beer(beerName: "PseudoSue")	// Beer reference count : 1
hop?.beerName = beer	// Hop reference count  : 2
beer?.hopName = hop		// Beer reference count : 2

hop = nil 		// Hop reference count : 1
beer = nil	// Beer reference count : 1

// 즉, 메모리에 reference count가 남아있는 상태로 deinit이 출력되지 않는다.

강한 참조 순환 문제를 막기 위해 weak와 unowned을 사용한다. 

 

 

 

 

 

weak, unowned

약한 참조 (weak) 

  • 변수 앞에 weak 키워드를 작성해 사용. 항상 옵셔널 변수에만 사용할 수 있고 상수에는 사용할 수 없다.
  • weak는 참조하는 인스턴스의 reference count를 추가하지 않는다. 
  • ARC는 인스턴스가 메모리에서 해제될 때 약한 참조하는 프로퍼티의 reference count를 nil로 초기화한다.
  • 때문에 참조되고 있는 동안에도 해당 인스턴스가 할당해제 될 수 있다.
class Hop { 
    let hopName: String 
    var beerName: Beer?

    init(hopName: String) {
        self.hopName = hopName
    }
    deinit {
        print("Hop: deinit")
    }
}

class Beer { 
    let beerName: String
    weak var hopName: Hop?		// weak변수로 선언

    init(beerName: String){
        self.beerName = beerName
    }
    deinit {
        print("beer: deinit")
    }
}

var hop: Hop? = Hop(hopName: "citra") 		
var beer: Beer? = Beer(beerName: "PseudoSue")	
hop?.beerName = beer	
beer?.hopName = hop		

hop = nil 		
beer = nil

// Hop: deinit 출력
// beer: deinit 출력

 

 

 

 

미소유 참조 (unowned)

 

  • 항상 값을 가지고 있어야 한다고 가정한다. 때문에 옵셔널이 아니며 ARC도 무소유 참조로 선언된 값을 nil로 만들지 않는다
  • 다른 인스턴스와 생명주기가 같거나 더 긴 경우에 사용.
  • 할당 해제되지 않은 인스턴스를 참조한다고 확신하는 경우에만 무소유 참조를 사용(할당 해제된 값에 접근하려고 하면 런타임 오류 발생)

 

class Hop { 
    let hopName: String 
    var beerName: Beer?
    
    init(hopName: String) {
        self.hopName = hopName
    }
    deinit {
        print("Hop: deinit")
    }
}

class Beer { 
    let beerName: String
    unowned var hopName: Hop
    
    init(beerName: String, hopName: Hop){
        self.beerName = beerName
        self.hopName = hopName
    }
    deinit {
        print("beer: deinit")
    }
}

var hop: Hop = Hop(hopName: "citra")         
var beer: Beer? = Beer(beerName: "PseudoSue", hopName: hop) 
beer?.hopName = hop        

beer = nil

// beer: deinit 출력

 

 

 

lazy 

정의 :

지연 저장 프로퍼티. lazy 프로퍼티는 처음 사용되기 전까지 연산되지 않는다.  대표적인 예로 인스타 스토리 등을 떠올려보라. 

 

 

사용

  • lazy 프로퍼티는 항상 결과를 저장한다. 연산 프로퍼티는 매번 계산하므로 반복적으로 사용할 때 lazy 프로퍼티가 유리하다.
  • let으로 사용할 수 없다. 반드시 var와 사용되어야 한다.
  • 기본적으로 struct와 class에서만 사용할 수 있다.
  • lazy에 값을 넣어주려면 클로저를 사용해야 한다. class나 struct의 다른 프로퍼티의 값을 넣기 위해서는 self를 통해 접근 가능하다. 

 

class Beer {
    var hop: String 
    lazy var likeHop: String = { 
        return "I like \(self.hop)"
    }()
    
    init(hop: String) {
        self.hop = hop
    }
}

var beer = Beer(hop:"galaxy")
print(beer.likeHop)        		// I like galaxy

beer.hop = "nelson sauvin"
print(beer.likeHop)			// I like galaxy

 

 다음의 경우 likeHop이 클로저를 실행하고 ()로 결과를 바로 돌려주며 끝내버리기 때문에 메모리는 실행 결과를 담게 된다. 때문에 beer.hop을 변경해도 처음 likeHop의 메모리에 "galaxy"가 올라갔기 때문에 그대로 "galaxy"가 출력된다. 

 

때문의 lazy var likeHop: String을 lazy var likeHop: () -> String 으로 선언해 실행결과가 아니라 클로저 자체를 담을 수 있는 변수로 선언해야 한다. 또한 클로저 자체를 담고 있는 변수라면 반드시 [weak self] 로 메모리 누수를 방지해야 한다. 

 

 

 

class Beer {
    var hop: String 
    lazy var likeHop: () -> String = { [weak self] in
        return "I like \((self?.hop)!)"
    }
    
    init(hop: String) {
        self.hop = hop
    }
}

var beer = Beer(hop:"galaxy")
print(beer.likeHop())        // I like galaxy

beer.hop = "nelson sauvin"
print(beer.likeHop())	// I like nelson sauvin

 

 

 

 

 

 

 

참고 : https://baked-corn.tistory.com/45

 

[Swift] lazy Variables

Lazy variables 이전의 글들을 보셨거나 스위프트 문법 공부를 해보신 분들이라면 스위프트에서 메모리는 굉장히 예민한 주제인 것을 알 수 있습니다. 저 역시 그렇게 느꼈고, 그런 예민함이 보다

baked-corn.tistory.com

 

'Swift' 카테고리의 다른 글

Swift - 고차함수 (map, filter, reduce)  (0) 2022.07.18
[iOS] PencilKit  (0) 2022.07.07
Swift의 특징과 프로그래밍 패러다임  (0) 2022.05.31
KVC와 KVO란?  (0) 2022.05.31
[Swift]Combine - Publisher, Subscriber, NotificationCenter  (0) 2022.04.18