Share
Sign In
[Swift - System] Swift의 Sandbox 시스템과 UserDefaults.
한결
App Sandbox 시스템
App Sandbox 시스템은 유저의 데이터를 보호하는 목적으로 커널 레벨에서 앱 자체의 데이터 접근을 제어할 수 있는 기술이다. (말이 좀 어렵다.) Swift는 기본적으로 C언어 - Objective-C 언어를 기반하기 때문에 다른 가상 머신 없이 바로 커널에 명령을 쏠 수 있다. 이 과정에서 앱이 동작하는데 필요한 특정 목적의 데이터(ex. 유저의 개인 정보라던지, 네트워크 연결성 등)를 위험에서 보호한다.
위험은 다양할 수 있는데, 앱 규모가 커질수록 더 많은 위험에 노출될 수 있겠다. 샌드박스에 보관하는 데이터를 앱과 어떻게 상호작용 할지를 개발자단에서 컨트롤 하면서, 유저로부터 올 수 있는 문제를 사전에 컨트롤 할 수 있다. 이 데이터를 앱이 사용한다는 것을 유저에게 특정 *인지 이벤트로 인식시키고, 앱이 그 데이터만 활용한다는 명확성을 부여해야 하는 시스템이 샌드박스 시스템이다. 명시적으로 허락 받지 않은 접근은 런타임이 차단한다.
*내가 이해한 인지 이벤트는 다음과 같다.
우리가 앱을 설치하고 처음 켰을 때, 런치 스크린 이후에 각종 동의를 받는 경험을 했을 것이다. 이런 데이터에 대한 접근 및 활용 동의가 샌드박스를 이용해 인앱에서 데이터를 관리할 수 있는 권한을 부여한다고 볼 수 있다.
- 위치 기반 데이터 활용 동의
- 사진 데이터 접근 및 활용 동의
기본적인 iOS 기반의 앱의 샌드박스 구조
우리가 앱스토어에서 특정 앱을 설치하면 iOS는 해당 앱을 위한 샌드박스 영역을 생성한다. 위 이미지처럼 Bundle, Data, iCloud 를 위한 공간이 생성된다. 쉽게 생각해서 유저의 의도와는 상관없이 앱의 운영을 위해 읽기, 쓰기를 위한 문서 저장 공간(애플은 데이터 파일을 Documents라고 한다 보통)을 만드는 것이다. 외부 다른 앱이나 사용자가 접근하기 힘든 공간을 이용해서 앱의 보안을 높이는 시스템이라고 볼 수 있다.
내가 간단하게 이해한 인앱 Sandbox 시스템
1.
앱을 실행하면, 앱은 iOS 전체 파일관리 시스템에서 자기자신의 샌드박스를 바라본다.
2.
샌드박스에서 필요한 데이터를 활용한다. (읽고, 쓰고, 업데이트하고 다 한다)
3.
앱을 종료한다. 해당 앱을 위한 샌드박스 위치를 자체적으로 변경한다.
4.
앱을 다시 실행한다.
5.
해당 앱을 위한 변경된 샌드박스 위치를 바라볼 수 있게 한다. 즉, 특정 앱을 위한 샌드박스가 늘 같은 위치에 있는 것이 아니다.
User Defaults
샌드박스 내부에 해당 앱을 위한 user's defaults database가 생성된다. 이 데이터베이스는 문서형 NoSQL 데이터베이스처럼 데이터를 키-값 형태로 저장한다. 정해진 여러 필드가 있고, 하나의 데이터가 해당 필드의 값들을 가지는 관계형 데이터베이스 형태는 아니다.
UserDefaults로 관리되는 데이터는 앱이 가동되거나 어떤 이벤트를 처리할 때, 기본적인 상태들을 관리한다. (공식 문서에 따르면 - determine an app’s default state at startup or the way it acts by default.)
핵심 사용 방식은 공식 문서의 다음 문장으로 정리될 것 같다. UserDefaults 클래스가 데이터를 바라보고 있다가 캐싱을 통해 데이터를 앱에 필요하게 세팅한다. 당연히 서비스 레이어에 민감하게 적용될 수 있는 데이터나 처리는 좋지 않다.
📱
At runtime, you use UserDefaults objects to read the defaults that your app uses from a user’s defaults database.
set 메서드를 통해서 키-값 형태로 값을 저장하고, UserDefaults 클래스의 인스턴스를 생성해서 저장한 값을 키로 조회할 수 있다. Dictionary를 사용하는 방식과 뭔가 비슷하다.
저장한 값은 UserDefault의 standard 메서드를 연결하여 조회할 수 있다. standard 메서드에는 불러온 값을 어떤 타입으로 가지고 올 것인지 정의할 수 있는 메서드가 함께한다. UserDefaults에 저장한 값이 정수형이라면 integer(forKey: String) 형태의 메서드를 사용하면 되는 것이다.
아직은 iOS 기반으로 앱 개발을 막 배우는 단계라서 앱 운영을 위한 데이터 관리 체계를 이정도로 정리해본다. iCloud나 외부 API를 통한 데이터 관리, 다른 앱과의 데이터 주고받기 (ex. iOS 기본 앱인 건강 앱의 데이터를 나이키 런클럽 앱에서 활용하는 방식) 방식을 더 공부해보면 이해하는 폭이 넓어질 것 같다.
참고한 문서 👇
Ha
/hankyeol
Subscribe
[Swift - UIKit] iOS에서의 기본적인 Notification System
알림센터나 사이드 푸쉬 등으로 특정 블록 형태의 UI가 뜨는 것을 Notification 이라고 한다. 카카오톡에서 친구가 채팅을 보냈을 때, 쿠팡에서 특가 상품이 떴을 때, 주변에 토스를 사용하는 사용자가 발견되었을 때와 같은 상황에서 주로 알림이 온다. 이런 기본적인 '알림'이라고 하는 것을 다룰 수 있는게 iOS Notification System이다. 내장된 User Notification Framework를 이용하여 쉽게 알림을 구현할 수 있다. 리모트 방식의 알림은 서버를 통해 인증된 유저에게만 보여줄 수 있기 때문에 APNs 라고하는 서비스를 거쳐야 한다. Notification System의 두 가지 방식 Notification 방식은 Local, Remote 형태가 있다. 이름에서 두 방식의 차이를 대강 알 수 있다. Local 알림에 전달할 내용이 전부 앱에 들어있다. 루틴한 내용이나 비슷한 컨텐츠를 전달해주는 용도로 사용된다. (디데이, 루틴, 투두 어플 등) Remote 서버단에서 보내는 알림을 컨트롤하는 형식. (푸시 알림이라고도 함) (서버에서 데이터가 오기 때문에) 유저가 알 수 없는 시간에 서로 다른 컨텐츠를 전달해주는 용도로 사용된다. 광고, 개인화된 추천, 채팅 알림(ex. 카카오톡 채팅별 알림) 등의 알림이 리모트 형식이다. Notification System에서 유의해야 할 점 Notification이 동작하기 위해서는 반드시 유저에게 권한 동의를 받아야 한다. 아이폰의 여러 앱을 써보면 알겠지만, 앱을 처음 실행할 때나 앱을 실행시키고 있는 도중에도 계속 '알림'에 대한 권한을 획득하려고 하는 뷰가 나타난다. 어떤 앱에서는 '권한이 어떤 시점에서 부여되지 않았다'는 사실을 알려주는 alert을 보여줄 때도 있다. 애플에서는 알림에 대한 유저 동의 여부를 강하게 체크한다. 무분별하게 알림을 내뱉는 앱은 앱의 사용 경험을 넘어서 아이폰의 사용 경험 자체를 헤칠 수 있기 때문에 애플에서 권한 획득을 중요하게 생각하는 것 같다. 커머스나 앱 자체로 비즈니스 가치가 발생하는 서비스들은 사용자에게 알림 권한을 획득하기 위해서 노력한다. 앱이 종료된 시점에도 우리 서비스가 있고, 우리 서비스를 통해 새로운 이점을 얻을 수 있다는 사실을 알림을 통해 간편하게 전달할 수 있기 때문이다. 더불어, 애플은 Notification에 대해서 아래와 같은 정책을 운영하고 있다. Notification은 고유한 identifier를 가진다. 하나의 앱에서 최대 64개의 identifier를 운영할 수 있다. 64개의 idendifier를 넘기면 앱이 종료될 수 있다. (무분별한 알림을 방지하는 장치인 것 같다.) 64개를 넘기기 전에 알림을 리프레시 할 수 있는 앱 내부 정책, 코드 관리가 필요해 보인다. TimeInterval로 반복할 수 있는 최소 시간은 60초다. 60초 보다 적은 시간 단위로 동일한 알림을 반복할 수는 없다. (역시 무분별한 반복적인 알림을 방직하는 장치로 보인다.) Notification은 기본적으로 앱의 background에서 동작한다. background는 앱이 켜져있지 '않은' 상태를 의미한다. 반대는 foreground인데, delegation을 통해서 앱이 실행중인 상태에서도 Notification이 오도록 설정할 수는 있다. Notification Center나 배너 상태 등에서 유저가 Notification 자체를 인지했는지는 알 수 없다. 유저가 해당 알림을 터치했을 경우에만 확인할 수 있다. UNUserNotificationCenter를 이용해서 간단하게 Notification을 구현해보자 AppDelegate 단에서 앱이 런칭되는 시점에 Notification에 대한 권한을 획득하는 코드를 추가하자. UNAuthorizationOptions를 이용해서 획득하고자 하는 알림 형식을 정해줄 수 있다. UNMutableNotificationContent, UNTimeIntervalNotificationTrigger, UNNotificationRequest 클래스로 Notification을 위한 인스턴스를 생성하여 알림을 제어하는 코드를 작성할 수 있다.
한결
[Swift - UIKit] UIViewController 위에 여러 Protocol을 적용하여 동작하는 다양한 뷰 만들기
코드 베이스로 뷰를 그리는 연습을 하기 전, SB + Outlet 코드로 뷰를 그리는 연습을 하면서 최종적으로 만들어 본 TravelProject에 대한 정리 글입니다. 전체 코드는 해당 링크에서 볼 수 있습니다. Swift를 학습하면서 정말 다양한 뷰를 직접 만들어보고 있다. 24년 5월 27일부터 30일까지 스토리보드 기반으로 UIViewController 위에서 코드로 여러 화면을 그리는 연습을 했다. 그 과정에서 나 스스로 '이건 꼭 기록으로 남겨봐야겠다.' 하는 점들이 있어서 남겨본다. 1. UIViewController 위에 커스텀한 TableView 올려보기 (with. XIB) UIViewController에 UITable을 핸들링하기 위한 Delegation, Datasource 프로토콜을 적용해준다. *Protocol을 통해 delegation을 위임하는 것에 대한 내용은 요 링크로 짧게 작성해봤다. UITable에 들어갈 커스텀한 TableCell을 작업하기 위해 Swift 파일과 XIB 파일을 함께 생성해준다. XIB에서 작업한 커스텀 셀을 재사용이 가능한 셀로 만들기 위해 정확한 Identifier를 지정해줘야 한다. 이 부분 역시 UITableViewCell 클래스를 상속받는 클래스들이 identifier라는 멤버 변수를 가질 수 있도록 protocol + extension 조합으로 코드를 미리 작성해두면 편하다. UIViewController 위에 올려진 UITableView의 인스턴스에 커스텀한 Cell을 연결하고, 어떤 행에 어떤 셀을 어떻게 보여줄 것인지를 위임받은 코드에 설정해준다. 코드로 한 번 보자. 위 코드 형태가 Swift에서 테이블 뷰를 그리는 가장 기본적인 방법이다. cellForRowAt 에서 커스텀하게 생성한 TableViewCell 클래스의 인스턴스를 as 키워드로 정확하게 캐스팅 해줘야 해당 클래스 내부에 구현한 멤버 요소들을 불러와 사용할 수 있다. 2. 연결된 VC에 데이터 넘겨주기 테이블 뷰에서 특정 셀을 터치하여 선택할 경우, 연결된 페이지 뷰(UIViewController)로 데이터를 전달해야하는 경우가 있다. 여러 방법이 있겠지만, 가장 쉽다고 생각하는 방법은 해당 VC의 멤버 변수(Outlet 변수 포함)에 값을 할당하는 것이다. 테이블의 특정 셀이 선택되어 연결된 VC 인스턴스가 Present되기 전에 반드시 데이터 자체를 VC의 내부 멤버들에 할당하는 코드가 반영되어야 한다. VC가 Present되면 ViewDidLoad 메서드가 호출되고, 해당 뷰에 있는 각각의 뷰 객체들의 속성 값이 반영되어 있지 않으면 알 수 없는 nil 값을 만나서 앱이 종료된다. 다른 SB에 있는 VC를 찾아 navigating하는 방법에 대해서는 요 링크에 작성해뒀다. 코드로 다른 VC를 연결해주는 방법은 오히려 더 간단하다. 그냥 VC 인스턴스를 생성해주면 된다. 유저의 액션에 의해서 VC간 데이터가 잘 전송되면 다양한 형태의 UI로 앱을 구성하기 좋다. 3. UISearchBar + UISegmentedControl + UITableView 조합으로 필터링된 데이터 보여주기 유저가 원하는 조건으로 데이터를 필터링해서 보여줘야 할 경우가 많다. 검색을 할 수도 있고, 메뉴를 여러개 생성할 수도 있고, 조건 자체를 미리 여러 개 생성해 둘 수도 있다. 이번 프로젝트를 하면서 iOS에 내장된 SegmentedControl, SearchBar를 통해 데이터를 필터링 해볼 수 있었다. 물론 미리 정의된 Protocol을 불러와 역할을 ViewController에 위임하는 형식으로 코드를 작성했다. 필터링 할 데이터셋 확인하기 데이터를 필터링하려면 데이터가 어떤 형식으로 생겼는지 알아야 한다. 데이터는 기본적으로 (관계성이 있는 정형화되었다면) 동일한 키를 서로다른 값으로 채우는 형식이다. 이런 데이터를 특정 기준으로 필터링 하는데 가장 좋은 기준은 Bool 타입을 찾는 것이다. City라는 구조체는 domestic_travel (국내 여행 여부) 라는 키(사실 멤버 변수명이지만)에 Bool 타입의 값이 들어와야 한다. 필터링하기 딱 좋은 기준이다. 해당 키를 기반으로 .filter 메서드를 활용해 국내 여행 / 비국내 여행을 구조체 내에서 계산형 변수로 반환해줄 수 있다. View 단에서 로직을 세울 필요가 없다. 세그먼트의 선택에 따라서 필터링된 데이터 할당하기 데이터 처리가 준비되었다면, 이제 세그먼트 요소별 선택 값만 잘 지정해주면 된다. UISegmentedControl 객체에 내장된 setTitle 메서드로 세그먼트 선택지 이름을 부여할 수 있다. UIButton에 액션 타겟 함수를 지정해주는 것과 동일하게, 세그먼트 인스턴스에도 값이 변경될 때마다 불러낼 액션 타겟 함수를 지정할 수 있다. .selectedSegmentIndex 라는 접근자로 필터링 해두었던 데이터를 맵핑만 잘 해주면 된다. 필터링 된 데이터를 테이블에 보여주려면, 테이블 reload 함수를 호출하면 된다. 기준이 명확하다면, 열거형으로 기준을 명확하게 정의해주는 것도 좋을 것 같다. 검색어에 따라서 필터링된 데이터 보여주기 UISearchBar 역시 VC에 그 역할을 위임할 수 있다. (UISearchBarDelegate) extension 키워드로 VC에 서치바에 대한 역할 코드를 구분하여 작성하면 코드를 관리하기 편하다. 서치바에서 입력이 어떻게 이루어졌을 때, 검색어를 어떻게 처리해줄 지를 결정하는 것이 로직의 핵심이다. 아래 목록 중에 밑줄을 그어둔 부분이 서치바에서 내가 고려한 유저 이벤트다. 유저가 보고 싶은 데이터를 키워드로 검색했을 때 (검색 완료) → 해당 키워드를 포함하는 or 키워드와 일치하는 데이터를 필터링해서 보여준다. 유저가 서치바에 입력할 때마다 데이터를 검증하면서 해당하는 내용이 있으면 필터링해서 보여준다. 유저가 서치바에 입력한 데이터를 다 지우면, 처음 데이터를 다시 보여준다. 검색어를 어떻게 필터링 할 것인지는 개발자의 뜻에 달려있는 것 같다. 양이 정해진 데이터에서 검색을 할 때는 매번 필터링을 거쳐도 괜찮겠지만, 검색에 따라 API를 호출하여 외부 서버와 통신을 해야한다면 필터 함수에 적절한 지연이 필요해보인다. 1~3을 통해 서치바 + 세그먼트 컨트롤로 데이터를 필터링하여 뷰를 다르게 보여준 예시는 아래 영상과 같다. 4. MapKit을 이용해서 지도상에 주소의 위치를 표시하고, UITextField에 PickerView 삽입하여 위치 데이터 선택하기 iOS에서는 자체 내장 패키지로 MapKit을 지원한다. Apple Map이 있기 때문에, 해당 지도에 구현되어 있는 내부 API를 활용하여 앱 위에서 지도를 그려갈 수 있다. VC에서는 위에서 계속 해왔던 대로 Delegation을 잘 해주면 된다. MapKit을 활용하기 위해서는, 다른 어떤 웹 지도와 동일하게 latitude, langitude 값이 필요하다. 위치에 대한 이름도 같이 기본적으로 표시하면 참 좋겠다. MKMapViewDelgete 프로토콜에 내장된 setRegion API를 이용해서 위도, 경도, 주소의 이름 값으로 지도에 위치를 찾고, 반경 얼마만큼으로 보여줄 지를 결정할 수 있다. 이 API를 이용해서 주소값을 알고 있으면 동일 지도의 위치로 이동시켜주는 간단한 메서드를 작성해서 이용했다. setAddressOnMap 위치 이름, 위도, 경도 순서대로 데이터를 관리하면 좋을 것 같아서 Tuple 타입을 이용했다. 사실 데이터의 명시성을 더 높이기 위해서는 구조체의 인스턴스를 이용하는게 더 좋았지 싶다. MKPointAnnotation 인스턴스를 이용해서 해당하는 주소 포인트에 Pin (Apple이 만들어주는)을 꼽을수도 있다. setAddressOnMap 메서드를 PickerView 선택 액션에서 호출될 수 있도록 코드를 작성하면 딱 깔끔할 것 같다. UITextField에 UIPickerView를 InputView로 넣어준다. 그리고, 텍스트 필드의 UI를 마치 버튼인 것처럼 꾸며주면 그럴싸한 UI가 된다. 코드로 살펴보면 정말 별거 아닌 기능인데, 생각보다 UX적으로 신경써야 할 것이 많았던 것 같다. iOS 학습을 시작한지 2~3주차 시점에 스토리보드와 여러 View 클래스들을 이용해서 어느정도 동작하는 다양한 뷰를 반복적으로 만들어 보면서 확실히 개발 매커니즘에 조금은 익숙해질 수 있었다. 현재는 코드베이스로 UIKit 기반의 앱을 만드는 연습에 매진하고 있다. SB에서 코드베이스로 넘어온다고 코드의 대격변이 일어나지는 않기 때문에, 이번에 기록한 것들이 좋은 자양분이 될 것 같다.
한결
[Swift - UIKit] UIViewController에서의 View life Cycle API
UIViewController에서 View 객체를 로드하고 (그리고) 어떤 페이지로 전환되어 그 객체가 사라지는 과정을 관리하는 시스템을 내가 학습하기로는 View Life Cycle 이라고 한다. View 객체의 생명주기를 관리하는 UIViewController 클래스의 내장 오픈 메서드(API라고 부르겠다)는 여러가지가 있지만, 대표적으로 위 이미지의 5가지가 대표적일 것 같다. viewWillAppear viewIsAppearing viewDidAppear viewWillDisappear viewDidDisappear 사실, API의 prefix에 붙은 키워드만 보더라도 View 객체가 어떤 상태일지에 따라 서로 다른 API가 동작한다는 것을 쉽게 유추해 볼 수 있을 것 같다. (will, -ing, did) 모달이나 페이지 전환에 따라 특정 View 객체가 어떤 생명 주기를 가지는지를 각 API에 print 구문으로 확인해봤다. XCode로 앱을 처음 빌드할 때. UIController의 viewDidLoad가 가장 먼저 앱을 그리는 로직을 수행한다. 그리고 나서 순서대로 willAppear → isAppearing → didAppear API가 호출된다. (prefix 만으로도 쉽게 유추가 가능하다.) 버튼 액션으로 모달 페이지가 떠오를 때. 앱 화면 하단에서 떠오른 모달 페이지가 직전 View를 가리더라도, 해당 View 객체 생명 자체를 사라지게 하지 않는다. 즉, 이전 화면을 완전히 가리지 않는다면 해당 View는 생명선을 유지한다는 뜻이다. 해당 모달을 내리더라도 willAppear, isAppearing, didAppear API가 콘솔에 전혀 찍히지 않는다. 모달 페이지에서 특정 액션을 발생시켜서 뒤에 있는 View에 어떤 화면 전환이 필요하면, 그걸 처리하는 로딩 로직을 만들어야 할 것 같다. 버튼 액션으로 페이지 자체가 전환될 때. 화면이 Push되어서 완전 새로운 화면이 이전 View를 가릴 경우, 해당 View 객체는 생명을 다한다. 순서대로 willDisAppear, didDisappear API가 호출된다. 즉, 화면이 전환될 경우는 화면이 꺼짐으로 발생하는 데이터 저장이나, 업데이트 로직이 해당 생명주기 API에서 처리되어야 할 것이다.
한결