[Swift] ViewController로 메모 앱 만들기(4) : 현재 코드 점검

Oni·2023년 9월 4일
0

TIL

목록 보기
21/47
post-thumbnail

원문 포스팅 🔗

오늘 팀별 회고 시간에 코드를 공유하면서 질문 받았던 내용을 좀 더 생각해볼 필요가 있어서 현재 코드를 점검해보고자 한다.

질의1) Memo 와 MemoManager 클래스를 따로 구분한 이유는?

질의2) MemoManager의 경우 꼭 클래스여야 하는지? 구조체로 하면 안되는지?


// Memo 클래스
import Foundation

class Memo {
    var content: String

    init(content: String) {
    self.content = content
    }
}
// MemoManager 클래스
import Foundation

class MemoManager {
    static let myMemo = MemoManager()
    var memoList: [Memo] = []
    
    // MemoManager 기능
    // 메모 추가
    func addMemo(content: String) {
        let newMemo = Memo(content: content)
        memoList.append(newMemo)
    }

    // 메모 수정
    func updateMemo(at index: Int, newContent: String) {
        guard index >= 0 && index < memoList.count else {
            return
        }
        memoList[index].content = newContent
    }

}

클래스 구분의 이유

처음엔 Memo를 배열로 선언하고 쓰려고 했었는데, 여러 뷰 컨트롤러에서 접근해서 사용해야하므로 별도의 클래스로 구분했다.
그러다가 메모에 작성날짜 또는 수정날짜(마지막 일자)도 추가하면 좋을 것 같아서 Memo 클래스 안에서도 메모에 들어갈 본문을 content로 쪼갰다.
그 상태로 코드를 작업하다가 메모를 추가, 수정, 삭제 등 해당 메모들을 관리할 필요성을 느꼈고, Memo 클래스 안에 기능들을 넣기에는 클래스들의 역할이 달라 따로 두는게 맞다고 생각했다.
그래서 Memo 클래스는 단순이 메모들이 모여있는 역할만 하고 그 외 관리는 MemoManager 클래스가 수행한다. -> 단일 책임(모듈화, 유지보수 용이)

왜 클래스?

구조체는 값 타입이고, 클래스는 참조 타입이다.
MemoManager는 여러 뷰 컨트롤러에서 참조되고 공유되어야 하기때문에 클래스로 정의하는게 적절할 것이라고 판단된다. 다만, 참조하고 있는 곳이 많아질수록 메모리를 많이 차지하는 이슈가 있기 때문에 적절한 메모리 해제 시점을 고민해야 한다.

🤔 클래스와 구조체

  • (공통점) 프로퍼티, 메소드, 서브스크립트, 초기화, 확장, 프로토콜
  • (차이점-클래스) 상속, 타입 캐스팅, 소멸화 구문, 참조에 의한 전달

🧐 구조체로 바꾼다면?

  • 복사 방식 : 구조체는 값 타입으로 전달하게 되는데, 여러 곳에서 MemoManager 인스턴스를 참조하고 있고 영향이 있을 때는 구조체로 변경하는게 적절하지 못할 수 있음. 클래스의 경우 하나의 인스턴스를 공유해 모든 곳에서 동일한 목록(메모)을 볼 수 있음
  • 메모리 사용 : 오히려 인스턴스를 생성할 때마다 복사되기 때문에 메모리를 더 사용하게 되는 문제가 발생할 수 있음
  • 코드의 유연성 : 구조체는 상속이 불가하기 때문에 단순한 데이터 저장구조에 적합함. 물론 메모 클래스가 굳이 상속을 받아야 하는건 또 아니라 이 부분은 고민이 필요해 보임. 다만, 개발하다보면 상속을 받아야 할 일이 오지 않을까?(...)

❗️ 클래스의 메모리 누수 방지

  • 주기적인 누수 검사 : 약한 참조(weak reference)를 사용하여 순환 참조 방지
  • 뷰 컨트롤러 생명주기 관리 : 메모리 해제 시점 계산
  • ARC(Automatic Reference Counting : 자동참조계수) : 약한 참조(weak)로 선언하게되면 순환 참조를 방지할 수 있음.
    🚨 (주의사항) 불필요한 강한 순환 참조(circular reference) 👉🏻 weak or unowned 참조 사용
weak var myMemo: MemoManager?

※ 약한 참조(weak)는 Optional 타입으로 선언되어야 하며, nil이 될 수 있기 때문에 옵셔널 바인딩을 확인해야 함!

Static: 싱글톤(Singleton) 패턴

싱글톤은 애플리케이션 내에서 오직 하나의 인스턴스만 존재하도록 보장하는 디자인 패턴이다. 일단 MemoManager는 다른 클래스에서 접근을 해야하기 때문에 찾아보니 싱글톤 패턴을 구현해야한다고 하더라. 이렇게 전역적으로 접근 가능한 공유 인스턴스를 만들면 동일한 객체를 참조하여 데이터를 공유하고 관리할 수 있다. 싱글톤 패턴은 자원 절약과 객체 생성/소멸 오버헤드를 줄여주는 장점이 있다고 한다.
static let으로 선언하여 불변성을 보장하며 안정적인 싱글톤을 구현할 수 있다.


결론적으로 메모리의 누수를 방지하기 위해 인스턴스 참조 카운트를 계산하여 적절한 시점에 메모리를 해제하는데 초점을 맞춰 수정하면 좋을 것 같다.
이번 회고는 한번 더 생각하고 고민할 수 있는 좋은 대화를 나눈 것 같다!

profile
하지만 나는 끝까지 살아남을 거야!

0개의 댓글