iyOmSd/Title: Swift

[Swift] ARC(Automatic Reference Counting) 스위프트 메모리 관리

냄수 2019. 8. 12. 02:00
반응형

이글은 야곰님의 '스위프트 프로그래밍'책을 공부하면서 작성한 글입니다.^^

 

 

스위프트를 공부하다보면 변수명앞에

weak 이나 가끔 strong이 붙어 있는것을 볼 수 있어요

 

스위프트는 자동으로 메모리를 관리해주기 때문에 프로그래머가 메모리관리에 신경을 덜 쓸 수 있어서 편리해요

이 방식이 ARC 이에요

참조 횟수 계산은 참조타입인 클래스의 인스턴스에만 적용이 되며, 값타입인 열거형이나 구조체에는 적용되지않아요

 

자바에도 이러한 비슷한 기능이 있는데 가비지 컬렉션(Garbage Collection)이라고 많이 들어보셨을꺼에요

서로 메모리는 관리하기 위해 존재하죠

우선 둘의 차이를 볼게요

 

 

ARC

가비지 컬렉션

작동 시점

컴파일시

프로그램 동작 중

컴파일당시에 인스턴스의 해제시점이 정해져있어서 언제 메모리에서 해제되는지 예측가능

규칙에 신경 쓸 필요 없음

복잡한 상황에서도 인스턴스 해제할수 있는 가능성 높음

규칙을 모르고 사용시 인스턴스가 메모리에서 영원히 해제되지 않음

메모리 감시를위한 추가 자원 필요에 따른 성능저하 가능성

언제 메모리에서 해제될지 예측하기 어려움

인스턴스가 지속해서 필요한 상황에서 ARC는 인스턴스가 메모리에서 해제되지 않도록 인스턴스 참조여부를 확인해요

만약 다른 인스턴스에서 참조한다면 해당 인스턴스를 해제하지 않고 유지해야 하는 명분이 되죠

인스턴스를 메모리에 유지시켜려면 이러한 명분을 제공해야되요

 

이렇게 계속 남아있어야하는 명분을 만들어 주는 게 강한참조 라고 하는 거에요

인스턴스는 참조횟수가 0이 되는순간 메모리에서 해제되는데

강한참조를 사용하면 참조횟수가 1증가해요

그리고 강한참조를 사용하는 곳에 nil을 할당해주면 참조횟수가 1감소해요

변수를 선언할 때 별도의 식별자가 없다면 기본적으로 강한참조를 해요

지금까지 생각하지않고 그냥 선언했던 변수, 프로퍼티들은 모두 강한참조에요

//Limit이란 클래스가 있다는 가정
var ref: Limit?
func neverEnd(){
    let a = Limit()    //참조횟수: 1
    ref = a    //참조횟수 2
    
    //함수종료
    //참조횟수 1
}

이런식으로 neverEnd()함수를 실행시키면 함수가 종료되도 참조횟수가 1이기때문에 메모리에서 해제되지 않아요

 

하나더 예시를 볼게요

이번에는 서로가 서로를 참조하는 경우에요

강한참조 순환 문제

class RoomType {
    var special: Bool?
    var room: Room?
}
class Room {
    var number: Int?
    var roomType: RoomType?
}

var room: Room? = Room()    //Room 참조횟수1
var type: RoomType? = RoomType()    //RoomType 참조횟수1
room?.roomType = type    //RoomType 참조횟수2
type?.room = room    //Room 참조횟수2
type?.room = nil    //Room 참조횟수1
room = nil    //Room 참조횟수0
type = nil    //RoomType 참조횟수1

type 변수처럼 인스턴스를 nil 해버릴경우에는 해당 인스턴스를 사용하지 못하지만

메모리에서 해제되지 않고 계속 남아있게되죠

즉 type이라는 변수는 사용하지못하지만 room.roomType = type처럼

type의 레퍼런스를 사용한 변수는 사용가능하다는거에요

 

이러한 메모리 순환 문제를 해결하려면

예를들어 Room을 참조하는 부분을 하나하나 해제시키려 한다면

type?.room = nil

room = nil

이렇게 해야겠지만

코드가 길어지고, 해제해야할 대상이 많아진다면... 너무 귀찮기도하고 빼먹을수도 있어요

 

이러한 해결방법으로 약한참조미소유참조가 있어요

.

.

.

.

약한참조

자신이 참조하는 인스턴스의 참조횟수를 증가시키지 않아요

사용법은 weak키워드를 변수를 선언할때 앞에 써주면 되요

class RoomType {
    var special: Bool?
    weak var room: Room?
}

class Room {
    var number: Int?
    var roomType: RoomType?
}

var room: Room? = Room()    //Room 참조횟수1
var type: RoomType? = RoomType()    //RoomType 참조횟수1

room?.roomType = type    //RoomType 참조횟수2
type?.room = room    //Room 참조횟수1
room = nil    //Room 참조횟수0
print(type?.room)    //nil

약한참조를 사용한다면 인스턴스가 메모리에서 해제될수도 있기때문에 nil이 할당될 수도 있어요

이러한 점 때문에 자신의 값을 변경할 수 있는 변수로 선언해야하고 항상 옵셔널이여야 하죠

 

 

 

 

 

미소유참조

약한참조와는 다르게 자신이 참조하는 인스턴스가 항상 메모리에 존재할 것이라는 전제를 두고 있어요

인스턴스가 해제되어도 nil을 할당하지 않습니다.!!

따라서 옵셔널 변수는 아니여도 되죠

하지만 이러한 점때문에 잘못된 접근으로 인한 오류가 발생할 수 있어요

사용법은 unowned키워드를 변수를 선언할때 앞에 써주면 되요

 

 

 

 

 

클로저는 호출되면 자신내부에 있는 참조타입변수를 획득하게 되고

자신이 호출되면 언제든지 자신내부의 참조들을 사용할 수 있도록 참조 횟수를 증가시켜요

자신을 프로퍼티로 갖는 인스턴스의 참조 횟수도 증가시키는거에서 문제가 시작되죠

여기서 강한참조 순환이 발생하면 자신을 갖는 인스턴스가 메모리에서 해제될 수 없어요

 

 

 

그 문제는

 

 

획득목록(Capture List)

을 통해 해결 할 수 있어요

클로저 내부에서 참조타입을 획득하는 규칙을 제시해줄 수 있는 기능 이에요

대괄호 [] 로 둘러싸고 그뒤에는 in을 써서 사용해요

class Person {
    let name: String
    
    lazy var myNameIs: () -> String = {
        return "my name is \(self.name)"       // -> 강한참조 순환 문제발생
    }
    
    init(_ n: String) {
        self.name = n
    }
    
    deinit {
        print("deinit!!")
    }
}

var people: Person? = Person("player")
print(people?.myNameIs())
people = nil
//Optional("my name is player")   ->  강한참조 순환문제로 인하여 deinit이 출력되지 않음

//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////

class Person2 {
    let name: String
    
    lazy var myNameIs: () -> String = { [unowned self] in          //-> 획득목록 사용
        return "my name is \(self.name)"
    }
    
    init(_ n: String) {
        self.name = n
    }
    
    deinit {
        print("deinit!!")
    }
}

var people2: Person2? = Person2("player")
print(people2?.myNameIs())
people2 = nil
//Optional("my name is player")
//deinit!!        -> 메모리에서 해제

획득목록도 앞서 말한 참조들의 종류와 마찬가지로 weak와 unowned를 쓸 수 있어요

 

반응형