이 글은 인트로스펙션이 무엇인가 아는데만 도움이 되는 수준이다. iOS Model구축을 위해서는 이 내용뿐 아니라 Key-Value Observing, Key-Value Coding도 아는게 좋다. http://blog.jidolstar.com/854


우리가 일상 c나 c++ 스타일로 개발하다보면 구조체나 클래스를 선언하고 그 선언된 구조체나 클래스를 malloc이나 new를 통해 직접 참조하고 사용한다. 이는 정적인 개발법으로 컨텍스트 통제를 철저히 하고 역할이 분명한 상태에서는 좋은 방법이다. 이 방법은 컴파일 타임에 에러를 추측할 수 있어 컴파일 기반 언어 장점을 가진다. 반대로 컴파일 타임이 아닌 런타임에 클래스를 생성하거나 메서드를 임의로 추가/삭제/교체 하던가 하는 동적인 개발법도 있다. 이 방법이 좋은 것은 매우 유연하고 통제에서 자유로워서 런타임에 컨텍스트를 내 마음대로 주무를 수 있다. 하지만 에러를 통제하기 힘들고 잘못사용하면 보안상 위험한데다가 메모리 관리도 신경쓰지 않으면 성능저하도 일으킬 수 있다는 단점이 있다.


요즘 개발법은 딱히 동적이냐 정적이냐에 치우치지 않으며 많은 언어가 이 두 방법을 섞어쓴다. 동적언어냐 정적언어냐라는 것은 사실 얼마나 한쪽에 치우쳐있는가로 갈리는 것뿐이다. 


리플렉션(Reflection)은 정의된 것을 찾는 행위로서 동적언어의 특징이다. 즉, 클래스나 메서드는 컴파일 입장에서 메모리에 올라가서 프로그램 입장에서는 그것을 호출하거나 사용할 수만 있는데, 리플랙션을 지원하는 언어는 그것 뿐 아니라 아에 정의자체를 찾아와서 조작할 수 있는 기능까지 지원한다. 



리플렉션(reflection)은 영어로 반사라는 뜻이 있다. 하지만 개발언어에서는 다른 뜻이며 한글로 반사라고 해석하면 안된다. 리플렉션은 결국 개발계의 고유명사이다. ^^



하지만 리플렉션은 컴파일 검사를 무시하게 되고 권한이나 보안을 깨뜨리기 된다. 그러므로 범용적으로 리플렉션을 쓰는 것은 그리 권장할 만한 것은 아니다. 대신 리플렉션이 정말 유용하게 쓰이는 것은 바로 펙토리 패턴(factory pattern)을 이용해 객체를 생성할 때이다. 정의된 클래스로부터 객체를 생성할 때 switch나 if문으로 분기처리하는 방법대신 클래스 이름만 알고 있으면 아름다운 방법으로 객체를 생성할 수 있다. Java의 경우 다음처럼 할 수 있다.


String[] clasNames = { "myClass1", "myClass2", "myClass3", "myClass4", "myClass5"};
try{
    @SuppressWarnings( "rawtypes" ) 
   Class[] t1 = {};
   for( int i = 0, j = clasNames.length ; i < j ; i++ ){ 
      _types.put( "test." + t0[i].substring( 2 ).toLowerCase(), 
       Class.forName( "com.cookilab.test." + t0[i] ).getConstructor( t1 ));
   }
}

위처럼 클래스의 Contructor를 저장해 두면 아래와 같은 형태로 클래스의 정의 참조가 필요없게 되어 조건문을 없애버리고 객체를 생성할 수 있다.

SuperClass r = (SuperClass) _types.get( "test.myClass2" ).newInstance(); 

iOS에서는 리플랙션(reflection)을 인트로스펙션 (Introspection)이라고 한다. 거의 마찬가지로 객체의 메타데이터(객체의 클래스, 구현 메소드, 프로퍼티, 프로토콜 등의 객체 정보)를 조사하는 과정을 의미한다.  아래 코드를 보자.

Class class = NSClassFromString(@"NSString");
id idOfClass = [[class alloc] initWithString:@"Reflection"];
SEL selector = NSSelectorFromString(@"length"];
NSLog(@"%d", (int)[idOfClass performSElector:selector];
[str release];

클래스를 직접적으로 접근하지 않고 NSString 클래스 명만 가지고 문자열 객체를 만들었고 또한 문자열 객체의 메시지도 직접 접근하지 않고 selector를 만들어 접근한다. 물론 selector가 있는지 없는지도 respondsToSelector:를 통해 알 수 있다. 아무튼 어느것 하나 진짜 정의를 직접 접근하지 않는다. 결국 이런 형태는 컴파일 타임에 체크할 수 없게되고 런타임이 되어야 에러인지 판명이 난다. 


이쯤되면 리플렉션이나 인트로스펙션이 재미있어질지 모르겠다. iOS의 경우 다음 글을 보면 이해가 될지 모른다.


Objective-C instrospection : http://soulpark.wordpress.com/2012/09/03/objective_c_introspection/

Objective-C Runtime Programming Guide : https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html


인트로스펙션을 사용하면 아래처럼 재미있는 유틸을 만들 수 있다. 이 유틸의 정적 메서드들은 동적으로 클래스나 객체의 프로퍼티를 직접 접근하지 않고 프로퍼티 이름만 가지고 정의 유무를 체크할 수 있고 값을 설정하거나 가져올 수 있다. 또한 클래스나 객체에 정의된 프로퍼티 이름 리스트까지 반환할 수 있다. 또한 프로퍼티의 클래스 정의도 가져올 수 있다. (ARC환경이라고 가정하자)

//클래스로부터 클래스 이름을 가져온다.
+(NSString*)stringFromClass:(Class)clazz {
    return NSStringFromClass( clazz );
}
//클래스 이름으로부터 클래스를 가져온다.
+(Class)classFromString:(NSString*)className {
    return NSClassFromString( className );
}
//객체로부터 클래스를 가져온다.
+(Class)classFromObject:(id)object {
    return [object class];
}
//객체로부터 클래스 이름을 가져온다.  
+(NSString*)classNameFromObject:(id)object {
    return NSStringFromClass( [object class] );
}
//객체에서 주어진 이름을 가진 프로퍼티의 값을 셋팅한다.
+(void)setPropValueOfObject:(id)object name:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([object class], [[NSString stringWithFormat:@"_%@", name] UTF8String]);
    object_setIvar( object, ivar, value );;
}
//객체에서 주어진 이름을 가진 프로퍼티의 값을 가져온다. 
+(id)getPropValueOfObject:(id)object name:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([object class], [[NSString stringWithFormat:@"_%@", name] UTF8String]);
    return object_getIvar( object, ivar );
}
//객체에서 주어진 이름의 프로퍼티를 가지고 있는가?
+(BOOL)hasPropAtObject:(id)object name:(NSString*)name {
    return [self hasPropAtClass:[object class] name:name ];
}
//클래스에서 주어진 이름의 프로퍼티를 가지고 있는가?
+(BOOL)hasPropAtClass:(Class)clazz name:(NSString*)name {
    objc_property_t p0 = class_getProperty(clazz, [name UTF8String]);
    return p0 ? YES : NO;
}
//주어지는 이름을 가진 객체의 프로퍼티를 찾아 그것의 클래스를 가져온다. 
+(Class)getPropClassOfObject:(id)object name:(NSString*)name {
    return [self getPropClassOfClass:[object class] name:name];
}
//주어지는 이름을 가진 클래스의 프로퍼티를 찾아 그것의 클래스를 가져온다. 
+(Class)getPropClassOfClass:(Class)clazz name:(NSString*)name {
    const char *nm = [name UTF8String];
    objc_property_t p0 = class_getProperty( clazz, nm );
    if( p0 == NULL ) {
        return NULL;
    }
    NSString *attr = [NSString stringWithFormat:@"%s", property_getAttributes( p0 )]; 
    NSArray *attrSplit = [attr componentsSeparatedByString:@"\""]; //"T@"NSString",R,V_test"에서 NSString만 추출해야 한다.
    NSString *className = nil;
    if ([attrSplit count] >= 2) {
        className = [attrSplit objectAtIndex:1];
    }
    if( className == nil ) return NULL;
    return NSClassFromString( className );
}
//클래스의 프로퍼티 이름을 배열로 가져온다. superInquiry는 해당클래스의 부모클래스 프로퍼티도 탐색할 것인지 결정하는 플래그다. 
+(NSArray*)getPropNamesOfClass:(Class)clazz superInquiry:(BOOL)superInquiry{
    if( clazz == NULL || clazz == [NSObject class] ) {
        return nil;
    }
    NSMutableArray *r = [[NSMutableArray alloc] init];
    unsigned int count, i;
    objc_property_t *ps = class_copyPropertyList( clazz, &count );
    for( i = 0; i < count; i++ ) {
        objc_property_t p = ps[i];
        const char *pn = property_getName( p );
        if( pn ) {
            [r addObject:[NSString stringWithUTF8String:pn]];
        }
    }
    free( ps );
    if( superInquiry ) {
        NSArray *sr = [self getPropNamesOfClass:[clazz superclass] superInquiry:YES];
        if( sr != nil ) [r addObjectsFromArray:sr];
    }
    return [NSArray arrayWithArray:r];
}

사실 위 코드만 가지고는 별로 유용하다고 볼 수 없다. 일단 유용성을 떠나서 동적으로 프로퍼티 정의가 있는지 체크하는 로직은 뭔가 캐쉬처리를 통해 속도개선해야 한다. 또한 인터페이스를 더욱 세밀하게 만들어 다양한 곳에 활용할 수 있게 만들 수 있다. 가령 값을 셋팅한다고 할때... (구축한게 아니라 이런걸 지원하는 걸 만들겠다는 것이다.)

MyModel *model = [[MyModel alloc]init];
[PropUtil setPropValueOfObject:model key:@"node1.node2[]" value:@"myValue"];

이렇게 만들면 model에 정의된 node1이름의 프로퍼티에 node2 프로퍼티가 정의되어 있고 node2는 배열이다. 여기에 문자열 "myValue"를 추가할 수 있다. 이쯤 되야 비로서 유용해질 수 있을 듯하다. 만약, 매크로(define)이나 c함수등의 인터페이스를 더 만들면 아래처럼도 가능해진다.

model( @"node1.node2[]", @"myValue"); 
NSString *v = model( @"node1.node2[#last]" ); //myValue 
즉, node1.node2배열에 "myValue"를 추가하고 방금 추가한 값을 돌려받는다. get, set은 인자의 숫자로 생각하면 된다. 즉, 짝수이면 set 홀수이면 get이다. 이걸 더 확장해서 생각해보면 아래처럼도 된다.

NSString *v = model( @"node1.node2[]", @"myValue", @"node1.node2[#last]" ); //myValue 

결국 set하고 마지막에 홀수 인자를 가지므로 그 값을 반환한다. 이쯤되면 아래와 같은 인터페이스도 가능하겠다.

NSString *v = model( @"node1.node2[]", [@"v1", @"v2", @"v3"], @"node1.node2[#last]", @"v4", @"node1.node2[#last]", __DEL__ , @"node1.node2[#last]" ); //v3

정적방식이였으면 아래처럼 해야한다.

MyModel *model = [[MyModel alloc]init];
MyNode1 *node1 = model.node1;
NSMutableArray *node2 = nodel1.node2;
[node2 addObject:@"v1"];
[node2 addObject:@"v2"];
[node2 addObject:@"v3"];
[node2 addObject:@"v4"];
[node2 removeLastObject];
NSString *v = [node2 lastObject];

길이가 확 늘어났다. 이뿐이겠는가? 위 코드는 컨테이너인 배열(NSMutableArray)의 참조를 받아 addObject, removeObject하고 있다. Model입장에서는 이런식으로 처리되는 것을 원치 않을 것이다. 뭔가 Model 내에 값이 바뀔 때, Model의 변화를 한번에 통지해야하는 방식으로 취해야 하거나 값의 유효성을 검증시켜야 한다는 상황이 발생하면 분명 해결하기 어려운 방법이다. 하지만 그에 앞선 코드인 1줄 코드를 보면 코드만 짧아진게 아니라 model()내부적으로 값의 변화를 감지해서 통제할 수 있게 된다. Model에 들어오는 자신의 데이터를 통제하고 변화를 외부에 통지할 수 있을때 진정한 Model이 아니었던가? 리플렉션(인트로스펙션)을 사용하면 바로 이러한 점을 장점으로 승화시킬 수 있게 된다.


참고 

SEL Method IMP 자료형에 대해서 : http://10apps.tistory.com/131

Java Reflection API의 사용 http://micropilot.tistory.com/entry/Java-Reflection-API-introduction

Java 리플렉션에 대한 재고(reflection)(1) http://www.hanb.co.kr/network/view.html?bi_id=1369 

Java 리플렉션에 대한 재고(reflection)(2) http://www.hanb.co.kr/network/view.html?bi_id=1370 


글쓴이 : 지돌스타(http://blog.jidolstar.com/851)




HTML5의 Canvas이미지를 가져다가 안드로이드에서 Bitmap으로 사용할 수 있다. (어떤 경우에 있겠지?) 아래 코드는 그걸 실험해 보려고 만든 테스트 코드이다. 일단 HTML5의 Canvas를 사용해 그림을 그린후 canvas.toDataURL().toString()을 사용해 Base64 문자열을 뽑아낸다. 


<!DOCTYPE HTML>
<html>
  <head>
    <style>
      body {
        margin: 0px;
        padding: 0px;
      }
      #myCanvas {
        border: 1px solid #9C9898;
      }
    </style>
    <script>
      window.onload = function() {
        var canvas = document.getElementById("myCanvas");
        var context = canvas.getContext("2d");

        // begin custom shape
        context.beginPath();
        context.moveTo(170, 80);
        context.bezierCurveTo(130, 100, 130, 150, 230, 150);
        context.bezierCurveTo(250, 180, 320, 180, 340, 150);
        context.bezierCurveTo(420, 150, 420, 120, 390, 100);
        context.bezierCurveTo(430, 40, 370, 30, 340, 50);
        context.bezierCurveTo(320, 5, 250, 20, 250, 50);
        context.bezierCurveTo(200, 5, 150, 20, 170, 80);

        // complete custom shape
        context.closePath();
        context.lineWidth = 5;
        context.strokeStyle = "blue";
        context.stroke();
		
	document.getElementById("canvasData").innerHTML = canvas.toDataURL().toString();
		
      };

    </script>
  </head>
  <body>
    <canvas id="myCanvas" width="578" height="200"></canvas>
    <div id="canvasData"></div>
  </body>
</html>


결과화면은 다음과 같다. 



base64 문자열을 복사해서 다음 안드로이드 코드에서 사용해본다.
package com.cookilab.base64bitmap;

import java.io.ByteArrayOutputStream;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Base64;
import android.view.View;

public class TestActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView( new TestView(this) );
    }
    protected class TestView extends View {
        public TestView(Context context) {
            super(context);
        }
        public String encodeTobase64(Bitmap image) {
            Bitmap immagex=image;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            immagex.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            byte[] b = baos.toByteArray();
            String imageEncoded = Base64.encodeToString(b,Base64.DEFAULT);
            return imageEncoded;
        }
        public Bitmap decodeBase64(String input) {
            byte[] decodedByte = Base64.decode(input, 0);
            return BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length); 
        }    
        public void onDraw(Canvas canvas){
            String base64 = "iVBORw0KGg............==";
            Bitmap bitmap = decodeBase64(base64);
            canvas.drawBitmap(bitmap, null, new Rect(0, 0, 300, 100), null);
        }
    }   
}




그래서 뭘 하고 싶은거냐?

자바스크립트와 안드로이드간 통신이 가능하므로 이미지도 전송할 수 있다는 것을 보여준 셈이다. 


끝 ^^


글쓴이 : 지돌스타(http://blog.jidolstar.com/822)

이 글은 iOS에서 Local Notification에 대한 전반적인 이해를 돕기위한 글이다. 그래서 유사형태인 APNS와 비교해보기도 했다. 검색해보면 Local Notification에 대한 문의는 있어도 이에 대해서 설명된 한글문서가 없는 것 같아 정리해보았다.

Local Notification에 대해
iOS 4.0 이상부터 Local Notification(지역알림, 내부통지, http://goo.gl/jFFsT)을 지원한다.  그러므로  iOS 4.0 미만 버전을 지원하는 어플의 경우 호환성을 유지할 수 있도록 개발해야할 것이다.

Local Notification은 어플이 실행중이 아니거나 Background, Foreground 상태에 관계없이 지정된 날짜와 시간에 필요한 최소한의 데이터를 전송하여 어플에게 전달해 실행하거나 다른 행동을 취할 수 있도록 구현할 수 있다. 심지어 Local Notification을 예약하고 iOS를 재부팅해도 정상적으로 동작한다.  이러한 형태가 가능한 것은 Local Notification 관리는 iOS 자체에서 하기 때문이다. 

Local Notification과 APNS(Apple Push Notification Service, http://goo.gl/fWknl)의 큰 차이점은 Notification을 위한 서버가 필요한가 아닌가의 차이에 있다. APNS는 서버가 준비가 되어 있어야 하고 Push를 위한 인증서도 발급해야한다. 처음 테스트 환경을 만들고 운용하는게 복잡한 편이다. 반면 Local Notification은 이런 절차가 전혀 필요없이 간단하게 코드상에서 처리할 수 있다. 이처럼 Local Notification을 사용하는 방법은 꽤 간단한 편이다. 하지만 APNS에 비해서 테스트하기가 좀 까다로운 면이 있다. 왜냐하면 Local Notification은 시간기반으로 하는 통지가 되는 형태이기 때문에 통지에 대한 제어권이 iOS에 있으며 개발자에게 있다고 볼 수 없기 때문이다. 반면에 APNS는 서버측에서 어떤 시간에 상관없이 개발자가 원하는데로 바로 통지를 할 수 있다. 

Local Notification와 APNS가 이처럼 차이점이 있지만 넘겨주는 데이터나 동작하는 방식은 거의 유사한 편이다.  그래서 어플내에서 동작하는 형태를 하나의 인터페이스로 만들어 운영할 수 있다. 이것이 가능한 이유는 둘다 PayLoad(http://goo.gl/mulVQ)가 유사한 형태를 가지며 통지되었을때 같은 동작을 하기 때문이다. (PayLoad는 일종의 메시지 데이터를 보유하는 패킷이나 프레임의 부분을 말한다.) PayLoad에는 Notification Type과 Custom Data를 담는다. Notification Type은 메시지 ,버튼 제목, 아이콘뱃지숫자, 사운드등으로 구성되어 있고 Custom Data에는 이외에 데이터가 필요한 경우  Dictionary형태로 구성된다. 하지만 APNS와 Location Notification간에 PayLoad데이터 관리의 차이는 있다. APNS는 Dictionary로 관리되며, Location Notification은 UILocalNotification 클래스(http://goo.gl/3TyTg)로 관리 된다. 

Local Notification은 APNS와 다르게 시간기반으로 운용된다. 이것은 iOS에서 스케쥴링을 해준다는 말과 같다. 그래서 통지가 일어나야하는 날짜와 시간을 정해줘야한다. 

Local Notification 사용법 
지금까지  Local Notification을 설명하면서 APNS와의 차이점도 알아보았다. 이러한 내용을 미리 언급하는 것은 처음 이를 접근할 때 Local Notification에 대한 전반적인 이해가 필요하기 때문이다. 여러가지로 복잡하고 잘 이해가 안되겠지만 실제로 해보면 이보다 쉬울 순 없다. 

Local Notification을 학습하기 위해서  Apple문서를 먼저 참고하는 것이 좋겠다.
 - About Local Notifications and Push Notifications : http://goo.gl/jFFsT

그럼 간단하게 Local Notification을 사용하는 방법을 알아보자. 크게 등록과 처리하는 방법을 구분해서 정리했다.

1. Local Notification 등록하기

Xcode상에서 Window Based 어플 프로젝트를 생성한뒤 AppDeleage 소스코드내에 application:didFinishLaunchingwithOptions: 메시지에 다음 코드를 넣는다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    

    // Override point for customization after application launch.

    

    [self.window makeKeyAndVisible];


    //통지시간 정하기 

NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];

NSDateComponents *dateComps = [[NSDateComponents alloc] init];

[dateComps setYear:2011];

[dateComps setMonth:3];

[dateComps setDay:22];

[dateComps setHour:15];

[dateComps setMinute:30];

[dateComps setSecond:0];

NSDate *date = [calendar dateFromComponents:dateComps];

[dateComps release];

UILocalNotification *localNotif = [[UILocalNotification alloc]init];

if (localNotif != nil

{

//통지시간 

localNotif.fireDate = date;

localNotif.timeZone = [NSTimeZone defaultTimeZone];

//Payload

localNotif.alertBody = [NSString stringWithFormat:@"내부통지 %@",date];

localNotif.alertAction = @"상세보기";

localNotif.soundName = UILocalNotificationDefaultSoundName;

localNotif.applicationIconBadgeNumber = 1;

//Custom Data

NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"mypage" forKey:@"page"];

localNotif.userInfo = infoDict;

//Local Notification 등록

[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];

}

[localNotif release];

    return YES;

}


이게 끝이다. Local Notification을 등록하는 시점은 어디라도 상관없다. 필요할 때 등록하면 된다. UILocalNotification 객체를 생성하고 통지시간, Payload, Custom Data를 설정한뒤 등록하면 지정된 시간에 통지가 발생할 것이다. APNS럼 APNS 서버에 DeviceToken을 받는 과정이 모두 생략되기 때문에 등록절차는 매우 쉬운 것을 알 수 있다. 

이 코드에서 Local Notification 등록을 scheduleLocalNotification을 사용했다. 필요한 경우에 스케쥴링 필요없이 즉각적으로 Local Notification을 줄 필요가 있는 경우에는 다음 메시지를 사용하면 되겠다. 

[[UIApplication sharedApplication] presentLocalNotificationNow:localNotif];


만약 fireDate를 현재시간보다 이전시간으로 등록하면 iOS는 바로 통지해버린다. 이 말이 그냥 쉽게 넘어갈 수 있는 문제인데 경험상 현재시간보다 이전시간으로 가는 경우는 없기 때문에 왠만하면 이전시간으로 등록하는 경우가 없도록 코딩을 해야 버그를 유발시키지 않을 것이다.

만약 이전에 등록된 Local Notification을 삭제하고 싶은 경우가 발생할 수 있다. 그런 경우에는 다음 메시지 중에 하나를 사용하면 되겠다.

[[UIApplication sharedApplication] cancelAllLocalNotifications];

[[UIApplication sharedApplication] cancelLocalNotification:localNotif];




2. Local Notification 처리하기 
앞서 Local Notification을 등록하는 방법을 알았다. 등록된 통지는 iOS에서 스케쥴링해준다.  어플의 실행여부, 및 iOS재부팅에 전혀 상관없이 동작하기 때문에 개발자는 이런부분에 대해 별다른 노력없이 통지 처리에 대한 로직만 만들면 된다.

내부 통지는 다음 3가지 형태로 처리가 가능하다. 이는 APNS와 유사하다.

[경우 1] 어플이 실행중이지 않은 경우 
이 경우에 iOS에 등록된 통지를 정해진 시간에 발생하면 자동으로 Alert창을 띄워준다. 거기에는 UILocalNotification에 Payload로 등록한 alertBody, alertAction이 반영되어 있으며 주어진 사운드를 들려준다. 어플의 아이콘에도 정해진 badge숫자가 표시된다. 


위의 경우 특별히 AppDelegate에서 application:didFinishLaunchingWithOptions: 메세지 내에서 통지된 UILocalNotification객체를 참고할 수 있다. Local Notification을 통해 어플이 실행되는 경우 별다른 작업이 필요하다면 이를 이용하면 유용할 것이다. 접근방법은 간단한다. 아래처럼 접근해서 사용하면 되겠다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    

    // Override point for customization after application launch.

    

    [self.window makeKeyAndVisible];

UILocalNotification *notif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];

if (notif != nil

{

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"알림" 

message:[NSString stringWithFormat:@"didFinishLaunchingWithOptions %@",notif.alertBody

  delegate:nil 

  cancelButtonTitle:nil 

  otherButtonTitles:@"확인",nil];

[alert show];

[alert release];

}

    return YES;

}


이 경우 다음과 같은 화면을 볼 수 있을 것이다. 



또한 AppDelegate 상에 application:didReceiveLocalNotification: 메시지를 구현하면 Local Notification을 처리할 수 있다. 이 메시지는 다음에 나오는 [경우2], [경우3]에도 동일하게 동작한다. 아래처럼 구현해보자.

-(void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification

{

application.applicationIconBadgeNumber = 0;

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"알림" 

message:[NSString stringWithFormat:@"didReceiveLocalNotification %@",notification.alertBody

  delegate:nil 

  cancelButtonTitle:nil 

  otherButtonTitles:@"확인",nil];

[alert show];

[alert release];

}


다음은 위코드가 실행될 때 화면이다.



[경우2]어플이 실행중이나 background에 위치해 있는 경우 
[경우1]과의 유일한 차이점은 application:didFinishLaunchingWithOptions: 를 통해 UILocalNotification 객체정보를 얻을 수 없다는 것이다. 이 경우에는 이미 어플이 실행중이기 때문에 어플이 처음 실행할때 최초 한번만 호출되는 AppDeleage의 application:didReceiveLocalNotification:가 실행될 수 없기 때문이다. 이 의미는 application:didFinishLaunchingWithOptions: 와 application:didReceiveLocalNotification: 에서 얻어지는 UILocalNotification 객체정보가 같더라도 서로 다른 일을 할 수 있도록 한다는 점을 시사한다.  

application:didReceiveLocalNotification: 메시지는 여전히 호출되므로 이때 UILocalNotification 객체를 처리하면 되겠다.


[경우3]어플이 실행중이며 foreground에 위치해 있는 경우
마지막으로 어플이 화면위에 실행중인 경우이다. 이때도 [경우1], [경우2]와 차이점은 iOS에서 직접적으로 Alert창을 띄워주지 않는다는 점이다. application:didFinishLaunchingWithOptions: 는 당연히 호출되지 않으며 application:didReceiveLocalNotification:  만 호출된다. 


Local Notification 처리에 대한 경우 1, 2, 3을 표로 정리하면 다음과 같을 것이다. 
   경고창   didFinishLaunchingWithOptions를 통한 UILocalNotification참고  didReciveLocalNotification메시지를 통해 UILocalNotification 참고
 경우 1 (어플 실행X)  O
 경우 2 (어플 background)  O
 경우 3 (어플 foreground)


지금까지 설명한 내용 대해서 더 자세히 알고 싶다면 다음 문서를 참고한다.
Scheduling, Registering, and Handling Notifications : http://goo.gl/qHVPi


팁(Tip)
Local Notification을 사용하면서 이런 점은 알아둘 필요가 있다. 

1. 등록시 현재 시간보다 빠른 시간을 등록하면 바로 통지가 된다. 이 점은 어플 버그를 유발할 수 있으므로 각별히 주의하길 바란다. 개발 경험상에서 말씀드린다. (여기서 버그는 실제 버그가 아니라 의도하지 않는 동작을 유발시킨다는 말이다.)

2. 테스트를 위해서는 아이폰 자체 시간설정 기능을 적극 활용하면 되겠다. Local Notification의 통지는 iOS가 스케쥴링을 해주기 때문에 개발자가 통제할 수 없다. 그러므로 아이폰 자체 시간설정 기능을 활용해 테스트할 수 있겠다.

3. PayLoad대신 Custom Data를 사용하는 방법은 별도로 설명을 안했지만 UILocalNotification상에 userInfo속성을 활용하면 된다는 점을 설명하지 않아도 여러분은 잘 사용할 수 있을 것이라 생각한다. ^^


정리하며
Local Notification에 대해서 전반적인 내용을 다뤄보았다. 생각보다 개념자체는 간단하지만 어느 경우에 어떻게 사용하고 동작하는지에 대한 명확한 지식이 없으면 오용할 여지는 많다. 또한 잘못 사용하면 뜻하지 않는 동작을 유발할 수 있기 때문에 등록시 시간을 잘 설정해야하는 것은 꼭 염두해야 한다. 또한 클라이언트 베이스로 등록/처리를 해야하는 부분은 이에 대한 적절한 설계를 바탕으로 하지 않으면 로직이 복잡해져 관리가 어려워질 수 있다는 점도 개인적인 경험이다. 

아무튼 회사에서 이를 이용한 관련 어플을 만들면서 처음 사용해 봤는데 자세히 알지 않고 사용하면 문제가 될 것 같아 정리를 해보았다.


관련글
About Local Notifications and Push Notifications : http://goo.gl/jFFsT
The Notification Payload : http://goo.gl/mulVQ
UILocalNotification 클래스 : http://goo.gl/3TyTg
Scheduling, Registering, and Handling Notifications : http://goo.gl/qHVPi
내가 만든 어플에 Push Notification 적용하기 : http://goo.gl/kH7BQ 
APNS 개발버전과 배포버전의 차이점. : http://blog.jidolstar.com/726
APNS Device Token을 못받아올때 : http://blog.jidolstar.com/725
 
글쓴이 : 지돌스타(http://blog.jidolstar.com/758)  
얼마전 아이폰 어플의 개발 요구사항중에 아이폰내 AppStore로 이동시키는 기능구현이 있었다. 이 글은 그것을 이해하는데 필요한 전반적인 기본 지식을 간단히 정리했다.

Apple iOS Applications의 URL 스키마
인터넷에서 링크는 URL 스키마(scheme)인 http, mailto, ftp, feed등을 이용해 종류가 구분된다. 이는 범용적인 예이다.  반면  iOS 링크는 이보다 더 특별한 기능을 가진다. 가령 "sms:전화번호"로 하면 문자메시지 보낼 수 있는 링크가 되며 "tel:전화번호"는 자동으로 전화를 걸 수 있다. 이런 특별한 스키마를 사용하지 않더라도 http를 그대로써서 필요하다면 관련 어플로 이동시키는 것도 가능하다. 가령, 구글맵의 경우 "http://maps.google.com/maps?q=검색위치" 형태로 접근하면 구글맵 어플이 열린다. 이이튠즈도 "http://itunes.apple.com"으로 시작하면 아이튠즈 어플이 실행된다. 유투브도 비슷하다. 

활용하는 방법은 어플상에서 iOS SDK에서 제공하는 [[UIApplication sharedApplication] openURL: ]을 이용하거나 사파리 주소창이나 링크에 위 사항을 적용하면 되겠다.

지금까지 설명한 URL 스키마들은 모두 iOS에서 기본으로 제공하는 것이다. 

다음 링크에는 iOS URL 스키마의 종류와 사용예를 잘 정리하고 있다.


Third Party Applications의 커스텀 URL 스키마
개발자는 커스텀 URL스키마를 만들 수 있다. 이는 어플만의 URL 스키마를 사용하면 가능해진다. 즉, URL스키마를 이용하면 자신이 만든 어플도 사파리에서 띄울 수 있게끔 유도할 수 있다는 것을 뜻한다.  

다음 글에는 커스텀 URL 스키마를 어떻게 어플에 적용하는가 보여주고 있다.


다음 소개하는 어플은 [[UIApplication sharedApplication] openURL:]을 활용한 어플로 URL 스키마를 테스트 해볼 수 있는 어플이다. (찾아봤더니 진짜 있네? ^^) 

Open URL : http://goo.gl/8n9mV



아이폰 어플에서 AppStore 검색페이지로 이동하기

이 글의 목적은 바로 아이폰 어플에서 아래 화면처럼 AppStore 어플의 검색페이지 이동하여 특정키워드로 검색하는 방법을 설명하는 것이다.  지금까지 URL 스키마를 설명했으니 이것도 URL을 통해 실행이 가능하다.


iOS 개발자 분들이라면 아래와 같은 URL을 이용하면 AppStore의 해당 어플 페이지로 이동한다는 것은 알고 있을 것이다.

http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=406292683
http://itunes.apple.com/kr/app/id406292683

이것은 1개의 어플을 찾을때는 유용하다. 그럼 검색은? 특별히 우리 회사에서 만든 어플을 찾아내야 한다면?

http://itunes.com/회사명
http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewArtist?id=회사아이디
http://itunes.apple.com/us/artist/회사명/id회사아이디 
item-apps://itunes.apple.com/us/developer/회사명/id회사아이디

등등..... 검색해서 다 해봤지만 전부 아이튠즈 어플로만 이동한다. 앱스토어에 이동해야하는데 말이다. 물론 맥에서는 잘 찾아간다. 단지 아이폰/아이팟터치에서는 아이튠즈와 앱스토어 어플이 분리되어 있는게 문제인 것이다.

하루 걸러서 검색으로 찾은 결과 방법을 알아냈다.
이것도 결국 URL을 이용하면 된다. (맥 사파리나, 아이폰 사파리, [[UIApplication sharedApplication] openURL: ] 을 이용해 접근하면 확인할 수 있겠다.)


마지막 term 파라미터에 회사명만 적어주면 된다. 

아~ 이것을 알아내느라 몇시간을 소비했다. 
좋은 팁이였길 바란다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/751)








개인적으로 ActionScript 3.0 기반에서 개발을 많이 하다보니 Objective-C의 target/selector 형태로 접근하는게 익숙하지 않았다. 사실 ActionScript 3.0에서는 더 고수준으로 랩핑 처리하다 보니 selector격인 함수의 참조만 보내줘도 되긴하다. 그렇지만 이러한 사실이 이해가 된다고 하더라도 매번 target/selector를 보내고 처리하는게 여간 불편하지 않았다.

아래는 Objective-C에서 target/selector를 사용하는 방법이다. 뭔가 다른 클래스에 대리 요청을 하기 위한 방법으로 이렇게 많이 쓸 것이다.

위 방법이 익숙해지면 사용하는데 어렵지는 않다. 그냥 target/selector 넘겨주고 실행해주면 그만이다. 앞서 말했지만 나는 이러한 것에 익숙하지 않다. 그래서 2개를 보내는 형태가 아닌 하나로 묶어서 보내고 싶었다. 아래처럼 말이다. 


위 도식에서 Action클래스는 target/selector를 관리하고 실행해주는 역할을 한다.

뭔가 더 복잡해보인다. 하지만 이 방법의 장점은 target/selector로 두개의 변수를 관리하는게 아니라 이것을 하나로 묶은 Action클래스의 객체만 관리하면 된다는 것이다. 

Action내에 있는 target/selector를 실행하기 위해 위 도식에서 [_tempSuccessAction run]과 같이 하면 된다. 이것을 조금더 확장해서 생각하면 [_tempSuccess runWithData:data] 처럼 할 수 있고 스레드에도 대응시키기 위해 [_tempSuccess runInThreadWithWaitUntilDone:YES] 와 [_tempSuccess runInThreadWithData:data waitUntilDone:YES] 처럼 사용할 수 있도록 Action에 관련 메서드를 추가해도 된다. 이렇게 생각해보니 Action 클래스로 target/selector를 관리하는게 여러가지로 장점이 될 수 있다는 것을 알 수 있다. 

아래 코드는 Action 클래스의 header 정의 부분이다.

//

//  Action.h

//

//  Created by Yongho Ji on 10. 12. 7..

//  Copyright 2010 Wecon Communications. All rights reserved.

//


#import <Foundation/Foundation.h>


//target/selector 하나로 묶고 실행해주는 클래스 

@interface Action : NSObject 

{

@private

id _target; //selector 가지고 있는 클래스 객체 참조

SEL _selector; //target안에 정의된 selector 메서드 

}


//초기화 

-(id)initWithTarget:(id)target selector:(SEL)selector;


//target/selector 실행 

-(void)run;


//target/selector 실행하되 selector인자로 data 넘김 

-(void)runWithData:(id)data;


//(Thread안에서 호출)target/selector 실행.

-(void)runInThreadWithWaitUntilDone:(BOOL)waitUntilDone;


//(Thread안에서 호출)target/selector 실행하되 selector인자로 data 넘김 

-(void)runInThreadWithData:(id)data waitUntilDone:(BOOL)waitUntilDone;

@end



Thread내에서는 runInThread... 를 호출해주면 그만이다. 구현부를 보자.

//

//  Action.m

//

//  Created by Yongho Ji on 10. 12. 7..

//  Copyright 2010 Wecon Communications. All rights reserved.

//


#import "Action.h"



@implementation Action


-(void)dealloc 

{

[super dealloc];

//[_target release];

}


-(id)initWithTarget:(id)target selector:(SEL)selector 

{

if (self = [super init]) 

{

//[target retain];

//[_target release];

_target = target; //retain하지 않고 assign 한다. 왜냐하면 retain하면 메모리 누수를 야기시킬 있으므로 

_selector = selector;

}

return self;

}


-(void)run 

{

@try {

if (_target != nil && _selector != nil

{

[_target performSelector:_selector];

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}


-(void)runWithData:(id)data 

{

@try {

if (_target != nil && _selector != nil

{

@try {

[_target performSelector:_selector withObject:data];

}

@catch (NSException * e) {

[_target performSelector:_selector];

}

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}


-(void)runInThreadWithWaitUntilDone:(BOOL)waitUntilDone 

{

@try {

if (_target != nil && _selector != nil

{

[_target performSelectorOnMainThread:_selector withObject:nil waitUntilDone:waitUntilDone];

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}


-(void)runInThreadWithData:(id)data waitUntilDone:(BOOL)waitUntilDone

{

@try {

if (_target != nil && _selector != nil

{

@try {

[_target performSelectorOnMainThread:_selector withObject:data waitUntilDone:waitUntilDone];

}

@catch (NSException * e) {

[_target performSelectorOnMainThread:_selector withObject:nil waitUntilDone:waitUntilDone];

}

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}



@end



사용방법은 이미 설명했다. 간단한 아이디어지만 좀 더 확장해서 생각해보면 Action 클래스에 추가할 수 있는 것들이 많을 것 같다.


비슷한 이유에서 통지대신 이벤트를 만들어 사용한다.
NSNotification 대신 Event 사용하기 http://blog.jidolstar.com/721

글쓴이 : 지돌스타(http://blog.jidolstar.com/742


iOS 어플상에서 기기의 종류를 알아올 수 있는 코드이다.

#include <sys/sysctl.h>    // sysctlbyname 의 사용을 위해

// 모델 정보 보기 - 상세히
+ (NSString *) platform {
    size_t size;
    sysctlbyname("hw.machine", NULL, &size, NULL, 0);
    char *machine = malloc(size);
    sysctlbyname("hw.machine", machine, &size, NULL, 0);
    /*
     Possible values:
     "i386" = iPhone Simulator
     "iPhone1,1" = iPhone 1G
     "iPhone1,2" = iPhone 3G
     "iPhone2,1" = iPhone 3GS
     "iPhone3,1" = iPhone 4
     "iPod1,1"   = iPod touch 1G
     "iPod2,1"   = iPod touch 2G
     "iPod3,1"   = iPod touch 3G
     */
    NSString *platform = [NSString stringWithUTF8String:machine];
    free(machine);
    
    return platform;
}

출처 : http://cafe.naver.com/mcbugi/81305


Objective-C에서 retain/release 메커니즘을 이해하고 익숙해지는데 오랜 시간이 걸렸다. 이해 자체는 빠를지언정 적절히 쓰는 건 많이 어려운 것 같다.

retain/release/autorelease 를 쓰는 이유는 사실 많이 공개되어 있다. 하지만 retain을 하면 안되는 경우에 대해 다루는 글이 많지 않은 것 같다.

delegate는 기본적으로 retain이 아닌 assign을 한다. 만약 ViewController가 어떤 객체의 delegate대상으로 설정될때 retain을 해버리면 어떤 객체가 delegate를 release해주지 않는 이상 ViewController는 dealloc이 호출되지 않는 문제가 발생한다. 그러면 자연스럽게 메모리 릭(memory leak)이 발생한다. 그렇기 때문에 delegate는 assign을 해주는 것이다.

이런 경우도 있다. UITableViewCell을 커스터마이징처리한 CustomTableViewCell이 있다고 하자. 여기에 UITableViewController를 확장한 MyTableViewController가 이 CustomTableViewCell을 사용한다고 가정하자. 보통 같으면 사용에 문제가 없지만 만약 CustomTableViewCell에서 MyTableViewCeontroller의 메서드를 호출할 수 있게 하기 위해 MyTableViewController 객체 자신(self)과 그 메서드(@selector(myMethod:))로 CustomTableViewCell에서 참조하게 할 수 있다. 이때 MyTableViewController의 참조를 retain해줘버리면 나중에 MyTableViewController이 dealloc 메서드가 호출될 시점을 읽어버릴 가능성이 100%가 된다. 왜냐하면 CustomTableViewCell에서 retain처리된 MyTableViewController의 객체를 다시 release할 수 있는 명확한 메커니즘을 추가하기 힘들기 때문이다. 그래서 이런 경우에도 retain이 아닌 assign을 사용함이 옳다.

조금 다르지만 NSTimer를 이용할때도 비슷한 경험을 할 수 있다. [NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:] 메서드를 통해 target과 selector를 넘겨주는데 이때 내부적으로 NSTimer는 자기 자신과 target을 retain시켜주는 것 같다. 그래서 [timer invalidate] 호출해주지 않으면 target의 dealloc이 절대 호출되지 않는다. 역시 이 경우에도 메모리 릭이 발생한다. 그러므로  NSTimer를 시작하고 나서 멈추는 시점은 dealloc 내에서 하면 안되며 UIViewController의 경우 viewWillDisappear나 viewDidDisappear와 같은 메서드에서 invalidate를 호출해줘서 정상적으로 UIViewController의 객체의 dealloc 메소드가 호출되도록 해주어야 한다.

이외에도 굉장히 많은 경우도 메모리 릭은 항상 존재할 수 있다. 그래서 보통 일반적으로 통용되는 규칙을 잘알고 그대로 하는 것이 좋고 그렇게 하는 이유도 이해하면 크게 도움된다.

결국 위의 예제는 여기서 제공해주는 일종의 팁정도로만 생각하고 요점은 항상 dealloc 메서드가 호출되는지 확인하는 자세가 필요하다. 그렇게 되면 최소한 메모리 릭이 발생되는 최초 근본원인은 찾아낼 수 있을 것이다. 

종류가 다른 하나의 팁으로 정확한 원인은 모르겠지만 NSZombieEnabled를 사용 하면 EXC_BAD_ACCESS가 발생하지 않는데, 그것만 없애면  EXC_BAD_ACCESS가 발생하는 경우에는 dealloc 부분에서 디버깅을 해보면 어떤 객체에서 문제가 발생하는지 찾을 수 있다. 이 에러는 보통 과도한 release를 하는 경우 발생하는 것인데, 아무리 봐도 문제점을 찾을 수 없는 경우였다. 이런 경우 dealloc 메서드 내에 [super dealloc]를 실행문의 맨 뒤에 호출하도록 하면 문제가 없어지기도 한다.

그림이나 코드없이 말로만 줄줄 써서 이해가 잘 안갈 수도 있다. 결국 중요한 것은 dealloc이 제대로 호출되는지 항상 체크하라는게 여기서 말하고 싶은것이다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/735)

아이폰 어플을 개발하다보면 현재 인터넷 접속이 가능한지 확인해야할 필요가 있다. 또 사용성 측면에 있어서도 네트워크가 연결되어 있는 상태인지 확인해서 인터넷 사용이 가능한지 메시지를 보여주는 일도 중요할 수 있다. 또 어떠한 경우에는 3G인지 WiFi인지 알 필요가 있을 수 있다.

 

이러한 일을 위해 직접 만드는 것도 좋지만, 빠르게 어플을 개발해야하는 입장에서는 그것마저 부담스럽니다. 

그러므로 검색이 필요하다. 

 

검색해보니깐 아주 유용한 소스를 발견했다. 바로 Reachability 이다. 

애플 예제소스로 공개된 것이다.

 

http://developer.apple.com/iphone/library/samplecode/Reachability/Introduction/Intro.html

 

위에서 제공하는 소스를 다운로드 받아 X-code에서 직접 실행해보자. 네트워크 상태를 껐다가 켰다가 하면서 실험해보자. 잘 동작한다. 아래는 본인이 직접 시뮬레이터 상에서 동작시켜본 것이다. 

 

 


사용법은 단순하다.

1. Reachability.h와 Reachability.m을 자신의 프로젝트의 클래스로 등록한다.

2. SystemConfiguration.framework 프레임워크를 추가시킨다. 

3. 사용한다. ^^

 

가령 현재 상태가 WiFi인지 WWAN(3G)인지 확인하려면 아래처럼 코딩을 하면 되겠다.


 


실시간으로 네트워크 상태변화를 감지하고 싶다면 Notification기능을 활용하면 된다. 이미 샘플코드에 다 정리가 되어 있지만 간단하게 설명하자면... 

 

먼저 아래와 같은 코드를 application:didFinishLaunchingWithOptions:와 같은 함수등에 넣는다. 여기서 _internetReach 변수는 Reachability *_internetReach;로 클래스 변수로 정의해둔다. 또한 dealloc시에는 [_internetReach release];_internetReach=nil;로 메모리 해제를 해주어야 한다.


 

그리고 다음과 같은 2개의 함수를 넣어서 간단하게 감지할 수 있도록 만들면 된다. 


 


이것으로 인터넷 연결상태를 실시간으로 감지할 수 있게 된다. 

 

개발자에 따라서 특정한 웹서버에 접속이 원할한지 확인할 필요가 있을지 모른다.

그러한 경우에는 샘플코드를 보면 아래와 같은 방법으로 사용했다. 

 

[[Reachability reachabilityWithHostName: @"www.apple.com"] retain];


이외에 샘플코드에 Local WiFi 감지를 하는 부분이 있는데, 사실 무슨의미인지는 잘 모를뿐더러 어디서 보니깐 실제 인터넷 접속감지는 위에서 소개한 2가지 방법만 사용해도 문제가 없는 것 같다.

 

동영상 강좌도 있다.

http://answers.oreilly.com/topic/1218-how-to-check-the-status-of-the-network-connection-from-your-iphone-app/

 

아주 간단하게 만들어진 소스도 있다.

http://theeye.pe.kr/entry/how-to-check-network-connection-on-iphone-sdk


글쓴이 : 지돌스타(http://blog.jidolstar.com/731)

아래와 같은 에러를 만나 본적이 있으신가요?


-[CALayer release]: message sent to deallocated instance


개발자가 만든 코드가 아니라서 디버깅해서는 찾기 힘든 경우입니다. 이런 경우에는 일단 예상되는 부분을 주석처리 해서 저 메시지가 안나오게 한다음 문제되는 부분을 분석해서 찾는 것이 속이 편한듯 싶습니다. 아니면 꼼꼼히 코드를 보면서 누수부분을 찾아야겠지요. 아직 Objective-C가 익숙하지 않아서 인지 찾는데 좀 시간이 걸리더군요.


제가 저 에러를 만났을때 문제가 되었던 것은 바로 적절한 retain을 해주지 않아서 였습니다.


//1 에러발생 : -[CALayer release]: message sent to deallocated instance

//단, dealloc에서 [_btnGlobal release];_btnGlobal=nil;을 호출해준다.

_btnGlobal = [UIButton buttonWithType:UIButtonTypeCustom];

[self addSubview:_btnGlobal];


//2 문제없음

//단, dealloc에서 [_btnGlobal release];_btnGlobal=nil;을 호출해준다.

_btnGlobal = [UIButton buttonWithType:UIButtonTypeCustom];

[_btnGlobal retain];

[self addSubview:_btnGlobal];


//3 문제없음

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

[self addSubview:button];


//4 문제있음 : Stack Over가 생김 

UIButton *button = [[UIButton buttonWithType:UIButtonTypeCustom]autorelease];

[self addSubview:button];


//5 문제있음 : 4번과 현상 같음 

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

[self addSubview:button];

[button release];

//6 문제없음

UIButton *button = [[UIButton alloc]init];

[self addSubview:button];

[button release];

//7 문제없음

UIButton *button = [[[UIButton alloc]init]autorelease];

[self addSubview:button];

//8 메모리 leak발생

UIButton *button = [[UIButton alloc]init];

[self addSubview:button];


기본적으로 objective-c에서는 retain수와 release 수가 항상 같아야 메모리 문제가 발생하지 않지요. retain수>release수 가 되면 메모리 leak가 발생하고 반대로 retain수 < release 수가 되면 중간에 뻗어 버립니다. 그래서 오히려 전자가 더 찾기 힘들죠.


저한테 닥친 문제는 1번의 경우였습니다. 일단 _btnGlobal은 전역변수입니다. 여기에 @property, @synthesize와 같은 설정은 안했습니다. 그리고 dealloc함수에서 release시키죠. 1번에서 문제는 객체 생성시 new, init, alloc을 사용하면 기본적으로 autorelease되지 않아서 수동으로 release해야하며 반대로 new,init,alloc으로 생성하지 않는 경우에는 autorelease처리되어 release를 하지 않아도 된다는 점을 간과했습니다.  1번의 경우는 자동으로 release되었으니 처음에는 문제없이 동작하지만 dealloc함수가 호출시에 [_btnGlobal release]를 호출하기 때문에 release를 한번더 한 경우가 되어 에러를 발생시킵니다.  


그래서 1번 문제는 2번처럼 해주면 문제가 발생하지 않습니다. 


3번의 경우 1번의 경우와 같은 것 같지만 button 변수가 함수내부에 있으므로 문제가 없습니다. dealloc함수에서 release를 호출할 일도 없고 또한 new, alloc, init함수등으로 객체를 생성한 것이 아니기 때문에 자동 release되므로 문제 없습니다.


반면 4의 경우처럼 autorelease를 붙이거나 5번의 경우처럼 release를 했다면 스택오버플로우(?)와 같은 현상이 발생합니다.


6, 7번의 경우는 alloc, init, new함수를 썼으므로 release나 autorelease를 사용했습니다.. 이 경우에는 문제없습니다.


하지만 8번의 경우 retain되었고 addSubview가 되는 순간 두번째 retain이 됩니다.  내부적으로 addSubView에대한 것은 자동 release되겠으나 dealloc함수등에서 release시키지 않으므로 메모리 leak이 발생합니다. 이는 Instruments를 이용해서 찾을 수 있을겁니다.


retain과 release를 적절히 사용하는게 무엇보다 중요합니다. 개발하면서 항상 체크하세요.


이 개념을 잡는데 가장 도움이 되었던 글은 문씨님 글인것 같습니다.

http://cafe.naver.com/mcbugi/71504 

글쓴이 : 지돌스타(http://blog.jidolstar.com/730)


UISegmentedControl을 기본 스킨만 사용하는 경우야 문제는 없습니다.
하지만 대부분의 경우 그렇지 못하죠.
저처럼 아이폰 개발을 처음 하는 경우에는 전체 개발의 80%이상은 UI에 시간을 쏟아붙는듯 합니다.
분명 시간이 매우 아깝지만 익숙해지면 20~30%로 내릴 수 있을것이라 생각합니다.

각설하고....
UISegmentedControl의 기본 스킨은 다음과 같습니다. UIToolBar에 [[UIBarButtonItem alloc]initWithCustomView:]를 이용해 등록하면 아래와 같이 됩니다.


기본스킨을 사용하면 아무 문제없이 선택이 잘됩니다.

하지만 UISegmentedControl의 tintColor를 [UIColor blackColor]로 지정하면 아래 처럼 보이게 되는데 실제로 실행해보면 선택되어지지 않습니다. 정말 난감해지는 순간입니다. 선택했을때 색, 선택되지 않았을때 색, 글자색 뭐 이런것들을 직접 다룰 수 있는 함수나 속성이 전혀 있지 않아 매우 난감했습니다. 다른 분들은 이것을 어찌 해결하셨는지 궁금할 정도입니다.



제가 하고자 했던 목표는 글자크기를 크게하고 선택이 될 수 있는 모습을 보고 싶다는 겁니다.
아주 아주 단순한 요구사항인데도 불구하고 속성변경으로 단순하게 처리할 수 없어 너무 답답합니다. 어쨌든...
해결하기 위해 구글신의 도움을 빌릴 수 밖에 없더군요. 그러다가 다음 내용을 찾았습니다.

iPhone: UISegmentedControl with custom colors
iPhone – Get Class Name

Change font size for text in UISegmentedControl

(아래 내용은 설명이라고 적었지만 소스를 먼저 분석하시고 보시는 편이 더 좋을 것 같습니다. ^^)
첫번째 내용은 UISegmentedControl을 확장해서 선택할때 배경색이 바뀌도록 한 것입니다. 이 사람은 이것을 쓴 어플이 문제없이 등록승인되었다고 하네요. 그러니 우리도 쓸 수 있습니다. ^^ 단지 이 소스의 단점은 UISegmentedControl의 selectedSegmentIndex를 0이 아닌 1,2 등으로 지정하면 오동작 한다는 겁니다. 당신의 어플에서 기본 0이 항상 선택되어야 하는 경우라면 전혀 문제 없겠지만 1, 2 등이 선택되어야 한다면 당연히 문제가 생기므로 이 마저도 수정해야합니다. 제가 선택한 방식은 UIView의 tag를 이용한 것입니다. UISegmentedControl의 내부에 생성된 것들은 검사해 보니 UISegment입니다. 이 클래스는 private로 지정되어 애플 정책상 직접 사용하는 것이 금지됩니다. 하지만 문제 해결을 위한 참조정도는 가능한가 봅니다. 이 UISegment가 "하나","둘","셋" 순서대로 인덱스를 0,1,2로 지정되어 있으면 문제 없는데 또 그런 것도 아닌 것 같더군요. UIView의 subviews속성을 통해 얻어온 자식들을 가지고 손쓰면 안된다는 것을 의미합니다. 그래서 UIView의 tag 를 사용합니다. ^^

세번째 내용은 폰트를 설정하기 위해 사용합니다. 내용을 보면 changeUISegmentFont 라는 함수를 설명하고 있는데요. 이것은 재귀함수로 UISegmentContoller에 자식뷰를 전부 돌면서 UISegmentLabel을 찾아 폰트를 수정해주는 식입니다. 하지만 이것은 직접쓰면 안되고 클래스의 이름을 가져오는 과정에서 두번째 내용이 필요하고 직접 쓴다고 해서 폰트가 변경되는 시점은 비동기적으로 동작하기 때문에 처음 생성시 변경해준다고 해서 되는 문제는 아닙니다. 첫번째 내용에 이 함수를 함께 쓴다면 어느정도 문제 해결의 실마리를 찾게 되더군요.

이제 소스 들어갑니다.
헤더 입니다. 이름은 CustomSegmentedControl이라 정했습니다.

//

//  CustomSegmentedControl.h

//

//  Created by Yongho Ji on 10. 9. 15..

//


#import <Foundation/Foundation.h>



@interface CustomSegmentedControl : UISegmentedControl {

UIColor *offColor; //off 배경색

UIColor *onColor; //on 배경색

UIColor *onTextColor; //off 글자색

UIColor *offTextColor; //on 글자색

int fontSize; //글자의 크기 

}


-(id)initWithItems:(NSArray*)items 

  offColor:(UIColor*)offcolor 

  onColor:(UIColor*)oncolor 

  offTextColor:(UIColor*)offtextcolor 

  onTextColor:(UIColor*)ontextcolor 

  fontSize:(int)fontsize;


@end



다음은 구현부입니다. 천천히 따라가 보시면 해석하시는데 무리는 없을겁니다.

//

//  CustomSegmentedControl.m

//

//  Created by Yongho Ji on 10. 9. 15..

//


#import "CustomSegmentedControl.h"



@implementation CustomSegmentedControl


//UISegment계열 폰트의 색과 크기를 조절시켜준다. 재귀적으로 찾아가는 것을 눈여겨 보자.

- (void)_changeUISegmentFont:(UIView*) aView 

fontSize:(int)fontsize 

  textColor:(UIColor*)textcolor 

{

NSString *typeName = NSStringFromClass([aView class]);

if([typeName compare:@"UISegmentLabel" options:NSLiteralSearch] == NSOrderedSame) {

UILabel *label = (UILabel*)aView;

UIFont *font = [UIFont boldSystemFontOfSize:fontSize];

[label setFont:font]; //글자크기 지정 

[label setTextColor:textcolor]; //글자색 지정 

//글자크기에 따라 위치/크기 보정 

CGSize size = [label.text sizeWithFont:font forWidth:320 lineBreakMode:UILineBreakModeClip];

[label setFrame:CGRectMake(0, 0, size.width, size.height)];

[label setCenter:CGPointMake(label.superview.frame.size.width/2, label.superview.frame.size.height/2)];

}

NSArray *subs = [aView subviews];  

NSEnumerator* iter = [subs objectEnumerator];

UIView *subView;

while (subView = [iter nextObject]) {

[self _changeUISegmentFont:subView fontSize:fontsize textColor:textcolor];

}


//색이 바뀔때마다 Segment 배경색과 폰트색을 바꿔준다.

-(void)_setToggleHiliteColors {

//NSLog(@"%d",self.selectedSegmentIndex);

int index = self.selectedSegmentIndex;

int numSegments = [self.subviews count];

id subview;

//리셋 선택 처리 

//   깜박임이 존재하는 것은 UISegmentedControl 내부적으로 폰트 색을 그렸다가 

//   여기서 강제로 다시한번 지정하기 때문에 그렇다

//   어찌할 방법을 찾지는 못했지만 그럭저럭 쓸만함 

for (int i=0; i<numSegments; i++) {

subview = [self viewWithTag:i];

if (i==index) { //선택

[subview setTintColor:nil];

[subview setTintColor:onColor];

[self _changeUISegmentFont:subview fontSize:fontSize textColor:onTextColor];

} else { //리셋

[subview setTintColor:nil];

[subview setTintColor:offColor];

[self _changeUISegmentFont:subview fontSize:fontSize textColor:offTextColor];

}

}

}


//초기화 함수 

-(id)initWithItems:(NSArray*)items 

  offColor:(UIColor*)offcolor 

  onColor:(UIColor*)oncolor 

  offTextColor:(UIColor*)offtextcolor 

  onTextColor:(UIColor*)ontextcolor 

  fontSize:(int)fontsize 

{

if (self = [super initWithItems:items]) {

// 폰트크기 지정 

offColor = [offcolor retain]; 

onColor = [oncolor retain];

offTextColor = [offtextcolor retain];

onTextColor = [ontextcolor retain];

fontSize = fontsize;

//스타일 고정 

[self setBackgroundColor:[UIColor clearColor]];

[self setSegmentedControlStyle:UISegmentedControlStyleBar];

//루프를 돌면서 태그를 달아줌 

id subview;

for (int i=0; i<[self.subviews count]; i++) {

subview = [self.subviews objectAtIndex:i];

[subview setTag:i];

}

//listen for updates

[self addTarget:self action:@selector(_setToggleHiliteColors) forControlEvents:UIControlEventValueChanged];

//비동기적으로 한번 호출해준다. 글자크기/배경색 적용을 위해...

[self performSelector:@selector(_setToggleHiliteColors) withObject:nil afterDelay:0.1];

}

return self;

}


//메모리 dealloc

-(void)dealloc {

[offColor release];offColor=nil;

[onColor release];onColor=nil;

[onTextColor release];onColor=nil;

[offTextColor release];offTextColor=nil;

[super dealloc];

}


@end



이제 만들어진  CustomSegmentedControl을 사용하는 예제 호스트 코드입니다.  복잡해 보이지만 IB에서 ToolBar 붙이고 거기에 UISegmentedControl을 중앙에 붙이기 위한 작업을 코드로 옮겨 넣은 것 뿐입니다.

UIToolbar *tb = [[[UIToolbar alloc]init]autorelease];

[tb setFrame:CGRectMake(0, 480-49, 320, 49)];

[tb setBarStyle:UIBarStyleBlack];

[window addSubview:tb];

CustomSegmentedControl *segControl;

segControl= [[CustomSegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:@"하나 ",@" ",@" ",nil

  offColor:[UIColor blackColor]

  onColor:[UIColor colorWithRed:120.0f/255.0f green:120.0f/255.0f blue:120.0f/255.0f alpha:1]

  offTextColor:[UIColor colorWithRed:153.0f/255.0f green:153.0f/255.0f blue:152.0f/255.0f alpha:1

  onTextColor:[UIColor whiteColor]

  fontSize:15];

segControl.frame = CGRectMake(0, 0, 250, 35);

segControl.selectedSegmentIndex=1;

//[segControl addTarget:self action:@selector(_valueChanged) forControlEvents:UIControlEventValueChanged];

UIBarButtonItem *segControlItem = [[[UIBarButtonItem alloc]initWithCustomView:segControl]autorelease];

UIBarButtonItem *leftItem = [[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]autorelease];

UIBarButtonItem *rightItem = [[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]autorelease];

[tb setItems:[NSArray arrayWithObjects:leftItem, segControlItem, rightItem, nil]];

[segControl release];


아래와 같이 선택했을때 색변경과 글자크기 변경이 가능해졌습니다.


좋은 지식이 되었으면 하며, 다른 의견 있으시면 댓글 부탁합니다. ^^

글쓴이 : 지돌스타(http://blog.jidolstar.com)

위 화면은 UINavigationBar 를 보여주고 있습니다.
오른쪽을 보면 버튼이 3개가 붙어 있습니다.
어떻게 한것일까요? 저도 어떻게 할 수 있을까 무척 고민했습니다.
일단 저기에 뭔가 붙이려면 UIViewController에서 self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]init...]autorelase]을 설정해주면 됩니다.

- initWithBarButtonSystemItem: target: action:
- initWithImage: style: target: action:
- initWithTitle: style: target: action:

위 함수중 1개를 선택해서 객체를 생성할 수 있는데 전부 1개만 넣을 수 있습니다. 2개 이상은 안되죠. 다행히도 아래와 같은 함수가 있습니다.

-initWithCustomView:

이 함수를 이용하면 UIView를 확장한 객체만 뭐든지 들어갈 수 있지요. 당연하지만 앞의 3개의 함수와 달리 버튼을 눌렀을때 액션을 취할 수는 없습니다. 이것은 커스터마이징 해야할 겁니다.

그럼 처음 위 Navigation Bar에서 보여준것처럼 하게 하려면 결국 UIView로 하면 됩니다.
하지만 UIView를 이용하면 위치를 일일히 지정하는 번거로움이 있어서 이런 경우에는 UIToolBar를 사용하면 되겠죠. 즉 아래처럼 하면 됩니다.

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]initWithCustomView:myToolBar]autorelease];

결과는 예~ 잘등록됩니다. ^^

하..지..만... 요래 됩니다.




당연하겠죠. UIToolBar 기본배경이 원래 저거니깐요.

이제 배경색만 지우면 되겠지 싶어
myToolBar.opaque = NO 도 해보고...
myToolBar.translucnet = YES 도 해보고...
myToolBar.backgroundColor = [UIColor clearColor] 도 해보고
myToolBar.tintColor = [UIColor clearColor] 도 해보고....

별짓을 다해봤지만.... 배경색이 지워지지 않더군요. 인터넷 뒤져봐도 투명으로 만드는 방법 말고는 설명된 것을 찾기가 어렵더군요. 그냥 UIView 확장해서 만들까 하지만 오기가 생겼습니다.

이리저리 찾아보다가.... 방법을 찾았습니다.

- (void) drawRect:(CGRect)rect {}

결국 위 함수를 오버라이드 해서 내부에 아무것도 정의하지 않으면 된다는 사실을 깨달았습니다.
말그대로 UIToolBar를 확장해서 아래처럼 아무 코드도 넣지 않으면 됩니다.

- (void) drawRect:(CGRect)rect {
    //이것으로 배경을 그리지 않는다.
}

그리고 내부적으로 아래와 같은 코드가 들어가야 합니다. init함수에 아래 코드를 넣으면 완성됩니다.

self.translucent= YES; 


저 버튼들을 눌렀을때 action은 어떻게 처리하냐고요? 그야 방법은 아주 많겠죠? 그건 나름 연구해보시길.... ^^

글쓴이 : 지돌스타(http://blog.jidolstar.com/728)
일반적으로 키보드를 감추기 위해  [myTextField resignFirstResponer] 메소드를 사용하실 겁니다.
UIViewController가 전환할때는 기존 ViewController에 떠 있던 키보드가 자동으로 감춰집니다. 

하지만  firstResponder의 출처를 찾기가 곤란한 경우도 있습니다. 가령.. 저의경우 
중간에 네트워크가 중지되거나 점검페이지등을 붙일때 Controller가 아닌 UIView를 직접 사용해 window의 최상위에 붙이게 되는 경우가 있는데, 이때 ViewController에 키보드가 보여져 있으면 이 페이지 위에 키보드가 그대로 남게 되는 경우가 발생합니다.

꼭 위와 같은 경우가 아니더라도 키보드가 어디를 기준으로 보이든지 감춰줄 수 있는 메서드가 필요했습니다.
저는 아래 메서드를 공통으로 사용할 수 있는 클래스(가령 AppDelegate)에 정의해놓고 언제든지 키보드를 감출 수 있도록 합니다.
여기서 호출할 메소드는 hideKeyboard  입니다. _hideKeyboardRecursion은 제귀함수로 직접 호출하지는 않습니다.

//키보드를 사라지게 하기 위해 사용하는 재귀함수 

- (void)_hideKeyboardRecursion:(UIView*)view 

{

if ([view conformsToProtocol:@protocol(UITextInputTraits)]) 

{

[view resignFirstResponder];

}

if ([view.subviews count]>0

{

for (int i = 0; i < [view.subviews count]; i++) 

{

[self _hideKeyboardRecursion:[view.subviews objectAtIndex:i]];

}

}

}


//키보드 감추기

- (void)hideKeyboard 

{

UIWindow *tempWindow;


for (int c=0; c < [[[UIApplication sharedApplication] windows] count]; c++) 

{

tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:c];

for (int i = 0; i < [tempWindow.subviews count]; i++) 

{

[self _hideKeyboardRecursion:[tempWindow.subviews objectAtIndex:i]];

}

}

}


혹시 부족한 점 있으면 보완 부탁드릴께요. 

글쓴이 : 지돌스타(http://blog.jidolstar.com/727)

이 내용은 검색해도 제대로 나와 있는게 없어서 간단히 정리하여 공유하는 수준입니다.

 

APNS에 관심을 가지시는 분들이 많을겁니다.

APNS는 프로바이더가 Push서비스를 하기 위한 시스템 구축할 필요없이 Apple을 도움을 받아 구현할 수 있기 때문에 적은노력으로 최대한의 효과를 볼 수 있는 아주 유용한 서비스이지요.

 

APNS를 접하다 보면 헷갈리는게 있습니다.

 

Provisioing Portal에 들어가면 APNS가 2가지 종류라는 겁니다.

1. 개발버전

2. 배포버전

 

개발버전 APNS는 Provisioning Profile에서 Development와 연관 있습니다. 단순히 Xcode상에서 디바이스로 디버깅할때 유용합니다.

배포버전 APNS는 Provisioning Porfile에서 Distribution과 연관 있습니다. 이는 Adhoc이든 AppStore든 상관없습니다.

 

이렇게 구분된 이유는 저는 개발할때와 배포할때 APNS가 동일하면 발생되는 문제점을 해소하기 위함이라고 이해했습니다.

만약 개발과 배포시 APNS가 동일하다면 서비스하는 도중에 개발하면서 잘못된 APNS를 사용자들에게 보내버릴 수 있는 경우가 발생할 수 있습니다. 이러한 상황을 미연 차단한 것이라고 생각됩니다.

 

그럼... 두가지 버전의 명확한 차이는 무엇일까요?

 

첫째, 바로 발행하는 인증서가 다르겠죠? 인증서는 App ID에서 발행하므로 어플마다 다릅니다.

 

둘째, Device Token 값이 다릅니다. 개발버전의 Device Token과 배포버전(Adhoc, Appstore)의 Device Token은 다릅니다. 이것은 어플마다 다르지 않고 Device마다 다릅니다. 하지만 기기의 고유 UUID와는 다릅니다.

 

셋째, 그리고 프로바이더(java든, php든 상관없이) 입장에서 gateway 주소가 다릅니다. 개발버전에서는 gateway.sandbox.push.apple.com 에 접속하고 배포버전에서는 gateway.push.apple.com에 접속합니다. 포트번호는 둘다 2195이지요.

 

이 부분에 대해서 명확하게(그것도 한글로.. ^^;) 설명한 내용이 없었습니다.

제가 영어를 잘해서 Apple문서를 정독했더라면 알 수 있는 내용이였지만.... 결국 삽질끝에 파악한 내용이네요. ㅎㅎㅎ

 

아무튼 유용한 정보가 되었길...

 

다른 유용글

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

APNS Device Token을 못받아올때

글쓴이 : 지돌스타(http://blog.jidolstar.com/724)

 

Error: Error Domain=NSCocoaErrorDomain Code=3000 "응용 프로그램에 대해 발견된 'aps-environment' 인타이틀먼트 문자열 없음" UserInfo=0x117d00 {NSLocalizedDescription=응용 프로그램에  발견된 'aps-environment' 인타이틀먼트 문자열 없음}

 

AppDelegate에서 DeviceToken을 받지 못하고 Error 메시지를 띄우는 경우가 있습니다.

분명 Development나 Adhoc Provisioning Profile을 받아서 정상적으로 컴파일 했는데도 발생되는 문제입니다.

위와 같은 내용이 들어오면 다음 방법으로 문제를 해결합니다.

(디바이스에서 테스트 경험이 있는 분들만 이해하실 수 있습니다.)

 

1. iOS Provisioning Portal 에 갑니다.

2. App IDs 메뉴로 들어가 APNS를 Enabled 시킵니다.

3. Provisioning 메뉴으로 들어가 이전 Profile을 지웁니다.(Development와 Distribution 모두)

4. 거기서 다시 새로운 Profile을 생성합니다.

5. Xcode의 Organizer에 등록되어 있는 Provisioning Profile은 지웁니다.

6. 새로 생성한 Provisioning Profile을 다운받은후 두번 클릭으로 Organizer에 설치합니다.

 

이렇게 한뒤 디바이스에서 테스트 해보면 잘됩니다.

 

Provisioning Profile이라는 것은 Provisioning Portal에서 Certificate, Device, App ID 순서로 만들어진 결과물의 정보를 담습니다. 그러다 보니 세가지중에 하나라도 바뀌면 다시 Profile을 다시 생성해서 받아야 합니다. 위 에러의 경우 먼저 Profile을 받고 난다음 AppID에서 APNS를 활성화 시켜 Profile에는 이 정보가 포함되어 있지 않아 발생하는 문제였던 겁니다.

 

여기서 교훈은... Certificate, Device, App ID 에서 하나라도 수정되면 반드시 기존 Provisioning Profile을 지우고 재생성해서 받아 써야한다는 겁니다.

 

저... 이것때문에 완전 머리 쥐어 짰습니다. ㅜㅜ;;;;;

글쓴이 : 지돌스타(http://blog.jidolstar.com/725)

최근 아이폰 어플을 공부하고 개발하고 배포까지 하면서 개발시 꼭 체크하거나 염두해야할 일을 생각나는데로 정리해 보았다. 

 

1. 메모리는 항상 관리한다.

Instruments를 이용해 Memory leaks를 반드시 한다. 특히 View Controller가 전환시, 또는 데이터 통신후에 체크하도록 하는 것이 중요하다. Instruments를 이용하면 완벽하지 않지만 문제를 해결할 수 있는 발판을 마련해준다.

 

또한 Build만 해보지 말고 Build and Analyze를 통해 잠재적으로 메모리 leaks가 생길 부분을 찾도록 한다. 완벽하지 않지만 retain후 release를 안한 부분도 은근히 잘 찾아주므로 꼭 활용하길 바란다.

 

2. 디바이스 테스트는 필수

아이폰 시뮬레이터에서 동작을 잘하던 것이 디바이스에서 테스트 하면 중간에 멈추던가 이상한 동작할 하는 경우가 발생한다. 거의 대부분 이것은 메모리 문제이다. 디바이스는 제한된 메모리이기 때문에 메모리 경고가 자주 일어난다. 또한 이러한 메모리 문제는 View Controller에서 viewDidLoad 메소드를 다시 호출시켜 그에 따른 대책을 마련하지 않으면 어플이 죽거나 이상한 동작을 하게 된다.

 

디바이스에서 테스트는 개발 중간중간 자주 해야한다. 그렇지 않고 시뮬레이터에서만 개발하다가 디바이스에 올린뒤 발생하는 문제를 해결하려면 노력이 배 이상이 들 수 있다.

 

 

3. Memory Warning을 지나치지 말아라.

일단 아이폰 시뮬레이터의 메뉴를 보면 강제적으로 메모리 경고를 줄 수 있다. 그렇게 되면 AppDelegate와 View Controller에서 메모리 경고를 감지할 수 있게 된다. 경고가 발생하면 필요없는 자원을 해제 시키길 바란다. 또한 View Controller에서는 viewDidUnload 메소드가 호출될 수 있다. 이때 self.myView = nil 처리가 되어 있으면 이 객체는 메모리 반환이 되고 나중에 viewDidLoad와 viewWillAppear 호출된다. 이 때 중복된 메소드 호출이나 잘못된 메모리 반환으로 어플이 중간에 중지되거나 이상한 데이터를 View상에 보여주게 된다. 이런 처리도 말끔하게 해주어야 된다. 결국 View Controller의 라이프 사이클을 잘 이해하는 것이 중요하고 그에 따라 대처해야한다.

 

 

4. 인터페이스 빌더를 잘 활용하자.

개발자이기 때문에 인터페이스 빌더를 사용안하고 오로지 코딩으로 뷰배치를 하시는 분들이 있다. 초반에 아이폰 개발때는 이 방법이 좋을 수 있다. 무언가 코드안에서 다 해결할 수 있기 때문이겠다. 하지만 아이폰과 같은 작은 기기의 어플들은 긴 기간의 개발을 요구될 수 없다. 짧은 기간안에 빠른 승부를 내야하는게 바로 아이폰 어플이라고 본다. 뭔가 View 구성을 코드로만 하다 보면 개발자는 나중에 지쳐버린다. 한 페이지 View를 만드는데 만해도 엄청난 노력이 들어가야 한다.

 

인터페이스 빌더를 이용해 ViewController 단위로 만들고 TableView에 보일 Cell들도 만들면 여러분은 어느정도 View 구성하는 스트레스에서부터 해방될 수 있다. 완벽하지 않지만 복잡한 배치를 위해 골머리 쌓는 일에서는 거의 70~80% 해방된다. 게다가 ViewController의 코드도 많이 짧아진다. 왜냐하면 View를 구성하기 위한 코드가 많이 줄기 때문이다.

 

또한 인터페이스 빌더를 이용하면 Command+R 만으로 시뮬레이터에서 View를 미리 볼 수 있다. 이런 것들을 활용하면 개발에 많이 도움된다.

 

 

5. SVN을 이용한 소스 관리를 꼭 하자.

개발하면서 소스관리 툴하나 쓰지 않는다는 것은 사실 말이 안된다. Eclipse와 같은 툴을 다뤄봤던 사람이라면 소스관리를 위해 SVN을 많이 사용했을 것이다. 협업을 위한 것도 있지만 개인적으로 쓰더라도 소스 관리는 반드시 필요하다. Xcode는 기본적으로 SCM 기능이 있어 이것으로 소스관리가 가능하다. Eclipse에 비해서 많이 불편한 편이지만 익숙하면 쓸만하다.

 

6. 최적화에 만전을 기하자.

아이폰은 데스크탑과 다르게 제한된 메모리, 제한된 CPU 기반이다. 그러므로 이런 제한된 환경에서도 부드럽게 동작하도록 노력해야한다. 가령 이미지를 프로세싱 하더라도 cache처리를 해야할지 말아야 할지, Thread나 Operator를 이용해 데이터를 가져오거나 말아야할지 등을 잘 결정해서 개발해야한다. 까페에 문씨님이 올린 Operator 활용법을 참고하면 좋겠다.

 

7. 다양한 장치에서 구동되도록 하자.

애플 개발 정책(?)상 xcode를 배포하면서 번들로 있는 SDK는 항상 최신을 유지한다. 그래서 구버전의 아이폰이나 아이팟 터치에 동작하도록 만들 필요가 있다. iOS 4 기반으로 개발하더라도 약간의 옵션 조정으로 3.0, 3.1 에서도 동작하도록 할 수 있다. 그것은 Groups & Files에서 어플의 이름을 선택한뒤 info에서 Build 탭을 선택한다. 거기서 iOS Deployment Target을 iOS 3.1 등으로 맞추면 가능하다. 물론 SDK 상에서 상위버전시에만 구동이 되는 부분에 대해서는 따로 조치를 취해야 하며 장치 구성상 전혀 사용할 수 없는 부분은 아에 AlertView등으로 알려줘야한다. 가령 카메라의 경우 구형 아이팟터치는 동작하지 않듯이 말이다.

 

8. [myObject release]; 후에는 반드시 myObject=nil 을 하자.

@property로 지정된 속성의 경우 self.myObject = nil 하는 것만으로도 [myObject release];myObject=nil; 이 진행된다. 하지만 속성이 @property로 지정되어 있지 않은 경우에는 self.myObject로 접근할 수 없으므로 release만 할 수 있다. 하지만 [myObject relase];만 하면 문제가 발생할 수 있다. 가령, release를 한뒤 retainCount가 0가 되면 이것은 이제 좀비가 된다. 이 좀비객체에 어떠한 메시지를 보내면 무조건 어플 죽는다. 하지만 myObject=nil 처리하면 nil에 어떤 메시지를 보내도 무시해버린다. 이러한 문제는 좀처럼 눈에 보이지 않게 어플을 죽게하는 요인이 된다. 정상적인 상황에서는 이런 문제가 안생기는데, 디바이스에 물려서 돌리면 어플 죽은 범인이 여기에도 있다. 왜냐하면 디바이스는 제한된 메모리로 waning이 빈번하게 날 수 있고 그에 따라서 viewDidLoad등이 다시 호출되면서 좀비가된 myObject를 엑세스해버리는 현상이 발생할 수 있기 때문이다. release후 다시 새로운 객체로 할당해 retain 하더라도 그냥 버릇처럼 [myObject release];myObject=nil; 이런식으로 하는게 좋다.

 

 

이외에도 많겠지만 일단 생각나는데로 정리해보았다.


글쓴이 : 지돌스타(http://blog.jidolstar.com/724)

아이폰 개발시작한지 3~4개월 되어가면서 조금 감각을 익히기 시작했습니다. 
어플도 그동안 3개 만들어 올려보고요.  

물론 사운드, 그래픽을 고급스럽게 다뤄보지는 못했지만 기본 UI기반으로 개발할때도 워낙 이슈가 많다보니 학습과 개발을 병행하는게 정말 쉽지 않더군요.... 

각설하고요 ㅎㅎ

여기에 소개할 것은 UIImageView에 원격 이미지를 비동기로 로드할 수 있도록 하는 기능과 이미지 캐쉬 기능을 추가한 소스를 공개하려고 합니다.(소스 분석은 주석을 참고 ^^)

아이폰 어플 개발하면 UIImageView를 매우 많이 사용할 겁니다.  번들이미지의 경우야 어짜피 문제 없지만 원격이미지를 로드할때는  몇가지 이슈가 발생합니다.

1. 원격이미지를 매번 로드하는 것은 네트워크 부하를 일으키며 특히나 3G사용자들에게 치명적이 될 수 있다.
2. 테이블에 원격이미지를 붙이는 경우에 동기적으로 이미지 로드하는 경우 멈춤 현상을 일으킬 수 있다. 
3. 비동기적으로 이미지를 로드하더라도 한번에 로드할 수 있는 이미지를 제어하지 않으면 어플의 전체적인 퍼포먼스가 죽는다.
4. TableView에서 TableViewCell는 캐쉬처리되어 재사용된다. 그러므로 거기에 붙은 UIImageView도 재사용하게 되는데 스크롤을 빨리 넘기는 경우 기존에 로드 요청한 이미지가 하나의 UIImageView에 계속 적재되는 현상이 발생할 수 있다.

위 이슈에서 1, 2는 금방 이해갈 갈겁니다. 3번의 경우 1,2번의 처리가 잘되었다고 해도, 한 화면에 5개 이상의 이미지를 비동기적으로 로드한다는 것은 스레드를 5개 이상 생성해서 처리한다는 의미와 같습니다. 어플 하나에 스레드를 너무 돌리면 별로 좋지 못하기 때문에 동시에 로드할 수 있는 이미지를 제어해주어야 합니다. 1~2개 정도로요. 
4번 이슈의 경우 예전에 문씨님이 올려주신 [문씨의 강좌]멀티스레딩2<NSOperation>에 올린 글에 소개된 소스의 경우에 발생합니다. 저는 여기에 이미지 캐쉬기능만 넣어서 실제로 썼습니다. 하지만 TableView에서 문제가 발생했습니다. 분명 NSOperation을 이용한 이미지 로드는 2,3 문제를 해결해줍니다. 하지만 TableViewCell을 재사용되기 때문에 빠른 스크롤을 하는 경우 거기에 붙은 UIImageView 하나에  지속적으로 다른 이미지가 붙도록 요청이 되어 이미지가 광고롤링되는 현상마냥 보이는 경우가 발생합니다. 

1,2,3,4 번의 이슈를 모두 해결하고자 간단하게 클래스를 제작했습니다.

사용하는 방법은 너무도 간단합니다. 그저 UIImageView를 붙히고 (IB에서든 코드상이든) #import "UIImageView+AsyncAndCache.h"를  넣습니다. 그 다음 아래 UIImageView 카테고리 4개 함수중 하나를 쓰시면 됩니다.  UIImageView *imageView = [[UIImageView alloc]init]; 하신뒤 [imageView setImageURLString:@"이미지 원격 경로"]; 형태로 쓰시면 됩니다. 

UIImageView를 카테고리로 만들었으므로 기존 UIImageView 기능은 그대로 사용할 수 있겠고요.

UIImageView 카테고리 외에 내부적으로 2개의 클래스가 정의되어 있습니다. 이는 개발자가 직접 제어하지 않고 위 4개 이슈를 해결하기 위해 내부적으로 사용되는 클래스입니다. 소스 분석을 원하신다면 주석을 달아두었으니 참고하시면 되겠습니다.

아래는 header만 올려놓습니다. 구현부는 첨부파일을 참고하세요.

uiimageview asyncandcache.zip


ARC환경에서는 아래것을 사용하면됩니다.(오래된 글이라... 개선여지가 많습니다. )

UIImageView AsyncAndCache.zip



//

//  UIImageView+AsyncAndCache.h

//

//  Created by Yongho Ji on 10. 12. 3..

//  Copyright 2010 Wecon Communications. All rights reserved.

//


#import <UIKit/UIKit.h>


@class AsyncAndCacheImageOperator;

@class AsyncAndCacheImageOperatorManager;


//////////////////////////////////////////////////////////////

//

// UIImageView 대한 카테고리 

// 카테고리는 테이블뷰에 적용된 Cell안에 UIImageView에서 활용하면 좋다.

// UIImageView 주요 기능은 다음과 같다

//

// 1. 이미지 비동기 로드 

//   이미지를 비동기로 로드해서 화면에 이미지가 뜨는데 버벅거림을 없앤다.

// 2. 이미지 캐쉬기능 

//   이미 로드한 이미지는 cache 디렉토리에 캐쉬해서 나중에 반복 요청시 

//   로컬에 저장된 캐쉬 이미지를 로드해서 네트워크 부하를 없애준다.

// 3. 반복적인 이미지 요청에 대한 로드부하 최소화 

//    같은 UIImageView 중복으로 로드 요청한다면 마지막에 요청한 이미지가 적용되도록 한다.

//    그뿐 아니라 수십번 반복해서 요청하더라도 무조건 이미지 로드 요청하지 않고 되도록이면 

//   마지막 이미지를 로드요청하여 네트워크 부하를 줄여준다

// 

// 개선해야할 사항 

//

// 1. 캐쉬기능 강제삭제기능 

// 2. 지정된 시간이 지난 캐쉬 이미지 자동 삭제기능 

// 3. 캐쉬사용여부 결정기능  

// 

//////////////////////////////////////////////////////////////


@interface UIImageView (AsyncAndCache)


//String형태의 이미지 URL 초기화

-(id)initWithURLString:(NSString*)url;


//NSURL 형태의 이미지 URL 초기화 

-(id)initWithURL:(NSURL*)url;


//String형태의 이미지 URL 셋팅 

-(void)setImageURLString:(NSString*)url;


//NSURL 형태의 이미지 URL 셋팅 

-(void)setImageURL:(NSURL*)url;


//동시에 로드할 이미지 최대수  

+(void)setMaxAsyncCount:(NSUInteger)count;


@end


//////////////////////////////////////////////////////////////

//

// 한개의 ImageView 대한 오퍼레이터이다.

// 비동기적으로 로드하는 것을 지원하며 더불어 캐쉬기능까지 지닌다.

// 개발자가 클래스를 직접 사용하지 않는다.

// 클래스는 AsyncAndCacheImageOperatorManager 클래스에서 동작/관리한다.

//

//////////////////////////////////////////////////////////////

@interface AsyncAndCacheImageOperator : NSObject

{

NSURL *_url; //로드할 이미지의 URL 정보 

UIImageView *_imageView; //이미지를 적용할 View

BOOL _canceled; //이미지 적용을 막는다. UIImageView 재사용시 나중에 로드되더라도 이게 YES이면 적용하지 못하도록 해서 사용자들로 하여금 잘못된 이미지가 로드되는 것을 방지 한다.

id _loadCompleteTarget; //이미지 로드가 완료되었을때 호출할 target

SEL _loadCompleteSelector; //이미지 로드를 완료했을때 호출할 selector

}


@property (readonly) UIImageView *imageView;


//초기화 함수 

- (id)initWithURL:(NSURL*)url imageView:(UIImageView*)imageView;


//스레드 적용 함수 

- (void)main;


//이미지 적용 취소

//main 메서드가 실행중일때 스레드자체는 중단시킬 없지만 imageView 로드한 image 적용하는 것은 방지시킨다.

- (void)cancel;


//이미지 로드 완료후 호출할 target/selector 적용 

- (void)setLoadCompleteWithTarget:(id)target selector:(SEL)selctor;

@end


//////////////////////////////////////////////////////////////

//

// ImageView정보를 담은 여러개의 오퍼레이터(AsyncAndCacheImageOperator클래스 객체) 관리한다.

// 개발자가 클래스를 직접 사용하지 않는다.

// setMaxAsyncCount 이용해 한번에 로드할 있는 이미지 갯수를 설정할 있다.

// 중요한 것은 동일한 UIImageView 대해서 다른 이미지 로드 요청이 있는 경우 

// 마지막에 요청한 이미지가 붙도록 하며, 같은 UIImageView 이미지 로드 대기중인 경우에는 

// 이전 UIImageView 대한 Operator 삭제함으로써 부적절한 로드로 인한 네트워크 부하를 최소화 해준다.

//

//////////////////////////////////////////////////////////////

@interface AsyncAndCacheImageOperatorManager : NSObject

{

@private

NSUInteger _maxAsyncCount; //동시에 비동기적으로 로드할 이미지 갯수 

NSUInteger _currentAscynCount; //현재 비동기적으로 로드하고 있는 이미지 갯수 

NSMutableArray *_standByImageOperators; //대기중인 Image Operator

NSMutableArray *_loadImageOperators; //로드중인 Image Operator 

}


//한번에 로드할 있는 이미지 갯수(스레드 최대 갯수)

-(void)setMaxAsyncCount:(NSUInteger)count;


//오퍼레이터 추가 

-(void)addImageOperator:(AsyncAndCacheImageOperator*)imageOperator;


@end



제 소스는 많은 테스트는 거치지 못했습니다. 그러므로 여기 개발자 분들께서 필요하시다면 제 소스를 분석하고 수정하면서 개선해주셨으면 합니다.  

----------------------------
수정사항 1
http://cafe.naver.com/mcbugi/95095 에도 이 글을 올렸습니다.
역시 소스가 공개되니 많은 분들이 테스트도 해주시고 좋네요. 만약 이미지 경로가 image.php?ggg=465.jpg로 되어 있다면 ?ggg=465.jpg 부분을 제대로 가져오지 못하는 버그가 있더군요. [_url path]로 되어 있는 부분을 [_url absoluteString]으로 하면 괜찮다고 합니다. 수정해서 쓰세요. 

수정사항 2
초기에 이미지가 붙어 있는데 주어진 이미지 경로에 이미지를 못불러오는 경우 image = nil이 되기 때문에 초기 이미지를 지우는 부분으로 이상하시다는 분도 계셨습니다. 이 부분은 아래처럼 처리하세요.

if (_canceled==NO) 
{
// 이 부분 추가
if (image)
_imageView.image = image;
}

if (_loadCompleteTarget!=nil) 
{
[_loadCompleteTarget performSelectorOnMainThread:_loadCompleteSelector withObject:self waitUntilDone:YES];
}


수정사항 3
이미지가 제대로 로딩이 안되는 버그가 있는데 메인스레드 관련 문제 때문입니다. 아래처럼 수정하세요.

if (_canceled==NO) {

    dispatch_sync( dispatch_get_main_queue(), ^{

        _imageView.image = image;

        [_imageView performSelector:@selector(layoutIfNeeded) withObject:nil];

    });

}


글쓴이 : 지돌스타(http://blog.jidolstar.com/723)


#ifdef ~ #else ~ #endif 
#define 

위 구문은 C, C++ 계열을 경험한 분들이라면 익숙한 내용일겁니다.
Xcode상에서 이것을 적절하게 활용하는 방법을 간단히 소개합니다.

커스텀 로그출력 

#ifdef USE_LOG

#define NSLog( s, ... ) NSLog(@"%s(%d) %@", __FUNCTION__, __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__])

#else

#define NSLog( s, ... )

#endif


위 코드처럼 사용한다면 USE_LOG가 정의되어 있으면 그 안에 있는 #ifdef~ #else 안에 정의된 #define 매크로가 사용되어집니다.  
즉 NSLog()사용시 커스터마이징된 NSLOG()로 출력해줍니다. USE_LOG가 정의되어 있지 않다면 #else ~ #endif내의 #define 매크로가 사용되어 지며  NSLog()를 사용한 모든 코드는 전부 무의미하게 만듭니다. 

이런 형태로 정의한 이유는 개발할때는 분명 상세한 로그가 필요하지만 배포시에는 필요없기 때문입니다. 개발시에 따로 커스터마이징된 NSLog를 사용하는 이유는 디버깅시에 좀더 상세한 로깅이 필요하고 NSLog()를 코딩하느라 힘빼지 않고도 단순히 원하는 내용만 삽입해도 어느 함수에서 실행된 건지, 몇번째 라인인지 등의 내용도 함께 출력해줄 수 있습니다. 

위 코드는 *.pch 에 넣어두시면 작성된 모든 코드에 적용됩니다. 꽤 유용하겠죠?


로컬,원격 접속 제어

#ifdef LOCAL 

#define _DEFAULT_URL @"http://localhost:8080"

#else

#define _DEFAULT_URL @"http://gateway.jidolstar.com"

#endif


위 방식의 경우 배포/개발에 상관없이 로컬테스트, 원격테스트를 각각 진행하고 싶을때 유용합니다. 가령, 일반 Debug시에는 로컬에서 테스트 해야하므로 LOCAL이 정의되어 있어야 합니다. 하지만 앱스토어에 올리는 경우라면 정의될 필요가 없는겁니다. Debug시더라도 Debug Local, Debug Remote로 나누어 관리할 필요도 있고 또는 배포라도 테스트 배포를 위해 Adhoc을 사용하는 경우는 Local, Remote로 나눠서 배포할 필요도 있을겁니다. 이럴때 이 방법은 유용합니다. 이 코드 또한 *.pch에 넣어두시면 됩니다.


사용방법
위 2가지 코드의 의미와 활용에 대해서는 언급했습니다. 하지만 구체적으로 어떻게 XCode상에 설정해야 저 코드를 사용할 수 있는지  설명하지 않았습니다.  

초반 프로젝트에 대한 Info에서 Build에는 Debug와 Release 설정만 있을겁니다.  Adhoc및 AppStore로 배포하기 위해서 이 설정을 두개 더 늘려 Debug, Release, Distribution Adhoc, Distribution AppStore 를 만들겁니다. 사실 위에서 언급한 내용이 없다면 이 정도로만 가져가셔도 테스트, 배포는 문제없습니다만...  로컬/원격 테스트를 빈번하게 진행해야한다던가 하는 경우라면 이야기가 좀 달라집니다. 위 코드에 필요한 #define을 매번 주석을 처리했다 풀었다 하는 것도 꽤 귀찮거든요. 

그래서 저는 Debug 를 하나더 카피해서 Debug Remote를 만들고 기존 Debug는 Local로 만듭니다. 
Distribution Adhoc도 Distribution Adhoc Local, Distribution Adhoc Remote 로 만듭니다. 하지만 Distribution AppStore는 로컬테스트는 진행하지 않으므로 그대로 둡니다.

그런다음 (맥 사파리에서는 네이버카페 글편집기가 이미지를 첨부할 수 없네요.)
Debug Local 설정을 선택후  Preprocess Macro 를 찾아 USE_LOG=1 LOCAL=1 을 입력합니다. 
Debug Remote 설정에는 Preprocess Macro에 USE_LOG 만 넣습니다.
Distribution Adhoc Local 설정에는 LOCAL=1 을 넣습니다. 
Distribution Adhoc Remote 와 Distribution AppStore 설정에는 아무것도 할필요가 없습니다.

이렇게 한다음 원하는 설정을 이용해 컴파일하는 것만으로 로그를 출력할지 말지, 로컬접속인지 원격접속인지 구분할 수 있게 됩니다. 

알고보면 진짜 별거 아닌데.... 이런 팁 하나하나가 생산성을 극대화 시키기 때문에 소개합니다.
좋은 팁 있으면 많이들 알려주세요. ^^

글쓴이 : 지돌스타(http://blog.jidolstar.com/722)
개인적으로 Flex, ActionScript 3.0 기반에서 개발을 오래도록 해와서 그런지 아이폰의 통지(Notification) 사용법이 꽤 낫설더군요.
그래서 ActionScript 3.0에서 사용되는 Event기반과 비슷하게 한번 만들어 봤습니다.

Event와 Notification은 비슷합니다.  하지만 사용하는 인터페이스가 불편함이 느껴졌습니다.

간단히 개념 설명이 들어가면... 
제가 만든 Event는 다음과 같은 용어를 씁니다.
1. 이벤트 송출자 ( Event Dispatcher)
2. 이벤트 (Event)
3. 이벤트 청취자 (Event Listener) 

이벤트는 데이터입니다. 이벤트가 발생(송출)할때 이벤트명,이벤트송출자,이벤트데이타를 담는 역할을 합니다. 이벤트는 이벤트 송출자가 만들고 이벤트 청취자가 받습니다.

이벤트 송출자는 이벤트를 만들어 보내는 역할을 합니다. 보낼때 이벤트를 송출하는 객체, 이벤트명, 그리고.. NSObject를 확장한 데이터를 담은 객체를 하나로 묶은 이벤트를 만들어주고 등록된 이벤트 청취자중 같은 이벤트명으로 등록된 청취자에게만 이벤트를 전달합니다. 

이벤트 청취자는 송출된 이벤트를 받습니다. 이벤트 청취자는 target/selector 를 지칭하며 청취자 등록시 함께 등록하는 이벤트명에 따라 실행됩니다. 즉, 이벤트 송출시 보내지는 이벤트명과 등록된 청취자의 이벤트 명이 같아야합니다. 반대로 이벤트 명이 다르면 절대 호출되지 않습니다. 


다음은 만들어진 소스의 header 코드입니다. 

//

//  Event.h

//

//  Created by Yongho Ji on 10. 10. 25..

//  Copyright 2010 Wecon Communications. All rights reserved.

//

// 이벤트를 다룬다. 내부적으로 Notification 기능을 한번 감싼형태이다

// 이렇게 만드는 이유는 호스트코드에서 쉽게 사용할 있게 하기 위함이다.

//


#import <Foundation/Foundation.h>


//=================================================

//

// 이벤트 

// 1. 내부 생성클래스이다

// 2. 개발자가 직접 생성하지 않는다.

// 3. 개발자는 등록된 이벤트 청취자로부터 이벤트 객체를 받게 되어 있다

//

//=================================================



@interface Event: NSObject

{

@private

id _dispatcher; // 이벤트 송출자

id _data; // 이벤트 데이타 

NSString *_eventName; // 이벤트  

}


@property (nonatomic, readonly) id dispatcher;

@property (nonatomic, readonly) id data;

@property (nonatomic, readonly) NSString *eventName;


@end



//=================================================

//

// 이벤트 청취자 

// 1. 내부 클래스이며 외부 노출되지 않는다.

// 2. 이벤트 청취 목록을 관리하기 위한 클래스이다

//

//=================================================

@interface EventListener : NSObject

{

@private

id _listener; //이벤트 청취자 

SEL _selector; //이벤트 청취자의 selector

NSString *_eventName; //이벤트 이름 

}


@property (readonly) id listener;

@property (readonly) SEL selector;

@property (readonly) NSString* eventName;


-(id)initWithListener:(id)listener selector:(SEL)selector eventName:(NSString*)eventName;

@end


//=================================================

//

// 이벤트 센터

// 1. 싱글톤 클래스이다.

// 2. [EventCenter defaultCenter] 접근해서 사용한다.

// 3. 이벤트 청취자 등록은 [[EventCenter defaultCenter]add:self selector:@selector(mySelector:) eventName:@"MY_EVENT"] 형태로 한다.

// 4. 이벤트 청취자 삭제는 3가지가 있다

//   - 1개의 이벤트 명에 대한 청취자 삭제 [[EventCenter defaultCenter] remove:self selector:@selector(mySelector:) eventName:@"MY_EVENT"] 형태로 한다.

// - 여러개의 이벤트 명에 대한 청취자 삭제 [[EventCenter defaultCenter] remove:self selector:@selector(mySelector:)] 형태로 한다.

// - 모든 이벤트 청취자 삭제 [[EventCenter defaultCenter] remove:self] 형태로 한다.

// 5. 이벤트 발송은 2가지 방법이 있다.

// - 데이터가 미포함된 이벤트 : [[EventCenter defaultCenter] dispatch:self eventName:@"MY_EVENT"] 

//   - 데이터가 포함된 이벤트 : [[EventCenter defaultCenter] dispatch:self eventName:@"MY_EVENT" data:myData]

//   - 데이터는 NSObject 확장한 것이라면 어떤 것이든 보낼 있다

//   - 이벤트가 발송되면 해당 이벤트 이름으로 등록된 모든 이벤트 청취자를 실행한다.

// 6. 이벤트 청취자는 listener selector 한묶음으로 본다. selector -(void)mySelector:(Event*)event; 또는 -(void)mySelector; 형태로 만들면 되겠다

//   , 인자로 Event객체가 오면 속성값에 dispatcher(이벤트를 발생한 ), eventName(이벤트명), data(이벤트시 보낸 데이타) 참조할 있다.

// 7. UIViewController기반에서 이벤트 등록과 삭제는 반드시 각각 viewWillAppear, viewWillDisappear에서 하도록 한다. 왜냐하면 등록된 이벤트 청취자 관리를 못하면 중복 호출이 있다.

//=================================================

@interface EventCenter : NSObject {

NSMutableArray *eventListeners;

}


//이벤트 기본 Center. 싱글톤 처리 

+(EventCenter*)defaultCenter;

//이벤트 청취자 등록 (중복된 등록은 무시됨)

-(void)add:(id)listener selector:(SEL)selector eventName:(NSString*)eventName;

//이벤트 청취자 삭제. selector, eventName 상관없이 같은 listener 등록되어 있으면 모두 삭제. 이미 삭제되어 있다면 무시

-(void)remove:(id)listener;

//이벤트 청취자 삭제. eventName 상관없이 같은 listener, selector 등록되어 있으면 모두 삭제. 이미 삭제되어 있다면 무시

-(void)remove:(id)listener selector:(SEL)selector;

//이벤트 청취자 삭제. listner, selector, eventName 모두 같아야 삭제된다. 이미 삭제되어 있다면 무시.

-(void)remove:(id)listener selector:(SEL)selector eventName:(NSString*)eventName;

//이벤트 송출. 데이터 미포함 

-(void)dispatch:(id)dispatcher eventName:(NSString*)eventName;

//이벤트 송출. 데이터 포함

-(void)dispatch:(id)dispatcher eventName:(NSString*)eventName data:(id)data;

@end


총 3개의 클래스로 구성되어 있으나 이벤트 송출자 등록, 이벤트 청취자 등록, 이벤트 송출은 모두 EventCenter 클래스가 관장하며, EventCenter는 싱글톤 처리되어 [[Event defaultCenter] ...] 형태로 접근이 가능합니다.

내부적으로 NSNotification을 한번 감싸서 통지를 이벤트로 추상화처리 한 것입니다. 

아마도 Objective-C의 NSNotification 또는 ActionScript의 Event를 사용해보신 경험이 있는 분은 별다른 설명없이도 이 소스를 그냥 가져다가 쓸 수 있습니다.

사용법은 주석으로 간단하게 적어두었습니다. 주의사항도 꼭 확인하시고 사용하시면 됩니다.

개선사항이나 버그가 있다면 댓글 부탁드리겠습니다. ^^

소스는 첨부합니다.


----------------------------------------------
사용법을 문의하시는 분들이 계시네요.  소스도 공개했구 주석처리 다했는데 ^^;;
기본적으로 Notification을 이해하시면 Event도 이해할 수 있습니다. 
그래도 사용하시는데 어려움을 겪으시는 것 같아 간단하게 예제를 들어보지요.

1. 이벤트명 정의 
#define EVENT_WRITE @"eventWrite"

2. 이벤트 발생 
DispatchClass 안에서 아래처럼 코딩합니다.

[[EventCenter defaultCenter]dispatch:self eventName:EVENT_WRITE data:postData];

3. 이벤트 청취 메소드 정의 
ListnerClass 내부에서 아래처럼 정의합니다.

-(void)_myMethod:(Event*)event
{
 PostData *postData = (PostData*)event.data;
 NSLog(@"eventName=%@,  dispatcher=%@",event.eventName, event.dispatcher);
}

4. 이벤트 청취자 등록 
ListenerClass 내부에서 아래처럼 정의합니다. 단 정의시에는 viewDidLoad가 아닌 viewWillAppear나 viewDidAppear등에서 하시는게 메모리 관리가 가능합니다.
[[EventCenter defaultCenter]add:self selector:@selector(_myMethod:) eventName:EVENT_WRITE];

5. 이벤트 청취자 삭제 
ListenerClass 내부에서 아래처럼 정의합니다. 단! dealloc에서는 하지 마세요. 왜냐하면 등록한 listener 내부적으로 retain되기 때문입니다. viewWillAppear등에 add 하셨다면 viewWillDisappear에서 하시면 됩니다. add로 등록하지 않아도 삭제한다고 문제되지 않으니 메모리 관리를 위해서 꼭!!! remove는 호출하셔야 합니다.
[[EventCenter defaultCenter]remove:self selector:@selector(_myMethod) eventName:EVENT_WRITE];

삭제방법은 총 3가지죠. 위 코드는 이벤트에 대한 등록한 청취자 1개만 삭제할때 씁니다. 
하지만 ViewController가 더이상 쓸모가 없어진다면 
[[EventCenter defaultCenter]remove:self] 만 하셔도 self에 정의된 모든 이벤트 청취자를 삭제해줍니다. 



이상입니다. 더 개선할 사항에 대한 아이디어가 있다면 언제든지 답글주세요. ^^

글쓴이 : 지돌스타(http://blog.jidolstar.com/721)

자바 커뮤니티 공동 3 세미나

자바 개발자를 위한 ‘共感(공감)을 찾아서 세번째

 

지난 2 세미나에 이어서 OKJSP, JBoss User Group, KSUG 공동 주최하고,

한국어도비시스템즈가 후원하는 자바 개발자를 위한개발 공감’ 세번째 세미나가

진행됩니다. 최근 IT 업계를 선도적으로 이끌어 가고 있는 모바일과 시장에서 차별화된

전략을 갖추실 있는 유용한 기술과 정보들을 자리에서 들으실 있는 기회입니다.

특별히, T스토어 주최 안드로이드 앱 공모전에서 금상을 수상한 블투맞고’ 개발자이신

김재철님을 초대하여 개발자 여러분과 함께할 수 있는 공감 세션을 마련했습니다.

 


행사 안내



 


AGENDA




특별 경품


5분께 화제의 서적 '안드로이드 프로그래밍 정복(김상형 저)'을 드립니다.



등록 : http://adoberia.co.kr/iwt/blog/blog.php?tn=flex&id=540


Java Spring Framework를 활용하는데 발생한 문제이다.  

@Controller 어노테이션을 가지고 한글과 영어가 섞인 문자열을 반환하기 위해 @ResponseBody 어노테이션을 이용해 다음과 같이 코딩했다. 

 

@Controller
@RequestMapping("/foo")
public class Foo {
    @RequestMapping(value="/foobar", method=RequestMethod.GET)
    @ResponseBody
    public string getFoobar() {
      // Do something that returns some UTF-8 stuff (probably some XML).
    }
}

 

그리고 문자 인코딩 필터 설정을 아래와 같이 했다. 

 

<filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
</filter>

<filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
</filter-mapping>

 

 

하지만 위 설정은 Request에 대해서만 CharacterEncodingFilter가 먹힌다고 한다. 그래서 Response에 대해서도 UTF-8이 될 수 있도록 설정할 필요가 있다.

 

<bean  class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
   <property name="messageConverters">
      <list>
         <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
            <property name = "supportedMediaTypes">
               <list>
                  <value>text/plain;charset=UTF-8</value>
               </list>
            </property>
         </bean>
      </list>
   </property>
</bean>

 

위 설정은 @Controller에서 @ResponseBody로 지정시 반환값에 한글이 있으면 깨지는 현상을 방지하기 위해 사용한다.

 

명확히 이해하고 사용하는 건지 모르겠지만 분명 저렇게 하면 한글이 안깨지고 잘 반환한다.

 

참고글 

1. https://issues.springsource.org/browse/SPR-6443

2. http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/filter/CharacterEncodingFilter.html

3. http://dev.firnow.com/course/3_program/java/javajs/20100719/455191.html

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/700)  

이 글은 JPetStore라는 간단한 쇼핑몰 웹애플리케이션을 Eclipse에서 테스트하기 위한 방법을 소개한다. JPetStore는 원래  MS에서 소개된 샘플이나 나중에 java진영에 컨버팅되면서 더 유명해진듯 하다. 그러므로 JPetStore는 java만을 위한 것이 아님을 알고 접근하는 것이 좋겠다.

Spring Framework에 번들로 제공하는 JPetStore샘플은 Spring을 학습하는데 많이 유용하다. Spring 서적을 통해 학습한 내용을 실습한다는 차원에서 접근하면 좋겠다. Spring framework 2.5iBatis로 구성된 예제를 참고했다.

Eclipse 갈릴레오(JEE), JDK 1.6.0.18, Tomcat 6.0 , MySQL 5.0, Window 7 환경에서 테스트했음을 밝힌다. 즉, 이들 환경을 구축하는 방법은 모두 생략한다.


1. Spring 2.5 버전을 다운로드
현재 Spring 3.0 버전이 배포되고 있지만 JPetStore의 경우 2.5버전에만 있다. 다운로드 받을때는 2.5 최상위 버전을 다운로드 받되 뒤에 dependencies가 붙은 압축파일을 받도록 하자. 이 압축파일은 spring 2.5에 의존하는 lib나 예제들이 수록되어 있다. JPetStore 샘플도 여기에 있다.



다운로드 받으러 가기 : http://www.springsource.com/download/community

위 주소로 접속하면 첫화면에서 개인정보를 넣으라고 할것이다. 그냥 무시하고 download page 링크를 찾아 클릭하길 바란다. 다운로드를 완료하면 아무데나 압축을 풀면 되겠다.


2. 배포파일(war) 만들기
다운로드를 받아 samples/jpetstore 로 들어가면 아래와 같은 폴더 및 파일을 볼 수 있다.

원래는 ANT를 설치한 상태에서 ANT 스크립트를 담은 build.xml 만을 이용해 내부에 있는 명령인 "clean", "build", "warfile" 명령들을 이용해 최종배포파일인 war파일을 만들 수 있다. 하지만 그렇게 하면 많은 부분 불편하므로 spring 배포자가 이와 더불어 warfile.bat와 build.bat를 추가해서 ANT 환경을 구축해야하는 번거로움을 없애줬다. warfile.bat만 더블클릭으로 실행하면 자동으로 war를 만들어준다.

warfile.bat 구조은 다음과 같다.
build.bat warfile

이것은 위 build.bat를 실행하고 "warfile" ANT 스크립트 명령을 실행하는 것을 의미한다.

build.bat 내용은 다음과 같다.
"%JAVA_HOME%/bin/java" -cp ../../lib/ant/ant.jar;../../lib/ant/ant-launcher.jar;"%JAVA_HOME%/lib/tools.jar" org.apache.tools.ant.Main %1

build.bat는 JDK 설치경로에 ANT관련 jar를 복사해서 ANT를 실행할 수 있도록 해주는 것이 주 목표인것이다. 마지막에 org.apache.tools.ant.Main %1은 1개의 parameter를 받아 실행하겠다는 의미이다. 결국 %1이 warfile.bat의 "warfile" 문자열로 대체되어 build.xml의 warfile명령을 실행하도록 하는 것을 의미한다.

이제 warfile.bat만 두번 클릭해서 실행하면 된다. 혹시 제대로 실행이 되지 않을지도 모른다. 이것은 JDK 경로 설정이 잘못되었기 때문인데 build.bat에 자신의 JDK 경로를 아래와 같이 추가해보고 warfile.bat를 재실행해보길 바란다.

set JAVA_HOME=C:\Program Files\Java\jdk1.6.0_18
"%JAVA_HOME%/bin/java" -cp ../../lib/ant/ant.jar;../../lib/ant/ant-launcher.jar;"%JAVA_HOME%/lib/tools.jar" org.apache.tools.ant.Main %

이제 %JAVA_HOME% 변수를 제대로 설정했기 때문에 이제 war파일을 제대로 만들 수 있게 된다.

만약 모두 지우고 war를 새로 만들고 싶다면 warfile.bat에서 warfile을 all로 수정하자.
build.bat all

이 모든과정을 이해하기 위해 build.xml에 있는 ANT 스크립트를 한번 훑어보는 것을 추천한다.

모든 과정을 마쳤다면 다음과 같이 .classes, dist 폴더가 추가되었을 것이다.


dist 폴더로 들어가면 jpetstore.war가 있다. 또한 war/WEB-INF/lib가 생성되어 그안에 필요한 jar가 복사된 것도 확인하자.


3. Eclipse에서 프로젝트 만들기

2가지 방법중 하나만 사용하면 되겠다. 먼저 Eclipse를 실행하기 바란다.


3.1. Web Project를 직접 만들기
File > New > New Project로 들어가 아래처럼 Dynamic Web Project를 선택한뒤 Next 버튼을 누른다.



Project name으로 jpetstore를 입력하고 Target runtime은 Tomcat 6.0을 선택하면 되겠다. Finish 버튼을 누른다.

2번 항목에서 samples/jpetstore 폴더로 가서 src와 war 내에 있는 파일을 각각 복사해 Eclipse내에 생성된 src와 WebContent 폴더에 파일을 복사한다.



3.2 war파일을 이용해서 프로젝트 만들기

이것은 3.1 방법외에 또 다른 방법이다.

2번 항목에서 samples/jpetstore/dist 폴더가 생성되었고 그 안에 jpetstore.war 를 생성했었다. 이것을 이용해 프로젝트를 생성한다. 이클립스에서 File > Import 를 들어가 아래처럼 WAR file을 선택한다.


아래 그림처럼 WAR file 경로(samples/jpetstore/dist)를 찾아 jpetstore.war를 선택하고 web project 이름과 target runtime을 설정한다. Finish 버튼을 누른다.


이렇게 프로젝트를 생성하면 src에는 아무것도 없다. 이 상태에서 실행해도 되지만 실제로 우리는 이클립스라는 개발툴을 활용하고 있으므로 java소스도 가져와야겠다. 그러므로 samples/jpetstore/src 에 있는 내용을 프로젝트의 src에 복사하도록 하자. Project Explorer에는 다음처럼 보여야 하겠다.



4. 실행해보기
3번 항목으로부터 만들어진 프로젝트는 이제 테스트해볼 수 있는 준비가 마련되었다.

이클립스에서 Window > Show View > Other 를 선택하면 아래와 같은 창이 나오고 거기서 Server를 선택한다.


이클립스 View 창에 아래처럼 Servers가 추가되어 있는 것을 확인할 수 있을 것이다. 테스트할 서버를 추가하자. 먼저 빈공간에 오른쪽 버튼을 눌러 New>Server를 선택한다.

아래와 같은 창이 나오면 Tomcat v6.0 Server를 선택한다.(필자는 Tomcat 6.0이 이미 설치되어 있으므로...) Next를 누른다.


다음과 같은 창이 나오면 우리가 만든 jpetstore 프로젝트가 왼쪽에 나오는데 이것을 선택한뒤 Add> 버튼을 눌러 Configured 부분에 추가한다. 이로써 우리 프로젝트는 Tomcat 서버위에서 하나의 웹애플리케이션으로 동작할 수 있게 된다. Finish 버튼을 누른다.


만들어진 서버에 jpetstore가 올라가 있음을 아래처럼 Servers View에서 확인할 수 있다. 이제 아래 그림처럼 실행버튼을 눌러 서버를 실행한다. 단, Tomcat 서버가 이미 실행되고 있으면 중지시킨뒤 해야하겠다.


참고로 필자는 서버 설정시 Ports를 80으로 지정한뒤 실행했다.



http://localhost/jpetstore 를 웹브라우저 주소창에 넣으면 아래와 같은 화면을 볼 수 있겠다. 만약 Ports가 8080이면 http://localhost:8080/jpetstore로 접속하자. 내용을 보면 이 jpetstore 애플리케이션은 Spring과 iBatis로 만들어졌음을 보여주고 있다. Enter the Store 버튼을 누르면 실제 Store로 입장할 수 있게 된다.


5. 데이타베이스(MySQL)과 연동
아직 설정이 완료된 것은 아니다. 데이타 베이스 설정이 남아 있기 때문이다. JPetStore의 이곳저곳 들어가보면 MySQL 접속문제로 에러가 발생하는 것을 알 수 있을 것이다.

Spring Framework는 데이터베이스와의 연동을 위한 ORM(Object-Relation Mapping)을 지원하여 iBatis, Hibernate, JPA와 같은 영속성 프레임워크와 연동을 지원한다. 본 JPetStore는 그중에 iBatis를 이용하게 된다. 근간으로는 모두 JDBC와 같은 추상층을 사용하기 때문에 DB종류에 상관없이 간단한 프로퍼티 설정만으로 DB접속이 가능하며 JPetStore 예제는 hsqldb, mysql, oracle, postgres로 테스트 할 수 있도록 하고 있다.

필자는 많은 DB중에 MySQL 환경이 이미 구축되어 있으므로 이것을 사용하도록 하겠다. 다른 DB도 거의 비슷하므로 따로 설명은 필요 없겠다.


5.1 JDBC MySQL 드라이버 설치

MySQL 데이타베이스에 접근하기 위해 JDBC 드라이버를 다운로드 받아 프로젝트내에 포함시켜야 한다. 먼저 아래 링크에서 다운로드 받자.

http://dev.mysql.com/downloads/connector/j/

압축을 푼 뒤, mysql_connector_java 로 시작하는 jar 파일이 존재할 것이다. 이것은 프로젝트 내에 WebContent/WEB-INF/lib에 복사한다. 여기서 lib는 JPetStore를 동작시키기 위한 프레임워크 및 라이브러리 jar가 포함되어 있다. Spring, iBatis도 있는 것을 확인하자.



5.2 MySQL 계정정보 설정
프로젝트내 WebContent/WEB-INF/jdbc.properties 는 JDBC 접속정보를 설정할 수 있도록 되어 있다.

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jpetstore
jdbc.username=jidolstar
jdbc.password=mypassword

위와 같은 설정은 5.1 항목에서 추가한 mysql driver에서 제공하는 MySQL 드라이버 클래스 사용한다는 것을 지정하고 있다. 또한 url, username, password를 설정하고 있다. 3306는 데이타 베이스 접속하기 위한 기본 포트번호 이며 그 뒤에 붙은 jpetstore는 접속할 DB 명이다. 그러므로 이들 정보는 필요에 따라서 수정해서 사용하면 되겠다.

5.3 MySQL DB를 만들고 테이블 생성
다운로드 받은 Spring Framework 폴더에 samples/jpetstore/db에 들어가면 총 4가지 자동 테이블 생성 코드가 있다. 여기서 mysql 폴더에 sql문이 존재한다.


먼저 MySQL에 "jpetstore" 이름을 가진 DB를 먼저 만든다.  jpetstore-mysql-schema.sql 을 열어보면 use jpetstore 부분이 맨 처음에 있다. 만약 DB 이름이 다른 이름을 가진다면 jpetstore 대신 다른 이름을 넣으면 되겠다. 그런 다음 위 두개의 sql 파일을 이용해 콘솔창에서 다음과 같이 해당 DB에 테이블과 데이터를 넣을 수 있다.
> mysql -uroot -p < jpetstore-mysql-schema.sql
Enter password: ***********
> mysql -uroot -p < jpetstore-mysql-dataload.sql
Enter password: ***********

물론 MySQL을 위한 클라이언트 애플리케이션(SQLGate나 SQLyog등)을 사용해도 되겠다.

이제 JPetStore는 모든 페이지에서 제대로 동작하게 될 것이다. 이를 이용해 Spring과 iBatis에 대해 공부하면 되겠다.

참고글

글쓴이 : 지돌스타(http://blog.jidolstar.com/688)

AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)는 OOP(Object Oriented Programming)의 한계를 극복하는데 매우 좋은 개념이다.

[출처]Toby's Epril : http://toby.epril.com


위 그림은 AOP 예를 간단히 보여주고 있다. 계좌이체, 입출금, 이자계산을 담당하는 Class는 핵심모듈로 구분된다. 보안, 로깅, 트렌젝션과 같은 것은 핵심을 보조하는 횡단모듈로 구분된다. OOP만으로 하다보면 핵심안에 횡단이 섞이는 현상이 발생하기 쉽다. 가령, 로깅기능이 계좌,입출금,이자계산에 모두 섞여서 나중에 유지보수 및 확장성이 매우 힘들게 된다. 그 예로, 핵심기능에 문제가 발생했을때 관리자에게 문자로 메시지를 보내는 기능도 포함한다고 하자. 그럼 핵심기능에 문자라는 횡단기능을 추가해서 더 복잡해지는 상황이 발생한다. AOP는 이러한 문제를 깔끔하게 해결해준다. 그러니깐 핵심모듈 Class내에 횡단모듈 Class가 섞이지 않도록 하는 것이 AOP의 핵심이다라고 할 수 있다. 일종의 Class 단일책임의 원칙을 실현할 수 있다.

Java 진영에서 엔터프라이즈급 웹애플리케이션을 제작하면서 AOP 개념을 사용하지 않고서는 확장/유지보수이 매우 어려울지 모른다. Spring framework는 AOP를 지원함으로서 보다 좋은 확장성과 유지보수의 유용성을 보장한다. 또한 다른 클래스에 대한 의존성 없는 클래스 제작이 좋아져 보다 완벽한 단위테스트가 가능해지는 장점도 있다.

.net 계열에서는 Spring.NET에서 AOP를 지원해준다.

하지만 AOP가 장점만 있지는 않을 것이다. 필연적으로 Proxy개념을 도입해 기존 클래스를 감싸서 다른 클래스와의 의존성을 더해주는 형태이므로 속도적인 측면에서 이득은 별로 없다.
AOP는 컴파일, 클래스 로딩, 런타임 3가지 형태로 적용(이 과정을 Weaving이라 함)하는 방법이 있다. 자바 Spring AOP는 Proxy개념을 도입하기 때문에 런타임시 기존 클래스를 감싸서 다른 클래스와의 의존성을 더해주는 형태이므로 속도적인 측면에서는 이득이 별로 없을 수 있지만 장점이 더 많기 때문에 잘 사용된다.

AOP에 대한 개념을 알기 위해 다음 글들이 매우 좋은 것 같다. 참고 바란다.


Java 진영에서 Spring Framework로 AOP에 관심을 가지면 다음 링크 정도는 알고 있어야 하지 않을까 생각한다.


다음글은 Spring 을 통한 AOP를 개발하기 위한 참고 글들이다. 좋은 참고가 될 것이다.


간단한 테스트를 하려면 Eclipse에 AJDT, SpringIDE를 설치하고 아래 소스로 감각을 익힐 수 있지 않을까 생각한다. 소스는 원래 http://whiteship.me/1173 의 소스를 약간 변경한것이다. Spring Framework 3.0을 다운받아 사용했다.


Eclipse에서 Java Project를 만들고 그 프로젝트에 Import 시켜서 테스트 하면 되겠다. 단, SpringAOPAspectJ는 반드시 AJDT 플러그인이 설치되어 있어야 컴파일/실행이 가능하겠다.

AOP는 웹애플리케이션에서는 큰 이득이 있겠으나 일반 애플리케이션에서는 속도 및 용량 측면에서 이득보다 실이 더 많을 수 있을지 모르겠다는 판단이 든다. 더 공부해 봐야겠다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/687)
Eclipse에서 CDT 플러그인과 MinGW에서 지원하는 C/C++ 컴파일러를 통해 개발하는 방법에 대해서 소개했었다.
Eclipse Galileo에서 C,C++ 개발환경 구축하기 - CDT, MinGW

참고로 오래전에 MS Visual Studio 환경에서 MySQL과 연동하는 방법과 ActionScript 3.0 으로 만들어진 MySQL Driver에 대해 소개한 적이 있다.
MS Visual C++ 6.0 환경에서 MySQL 연동하는 방법
Actionscript 3 Mysql Driver

이 글은 Eclipse + MinGW 환경에서 C/C++로 MySQL과 연동하는 방법을 소개한다. MySQL 연동을 위해서 Eclipse환경을 선택한 이유는 기존 프로젝트들이 Java, Flash 로 제작되어서 하나의 툴에서 개발하고 싶은 욕구가 있기 때문이다. 하나의 Eclipse로 Java, PHP, C/C++, Flash 개발을 다할 수 있기 때문에 지원하는 각종 플러그인을 함께 공유할 수 있고 툴을 이리저리 이동할 필요도 없어진다.

Eclipse와 MinGW으로 개발환경을 구축한 만큼 그에 맞게 개발방식을 알아야 할 것이다.


1. MinGW Utils 설치

MinGW에서 지원하는 Utils을 다운로드 받아서 설치해야 한다. 이것이 필요한 이유는 reimp.exe 가 필요하기 때문이다. 자세한 내용은 다음에 설명한다.

다음 링크로 들어가서 아래 리스트에 MinGW Utilities > mingw-utils > mingw-utils-0.3 로 가면 mingw-utils-0.3.tar.gz 파일이 있다. 이것을 다운로드 받는다.

http://sourceforge.net/projects/mingw/files/

압축을 풀면 bin, doc 폴더가 있는데 이 두개 폴더를 복사해서 MinGW 설치폴더에 덮어씌우면 된다. 그것으로 설치 끝이다.


2. MySQL 라이브러리 설치
MySQL 서버가 이미 설치가 되어 있는 경우라고 가정한다. 일단 다음 링크로 가서 MySQL MSI 설치자를 다운로드 받는다.

http://dev.mysql.com/downloads/mysql

다운로드 받은수 설치시에 아래와 같은 Setup Type을 묻는 내용이 나오면 custom을 선택한다.

MySQL이 이미 설치되어 있거나 어떠한 경로라도 접속할 수 있는 환경이 이미 조성되어 있는 경우에는 Server를 굳이 설치할 필요가 없으므로 아래처럼 C개발시 필요한 파일만 설치가 되도록 설정한다.

아마도 C:/Program Files/MySQL/MySQL Server 5.1/ 내에 설치될 것이다. 그 안에는 include와 lib만 존재할 것이다. include는 c언어의 header 파일들이 존재할 것이다. 그리고 lib에는 MySQL에 접속하기 위한 각종 Lib파일들이 존재한다.


3. MySQL 연동을 위한 MinGW 컴파일러 라이브러리로 만들기
MySQL설치 폴더에 lib/opt/libmysql.lib 가 있다. 이것은 MySQL을 접속하기 위한 구현체 정의가 담겨있다. C,C++로 개발할 때는 이것을 참조해서 개발하고 컴파일해야 할것이다.

하지만 MinGW에서 제공하는 컴파일러는 .lib를 직접 사용할 수 없다. 그래서 대신 확장자가 .a인 MinGW 전용의 라이브러리로 변경해야하는데 앞서 소개한 MinGW Utils에 reimp.exe와 dlltool.exe가 그것을 가능하게 해준다.

먼저 CMD 창에 들어가서 다음과 같이 입력해본다.

C:\>cd C:/Program Files/MySQL/MySQL Server 5.1/lib/opt
C:\>reimp -d libmysql.lib
C:\>dlltool -d LIBMYSQL.def -D libmysql.dll -k -l libmysql.a

위처럼 명령후 opt 폴더안에 libmysql.a가 있는지 확인한다. 만약 없다면 opt폴더를 c:\ 하위로 옮겨보고 그 안에서 위 명령을 다시 해보길 바란다.

reimp -d libmysql.lib 는 LIBMYSQL.def를 만들어내는데 이것을 열어보면 라이브러리에 정의된 함수가 쭉 나열되어 있음을 확인할 수 있다. dlltool 은 이것을 이용해 libmysql.a를 뽑아낸다.

libmysql.a는 복사해서 MinGW의 lib폴더에 복사한다.

그리고 MySQL 설치폴더에 include은 mysql로 수정하고 MinGW의 include 내에 복사하면 아래처럼 사용할 수 있게 된다.

#include <mysql/mysql.h>


5. Eclipse에서 C프로젝트를 생성해 MySQL 접속해보기
이제 C로 MySQL에 접속하기 위한 준비는 완료했다. 이제 C프로젝트를 만들고 MySQL 접속하기 위한 간단한 예시를 만들어보자. File>New>C Project를 아래처럼 생성한다.



Project Explorer에 MySQLTest 프로젝트가 만들어졌을 것이다. src 폴더를 열어 MySQLTest.c를 다음 코드로 수정하자.


#define SOCKET int

#include <stdio.h>
#include <mysql/mysql.h>

#define DB_HOST "localhost 또는 아이피, 도메인"
#define DB_USER "DB 사용자 ID"
#define DB_PASS "DB 사용자 Password"
#define DB_NAME "DB 이름"

int main() {
	MYSQL *conn_ptr;
	conn_ptr = mysql_init(NULL);
	if(!conn_ptr) {
		printf("mysql_init failed!!\n");
		return 0;
	}

	conn_ptr = mysql_real_connect(conn_ptr, DB_HOST, DB_USER, DB_PASS, DB_NAME, 3306,(char *)NULL, 0);

	if(conn_ptr) {
		printf("연결이 성공 되었습니다.\n");
	} else {
		printf("연결에 실패했습니다.\n");
	}
	mysql_close(conn_ptr);
	return 1;
}

코드를 만들었으니 이제 MySQL 라이브러리를 사용한다는 컴파일 옵션을 주어야 한다. 방법은 다음과 같이 하면 되겠다.

이클립스 메뉴에서 Project > Properties로 들어간다. C/C++Build > Setting으로 들어가 Tool Setting 탭으로 누른다. 다음으로 MinGW C Linker > Libraries를 선택한다. 아래처럼 mysql을 등록한다. 이렇게 하면 컴파일시 -lmysql로 옵션을 주는 것과 같아진다.


Ctrl+B나 메뉴>Project>Build All을 선택해서 컴파일 해보자. 다음과 같처럼 컴파일이 정상적으로 나왔다면 성공한 것이다. 
 

**** Build of configuration Debug for project MySQLTest ****

**** Internal Builder is used for build               ****
gcc -O0 -g3 -Wall -c -fmessage-length=0 -osrc\MySQLTest.o ..\src\MySQLTest.c
gcc -oMySQLTest.exe src\MySQLTest.o -lmysql
Build complete for project MySQLTest
Time consumed: 578  ms. 

Ctrl+F11이나 Run > Run 을 해보자. 콘솔창에 아래처럼 나오면 성공한 것이다.


만약 위처럼 아무 메시지 없이 실행되지 않는 것처럼 보인다면 C:/Windows/System32 내에 libmysql.dll이 없는 것을 의심해본다.

실제로 CMD창을 열고 프로젝트의 Debug안에 있는 MySQLTest.exe를 CMD 창에 드래그해 붙여넣은 다음 Enter를 눌러 실행해보자. 아래처럼 "연결이 성공 되었습니다"라고 보이지 않고 그 아래처럼 libmysql.dll이 없다고 나오면 아래서 소개하는 두가지 방법으로 해결할 수 있다.



일단 libmysql.dll 이 있다고 가정한다면 아래 둘중에 하나를 선택해서 하면 된다.

  1. libmysql.dll을 C:/windows/System32에 복사한다.
  2. 프로젝트의 Debug폴더나 Release폴더(즉, exe 실행파일과 같은 경로)에 libmysql.dll을 복사한다.


dll과 함께 컴파일 처리하면 좋겠는데 아직 방법은 모르겠다.

libmysql.dll을 얻는 방법은 http://www.dll-files.com/ 에 접속해 libmysql.dll을 찾는다. libmysql.dll을 다운로드 받을 수 있는 페이지가 나오면 다른거 누르지 말고 아래 그림과 같은 Free download의 Download 버튼을 눌러 다운로드 받는다.



6. 테이블 생성, 선택, 삽입, 편집, 삭제 해보기
접속까지 했으니 응용하는 것은 누워서 떡먹기다. Create table, Select, Insert, Update, Delete 예제는 다음과 같다.

#define SOCKET int

#include <stdio.h>
#include <mysql/mysql.h>

#define DB_HOST "localhost 또는 아이피, 도메인"
#define DB_USER "DB 사용자 ID"
#define DB_PASS "DB 사용자 Password"
#define DB_NAME "DB 이름"

#define SQL_CREATE_TABLE "CREATE TABLE `mysql_api_test` (\
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,\
    `num` INT NULL ,\
    `string` VARCHAR( 20 ) NULL \
    ) TYPE = MYISAM ;"
#define SQL_INSERT_RECORD "INSERT INTO `mysql_api_test` ( `id` , `num` , `string` ) \
    VALUES (\
    NULL , '%d', '%s'\
    );"
#define SQL_SELECT_RECORD "SELECT * FROM `mysql_api_test`"
#define SQL_DROP_TABLE "DROP TABLE `mysql_api_test`"

int main() {
	MYSQL *connection=NULL, conn;
	MYSQL_RES *sql_result;
	MYSQL_ROW sql_row;
	int query_stat;
	int i;

	char query[255];

	mysql_init(&conn);

	// DB 연결
	connection = mysql_real_connect(&conn, DB_HOST, DB_USER, DB_PASS,DB_NAME, 3306,(char *)NULL, 0);
	if(connection==NULL) {
		fprintf(stderr, "Mysql connection error : %s", mysql_error(&conn));
		return 1;
	}

	// 테이블 생성
	query_stat=mysql_query(connection,SQL_CREATE_TABLE);
	if (query_stat != 0) {
		fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
		return 1;
	}

	// 레코드 삽입
	for(i=0;i<5;i++) {
		sprintf(query,SQL_INSERT_RECORD,100+i,"안녕하세요 지돌스타예요~");
		query_stat = mysql_query(connection, query);
		if (query_stat != 0) {
			fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
			return 1;
		}
	}

	// 셀렉트
	query_stat=mysql_query(connection,SQL_SELECT_RECORD);
	if (query_stat != 0) {
		fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
		return 1;
	}

	// 결과 출력
	sql_result=mysql_store_result(connection);
	while((sql_row=mysql_fetch_row(sql_result))!=NULL) {
		printf("%2s %2s %s\n",sql_row[0],sql_row[1],sql_row[2]);
	}
	mysql_free_result(sql_result);

	// 테이블 삭제
	query_stat=mysql_query(connection,SQL_DROP_TABLE);
	if (query_stat != 0) {
		fprintf(stderr, "Mysql query error : %s", mysql_error(&conn));
		return 1;
	}

	// DB 연결 닫기
	mysql_close(connection);
	return 0;
}


7. 정리하며
이미 많은 분들이 삽질(?)을 많이 해주셔서 비교적 쉽게 MySQL 개발 환경을 조성할 수 있었다. 뭔가 이런 환경에서 개발한다면 크로스 플랫폼 애플리케이션을 만드는데 도움이 될 것 같다. 관련된 정보를 찾아봐서 개발방법에 대해서 좀더 숙지해야할 필요가 있을 것 같다.


8. 참고글
Eclipse Galileo에서 C,C++ 개발환경 구축하기 - CDT, MinGW
MS Visual C++ 6.0 환경에서 MySQL 연동하는 방법
VC용 lib화일을 mingw용 라이브러리로 변환하기
MinGW로 Mysql plugin driver compile하는 방법
DLL 다운받기


글쓴이 : 지돌스타(http://blog.jidolstar.com/679)

윈도우 환경에서 Eclipse Galileo 버전으로 C, C++ 개발을 위한 환경을 만드는 것을 설명하는데 목표가 있다. 기존에 Eclipse기반으로 Flash Builder 플러그인을 설치해 Flash 개발을 하거나 Java 개발하시는 분들이 같은 환경에서 C, C++을 개발하고자 한다면 이 글은 유용한 팁정도가 될 것이다.


1. MinGW를 설치한다.
MinGW(한국어 발음 밍우?)는 무료로 쓰고 배포할 수 있는 MS 윈도우 헤더 파일과 라이브러리로, 어떠한 써드 파티 C 런타임 DLL에 의존하지 않고 네이티브 윈도우 프로그램을 만들 수 있는 GNU 툴을 제공한다. 쉽게 이야기해 MinGW는 윈도우에서 툴이나 dll에 의존하지 않는 동작하는 프로그램을 만들 수 있도록 도와준다.

MinGW에 대해 : http://ko.wikipedia.org/wiki/MinGW 

물론 C/C++를 개발하기 위해 Cygwin을 이용해도 된다. 하지만 cygwin 기반으로 제작한 것은 항상 cygwin1.dll이 필요한다. 또한 개발한 결과물은 라이센스 문제로 상용으로 팔기가 곤란하다. 게다가 윈도우에서 직접 개발한다기 보다 가상의 리눅스 콘솔을 이용하는 것이다. 그래서 여러가지로 MinGW이 장점이 많다.


1.1 MinGW 받기
공식 사이트 http://www.mingw.org/ 로 간다. 좌측 메뉴에 Downloads 페이지로 이동한다. Download Now 버튼을 눌러 최신버전인 MinGW-5.1.6.exe(2010.04.20)를 다운로드 받는다.


1.2 MinGW 설치
  • 다운받은 MinGW-5.1.6.exe를 실행한다.
  • Welcome 화면 Next를 클릭한다.
  • Download and install을 선택후 Next 버튼을 클릭한다.
  • I agree를 클릭한다.
  • Candidate를 선택후 Next를 클릭한다.
  • 설치하고자 하는 것을 선택후 Next를 클릭한다. g++ compiler와 MinGW Make는 반드시 선택한다.
  • 설치하고자 하는 디렉토를 선택한 다음 Next 클릭
  • Install을 클릭하면 이제 필요한 것을 다운로드 받아 설치를 시작한다.
  • 설치가 완료되면 Next 클릭후 Finish를 누른다.

 

1.3 GCC를 실행하기 위한 윈도우 환경변수 설정하기

이제 어느 경로에 있던지 gcc 컴파일러를 실행할 수 있도록 환경을 조성해줄 필요가 있다.

이 방법은 Cygwin을 사용하지 않고 오로지 MinGW만 사용하는 경우에 해당한다. 만약 Cygwin을 함께 사용하시는 분이라면 배치파일을 만들어 사용하는 방법도 있다. (http://kldp.org/node/48962)

  • 제어판에서 시스템 폴더 클릭. window 7환경에서 개발한다면 검색창에서 시스템을 입력한뒤 시스템 환경 변수 편집을 선택한다.
  • 아래 표시된 방법대로 입력하면 되겠다. 최종적으로 MinGW 설치된 폴더에 Bin 폴더를 입력하는 것을 목표로 한다.


  • CMD창에서 gcc --version과 mingw32-make를 입력하고 다음과 같이 나오면 성공적으로 설치된것임

 이제 MinGW가 설치되었으므로 window 기반에서 C, C++등을 개발할 수 있는 환경이 만들어진 것이다. 다음에 나오는 Eclipse기반이 아니더라도 메모장에서 C코드를 짜고 GCC로 컴파일할 수 있다. 하지만 개발툴을 메모장을 사용할 수는 없는 노릇아닌가?


2. Eclipse Galileo 버전을 설치한다.
Eclipse는 기존에 설치했던 사람이 대부분일 것이다. 이 설명은 필요없는 내용일 수 있으나 그냥 적어본다.

일단 Eclipse는 아래 링크에서 자신의 개발하고자 하는 목적별(Java, C/C++, PHP등)로 다운로드 받아 설치할 수 있다.

http://www.eclipse.org/downloads/

필자는 Java기반에서 개발하는 일이 많으므로 Java EE Developers를 위한 Eclipse IDE를 설치했다. Windows 32bit 기반을 다운로드 받았다. 설치는 받은 압축파일을 원하는 곳에 압축만 풀어주는 것으로 완료가 된다. 본인은 E:\eclipse 에 설치했다. C 드라이브에 설치하지 않은 이유는 나중에 운영체제를 다시 설치하는 경우에 Eclipse를 보존하기 위함이다.

Eclipse를 처음 설치하는 사람이라면 반드시 JRE가 자신의 컴퓨터에 미리 설치가 되어 있어야 Eclipse 구동이 가능하다. 다음 링크에서 JRE나 JDK 최신버전을 설치하면 되겠다.

http://java.sun.com/javase/downloads/index.jsp

만약 C/C++기반인 Eclipse를 다운로드 받아 설치하면 다음에 "3. CDT 플러그인을 설치한다"를 넘겨도 된다.


3. CDT 플러그 인을 설치한다.

CDT는 C/C++ Development Tool이다. 기존에 이클립스 기반으로 개발하던 사람이라면 C/C++도 Visual Studio와 같은 툴을 활용하지 않고 개발하고자 하는 욕구(?)가 들지도 모르겠다. CDT를 Eclipse에 설치하면 C,C++ 개발이 가능한 환경이 된다.

CDT는 개발하는 툴이지 컴파일러가 아니다. CDT를 설치했더라도 각각 운영체제 기반에서 제공하는 C/C++ 컴파일러와 연결하는 작업은 필요하다. 여기서는 CDT를 Eclipse에 설치하는 방법만 소개한다.

Eclipse 메뉴에서 Help > Install New Software... 를 선택한다.

아래와 같은 화면이 나오면 Add 버튼을 누른다.


http://www.eclipse.org/cdt/downloads.php 에 가면 최신버전(현재 CDT 6.0.x)을 다운로드 받을 수 있는 링크가 소개되어 있다. Eclipse가 Galileo버전이므로 http://download.eclipse.org/tools/cdt/releases/galileo 를 URL로 삼아 플러그인을 설치하면 되겠다.

아래처럼 Add Site창에 Name과 Location을 입력한다. Name은 아무거나 입력하면 된다.


참고로 위처럼 등록하면 Windews > Preferences > Install/Update > Avaliable Software Sites에 아래처럼 등록되어 있다.


이제 Work with 란에 CDT를 입력해보면 금방 등록했던 CDT 정보를 선택할 수 있다. 그럼 아래처럼 나온다. CDT Main Features, CDT Optional Features 모두 Check하고 Next버튼을 누른다.


아래처럼 설치할 목록들이 나온다. Next버튼을 누른다.



아래와 같은 화면이 나오면 I accept the terms of the license agreements를 선택후 Finish 버튼을 누르면 설를 시작한다. 시작이 완료된 다음에는 Eclipse를 재구동한다.



Eclipse가 구동된 다음 C/C++ Perspective를 열어보자. Windows>Open Perspective>Other 를 선택한다. 아래처럼 창이 뜨면 C/C++를 선택한다. 
 

Perspective창에 아래처럼 추가 된것을 확인하자. 이제 Eclipse에서

File > New를 가도 C, C++ 프로젝트를 만들 수 있게 되었다.


4. 간단한 C/C++ 개발해보기

MinGW의 bin 폴더에 보면 gcc, g++등 각종 컴파일 도구가 있다. 이클립스의 CDT환경에서 C/C++로 개발할때는 MinGW의 컴파일 도구를 이용해 컴파일 한다는 사실을 기억하자.

다음과 같이 C++ 프로젝트를 만들어보자.
  • 이클립스 메뉴에서 File > New > C++ Project를 선택한다.
  • 다음 그림과 같이 프로젝트를 만들기 위한 창이 나오면 Project name을 넣고 Project type을 Executable > Hello World C++ Project를 선택한다음 Toolchains를 MinGW GCC로 선택하도록 한다. 이렇게 하면 자동으로 Hello World CPP가 만들어지면서 컴파일은 MinGW의 g++컴파일러를 이용해 컴파일 할 수 있도록 설정 되는 것이다. Next 버튼을 누른다.
  • 간단한 셋팅을 하는데 이 설정에서 중요한 것은 Source 파일은 src 폴더에 들어가게 된다는 것이다. Next 버튼을 누른다.


  • 다음 그림과 같이 Debug, Release 두 버전으로 프로그램 결과를 별 수 있도록 셋팅된다. Finish 버튼을 누른다.

 

이클립스의 Project > Build Automatically가 선택되어 있다면 위처럼 프로젝트를 만들게 되면 자동으로 MinGW의 g++을 찾아 컴파일을 실시하게 된다. 아래와 같은 메시지가 이클립스의 Console창에 나오면 제대로 실행된 것이다.


위 메시지 처럼 컴파일을 하지 못하고  아래처럼 Nothing to build for Test1 메시지가 뜬다면 이것은 수정된 내용이 없으므로 컴파일할 필요가 없다는 것을 의미한다. 대신 Ctrl+B 하면 강제로 컴파일한다.


이 프로젝트는 C++ 프로젝트이므로 gcc 컴파일러가 연결되어야 한다. 만약 컴파일에 실패한다면 Mingw/bin에 gcc.exe를 확인해보자. 만약에 없다면 위에 Mingw 설치하기를 다시 한번 따라해보길 바란다.

실행해보자. 아래처럼 만들어진 프로젝트의 src폴더를 열어 Test1.cpp를 선택한다. 그리고 빨간 박스로 표시된 아이콘을 클릭하거나 Ctrl+F11을 하면 실행할 수 있다.

Console창에 아래처럼 Hello World가 출력된것을 확인하자.

Build Automatically를 체크한 경우에는 코드를 수정하고 저장할 때마다 자동 Build처리된다. 하지만 수동으로도 할 수 있는데 Ctrl+B 또는 아래 그림처럼 망치툴을 클릭해서 Build할 수 있다.



개발시에는 Debug버전과 Release 버전이 있다는 것을 기억하자. 아래 보이는 도구는 현재 개발 환경이 Debug인지 Release인지 결정해 주는 도구이다. Debug는 개발용이고 Release는 외부로 배포할때 사용한다.


실제로 이 도구를 이용해 Debug에서 Release로 바꿔보자. 아래처럼 Release 폴더가 생겨나고 컴파일 결과가 이 폴더안에 만들어진다. 이때부터는 실행할때 Release폴더에 있는 exe파일이 실행되게 된다.


참고로 makefile을 기반으로한 개발을 하고 싶은 경우가 있을 수 있다. 이런 경우에는 C++ 프로젝트를 생성시에 아래 그림처럼 Makefile Project를 선택해서 생성해야한다.

하지만 실제로 이 상태로 프로젝트를 생성해 컴파일하면 Cannot run program "make": Launching failed 메시지가 console창에 보이면서 컴파일이 불가능하다. 이 메시지는 컴파일을 수행할 수 있는 make 명령어를 찾지 못한다는 것을 의미한다. Eclipse에서는 기본적으로 make가 지정되어 있지만 MinGW의 make 명령은 mingw32-make로 다르다. 그러므로 이 문제를 해결하기 위해 mingw32-make.exe를 복사해 같은 폴더에 make.exe로 이름을 바꿔주면 된다. (물론 찾아보면 다른 방법도 있지만 이 방법이 가장 빠르고 쉽다.)

makefile을 이용해 개발해보는 간단한 예제는 다음글을 참고한다.
http://kkamagui.springnote.com/pages/446531


5. 간단한 win32용 프로그램 개발하기
지금까지의 예제는 단순히 Console창에 결과가 나온다. MinGW는 Windows API를 이용한 개발도 지원하므로 앞서 설명한 같은 C++ 프로젝트로 만들고 아래 코드로 변경해도 컴파일이 가능하다.


#include <windows.h>

int WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
        MessageBox(NULL, "Hello in EclipseCDT", "EclipseCDT", MB_OK);
        return 0;
}


실행해 보면 아래와 같은 다이얼로그 창이 나온다.

경험자들에 따르면 Win32 로 개발하는 경우에는 한가지 중요한 C++ 링커 설정을 해야한다고 한다.

먼저 해당 Win32 프로젝트를 선택후(필자의 경우 Test3) 이클립스 메뉴에서 Project > Properies로 들어갑니다. 아래처럼 창이 뜨면 C/C++ Build > Setting으로 들어가 Tool Setting 탭을 선택합니다. 거기에 MinGW C++ Linker>Miscellaneous를 선택해 Linker flags에 -mwindows를 입력한다. 완료하면 Apply버튼이나 OK 버튼을 누르면 적용된다.

이 설정을 해야 군데 군데 "undefined reference"와 같은 에러를 발생시키지 않는다고 한다. 수정했는데도 에러를 띄우면 File->Save all 또는 이클립스 종료후 다시 시작하면 된다.

Windows API 학습을 위해 다음 링크를 참고한다.
http://www.winapi.co.kr/

6. 디버깅 환경 만들기
지금까지 개발환경으로 개발자체는 문제 없지만 디버깅은 할 수 없다. MinGW에 디버깅을 하기 위한 도구가 설치되어야 하는데 이를 가능하게 하는 것이 gdb이다.

먼저 gdb를 다운로드 받자.
http://downloads.sourceforge.net/project/mingw/GNU%20Source-Level%20Debugger/GDB-7.1/gdb-7.1-2-mingw32-bin.tar.gz

압축을 풀고 bin과 share 폴더를 그대로 설치된 MinGW 폴더에 복사해준다. 이것으로 MinGW의 C/C++ 디버깅 환경이 구축되었다.

이제 Eclipse에서 F11 누르거나 아래처럼 벌레모양의 아이콘을 눌러 Debugging을 할 수 있게 된다.


디버깅을 하게 되면 Debug Perspective창으로 바뀌면서 아래처럼 디버깅이 가능해진다.

참고로, 이클립스에서 디버깅 설정은 Run > Debug Configurations에서 할 수 있다. 좌측 리스트에서 C/C++ Application을 선택해 해당 exe를 선택한뒤 우측에서 Debugger 탭을 선택하면 Debugger를 선택할 수 있게 된다.

만약 gdb를 설치했음에도 불구하고 에러를 내면서 디버깅을 할 수 없는 경우 CMD창에서 다음과 같이 입력해보길 바란다.



만약 위처럼 메시지가 나오지 않고 libexpat-1.dll이 없다고 경고창이 나오면 현재 설치한 gdb버전이 libexpat-1.dll과 의존성이 있는 것이다. 이때는 libexpat-1.dll도 함께 다운로드 받아 MingGW/bin에 복사해줘야 한다.

libexpat-1.dll은 아래 링크에서 다운로드 받는다.
http://downloads.sourceforge.net/project/mingw/MinGW%20expat/expat-2.0.1-1/libexpat-2.0.1-1-mingw32-dll-1.tar.gz

다음 글을 참고한다.
http://forums.codeblocks.org/index.php/topic,11301.msg77273.html

7. 정리하며
프로젝트 중에 C와 MySQL을 연동하여 개발할 경우가 있어서 전체 환경설정을 정리한다는 마음으로 글을 적었다. 기존에 나와 있는 많은 글들이 너무 옛날 버전이라서 그런지 지금과 맞지 않는 부분도 있었다. 앞으로 MySQL과 연동하게 되면 관련 내용도 소개해 볼까 한다. 개발에 있어서 환경을 구축하고 적응하는게 반인 것 같다. ^^;


8. 참고글

이클립스(Eclipse) CDT 설치
(Eclipse Galileo) C/C++ Developments User Guide
CDT를 이용한 Windows C/C++ 개발 환경 만들기
Eclipse/CDT
Eclipse Project CDT (C/C++) Plugin Tutorial 1, 2By Brian Lee
이클립스(eclipse)를 이용해서 C/C++ 프로그래밍 환경설정
MinGW + Eclipse 를 이용한 Windows 개발 환경 구현

글쓴이 : 지돌스타(http://blog.jidolstar.com/677)

MS Windows에 Subversion을 설치할 일이 생겨서 설치했다. 이 글은 설치하는 방법 및 테스트를 위한 나만의 기록이다. 다른 방법도 얼마든지 있지만 모두 생략한다.

 

 

Subversion 서버 구축하기

 

1. 설치 목표

Http 프로토콜로 SVN 서비스 지원하도록 서버측 환경을 만드는 것을 목적으로 한다. svn 프로토콜이나 https로 하는 방법은 생략한다. 이 목표를 달성하기 위해 다음과 같은 프로그램을 다운로드 받아 설치해야 한다.

 

- Apache 웹서버 2.2.x

- Subversion 1.6.x

- TortoiseSVN 1.6.x

 

2. Apache 웹서버 설치 하기

http://httpd.apache.org/download.cgi 에서 Openssl을 지원하는 Win32 Binary 설치프로그램을 다운로드 받아 설치한다. 현 최신 버전인 apache_2.2.11-win32-x86-openssl-0.9.8i.msi를 받아 설치함

 

3. Subversion 서버 설치

http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=8100 에서 Apache 2.2 모듈을 포함하는 최신 SVN 바이너리 프로그램을 받는다. 현 최신 버전인 Setup-Subversion-1.6.3.msi를 받아 설치함

 

4. TortoiseSVN 설치 하기

이 프로그램은 클라이언트에서 주로 사용한다. 콘솔창에서 svnadmin create와 같은 명령어를 이용해 저장소 만드는 불편함을 없애기 위해 설치한다.

 

http://tortoisesvn.net/downloads 에 가서 다운로드 받아 설치한다. 현 최신 버전인 TortoiseSVN-1.6.3.16613-win32-svn-1.6.3.msi를 설치하면 된다.

 

5. SVN 저장소(Repository)를 생성한다.

저장소를 만들기 위해svnadmin create 명령어를 콘솔창에서 하는 방법도 있지만 방금 설치한 TortoiseSVN을 이용하면 쉽게 만들 수 있다. 먼저 아래 그림처럼 임의의 위치에 저장소 폴더를 만든다. 그리고 오른쪽 마우스 버튼을 눌러 컨텍스트 메뉴에서 TortoiseSVN을 선택한 뒤 Create repository here를 선택하면 된다. 그럼 The repository was successfully created 메시지가 뜨면서 저장소가 만들어진다. 테스트를 위해 저장소를 C:\svn 에 만들었다.

 

 

 

만들어진 저장소에 들어가보면 아래와 같은 내용이 만들어진 것을 확인할 수 있겠다.

 

conf 폴더 안에 있는 authz, passwd, svnserve.conf를 설정하여 이 저장소에 접근할 수 있는 권한 및 운영제반 사항을 설정할 수 있다. http 프로토콜로 서비스를 할 것이므로 authz 설정만 하면 된다. 즉, passwd와 svnserve.conf는 건드릴 필요가 없다. 이들은 svn프로토콜을 이용하면 반드시 설정해야한다.  설정 방법은 다음에 설명한다.

 

6. Apache 서버에 SVN 모듈 등록

 

Subversion의 설치 폴더(C:/Program Files/Subversion)에 bin폴더에 있는 mod_dav_svn.so와 mod_authz_svn.so 파일을 Apache의 modules 폴더에 복사한다.

 

그 다음 Apache 설치 폴더에 conf 폴더에 있는 http.conf를 아래와 같이 편집한다.

 

# 아파치 모듈 설정 부분에서 다음 부분의 주석을 푼다.
LoadModule dav_module modules/mod_dav.so
LoadModule dav_fs_module modules/mod_dav_fs.so

 

# 다음과 같은 SVN 모듈을 추가한다.
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so

 

7. Apache 가상 호스트 설정

 

가상호스트는 말그대로 가상적인 호스트를 의미한다. 앞으로 만들 SVN 저장소에 접근할 수 있도록 가상으로 정한 주소를 이용해 Apache에 설정하는 것을 의미한다.

 

여기서 Apache 2.2 버전을 설치했으므로 http.conf에 아래 부분 처럼 가상 호스트 관련 설정파일을 포함하도록 되어 있다. Include 앞에 #을 지워준다.

 

# Virtual hosts
Include conf/extra/httpd-vhosts.conf

Apache 설치폴더에서 conf/extra/httpd-vhosts.conf 를 열어서 다음과 같이 추가한다.

 

############################################
# Sample 테스트용 SVN 가상호스트
############################################
<VirtualHost *:80>
    <Location /svn/test>
        DAV svn
        SVNPath "C:\svn"
        #SVNParentPath "C:\svn"
        #SVNListParentPath On
        AuthType Basic
        AuthName "SVN Test Repository"
        AuthUserFile "c:\svn\htpasswd"
        AuthzSVNAccessFile "c:\svn\conf\authz" 
        Require valid-user
    </Location>
</VirtualHost>

위 설정은 http://주소/svn/test 로 접근하게 되면 C:\svn 을 SVN 저장소 위치로 찾게 해준다는 의미가 내포되어 있다.

 

AuthUserFile은 사용자 목록 위치이며 AuthzSVNAccessFile은 접근권한 파일을 의미한다. 주석 처리된 SVNParentPath는 각 프로젝트의 저장소 디렉토리가 아닌 그 바로 위의 디렉토리를 뜻한다. http://주소/svn/test로 접속하여 C:\svn 뿐 아니라 http://주소/svn/test/other 로 하면  C:\svn\other 로도 접근이 가능해지는 것이다.

 

Require valid-user는 인증된 사용자만 사용할 수 있다. 만약 체크아웃(check-out)은 아무나(Anonymous)가 할 수 있게 하고 커밋(commit)은 지정된 사용자만 할 수 있게 하려면 Require valid-user 대신 아래처럼 사용할 수 있다.

 

############################################
# Sample 테스트용 SVN 가상호스트
############################################
<VirtualHost *:80>
    <Location /svn/test>
        DAV svn
        SVNPath "C:\svn"
        #SVNParentPath "C:\svn"
        #SVNListParentPath On
        AuthType Basic
        AuthName "SVN Test Repository"
        AuthUserFile "c:\svn\htpasswd"
        AuthzSVNAccessFile "c:\svn\conf\authz"  
        <LimitExcept GET PROPFIND OPTIONS REPORT>
            Require valid-user
        </LimitExcept>
    </Location>
</VirtualHost>

 

8. Apache 사용자 인증 파일 생성

 

위에서 AuthUserFile의 경로를 설정할 것을 보았을 것이다. 이제 그 파일을 만들자.

 

http 프로토콜을 사용하여 SVN에 접근하므로 Apache의 htpasswd를 이용해 사용자 인증을 한다.

 

htpasswd 파일을 생성은 다음과 같이 한다.

{아파치 설치 폴더}\bin\htpasswd -c htpasswd 사용자계정

 

htpasswd 파일에 사용자 추가 명령

{아파치 설치 폴더}\bin\htpasswd htpasswd 사용자계정

 

위와 같은 방법으로 사용자 계정을 추가해보자.

C:\svn\conf>C:\Apache\Apache2.2\bin\htpasswd -c htpasswd jidolstar
Automatically using MD5 format.
New password: ********
Re-type new password: ********
Adding password for user user1

만들어진 c:\svn\htpasswd 파일을 보면 아래와 같이 추가 되어 있는 것을 확인할 수 있을 것이다.

 

jidolstar:$apr1$IswHvHiJ$rBcOZc8bs59IUJIC/hWgO.

 

중요한 것은 -c는 한번만 사용한다. 안그러면 이전 내용이 지워져 버린다.

 

9. Subversion 접근 권한 파일 설정

c:\svn\conf 폴더에 보면 authz, passwd, svnserve.conf 파일이 있다. svn 프로토콜로 접근하지 않을 것이므로 authz만 수정하면 된다.

 

다음은 authz 파일 접근 권한 설정의 예이다.

 

[groups]
group1 = user1,user2
group2 = user3,user4,user5

 

[/]
* = r

 

[/project1]
@group1 = rw
@group2 = r

 

[/project2]
@group1 = rw
@group2 = r

무엇을 의미하는가? 2개의 그룹이 있다. 그룹 group1에는 사용자 1,2가 있고 그룹  group2에는 사용자 3,4,5가 있다. 이들은 http://주소/svn/test로 접근했을때 모두 read할 수 있으나 write권한은 없다. http://주소/svn/test/project1 에는 group1은 read/write를 다할 수 있으나 group2는 read만 할 수 있다. project2는 그 반대이다. 이런 식으로 svn에 접근 권한을 설정할 수 있는 것이다. 앞서 설명했지만 이 파일은 앞서 수정했던 가상 호스트 설정에서 Apache가 실행될 때 로드되어 진다.

 

본인은 앞서 jidolstar 아파치 계정을 하나 만들었으므로 다음과 같이 authz를 설정했다.

 

[groups]
group1 = jidolstar

 

[/]
* = rw

 

10. 웹브라우저에서 SVN 서버에 http로 접근하기

아래와 같은 윈도우 맨 우측아래 트레이 아이콘을 두번 클릭해 창이 뜨면 아파치를 구동하자. 미리 구동되어 있는 경우에는 새로운 설정을 로드해야하므로 stop했다가 다시 start한다. 위 설정을 잘 했다면 에러없이 구동이 될 것이다.

 

먼저 아파치가 잘 구동되는지 확인한다. 아래와 같이 창이 뜨면 정상이다. (참고로 이 페이지는 {아파치 설치경로}\htdocs에 있는 index.html 파일이다.)

 

 

이제 http://localhost/svn/test 로 접속해보자. 아래와 같은 인증창이 나오면 등록했던 사용자계정정보를 넣자.

 

인증이 완료되면 아래와 같은 화면이 나왔으면 성공이다.

 

이것으로 SVN 서버 구축을 모두 완료했더 테스트 까지 마쳤다.

 

여기서는 c:\svn에 저장소를 만들었지만 사람마다 다른 곳에 저장소를 만들고 싶을 것이다. 원하는 곳에 저장소를 만들어 테스트를 꼭 해보길 바란다. 가상호스트는 중복으로 만들 수 있으니 여러개의 저장소를 만드는 것도 가능하다.

 

 

Subversion 서버에 HTTP로 접속해 사용해 보기

 

SVN 서버는 모두 구축이 되었다. 이제 개인 소스 관리를 위한 테스트를 해보자. 만약 다른 컴퓨터에서 테스트 해보고 싶다면 그 컴퓨터에 TortoiseSVN 을 설치하길 바란다.

 

1. 체크아웃(check-out)

먼저 SVN 서버에 c:\svn에 있는 자료를 첫번째로 가져오는 일을 해야할 것이다. 이때 하는 일이 check-out이다. 바탕화면에 TestSVN 폴더를 만들어보자. 그런다음 아래 그림처럼 SVN Checkout를 실시한다.

 

그럼 아래처럼 Checkout 창이 뜬다. 우리는 http://localhost/svn/test 로 접속해서 이전에 만들어진 저장소 C:\svn에 있는 내용을 읽어올 수 있다고 했다. 아래처럼 URL of repository에 이 URL을 적고 OK버튼을 누른다.

 

 

브라우져에서 접속할때와 마찬가지로 인정절차가 필요하다. username과 password를 넣자. 다음부터 이 절차를 생략하고 싶다면 Save authentication을 check한다.

 

 

아래와 같이 Completed 가 나오면 완료가 된것이다. 지금은 아무 내용이 없다. ^^;

 

바탕화면에 만든 TestSVN 폴더에 보면 .svn 이 생성된다. .svn은 TestSVN이 접속한 저장소에 대한 정보를 가지고 있다. 앞에 점(.)이 있기 때문에 폴더옵션에서 숨김 파일 및 폴더 표시를 해주어야 보인다. 그렇지 않으면 TestSVN은 빈폴더처럼 보일것이다.

 

 

2. 커밋(commit)

체크아웃 한 소스를 수정, 파일 추가, 삭제 등을 한 뒤 저장소에 저장하여 갱신 하는 것이다. 커밋을 하면 전체 리비전이 1 증가하게 된다.

 

해보자. 앞에서 체크아웃한 TestSVN 폴더 내로 가서 tags, trunk, branches 폴더를 만든다. 또 각각의 폴더안에 tags.txt, trunk.txt, branches.txt를 만들어 본다. ? 표가 표시된 것은 이 파일이 어떤 상태인지 모른다는 것을 의미한다.

 

모두 선택한 뒤 아래처럼 TortoiseSVN > Add를 선택해서 커밋할 목록으로 등록한다.

 

이제 ?표에서 벗어나 + 표시로 바뀌었다. SVN 서버로 커밋할 준비가 된 것이다.

 

모두 선택한 뒤 아래 화면처럼 SVN Commit을 실시한다.

 

아래 그림의 창이 뜨면 SVN으로 전송할 목록이 선택되어 있고 Message를 남길 수 있게 된다. 이런식으로 만들어진 자료를 SVN로 전송할 수 있다. 지금은 추가된 내역만 올라가지만 만약 폴더 이름이 바뀌거나 위치를 옮기거나 삭제 되었거나 했을때는 added가 아니라 deleted, moved 이런 표시가 붙을 것이다.

 

아래 화면처럼 추가했던 + 표시는 없어지고 체크표시가 되었다. 정상적으로 SVN 서버에 올라간 것이다.

 

trunk를 선택하고 오른쪽 마우스 키를 눌러 Tortoise > show log를 보자. 아래처럼 Revision 번호가 1로 되어 있는 것을 확인할 수 있으며 그에 대한 Message와 함께 Commit된 것들도 확인할 수 있다.

 

 

TestSVN/trunk/trunk.txt 파일을 열어 내용을 입력하고 저장해보자. 아래처럼 !(느낌표) 표시가 될 것이다.

 

이것을 커밋해보자. 기존에 있는 것을 수정했으므로 상태는 modified가 되어 있다. 커밋하면 이제 리비전(revision)은 1에서 2로 바뀌게 된다.

 

브라우저에서도 확인해보자. http://localhost/svn/test 로 접속해서 아래와 같은 화면을 확인하자. add, modify 두번 처리해서 커밋했으므로 Revision이 2이다.

 

 

3. 업데이트(Update)

 

체크아웃을 해서 소스를 가져 왔더라도 다른 사람이 커밋(추가, 삭제, 이동, 변경등)을 하였다면 소스가 달라졌을 것이다. 이럴 경우 업데이트를 하여 저장소에 있는 최신 버전의 소스를 가져온다. 물론 바뀐 부분만 가져온다.

 

다른 사람이 변경한 내용이 있더라도 내가 변경한 내용이 따로 있다면 그 파일이나 폴더는 무시한다. 그러므로 항상 작업을 할 때는 먼저 업데이트를 하고 난다음 수정, 추가, 삭제, 이동의 작업을 완료후에 커밋해야 다른 사람 변경내용과 충돌이 일어나지 않는다. SVN은 겹쳐서 작업해서 서로 소스가 달라지는 경우 충돌보고를 해주기 때문에 안심하고 작업할 수 있다.

 

업데이트 방법은 매우 단순하다. 아래 그림처럼 SVN Update만 해주면 된다.

 


정리하며

 

윈도우 환경에서 Subversion 서버를 구축하는 방법과 사용법을 간단히 논했다. 사실 아주 일부분만 설명되어 있는 것이고 실무에서는 더욱 다양하게 쓰이게 된다. 개발자의 경우 TortoiseSVN을 이용한다기 보다 Eclipse나 Flash Builder 등에 SVN 플러그인을 설치하여 개발 코드 관리를 하는 것처럼 사용한다.

 

외부 컴퓨터에서 접속이 가능하게 하려면 반드시 80번 포트의 방화벽을 해제해주어야 한다.

 

쉬운 내용이지만 한번 정도는 정리할 필요가 있다고 생각했다. 막상 작성해보니 내용이 너무 길다. ^^;

 

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/552)

구글 애플리케이션 엔진(Google App Engine)에 대해서 들어본 적 있는가? 구글 애플리케이션 엔진은 구글의 제공하는 통합개발환경이다. 플랫폼에 제한 받지 않고 구글의 자원을 적극적으로 활용해서 웹애플리케이션을 만들 수 있도록 한다.

 

 

구글 애플리케이션 엔진을 이용하여 개발한 Ajax API Playground라고 명명된 애플리케이션이 내 눈을 사로잡았다. 이것은 구글에서 제공하는 Open API를 사용하는 예제를 총 망라해서 보여주는 프로그램이다. 한눈에 Google Open API 사용법을 볼 수 있기 때문에 개발자들에게 매우 도움이 된다. 현재 Ajax API Playground에서는 Lanaguage, Blogger, Calendar, Earch, Feeds, Friend Connect, Maps, Libraries, Search, Visualization, YouTube등의 Open API를 사용하는 예제가 있다. 매우 훌륭한 점은 Edit HTML 기능을 통해 직접 코딩도 해볼 수 있다는 점이다. Open API를 사용하려면 해당 라이브러리를 다운받고 이것저것 환경설정하고 Key도 발급받아야하는데 이런 과정이 전혀 없이 테스트해볼 수 있으니 실로 놀랍다.

 

이미 오래전에 나왔던 것인데 이제서야 발견했다. RSS 구독하면서도 왜 이걸 못봤는지… ㅡㅡ;; 정보에 좀 민감해야하겠다는 생각이 든다.

 

이런 비슷한 것으로 ActionScript 3.0 코드를 웹에서 직접 코딩하고 하나의 포트폴리오로 제작할 수 있는 Wonderfl 서비스가 있다.

 

 

 

Flex에서는 Tour de Flex라는 흥미로운 서비스가 있는데 Flex에서 제공하는 컴포넌트를 사용하는 예제가 한페이지 안에 담겨있다. 하지만 Ajax API Playground나 Wonderfl 처럼 직접 코딩해보지는 못한다.

 

 

오랜만에 너무 놀랍고 흥미로운 기술을 보게 되어 흥분된다.

 

 

관련글

 

[Intel&AMD] MacOSX Leopard Install Guide

 

Min의 허접한 가이드 2탄 입니다.


Intel&AMD 입니다. (사실 별다른건 없어요...)


 

Intel&AMD MacOSX Leopard Install Guide

 

출처 : x86osx.com

 

Mac 세계에 빠져보자. ^^;



FDT를 이용해 순수 ActionScript 3.0 코딩의 세계로 빠져보기 위해 설치했다. Flex 프레임워크가 너무 무거워 ActionScript 3.0 코드로 주로 작성하게 될터인데 Flex Builder는 사용하기에 너무 부족한 면이 있는 것 같다. 그렇다고 해본적도 없는 Flash를 할 수 있는 것도 아니고… 그래서 선택한 것이 FDT이다.

 

http://fdt.powerflasher.com/

 

위 사이트에 가면 FDT를 다운로드 받을 수있다. 일단 30일 체험판이 있으니 사용해보고 정말 쓸만하다면 구입도 고려해볼 예정이다.

 

설치하고 실행하자마자  JVM terminated 에러가 떳다

 

 

 

이런 경우에는 설치 디렉토리내에 있는 자신의 컴퓨터 사양에 맞게 FDT.ini를 약간 수정하면 된다.

 

-vmargs
-Xmx512m
-XX:MaxPermSize=128m

 

잘 실행 된다. ^^

 

외관이 완전 이클립스다. Flex Builder 처럼 Eclipse를 가지고 만들었으니 당연하겠다.

 

 

 

Preferences를 살펴봤다. FDT에도 Flex SDK를 그대로 사용하는 것을 확인할 수 있었다. 더불어 Flex Builder에서는 못하는 AS2 코딩도 가능해진다.

 

 

FDT의 Code Style에서 Formatter를 보니 Braces({,})사용하는 방식이 맘에 안든다. 내가 사용하는 방식으로 바꿔야겠다. 아래 그림에서 Braces 탭을 선택해서 수정하면 되겠다.

 

 

 

FDT의 Core Libraries를 보니깐 여기에서 라이브러리를 등록할 수 있다는 것을 확인할 수 있었다.

 

 

새로운 Flash 프로젝트를 만들어보겠다.

 

 

 

Flash Player 9이하 환경에서  ActionScript 3.0으로 개발할 것이므로 Flex_3_SDK_0_Pure를 선택하면 되겠다.

 

역시 순수 ActionScript 3.0으로 개발할때 필요한 playerglobal.swc만 라이브러리로 등록된다.

 

 

 

직접 해보니깐 Flex Builder 3.0에서 보지 못했던 맘에 드는 구석이 많은 것 같다. 뭐 이미 FDT의 장점에 대해서는 구구절절 말하기 귀찮긴 하다. 사실 이제 처음 사용한거라 ㅎㅎ

 

 

FDT는 유료의 압박이 있는데 무료인 FlashDevelop 프로그램에 대해서도 관심을 가져볼려고 한다. 물론 FDT보다는 못하겠지만 많은 개발자가 즐겨 사용하는 것으로 알고 있기 때문에 관심이 간다.

 

선택의 폭이 넓어 좋다. ^^

 

 

 

 

WordPress는 매우 많은 플러그인(plug-in)이 존재한다. 더불어 다양한 테마(themes)까지… TextCube와 Tistory와는 비교가 안될 정도로 많은 플러그인과 테마 때문에 요즘은 블로그 꾸미는게 즐겁기 만하다.

WordPress의 플러그인 중에 매우 유용한 플러그인이 있어서 소개하겠다.

 

플러그인 이름은 “Ozh’ Admin Drop Down Menu”이다. 이 플러그인은 WordPress 2.7+ 이상 버전에서 제대로 동작한다. 이 플러그인은 아래 화면처럼 기본 관리자 메뉴를 위 화면처럼 깔끔하고 사용하기 편한 Drop&Down 메뉴로 변경해준다. 사실 TextCube를 사용하다가 WordPress를 쓸 때 불편했던 점 중에 하나가 관리자 메뉴였다. 그러한 불편함을 말끔하게 없애주는 플러그인이라 할 수 있겠다.

 

Ozh’ Admin Drop Down Menu 플러그인 설치방법

 

WordPress는 플러그인을 매우 쉽게 설치할 수 있다. 관리자 페이지에서 직접 설치가 가능한데 아래와 같은 방법으로 설치를 따라해보면 다른 플러그인도 원하는데로 설치해서 사용할 수 있겠다.

 

1. 소개한 Drop&Down Menu 플러그인을 설치하기 위해 관리자 메뉴에서 플러그인->새로추가 로 들어간다.

2. 아래화면과 같이 플러그인 설치의 검색에서 “drop down menu”으로 검색한다.


3. 검색해보면 Ozh’ Admin Drop Down Menu을 발견할 수 있을 것이다. 이것을 선택해보자.


4. 아래와 같은 창이 뜨면 “지금 설치하기” 버튼을 눌러 설치를 시작한다.

 

5. FTP를 통해 해당 플러그인을 다운로드받아 설치하도록 한다. 자신의 FTP 계정 ID와 Password를 넣고 “처리하기”버튼을 누른다.

 

6. 설치가 완료되면 아래와 같은 화면이 나온다. “작업 : 이 플러그인을 활성화”를 눌러서 플러그인을 실행시켜보자. 만약 이런 화면이 나오지 않는다면 FTP 계정 정보를 잘못 입력했을 가능성이 크다.

 

7. 설치 및 실행을 모두 완료했다. 아래 화면은 실행한 화면이다. 

 

8. 세부적인 설정을 위해 메뉴에서 설정->Admin Menu로 들어가보자.

 

9. 다음과 같이 세부적 설정도 할 수 있다.

 

정리하며

다양한 플러그인과 테마가 있는 WordPress의 매력에 같이 빠져보지 않으실래요? ^^;

워드프레스(WordPress)를 설치하면 기본 접속 URL은 “http://localhost/?p=번호”가 된다. 하지만 이러한 형태는 웬지 깔끔해 보이지 않는다. 그래서 워드프레스 설정에는 아래 그림처럼 여러가지 형태로 접속 URL을 변경할 수 있도록 하고 있다.

 

 

위와 같이 설정하면 WordPress 설치 폴더에 .htaccess가 생성된다. .htaccess를 확인해보면 아래와 같은 코드가 생성된 것을 확인할 수 있을 것이다. 참고로 본인의 블로그 루트는 “/blog/”이다. 대부분의 사람들은 “/” 이 될 것이다.

<IFMODULE mod_rewrite.c>  
RewriteEngine On  
RewriteBase /blog/
RewriteCond %{REQUEST_FILENAME} !-f  
RewriteCond %{REQUEST_FILENAME} !-d  
RewriteRule . /blog/index.php [L]  
</IFMODULE>

위 코드는 Rewrite엔진을 On하고 Rewrite기본경로를 /blog/로 하며, 파일이름이 기존에 있는 파일과 디렉토리가 아니라면 /blog/index.php로 rewrite 하겠다는 의미와 같다. 즉, “/archives/번호” 로 접속하면 기존에 있는 파일과 디렉토리가 아니므로 접속한 URL변경없이 “/blog/index.php”를 실행하게 된다.

 

이처럼 “?p=번호” 대신 “/archives/번호” 형태로 접속을 할 수 있도록 설정했지만 제대로 실행이 안된다면  Apache 설정에 .htaccess를 사용할 수 있도록 하고 mod_rewrite가 동작하도록 해야한다.

 

Apache 설정은 httpd.conf에서 할 수 있다.
이 파일의 위치는 설치한 서버마다 다를 것이며 개인서버를 운영하는 사람은 root로 접속해서 사용이 가능하며 호스팅을 받는 사람이라면 php_info()함수로 mod_rewrite 모듈이 동작하는지 여부를 확인하여 동작이 안된다면 사용할 수 있도록 호스팅 업체에 요청해야한다.

 

httpd.conf에서 2가지 영역을 수정하면 되겠다.

  1. LoadModule rewrite_module_modules/mod_rewrite.so 의 주석을 제거한다.
  2. 다음 부분에서 AllowOverride를 All로 설정함
    <Directory “home/httpd/html”>
    Options Indexes FollowSymLinks
    AllowOverride All
    Order allow, deny
    All from all
    </Directory>

이와 같이 설정하고 Apache를 재실행하면 새로운 접속 URL인 “/archives/번호” 형태로 접근할 수 있을 것이다.

 

mod_rewrite를 이용하면 이렇게 깔끔하게 URL을 정리할 수 있으니 활용가치가 높다. 따로 학습을 해두면 분명히 유용하게 사용할 수 있을 것이다.

+ Recent posts