"App Life Cycle"

앱의 실행부터 종료까지의 주기이며,

- 앱이 화면에 보이고 있는 foreground (Active)

- 앱이 화면에 보이지 않고 실행되고 있는 background

- 앱이 실행 중이지만, 아무런 이벤트가 없는 InActive (ex. 전화)

- 앱이 background에 있지만, 실행되는 코드가 없는 상태인 Suspend

    - 메모리가 부족한 상황이 오면 이 상태에 있는 앱들을 특별한 알림 없이 정리한다.

- 실행되지 않은 상태인 Not Running 

이 있다.

-> Active와 InActive를 합쳐서 Foreground 상태라고 한다.

 

상태 변화

iOS는 시스템에서 발생하는 특정 상황에 맞게 앱의 상태를 변화시키고 제어한다.

ex) 우리가 카카오톡을 사용하다가, 친구에게 전화가 오면 카톡이 꺼지고 '전화 앱'이 켜지는 상황을 보셨을 것이다.

이는 iOS가 '전화'라는 상황에 맞게 앱의 상태를 변경한 것이다.

 

이처럼 iOS는 각각의 앱마다 상태 변화를 제어하여 어떤 앱은 실행시키고, 어떤 앱은 홈 화면으로, 종료를 하는 등 다양한 처리를 한다.

 

앱 상태에 따른 메소드 ( ~ iOS 12 )

앱의 상태가 변화할 때마다 앱 객체는 AppDelegate에 정의된 특정 메소드를 호출한다.

AppDelegate는 앱의 모든 Window를 관리한다.

 

  • application(_:willFinishLaunchingWithOptions:)
    • 앱이 구동되어 필요한 초기 실행 과정이 완료되기 직전에 호출되는 메소드
  • application(_:didFinishLaunchingWithOptions:)
    • 앱이 사용자에게 화면으로 표시되기 직전에 호출되는 메소드
    • 앱이 실행된 후에 진행할 초기화 작업들을 주로 수행
  • applicationDidBecomeActive(_:)
    • 실행된 앱이 화면 전면에 표시될 때 호출되는 메소드
    • 앱이 InActive 상태에 들어가면서 일시 중지된 작업이 있다면, 재시작 코드를 여기서 작성해야 한다.
    • ex) 타이머 앱의 경우, InActive 상태로 들어가면 더이상 화면 갱신이 이뤄지지 않기 때문에, 이 메소드에서 갱신해야 함
  • applicationDidEnterBackground(_:)
    • 앱이 Background 상태에 진입했을 때 호출되는 메소드
    • 이 메소드가 호출되면 미래에 앱이 종료될 가능성이 있다는 뜻이므로, 사용자의 데이터를 미리 저장하거나, 공유 자원을 해제해야 함
  • applicationWillTerminate(_:)
    • 앱이 종료되기 직전에 호출되는 메소드

 

앱 상태에 따른 메소드 ( iOS 13 ~ )

Scene을 iOS 13부터 지원하게 되면서 iOS 12까지의 AppDelegate와는 다르다.

같은 App을 동시에 여러개 켰다..!

각각의 App의 화면을 Scene이라고 일컫는다.

따라서 iOS 13부터는 Scene을 지원하게 되면서, 하나의 App을 여러개(다수의 인스턴스 / Scene)를 사용할 수 있는 것이다.

 

이전까지의 AppDelegate에서는 하나의 Scene만을 관리하게 설계되어 있었다.

하지만 위와 같은 iPad의 경우엔, 상황에 따라 여러 Scene을 허용해야 하는 경우가 있을 것이다. 그렇다면 여러 Scene을 AppDelegate에서 동시에 관리할 수 없지 않은가?

따라서 iOS 13부터는 SceneDelegate를 통해 Scene을 관리할 수 있다.

 

 

AppDelegate(~ iOS 12) vs SceneDelegate(iOS 13 ~)

 

12까지의 AppDelegate의 역할은 크게 2가지 였습니다.

Process LifeCycle, UI LifeCycle

 

 

13부터는 UI LifeCycle은 SceneDelegate가 처리합니다.

각각의 Scene마다 현재 가장 상단에 있는 VC, 내부에 UI Components가 각각의 상황에 따라 다를 것이다.

-> 그렇지 않으면 Scene을 사용할 의미가 없다고 생각함...

 

(이후에SceneDelegate에 대해서 좀 더 알아보도록 하겠다.)

 

 

ViewController의 수명주기 보러 가기

Animation

UIView.animate(...) 를 사용해서 애니메이팅을 구현할 수 있다.

 

animation(애니메이션) 사전적 의미

동작이나 모양이 조금씩 다른 많은 그림이나 인형을 한 장면씩 촬영하여 영사하였을 때에 화상이 연속하여 움직이는 것처럼 보이게 하는 것

어렸을 때, 책 가장자리에 조금조금씩 움직임을 표현하여 100p를 그린 뒤, 책을 빠르게 넘겨 촤르르르륵~ 하는 것이 애니메이션이었다..!

 

iOS에서의 Animation

Changes to several view properties can be animated—that is, changing the property creates an animation starting at the current value and ending at the new value that you specify. The following properties of the 
UIView
 class are animatable:

뷰의 여러 속성을 변경하여 애니메이팅을 할 수 있다. 즉, 속성을 변경하는 것은 현재 값에서 새로운 값으로의 애니메이션을 생성한다.

볼드체가 핵심이다..! 위의 애니메이션 정의만 생각하면 View를 조금조금씩 그려 촤라라라락하는 것처럼 해야하기 때문에, 직접 자잘한 움직임과 위치, 크기 등의 속성을 일일이 지정해줘야한다.

 

B.U.T.하지만 현재 값에서 새로운 값으로의 애니메이션을 생성한다!

 

애니메이팅할 수 있는 UIView의 속성은 다음과 같다.

 

먼저 Frame값을 변경해보자!!

이러한 화면이 있다고 하자!

 

UIView.animate(withDuration: 2.0) {
	self.playPauseButton.frame = CGRect(x: 0, y: 0, width: self.playPauseButton.frame.size.width, height: self.playPauseButton.frame.size.height)
}

위에서 얘기한대로 난 최종 상태값만을 지정해줬을 뿐인데!! 자연스럽게 이동한다!!!

 

다음으로 backgroundColor를 변경해보자

UIView.animate(withDuration: 2.0) {
	self.view.backgroundColor = .black
}

 

 

이 또한 흰색에서 검은색으로 변경한다고 지정했을 뿐인데! 흰색 -> 회색 -> 검은색 으로 자연스럽게 변환된다.!!

 


  • UIView.animate 를 사용한 애니메이팅은 자동으로 MainQueue(UIQueue)에서 동작한다
    • 따라서 따로 DispatchQueue.main.async를 사용하지 않아도 된다.
  • origin값을 변경하는 것이 아니라, Transform을 변경하는 것이다.
    • Transform.identity를 이용하여 원래값을 얻을 수 있다
  • 애니메이팅이 끝난 후, 동작하는 completionHandler가 존재한다.
    • Animation이 중간에 cancel되는 경우가 있어서 파라미터로 Bool 변수를 받는다.

 

문제 상황

Header(최근 검색어 Label)

Footer(전체삭제, 최근 검색 닫기 Button)

스크롤을 하게 되면 저것들이 따라온다...

 

해결방법

1. StoryBoard or Nib

테이블 뷰의 Inspector에서 Style을 Plain에서 Group으로 변경해준다

 

더이상 따라오지 않는 것을 볼 수 있다

 

해결 상황

 

2. Frame 사용

//Frame은 임시
myTableView = UITableView(frame: .zero, style: .grouped)

 

해결!

방법들

Data.init(contentsOf url: URL)

기본적으로 네트워크를 통해 이미지를 가져오기 위해 사용했던 첫번째 방식이었다.

그 당시, DispatchQueue를 사용하여 UI는 Main Thread에서 변경했지만, 이미지를 가져오기위한 작업은 저 Data(contentsOf:)를 사용했다.

간단한 코드였으며, CollectionView나 TableView처럼 많은 이미지를 가져온다거나 이미지의 크기가 크지 않기에 앱이 죽지 않고 돌아갔지 싶다.

 

쩃든, 네트워크 통신을 통한 파일/이미지를 받을 때는 해당 방법을 사용하면 안된다.

Important
Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the dataTask(with:completionHandler:) method of the URLSession class. See Fetching Website Data into Memory for an example.

첫줄에서도 나와있다시피 이 init은 synchronous하다 -> 동기방식으로 동작한다.

만약, 해당 코드를 Main Thread에서 수행한다면, 응답이 올때까지 Thread는 멈추게된다. (안드에서는 이러한 상황을 ANR이라 했던 것 같은데, iOS에서는 어떻게 부르는지 모르겠다)

따라서 아래에 적혀있는 것처럼 URLSession Class에 있는 dataTask 메소드 사용을 권장한다.

이미지를 다운로드하는 상황에는 downloadTask도 있다.

 

URLSession - downloadTask

  • HTTP / HTTPS 를 통해 데이터를 주고받기 위해 API를 제공해주는 클래스

Session 과 Task 등 자세한 이야기는 이후의 포스팅에서 다루겠다.

 

downloadTask

서버로부터 데이터를 다운로드 받아서 파일의 형태로 저장한다.

 

1. URL을 사용하는 방법 -> URL을 알고 있는 상황에서 사용

2. URLRequest를 사용하는 방법-> Query, HTTP메소드, 헤더 등 설정이 필요한 경우 사용

 

//4개의 메소드가 존재
downloadTask(with url: URL) -> URLSessionDownloadTask

downloadTask(with url: URL, completionHandler: @escaping(URL?, URLResponse?, Error?) -> Void)

downloadTask(with request: URLRequest) -> URLSessionDownloadTask

downloadTask(with request: URLRequest, completionHandler: @escaping(URL?, URLResponse?, Error?) -> Void)

 

URLSession.shared.downloadTask(with: imageURL){ url, response, error in
	.........
    
    if let url = url{
    	let image = UIImage(contentsOfFile: url){
        	DisptchQueue.main.async{
            	......
            }
        }
    }
    
    .........
}.resume()

 

위와 같은 코드로 동작시킬 수 있다.

이미지를 계속해서 불러오는 것은 비효율적일 수 있다. 따라서 Cache(캐시)를 사용할 수 있는데

FileManager를 사용하면 캐시를 구현할 수 있다.

 

Kingfisher (https://github.com/onevcat/Kingfisher)

캐시 전략, 코드의 길이, 사용법 등 번거롭고 귀찮음을 해결하기 위해 나온 라이브러리이다.

 

  • 비동기 이미지 다운로드 및 캐싱을 위해 사용
  • URLSession 기반이기 떄문에, 네크워킹 및 로컬 데이터도 로드 가능
  • 유용한 이미지 프로세서 및 필터가 제공됨
  • 메모리와 디스크의 계층 하이브리드 캐시
  • 성능 향상을 위해 이전에 다운로드한 콘텐츠를 취소 가능, 재사용 가능
  • Indicator
  • SwiftUI 지원
  • GIF 형식 지원

 

//정말 쉬웠다

imgView.kf.setImage(with: imageURL)
//끝...
//비동기로 동작한다

 

오늘 처음 써보며... 오픈소스의 대단함을 새로 느꼈다. 이미지를 불러오는 데에 조금씩 버벅이길래 성능 향상을 위해 Disk에 Cache도 구현하고 CollectionView의 Prefetching도 사용해보았지만 눈에 띄게 향상되진 않았었다.

그런데 KingFisher를 사용하니 눈에 띄게 성능이 향상된 것을 볼 수 있었다. -> 코드를 까보면서 뭐가 다른건지 확인해봐야겠다

 

Processor

이미지를 좀더 유연하게 만들어주는 역할을 한다.

setImage의 매개변수에는 Resource, Placeholder, options, progressBlock, completionHandler 등이 있다.

이중 options에 해당하며 없다면 default를 사용한다고 한다.

 

  • cornerRadius
  • Resizing
  • Blur
  • TintColor
  • Sampling

등의 기능을 제공하는 프로세서들을 사용할 수 있다.

 

또한, 싱글톤 객체를 이용해서 함수를 만들 수도 있다.

func downloadImage(with url: URL){
	let resource = ImageResource(downloadURL: url)
    
    KingfisherManager.shared.retrieveImage(with: resource){ result in
    	
        switch result{
        case .success(let value):
	        print("Image : \(value.image)")
        case .failure(let error):
        	print(error)
        }
    }
}

 

Cache

KingFisher가 알아서 메모리와 디스크에 캐시를 하고 전략 또한 정해져있는 것으로 알고 있다. -> (각 애플리케이션의 구조에 따라 캐시전략이 다를 수도 있는데, 이건 더 알아봐야겠다)

위에서 precessor를 넣을 수 있었던 options에 .cacheMemoryOnly 를 넣어주면 메모리캐싱만 수행하기된다. -> 메모리에만 캐싱하면 빠르긴하겠지만.... 굳이 좋을까...? 궁금증이 든다

캐시는 메소드를 통해 지울 수 있다.

 

 

이후에 보면 좋은 글 : 1consumption.github.io/posts/about-kingfisher(1)/

 

 

+ Recent posts