[iOS] Push Notification – 앱 삭제 여부 판별이 가능한가?

‘앱이 Push Notification(푸쉬 알림)을 정상적으로 수신 가능한지 서버에서 판별할 수 있을까?’ 란 의문이 들었다. (‘앱에서 푸쉬 알림을 정상적으로 수신했느냐?’ 와는 다름)

개발 서버에서 APNS (Apple Push Notification Service)에 푸쉬 알림을 신청하기 전(또는 직후)에 디바이스로의 전달여부를 파악할 수 있다면, 실패시 사용자에게 다른 액션을 취할 수 있을 것이다.

APNS가 전송을 100% 보장하지 않음을 알고 있었지만, 혹시나 하는 맘에 iOS Developer Library를 다시 한번 들춰 보았다.

검토한 결론은 아래와 같다.

  • 앱이 삭제된 경우는 확인할 수 있다. (Feedback 서비스)
  • 앱에서 Push Notification 수신을 허가한 후, 사용자가 설정에서 알림 받기 거부로 설정한 경우는.. 명확치 않다. (Feedback 서비스를 통해 식별 가능한지 테스트가 필요하다. 안될 것 같긴 하다. )
  • 실제 디바이스에 푸쉬 알림이 도착했는지에 대한 여부는 알 수 없다.
  • 디바이스가 장시간 꺼져 있거나 잠시 꺼져있는 동안 여러 개의 푸쉬 알림이 온 경우,  알림이 유실될 수 있다.

이와 관련한 iOS Developer Library 문서 내용을 이번 포스팅에서 정리한다. (서버를 이용한 실제 테스트는 차후 해볼 예정이다.)

Push Notification

기본적으로 Push Notification 은 단방향 서비스이다. 개발 서버에서 APNS 에 푸쉬 알림을 요청하면 APNS가 알아서 디바이스에 전송한다. 개발 서버는 APNS에 요청하면 할 일은 끝이다.

 

< from iOS Developer Library >

개발 서버는 APNS 에 푸쉬 알림을 요청할 때, 데이터 format 등에 대한 정상 여부는 리턴 받지만 실제 도착여부에 대해서는 리턴을 받지 못한다.

앞에서도 언급했듯이 APNS 는 100% 전송을 보장하지 않는다. 개발문서에서 기술하고 있는 유실 케이스는 디바이스 전원이 꺼져있는 경우다. APNS의 QoS (Quality of Service) 컴포넌트는 디바이스 전원이 꺼져있는 경우, Notification을 잠시 저장했다가 다시 사용 가능해질 때 Notification이 전달되도록 하지만, 다음의 경우 저장된 Notification이 폐기된다.

  • 잠시 꺼져 있는 경우(얼마나 잠시인지 명시되지 않았음) , APNS는 Notification을 저장을 하는데 저장 가능한 Notificaiton은 하나다. 디바이스가 꺼졌있는 동안 하나의 앱에서 여러 개의 푸쉬 알림을 보낸다면, 제일 나중에 보내진 Notification만 저장되어 전달된다. 이전에 저장된 Notification 은 폐기된다.
  • 장시간 꺼져 있는 경우 (장시간이 얼마나인지는 명시되지 않았음), 저장된 Notification은 폐기된다.

개인적으로 아이폰 베터리가 다되어 충전 후 다시 켰을 때, 하나의 앱에서 여러 개의 푸쉬가 전달되는 경험을 한 것 같다. 애플 내부에서 개선이 되고 있는데 여전히 100% 장담이 안되어 개발 문서를 업데이트 하지 않은 건지, 아니면 본인의 착각인지 이것도 테스트 해봐야 겠다.

Feedback Service

The feedback service gives providers information about notifications that could not be delivered—for example, because the target app is no longer installed on that device. For more information, see “The Feedback Service.”

위 개발 문서의 내용과 같이 Feedback 서비스는 Notification 전달 실패 정보를 개발 서버에 알려주기 위한 서비스다. APNS는 Feedback 서비스를 이용한다. (Feedback 서비스를 통해 불필요한 푸쉬 요청을 피하고 전반적인 Performance 향상을 얻을 수 있다.)

푸쉬 알림이 앱이 삭제된 이유로 전달되지 못했다면 , APNS 는 이를 Feedback 서비스에 알리고 Feedback 서비스는 실패한 디바이스의 토큰을 리스트에 저장한다. APNS 에서 푸쉬 알림을 디바이스로 전송하기 전에 실패한 – data format error 등  – 경우에는 Feedback 서비스의 리스트에 등록되지 않는다. (APNS는 실제로 디바이스에 Notification을 전달해보고 앱의 삭제 여부를 파악하는 듯하다.)

몇 번이고 확인해봤지만, 사용자가 Push 접근을 허락한 이후 설정에서 이 기능을 disabled 한 케이스에 대해선 언급이 없다. 실제로 ‘알림센터에서 보기’를 끄더라도 알림 타입이 ‘None’이 아닌 다른 것 – 배너 등- 으로 선택되어 있다면 푸쉬 알림을 받는다. 앱의 푸쉬 알림에 대한 모든 설정을 전부 Off 로 해놔야 특정 앱에 대한 푸쉬 알림을 받지 못하는데, 이 경우에 Feedback 리스트에 등재가 될지 테스트를 해봐야겠다.

개발 서버는 APNS 연결과는 별개로 Feedback 서비스에 접속해서 디바이스 토큰 리스트를 요청해야 한다. Feedback 서비스는 리스트가 요청될 때마다 리스트를 전달한 후 리스트를 초기화(clear) 한다.

Feedback 서비스로부터 받은 리스트의 데이터에는 timestamp 정보가 포함되어 있다. 이를 이용하여 리스트에 있는 디바이스 토큰이 Push Service에 재등록되어 있는지를 확인할 수 있다. (푸쉬 알림을 보낸 시각과 디바이스 토큰의 timestamp 를 비교하면 디바이스 토큰의 재등록 여부를 확인할 수 있을 것 같다.)

< Binary format of a feedback, from iOS Developer Library >

Feedback 서비스로의 요청을 위한 url, port 및 format 등의 내용은 The Feedback Service를 참고하자.

개발 문서에는 적어도 하루에 한번 Feedback 서비스를 확인하라고 나왔는데, 서비스마다 Push Notification 이용처가 다르니 필요에 따라/용도에 맞게 확인 주기를 결정해야 할 것 같다.

삭제된 앱이 삭제되기 전 디바이스에 남은 최후의 push-enabled 앱이였다면, 앱이 삭제되는 순간 디바이스와 push service 간의 커넥션이 끊겨버려 Feedback 서비스에 추가되지 않는다고 한다. (아마 개발폰일 경우에나 간혹 발생할 일일 것 같다.)

마무으리

몰랐던 내용을 알아서 좋긴 한데, 찾고 있던 답은 아닌 것 같다. 좀 더 고민해보고 테스트해서 결론을 정리하자.

* Reference

 

Advertisements

iOS App States

iOS 앱 개발시 경우 – 네트워크를 통한 다운로드/업로드, Music Player 등 – 에 따라 앱이 Background  – 앱 실행 중 홈버튼을 눌렀을 때와 같은 경우 – 에 있을 때도 코드를 수행시켜야 할 필요가 있다. iOS 4.0 부터는 Multitasking를 지원하여 Background 에서도 코드를 수행시킬 수 있다. 이 기능을 알아보기 전에  iOS 앱이 가지는 5가지 상태에 대해 알아보자.

참고로, iOS 3.2 이전 버전에서는  앱은 Background, Suspended 상태를 갖지 못한다. (iOS 4 라 할지라도 특정 기기 – 2009년 중반 이전의 아이폰, 아이팟 터치 – 들도 그렇다. ) 이러한 디바이스에서 동작하는 앱은 Foreground 를 벗어나면 종료된다. 따라서 Background 에서 수행되는 코드를 개발할 때, 디바이스의 Background 지원 여부를 확인할 필요가 있다. (참고 : Background Execution and Multitasking)

State Changes of iOS App < from iOS Developer Library >

iOS App 은 항상 아래의 5가지 중 하나의 상태를 가진다.

  • Not Running : 앱이 실행되기 전 상태, 또는 실행이 되었지만 시스템에 의해 종료된 상태
  • Inactive : Foreground 로 진입은 했으나 이벤트는 아직 받지 못한 상태. iOS 앱은 다른 두 상태 사이에서 상태변환을 할 때 중간  단계로  짧은 시간동안 InActive 에 머물게 된다.
  • Active :  앱이 화면에 보이고 이벤트도 받으며 실행되는 정상 상태. 일반적인 앱의 실행상태이다.
  • Background : 대부분의 앱은 홈버튼을 눌렀을 때 Suspended  상태가 되기 전 잠시 Background 상태에 머문다. 이 상태에서 앱은 일정 시간 일부 코드를 수행할 수 있지만 사용자의 이벤트를 받을 수는 없다. Background에서 추가적인 코드를 수행할 필요가 있는 경우 Background 상태로 남아 일정 시간동안 코드를 수행할 수 있다. 앱이 실행되자마자 Background 로 진입하는 앱은 Inactive 상태를 거치지 않고 바로 Background  상태로 진입한다.
  • Suspended : 앱 실행이 중단된 상태. Suspended 상태에서는 앱은 메모리에 존재하지만 아무런 코드를 수행하지 않는다. System은 앱을 아무런 통보 없이 Background상태에서 자동으로 Suspended 상태로 바꿀 수 있다. 메모리 부족현상이 발생하면 System은 별다른 통보 없이 Suspended 상태의 앱을 종료할 수 있다.

UIApplication Delegate 는 이러한 상태 전이에 반응할 수 있도록 메소드를 정의해 놓고 있다.

  • applicationDidBecomeActive : Active 상태로 전이 될 때 호출된다. Background 앱이 다시 호출되어 Active 상태가 되도 호출된다.
  • applicationWillResignActive : 앱이 Active 상태를 벗어날 것을 알려준다.(ex. 홈 버튼을 누르면 호출됨) 단, 앱이 Background 상태로 전이된다는 보장은 없다. 이 상황에 잠깐 머물다가 다시 바로 Active 상태로 바뀔 수도 있다.
  • applicationDidEnterBackground : Background로 진입. 언제든 System에 의해 Suspended 상태로 전이될 수 있다. 나중에 재생성할 수 있는 모든 리소스들을 제거하고 사용자 데이터를 저장한는 등의 작업을 수행하는 용도로 사용될 수 있다. 이 메소드 호출 이전에 applicationWillResignActive가 호출된다.
  • applicationWillEnterForeground : Background를 벗어나 Foreground로 진입될 것을 알려준다.(아직 Active 상태는 아님) applicationDidEnterBackground 에서 해제한 리소드들을 재생성하는 용도로 사용할 수 있다.이 메소드 호출 뒤에 applicationDidBecomeActive 가 호출된다.
  • applicationWillTerminate : 앱이 종료될 것임을 알려준다. 앱이 Suspended  상태에서 종료되는 것이라면 호출되지 않는다.

[iOS]2계층 UIViewController의 modalViewController 동시에 없애기(dismiss)

위 그림과 같은 상황을 가정해보자. (유저인터페이스가 적합한지는 별개의 문제다. 일단 가정하자.)

로그인뷰(Login View Controller)에서 사용자의 계정을 확인 후, Navigation Controller 를 Modal View 로서 화면에 나타낸다. Navigation Controller 의 설정 버튼을 통해 설정 화면(Settings View Controller)이 Modal View 로 나타나고 이곳에서 특정 이벤트(사용자 계정 log out )가 발생하면, 최초의 로그인 뷰로 돌아간다.

이 경우,  설정화면의 이벤트(ex. log out)에 의해 설정화면(Settings View Controller)과 Navigation Controller 가 함께 사라지는 효과를 구현해야 한다. 어떻게 하면 될까?

쉽게 생각할 수 있는 방은, 먼저 Settings View Controller 에서 dismissModalViewController 를 호출하고,  순차적으로 delegate 를 이용하여 Navigation Controller 에서 dismissModalViewController 를 호출하는 것이다. (참고로 iOS5 이후부터는 presentModalViewController 및 dismissModalViewController 는 deprecated 된다. 대안으로 presentViewController, dismissViewController 를 사용할 것을 권장한다. 정확한 메소드 사용법은 UIViewController Class Reference참조하라.)

이 방법이 제대로 동작할까? 실제로 해보면 쉽게 정상적으로 동작하지 않는 것을 확인할 수 있다. (본인의 개발환경은 OS X 10.6.8, iOS 4.3, XCode 3.2.6 이다.)

먼저 간단하게 위에서 언급한 방법을 구현한 코드를 보자.

Settings View Controller 내부 메소드

<pre>
// settings View controller 에서 사용자 이벤트가 전될되는 메소드
-(IBAction)didPressDone:(id)sender{</pre>
[self.parentViewController dismissModalViewControllerAnimated:YES]; // dismiss SettingsViewController
[delegate userAccountIsDisconnected]; // delegate 를 통하여 Navigation Controller 측에 이벤트를 알림

}
<pre>

코멘트에서와 같이 먼저 Settings View Controller 자신에 dismissModalViewController 를 적용하고, delegate 를 통하여 Navigation Controller 측에 이벤트를 전달한다. (여기서 delegate 사용법은 다루지 않겠다. 생각보다 어렵지 않고 간단하다.)

delegate 메소드가 구현된 Navigation Controller 측 내부 메소드

<pre>
// delegate 메소드
-(void)userAccountIsDisconnected{</pre>
NSLog(@"delegate method");
[self.naviController.parentViewController dismissModalViewControllerAnimated:YES]; //dismiss Navigation Controller

}
<pre>

위 코드는 Navigation Controller 를 dismiss 하여 로그인뷰를 나타나게 하는 것이 목적이다.

실제 위와 같은 코드로 동작을 시켜보면, Settings View Controller  는 정상적으로 사라지지만, Navigation Controller 화면은 사라지지 않아 로그인뷰가 화면에 나타나지 않는다.

디버그를 해보면 다음과 같은 에러메시지를 확인할 수 있다.

attempt to dismiss modal view controller whose view does not currently appear”

Settings View Controller 를 없애는 작업 (dismiss settings view controller)가 완전히 완료되지 않은 시점에서, 다른 뷰를 없애는 작업(dismiss navigation controller)를 하려고 하니 문제가 발생하는 것이다.

Settings View Controller 를 없애는 작업이 완전히 완료가 되고 화면이 안정적으로 구조화 된 후에 Navigation Controller 를 없앤다면 문제가 발생하지 않겠지만, 이는 화면에 별도의 이벤트(버튼)가 필요할 수도 있으므로 우리가 바라는 UI는 아니다.

그럼 해결책은 뭘까? 여러가지 방법이 있겠지만, Settings View Controller 를 없애는 작업을 수행하지 않은 채 바로 Navigation Controller 를 없애는 것이 하나의 해결책이다. 이렇게 되도 정상적으로 Settings View Controller 는 사라지고 메모리에서도 dealloc 된다.

하위층의 View Controller를 사라지게(dismiss)하면, 그 상위의 뷰들도 자연히 사라지는 듯 하다.

코드는 다음과 같이 Settings View Controller 에서의 설정뷰 자신을 없애는 부분을 생략하고 사용자 이벤트를 바로 Navigation Controller 측에 넘겨주면 된다.

<pre>
// settings View controller 에서 사용자 이벤트가 전될되는 메소드
-(IBAction)didPressDone:(id)sender{</pre>
//[self.parentViewController dismissModalViewControllerAnimated:YES]; // dismiss SettingsViewController
[delegate userAccountIsDisconnected]; // delegate 를 통하여 Navigation Controller 측에 이벤트를 알림

}
<pre>

실제 해결책은 생각보다 간단하였지만  UIViewController 의 계층 구조 및 라이프 사이클 등에 대해 좀 더 정확하게 파악할 필요가 있다.

iOS 앱 컴파일 디렉토리 경로 (iOS Simulator)

iOS Simulator 를 이용하여 Mac OS X 상에서 iOS 앱 개발시, 때에 따라 -파일 접근 등- 앱이 구동되는 디렉토리를 Finder 를 통해 접근할 필요가 있다. 경로는 다음과 같다. (iOS 4.3, Xcode 3.2.6, Mac OS X 10.6.8 기준 )

/Users/본인피씨이름/Library/Application Support/iPhone Simulator/iOS개발버전/Applications/개발프로젝트ID

개발프로젝트ID 는 본인이 지정한 프로젝트 명이 아닌, 문자/숫자로 구성된 일련의 key 로 폴더가 생성된다.

* Update : OS X Lion, Xcode 4.3 에는 User 폴더 및에 Library 폴더가 Finder에서 보이지 않는다. (숨겨져 있다.) 터미널을 이용하여 숨겨진 파일을 보이게 하면 위와 동일하게  iOS Simulator 의 디렉토리에 접근 가능하다.

터미널을 시작시키고 아래 명령어를 입력하자.

defaults write com.apple.finder AppleShowAllFiles -bool true
killall Finder

명령어를 실행시키고 Finder 를 열면, 그동안 숨겨져 있던 폴더/파일들이 흐릿한 색으로 표시된다.

추가로 Xcode 4.3 에서 프로젝트 빌드 경로를 확인/변경할 수 있다.  이 폴더 역시 숨겨져 있으나 Xcode 를 통해 접근 가능하다. 위 터미널 메시지를 통해 숨겨진 파일 보기 설정이 되어 있으면 Finder 에서도 접근 가능하다.

Xcode -> Preference -> Locations 을 열자.

Derived Data 섹션을 보면 디폴트로 지정된 경로가 보인다. 경로를 나타내는 텍스트 우측의 화살표를 클릭하면 폴더로 이동한다.(아래 그림 참조).  콤보박스를 이용하면 경로 변경도 가능하다.

 

%d bloggers like this: