포스트

7주차 (1)

심화내용 시작이다.

첫날은 가볍게 시작하고 싶었는데, 하필이면 관심을 요근래 가지던 메모리 구조라서

공부도할겸 이것저것 여러 자료를 찾아서 정리를 해보려한다.

Swift Memory Structure

Code 영역

  • Code 영역은 프로그램의 실행 코드가 저장되는 공간입니다.
  • 컴파일된 소스 코드와 프로그램의 명령어들이 저장되어 있습니다.
  • 이 영역은 읽기 전용(Read-Only)이며, 프로그램이 실행되면서 수정되지 않습니다.

Data 영역

  • Data 영역은 전역 변수(global variables)와 정적 변수(static variables), 상수(constants)가 저장되는 곳입니다.
  • 프로그램 시작 시 할당되고 프로그램이 종료될 때까지 유지됩니다.

Stack 영역

  • 소스코드가 컴파일러에 의해 기계어로 변환되는 시점(컴파일 타임)에 크기가 결정됩니다.
  • Stack 영역은 함수 호출과 관련된 정보를 저장하는 데 사용됩니다.
  • 함수가 호출될 때마다 해당 함수의 로컬 변수(local variables), 함수 매개변수(parameters), 반환 주소(return address) 그리고 함수 호출에 필요한 기타 정보가 스택에 저장됩니다.
  • 함수가 실행을 마치면 해당 함수와 관련된 데이터가 스택에서 제거됩니다.
  • 스택은 후입선출(Last-In-First-Out, LIFO) 구조를 가지고 있습니다.
  • 값타입(value Type)이 저장되는 영역

Heap 영역

  • Heap 영역은 프로그램 실행 중(런타임) 동적으로 할당된 데이터가 저장되는 곳입니다.
  • 힙은 런타임 중에 메모리 할당이 필요한 경우 사용되며, 개발자가 직접 제어할 수 있습니다.
  • 힙에 저장된 데이터는 직접적으로 포인터를 통해 접근(참조)됩니다.
  • 힙은 스택과 달리 메모리의 자유 공간을 사용하여 데이터를 저장하므로, 크기나 수명에 대한 동적인 요구에 대응할 수 있습니다.
  • 참조타입(reference Type)이 저장되는 영역

Swift의 ARC

Swift는 ARC를 사용하여 Heap 영역의 객체 메모리를 자동으로 관리합니다.

  • ARC : Automatic Reference Counting의 약자

ARC는 Heap 영역의 객체에 대한 Strong Reference(강한 참조) count를 추적하고, 객체가 더 이상 필요하지 않을 때 (참조 count가 없을 때) 해당 객체에 대한 메모리를 자동으로 해제하는 방식으로 메모리를 관리합니다.

Strong Reference (강한 참조)

기본적으로 Swift에서 변수나 상수는 Strong Reference(강한 참조)를 합니다.

객체에 대한 Strong Reference(강한 참조) count가 증가되어 있다면, 해당 객체는 메모리에 유지됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

var person1: Person? = Person(name: "Alice") // strong 참조
var person2 = person1 // 또 다른 strong 참조

// person1이 nil이 되더라도 person2가 여전히 strong 참조를 가지고 있으므로 객체는 메모리에 유지됨
person1 = nil

Circular References (순환 참조)

ARC 작동 방식의 특성상 두 객체가 서로를 강하게 참조하는 경우, 순환참조가 발생합니다.

두 객체 모두 더이상 사용되지 않더라도 두 객체가 서로를 강하게 참조하는 경우, Strong Reference count가 감소하지 않아, 메모리에서 해제되지 못하고 메모리가 누수되는 문제가 발생합니다.

이러한 문제를 해결하기 위해, 약한 참조(weak reference)와 미소유 참조(unowned reference) 같은 다른 참조 유형을 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
    var name: String
    var pet: Pet? // Person이 소유하는 Pet

    init(name: String) {
        self.name = name
    }
}

class Pet {
    var species: String
    var owner: Person? // Pet을 소유하는 Person

    init(species: String) {
        self.species = species
    }
}

var person: Person? = Person(name: "Alice") // Person 클래스의 인스턴스를 생성
var pet: Pet? = Pet(species: "Dog") // Pet 클래스의 인스턴스를 생성

person?.pet = pet // Person 클래스의 pet에 Pet 인스턴스를 할당
pet?.owner = person // Pet 클래스의 owner에 Person 인스턴스를 할당

Weak Reference (약한 참조)

약한 참조는 Strong Reference(강한 참조)와는 달리 객체의 참조 count 증가시키지 않습니다.

객체의 생명 주기에 영향을 주지 않으면서 참조를 유지할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
    var name: String
    weak var friend: Person? // 약한 참조
    init(name: String) {
        self.name = name
    }
}

var person1: Person? = Person(name: "Alice")
var person2: Person? = Person(name: "Bob")
person1?.friend = person2
person2?.friend = person1

// person1이 메모리에서 해제됨, 그에 따라 person1과 연결된 모든 객체의 참조 카운트가 감소됨
person1 = nil

Unowned Reference (미소유 참조)

미소유 참조는 약한 참조와 유사하지만, optional 값이 아니라, nil로 설정될 수 없습니다.

만일 참조하는 객체가 이미 메모리에서 해제된 상태에서 접근하려 하면 runtime error가 발생되며, 사용 시점에 객체가 이미 해제되지 않았다고 확신할 수 있는 경우에 사용하여야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Country {
    var name: String
    var capital: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capital = City(name: capitalName, country: self)
    }
}

class City {
    var name: String
    unowned var country: Country // 미소유 참조
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

여기까지가 강의의 내용.

실제로 해보기.

사이트를 참고하여 코드를 작성하면서 정리를 해보려 한다.

Reference Type의 특징

1
2
3
4
5
6
class MyClass {
    var myInt: Int = 8
    deinit {
        print("this is now deallocated from memory")
    }
}

MyClass라는 테스트용 클래스를 하나 만들어 주었고,

ViewDidload에 다음과 같이 적어주었다.

1
2
3
4
5
6
7
8
9
var first: MyClass? = MyClass()
var second = first
var third = first

        
print(first?.myInt)
first?.myInt = 2
print(second?.myInt)
print(third?.myInt)

출력하면 결과는 다음과 같이 나온다.

1
2
3
Optional(8) // first
Optional(2) // second
Optional(2) // third

first에서는 myInt가 기존 클래스 값 그대로 8을 유지 했으나. 그다음 바로 first의 myInt를 2로 바꾸면서

second와 third는 first와 같은 메모리 영역을 참조하고 있기에 값이 바뀌면서 같이 값이 바뀌어 버렸다.

breakpoint를 찍어서 확인을 해보면

Clean-Shot-2024-04-09-at-18-09-47-2x

first, second, third 모두 같은 메모리 영역을 사용함을 볼 수 있다.

Clean-Shot-2024-04-09-at-18-12-22-2x

그리고 그 메모리 영역은 MyClass이다.

1. nil을 주었을때 변화

1
2
3
4
5
6
7
8
var first: MyClass? = MyClass()
var second = first
var third = first

first = nil // new
print(first)
print(second?.myInt)
print(third?.myInt)

실행을 해보면

Clean-Shot-2024-04-09-at-18-16-26-2x

first는 역시 nil이 되었지만.

나머지 second, third는 8을 유지하고 있다.

저 메모리 값은

Clean-Shot-2024-04-09-at-18-18-50-2x

MyClass와 같다.

first가 nil이 되면서 참조가 first는 메모리에서 해제 되었지만.

second, third는 강한 참조이기에 MyClass 메모리에 링크 되어있는걸 볼 수 있다.

이게 바로 강한 참조의 가장 큰 문제이다.

내가 어떤 변수나 클래스를 더이상 사용하지 않더라도 이렇게 메모리에 남아 누수를 일으킬 수 있다는 것이다.

2. 모두 nil을 준다면?

Clean-Shot-2024-04-09-at-18-22-45-2x

메모리에 할당되어있던게 사리진걸 볼 수 있다.

Clean-Shot-2024-04-09-at-18-23-54-2x

순환 참조

서로가 서로를 강하게 참조할때 발생.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
    var job: Job?
    
    deinit{
        print("Deallocating Person")
    }
}

class Job {
    var person: Person?
    
    deinit{
        print("Deallocating Job")
    }
}

이번엔 위와 같이 클래스를 작성해준다.

그리고 ViewDidload에 다음과 같이 작성해주자

1
2
3
4
5
var joe: Person? = Person()
var dev: Job? = Job()
        
joe?.job = dev
dev?.person = joe

서로가 서로를 참조하는 매커니즘이다.

역시 실행을 해보자.

Clean-Shot-2024-04-09-at-18-45-57-2x

서로가 서로를 참조하기에 계속 생성이 된다.

Clean-Shot-2024-04-09-at-18-47-00-2x

그리고 메모리 누수가 발생하는걸 알 수 있다.

Clean-Shot-2024-04-09-at-18-49-42-2x

Clean-Shot-2024-04-09-at-18-52-24-2x

무한루프 발생.

그리고 하나를 nil로 주어도

1
2
3
4
5
6
7
var joe: Person? = Person()
var dev: Job? = Job()
        
joe?.job = dev
dev?.person = joe
        
joe = nil //new

Clean-Shot-2024-04-09-at-18-56-35-2x

이렇게 이미 무한 루프에 빠진 dev는 계속 챗바퀴처럼 돌게된다.

해결방안

1. 약한 참조 (weak)

변수 선언 앞에 weak을 붙여 약한 참조로 하는것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
    weak var job: Job? //new
    
    deinit{
        print("Deallocating Person")
    }
}

class Job {
    weak var person: Person?
    
    deinit{
        print("Deallocating Job")
    }
}

실행.

Clean-Shot-2024-04-09-at-19-01-31-2x

무한루프가 사라졌다.

그리고 joe에 nil을 줘도 아무런 콘솔에 뜨던게 없었는데 약한참조를 하면서 nil을 하니 Person 클래스가 메모리에서 사라진걸 확인 할 수 있다.

Clean-Shot-2024-04-09-at-19-02-28-2x

2. 미소유 참조 (Unowned)

약한 참조와 달리 자신이 참조하는 인스턴스가 항상 메모리에 존재할 것이라는 전제를 기반으로한다.

즉 해당 인스턴스가 nil이 아닐것이라는 확신을 가지고 미소유 참조를 사용.

메모리에 해제된 인스턴스에 접근하려하면 런타임 에러가 발생하며 강제종료 된다.

다음과 같이 클래스를 구성하였다.

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
class Person2 {
    let name: String
    
    var card: CreditCard?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("\(name) is deinitialized")}
    
}

class CreditCard {
    
    let number: Int
    unowned let owner: Person
    
    init(number: Int, owner: Person) {
        self.number = number
        self.owner = owner
    }
    
    deinit {
        print("Card \(number) is deinitailized")
    }
}

viewDidLoad에 다음과 같이 해주었다.

1
2
3
4
5
var harold: Person2? = Person2(name: "harold")
        
        if let person2: Person2 = harold {
            person2.card = CreditCard(number: 1, owner: person2)
        }
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.