[iOS] Strong, Weak, Unowned, Circular Reference

r1verfuture·2022년 4월 13일
0

iOS

목록 보기
13/30

Strong

  • 강한 참조
  • 인스턴스의 주소값이 변수에 할당될 때 RC가 증가하는 경우
  • 평소에 내가 인스턴스 생성하고 사용하던 것이 다 강한 참조를 한 것이다. (기본값이 strong이기 때문이다.)
// MARK: [코드 1] Developer 클래스 생성
class Developer {
	var name: String?
    var language: String?
    
    init(name: String?, language: String?) {
    	self.name = name
        self.language = language
    }
}
// MARK: [코드 2] Developer 클래스의 인스턴스 생성
let r1verfuture = Developer(name: "r1verfuture", language: "Swift")
  • [코드 1], [코드 2] 를 실행하면 강한 참조를 한 것이다.

Circular Reference

  • 순환 참조
  • ARC 의 단점
  • 영구적으로 메모리가 해제되지 않을 수 있다.
// MARK: [코드 3] Man 클래스 & Woman 클래스 생성
class Man {
    var name: String
    var girlfriend: Woman?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("Man Deinit") }
}
 
class Woman {
    var name: String
    var boyfriend: Man?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("Woman Deinit") }
}
  • [코드 3] 에는 Man 클래스에는 Woman 타입의 프로퍼티가 있고, Woman 클래스에는 Man 타입의 프로퍼티가 있다.
// MARK: [코드 4] whatso & r1verfuture 인스턴스 생성
var whatso: Man? = .init(name: "왓소")
var r1verfuture: Woman? = .init(name: "밀애")
  • [코드 3], [코드 4] 를 실행하면 [코드 4] 의 첫번째 줄에서 Man 인스턴스의 RC값이 1이 된다. (강한 참조)
  • [코드 4] 의 두번째 줄에서 Woman 인스턴스의 RC값이 1이 된다. (강한 참조)
// MARK: [코드 5] whatso & r1verfuture 각각의 프로퍼티 설정
whatso?.girlfriend = r1verfuture
r1verfuture?.boyfriend = whatso
  • [코드 3], [코드 4], [코드 5] 를 실행하면 [코드 4] 에 의해 Man 인스턴스와 Woman 인스턴스의 RC값이 각각 1씩 증가된다. (↑ 바로 위 설명 참고)
  • [코드 5] 의 첫번째 줄에서 Woman 인스턴스의 RC값이 1 증가된다. (즉, Woman 인스턴스의 RC값이 2가 된다.)
  • [코드 5] 의 두번째 줄에서 Man 인스턴스의 RC값이 1 증가된다. (즉, Man 인스턴스의 RC값이 2가 된다.)
  • 이렇게 2개의 객체가 서로를 참조하고 있는 형태순환 참조라고 하며, 순환 참조 중에서 강한 참조로 인해 순환 참조가 발생한 경우를 강한 순환 참조라고 한다.
  • weak 와 unowned 사용하면 강한 순환 참조를 해결할 수 있다.
// MARK: [코드 6] whatso & r1verfuture 각각에 nil 부여
whatso = nil
r1verfuture = nil
  • [코드 3], [코드 4], [코드 5], [코드 6] 을 실행하면 인스턴스를 가리키던 변수에 nil을 대입했으니까 RC값이 1 감소하고, 감소한 결과로 RC값이 0이 되면 메모리에서 해제될 것 같지만 그렇지 않다. (실행해보면 deinit 함수가 호출되지 않는 것을 볼 수 있다.)
  • whatso, r1verfuture 가 가리키던 인스턴스가 힙에서 사라지지 않고 계속 메모리를 먹고 있는 것이다. (Man과 Woman 인스턴스의 RC값은 각각 1로 남아있다.)
  • 심지어 [코드 6] 처럼 nil을 부여해버리면 Man과 Woman 인스턴스에 접근할 방법도 없어서 메모리 해제도 못하고, 앱이 죽기 전까지 기다려야 한다. (메모리 누수가 생기는 것이다.)

Weak

  • 약한 참조
  • 인스턴스를 참조할 때 RC값을 증가시키지 않는다.
  • 참조하던 인스턴스가 메모리에서 해제된 경우, 자동으로 nil이 할당되어 메모리가 해제된다.
  • 프로퍼티를 선언한 이후, 나중에 nil이 할당되는 것이기 때문에 weak는 무조건 옵서널 타입의 변수여야 한다.
  • 강한 순환 참조는 2개의 객체가 서로를 참조하고 있는 형태라고 했는데, 이 2개의 객체 중 하나를 weak로 선언하면 순환 참조에서 벗어날 수 있다. (2개의 객체가 수명이 같은 경우에는 아무 쪽에나 weak를 붙여줘도 상관없지만, 수명이 다른 경우에는 둘 중에 수명이 더 짧은 인스턴스를 가리키는 애를 약한 참조로 선언해야 한다.)
  • 순환 참조를 일으키는 프로퍼티 앞에 weak를 붙여주면 된다.
// MARK: [코드 7] Man 클래스 & Woman 클래스 weak 추가해서 다시 생성
class Man {
    var name: String
    weak var girlfriend: Woman?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("Man Deinit") }
}
 
class Woman {
    var name: String
    var boyfriend: Man?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("Woman Deinit") }
}
  • [코드 4], [코드 5], [코드 6], [코드 7] 을 실행하면 [코드 3] 에서 발생할 수 있던 순환 참조를 해결할 수 있다.
  • [코드 4] 에서 Man과 Woman 인스턴스의 RC값이 각각 1 이 된다.
  • [코드 5] 의 첫번째 줄에서 'girlfriend' 프로퍼티가 weak이기 때문에 Woman 인스턴스의 주소값을 할당 (참조) 받기는 하지만 Woman 인스턴스의 RC값은 증가하지 않는다. (Woman 을 참조했는데도 불구하고 Woman 의 RC값은 그대로 1 이다.)
  • [코드 5] 의 두번째 줄에서 Man 인스턴스의 RC값이 2 로 증가한다. (weak 아니기 때문이다.)
  • [코드 6] 의 첫번째 줄에서 whatso 에 nil 이 부여되었기 때문에 2 였던 Man 인스턴스의 RC값이 1 줄어들어 1 이 된다. (whatso 가 사라지면서 girlfriend 프로퍼티의 RC값도 덩달아 1 줄어들어 0 이 되고, girlfriend 프로퍼티가 메모리에서 해제된다.)
  • [코드 6] 의 두번째 줄에서 r1verfuture 에 nil 이 부여되었기 때문에 1 이었던 Woman 인스턴스의 RC값이 1 줄어들어 0 이 되고, Woman 인스턴스가 메모리에서 해제된다. (r1verfuture 가 사라지면서 1 이었던 boyfriend 프로퍼티의 RC값도 덩달아 1 줄어들어 0 이 되고, 메모리에서 해제된다.)
  • 이대로 실행하면 deinit 함수도 정상 작동한다.
  • 이렇게 weak 를 써서 강한 순환 참조를 해결하는 것을 약한 순환 참조라고 한다.

Unowned

  • 미소유 참조
  • 강한 순환 참조를 해결할 수 있고, RC값을 증가시키지 않는다는 점에서 weak 와 같지만, 인스턴스를 참조하는 도중에 해당 인스턴스가 메모리에서 사라질 일이 없다고 확신하는 점이 weak 와 다르다.
  • 참조하던 인스턴스가 메모리에서 해제된 경우 nil 을 할당받지 못하고 해제된 메모리 주소값을 계속 들고 있는다.
  • unowned 로 선언된 변수가 가리키던 인스턴스가 메모리에서 먼저 해제된 경우, 접근하려고 하면 에러가 발생한다.
  • 가리키던 메모리가 해제되어도 nil 을 할당받지 않아서 Non-Optional Type 으로 선언해야 됐지만 Swift 5.0 부터는 옵셔널 타입으로 선언 가능하도록 바뀌었다.
// MARK: [코드 8] Man 클래스 & Woman 클래스 unowned 추가해서 다시 생성
class Man {
    var name: String
    unowned var girlfriend: Woman?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("Man Deinit") }
}
 
class Woman {
    var name: String
    var boyfriend: Man?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("Woman Deinit") }
}
  • [코드 8] 에서 'unowned' 로 선언된 whatso 의 girlfriend 프로퍼티가 가리키는 r1verfuture 인스턴스는 whatso 인스턴스가 메모리에서 해제되기 전까지는 절대 먼저 해제되어서는 안된다.
// MARK: [코드 9] r1verfuture 에 nil 부여
r1verfuture =  nil
  • [코드 7], [코드 9] 를 실행하면 Man 의 girlfriend 프로퍼티를 weak 로 해줬기 때문에 자동으로 girlfriend 값이 nil 로 지정된다.
  • 하지만, [코드 8], [코드 9] 를 실행하면 girlfriend 값이 nil 로 지정되지 않기 때문에 이미 해제되었는데도 불구하고 메모리 주소값을 계속 들고 있는다.
// MARK: [코드 10] whatso 의 girlfriend 프로퍼티에 접근
whatso?.girlfriend
  • [코드 8], [코드 9], [코드 10] 을 실행하면 이미 메모리에서 해제된 포인터 값에 접근하려고 하는 것이기 때문에 에러가 발생한다.

그렇다면 Weak 와 Unowned 중 어떤 것을 쓰는게 더 좋을까 ?

  • unowned 는 에러를 발생시킬 위험이 있기 때문에 weak 를 사용하는 것이 더 좋다.
  • 하지만 unowned 을 굳이 써야겠다면 weak 와 반대로 2개의 객체 중 수명이 더 긴 인스턴스를 가리키는 애를 미소유 참조로 선언하면 된다. (수명이 더 긴 인스턴스가 누구일지 판단 불가능할 때 unowned 를 쓰면 에러나기 때문에 웬만하면 쓰지 않는 것이 좋다.)

참고

profile
#iOS #Swift #Developer #Python

0개의 댓글

관련 채용 정보