Category Archives: iOS/Swift

[iPhone/Java] 내가 만든 어플에 Push Notification 적용하기

사용자 삽입 이미지
오늘은 애플사에서 제공하는 Apple Push Notification Service 줄여서 APNS를 사용해서 나의 어플리케이션이 메시지를 푸싱하는것에 대해 간단하게 정리해 보겠습니다. 애플에서 제공하는 푸시의 경우 위의 프리젠테이션에서 볼 수 있듯이 써드파티의 서버에서 APNS서버로 메시지를 보내면 APNS에서 해당 디바이스에 직접적으로 메시지를 전달해주는 일을 합니다. 실제로 테스트 해본 결과 단 몇초안에 메시지가 전달이 되더군요. 와우!

1. 인증서 준비

사용자 삽입 이미지
위의 화면은 개발자 사이트의 Provisioning Portal – App IDs 에 들어가면 볼 수 있는 화면입니다. 두번째 부분이 Push Notification에 대한 설정인데요. 첫번째 App ID의 경우에는 현재 APNS설정을 할 수 있는 상태입니다. 그리고 두번쨰는 아예 설정이 불가능한 경우입니다. 이 경우는 App ID의 Identifier에 *가 들어간 경우에 해당됩니다. 꼭 어플과 App ID가 1:1매치가 되어야 하겠죠. 3번째는 예상하시다시피 이미 설정이 끝난경우 입니다.

사용자 삽입 이미지
Configure를 눌러 들어간 화면에는 위와같은 화면이 나옵니다. Enable을 눌러주시면 아래의 Configure 버튼이 활성화가 됩니다. 보시면 대충 Push를 보내기 위한 관련 인증서를 발급 받는 부분이라는것을 아실수 있으실텐데요 두가지가 있네요. 한가지는 개발용 하나는 실제 서비스용입니다. 우선은 시범적으로 개발용만 발급받아보겠습니다. 실제 서비스때는 밑의 것을 발급받아 사용하시면 됩니다.

사용자 삽입 이미지
위와 같은 화면이 나옵니다. 아마 대부분의 개발자분들이 이 화면을 보시면 어떤것이 필요한 것인지 감이 오실것이라 생각되네요. 잘 모르시겠다면 [Apple Program 이용가이드]의 인증서 등록 부분을 참고하시면 될것 같네요. 화면이 요즘과 좀 많이 달라지긴 했지만 참고하시는데에는 무리가 없을것입니다. 저때에 사용하셨던 CSR파일을 그대로 이용하시면 됩니다.

사용자 삽입 이미지
잠시후에 자동으로 인증서가 발급됩니다. 위와같이 화면이 뜨게 됩니다.

사용자 삽입 이미지
이제 인증서를 다운받을 수 있게 되는데요, 위에 보이는 Download버튼을 눌러 인증서를 다운 받습니다. 그리고 더블클릭하여 시스템의 인증서로 등록하여 줍니다.

aps_developer_identity.cer – 개발용 인증서
aps_production_identity.cer – 실서비스용 인증서

맥의 유틸리티 – 인증서 메뉴에 가보면 위와 같이 Push Service에 대한 인증서가 등록된것을 확인하실 수 있습니다. 이제 해당 인증서와 아래에 선택한것과 같이 CSR 발급에 사용되었던 개인키(Private Key)를 동시에 선택을 한후에 마우스 오른쪽 버튼을 눌러 “외부로 보내기”(기억이 확실치 않네요; 아무튼 Export입니다)를 하여 줍니다. p12라는 확장자의 파일로 저장이 가능합니다. 이때에 암호를 정할 수 있는데 적절한 암호를 입력하여 줍니다.

2. 아이폰 어플리케이션 수정

이제 아이폰 어플리케이션에 Push Service를 등록할 수 있도록 몇가지 추가를 해야 합니다. -AppDelegate.m 파일에 다음의 내용을 추가합니다.

- (void)application:(UIApplication *)application 
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken 
{ 
 NSMutableString *deviceId = [NSMutableString string]; 
 const unsigned char* ptr = (const unsigned char*) [deviceToken bytes]; 

 for(int i = 0 ; i < 32 ; i++) 
 { 
  [deviceId appendFormat:@"%02x", ptr[i]]; 
 } 

 NSLog(@"APNS Device Token: %@", deviceId); 
}

위의 추가된 코드는 어플리케이션이 최초 실행될때에 어플리케이션이 푸시서비스를 이용함을 알리고 허용할지를 물어보게 하고 사용자의 동의를 얻었을 경우 실행됩니다. APNS에 디바이스 정보를 등록하고 64바이트의 문자열을 받아오게 됩니다. 마지막에 NSLog로 확인을 하게 되는 deviceId를 써드파티 서비스의 서버로 전송하여 관리하시면 됩니다.

- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
{ 
 NSString *string = [NSString stringWithFormat:@"%@", userInfo]; 
 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil 
                             message:string delegate:nil
                             cancelButtonTitle:@"OK"
                             otherButtonTitles:nil]; 
 [alert show]; 
 [alert release]; 
}

위의 새로 추가된 메서드는 어플리케이션이 푸시를 받았을 경우 호출되는 메서드입니다. 그냥 받은 메시지를 그대로 찍도록 해보았습니다. 넘어오는 데이터에 대해서는 직접 한번 디버깅하여 보시길 추천해드립니다.

사용자 삽입 이미지

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{ 
 NSDictionary *userInfo = [launchOptions objectForKey:
              UIApplicationLaunchOptionsRemoteNotificationKey]; 

 if(userInfo != nil) 
 { 
  [self application:application didFinishLaunchingWithOptions:userInfo]; 
 } 

 // APNS에 디바이스를 등록한다. 
 [[UIApplication sharedApplication] registerForRemoteNotificationTypes: 
  UIRemoteNotificationTypeAlert| 
  UIRemoteNotificationTypeBadge| 
  UIRemoteNotificationTypeSound]; 

 return YES; 
}

이제 하이라이트입니다. 위의 메서드는 어플리케이션이 실행될때에 호출이 됩니다. 요즘은 URI 스키마 혹은 푸시 서비스를 통해 어플리케이션을 실행할 수 있게 되었고 관련 옵션들이 launchOptions인자로 넘어오게 됩니다.

위의 경우에는 어플리케이션이 실행되어있지 않은 경우 푸시를 받아서 어플리케이션이 실행될때 관련 정보가 launchOptions에 담겨오게 되며 그 정보를 가지고 didFinishLaunchingWithOptions를 재호출하는 로직입니다. 그 뒤에 보면 나와있는 APNS등록 부분이 사용자에게 푸시를 허용할지 물어보는 부분이 되겠습니다.

이때에 알아두실 점은 위의 메서드가 구현된 시점부터 원래 사용되던 다음의 메서드는 사용되지 않습니다.

- (void)applicationDidFinishLaunching:(UIApplication *)application 
{ 
 // didFinishLaunchingWithOptions를 구현할 경우 사용되지 않는다. 
}

3. 푸시 서버 준비

이 블로깅에서는 푸시 서버를 Java기반에서 개발하는것으로 설명합니다. 검색해 보면 PHP, Ruby, Python등 매우 다양한 언어로 이미 포팅되어 잘 만들어진 라이브러리도 많이 있더군요. 저도 역시나 잘 만들어진(실제로 여러 포럼에서 추천받는) javapns를 사용하도록 하겠습니다.

필요한 JAR 파일은 다음과 같습니다.

javapns
log4j
bcprov-ext
commons-io
commons-lang

 

이제 해당 홈페이지에 나와있는 간단한 예제를 실행해 보도록 하겠습니다. 다음 소스에서 사용되는 iPhoneId의 경우 didRegisterForRemoteNotificationsWithDeviceToken 메서드에서 언급되었던 deviceId값이며 certificate의 경우 인증서 준비단계에서 만들었던 p12확장자 파일을 뜻합니다. passwd는 p12를 만들당시에 물어보았던 비밀번호입니다.

public class Push {
     // APNs Server Host & port
     private static final String HOST = "gateway.sandbox.push.apple.com";
     private static final int PORT = 2195;

     // Badge
     private static final int BADGE = 66;

     // iPhone's UDID (64-char device token)
     private static String iPhoneId = "2ed202ac08ea9...cf8d55910df290567037dcc4";
     private static String certificate = "/absolute/path/to/my/certificate";
     private static String passwd = "mycertificatepassword";

     public static void main( String[] args ) throws Exception {

        System.out.println( "Setting up Push notification" );

        try {
             // Setup up a simple message
             PayLoad aPayload = new PayLoad();
             aPayload.addBadge( BADGE );
             aPayload.addAlert("Hello World!");
             aPayload.addSound("default");
             System.out.println( "Payload setup successfull." );

             System.out.println ( aPayload );

             // Get PushNotification Instance
             PushNotificationManager pushManager = 
                PushNotificationManager.getInstance();

             // Link iPhone's UDID (64-char device token) to a stringName
             pushManager.addDevice("iPhone", iPhoneId);
             System.out.println( "iPhone UDID taken." );

             System.out.println( "Token: " +
                pushManager.getDevice( "iPhone" ).getToken() );

             // Get iPhone client
             Device client = pushManager.getDevice( "iPhone" );
             System.out.println( "Client setup successfull." );

             // Initialize connection
             pushManager.initializeConnection( HOST, PORT, certificate, passwd, 
                SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
             System.out.println( "Connection initialized..." );

             // Send message
             pushManager.sendNotification( client, aPayload );
             System.out.println( "Message sent!" );

             System.out.println( "# of attempts: " +
                pushManager.getRetryAttempts() );
             pushManager.stopConnection();

             System.out.println( "done" );

         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }

사용자 삽입 이미지

예제코드에 이어 바로 결과 화면을 붙여보았습니다. 위와같이 메시지를 전송하면 잠자고 있던 아이팟이 알람을 울리게 됩니다.

여기서 한가지 더 알아두셔야 할 점으로 Feedback이라는 것이 있습니다. 위와 같은 Push의 경우에는 해당 사용자에게 제대로 메시지가 전달 되었는지 여부를 확인할 길이 없습니다. 만약에 10만명의 푸시 사용자가 가입을 했고 5만명이 해당 어플의 푸시를 받지 않겠다고 설정을 바꾸었거나 삭제를 했다면 서비스는 일일이 받지 않는 사용자에게 푸시를 날리느라 시간을 낭비하게 되겠죠. 그래서 이 Feedback을 이용하여 사용하지 않는 사용자의 리스트를 받아오게 됩니다.

public class Feedback {
     // APNs Server Host & port
     private static final String HOST = "feedback.push.apple.com";
     private static final int PORT = 2196;

     private static String certificate = "/absolute/path/to/certificate";
     private static String passwd = "certificatePassword";

     public static void main( String[] args ) throws Exception {

        try {
             // Get FeedbackServiceManager Instance
             FeedbackServiceManager feedbackManager = 
                FeedbackServiceManager.getInstance();

             // Initialize connection
             LinkedList<Device> devices = feedbackManager.getDevices( HOST, PORT, 
                certificate, passwd, SSLConnectionHelper.KEYSTORE_TYPE_PKCS12 );
             System.out.println( "Connection initialized..." );

             System.out.println( "Devices returned: " + devices.size() );

             ListIterator<Device> itr = devices.listIterator();
             while ( itr.hasNext() ) {
                 Device device = itr.next();
                 System.out.println( "Device: id=[" + device.getId() +
                " token=[" + device.getToken() + "]" );
             }

             System.out.println( "done" );

         } catch (Exception e) {
             e.printStackTrace();
         }
     }
}

사용자 삽입 이미지

테스트 몇번 해보았을 뿐이라 사용하지 않는 사용자는 존재하지 않는군요. 위와같은 명령을 하루 한번정도의 텀으로 주기적으로 실행하여 발송 목록에서 제거하는 등의 처리를 하시면 되겠습니다. 더 많은 관련 문서는 javapns의 WIKI 페이지에 잘 나와있습니다^^

[iPhone] Build and Archive를 이용하여 편하게 앱스토어에 등록하기

아이폰 개발을 너무 오래 손을 놓았던 것일까요. 저도 모르는 좋은 기능들이 많이 생겨났더군요. 더군다가 Xcode는 많은 발전을 이룬것 같습니다. 제가 생각하는 그중에 가장 많은 발전이라 생각되는 기능중에 한가지가 바로 “Build and Archive”입니다.

예전에는 Adhoc 또는 AppStore를 통한 배포를 하기 위해서는 빌드된 바이너리 파일(폴더)와 Provisioning 파일이 함께 필요로 하였습니다. 그 절차도 까다로웠고 분명히 되어야 하는것임에도 잘 될때도 있고 안될때도 있고(제가 잘 몰라서 그런것일지도..) 아무튼 문제가 많았었는데요.

위의 기능을 이용하니 매우 편리하게 잘 되는군요. 하나하나 조금은 불친절하겠지만 기억용으로 기록해 봅니다. 이부분에 대해 잘 모르시는 분이라면 우선 [이글]을 먼져 읽어보시길 바랍니다.

사용자 삽입 이미지
Xcode의 Build – Build and Archive로 들어갑니다. 보시다시피  단축키도 없군요.

사용자 삽입 이미지

이제 위와같은 창이 뜨게됩니다. 많이 보던 Organizer 창이군요. Archived Applications탭에 보시면 지금까지 Build된 내역이 시간별로 표시가 되게 됩니다. 그나저나 모자이크 처리가 좀 허접하네요;ㅎㅎ

사용자 삽입 이미지
이제 Share Application을 눌러봅니다. 뜬금없이 아이디와 비밀번호를 물어 보는군요, 배포를 위한것이기 때문에 당연히 개발자센터의 Agent 계정을 입력하여 줍니다.

참고로 Share Application 메뉴는 Adhoc/In House 배포를 위해 사용됩니다. 이 메뉴에서 곧바로 앱스토어에 파일을 등록할수도 있는데요 맨 밑에 있는 Submit Application to iTunes Connect를 선택하시면 됩니다. 진행은 다들 비슷하므로 일단 Adhoc 기준으로 설명을 진행하겠습니다.

사용자 삽입 이미지
이제 위와 같은 화면이 뜨는데요, 패키징될때에 포함시킬 적절한 Provisioning 프로필을 선택하시면 됩니다. 그리고 Save to Disk를 누르면 ipa파일로 디스크에 저장됩니다. 해당 파일을 등록된 디바이스의 사용자에게 전달해주시면 됩니다. 이 메뉴에서 곧바로 메일까지 보낼수 있군요.

이제 다시 앱스토어에 등록하는 과정에 대해 이야기 해보도록 하겠습니다. 몇가지만 첨언을 해보겠습니다.

사용자 삽입 이미지새로운 어플리케이션을 등록할때 iTunes Connect에서 기본적인 정보를 입력하게 되면 위와 같은 화면이 뜹니다. 실제는 이 화면은 파일 업로드가 성공한 시점의 화면을 캡춰해놨군요; 실제로는 파일업로드 대기 상태로 들어가고 위에서 언급한 Submit Application to iTunes Connect를 눌러 바로 업로드를 하게 되면 바로 위의 화면처럼 Status가 변경됩니다.

사용자 삽입 이미지그리고 몇분 있으면 자동으로 위와같이 리뷰 대기 상태로 들어가게 됩니다. 예전에 힘들게 어플리케이션을 올릴때가 생각나는군요. 가면 갈수록 어플리케이션 등록과정이 쉬워지는것 같습니다.