Preserving and Restoring State

Young Min Sim ·2021년 1월 24일
0

Preserving and Restoring State

  1. Tagging ViewControllers
  2. UI Preservation Process
  3. UI Restoration Process

원문

Preserving and Restoring State 이란?

  • APP 이 중지되기 전(suspended) APP 의 구성(configuration)을 기록하여 이후 APP 이 다시 시작되었을 때 구성이 복원되도록 하는 것

필요한 이유

  • 백그라운드 상태에서 메모리에서 제거 됐다고 기존의 상태가 다 날라가버린다면 이는 사용자의 사용 흐름을 크게 헤칠 수 있다. 따라서 State 를 보존(Preserve) 하고 복원(Restore) 함으로써 사용자는 다른 앱으로 전환했을 때 작업을 잃는 것에 대해 걱정하지 않아도 되고 사용자의 시간을 절약시켜 더 나은 UX를 제공할 수 있다.

  1. Tagging View Controllers(Restoration Identifier 등록)
    → Preservation(보존) & Restoration(복원) 을 위한 공통 작업
  2. UI Preservation Process
  3. UI Restoration Process

1. Tagging View Controllers

VC와 VC의 view 들을 보존(preservation) 하기 위해선 VC의 restorationIdentifier 를 설정해야 함

📌 사용 방법

restorationIdentifier 는 스토리보드 or 코드로 구현할 수 있다.

  • 스토리보드

  • 코드
restorationIdentifier = "ViewController"
  • 테이블뷰 및 콜렉션뷰의 경우 restorationIdentifier 뿐 아니라 (VC에서) UIDataSourceModelAssociation 프로토콜을 채택하여 구현해줘야 한다.
    • 스크롤 위치
    • 어떤 cell 이 selected 된지

네이밍 팁

  • Restoration ID 는 유니크 해야 함

  • 따라서, 일반적으로 클래스 이름과 동일하게 설정되는 것이 권장되지만,

  • 같은 클래스가 여러 번 사용 되는 경우 해당 VC 에서 관리하는 데이터와 관련된 문자열을 설정하면 된다.

    ex) ProductListViewController 처럼 여러 번 재사용되는 VC 의 경우 FreeMovieProductListViewController, Price1000MovieProductListViewController

ViewController 그룹 제외 (Excluding Groups of Views Controllers)

restoration Identifier 를 할당 할 때 주의점은, 부모 VC도 마찬가지로 restoration Identifier 를 가져야 한다는 점이다.

Preservation process(보존 과정) 동안, UIKit 은 Window(UIWindow)의 Root VC 부터 시작하여 VC 계층을 따라 이동한다. 만약 계층 내의 VC가 restoration identifier 를 가지고 있지 않은 경우 그 VC 뿐만 아니라 해당 VC의 모든 Child VC, presented VC 의 Preservation process 또한 진행되지 않는다.


2. UI Preservation Process

원문

2-1. State 를 보존할 것인지 확인

func application(_ application: UIApplication, shouldSaveSecureApplicationState coder: NSCoder) -> Bool {
        true
}
  • (필수) UIKit 이 preservation-process 를 시작하려고 할 때마다 호출되는 메서드.
  • true 를 반환해야 preservation-process 가 시작
func application(_ application: UIApplication, willEncodeRestorableStateWith coder: NSCoder) {
        
}

// Tells your delegate to save any high-level state information
// at the beginning of the state preservation process.

링크

  • (선택 사항) preservation-process 의 시작지점에서 처음 호출되는 메서드
  • 버전 정보, 고수준의 configuration 저장

2-2. 각 VC 의 State Preservation-Process

(선택 사항)

  • Window 의 Root VC 부터 시작하여 VC 계층 순회하며 각 VC에서 encodeRestorableState(with:) 를 수행하여 각 VC의 상태를 보존하는 작업을 진행
override func encodeRestorableState(with coder: NSCoder) {
      super.encodeRestorableState(with: coder)
      
      coder.encode(firstTextField.text, forKey: "firstLabel")
      coder.encode(secondTextField.text, forKey: "secondLabel")
      coder.encode(thirdTextField.text, forKey: "thirdLabel")
      
      if firstTextField.isFirstResponder {
          coder.encode(1, forKey: "EditField")
      } else if secondTextField.isFirstResponder {
          coder.encode(2, forKey: "EditField")
      } else if thirdTextField.isFirstResponder {
          coder.encode(3, forKey: "EditField")
      }
}
    
override func decodeRestorableState(with coder: NSCoder) {
    super.decodeRestorableState(with: coder)
    
    if let text = coder.decodeObject(forKey: "firstLabel") as? String {
        firstTextField.text = text
    }
    if let text = coder.decodeObject(forKey: "secondLabel") as? String {
        secondTextField.text = text
    }
    if let text = coder.decodeObject(forKey: "thirdLabel") as? String {
        thirdTextField.text = text
    }
    
    switch coder.decodeInteger(forKey: "EditField") {
    case 1:
        firstTextField.becomeFirstResponder()
    case 2:
        secondTextField.becomeFirstResponder()
    case 3:
        thirdTextField.becomeFirstResponder()
    default:
        break
    }
}


  • Encode 해야 하는 데이터는 ❓

    • 이전에 작업하던 화면으로 돌아와 작업을 그대로 진행할 수 있는 최소한의 데이터만 Encode 해야 함.
    • 예를 들어 앱의 Persistent storage 에 있는 데이터를 Encode 하기 보다는 해당 데이터에 접근하기 위한 identifier 를 encode 하는 것이 권장됨
  • 만약 보존하고 싶지 않은 VC가 있는 경우 ❓

    1. restorationIdentifier 를 nil 로 하거나,
    2. restoration class 를 할당하고 viewController(withRestorationIdentifierPath:coder:) 에서 nil 을 반환

해당 VC 뿐 아니라 Child, Presented VC 들 모두 Preservation process 를 진행하지 않음

2-3. State 를 Disk 에 작성

3. UI Restoration Process

문서

3-1. State 를 복원할 것인지 확인

func application(_ application: UIApplication, shouldRestoreSecureApplicationState coder: NSCoder) -> Bool {
      true
}
  • Restoration process 를 시작할 때 호출되는 메서드로, true 를 반환해야 Restoration process 가 시작

3-2. ViewController 객체 재생성

  • 4가지 단계의 프로세스
  1. Ask the view controller’s restoration class

    : VC의 restoration class 에게 객체 재생성을 요청한다.

    • restoration class: APP 의 상태를 복원 할 때 VC 를 다시 만드는 클래스
    • 사용법
    class PresentedViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            restorationIdentifier = String(describing: PresentedViewController.self)
            restorationClass = PresentedViewController.self
        }
        
        override func encodeRestorableState(with coder: NSCoder) {
            super.encodeRestorableState(with: coder)
            print("presented", #function)
        }
        
        override func decodeRestorableState(with coder: NSCoder) {
            super.decodeRestorableState(with: coder)
            print("presented", #function)
        }
    }
    
    extension PresentedViewController: UIViewControllerRestoration {
        static func viewController(withRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? { // 코드로 하는 경우 필요
            let vc = PresentedViewController()
            
            vc.view.backgroundColor = .white // restoration class 에서 이상하게 view background가 black이 됨
            
            return vc
        }
    }
  2. Ask the app delegate

restoration class 를 설정하지 않는 경우 AppDelegate 로 Restoration process 가 위임됨

func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
        switch identifierComponents.last {
        
        case "ViewController":
            return ViewController() // 2번 app delegate(실행O)
        case "PresentedViewController":
            return PresentedViewController() // 1번 restoration class(실행X)
        case "SecondViewController":
            return SecondViewController() // 2번 app delegate(실행O)
        default:
            return nil // 3,4번 tabbarcontroller(실행O 지만, 3/4번으로 감)
        }
    }

1번(restorationClass), 2번(AppDelegate) 사용 이유 ❓

  • Before state restoration even begins, UIKit loads your app’s default view controllers from your storyboards. Because UIKit loads these view controllers for free, it is better not to create them using a restoration class or your app delegate. For all other view controllers, assign a restoration class only if the view controller is not defined in your storyboards. You might also assign a restoration class to prevent the creation of your view controller in specific circumstances.

    1. VC 를 코드로 구현하는 경우

      → 스토리보드로 구현하는 경우에는 UIKit 이 Restoration Process 가 시작되기 전에 스토리보드에서 VC를 로드하기 때문에 스토리보드를 사용하는 경우 restoration class 나 app delegate 를 통해 VC를 다시 로드하는 것은 오히려 비효율적
      즉, 코드로 구현한 경우 1,2 단계, 스토리보드로 구현한 경우 3,4 단계 방법 사용

    2. 특정 VC에 대해 restoration-process을 하고 싶지 않은 경우(restoration class 만 해당)
      → static func viewController(withRestorationIdentifierPath ) 에서 nil 반환


restoration class(1번) vs AppDelegate(2번)

  • 모든 VC 에 대한 생성을 담당하게 되면 AppDelegate 가 커질 수 있으므로
  • 만약 코드로 구현하여 1번(restoration class) 혹은 2번(App Delegate) 방법을 꼭 써야 하는 경우라면 각 VC에 restoration class 를 구현하는게 나음

주의할 점

  • 1, 2번 방법을 이용하여 VC를 반환하면 스토리보드를 통해 인스턴스화를 하는 것이
  • 아니기 때문에 awakeFromNib가 호출되지 않는다. 따라서 IBOutlet에 접근 시 crash 발생
    → 따라서 스토리보드로 구현한 경우 위 메서드에서 nil 을 반환하여 3,4번 방법을 사용해야 함

(3, 4 단계는 UIKit 에 의해 자동으로 VC 객체가 생성됨)

  1. Check for an existing object

: UIKit 은 정확히 같은 restoration path 에 있는 이미 생성된 VC를 찾는다.

  1. Instantiate the view controller from your storyboards.

: 여전히 VC를 찾지 못한 경우, UIKit 은 자동으로 스토리보드로부터 VC를 인스턴스화 한다.

3-3. 각 VC 의 State Restoration-Process

  • restoration Identifier 가 할당된 각 VC 객체의 상태를 decode 하여 restore
override func encodeRestorableState(with coder: NSCoder) {
    super.encodeRestorableState(with: coder)
    
    coder.encode(firstTextField.text, forKey: "firstLabel")
    coder.encode(secondTextField.text, forKey: "secondLabel")
    coder.encode(thirdTextField.text, forKey: "thirdLabel")
    
    if firstTextField.isFirstResponder {
        coder.encode(1, forKey: "EditField")
    } else if secondTextField.isFirstResponder {
        coder.encode(2, forKey: "EditField")
    } else if thirdTextField.isFirstResponder {
        coder.encode(3, forKey: "EditField")
    }
}

override func decodeRestorableState(with coder: NSCoder) {
    super.decodeRestorableState(with: coder)
    
    if let text = coder.decodeObject(forKey: "firstLabel") as? String {
        firstTextField.text = text
    }
    if let text = coder.decodeObject(forKey: "secondLabel") as? String {
        secondTextField.text = text
    }
    if let text = coder.decodeObject(forKey: "thirdLabel") as? String {
        thirdTextField.text = text
    }
    
    switch coder.decodeInteger(forKey: "EditField") {
    case 1:
        firstTextField.becomeFirstResponder()
    case 2:
        secondTextField.becomeFirstResponder()
    case 3:
        thirdTextField.becomeFirstResponder()
    default:
        break
    }
}
  • 테이블뷰, 컬렉션뷰의 경우 해당 View에 restorationIdentifier 뿐 아니라 해당 VC에서 UIDataSourceModelAssociation 를 채택해주어야 함
  • 스크롤 위치, 선택된 Cell 정보
extension SecondViewController: UIDataSourceModelAssociation {
    func modelIdentifierForElement(at idx: IndexPath, in view: UIView) -> String? {
        return "\(idx.row)"
    }
    
    func indexPathForElement(withModelIdentifier identifier: String, in view: UIView) -> IndexPath? {
        guard let id = Int(identifier) else { return nil }
        return IndexPath(row: id, section: 0)
    }
}
func application(_ application: UIApplication, didDecodeRestorableStateWith coder: NSCoder) {
       
}
  • (선택 사항) 모든 복원 작업이 끝나고 수행되는 마지막 decode 작업
  • restoration process 에서 Encode 했던 고수준의 정보 Decode

4. 기타

  • State Preservation 에는 2가지 접근법이 존재 문서링크

    1. (iOS 13 이상) NSUserActivity object 를 이용하여 각 window scene의 state 를 저장
    2. (iOS 12 이하) VC 의 configuration 을 저장하고 복원하는 식으로 state 저장
  • iOS12 이하의 VC based state restoration 사용하기 위해 SceneDelegate 를 삭제함

0개의 댓글

관련 채용 정보