 Apple Lover Developer & Artist

영속적인 디자인에 현대의 공감을 채워넣는 공방입니다

 Apple/iOS Dev Challenges

[Challenge] 3주만에 앱스토어에 앱 출시하기 - 4부 설계 및 데이터 모델 개발

singularis7 2024. 2. 12. 18:01
반응형

Overview

본 글은 짧은 시간을 투자하여 앱스토어에 나만의 앱을 출시하고 싶은 독자를 대상으로 개발 가이드를 제공하고자 작성하였습니다. 3부에서는 가시적인 성과물을 만들기 위한 프로토타입 제작 과정을 조망해 보았습니다. 4부에서는 iOS 개발 프레임워크를 사용하여 데이터 모델의 개발 과정을 조망해 보겠습니다. 독자 여러분께서 포스팅을 읽고 "앱 개발 별거 아니네!" 생각할 수 있도록 인도할 수 있다면 본 포스팅은 목적을 다하는 것입니다. 이전 포스팅을 읽지 않으셨다면 반드시 보고 오세요.

GoodJob!

 

[Challenge] 3주만에 앱스토어에 앱 출시하기 - 3부 프로토타이핑

Overview 본 글은 짧은 시간을 투자하여 앱스토어에 나만의 앱을 출시하고 싶은 독자를 대상으로 개발 가이드를 제공하고자 작성하였습니다. 2부에서는 성과물을 만들어나가기 위해 필요한 작업

singularis7.tistory.com

실제 프로젝트 개발

이전 시간에 프로토타이핑을 통해 사용자 경험 설계에 집중했다면 실제 프로젝트 개발 단계에서는 실질적으로 기능이 동작하도록 만드는 것에 집중할겁니다. 여기서 문뜩 이런 의문이 들 수 있어요. 프로토타입을 구현해 둔 스토리보드에 코드를 붙여서 기능을 확장하면 더 빠르게 구현할 수 있는데 왜 SwiftUI로 개발 도구를 갈아타는지 말이죠. 

저는 개발 작업에 있어서 관심사의 분리를 통한 집중 그리고 확장성에 포인트를 두었어요. 사용자 경험을 설계하는 과정에서 코드를 작성하는 개발 방식이 사용자 경험 설계를 향한 집중을 방해한다고 느껴졌어요. 코드를 수정하거나 확장하기 쉽도록 디자인하는 것에 신경 써야 하니까 말이죠. 그 측면에서 SwiftUI는 프로젝트 초기의 프로토타이핑 도구로써는 적합하지 않다고 생각했어요. 대신 실제 프로젝트에서 UI 컴포넌트를 스타일링하고 기능을 개발하는 측면에서 SwiftUI가 제공하는 생산성이 훌륭하다고 생각하기 때문에 실제 개발에서 사용하게 되었어요.

반면에 스토리보드는 코드를 사용하지 않기 때문에 화면 흐름, 책임, 정보 등에 집중하여 시각적인 방식으로 온전히 사용자 경험을 설계하는데 도움을 주죠. 물론 사용자 경험 설계 과정에서 피그마 등의 디자인 도구를 사용할 수 도 있어요. 스토리보드를 사용하면 iOS 표준 UI 컴포넌트와 익숙한 오토레이아웃 조작 방식을 사용하여 화면을 설계할 수 있어요. 필요하면 프로토타입용 소규모 코드를 붙여볼 수도 있고요. 네이티브 앱 형태로써 직접 아이폰에서 테스트해 볼 수 있다는 점이 매력적으로 다가왔죠. 익숙한 도구를 사용할 수 있는 게 구현하고자 하는 본질인 "목적"에 집중할 수 있도록 도와주었다고 생각합니다. 그래서 디자인 도구로써 스토리보드와 개발 도구로써 SwiftUI의 조합을 선택하게 되었습니다.

실제 프로젝트 생성

프로토타입 디자인을 바탕으로 실제 기능이 동작하는 앱을 개발해보겠습니다. 새로운 Xcode 프로젝트를 생성할게요. 신규 프로젝트의 템플릿은 멀티플랫폼 앱을 사용합니다. 인터페이스는 자동으로 SwiftUI가 사용될 거예요. 실제 앱에서는 Persistence 기능을 사용하기에 storage 옵션을 선택해 줄 거예요.

storage 옵션에는 두 가지 선택지가 보일 거예요. Core Data와 Swift Data가 존재합니다. 전자의 경우 전통적으로 사용되어 온 도구이고요. 후자의 경우 지난 2023 WWDC에서 공개된 영속성 개발 도구입니다. iOS16 ~ iOS17 범위의 타깃 지원을 목표하기 때문에 필자는 코어 데이터를 사용했어요. 익숙한 개발 도구를 활용해 빠르게 앱을 개발하고자 하는 이유도 있었고요. 아이클라우드를 활용한 클라우드 영속성 기능을 지원하고 싶다면 클라우드킷 옵션 켜주시면 되고요. 개발의 기본인 테스트는 무조건 켜주세요.

SwiftUI 신규 프로젝트 생성하기

거시적인 설계도

코드를 작성하기 전에 간략한 설계도를 그려볼게요. 별거 없어요. 모듈 간의 의존성과 데이터의 흐름의 질서 정도만 간략히 잡아줄 거예요. 나머지 설계는 코드를 구현하면서 문제를 마주하면 유연하게 해결해 보도록 해요.

설계도

설계도를 만드는데 대단한 도구나 철학이 필요하지 않아요. 어지러운 방을 정리 정돈해주는 느낌으로 코드를 역할 별로 구분 지어주고 질서를 부여하는 것뿐이에요. 설계도를 만드는데 필요한 도구는 백지 한 장과 검은 펜 하나만 있으면 충분합니다.

저는 크게 3가지 역할로 구분해 보았어요. Presentation 영역, Business 영역, Data Model 영역입니다. Presentation 영역은 사용자 인터페이스를 선언하는 책임을 가진 코드들이 모이는 곳입니다. SwiftUI 프레임워크에 의존하는 코드들은 모두 여기에 모이게 됩니다. Business 영역은 1부에서 기획한 Usecase에 맞추어 동작하는 코드들이 모이는 곳입니다. 자신의 앱의 기획과 밀접하게 연관된 도메인 모델에 의존하는 코드가 여기서 정의됩니다.  Data Model 영역은 Persistence를 구현하는 데이터 모델과 코드들이 모이는 곳입니다. 코어 데이터 프레임워크에 의존하는 코드들은 모두 여기에 모이게 됩니다.

의존성 방향에 관하여 설명드릴게요. Business 영역의 핵심 기능은 Data Model 영역의 코드와 데이터들을 해석하여 Presentation 영역에 제공해주는 역할을 합니다. 이런 이유로 의존성의 방향은 Presentation 영역의 코드와 Data Model 영역의 코드가 Business 영역을 중심으로 의존하게 됩니다. 다르게 표현해보면 Presentation 영역의 코드는 Data Model 영역의 코드를 직접 참조할 수 없고요. 반대로 Data Model 영역의 코드가 Presentation 영역의 코드를 직접 참조할 수 없습니다.

데이터 흐름에 관하여 설명 드릴께요. 데이터는 단방향으로 흐릅니다. Presentation 영역에서 시스템 혹은 사용자 이벤트에 기반하여 코드를 실행합니다. Business 영역의 코드를 거쳐 Data Model 영역에서 데이터를 얻거나 조작합니다. Data Model 영역이 반환한 데이터를 Business 영역이 받아서 Presentation 영역에서 사용할 수 있는 형태로 가공한 후 Presentation 영역에 결과물을 제공합니다. 이와 같은 거시적인 규약을 따르기만 하면 세부적인 코드 디자인은 상황에 맞추어 개선해 나가게 됩니다.

타입명 규약을 정하여 코드가 위치한 영역을 구분 지어보았습니다. Presentation과 Business 영역 사이에 사용되는 코드들은 타입명의 접두사로 "GJ"를 붙여주었습니다. 대단한 의미는 없고요. 앱 이름인 "GoodJob"의 축약어입니다. Business와 Data Model 영역 사이에 사용되는 코드들은 타입명의 접두사로 "CD"를 붙여주었습니다. 마찬가지로 영속성 프레임워크 이름인 "Core Data"의 축약어 입니다. 이제 타입명만 보면 어떤 세상에서 사용되는 코드인지 직관적으로 파악할 수 있게 되었습니다.

세부적인 설계도

앱에서 사용할 데이터 모델부터 구현해 볼게요. 마침 1부 기획 파트에서 설계한 ER다이어그램이 떠오르지 않나요? 기획해 둔 데이터 모델을 활용하여 코어데이터 영역의 엔티티와 도메인 비즈니스 영역의 엔티티를 구현해 볼 거예요.

갑자기 의문이 들 겁니다. 왜 똑같은 엔티티를 두 번이나 구현하냐고요. 불필요한 코드 중복만 생기는 건 아니냐고요. 저는 코드가 수정되는 이유에 대한 관심사의 분리 목적으로 개별 영역에서 엔티티를 구현하게 되었어요. 코어 데이터 영역의 엔티티는 영속성 지원을 위한 코어 데이터 관련 기능과 의존성이 있어요. Presentation 영역의 엔티티는 SwiftUI 지원을 위한 관련 프로토콜을 채택하게 되죠. Presentation 영역의 엔티티가 코어 데이터 영역의 엔티티와 동일한 형태로 사용되지 않을 수 있어요. 사용자와 가까운 영역은 기능의 변화가 크기 때문이죠. 두 엔티티를 분리해 두면 변화를 통제하기가 수월해져요. 프레젠테이션 영역의 변화가 코어데이터 영역까지 전파되지 않고 이의 반대 역시 마찬가지이기 때문이죠.

코어 데이터 모델링

먼저 코어 데이터에 대한 설명을 읽고 오세요. 앞으로 우리가 해야 할 목표를 생각하면서 읽으면 문서가 금방 파악될 겁니다. 기획 단계에서 설계해 준 데이터 모델을 코어 데이터 프레임워크에서 사용되는 엔티티와 관계로 표현할 수 있어야 하고요. 데이터 모델을 활용하여 코어 데이터 영속성을 활용한 CRUD 연산을 지원할 수 있어야 합니다. Essentials, Data Modeling, Fetch Requests 섹션은 반드시 확인하세요.

 

Core Data | Apple Developer Documentation

Persist or cache data on a single device, or sync data to multiple devices with CloudKit.

developer.apple.com

문서를 읽고 나면 코어 데이터 프레임워크의 구조, 모델링과 코드 생성, Fetch Request를 활용한 CRUD 연산 방식을 파악할 수 있을 겁니다. 간략하게 다시 한번 요약해 드릴게요. 코어 데이터는 객체 그래프의 영속성 지원 프레임워크입니다. 코어 데이터를 활용하여 ER 다이어그램의 엔티티를 객체지향 프로그래밍 상의 객체로 표현하고 엔티티 간의 관계를 객체 간의 관계로 표현하여 관계에 기반한 연산을 정의할 수 있어요. 영속성 지원을 통해 앱이 종료되어도 데이터를 보존할 수 있고요. 클라우드 킷을 활용하면 기기간 데이터 연동성을 구현할 수 있습니다.

코어 데이터를 활용해 앱의 모델 계층을 관리하고 영속성을 지원하는 코드를 구현하기 위해선 코어데이터 스택 구조를 알고 있어야 합니다. Xcode 프로젝트 생성 시 코어 데이터 지원을 활성화하면 PersistenceController 타입의 구조체 코드가 자동으로 생성되는데 여기에 코어 데이터 스택을 관리하는 코드가 모이게 됩니다. 크게 세분류로 나눌 수 있죠. 앱의 엔티티 모델을 의미하는 NSManagedObjectModel과 엔티티 인스턴스의 변경사항을 추적하는 NSManagedObjectContext 그리고 내부적으로 영속성 지원을 위한 DBMS(SQLite)를 관리하는 Store Coordinator로 구성됩니다.

코어 데이터 스택 구조를 활용하여 CRUD 연산 예시를 들어볼게요. NSManagedObjectModel의 인스턴스를 생성하여 NSManagedObjectContext에 해당 인스턴스를 저장해 주는 방식으로 Create 할 수 있어요. NSFetchReqeust를 작성하여 NSManagedObjectContext에 요청함으로써 Read 할 수 있어요. Update 혹은 Delete 하려는 객체를 FetchRequest를 활용해 조회한 후 객체의 속성을 변경하거나 객체를 제거한 후에 NSManagedObjectContext에 반영해 주는 방식으로 나머지 연산들도 정의할 수 있어요. 데이터 모델링 과정에서 정의한 관계 규칙에 맞추어 연산이 수행되도록 프레임워크를 활용해 볼 수도 있습니다.

영속성 엔티티 생성

앞서 생성한 Xcode 실제 앱 프로젝트로 돌아와 볼게요. 확장자명이. xcdatamodeld 인 파일 하나가 보일 거예요. 여기서 엔티티 객체와 관계를 정의해 줄 수 있어요. 과거에는 엔티티와 그래프를 시각화된 그래프 형태로 편집할 수 있었는데 Xcode 14부터 이 기능이 빠졌어요. 그래서 그래프를 시각화해 주는 3rd 파티 앱 하나를 함께 활용해 볼 거예요. 다음의 앱을 맥에 설치해 주세요.

 

‎CoreDataGraph

‎Introducing CoreDataGraph – the ultimate tool for developers and data modelers working with CoreData in iOS and macOS applications. With this app, you can effortlessly visualize your `.xcdatamodel` files, making data modeling a breeze. Key Features: 1

apps.apple.com

앱을 실행 후 프로젝트 디렉터리에서. xcdatamodeld 파일을 열어주면 정의한 엔티티와 관계가 시각화되어 보입니다. Xcode의 데이터 모델링 편집기에서 엔티티와 관계를 수정하고 위 프로그램을 활용해 시각적으로 검토해 나가는 방식으로 구현하면 됩니다. 필자는 다음과 같이 구현해 보았어요.

코어데이터 Xcode 편집기

코어 데이터 모델 편집기에서 정의하는 엔티티들은 코어 데이터 영역에 있기 때문에 거시적인 설계도 규칙에 따라서 타입명 접두사로 CD를 사용해 주었습니다. 속성이나 관계에 대한 네이밍은 접미사로 언더바 "_" 를 사용해주었습니다. 추후에 Swift Extension을 활용해 연산 프로퍼티를 활용하여 wrapping 할 것이기에 내부적으로 사용되는 속성이라는 의미입니다. 앞서 설치한 앱으로 시각화된 그래프를 살펴보면 다음과 같습니다.

코어데이터 모델 시각화 그래프

엔티티 오브젝트와 관계가 잘 보이고 있죠. 관계에는 화살표의의 이중 여부로 카디널리티까지 표현해 주네요. 다만, 관계를 정의할 때 주의할 점이 있어요. Delete Rule은 시각화되어 보이지 않기 때문에 코어 데이터 모델 편집기 내에서 반드시 확인해주셔야 합니다. 보통 기본값은 Nullify로 설정되어 있을 거예요. 서로 1:1 관계를 맺는 객체 A와 객체 B가 있다고 가정해 볼게요. A가 제거 시 Delete Rule을 Nullify로 해두면 B 객체는 살아있으면서 A 객체에 대한 참조만 Null 값으로 만들어요. 메모리상 A만 제거되고 B만 남은 겁니다.

1:N 관계에서 간혹 Cascade Delete Rule이 필요한 경우가 있어요. 부모가 A이고 자식으로 B, C, D가 있는 객체 그래프가 있다고 가정해 볼게요. A가 제거되었을 때 B, C, D 객체가 함께 제거되도록 설정하고 싶을 때 Cascade를 사용해요. 관계 설정이 잘못되면 제거 이상 현상이 발생할 수 있으니 테스트해 가며 조심히 설정해주어야 합니다.

요정도만 주의해 주셔도 코어 데이터 프레임워크를 활용한 데이터 모델링을 비교적 수월하게 하실 수 있을 거예요. 갑자기 의문 하나가 들 수 있어요. 코어 데이터 모델 편집기에서 생성한 타입은 어떻게 코드로 사용하는지 말이죠. 편집기에서 별도의 설정을 건들지 않았다면 Xcode가 자동으로 엔티티 타입 코드를 생성해 줍니다. 편집기에서 개발자가 설정해 준 방식을 참조해서요. 코드 편집기의 우측 사이드 인스펙터 창에서 codegen 설정을 변경하여 개발자가 수동으로 코드를 작성할 수도 있는데요. 이번 프로젝트에서는 Xcode가 자동으로 생성한 코드에 extension을 활용하여 확장하는 방식을 사용할 겁니다.

엔티티 코드 커스터마이징

앞서 코어 데이터 모델 편집기에서 속성과 관계명 네이밍 규약으로 언더바를 사용했었죠. 이유가 있어요. 코어 데이터는 역사가 오래된 전통적인 프레임워크예요. Objective-C (이전 세대 프로그래밍 언어)부터 사용되어 왔죠. 코어 데이터 상에서 사용할 수 있는 Primitive 타입 혹은 옵셔널 지원 등을 Swift 환경에 맞추기 어려울 수 있어요. 그래서 Xcode가 자동으로 생성해 주는 속성 타입을 그대로 사용하지 않고 Swift 환경 혹은 사용자의 목적에 알맞게 변환하여 사용할 수 있도록 각종 코어데이터 엔티티의 속성들을 랩핑 해서 사용할 겁니다. 저는 다음과 같이 사용해 보았어요.

채용공고 코어데이터 엔티티 커스텀 확장 코드

연산 프로퍼티의 이름은 언더바를 빼고요. 내부의 getter와 setter에는 코어데이터 엔티티 속성 혹은 관계를 통해 연산을 수행합니다. 새로운 인스턴스를 생성할 때 사용할 목적의 편의 생성자를 정의해 주었습니다. NSManagedObject는 연산 맥락에 맞추어 이벤트를 받을 수 있어요. 예를 들어 제가 오버라이드 해둔 awakeFromInsert 메서드의 경우 새로운 인스턴스가 생성되어 NSManagedObjectContext에 삽입될 때 이벤트를 받아서 실행되는 메서드예요. 이곳에 인스턴스 초기 생성 시 범적으로 사용할 코드를 넣어주면 됩니다. 예를 들어 객체를 식별하기 위한 id값, 객체의 생성 일자 등을 일률적으로 초기화할 수 있습니다. 여기까지 오시면 코어 데이터를 활용한 데이터 모델과 코드 생성이 완료되었습니다.

SwiftUI 데이터 모델링

지금부터는 SwiftUI View에서 사용할 앱 도메인 모델을 개발해보려고 합니다. SwiftUI를 사용하다 보면 각종 데이터 타입을 사용자 인터페이스에 표현하거나 Navigation 탐색 구조를 구현하는 개발 패턴을 접할 수 있을 겁니다. SwiftUI 프레임워크 내부적으로 객체 식별자와 해쉬 연산을 통해 데이터의 변화를 추적하여 효율적으로 UI 상태를 갱신하고 애니메이션을 구현하는 등 최적화 기법이 적용되어 있습니다. 그래서 SwiftUI와 함께 사용되는 모델에 Swift 표준 모델링 프로토콜로 사용되는 Identifiable 혹은 Hashable 프로토콜을 채택해야 하는 경우가 다수 발생합니다. SwiftUI 프레임워크와 호환되도록 모델 타입을 정의하는 방법을 알기 위해선 다음의 문서를 참조하시면 좋습니다.

 

Displaying data in lists | Apple Developer Documentation

Visualize collections of data with platform-appropriate appearance.

developer.apple.com

 

 

Adopting Common Protocols | Apple Developer Documentation

Make your custom types easier to use by ensuring that they conform to Swift protocols.

developer.apple.com

도메인 엔티티 생성

프로토타입을 살펴보며 도메인 모델을 구현하겠습니다. 앞서 프로토타이핑을 통해 화면 당 자주 사용되는 정보를 구분하여 설계해 두었죠. 정보는 ERD 상의 엔티티로 표현해보기도 했고요. 화면에 보일 정보들을 중심으로 도메인용 엔티티를 생성해 볼 거예요.

GJJobPosting 엔티티 타입 코드

타 객체와의 관계는 엔티티 객체 인스턴스의 id 값을 엔티티 속성에 담아주어 필요할 때마다 조회할 수 있도록 구현했습니다. 필요한 정보별로 묶어서 타입을 구현하면 너무 많은 코드를 작성해야 합니다. 재사용성도 떨어지고요.

코어 데이터에서는 확장성을 고려하여 엔티티를 세밀하게 쪼개두었는데요. 반면에 앱 모델에서 특수한 목적으로 반정규화하여 사용하는 경우가 있어요. 예를 들어, 코어 데이터 영역에서는 JobPosting을 Company와 JobPosition 엔티티로 쪼개서 관리하고 있었다면 도메인 모델에서는 하나의 JobPosting 엔티티에서 Company와 JobPosition 속성을 합쳤습니다. 위 형식으로 자주 사용되기 때문에 참조 연산을 줄여 편리하게 사용할 수 있었기 때문입니다.

이로써 데이터 모델링 기획안을 바탕으로 앱 모델과 코어 데이터 모델을 구현하게 되었습니다!

마무리

여기까지 오시느라 수고 많으셨습니다. 이번 시간에는 데이터 모델링 기획안을 코어 데이터와 Swift 표준 프로토콜을 활용해 실체화시켜 보는 시간을 가져보았습니다.

필자는 코어 데이터를 사용해 보았지만 영속성 지원 방법이 이것만 있는 것은 아닙니다. realm을 사용할 수도 있고요. Firebase를 사용할 수도 있습니다. 데이터베이스와 연동된 웹서버 API를 활용해 볼 수도 있죠. 상황에 알맞은 기술을 선택해 보세요.

제가 사용한 코어 데이터 활용 방법이 정답이 아닐 수 있습니다. 코어 데이터가 초기 학습 장벽이 있는 기술이라고 생각합니다. 이 기술에 익숙하지 않으시다면 작은 프로젝트 하나를 생성하여 연습해 보실 것을 권장합니다.

긴 글을 읽어주셔서 감사합니다.

반응형