[iOS Study] 상태 복원 |
출처 : 아론 힐리가스의 IOS 프로그래밍
-
앱의 수명은 제한돼 있다.
만약 시스템이 더 많은 메모리가 필요하고 자신의 앱이 백그라운드 상태에 있다면, iOS는 아마 그 앱을 죽여 시스템에 메모리를 반환하도록 할 것이다.
사용자는 이러한 사실을 알 필요가 없다.
항상 앱의 마지막 장면으로 돌아가야 할 것이다.
-
앱에 상태 복원(state restoration)을 적용해야 한다.
상태 복원은 데이터를 저장하는 데 사용했던 아카이빙과 매우 비슷하게 작동한다.
앱이 잠시 중단 상태로 들어가면 뷰 컨트롤러 계층의 스냅샷(snapshot)이 저장된다.
사용자가 다시 열기 전에 앱이 죽었다면 시작 시에 그 상태가 복원될 것이다.
(만약 앱이 죽지 않았다면 모든 것은 메모리에 남아있고 상태를 복원할 필요가 없다.)
-
iOS 시뮬레이터에서 홈 버튼을 눌러 앱이 백그라운드로 진입하도록 한다.
이제 시스템이 그 앱을 죽이는 것과 같이 하기 위해 Xcode 로 돌아가서 정지 버튼을 누른다. (Command + - )
그리고 나서 앱을 다시 시작하면, Memory Kill 을 simulate 할 수 있다.
-
앱이 백그라운드로 들어가면 iOS 는 사용자 인터페이스의 스냅샷 이미지를 찍는다.
그리고 앱을 다시 시작하면 앱이 메모리에 올라올 때까지 이 스냅샷이 실행 이미지로 사용된다.
앱에 상태 복원 기능이 없다면 사용자는 잠시 동안만 앱의 이전 상태를 볼 수 있고 화면은 다시 처음 시작 상태로 바뀔 것이다.
-
상태 복원을 하려면 앱이 종료되기 전에 시스템은 이 트리의 각 노드를 돌면서 “이름이 무엇인가?”, “클래스는 무엇인가”, “저장해야 할 데이터를 가지는가?” 등에 대해 알아봐야 한다.
앱이 종료되는 동안 이 트리의 정보는 파일시스템에 저장된다.
-
실제로 노드의 이름은 복원 식별자(restoration identifier)라 불리고, 이것은 보통 객체의 클래스명이다.
복원 클래스(restoration class)는 보통 객체의 클래스이다.
그 데이터는 객체의 상태를 갖는다.
예를 들어, 탭 뷰 컨트롤러의 데이터는 현재 어느 탭이 선택되었는지를 포함한다.
앱이 재시작하면 시스템은 저장된 정보로부터 뷰 컨트롤러와 뷰의 트리를 다시 만든다.
각 노드는 마음과 같다.
시스템은 그 노드에 대한 새로운 뷰 컨트롤러를 만들기 위해 복원 클래스를 요청한다.
새로운 노드에는 복원 식별자 배열이 전해진다. 생성된 노드의 식별자와 모든 조상들의 식별자이다. 이 배열에서 첫 번째 식별자는 루트 노드의 식별자이다. 마지막은 재생성된 노드의 식별자이다.
새로운 노드는 상태 데이터가 주어진다. 이 데이터는 NSCoder 의 형태로 온다.
-
기본적으로 앱에서 상태 복원은 비활성화돼 있다.
복원을 사용하려면 AppDelegate 에 다음을 구현해주어야 한다.
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder{
return YES;
}
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder{
return YES;
}
-
앱의 상태가 저장될 때 윈도우의 rootViewController 에 restorationIdentifier 를 요청한다.
루트 뷰 컨트롤러가 restorationIdentifier 를 가지고 있다면 상태를 저장(또는 인코딩)하도록 요청받는다.
그리고 나서 같은 방식으로 자식 뷰 컨트롤러의 처리를 책임진다.
그리고 차례차례 그들의 자식 뷰 컨트롤러에 똑같이 처리하도록 책임을 넘긴다.
이 계층 구조상의 어떤 뷰 컨트롤러가 restorationIdentifier 를 가지고 있지 않다면 그것은(자식 뷰 컨트롤러가 restorationIdentifiers 를 가지고 있든 없든 간에 자식 뷰 컨트롤러도 함께) 저장 상태에서 제외된다.
-
아래와 같이 식별자 세팅이 가능하다.
navController.restorationIdentifier = NSStringFromClass([navController class]);
self.restorationIdentifier = NSStringFromClass([self class]);
self.restorationClass = [self class];
-
복원 클래스를 설정해주지 않으면, 앱 델리게이트가 이들 뷰 컨트롤러의 새로운 인스턴스를 만들도록 요청받을 것이다.
-
상태복원을 사용하는 경우에는, application:willFinishLaunchingWithOptions: 메소드에서 상태 복원이 시작되기 전에window를 설정하는 등의 해야 할 것들을 처리해야 한다.
-
restorationClass 를 지정한 UIViewController 는 header 파일에 UIViewControllerRestoration protocol 을 따르도록 하고,
구현 클래스에서 필수메소드인 아래 method 를 구현해주어야 한다.
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)path coder:(NSCoder *)coder{
return [[self alloc] init];
}
-
복원 식별자 경로는 뷰 컨트롤러의 상태가 저장되는 순산의 뷰 컨트롤러와 그 조상들을 나타내는 복원 식별자의 배열이다.
그것이 복원 경로를 보여준다.
모달로 표시되는 ViewController 의 복원 경로는 조금 이상하게 보일 수 있다.
UINavigationController 에 포함된 ViewController 가 다른 ViewController를 모달로 띄울 때, 띄우는 ViewController 가 아닌 그 부모에 해당하는 UINavigationController 가 해당 모달을 띄우는 것이다.
따라서 띄우는 ViewController 는 식별자 경로에 없다.
ex) UINavigationController / BNRItemsViewController
-
복원될 뷰 컨트롤러가 복원 클래스를 갖고 있지 않으면 대신에 앱 델리게이트에 해당 뷰 컨트롤러가 요청된다.
- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)path coder:(NSCoder *)coder{
// 요청받은 ViewController 를 init 해서 return 해준다.
}
-
다른 관련 정보를 유지하기 위해 UIViewController 에 아카이빙과 매우 유사한 방식으로 관련 데이터를 인코딩할 기회가 주어진다.
실제로 두 인코딩 방식은 NSCoder 객체를 사용하여 작동한다.
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder{
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder{
}
-
뷰 컨트롤러가 복원 식별자를 가질 뿐만 아니라 UIView 하위 클래스도 뷰에 관한 정보를 확실히 저장하기 위해 복원 식별자를 가질 수 있다.
특히 UICollectionView, UIImageView, UIScrollView, UITableView, UITextField, UITextView, UIWebView 와 같은 하위 클래스들은 그것들의 정보를 저장할 수 있다.
이들 클래스 각각의 문서에는 어떤 정보가 보존되는지 서술돼 있다.
UITableView 의 저장되는 유용한 정보는 테이블뷰의 콘텐츠 오프셋(스크롤 위치)이다.
self.tableView.restorationIdentifier = @“BNRItemsViewControllerTableView”; // that's all !
-
UIDataSourceModelAssociation 프로토콜을 TableViewController 에 설정하면 아래의 메소드들을 구현할 수 있다.
- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)path inView:(UIView *)view{
}
- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view{
}
위의 메소드들을 구현하면 적절한 모델 객체를 위치, 선택시켜 상태 복원을 도울 수 있다.
앱의 상태가 저장될 때 선택된 행 또는 행들과 관련된 모델 객체의 식별자를 요청한다.
앱이 다시 시작되면 상태 복원 시스템은 그 식별자를 제공하고 그 모델 객체의 인덱스 패스를 요청한다.
재시작 시에 모델 객체는 UITableView 에서 위치가 바뀔지도 모른다.
하지만 매핑이 제대로 되는 한 해당하는 행이 선택된다.
-
시스템은 앱이 백그라운드 모드로 들어가면 앱의 스냅샷을 찍는다.
때때로 재시작 시에 사용자에게 보여줄 것(또한 멀티태스킹 화면에서 앱을 볼 때 사용자에게 보여줄 것)을 제어하길 원할 것이다.
예를 들어, 앱이 민감한 정보를 보여준다면(은행 앱이 계좌번호와 잔고를 보여주는 것처럼) 허가받지 않은 이에게 보이지 않도록 이 정보를 숨기길 원할 것이다.
-
스냅샷을 수정하는 것은 쉽다.
스냅샷이 찍히기 전에 자신이 원하는 화면이 찍히도록 뷰를 갱신한다.
-
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(applicationResigningActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
[nc addObserver:self
selector:@selector(applicationBecameActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
- (void)applicationResigningActive:(NSNotification *)note{
// 스냅샷 찍히기 전 뷰를 수정
}
- (void)applicationBecameActive:(NSNotification *)note{
// 앱으로 돌아오기 직전이라, 스냅샷용으로 변경된 뷰를 되돌린다.
}
-
아래 함수를 불러주면 다음 번 실행 시에 스냅샷을 무시하도록 한다.
[[UIApplication mainApplication] ignoreSnapshotOnNextApplicationLaunch];
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[iOS] keyboard type 바꾸기 (0) | 2016.09.19 |
---|---|
[iOS Study] NSUserDefaults (2) | 2016.03.27 |
[iOS Study] 코어 데이터 (0) | 2016.03.25 |
[iOS Study] 지역화 (0) | 2016.03.17 |
[iOS Study] UISplitViewController (0) | 2016.03.16 |
댓글