NSNotification

아이폰 2011. 4. 29. 11:54




Cocoa 와  Cocoa Touch 프레임웍의 이벤트 통지 메커니즘인 NSNotification 에 대해서 간략하게 알아보자. 

1. NSNotification 이란? 

다수의 객체들 사이에서 이벤트가 발생했음을 알리는 방식으로, 한 객체와 다른객체 간에 의존관계를 중계하는 클래스에
집중화시켜서 서로 간에 결합도를  느슨하게 할 수 있다.  NSNotificationCenter,  NSNotification, Observer 로 구성된다.
NSNotificationCenter는 객체들 사이에 이벤트 전달을 중계하는 역할을 하며,  Observer는 이벤트가 발생하기를 기다리는 대상을
나타낸다. Observer 가 기다리는 이벤트에 대한 정보와 부가적인 데이터를 전달하기 위한 객체가 Notification 이다. 
Observer 는 객체의 역할을 지칭하는 용어이며, NSObject를 상속하는 모든 클래스가 Observer 가 될수 있다. 



Notification  사용예 
푸시 메시지가 도착했을 때, 현재 화면에 상관없이 특정화면으로 전환하기 위해 사용하였다. 
화면은 항상 (화면1 > 화면2 > 화면3) 순서로 네비게이션 이동이 이루어진다고 해보자.  푸시 이벤트는 UIApplicationDelegate 프로토콜을 구현하는 클래스에서 수신하면,  이곳에서 NSNotification 이벤트를 발생시킨다.  실행되고 있는 App이  현재 어떤 화면을 보여주고
있던지 이벤트를 수신하는 클래스에서는  연쇄적으로 Notification을 다음화면으로 전달하여 (화면1 > 화면2 > 화면3) 의 순서를 유지하며 화면 전환을 할 수 있다. (화면1  > 화면3으로 바로 전환해버리면 네비게이션 순서가 틀어져버림)

 



이외에도 비동기 방식으로 동작하는 NSURLConnection에서 데이터 수신이 완료되면, 수신한 데이터를  전달할 때 NSNotification을
사용하면  클래스간에 참조를 가지고 있지 않아도 데이터를 쉽게 전달할 수 있다.  




2. NSNotification 을 사용하기 위한 절차 
① NSNotificationCenter에 Observer 와  Notification 등록 
② Notification 포스팅 
③ 처리
④ NSNotificationCenter 에서 Observer 와 Notification 제거 

 
   
  

 
2.1 Notification 등록
NSNotificationCenter 의 클래스 메소드 defaultCenter를 사용하여 로컬 NotificatonCenter의 싱글턴 객체를 얻을 수 있다.
이 클래스에서 지원하는 메서드를 사용하여  Notification을 등록 할 수 있다.  인자 Observer는  이벤트 발생시 수신할 객체, 
selector는 이벤트 수신시 수행할 메시지 selector,  name은 이벤트의 이름,  object는 이벤트를 발생시킬 객체이며,  nil을 주면
이벤트를 발생시킬 객체의 종류에는 상관없이 이벤트를 수신한다
 
NSNotificationCenter *notifCenter = [NSNotificationCenterdefaultCenter];

[notifCenter addObserver:self
                         selector:@selector(viewTrasitionToMyLostArticleList:) 
                            name:@"moveToPushList" object:nil];

위 문장은 다음과 같이 해석된다.  
“ 어떤 object가 어떤 이름의 이벤트를 보내면, observer의 selector를 호출해줘” 




2.2 Notification 메시지의 형태
Observer의 수신 메시지의 형태는 반드시 NSNotification* 타입의 인자 하나를 받아야 한다. 그렇지 않으면 unrecognized selector sent
에러가 발생한다.
(void)viewTrasitionToMyLostArticleList:(NSNotification*)notif {
   NSString *pushSeq = [[notif userInfo] objectForKey:@"pushSeq"];
   // 의심분실물 목록뷰로 전환 
}




2.3 이벤트 발생시키기 
NSNotificationCenter의 다음과 같은 메소드를 사용하여 Notification을 발생시킬 수 있다.

  * Notification을 직접 생성하여 전달 - 데이터 전달이 필요없을 경우  
(void)postNotification:(NSNotification*)notification;


* 부가적으로 전달해야할 데이터가 있을 경우 사용
(void)postNotification:(NSString*)notificationName 
                object:(id)anObject 
              userInfo:(NSDictionary*)userInfo




2.4 등록된 observer와 Notification 제거
NSNotificationCenter 등록되어 있는 observer와 notification은 사용후 메모리 절약을 위해 제거해주는 것이 좋다.
 
// notificationObserver와 동일한 참조의 모든 observer를 제거 
(void)removeObserver:(id)notificationObserver

// 송신자가 notificationSender로 등록된 notificationName으로 등록된 모든 observer 제거 
(void)removeObserver:(id)notificationObserver
                name:(NSString *)notificationName
              object:(id)notificationSender



'아이폰' 카테고리의 다른 글

[번역] 사용자 인터페이스 설계의 원리  (0) 2012.06.24
수동으로 UI 컨트롤에 이벤트 전달  (0) 2011.11.13
NSNotification  (1) 2011.04.29
푸시 메시지 포맷  (0) 2011.04.07
애플 푸시 서비스  (0) 2011.04.07
Sprite Sheet 제작툴 Zwoptex  (0) 2011.03.16
  • 사공석준 2011.07.12 17:33 ADDR 수정/삭제 답글

    좋은정보 감사합니다 퍼가요 ^^

푸시 메시지 포맷

아이폰 2011. 4. 7. 23:39



 Provider와 APNS 간에 SSL(Secure Socket Layer)를 통해 통신을 하며, 서로 간에 올바른 통신을 위한 몇 가지
요구사항이 존재한다.   다음은  Local, Remote Notification Programming Guide 문서에서 일부 발췌한 내용이다.
자세한 내용은 실제 문서를 참고하자. 


1. Provider가 APNS와 통신하기 위한 요구사항
 〮 Provider는 Apple Push Notification Service와 바이너리 인터페이스를 사용해 통신함
 〮 TCP Socket을 사용하며, 안전한 통신채널을 위해 TLS(또는 SSL)을 사용함
 〮 APNS와의  잦은 연결과 종료는 DOS 공격으로 오인받을 수 있음 
 〮 에러가 발생하면 APNS는 해당연결을 종료함 
 〮 서버에 접속해서 반복적으로 푸시전송에 실패한 클라이언트 디바이스 리스트를 조회할 수 있음 => Feedback service



2. Binary Interface and Notification Formats
〮순수 TCP 소켓을 사용하며, 최적의 성능을 위해서는 단일 연결을 맺고, 다중 통지를 보내야함 
〮인터페이스는 simple 포맷, enhanced 포맷 2가지의 notification format을 지원함 
〮Simple 포맷에서는 payload 데이터가 제한크기를 초과하면 통지를 거부하지만, enhanced 포맷에서는
    notification 에 임의의 식별자를 할당하여, 에러가 발생하면 식별자와 연관된 에러코드를 확인할 수 있음


위에서 언급된 것처럼 2가지 종류의 메시지 형태가 존재한다. 대부분의 앱에서는 Simple 포맷으로도 충분하다. 하지만    
Simple 포맷은 실제로 푸시 메시지가 전송됐는지를 보장하지 않기 때문에,   푸시 메시지에 대한 전송 신뢰성이 보장되야 할경우엔는
Enhanced 포맷을 사용하자. Enhanced 포맷에서는 메시지 전송 실패시 에러를 반환하기 때문에,  Provider가 주기적으로 APNS에서 
이 값을 체크하여 실패시 다시 전송을 요청할 수 있다. 



3. Simple Format
APNS와  SSL 연결과, peer-exchage 인증을 가정한다. (SSL을 사용한 1:1 통신을 한다는 의미같다.)
메시지 포맷은 다음과 같다. 




Command : Simple 포맷인지 Enhanced  포맷인지 구별하기 위한 값. simple 포맷은 0을 갖는다.
Token length     : DeviceToken의 길이
Device Token    : 바이너리 포맷으로 인코딩 되야함
Payload length : 푸시로 전달할 메시지의 길이로 256 바이트를 초과할 수 없으며,
                           널(null)문자로 종료해서는 안됨
* token length와 payload length 는 네트워크 오더(big endian)이어야 함




3.1 SimpleFormat을 사용하는 예제코드 
Local, Remote Notification Programming Guide 문서에 나와있는 샘플 코드이다. 실제 메시지 전송을 구현해야할 경우 참고하자

static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength)
{
        bool rtn = false;
        if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength) {
        uint8_t command = 1; /* command number */ 
        char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t)
                + sizeof(uint16_t) + DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
        
        /* message format is, |COMMAND|ID|EXPIRY|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD|  */
        char *binaryMessagePt = binaryMessageBuff; 
        uint32_t whicheverOrderIWantToGetBackInAErrorResponse_ID = 1234; 
        uint32_t networkOrderExpiryEpochUTC = htonl(time(NULL)+86400); 
        // expire message if not delivered in 1 day 

        uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE); 
        uint16_t networkOrderPayloadLength = htons(payloadLength);
        
        /* command */ 
        *binaryMessagePt++ = command;
        
        /* provider preference ordered ID */
        memcpy(binaryMessagePt, &whicheverOrderIWantToGetBackInAErrorResponse_ID, sizeof
                                                                                                                (uint32_t));
        binaryMessagePt += sizeof(uint32_t);

        /* expiry date network order */ 
        memcpy(binaryMessagePt, &networkOrderExpiryEpochUTC, sizeof(uint32_t)); 
        binaryMessagePt += sizeof(uint32_t);
        
        /* token length network order */ 
        memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* device token */ 
        memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE); 
        binaryMessagePt += DEVICE_BINARY_SIZE;

        /* payload length network order */ 
        memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* payload */ 
        memcpy(binaryMessagePt, payloadBuff, payloadLength); 
        binaryMessagePt += payloadLength; 
        
        if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt -binaryMessageBuff)) > 0) 
                rtn = true;
        } 
        return rtn;
}



4. Enhanced Format

Enhanced Format 를 사용하여 메시지를 전송하면, APNS가 인지 불가능한 명령을 만났을 경우 연결을 종료하기 전에 에러 응답을
반환해주어 에러의 원인을 확인할 수 있다. Provider 에서는 주기적으로 APNS의 에러응답에 접근하여 결과를 확인할 수 있다. 



first byte: 1
identifier: notification을 식별하기 위한 임의의 값. 에러가 발생하면 APNS는 이값을 반환함
expiry : notification이 더이상 유효하지 않는 때를 나타내는 초로 표현된 시간.  fixed UNIX epoch  date(UTC)
...나머지 필드는 Simple Format과 동일하다.




4.1 Enhanced Format을 사용하는 예제코드 
역시 실제 구현시에 참고하자. 
 
static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength)
{
        bool rtn = false;
        if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength) {
        uint8_t command = 1; /* command number */ 
        char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t)
                + sizeof(uint16_t) + DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
        
        /* message format is, |COMMAND|ID|EXPIRY|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD|  */
        char *binaryMessagePt = binaryMessageBuff; 
        uint32_t whicheverOrderIWantToGetBackInAErrorResponse_ID = 1234; 
        uint32_t networkOrderExpiryEpochUTC = htonl(time(NULL)+86400); 
        // expire message if not delivered in 1 day 

        uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE); 
        uint16_t networkOrderPayloadLength = htons(payloadLength);
        
        /* command */ 
        *binaryMessagePt++ = command;
        
        /* provider preference ordered ID */
        memcpy(binaryMessagePt, &whicheverOrderIWantToGetBackInAErrorResponse_ID, sizeof(uint32_t));
        binaryMessagePt += sizeof(uint32_t);

        /* expiry date network order */ 
        memcpy(binaryMessagePt, &networkOrderExpiryEpochUTC, sizeof(uint32_t)); 
        binaryMessagePt += sizeof(uint32_t);
        
        /* token length network order */ 
        memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* device token */ 
        memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE); 
        binaryMessagePt += DEVICE_BINARY_SIZE;

        /* payload length network order */ 
        memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t)); 
        binaryMessagePt += sizeof(uint16_t);

        /* payload */ 
        memcpy(binaryMessagePt, payloadBuff, payloadLength); 
        binaryMessagePt += payloadLength; 
        
        if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt -binaryMessageBuff)) > 0) 
                rtn = true;
        } 
        return rtn;
}



5. Error 응답 포맷
notification 포맷을 인식할 수 없을 경우 APNS는 연결을 종료하기 전에 error 응답을 전송해준다.  error가 없다면 어떠한 값도 
반환하지 않는다.  에러 응답의 포맷은 다음과 같다. 



first byte: 8
status : 상태코드
identifier : notification을 구성할 때 사용한 식별자 

상태코드
0 : 에러없음
1 :  에러 처리중
2 : device token 이 없음
3 : topic이 없음
4 : payload 없음
5 : 잘못된 token 크기
6 : 잘못된 topic 크기
7 : 잘못된 payload 크기
8 : 잘못된 token
255 : 알수없음




'아이폰' 카테고리의 다른 글

수동으로 UI 컨트롤에 이벤트 전달  (0) 2011.11.13
NSNotification  (1) 2011.04.29
푸시 메시지 포맷  (0) 2011.04.07
애플 푸시 서비스  (0) 2011.04.07
Sprite Sheet 제작툴 Zwoptex  (0) 2011.03.16
애플리케이션 응답성 향상을 위해 동시성 사용하기  (0) 2011.03.15

애플 푸시 서비스

아이폰 2011. 4. 7. 23:01



이 포스팅에서는 애플 푸시 서비스를 사용하는 방법에 대해서 알아보자. 작년 겨울 앱공모전을 준비하면서 "분실물 다나와"
라는 앱을 개발했다.  서울 분실물 센터에 수거되어 있는 분실물 목록을 조회할 수 있고, 등록된 분실물과 유사한 분신물 목록을
사용자 아이폰으로 알리는 앱이었다. 이때 유사 분실물 발견유무를 푸시 기능을 통해 구현하였다. 당시에 푸시와 관련된 
자료가 별로 없어서(내가 못찾았겠지만...) 애플 개발자 가이드 문서를 참고하여 구현하였다. 그 때 정리한 내용을 올려본다.
(사진 캡처를 안해서 이해가 잘 안될 수도....-_-;; )




1. Push Service 란?
서버에서 아이폰 애플리케이션으로 데이터를 역으로 전송할 수 있는 서비스이다. 
Remote  Notification을 등록하고 수신하는 애플리케이션과 푸시 서비스를 제공하는 APNS(Apple Push Notification Service),
APNS에 데이터 푸시요청을 수행할 수 있는 권한을 획득한  Provider(서버) 로 구성된다.



"분실물 다나와" 프로젝트로 예를 들면...Provider는 "분실물 다나와" 서버가 되고, Client App 은 "분실물 다나와" 아이폰 앱,
APNS는 애플에서 제공하는 푸시서버가 된다.  iPhone에 전송된 푸시메시지는 iOS 를 거쳐 실제 앱에 전달된다. 




2. Push 서비스 사용절차 
APNS는 실제 아이폰 기기로 푸시를 전송해주는 애플측에서 제공하는 서비스이다. 푸시서비스를 이용하는 서버인 Provider는
푸시할 데이터만을 가공하여 APNS에 푸시를 요청한다.  Provider가 APNS에 접속하여 푸시데이터를 전송하려 할 때, APNS는
푸시요청을  할수 있는 권한을 SSL 인증을 통해 검사하게 된다. 그래서 Provider는 사전에 이 인증을 위한 암호화 파일을 사전에
가지고 있어야 한다. 


① 개발자 포탈에서 애플리케이션에 대한 Push Serivce 사용권한 받기 
개발자 포탈에서 App Id에 대한  Development 또는 Product 푸시 서비스 옵션을 enable 시키고,  다운받은 인증파일을 KeyCain에
등록시킨다.  이 파일을 p12 파일로  export 하여, 파일, 비밀번호와 함께  Provider 에게 사전에 전달해야 한다. 


② 아이폰 애플리케이션 실행시 APNS에 RemoteNotification 등록 
아이폰 애플리케이션이 처음 구동시 APNS에 RemoteNotification 을 등록하고, 애플리케이션 DeviceToken을 수신받는다. 


③ Provider에게 DeviceToken 전송  
DeviceToken을 수신하면, 서버에 아이폰 UUID와 함께 전달한다. 이 토큰은 APNS에서 아이폰의 애플리케이션을 식별하는 키
되기 때문에 Provider가 보유하고 있어야 하는 데이터이다.  DeviceToken은 거의 변하지 않지만, 애플리케이션이 삭제되고 다시
설치될 때 변할 수도 있기 때문에 애플리케이션이 실행될 때마다,  Provider에게 전송하여 갱신여부를 확인하는 것이 좋다. Development용과 Product용의 DeviceToken 값은 다르다는 것에 주의한다. 
 




④ Provider 에서 푸시 메시지를 생성하여  APNS에 푸시 요청 
Provider 에서는 ①단계에서 전달받은 인증파일을 보유하고 있고, 인증파일 비밀번호를 알고 있어야 한다.
DeviceToken와 함께 다음의 주소로 push를 요청할 수 있다. 개발환경과 실제 서비스를 위한 APNS 의 주소가 다르다.
 
개발환경 푸시서버 주소: gateway.sandbox.push.apple.com / 2195
제품환경 푸시서버 주소: gateway.push.apple.com / 2195



⑤ APNS 에 전송하는 메시지의 포맷 
메시지는 JSON 포맷을 따르며 256바이트 이내야한다. 메시지를 구성하는 키는 다음과 같다. 


단순한 메시지 포맷은 위와 같으며, 좀더 세부적으로 메시지를 지정할 수도 있다. 더 자세한 사항은
Local, Remote Notification Programming Guide 를 참고하자. 

JSON 형태로 생성된 메시지 형태는 다음과 같다. 
{"aps":{"sound":"default","alert":"My alert message","badge":45}}

 
 
⑥ APNS에 푸시 요청하기 
Provider는 APNS가 요구하는 프로토콜을 따라 데이터를 전송해야 한다. 프로토콜은 APNS에 전송하는 Push 메시지 포맷을
참고하자.  이 메시지 포맷과 APNS 의 요구사항에 맞게 구현한 javapns라는 오픈소스 라이브러리를 사용하여 APNS에 푸시를
요청해보자 

javapns를 사용하기 위해서 아래의 의존 라이브러리를 가지고 있어야한다.
commons-lang-2.4.jar   // String클래스 등의 유틸리티 기능을 제
commons-io-1.4.ja
bcprov-jdk16-145.jar    // Bouncy Castle 단체의 암호화 관련 api
log4j-1.2.15.ja

 javapns 오픈소스 라이브러리 :  http://code.google.com/p/javapns/ 
Legion of the Bouncy Castle 사이트 : http://www.bouncycastle.org/java.html


다음은 javapns 라이브러리를 사용하여 Provider 에서 APNS 에 푸시를 요청하는  예이다. 
String password = "1q2w3e4r";
String certicatePath = "/home/CertificateSR/Interesting_Dev_Cert_pass.p12";
String apnsAddress = "gateway.sandbox.push.apple.com";
String apnsPort = "2195";

try {
        PayLoad payload = new PayLoad();
                                
        payload.addAlert(pushMessage.getAlert());
        payload.addBadge(pushMessage.getBadge());
        payload.addSound(pushMessage.getSound());
                                
        HashMap customMap =  (HashMap) pushMessage.getCustomData(); 
        Iterator iterator = customMap.keySet().iterator();
        
        while (iterator.hasNext()) {
                String key = iterator.next();
                payload.addCustomDictionary(key, customMap.get(key));
        }
                                
        PushNotificationManager manager = PushNotificationManager.getInstance();
                        
        manager.addDevice("myIphone", pushMessage.getDeviceToken());
        
        Device device = PushNotificationManager.getInstance().getDevice("myIphone");
        manager.initializeConnection( apnsAddress, 
                                          Integer.parseInt(apnsPort), 
                                          certicatePath, 
                                          password,                                                                            
                                          SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
        manager.sendNotification(device, payload);

        manager.stopConnection();
        manager.removeDevice("myIphone");
                                
} catch (Exception e) {
        e.printStackTrace();
}

 


⑦  애플리케이션측에서 Push를 수신하기 위해서  UIApplicationDelegate 프로토콜의 메소드를 구현 

(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // 배지넘버 초기화
        application.applicationIconBadgeNumber = 0;

        NSDictionary *aps = 
          [launchOptionsobjectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
        
        // 애플리케이션을 처음실행 : RemoteNotification을 등록함
        if (aps == nil) {       
                [[UIApplication sharedApplication]              
                                                registerForRemoteNotificationTypes:
                                                        (UIRemoteNotificationTypeAlert | 
                                                         UIRemoteNotificationTypeSound | 
                                                         UIRemoteNotificationTypeBadge)

                ];
                        
        } else {
                // 애플리케이션이 원격 통보에 의해 실행됐음
                // alert 추출 
                NSString *alert = [aps objectForKey:@"alert"];
                // custom 데이터 추출 
                NSString *pushSeq = [userInfo objectForKey:@"pushSeq"];
        }

        return YES;

}

// RemoteNotification 등록 성공. deviceToken을 수신
(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        // Provider에게 DeviceToken 전송
        //[service registDeviceToken:[deviceToken description]];
}


// APNS 에 RemoteNotification 등록 실패
(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
        NSLog(@"fail RemoteNotification Registration: %@", [error description]);
}


// 애플리케이션 실행 중에 RemoteNotification 을 수신
(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
        // push 메시지 추출
        NSDictionary *aps = [userInfo objectForKey:@"aps"];
        // alert 추출 
        NSString *alert = [aps objectForKey:@"alert"];
        // custom 데이터 추출 
        NSString *pushSeq = [userInfo objectForKey:@"pushSeq"]; 
}


RemoteNotification이 성공적으로 등록되면, 응답으로 호출되는 didRegistForRemoteNotificationWithDeviceToken 메시지의 파라미터에
DeviceToken 값이 담겨져 온다. 이  Token의 값은 다음과 같은 형태를 갖는다.  

<12345678 12345678 12345678 12345678 50cfb1 3f5febbe497df>

APNS에 전송하기 하기 위해서는  애플리케이션 또는 Provider 측에서 <, >, 공백문자를 제거해야 한다는 것을 잊지 말자!! 

 지금까지 애플 푸시서비스에 대해 알아보았다.  사실 푸시 서비스를 이용하는 과정은 그렇게 어렵지 않다. 오히려 개발자 포탈에서
푸시 서비스 권한을  설정하고, 키체인에서 인증파일을 생성하는 과정이 번거롭고... 푸시를 받을 앱이 실행중일 때와 실행중이지 
않을 때 푸시 수신 시 동작방식을 달리 처리해줘야 하는 것이 조금 번거로울 뿐이다.  다음 포스팅에서는 실제 푸시메시지 포맷에 
대해서 좀더 자세히 알아보자.
 



'아이폰' 카테고리의 다른 글

NSNotification  (1) 2011.04.29
푸시 메시지 포맷  (0) 2011.04.07
애플 푸시 서비스  (0) 2011.04.07
Sprite Sheet 제작툴 Zwoptex  (0) 2011.03.16
애플리케이션 응답성 향상을 위해 동시성 사용하기  (0) 2011.03.15
OpenGL ES 게임 프레임웍  (0) 2011.02.14

Sprite Sheet 제작툴 Zwoptex

아이폰 2011. 3. 16. 00:55




1. Zwoptex 란?
이미지를 모아서 Sprite Sheet (텍스처맵) 을  쉽게 만들 수 있게 해주는  MacOs 기반의 툴이다. 

 


2. Zwoptext의 특징 
Trimming : 이미지의 투명한 부분을 잘라주어 파일사이즈를 절약할 수 있게 해주는 기능 
Rotation : 개별 이미지를 회전시켜주는 기능 
Automatic Layout : 3가지 알고리즘을 이용해서, 이미지 파일들을 정렬시켜주는 기능(애니메이션 생성시)
Sprite Updating : 외부 프로그램에서 읽고 있는 이미지 소스파일을 변경하면, 해당 변경사항을 적용시켜주는 기능 
One-Click Publish : Sprite Sheet을 변경할 때마다, 텍스처 이미지와 좌표리스트 파일을 생성해주는 기능 




3. Zwoptex 다운받기 
 아래 주소에서 Zwoptext 다운을 받아 설치한다. 
http://zwoptexapp.com/






4. Zwoptex의 간략한 사용법 
 Zwoptex 를 실행시키고, 8개의 애니메이션 이미지 파일을 로드한 그림이다.



Zwoptex는 디폴트로 로드한 이미지의 투명한 부분을 잘라내어( trim), 최종 Sprite Sheet의 크기를 줄이려고 한다.  
애플리케이션에서 텍스처맵을 읽기 쉽게 하기 위해서, 위와 같이 triming 없이 이미지를 정렬해보자. 모든 이미지를 선택하고, 
툴바에서 Untrimming 버튼을 클릭하면, 위와 같이 이미지들이 정렬될 것이다.
 
좌측 Layout 패널에서는 정렬 알고리즘과 정렬시 기준으로 사용할 값을 선택할 수 있다.  어떤 값을 변경시에는 항상 Apply를
클릭해서, 변경사항을 적용시켜야 한다. 
 
Canvas 패널은 이미지들이 정렬되는 배경의 크기와 색을 지정할 수 있도록 해준다. 배경의 크기가 작다면, 이미지들이  겹쳐지게
되는데, 이때 width, height 값을 조정하고, Apply 를 클릭하여  배경의 크기를 변경할 수 있다. 

생성할 Sprite의 수정을 마친 후 ,  툴바의 Publish 버튼 클릭하면, Generic/Cocos2D 포맷으로 텍스처 이미지와 이미지에 대한
좌표를 담은 파일(plist) 을  저장 할 수 있다. 

이후 게임엔진에서 이미지와 좌표 정보를 담은 파일을 읽어, 애니메이션을 구현 할 수 있다. 

 



'아이폰' 카테고리의 다른 글

푸시 메시지 포맷  (0) 2011.04.07
애플 푸시 서비스  (0) 2011.04.07
Sprite Sheet 제작툴 Zwoptex  (0) 2011.03.16
애플리케이션 응답성 향상을 위해 동시성 사용하기  (0) 2011.03.15
OpenGL ES 게임 프레임웍  (0) 2011.02.14
OpenGL ES 사용  (0) 2011.02.14

애플리케이션 응답성 향상을 위해 동시성 사용하기

아이폰 2011. 3. 15. 23:00



1. 애플리케이션에서의 응답성

아이폰, 아이패드를 포함한 모든 애플리케이션의 사용자 인터페이스에 있어서 응답성은  중요한 요소중에 하나이다.

많은 시간을 소비하는 태스크를 사용자 인터페이스가 동작하는 쓰레드에서 수행하면, 해당 태스크가 종료하기 까지

사용자는 어떠한 인터렉션도 행할 수가 없기 때문이다.  트위터의 글목록을 가져와서 보여주는 앱을 생각해보자.

최신글 목록과 사용자 프로필 이미지를 가져오는 동안, 버벅이 되며 먹통이 되면 누가 이 앱을 사용하겠는가?

이 포스팅에서는 Cocoa  Touch에서 지원하는 동시성 향상을  방법에 대해서 알아보겠다. 

MacOsX와 iOS에서 지원하는  사용자 인터렉션 응답성을 향상시키기 위한 다음과 같은 동시성 방법들이 있다.    
NSThread / NSObject가 지원하는 백그라운드/메인 쓰레드,  C기반의 POSIX 쓰레드,  비동기 기반의 메소드,  NSOperation /NSOperationQueue 등이 있다.  각각의  추상화 수준(사용 용이성)과 복잡성이 다르다.  추상화 수준이 높은 방법일 수록 
하드웨어 의존적인 부분을 감추어주고 쉽게 사용할 수 있어서, 비지니스 로직에 더 집중 할 수 있다.  좀더 세밀한 컨트롤과
뛰어난 성능을 원한다면 C 기반의 동시성 API 를 사용하자.   



2. NSOperation /  NSOperationQueue

웹에서 사진들의 목록을 받아와 이미지와 함께 사진의 이름을 출력하는 플리커 애플리케이션을 생각해보자.

사진들의 목록을 받아오고, 목록에 해당하는 이미지 각각을 로딩하는데 상당한 시간이 소모될 것이다. 이러한 작업을

아이폰 애플리케이션의 메인쓰레드에서 수행할 경우, 애플리케이션은 상당히 버벅거리게 된다. 백그라운드 쓰레드로 

이미지를 가져오는 방법을 선택하더라도 성능의 향상은 있겠지만 그 효과는 미미하다. 

하나의 이미지를 가져오는 작업에 하나의 쓰레드를 할당하여 완료되면 이미지를 목록을 추가하는 방식이 자연스러운 

애플리케이션이 될 것이지만,  몇 개의 쓰레드를 할당하는 것이 성능의 저하를 가져오지 않고 최적의 성능을 낼 것인지는

하드웨어에 의존적이어서 정확히 알 수가 없다.  아이폰에서 제공하는 비동기 방식의 메소드를 사용하여 해결 할 수 있지만, 

좀더 추상화 수준이 높으면서 하드웨어 변경에도 코드의 변경이 거의 없는 NSOperation/NSOperationQueue 를 사용해보자.

NSOperatinoQueue 는 내부적으로 최적의 성능을 낼 수는 쓰레드의 개수를  생성하고 관리해 준다. 

NSOperation은 NSObject를 상속하고, 하나의 작업단위를 캡슐화하는 추상클래스이다. 




NSOperation을 상속하여 커스텀 클래스를 작성하거나, NSBlockOperation , NSInvocationOperation을 
사용하여 작업을 캡슐화하고 NSOperationQueue에 삽입하면 별도의 쓰레드에서 태스크를 수행할 수 있다. 
이때 그래픽 사용자 인터페이스를 변경하는 작업은 반드시 메인쓰레드상에서 수행해야함을 주의하자!!
NSOperation을 상속하는 클래스는 main 이라는 이름의 메소드에서 독립적인 작업을 캡슐화한다.
NSInvocationOperation은 별도의 쓰레드에서 호출할 객체와 셀렉터를 지정할 수 있다.  



3. NSBlockOperation 

NSBlockOperation은 Lisp, Ruby와 같은 언어의 클로져와 유사한 Block 이라는 요소를 사용한다.  Block은 마치
데이터 스콥을 공유하는 함수와 유사하며, Block으로 지정한 코드를 캡슐화하여 별도 쓰레드로 동작시킨다.
NSOperation / NSInvocationOperation 과 NSOperationQueue를 사용하는 방법도 훌륭하다.  하지만 NSBlockOperation을
사용하면 동일한 성능의 장접을 갖되,  연관된 코드를 한곳에 집중 시킬수 있는  부수적인 장점이 생긴다.
그러나 아이폰 운영체제의 하위 호환성이 떨어지는 것이 단점이다.  



 블럭은 사용하는 코드는 아래와 같다. 

NSBlockOperation의 blockOperationWithBlock 메시지의 인자로 블럭을 정의하여 인자로 전달하였다. 

블럭 ^{} 의 내부에서  긴 시간이 소모되는 코드를 수행하고, 작업이 완료되면 MainThread 에서 GUI를 변경하도록 하고 있다.

블럭 내 코드는 별도의 쓰레드에서 실행되기 때문에 자체적인 릴리지 풀을 생성하여 사용해야한다. 마지막으로 블럭에 정의한

작업을 수행하기 위해 NSOperationQueue(workQueue)에 추가하고 있다. 

 

NSBlockOperation *fetchImageOp = [NSBlockOperationblockOperationWithBlock:^{

NSAutoreleasePool *localPool;

@try {
// Autorelease pool 생성
localPool = [[NSAutoreleasePool alloc] init];
// 시간이 소요되는 작업 수행 
[selfperformSelectorOnMainThread:@selector(storeImageForURL:) // 메인쓰레드에서 GUI 변경

withObject:result 

    waitUntilDone:NO];

}

@catch (NSException * exception) {

NSLog(@"Exception: %@", [exception reason]);

}

@finally {
// Autorelease pool 해제
[localPool release];

}

}];
// NSOperationQueue에 추가 
[workQueue addOperation:fetchImageOp];  

 


4. 블럭 선언 및 정의하기
다음과 같이 익명으로 정의되어, 메시지의 파라미터로 전달될 수 있다. 

int (^cubeIt)(int);// 블럭변수 선언    
cubeIt = ^(int num) { return num * num * num; };// 블럭정의


다음과 같이 익명으로 정의되어, 메시지의 파라미터로 전달될 수 있다.

(returnDataType (^) ( parameterType1, parameterType2, ...))blockName


 



'아이폰' 카테고리의 다른 글

애플 푸시 서비스  (0) 2011.04.07
Sprite Sheet 제작툴 Zwoptex  (0) 2011.03.16
애플리케이션 응답성 향상을 위해 동시성 사용하기  (0) 2011.03.15
OpenGL ES 게임 프레임웍  (0) 2011.02.14
OpenGL ES 사용  (0) 2011.02.14
OpenGL ES 사용설정  (0) 2011.02.13

OpenGL ES 게임 프레임웍

아이폰 2011. 2. 14. 16:04



이 포스트는 작성중... -_-;; 음 마무리 지어야 하는데 이런 저런 일들로 정리를 못하고 있네;;;




메시 사각영역을 스크린 사각영역으로 변환 
-(CGRect)screenRectFromMeshRect:(CGRect)rect atPoint:(CGPoint)meshCenter
{
	// find the point on the screen that is the center of the rectangle
	// and use that to build a screen-space rectangle
	CGPoint screenCenter = CGPointZero;
	CGPoint rectOrigin = CGPointZero;
	// since our view is rotated, then our x and y are flipped
	screenCenter.x = meshCenter.y + 160.0; // need to shift it over
	screenCenter.y = meshCenter.x + 240.0; // need to shift it up
	
	rectOrigin.x = screenCenter.x - (CGRectGetHeight(rect)/2.0); // height and width 
	rectOrigin.y = screenCenter.y - (CGRectGetWidth(rect)/2.0); // are flipped
	
	return CGRectMake(rectOrigin.x, rectOrigin.y, CGRectGetHeight(rect), CGRectGetWidth(rect));
}



SceneObject 업데이트 메소드 
-(void)update
{
	glPushMatrix();
	glLoadIdentity();
	
	// move to my position
	glTranslatef(translation.x, translation.y, translation.z);
	
	// rotate
	glRotatef(rotation.x, 1.0f, 0.0f, 0.0f);
	glRotatef(rotation.y, 0.0f, 1.0f, 0.0f);
	glRotatef(rotation.z, 0.0f, 0.0f, 1.0f);
	
	//scale
	glScalef(scale.x, scale.y, scale.z);
	// save the matrix transform
	glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
	//restore the matrix
	glPopMatrix();
	if (collider != nil) [collider updateCollider:self];
}



SceneObject 렌더메소드 
-(void)render
{
	if (!mesh || !active) return; // if we do not have a mesh, no need to render
	// clear the matrix
	glPushMatrix();
	glLoadIdentity();
	glMultMatrixf(matrix);
	[mesh render];	
	glPopMatrix();
}


OpenGL ES 사용

아이폰 2011. 2. 14. 10:50



이미지 데이터 OpenGL ES 메모리로 로드하기 
-(CGSize)loadTextureImage:(NSString*)imageName materialKey:(NSString*)materialKey
{
	CGContextRef spriteContext; //context ref for the UIImage
	GLubyte *spriteData; // a temporary buffer to hold our image data
	size_t	width, height;
	GLuint textureID; // the ultimate ID for this texture
	// grab the image off the file system, jam it into a CGImageRef
	
	UIImage*	uiImage = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:nil]];
	CGImageRef spriteImage = [uiImage CGImage];
	// Get the width and height of the image
	width = CGImageGetWidth(spriteImage);
	height = CGImageGetHeight(spriteImage);
	
	CGSize imageSize = CGSizeMake(width, height);
	// Texture dimensions must be a power of 2. If you write an application that allows users to supply an image,
	// you'll want to add code that checks the dimensions and takes appropriate action if they are not a power of 2.
	
	if (spriteImage) {
		// Allocated memory needed for the bitmap context
		spriteData = (GLubyte *) malloc(width * height * 4);
		memset(spriteData, 0, (width * height * 4)); 
		// Uses the bitmatp creation function provided by the Core Graphics framework. 
		spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
		// After you create the context, you can draw the sprite image to the context.
		CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), spriteImage);
		// You don't need the context at this point, so you need to release it to avoid memory leaks.
		CGContextRelease(spriteContext);
		
		// Use OpenGL ES to generate a name for the texture.
		glGenTextures(1, &textureID);
		// Bind the texture name. 
		glBindTexture(GL_TEXTURE_2D, textureID);
		// Specidfy a 2D texture image, provideing the a pointer to the image data in memory
		
		//Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGBBBBAAAA"
		// this will make your images take up half as much space in memory
		// but you will lose half of your color depth.
		if (BB_CONVERT_TO_4444) {
			void*					tempData;
			unsigned int*			inPixel32;
			unsigned short*			outPixel16;
			
			tempData = malloc(height * width * 2);
			
			inPixel32 = (unsigned int*)spriteData;
			outPixel16 = (unsigned short*)tempData;
			NSUInteger i;
			for(i = 0; i < width * height; ++i, ++inPixel32)
				*outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | ((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | ((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | ((((*inPixel32 >> 24) & 0xFF) >> 4) << 0);
			
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, tempData);
			free(tempData);
		} else {
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);			
		}
		
		free(spriteData);
		// Release the image data
		// Set the texture parameters to use a minifying filter and a linear filer (weighted average)
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		
		// Enable use of the texture
		glEnable(GL_TEXTURE_2D);
		// Set a blending function to use
		glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
		// Enable blending
		glEnable(GL_BLEND);
	} else {
		return CGSizeZero;
	}
	[uiImage release];
	
	if (materialLibrary == nil) materialLibrary = [[NSMutableDictionary alloc] init];
	
	// now put the texture ID into the library
	[materialLibrary setObject:[NSNumber numberWithUnsignedInt:textureID] forKey:materialKey];
	return imageSize;
}


정점그리기 
-(void)render
{
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisable(GL_TEXTURE_2D);
	// load arrays into the engine
	glVertexPointer(vertexSize, GL_FLOAT, 0, vertexes);
	glEnableClientState(GL_VERTEX_ARRAY);
	glColorPointer(colorSize, GL_FLOAT, 0, colors);	
	glEnableClientState(GL_COLOR_ARRAY);
	
	//render
	glDrawArrays(renderStyle, 0, vertexCount);	
}



텍스처 그리기 
-(void)render
{
	glVertexPointer(vertexSize, GL_FLOAT, 0, vertexes);
	glEnableClientState(GL_VERTEX_ARRAY);
	glColorPointer(colorSize, GL_FLOAT, 0, colors);	
	glEnableClientState(GL_COLOR_ARRAY);	
	
	if (materialKey != nil) {
		[[BBMaterialController sharedMaterialController] bindMaterial:materialKey];

		glEnableClientState(GL_TEXTURE_COORD_ARRAY); 
		glTexCoordPointer(2, GL_FLOAT, 0, uvCoordinates);
	} 
	//render
	glDrawArrays(renderStyle, 0, vertexCount);	
}

OpenGL ES 사용설정

아이폰 2011. 2. 13. 01:51


 OpenGL ES 를 사용하기 위한 설정방법을 알아보자. 
아이폰에서는 Core Animation의 CAEAGLayer 클래스를 통하여,  OpenGL ES 엔진을 사용한  그래픽 하드웨어 연산을
추상화 해준다.  UIView의 서브클래스에 일반 코어애니메이션 레이어가 아닌 CAEAGLayer 를 추가하면,   UIView의
 drawRect 메소드가 아닌 표준 OpenGL ES 함수호출을 통헤 드로잉 작업을 할 수 있게된다. 

OpenGL ES를 사용하기 위해 EAGLView 클래스를 생성해보자.  "Beginning iPhone Game Development (출판사:Apress) " 를
참고하였다. (물론 이 책도 애플 OpenGL ES 개발자 레퍼런스의 샘플을 수정한 예제를 제공한 것이다.)

1. 먼저 OpenGLES.framework와 QuartzCore.framework 를 프레젝트에 추가한다 


2. EAGLView 클래스 정의는 다음과 같다. 
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

@interface EAGLView : UIView {
    
@private
    // 백버퍼의 넓이, 높이 
    GLint backingWidth;
    GLint backingHeight;
    
    EAGLContext *context;
   
 // 렌더버퍼, 프레임버퍼 식별자
    GLuint viewRenderbuffer, viewFramebuffer;
    
   // 깊이 버퍼, 사용하지 않으면 0
    GLuint depthRenderbuffer;
}

- (void)beginDraw;
- (void)finishDraw;
- (void)setupViewLandscape;
- (void)setupViewPortrait;

3.  UIView 클래스의  +(Class)layerClass 메소드를 오버라이드하여, CAEAGLayer 반환하도록 하여 OpenGL 드로잉을 가능하게 한다
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

4.  드로잉 목적에 맞게 CAEAGLayer 의 속성을 설정하고, Context 생성 
- (id)initWithFrame:(CGRect)rect {
    
    if ((self = [super initWithFrame:rect])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
        
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
        
        if (!context || ![EAGLContext setCurrentContext:context]) {
            [self release];
            return nil;
        }
		self.multipleTouchEnabled = YES;
	}
    return self;
}


5. 프레임 버퍼와 렌더링 버퍼를 생성한다. 
렌더버퍼는 색상 등의 데이터로 채워진 2차원 표면이고, 프레임버퍼는 여러 렌더버퍼로 구성된다.
(이 예제에서 프레임버터에 렌더버퍼와 깊이 버퍼를 연결하였다. )
- (void)layoutSubviews 
{
	[EAGLContext setCurrentContext:context];
	[self destroyFramebuffer];
	[self createFramebuffer];
	[self setupViewLandscape];
}

- (BOOL)createFramebuffer {    
    // 프레임버퍼와, 렌더버퍼를 위한 식별자 값 얻기 (식별자는 GLuint 형 타입)
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
    
    // 파이프라인에 바인딩 
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	
    // 렌더버퍼 메모리 할당
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
     // 렌더버퍼를 프레임버퍼에 연결 
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
    
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
    
    // 깊이 버퍼도 사용한다면, 위와 동일한 과정으로 생성후 프레임버퍼에 연결
    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }
    
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
    
    return YES;
}

- (void)destroyFramebuffer {
    
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
    
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}


6. 뷰포트와 투영변환을  설정한다 
- (void)setupViewLandscape
{		
	// Sets up matrices and transforms for OpenGL ES
	glViewport(0, 0, backingWidth, backingHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glRotatef(-90.0f, 0.0f, 0.0f, 1.0f);
	// set up the viewport so that it is analagous to the screen pixels
	glOrthof(-backingHeight/2.0, backingHeight/2.0, -backingWidth/2.0, backingWidth/2.0, -1.0f, 1.0f);
	
	glMatrixMode(GL_MODELVIEW);
	
	// Clears the view with black
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

7.  카메라를 위치를 설정한다. 
-(void)perspectiveFovY:(GLfloat)fovY 
                        aspect:(GLfloat)aspect 
			 zNear:(GLfloat)zNear
			   zFar:(GLfloat)zFar 
{
	const GLfloat pi = 3.1415926;
	//	Half of the size of the x and y clipping planes.
	// - halfWidth = left, halfWidth = right
	// - halfHeight = bottom, halfHeight = top
	GLfloat halfWidth, halfHeight;
	//	Calculate the distance from 0 of the y clipping plane. Basically trig to calculate
	//	position of clip plane at zNear.
	halfHeight = tan( (fovY / 2) / 180 * pi ) * zNear;	
	//	Calculate the distance from 0 of the x clipping plane based on the aspect ratio.
	halfWidth = halfHeight * aspect;
	//	Finally call glFrustum with our calculated values.
	glFrustumf( -halfWidth, halfWidth, -halfHeight, halfHeight, zNear, zFar );
}


8. 렌더링한다. 
-(void)beginDraw
{
	// Make sure that you are drawing to the current context
	[EAGLContext setCurrentContext:context];
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
	// make sure we are in model matrix mode and clear the frame
	glMatrixMode(GL_MODELVIEW);
	glClear(GL_COLOR_BUFFER_BIT);
	// set a clean transform
	glLoadIdentity();	
}

-(void)finishDraw
{
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	[context presentRenderbuffer:GL_RENDERBUFFER_OES];	
}


실제로 5번 과정까지가 EAGLView를 사용하기 위한 준비단계이며 한번 설정이 되면 거의 바뀌지 않는 부분들이다. 
6번 이후 과정부터는  애플리케이션에 따라 사용자 입력에 대한 렌더링을  수행하는 작업들이다. 



 

[iPhone] 로깅 프레임웍 cocoalumberjack

아이폰 2010. 12. 21. 00:36


아이폰에서 사용할 수 있는 Log4j 같은 로깅 프레임웍을 찾아 구글링을 하다,

cocoalumberjack 이라는 프레임웍을 찾게 되었다. 사용법도 log4j 와 유사하면서,

설정하기 쉽고, NSLog 보다 빠른속도와 커스텀 로거를 작성할 수 있는 유연성도 제공한다.

실제로 사용해보고, 유용성에 대해서는 추후 포스팅 해야겠다.

아래 주소에서 프레임웍 소스를 다운받자. (svn 으로 체크아웃하자)

http://code.google.com/p/cocoalumberjack/


Lumberjack( 이름이 머 이래 -_-;;) 의 사용법은 다음과 같다

1 소스를 아이폰 프로젝트에 복사한다 (logging이라는 폴더를 만들고, lumberjack 폴더를 통째로 복사했다)

2 프레임웍 사용을 위한 설정을 한다. 사용할 로거의 종류를 결정하는 부분으로 초기화하는 부분에 들어간다

( 예를 들면 applicationDidFinishingLaunching...)

DDLog : 프레임웍의 기초가 되는 부분

DDASLogger : 로그메시지를 Apple System 로거인 Console.app 으로 출력한다

DDTTYLogger : 로그메시지를 Xcode 터미널로 출력한다.

DDFileLogger : 로그메시지를 파일로 출력한다.

다음과 같이 사용할 로거의 인스턴스를 생성하여 DDLog에 추가한다

#import "DDLog.h"
#import "DDTTYLogger.h"
#import "DDASLLogger.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [DDLog addLogger:[DDASLLogger sharedInstance]];
    [DDLog addLogger:[DDTTYLogger sharedInstance]];
    // ...
}

3 기존 NSLog 문을 프레임웍에서 제공하는 로깅 메서드로 대체한다.

로깅 레벨은 error < warn < info < verbose 가 지원된다. 해당 로깅레벨은 설정하면, 자신과 하위레벨의

로깅메시지만을 출력한다. 레벨별 로깅 메서드는 다음과 같다.

DDLogError(@"");
DDLogWarn(@"");
DDLogInfo(@"");
DDLogVerbose(@"");

로깅 레벨은 전역으로 설정 할 수도 있으며, 파일별로 설정할 수도 있다. 로깅 레벨은 상수로 정의되어 진다.

LOG_LEVEL_ERROR : 에러레벨의 로깅메시지 출력
LOG_LEVEL_WARN : 에러, 경고레벨의 로깅메시지 출력
LOG_LEVEL_INFO : 에러, 경고, 인포 레벨의 로깅메시지 출력
LOG_LEVEL_VERBOSE : 모든 로깅메시지 출력
LOG_LEVEL_OFF : 모든 로깅메시지를 출력하지 않음

이제 파일의 상단에 헤더파일과 로깅 레벨을 선언하고, 로깅메서드를 사용한다

#import "DDLog.h"
// 모든 로깅메시지 출력하도록 레벨 설정
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
// 에러 레벨 로깅메시지 출력
DDLogError(@"fail!!");
커스텀 로거를 작성하는 방법 또는 파일로거를 작성하는 방법은 프로젝트 사이트를 참고하자~