VesselWheel

OrderListView와 MenuView 간의 데이터 연결(feat. 델리게이트, 약한참조) 본문

Xcode Study

OrderListView와 MenuView 간의 데이터 연결(feat. 델리게이트, 약한참조)

JasonYang 2024. 1. 2. 11:12

MenuView와 OrderListView 간의 데이터 연결을 위해서 MenuDataDelegate를 약한 참조로 연결하였다. 

weak var delegate: MenuDataDelegate?는 약한 참조를 사용하여 MenuDataDelegate 프로토콜에서 구현한 객체를 참조하는 변수를 선언하는 코드로 이를 통해 객체 간의 통신을 위임하고, 메모리의 누수를 일으키지 않고 안전하게 처리할 수 있게 된다. 

약한참조란?(ps. 미소유참조)

더보기

Weak Reference (약한 참조)

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

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

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

약한 참조(weak reference)는 객체 간의 참조 관계에서 강한 참조와 달리 상호 참조를 막을 수 있는 참조 방식입니다. 약한 참조는 메모리 누수를 방지하고 순환 참조(circular reference)를 해결하는 데 사용됩니다.

위의 코드에서 weak 키워드는 friend 변수가 약한 참조를 가리키도록 선언되었습니다. 이것은 Person 객체들 간의 상호 참조를 막기 위해 사용됩니다.

상호 참조는 두 개 이상의 객체가 서로를 강한 참조하고 있을 때 발생합니다. 이 경우 메모리에서 객체들이 해제되지 않고 유지되는 메모리 누수가 발생할 수 있습니다. 약한 참조를 사용하면 이러한 문제를 방지할 수 있습니다.

약한 참조는 참조하고 있는 객체가 메모리에서 해제될 경우 자동으로 nil로 설정됩니다. 따라서 약한 참조를 사용하는 객체에 대한 접근 시 항상 옵셔널 체이닝(optional chaining)을 사용하여 안전하게 접근해야 합니다.

위의 코드에서 person1과 person2 사이에 서로를 약한 참조로 설정하였으므로, 두 객체가 서로를 참조하지만 메모리 누수는 발생하지 않습니다. 하나의 객체가 다른 객체를 참조하지 않을 때, 해당 객체는 메모리에서 해제될 수 있습니다.

이렇게 약한 참조를 사용하여 순환 참조를 효과적으로 해결할 수 있습니다. 순환 참조는 큰 규모의 애플리케이션에서 메모리 누수와 성능 저하를 야기할 수 있으므로 주의해야 합니다.


Unowned Reference (미소유 참조)

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

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

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
    }
}

위의 코드에서 unowned 키워드는 미소유 참조(unowned reference)를 나타내며, 약한 참조와는 다른 참조 방식입니다. 미소유 참조는 약한 참조와 유사하게 순환 참조를 방지하기 위해 사용되지만, 약한 참조와는 몇 가지 중요한 차이점이 있습니다.

미소유 참조는 약한 참조와 달리 참조하고 있는 객체가 항상 유효하다고 가정합니다. 따라서 참조하고 있는 객체가 해제될 경우에도 자동으로 nil로 설정되지 않습니다. 이는 미소유 참조를 사용할 때 조심해야 할 점입니다. 만약 미소유 참조로 참조하고 있는 객체가 이미 해제되었을 경우, 해당 객체에 접근 시 런타임 오류가 발생할 수 있습니다.

약한 참조는 참조하는 객체가 메모리에서 해제될 경우 자동으로 nil로 설정되지만, 미소유 참조는 그렇지 않습니다. 따라서 미소유 참조는 객체의 생명 주기(lifetime)에 대한 명확한 보장이 필요한 경우에 사용됩니다. 예를 들어, City 객체는 항상 Country 객체에 속하기 때문에 미소유 참조를 사용하여 Country 객체에 대한 강한 참조를 유지할 수 있습니다.

위의 코드에서 City 클래스의 country 프로퍼티는 미소유 참조로 선언되어 있으며, Country 객체에 대한 강한 참조를 유지합니다. 이를 통해 City 객체는 항상 해당하는 Country 객체에 접근할 수 있습니다.

미소유 참조는 객체의 생명 주기를 명확히 이해하고 사용해야 하므로 주의가 필요합니다. 객체가 해제되었을 경우에도 미소유 참조로 접근하면 런타임 오류가 발생하므로, 객체의 생명 주기에 대한 책임은 개발자에게 있습니다.

MenuView에 약한 참조로 연결한 MenuDataDelegate

델리게이트(delegate)는 객체 간의 통신을 위해 사용되는 디자인 패턴으로 일반적으로 한 객체가 다른 객체에게 특정 이벤트나 작업의 처리를 위임할 때 사용된다. 델리게이트를 사용하면 객체 간의 결합도를 낮출 수 있고, 코드의 모듈화와 재사용성을 높일 수 있다.

약한 참조(weak)는 메모리 관리를 위한 키워드로 약한 참조로 선언된 객체는 참조 카운트를 증가시키지 않으며, 참조하던 객체가 메모리에서 해제되면 자동으로 nil로 설정된다. 이를 통해 델리게이트 패턴에서 발생할 수 있는 순환 참조(circular reference) 문제를 방지할 수 있다.

 

델리게이트 프로토콜 선언부

import Foundation

protocol MenuDataDelegate: AnyObject {
    func didSelectMenuItem(_ item: SpabucksMenuItem)
}

struct SpabucksOrderItem {
    var menuItem: SpabucksMenuItem
    var orderCount: Int
    
    init(menuItem: SpabucksMenuItem) {
        self.init(menuItem: menuItem, orderCount: 1)
    }
    
    init(menuItem: SpabucksMenuItem, orderCount: Int) {
        self.menuItem = menuItem
        self.orderCount = orderCount
    }
}

- 매소드 didSelectMenuItem은 SpabucksMenuItem 타입의 파라미터 아이템을 받는다.

- 구조체 SpabucksOrderItem은 menuItem과 orderCount 프로퍼티를 가지며,

-  didSelctMenuItem매소드를 통해 선택된 메뉴아이템은 1 카운터로 올라간다. 

- menuItemr과 orderCount는 각각 menuItem과 orderCount 값으로 초기화 한다. 

- AnyObject를 사용한 이유는?

더보기

AnyObject는 모든 클래스 타입을 나타내는 프로토콜입니다. AnyObject 프로토콜을 사용하면 해당 프로토콜을 채택한 객체를 약한 참조(weak reference)로 저장할 수 있습니다.

 

약한 참조는 객체 간의 순환 참조(circular reference)를 방지하기 위해 사용됩니다.

순환 참조는 두 개 이상의 객체가 서로를 강한 참조로 참조하는 상황을 말합니다.

이러한 상황에서는 메모리 누수(memory leak)가 발생할 수 있어서 메모리 관리에 문제가 생깁니다.

하지만 약한 참조를 사용하면 객체 간의 순환 참조를 방지하고 메모리 누수를 예방할 수 있습니다.

AnyObject 프로토콜을 사용하여 프로토콜을 정의할 때, 해당 프로토콜을 채택한 객체를 약한 참조로 사용할 수 있습니다. 위의 코드에서 MenuDataDelegate 프로토콜은 AnyObject 프로토콜을 상속받고 있으므로, MenuDataDelegate 프로토콜을 채택한 객체는 약한 참조로 저장될 수 있습니다. 이를 통해 객체 간의 순환 참조를 방지하고 메모리 누수를 예방할 수 있습니다.

 

 

델리게이트로 연결된 collectionView와 OrderListView

// MARK: - UICollectionViewDelegate

extension MenuView: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let selectedData = dataSource[indexPath.row] // 선택된 셀의 데이터
        delegate?.didSelectMenuItem(selectedData)
        collectionView.deselectItem(at: indexPath, animated: true)
    }
}

- 콜랙션뷰에서 didSelectItemAt할 경우, selectedData로 선언된 프로퍼티의 indexPath 값을 주입하고,

- seletedData프로퍼티를 받은 didSelectMenuItem을 델리게이트에서 참조한다. 

 

HeaderView와 MenuView 메뉴 선택 간 캘슐화 과정에서 private 접근제한제로 인한 직접 접근이 되지 않아 에러 발생

MenuCell에 정의한 데이터 할당 매소드 configure

 

OrderListTableViewCell의 데이터를 받아 UI프로퍼티에 설정하는 매소드 configure

- collectionView의 UIComponet들을 캡슐화하는 과정에서 private 접근제한자 때문에 view에서 직접 접근이 되지 않아 에러가 발생

- HeaderView와 OrderViewList에서 cell 컴포넌트에 데이터를 할당해주는 이벤트를 cell 클래스에서 매소드화함