Protocol 과 Category

Objective-C 2011. 3. 22. 23:53



이번 포스팅에서는 Protocol과 Category에 대해서 알아보자.  Protocol은 자바의 인터페이스에 유사한 개념이다. 
Protocol은 공식 프로토콜과 비공식 프로토콜이 있으며, 그 중 공식 프로토콜이 자바의 인터페이스에 해당한다.
Category는 상속의 대안으로 클래스를 변경하지 않고도, 새로운 기능(메서드)을 추가할 수 있는 메커니즘이다. 
프로토콜과 카테고리에 대해서 좀 더 자세히 알아보자. 


1. Protocol 이란? 
자바언어의  Interface에 해당하는 개념으로 @protocol 지시어를 사용하여 정의한다. @protocol 을 구현하는 모듈은
프로토콜이 규약하는 모든 메소드를 반드시 구현해야하는 프로토콜을 공식 프로토콜(Formal Protocol)이라고 하며, 
구현해도 되고 구현하지 않아도 되는 프로토콜을 비공식 프로토콜 (Informal Protocol)이라고 한다.  공식 프로토콜은 
@required 지시어를,  비공식 프로토콜은 @optional 지시어를 사용한다.  
(비공식 프로토콜은 런타임시에 비공식 프로토콜을 구현하는 메소드가 존재하는 경우에만 호출된다.)
공식프로토콜의 모든 메소드들이 구현되지 않으면 컴파일 시에 에러가 발생할 것이다.  2개 이상의 프로토콜을 구현할
경우 ,(콤마)를 구분자로  구현할 프로토콜을 <> 안에 나열한다.

 Objective-C 에서는 Java 와는 다르게 컴파일 타임에 프로토콜 할당에 대해 타입검사를 수행하지 않는다.
 런타임시 메시지가 호출될 때, 해당하는 메소드가 구현되지 않았다면 selector  exception 예외가 발생한다. 

@interface VenusAttacks : Game
...
@end

@class Thing;

@protocol Living
- (float)age;
- (float)health;
(NSDictionary*)healthInfo;
@end

@protocol Communicating
- (NSArray*)recipientsInRange;
- (void)sendMessage:(NSString*)message to:(id)recipient;
@end

@interface Thing : NSObject 
...
@end

@interface Character : Thing 
...
@end




1.1 비공식 프로토콜(Informal Protocol)
비공식 프로토콜은 모듈클래스가 반드시 구현하지 않아도 되는 프로토콜이다.
비공식 프로토콜은 (반드시 구현하지 않아도 되기 때문에) 유연하고 동적이지만,  추가적인 문서화와 프로그래머들 간에
(통일된 사용을 위해) 협조가 요구된다.  비공식 프로토콜에서는  메서드가 구현되었는지 검사하는 코드가 들어가며  
그 형태는 아래 코드와 같다. 자바에서는 윈도우 종료시 이벤트를 인터셉트 하기위해서,  해당하는 이벤트에 대한 리스너를
등록하고, 이벤트가 발생하면 리스너를 통해 통보받는다.  Objetive-C 에서는  delegate 가 윈도우 종료시 Notification 을
통보받는 메소드를 비공식 프로토콜로 선언하고 있다. 
 
BOOL shouldClose = YES;
if ([delegate respondsToSelector:@selector(windowShouldClose:)])
   shouldClose = [delegate windowShouldClose:self];
if (!shouldClose)
return;





1.2 공식프로토콜과 비공식 프로토콜의 조합(Combining Formal and Informal Protocols)

프로토콜은 비공식 프로토콜과 공식 프로토콜이 혼합되어 사용되어지는 경우가 많다.
프로토콜에 선언한 메소드는 디폴트로 공식 프로토콜이며, @required 이라는 지시어를 명시적으로 사용할 수 있다. 
비공식 프로토콜은 @optional  지시어를 사용한다.

다음은 TableDataSource 프로토콜이며, 테이블 행의 개수를 반환하는 메소드와 해당하는 열과 행의 셀객체를 반환하는 
메소드는  @required 로 , 테이블 특정 행과 열의 셀객체를 변경하는 메소드는 @optional 로 선언되어 있어서, 비공식 프로토콜이
구현되지 않았을 경우,  read-only 로 동작하게 된다. (구현 유무에 따라 동작용도를 변경할 수 있다. )

@protocol TableDataSource
@required
- (int)numberOfRowsInTable:(Table*)table;
        - (id)table:(Table*)table objectForColumn:(int)col row:(int)ro
@optional
- (void)table:(Table*) setObject:(id)object forColumn:(int)col row:(int)row;
@end

 





 
2. 카테고리(Categories)
클래스 정의 일부에 이름을 붙힌 조각이라 할 수 있다..  자바에서는 단일블럭에서 하나의 클래스 정의가 모두 이루어지지만, 
Objective-C에서는 한 클래스 정의를 카테고리를 사용하여 여러 파일에 분리하여 정의할 수 있다.
카테고리를 사용함으로써, 복잡한 클래스를 관련이 되어 있는 인스턴스 변수와 메소드들의 그룹으로 분리하여
복잡성을 줄이고,  프로그래머들이 독립적으로 개발할 수 있도록 해준다. 
이외에도 카테고리는 클래스 정의(@interface) 와 독립적으로 클래스 메소드를 추가할 수 있게하며,
클래스 구현의 일부를 은닉할 수 있는 메커니즘을 제공한다. 클래스와 카테고리는 별도의 헤더파일과 모듈파일에
저장될 수 있으며, 해당 클래스를 사용하는 클라이언트 클래스는 관련있는 헤더파일만을  import 하여 사용한다. 

// 카테고리를 사용하지 않고 단일 클래스에 정의한 경우

@interface RecipeBoxController : NSObject {
       NSMutableArray*recipes;
NSMutableDictionary*  recipeIndex;   
}
- (id)init;
- (Document*)newRecipe;
- (Document*)newShoppingList;
- (Document*)newShoppingListFromRecipes:(NSIndexSet*)recipeIndexes;
@end
@implementation RecipeBoxController
...
@end

다음 카테고리는 리스트 설정하고 획득하는 역할만을 수행한다.
// Category 를 사용하여 클래스를 분리 
@interface RecipeBoxController (ShoppingLists)
- (Document*)newShoppingList;
- (Document*)newShoppingListFromRecipes:(NSIndexSet*)recipeIndexes;
@end

@implementation RecipeBoxController (ShoppingLists)
...
@end




2.1 카테고리 사용하여 조직하기(Using Categories for Organization)
거대한 클래스를 다룰수 있는 기능적인 단위로 나누는 것이 카테고리의 주요한 용도이다.  이 개념은 한 클래스를 
모듈화 할 수 있게하며, 각 모듈간에 캡슐화와 의존성을 줄여준다. 빌터패턴이 카테고리를 사용하여 얻을 수 있는 응용의 한
예이다. 자바에서 빌드에서 복잡한 객체 생성의 과정이 별도 클래스로 분리되는데, 
Objective-C에서는 별도의 객체생성과정이 메인 클래스 정으로부터 카테고리를 사용하여 분리될 수 있다. 


 

  2.2 메서드 은닉 (Hiding Methods)
카테고리의 또 다른 사용용도는 클래스의 일부를 은닉하는 것이다. 자바에서는 private, protect 키워드를 사용하여
내부 데이터를 은닉한다.  아래는 카테고리를 사용하여 메소드 일부를 은닉하는 예이다.  Private 이름으로 선언된 카테고리는
컴파일러에게 무시되며, 해당 클래스를 사용하는 클라이언트 클래스는 카테고리의 메소드들을 볼 수 없다. 
두개의 클래스 선언을 별도의 헤더 파일로 분리할 수도 있다.  이때, 클라이언트 클래스는 ToasterController.h 헤더파일만을  
import 하여 사용하면 된다. 카테고리의 헤더파일은 관례적으로  (클래스이름+카테고리이름.h) 이라는 형태를 갖는다.
아래에서는 ToasterController+Private.h 라는 이름을 갖을 것이다.
이런한 개념을 확장하여 Objectiv-C 에서는 extension 이라는 개념을 제공한다.

@interface ToasterController : NSObject {
   @private
float darkness;
}
- (void)setDarkness:(float)level;
- (void)startToasting
- (void)stopToasting
@end

@interface ToasterController (Private)
- (float)darkness
- (CarrierState)carrierPosition;
- (NSTimeInterval)cycleTime;
- (void)setCycleTime:(NSTimeInterval)cycleTime;
- (void)finishedToasting:(NSTimer*)timer;

 
2.2 외부 클래스 확장 (Augmenting Foreign Class)
카테고리를 사용하여 원 클래스에 어떠한 변경을 하지 않고도, 새로운 메소드를 정의할 수 있다. 
아래 코드는 Foundation Framework 의 NSArray 클래스에  카테고리를 사용하여, 배열의 첫번째 요소를 얻는
(id)firstObject 메소드를 추가하고 있다.  

* 만일 카테고리와 클래스가 동일한 메소드를 구현한다면, 카테고리 메소드가 클래스의 메소드를 대체하게 되어, 본 클래스의
메소드를 호출하지 못하게 된다.  동일한 메소드를 구현하는 카테고리가 2개 이상 있게되면 런타임에 무엇이 호출될지는 예측할 수 없다.
@interface NSArray (MyCollectionAdditions)
- (id)firstObject;
@end
@implementation NSArray (MyCollectionAdditions)
- (id)firstObject {
   return ([self objectAtIndex:0]);
}
@end




2.3 Extesions
Extension 은 익명 카테고리이다. 다만 Extension은 클래스의 @implementaion 구현부아 아닌  클래스의 @interface 정의부를
분리하기 위해 사용한다. extension 의 @interface 지시어는 비어있는 카테고리 이름을 사용한다.  extensions 내에 선언된 메소드들은 동일하게 @implentation  에서 모두 구현되어져야 한다. 카테고리와 extension은 컴파일 타임에만 프로그래머와 컴파일러로부터 메소드 선언을 은닉할 수 있으며, 런타임시에는 introspection(리플렉션)을 사용하면 노출될 수 있다. 

@interface CaseDocument : NSObject {
@private
   CaseNumber caseNumber;
}
- (CaseNumber)caseNumber;
@end
@interface CaseDocument()
- (void)setCaseNumber:(CaseNumber)number;
@end
@implementation CaseDocument
- (CaseNumber)caseNumber { return (caseNumber); }
- (void)setCaseNumber:(CaseNumber)number { caseNumber = number; }



 
 

'Objective-C' 카테고리의 다른 글

Objective-C 문자열 조작 메서드  (0) 2011.04.29
다중인자를 갖는 PerformSelector 메시지  (0) 2011.03.23
Protocol 과 Category  (0) 2011.03.22
Objective-C 훑어보기  (2) 2011.03.20
[Cocoa] Cocoa Design Pattern  (0) 2011.03.20
HMAC-SHA1 구현  (0) 2011.03.03

Objective-C 훑어보기

Objective-C 2011. 3. 20. 23:40



iPhone 의 인기와 더불어 주목받고 있는 Objective-C 언어에 대해서 간략히 알아보자. 
Objective-C 언어는 C 언어 기반위에 객체지향적인 요소를 가미한 언어로서, 객체지향 언어의 원류인  Smalltalk 에서
그 개념을 많이  차용했다고 한다.  이 포스팅에서는 언어의 구체적인 설명은 하지않고,  간략히 어떤 언어 요소들이
있는지만  알아보겠다. 



1. Objective-C 에서 클래스 정의 
#import "SuperClass.h"
@interface NewClass : SuperClass {
   int instanceVariable;
}
- method;
- methodWithParameter:param;

@end
@implementation NewClass

- method {
   return (nil);
}
- methodWithParameter:param {
   return (nil);
}



2. Object Pointers
SpecificClass * specificObject;
id anyObject;
id 타입은 void * 형 포인터로 어떤 객체 포인터도 할당하거나 할당받을 수 있다.


3. Sending Messages
[object method];
[object methodWithParam:param];

objective-c에서는 메시지를 호출한다고 하기 보단, 객체에게 메세지를 보낸다고 한다. 



3. Naming Method

Objective-C에서는 메시지의 이름을 부여할 때,  메소드가 취하는 행위를 기술하도록 이름짓는다. 메시지의 이름은
다음과  같은 형식을 따른다.
반환타입+메시지행위+첫번째 인자이름: 첫번째 인자 이름:  두번째 인자이름...
반환타입이나 인자가 존재하지 않는 경우 생략될 수 있다. 


- valuePrepositionParameter:parameter
- objectForKey:key
- stringByAppendingString:string;
- numberFromString:string;




4. Parameter and Return Types
인자와 반환값은 괄호사이에 기술한다
- (id)objectForKey:(id)aKey;
- (NSMenuItem)itemWithTag:(NSInteger)aTag;
- (unichar)characterAtIndex:(NSUInteger)index;
- (NSString*)stringByAddingPercentEscapeUsingEncoding:(NSStringEncoding)encoding;
- (void)runInNewThread;
- (void)addAttribute:(NSString*)name value:(id)value range:(NSRange)aRange;



5. Method Selectors
메서드의 이름, 인자변수의 이름, 인자의 타입이 유일한 식별자를 구성하며, Java 의 메소드 시그니처와 동일한 의미를 갖는다. 
(NSfont*)fontWithFamily:(NSString*)family traits:(NSFontTraitMask)fontTraitMask weight:(NSInteger)weight size:(CGFloat)size

의 메소드 식별자는  fontWithFamily:family:traits:weight:size 가 된다.  이 식별자는  실제로 selector 라 불리는 숫자 상수와 
맵핑이 되며, 그 숫자 상수는 SEL 타입을 갖는다.  @selector()  지시어는  특정 메소드의 selector  상수를 반환한다
SEL selector = @selector(fontWithFamily:traits:weight:size)


6. Instance Variable

isa 인스턴스 변수
모든 Objective-C 객체가 상속받는 인스턴스 변수로,  클래스 자체를 정의하는  Class  타입의 객체이다.
- (Class)class 메소드 호출을 통해 얻을 수 있으며, - (NSString *)className 통해 클래스의 이름을 문자열로 얻을 수 있다.
 

Properties
객체의 프로퍼티를 정의하는 키워드로 @property  와 @synthesize 가 있다. @property 는 클래스가 계약을 지킬 것이라고 컴파일러에게 알려주는 역할을 한다.  @synthesize 는 컴파일러에게 해당 인스턴스 변수에 대한 getter와 setter  를 생성하라고 지시한다    
 int tag;

라는 인스턴스 변수에 대해 @property int tag; @synthesize tag;를  선언되면 컴파일러는
- (void)setTag:(int)newTag  
- (int)tag

와 같은 한쌍의 접근자를 생성할 것이다. 이렇게 생성된 접근자는 디폴트로 threadsafe 하다. @property만 선언했다면, 구현파일에
 getter 와 setter를 기술하는 것은 프로그래머의 몫이다. 즉 @synthesize를 통해 자동생성하거나,   프로그래머가 직접 구현해줘야 한다. 그렇지 않으면 컴파일 에러가 발생할 것이다.

@property 지시어와 함께 사용할 수 있는 속성들이 있는데, getter와 setter울 생서을 위한 속성이 된다. 

 Property Attributes 

 Attribute  Description
 readonly  getter 만을 생성한다. readwrite 속성과 상반된다. 
 readwrite  디폴트 값이며,  getter 와 setter 를 모두 생성한다. 
 copy  setter 에서 인자를 인수변수에 저장할 때, 인자의 복사본을 저장한다.  객체 포인터 타입에만 유효한 속성이다.  (primitive 타입은 항상 복사되기 때문) 리시버가 센더의 인자값을 변경하지 못하게
할 때 사용한다. 
 assign  인스턴스 변수에 레퍼런스 카운팅 없이 단순 할당한다.  가비지 컬렉션이 존재하는 환경에서 참조를 유지하고 싶거나, 인자값이 변경불가능할 때 사용한다. 
 retain  assign과 유사하며, 가비지 컬렉션이 없는 Managed memory 환경에서 객체의 참조 카운터를 증가
시킨다. 
 getter=name 지정한 name 이름으로 getter 를 생성한다.  getter와 setter의 표준 패턴을 위반해서  Key-Value Coding 과 같은 기술들이 인식할 수 없게 될수도 있기 때문에 사용에 주의해야한다. 
 setter=name 지정한 name 이름으로 setter 를 생성한다.
 nonatomic 디폴트로 생성된  setter 는  atomic 속성을 갖는다. 락을 사용하여 thread-safe 하지만, 상당한 오버헤드를 수반한다. 쓰레드의 개입이 없을 경우,  명시적으로 nonatomic 으로 선언하여 performance 를 높이는게 좋다.
 
 

7. Class Methods
클래스 메소드는 메소드 이름 앞에 + 기호를 갖는다. 모든 클래스는 런타임에  그 클래스 인스턴스의 identity와 behavior를  정의하는 
하나의 Class  객체를 생성한다.  클래스는 이 Class 객체가 응답하는 메시지를 정의한다.  Objective-C의 클래스 메소드는 자바와 비슷한 역할을 하지만 기술적으로는 다르다.
@interface RandomSequece : NSObject {
long long int seed;
}
+ (NSNumber*)number;
+ (NSNumber*)string;
- (NSNumber*)nextNumber;
- (NSString*)string;
@end
...
NSNumber *n = [RandomSequence number];
NSString *s = [RandomSequence string];

관례적으로  클래스 메소드를 먼저 정의하고, 인스턴스 메소드를 정의한다. 자바에서 클래스메소드는 this 키워드를 사용할 수 없지만,  Objective-C 의 클래스메소드 에서는 self  키워드를 사용할 수 있다.  클래스 메소드에  self 는 Class 객체를 가리키며, 인스턴스 메소드 처럼 서브클래스에 상속할 수 있으며 오버라이딩 또한 가능하다.

@interface Classy : NSObject {
   + (void)greeting;
+ (NSString*)salutation;
}
@end

@implementation
+ (void)greeting {
NSLog(@"%@, world!", [self salutation]);
 }
+ (NSString*)salutation {
NSLog(@"Greetings");
}
@end
@interface Classic : Classy
   + (NSString*)salutation;
@end
   + (NSString*)salutation {
     return (@"Hello");
}
@end
...
[Classy greeting];    // Logs "Greetings, world"
[Classic greeting];   // Logs "Hello, world!" 

[Classic greeting]  은 Classy Class 객체에 메시지를 보내고,   [self salutation]은 해당 Cass 클래스 객체에 메시지를 보낸다.
[Classic greeting] 은 greeting 메소드가 없지만, 수퍼클래스로부터 상속받은 greeting 메시지를 호출하며, [self salutation] 은 Classic
Class 객체에 메시지를 보낸다. 

 

8. Constructing Objects
java 에서는 객체를 생성하는 문법을 존재하여, 객체를 생성하는 방법을 명시적으로 제약한다.  반면 Objective-C 는 객체를
생성하는 방법을 클래스 설계자가 결정하도록 한다. 
 
객체를 생성하는 과정은 2가지 과정을 거친다. 
1) 메모리 구조 할당
2) 인스턴스 초기화

@interface RandomSequence : NSObject {
long long seed;
}
- (id)init;
- (id)initWithSeed:(long long)startingSeed;
@end

@implementation RandomSequence
- (id)init {
self  = [super init];
if (self != nil) {  
   seed = 1;
}
return (self);
}
- (id)initWithSeed:(long long)startingSeed {
self = [super init];
if (self != nil) {
seed = startingSeed;
}
return (self);
}
@end
...
RandomSequence *r1 = [[RandomSequence alloc] init];
RandomSequence *r2 = [[RandomSequence alloc] initWithSeed:-43 ];
RandomSequence * r3 = [RandomSequence new];

alloc 은 NSObject 에 정의된 클래스 메소드 +(id)alloc   이다.  객체를 생성하기 위해 클래스 참조를 사용하여 메모리를 할당하고,
그것을  isa 변수에 할당한다. 그리고 인스턴스를 모두 0으로 채운뒤, 참조를 반환한다. 이 시점에서 요청된 객체가 존재하지만
아직 초기화는 되지않았다.  init 메시는 객체를 초기화를 담당하며, 이메시지가  반환되면 깨체는 사용할 준비가 된 상태가 된다. 
new 도 NSObject의 클래스 메소드로 내부적으로 alloc과 init 를 호출한다

- (id)init...계열의 메소드를 오버라이드 하거나 재정의 해서 객체생성을 커스터마이징 할 수 있다. 
+ (id)alloc  또는 +(id)new 를 재정의 해서는 안된다. 



9. Writing an init Method
Objective-C에서 생성자를 작성하기 위해서 4가지 계약을 충족해야 한다
1) 수퍼 클래스의 생성자를 호출한다  :  [super init]
2) 자신의  self 변수를 업데이트 한다  :  self = [super init]
3) nil 객체 검사를 한다                          : if (self != nil ) { ... }
4) 자신의 포인터를 반환한다                : return self

모든 생성자는 수퍼클래스의 생성자를 호출해야한다. 자바는 언어 차원에서 이를 지원하지만,  Objective-C 는 프로그래머에게
책임이 있다.  self 를 변경하는 것은 자바 프로그래머에게는 이상하지만,  Class Cluster를 위해 핵심이 되는 부분이다.
nil 객체 검사를 하는 이유는, 반드시 메모리 할당이 성공한다고 보장할 수 없기 때문이다. 메모리 고갈로 인한 실패가 있을 수 있기
때문에 방어적인 프로그램을 위한 것이다.  모든 잘 작성된 생성자는 위의 4가지 조건을 만족해야 한다 

Chaining Initializers
자바에서는 명시적인 생성자 호출 문법을 가지고 있어서, 특정한 수퍼클래스의 생성자나 자신의 생성자를 호출 할 수 있다.
Objective-C에서는 이러한 문법은 없지만, 동일한 원리로  수퍼클래스나 자신의 init...계열의 메시지로 초기화가 가능하다.

Designated Initializer
Objective-C 의 많은 클래스들이 "설계된" 생성자를 갖으며, 객체 생성시 이를 사용하여 초기화를 해야한다
그렇기 때문에 이러한 클래스들을 사용할 경우에는 문서를 참조해야 한다. 

Convenience Constructors
클래스 메소드의 일반적인 용례는 복잡한 객체 생성 및 초기화를 간소화시킷는 것이다. 
Cocoa framework의  NSDictrionary  클래스는 [NSDictionary dictionary]  클래스 메소드를 제공하며, 이것은
[[NSDictionary alloc] init]  와 동일하다 


 
10. Destructors
Objective-C 에서도 선택적으로 - (void)finalize 메소드를 오버라이드 할수 있다. 이 메시지는 가비지 컬렉터에 의해 객체가 파괴되기
전에 호출된다. 모든 finalize 메소드는  반환되기 직전에 수퍼클래스의 finalize  메시지를 호출해야한다
- (void)finalize {
if (file != nil) {
[file close];
file = nil;
}
[super finalize]
}   

 
   
11. etc...

Inner / Anonymous Classes (내부 클래스와 익명클래스)
내부 클래스와 익명클래스는 코드 캡슐화나 어댑터로 사용되는데, Objective-C 에서는 비공식 프로토콜과 코드 블록을
사용하여 구현된다. (코드 블록은 변수에 실행가능한 코드 블록 할당을 허용하는 Objective-C 언어에 최근 추가된 개념이다)
 
Object Arrays
Objecitve-C에서는  C object 포인터 배열이나  NSArray 컬렉션 클래스를 사용하여 구현한다
 
final
final 키워드를 지원하지 않으며, 클래스 상속이나 메소드 오버라이딩을 막을 수 없다. 변수에 적용 할 경우,  const가 유사하다.
 
abstract
Objective-C 의 모든 클래스와 메소드는 구체 클래스이다 
 
package
Objective-C에는 패키지가 없으며, 패키지 범위 또한 없다. 
 
 


'Objective-C' 카테고리의 다른 글

Objective-C 문자열 조작 메서드  (0) 2011.04.29
다중인자를 갖는 PerformSelector 메시지  (0) 2011.03.23
Protocol 과 Category  (0) 2011.03.22
Objective-C 훑어보기  (2) 2011.03.20
[Cocoa] Cocoa Design Pattern  (0) 2011.03.20
HMAC-SHA1 구현  (0) 2011.03.03
  • sungsik81 2011.03.22 09:49 ADDR 수정/삭제 답글

    날 위한 포스트인가... 두둥...

  • 로그 @로그 2011.03.23 14:00 신고 ADDR 수정/삭제 답글

    예전에 자바 개발자를 위한 objective-c 보고 정리했던 거에요.. 정리라기 보단...
    책에서 그대로 가져온...^^;;

[Cocoa] Cocoa Design Pattern

Objective-C 2011. 3. 20. 01:44





'Objective-C' 카테고리의 다른 글

Objective-C 문자열 조작 메서드  (0) 2011.04.29
다중인자를 갖는 PerformSelector 메시지  (0) 2011.03.23
Protocol 과 Category  (0) 2011.03.22
Objective-C 훑어보기  (2) 2011.03.20
[Cocoa] Cocoa Design Pattern  (0) 2011.03.20
HMAC-SHA1 구현  (0) 2011.03.03

[Java] 어노테이션 사용하기

언어로그/Java 2011. 3. 19. 23:38



Annotation을 실제로 사용하는 예제를 알아보자.  

첫번째 예제는 UseCase라는 어노테이션을 정의한다. 이 어노테이션은 id와  description이라는 값을
멤버로 갖으며 각각 기능번호와 기능에 대한설명을 나타낸다.  Password 검사와 관련된 클래스에스는 각 메소드에 
UseCase 어노테이션을 사용하여 메서드들이 어떤 유스케이스를 구현하고 있는지를 표시한다. 
나중에 모든 유스케이스를 구현하는 모든 메소드들이 잘 구현되었는지 확인하기 위해  UseCaseTracker 를 사용하여 
어노테이션 정보를 출력한다. (코드는 Thinking in Java 4E 에 있는 예제코드를 사용하였다. )


1. UseCase 어노테이션 정의
메서드에 사용할 어노테이션이므로 @Target을 ElementType.METHOD를 설정하였고, 
 런타임 시에 사용되기 때문에 @Rention을 RetentionPolicy.RUNTIME 로 설정하여 class 파일에 어노테이션 정보가 남도록
지정하였다. 
package net.atgame.annotation.usecase;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
	public int id();
	public String description() default "no description";
}



2. PasswordUtil 클래스에 정의한  어노테이션을 사용한다. 
UseCase 어노테이션은 코드 작성간에는 주석의 역할을 하고,  코드 실행시에는 테스트로서의 역할도 한다.
import java.util.List;

public class PasswordUtils {
	
	@UseCase(id = 47, description = "passwords must contain at least one numeric")
	public boolean validatePassword(String password) {
		return (password.matches("\\w*\\d\\w*"));
	}
	
	@UseCase(id = 48)
	public String encryptPassword(String password) {
		return new StringBuilder(password).reverse().toString();
	}
	
	@UseCase(id = 49, description = "New passwords can't equal perviously used ones")
	public boolean checkForNewPassword(List prevPasswords, String password) {
		return !prevPasswords.contains(password);
	}
}




3.  UseCaseTracker를 사용하여 모든 유스케이스를 구현하는 메소드들이 작성되었는지 확인한다. 

아래 코드에서는 리플렉션을 사용하여 유스케이스 아이디 47, 48, 49, 50 을 사용하는 메소드들을 검색하여 
그 메서드의 어노테이션 멤버를  출력하고 있다.  이 프로그램 실행을 통해 어떤 유스케이스가 아직 구현되지 
않았는지 알 수 있다. 
package net.atgame.annotation.usecase;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class UseCaseTracker {
	
	public static void trackUseCases(List useCases, Class cl) {
		for (Method m : cl.getDeclaredMethods()) {
			UseCase uc = m.getAnnotation(UseCase.class);
			if (uc != null) {
				System.out.println("Found Use Case:" + uc.id() + " " + uc.description());
				useCases.remove(new Integer(uc.id()));
			}
		}
		
		for (int i : useCases) {
			System.out.println("Warning: Missing use case~" + i);
		}
	}
	
	public static void main(String[] args) {
		List useCases = new ArrayList();
		Collections.addAll(useCases, 47, 48, 49, 50);
		trackUseCases(useCases, PasswordUtils.class);
	}
}

어노테이션이 어떻게 정의되고 사용되는지 보여주기 위해 위 예제를 사용하였다. 어노테이션(UserCase)을 정의를 했다면, 
그 어노테이션을 처리하기 위한 클래스(UseCaseTracker)도 반드시 작성해야함을 눈여겨보자.












 이제 좀더 실용적인 두번째 예제를 소개하겠다.  데이터베이스 접속 처리를 하기위해 데이터베이스 종류에 맞는 
SQL을 작성하여 DBMS에 전송해야한다.  데이터베이스에 맞는 SQL 작성! 이 상당히 코드를 지저분하게 만들수 있기 때문에
모듈화가 잘 고려되어야 한다. 데이터베이스별 SQL 작성을 안할 수는 없지만  어노테이션을 사용하면, DB 접속에 관한 부분을
클라이언트 프로그래머에게 투명하게 처리할 수 있다. 그 의미에 대해서 알아보자 


1. 테이블명을 나타내기 위한 DBTable 어노테이션을 정의한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
	public String name() default "";
}


2. 테이블 컬럼의 타입을 나타내기 위한 SQLInteger 와 SQLString 어노테이션을 정의한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
	String name() default "";
	Constraints constraints() default @Constraints;
}



3. 제약조건을 나타내기 위한 Constraint 어노테이션을 정의한다.
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
	int value() default 0;
	String name() default "";
	Constraints constraints() default @Constraints;
}



4. Memeber 모델 클래스에 정의한 어노테이션들을 사용한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
	boolean primaryKey() default false;
	boolean allowNull() default true;
	boolean unique() default false;
}



5. TableCreator 클래스에서 어노테이션 정보를 사용하여, 데이베이스 생성 SQL를 만들어낸다. 
package net.atgame.annotation.database;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class TableCreator {
	
	public static void main(String[] args) throws Exception {
		if (args.length < 1) {
			System.out.println("arguments: annotated classes");
			System.exit(0);
		}
		// 모든 엔티티 클래스를 읽음 
		for (String className : args) {
			Class cl = Class.forName(className);
			// 클래스 어노테이션 가져오기 
			DBTable dbTable = cl.getAnnotation(DBTable.class);
			if (dbTable == null) {
				System.out.println("No DBTable annotations in class " + className);
				continue;
			}
			String tableName = dbTable.name();
			
			if (tableName.length() < 1) {
				tableName = cl.getName().toUpperCase();
			}
			List columnDefs = new ArrayList();
			
			/// 필드목록 조회 
			for (Field field : cl.getDeclaredFields()) {
				String columnName = null;
				Annotation[] anns = field.getDeclaredAnnotations();
				if (anns.length < 1) {
					continue;
				}
				if (anns[0] instanceof SQLInteger)  {
					SQLInteger sInt = (SQLInteger)anns[0];
					if (sInt.name().length() < 1) {
						columnName = field.getName().toUpperCase();
					} else {
						columnName = sInt.name();
					}
					columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
				}
				if (anns[0] instanceof SQLString) {
					SQLString sString = (SQLString)anns[0];
					if (sString.name().length() < 1) {
						columnName = field.getName().toUpperCase();
					} else {
						columnName = sString.name();
					}
					columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
				}
				
			}
			StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + " (");
			for (String columnDef : columnDefs) {
				createCommand.append("\n " + columnDef + ",");
			}
			// 마지막 쉼표제거 
			String tableCreate = createCommand.substring(0, createCommand.length()-1) + ");";
			System.out.println("Table Creation SQL for " + className + " is :\n" + tableCreate);
		}
		
	}
	
	private static String getConstraints(Constraints con) {
		String constraints = "";
		if (!con.allowNull()) {
			constraints += " NOT NULL";
		}
		if (con.primaryKey()) {
			constraints += " PRIMARY KEY";
		}
		if (con.unique()) {
			constraints += " UNIQUE";
		}
		return constraints;
	}
}



어노테이션을 읽어 데이터베이스 SQL을 생성하는 부분에 주목하자. 
Model 클래스 어노테이션 정보를 읽어서 DB에 해당하는 SQL를 생성하고, 테이블을 생성하게 할 수 있다.  
더 나아가 getter 와 setter 메소드를 오버라이드하여 인스턴스에 대한 조작만으로 데이터베이스 CRUD(등록,조회,수정,삭제)
연산을 처리하게 할 수도 있을 것이다. 이렇게 하면 데이터베이스 종류에 따른 번거로운 처리를 클라이언트 프로그래머에게
좀더 투명하게 사용할 수 있다. 


이상 어노테이션을 사용하는 예제를 간략히 알아보았다. 어노테이션을 잘 활용하면 간결하고, 투명한 코드를 작성할 수 있다.
 하지만 언제나 그렇듯 "잘 활용하면" 이라는 조건이 붙는다.  어떻게 잘 활용할지에 대한 생각은 이제 개인의 몫이다.
 

'언어로그 > Java' 카테고리의 다른 글

(2) 리플렉션(Reflection) 사용하기  (0) 2011.04.07
(1) 리플렉션(Reflection)  (3) 2011.04.07
예외처리 (Exception Handling)  (0) 2011.03.24
[Java] 어노테이션 사용하기  (1) 2011.03.19
[Java] 어노테이션(Annotation)  (2) 2011.03.15
[자바] (1) 자바소개  (0) 2011.03.10
  • 장곰부대 2018.06.12 09:20 신고 ADDR 수정/삭제 답글

    안녕하세요 블로그 글 잘보고 있습니다.

    궁금한 점이 있어서 질문 드려봅니다.

    마지막 코드 약 22번째 줄에서
    String tableName = dbTable.name();
    if(tableName.length() < 1) {
    tableName = c1.getName().toUpperCase();
    }
    이걸 왜하는 건지 궁금합니다.
    아예 처음부터
    tableName = c1.getName() 이런식으로 처리하면 안됩니까?

[SVN] Subversion 명령들

개발도구로그 2011. 3. 18. 13:15



 Subversion 에서 사용되는 기본적인 명령들을 알아보자.  IDE (통합 개발환경)을 사용하면 쉽게 사용할 수 
있는 기능들이지만 커맨드 사용법을 알아두면 유용하게 사용할 수 있다.  커맨드를 직접 사용해 보면 내부동작을
좀 더 쉽게  이해할 수 있으며 특히 자동화 스크립트를 작성할 때 도움이 된다.  
(소스관리 프로젝트를 위해 svn 명령을 사용할 경우와 svn 자바 라이브러리를 다룰 때도 도움이 된다. )


다음과 같은 순서로 Subversion 사용방법을 알아보겠다. 
1 저장소 생성
2 프로젝트 생성
3 프로젝트 저장소에 임포트(import)
4 작업본(working copy)  내려받기(checkout) 
5 woking copy에 파일 추가(add)
6 로컬 수정사항 커밋(commit)
7 저장소 변경사항 업데이트 받기 (update) 
8 브랜치/태그 (저장소 파일 복사/이동)
9 저장소 파일 삭제 
10 working copy 정보 보기




1. 저장소(Repository) 생성
가장 먼저 해야할 일은 저장소를 생성하는 작업이다. 저장소 생성에 관련된 내용은 지난 포스팅을 참고하기 바란다. 
svnadmin create --fs-type fsfs  MyRepository





2.  프로젝트 생성
이제 저장소에서 관리할 프로젝트를 위한 최초 디렉토리 구조를 로컬상에 생성한다. 
다음과 같은 구조로 "문서관리 시스템"을 위한 디렉토리 구조를 생성하였다. 







3. 프로젝트 저장소에 임포트(import) 
이제 로컬에 생성한 프로젝트를 저장소로 옮기자. 이 작업은 공유를 위한 프로젝트 최초 기본파일을 구성하는 단계이다. 
아래 명령으로 로컬 프로젝트 파일을 저장소로 복사한다. 

 svn import  -m "First DMS Project Import"  ./dms

저장소에 프로젝트 파일들이 옮겨졌기 때문에, 로컬 상에 있는 프로젝트 파일들을 삭제하자. 
이제 저장소에 있는 파일들이 모든 공유 문서들의 원본이 되고, 변경이 추가될 때마다 그 이력이 버전별로 관리가 된다. 




4. 작업본(working copy) 내려받기(checkout)
여러 사람이 저장소에 접근하여 산출물을 공유할 수 있게 되었다. 저장소에 있는 프로젝트 파일들을 
로컬상에 먼저 내려 받아보자.  로컬상에 자신이 다루게 되는 프로젝트 파일들을 작업본(working copy) 이라고 한다. 
작업본 내 모든 디렉토리에는 숨김파일 .subversion 이 생성되며 디렉토리 내 파일들의 목록과 수정사항들이 관리된다. 

svn checkout svn://repository_ip_address/MyRepository/trunk dms 

※ svn 명령들은 -r (revision) 옵션을 주어 아래와 같이 특정 리비전의 파일들을 다운 받을 수 있다. 
svn checkout -r  7 svn://repository_ip_address/MyRepository/trunk dms




5. 작업본에 파일 추가 (add) 
내려받은 작업본을 기반으로 자신이 담당한 모듈을 개발해 나가면 된다.  
새로운 파일을 작업본에 추가하고 싶은 경우,  새로운 파일을 생성하는 것만으로는 되지 않는다. 
작업본 파일목록에 명시적으로 새로 생성한 파일을 번영해 주어야 한다. 다음과 같은 명령을 사용한다. 
svn add build.xml




6. 로컬 변경사항 커밋 (commit)
작업본에 대한 모든 수정사항은 커밋을 해야 저장소에 반영이 된다.  파일 하나하나를 수정할 때마다 
커밋을 하는 것은 좋은 방법이 아니다.  에러가 발생하는 파일들을 저장소에 반영하면 업데이트를 받은 다른 팀웍들의
작업에도 영향을 주기 때문이다. 되도록이면 명확한 단위작업별로 구분하여 완전히 동작하는 수정사항만을 커밋하는
것이 좋다.  커밋을 할 때마다 -m 옵션으로 변경사항에 대한 메시지를 반드시 달아주어 변경에 대한 정보를 알수있게 하자
svn commit -m "Added build.xml ant file"




7 저장소 변경사항 업데이트 받기 (update) 
작업본 checkout 이후부터,  작업을 시작하기 전에 저장소 최신 변경사항을 update 받아 작업본을 최신버전으로
유지하는 것이 좋다. 저장소와 작업본 사이에 버전 차이가 커질 수록 나중에 commit이 충돌문제가 발생할 수 있기
때문이다.  다음 명령으로 저장송 변경사항을 작업본에 최신화시킨다. 
svn update




8 브랜치(branch)와 태그(tag) 
특정시점의 문서버전(예를 들어 1차 개발버전과 같은...)에 대한 복사본을 남겨두고 싶을 때 브랜치를  사용한다. 
브랜치를 사용하면 복사본이 생성되고,  이후 수정작업들은 다시 작업본에 계손 수정되어 나간다.   branch는 가지라는 뜻으로
현재 작업버전에 대해 분기점이 되는 버전이라는 의미를 갖는다.  태그는  브랜치와 비슷한 의미를 갖지만, 분기되어 
완전히 작업이 완료된 버전(첫번째 릴리지 버전과 같은...)이며  이름표를 달아서 알아보기 쉽게 표시를 한다. 
svn에서 브랜치와 태그를 생성하는 명령은 copy 로 동일하다.  태그 생성시는 t 옵션을 주어 이름표를 달아준다.

브랜치 생성 
svn mkdir -m "Create branch directory" svn://repository_ip_address/MyRepository/branches
 

svn copy -m "Creating release branch for 1.0"  svn://repository_ip_address/MyRepository/trunk  \
                                                                          svn://repository_ip_address/MyRepositorybranches/RB-1.0


태그 생성 
 svn mkdir -m "Create branch directory" svn://repository_ip_address/MyRepository/tags
 

svn copy -m "Tag release 1.0.0" svn://repository_ip_address/MyRepository/branches/RB-1.0 
                                                    svn://repository_ip_address/MyRepository/tags/REL-1.0.0




9. 저장소 파일 삭제 (delete)
저장소에 있는 파일을 삭제하는 명령은 다음과 같다. 파일 삭제후 update 를 수행하면 작업본에도 반영이 된다. 
 
svn delete -m  "Remove branch 1.0" svn://repository_ip_address/MyRepository/branches/RB-1.0 




10. working copy 정보보기 
다음명령으로 작업본 리비전 정보와 저장소 루트, 최신 수정일 등의 정보를 확인할 수 있다. 
svn info 






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

2D 맵 에디터 Tiled

게임개발로그 2011. 3. 16. 00:34




 1. Tiled 란?
Tiled 는 2차원 타일기반의 게임을 개발하기 위한 범용 맵 에디터로,  사용하기 쉬우며  다양한 게임 엔진과 함꼐 사용할 수 있다.
프리 소프트웨이며, QT 프레임웍을 사용하여 C++로 작성되었다. Tiled 특징은 다음과 같다.  




2. Tiled의 특징 
XML 기반 맵포맷의 범용 맵에디터이다. 
orthogonal 과 isometric 맵을 지원한다
커스템 객체들을 픽셀단위 정밀도로 배치할 수 있다.
undo/redo 와 copy/paste 를 지원한다. 
타일 ,레이어, 오브젝트, 맵에 커스텀 속성을 추가할 수 있다. 
외부에 변경된 타일셋을 자동으로 리로드한다. 
필요할 때, 타일맵의 크기를 변경할 수 있다. 
stamp와 fill brush 같은 호율적인 타일에디팅 도구를 제공한다. 





3. Tiled 설차하기
아래 사이트에서 Tiled를 다운로드하여 설치하자. 
http://www.mapeditor.org/





4. Tiled의 간략한 사용법
애플리케이션을 실행시키면 다음과 같은 인터페이스로 구성되어 있다. 



 Tiled Map Editor를 사용하여 2가지 방식의 맵을 생성할 수 있다. orthogonal 맵은 그림과 같은 하늘에 내려다 보는듯한 시점의 
맵이다. isometric 맵은 고전 RPG인 파랜드 택틱스 같은 게임에서 사용됐던 비스듬한 시점에서 보는 듯한 방식이다.
최초에 2가지 방식 중  타입을 지정하고, 타일 한개의 픽셀 크기와 가로, 세로가 몇개의 타일로 구성되어 있는지 크기를 지정
해서 맵을 생성해한다. 이렇게 생성된 맵파일은 .tmx 라는 확장자를 갖는다. 

우측하단의 Tilesets 은 맵을 구성하기 위해 필요한 타일들로만 구성된다.  타일이미지 샘플이라 생각할 수 있다. 원하는 타일을
선택하여
맵에 타일을 배치할 수 있다.  이와 같이  타일셋 이미지를 하나로 구성해야,  애플리케이션에서 로딩시 메모리를 절약
할 수 있다. 

이렇게 배치된 타일들은 게임에서 의미있게 해석되기 위해서 레이어들로 구분되어 그려진다.  
Background , Objects, Forground, Meta 와  
같은 레이어들은 서로 다른 z 축 값을 가지는데, 위 그림에서는 Background 레이어가
가장 바닥에 그려진다.   
각 레이어들은 동일한 깊이를 갖는 Sprite 집합 이라 생각할 수 있으며, 게임엔진에서 레이어 이름을 통해
레이어 내 Sprite 들을 얻어올 수 있다. 

tileset 에 있는 특정 타일들에는 타일속성을 키와 값 형태로 지정할 수 있으며, 게임엔진에서 특정 타일좌표에 있는 타일의 속성을
읽어와서  
게임 로직을 처리할 수 있다. (예를 들면, 아이템인지 장애물인지 판단할 수 있다.)
위 그림에서 Objects 라는 이름을 갖는 레이어만  오브젝트 레이어이며, 나머지 레이어들은 타일 레이어이다. 이름에서 의미하듯
오브젝트 레이어는  
배경이 아닌,  게임에서 특징한 의미를 갖는 객체들이 위치하는 레이어이다.  게임 로직을 구성하기 위해 사전에 미리 속성정보를 알아야 할 대상을 위해 사용한다. 




File 메뉴에서 새로운 맵을 생성하는 다이얼로그 창이다. 

이 맵은 32 pixel x 32pixel 크기의 타일이 가로, 세로 50개가 배치된 크기를 갖는다. 



Map 메뉴에서 새로운 타입셋을 추가하는 다이얼로그 창이다 




이렇게 생성한 맵을  저장한 파일 TileMap.tmx 의 내용은 다음과 같다.  
Cocos2D 게임엔진에서 이 파일을 읽어서 타일들의 정보들을 가져와 맵을 구성할 수 있다.  
<?xml version="1.0" encoding="UTF-8"?>

<map version="1.0"orientation="orthogonal"width="50"height="50"tilewidth="32"tileheight="32">

<tileset firstgid="1"name="tmw_desert_spacing"tilewidth="32"tileheight="32"spacing="1"margin="1">

<image source="tmw_desert_spacing.png"/>

</tileset>

<tileset firstgid="49"name="meta_tiles"tilewidth="32"tileheight="32"spacing="1"margin="1">

<image source="meta_tiles.png"/>

<tile id="0">

<properties>

<property name="Collidable"value="True"/>

</properties>

</tile>

<tile id="1">

<properties>

<property name="Collectable"value="True"/>

</properties>

</tile>

</tileset>

<layer name="Background"width="50"height="50">

<data encoding="base64"compression="gzip">

   H4sIAAAAAAAAA+3Uyw6CMBCF4a7EhYo7bwmp8ZIoiu//do6Ji4YgaZGWEf/Ft+uEczJkCmNMMTIbse1gpyC7ay+O4hrgJA4Kstd7hGbqMhOD7bGHDZxlH//T42bab1L9pt0V9riYz3epbHlT9vT92CoxEZmYKsjT5OxpIXKx9Hj7cAzdr87tMXSWJpmn+bvHLGAm5T+YR/TaXZWoxyqidcJ9fMsqyPCL2TBOVkEGAAAAAAAAAAAQxxPutT2PECcAAA==

</data>

</layer>

<objectgroup name="Objects" width="50" height="50">

<object name="SpawnPoint"x="77"y="432"/>

</objectgroup>

<layer name="Foreground"width="50"height="50">

<data encoding="base64"compression="gzip">

   H4sIAAAAAAAAA+3UQQ0AAAwCMZzMv8vJINlaBRceJNA17YDHbA9c5+cAAAAAAAAAfln1IckFECcAAA==

</data>

</layer>

<layer name="Meta"width="50"height="50">

<data encoding="base64"compression="gzip">

   H4sIAAAAAAAAA+3UUQqAIBAFQK9k9z9c9CeVZAm2ygwI/iy8VdeU1pM7ViSr9VGzfagZJZ/2bzOVNRH6OZSZ7s7+qSaK3vuIomWWZ5rz2fuomSUnV63/HP8wW2NFPu/I2ViTNwcAAAAAAOvaAbuBPXIQJwAA

</data>

</layer>

</map>




'게임개발로그' 카테고리의 다른 글

2D 맵 에디터 Tiled  (0) 2011.03.16
아이폰 2D 게임엔진 Cocos2D  (1) 2011.03.16
게임 개발 기초  (2) 2011.02.14
게임 시스템 모듈 구성  (0) 2011.02.13
DirectX 와 OpenGL  (0) 2011.02.13
3차원의 기초  (0) 2011.02.13

아이폰 2D 게임엔진 Cocos2D

게임개발로그 2011. 3. 16. 00:09



 아이폰용 2차원 게임엔진으로 많이 사용되고 있는 Cocos2D에 대해서 알아보자. 


1. Cocos2D의 역사 
Cocos2d는 파이썬 기반의 오픈소스 게임 라이브러리로 부터 시작했다. 아르헨티나의 Los Cocos 지역에 50명의 파이썬
개발자가 모여 컨런스를  가졌고,  2008년 1월 6명의 개발자의 의해 오픈소스 게임라이브러리로 태어났다. 이후 아이폰으로
포팅되었으며, 애플의 제약이 해제되면서 앱스토어에 다수의 게임이 cocos2D를 기반으로 개발되었다. 2010년  5월 1.0 버전이
릴리지 되었으며,  계속 발전중이다. 
OpenGL ES는 강력한 그래픽 엔진이지만, 상대적으로 어렵고 생산성이 떨어진다.  cocos2D는 이런 복잡함을 감추고  
상위수준에서 게임 애니메이션을 사용할 수 있도록 지원한다.  또한  Box2D 와 Chipmonk라는 물리엔진을 포함하고 있다.
Box2D는 C++ 언어로 작성되어 생산성이 좋은 반면 상대적으로 참고자료가 적다. Chipmonk는 역사가 더 오래되어 샘플코드 및 참고자료가 더 많다는 장점이 있다.  일반적으로 개발자들은 Box2D를 더 선호한다. 
최상위 수준에서 CCDirector, CCScene, CCLayer, CCSprite 객체를 사용하게 되며, 접두어로  CC를 사용한다.



2. Cocos2D 설치하기 
Cocos2D  템플릿을 Xcode에 설치하는 방법을 알아보자. 



1. Cocos2D 홈사이트에서 최신버전을 다운받는다. 
   http://cocos2d.org





2. 압축을 해제하고, 내부 폴더로 이동하여 터미널에서 다음과 같은 명령을 실행한다. 
./install_template.sh




3. Xcode를 실행하면 아래와 같은 Cocos2D 사용자 템플릿이 추가됐을 것이다. 





3.  Cocos2D 템플릿 삭제하기 
나중에 이 템플릿을 제거하고 싶다면, 다음 경로에서 해당 템플릿을 삭제하면 된다. 
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application





4. 2D 게임 개발을 위한 보조툴 
2D 게임 개발을 위한 보조툴인 이차원 맵 에디터인 Tiled와 Sprite 편집기인 Zwoptex 를 Cocos2D 와 사용하면 좀더 쉽게
아이폰용 게임을 개발할 수 있다. 다음 포스팅에서 이 툴들을 사용하는 방법에 대해서 간략히 알아보겠다. 




'게임개발로그' 카테고리의 다른 글

2D 맵 에디터 Tiled  (0) 2011.03.16
아이폰 2D 게임엔진 Cocos2D  (1) 2011.03.16
게임 개발 기초  (2) 2011.02.14
게임 시스템 모듈 구성  (0) 2011.02.13
DirectX 와 OpenGL  (0) 2011.02.13
3차원의 기초  (0) 2011.02.13
  • geek 2012.05.30 19:30 ADDR 수정/삭제 답글

    역시 로그님 ^^ 잘 보고 갑니다.

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

아이폰 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

[Lucene] java.lang.OutOfMemoryError : Java heap space

검색엔진로그 2011. 3. 15. 12:43



루씬을 사용하는 도중  Searcher 클래스에서 다음과 같은 에러가 발생했다. 

java.lang.OutOfMemoryError: Java heap space
        at org.apache.lucene.index.SegmentReader$Norm.bytes(SegmentReader.java:463)
        at org.apache.lucene.index.SegmentReader.getNorms(SegmentReader.java:1017)
        at org.apache.lucene.index.SegmentReader.norms(SegmentReader.java:1024)
        at org.apache.lucene.search.TermQuery$TermWeight.scorer(TermQuery.java:79)
        at org.apache.lucene.search.IndexSearcher.search(IndexSearcher.java:210)
        at org.apache.lucene.search.Searcher.search(Searcher.java:67)

 색인파일이 31G 정도 쌓였을 쯤 발생한 에러인데,  색인이 너무 커서, 검색도중  메모리가 부족해지는 현상이었다.  
임시방편으로 
set  JAVA_OPTS="-Xms512m -Xmx1024m"

명령으로  JVM의 힙메모리 크기를 늘려주었더니 뻣는 현상은 해결이 되었다.  하지만 색인 파일은 
자료가  쌓일수록 더 커질 것이기 때문에 언젠가 다시 메모리 부족현상이 발생하게 될 것이다.  
좀더 근본적인 해결책을 찾아봐야 겠다. 

현재는 다수의 램디렉토리와 단일 파일디렉토리를 사용하여 색인을 수행하고 있다.  즉 하나의 파일 색인기를 사용하고 있는데 
이 파일 색인기를 하나만 사용할 것이 아니라  다중으로 분리하여 구성하면 위 문제에 대한 해결책이 어느정도 될 것 같다. 
이렇게 하면 합너에 읽어들이는 색인파일의 크기도 줄어들 것이고, 최적화도 색인기별로 별도로 수행할 수 있으며, 
하나의 색인기가 에러가 발생하더라도 나머지 색인기를 사용할 수 있기 때문에 큰 장점이 된다. 
다중 색인기에서 동시에 검색을 지원하는 루씬 클래스도 지원되기 때문에 검색모듈을 구축하는 부분도 크게 달라지지
않는다.