Showing posts with label 튜토리얼. Show all posts
Showing posts with label 튜토리얼. Show all posts

Thursday, 20 October 2011

[DEV] 아이폰 블루투스 프로그래밍 iPhone Bluetooth Programming


이번 호에서는 iOS에서 사용되는 블루투스의 사양과 특징 및 블루투스를 활용한 데이터 통신 방법에 대해 알아보고 실제로 프로그램을 작성해보자.

<화면 1> 블루투스 로고
블루투스는 1994년에 에릭슨이라는 회사에서 최초로 발표한 근거리 무선통신 규약이다. 다른 표준 규약들이 발전하는 방식과 유사하게 블루투스 SIG(Special Interest Group)가 정식으로 발족했으며, 1999년 5월 20일에 공식적으로 배포됐다(IEEE 802.15.1). 블루투스 SIG에는 소니에릭슨, IBM, 노키아, 도시바 등의 전자제품 회사들이 참여하고 있다.
블루투스는 2.45㎓의 대역폭을 사용해 통신을 하고 있으며, 현재 3.0 버전까지 나와 있다. 각 버전이 올라갈 때마다 속도의 향상과 데이터 통신을 할 수 있게 되는 등 기능의 변화가 있었다. 1.0 버전은 각각의 회사별로 호환성에 문제가 있는 경우가 많이 있었으며, 속도를 논의할 단계가 아니었다. 2002년에 정식으로 배포된 1.1 버전부터 723.1kbps에 달하는 속도를 내게 됐고, 버전 2.0부터는 2.1Mbps라는 속도를 낼 수 있어서 음성이나 간단한 command 뿐 아니라 각 장치가 서로 데이터를 주고받을 수 있는 수준이 됐다.
iOS에서 사용되는 블루투스 프로파일
블루투스는 장치에 따라 지원하는 프로파일들이 따로 있다. 이 프로파일들에 따라 지원하는 방식도 달라진다. <표 1>은 iOS4에서 지원되는 블루투스의 프로파일이다.

<표 1> iOS4에서 지원되는 블루투스 프로파일
각각의 프로파일은 다음과 같은 의미를 갖는다.
HFP(Hands-Free Profile)

일반적으로 핸즈프리를 지원하는 기기에서 사용되는 프로파일이다. 오직 음성만을 지원하며, 주로 자동차에 사용되는 블루투스 기기에서 사용된다. 현재 최신 버전은 1.5이며, iOS4에서 지원하고 있다.
PBAP(Phone Book Access Profile)

전화번호부에 접근하기 위한 프로파일이다. 아이폰에서 <홈> 버튼을 계속 누르고 있으면 시작되는 ‘음성명령’ 애플리케이션 등이 PBAP를 지원하는 프로그램이다.

<화면 2> 아이폰의 음성명령 애플리케이션
A2DP(Advanced Audio Distribution Profile)

헤드셋이나 스피커로 음악을 전송할 때 사용하는 프로파일이다. 아이폰3G와 iOS3 버전부터 이 방식이 지원되기 시작했다. 아이폰으로 음악을 들으려면 직접 화면을 보고 조절할 수 있다.
AVRCP(Audio/Video Remote Control Profile)

오디오 뿐만 아니라 비디오까지 컨트롤 할 수 있다. 아이폰3G, iOS4.1 버전부터 지원된다.
PAN(Personal Area Network Profile)

piconet이라고 불리기도 하는데, 최대 8개의 장치와 마스터-슬레이브로 연결될 수 있는 연결방식이다(IEEE 802.15에 정의돼 있다). 일반적으로 지원영역은 10m 정도이며, 기기간에 연결해 최대 100m까지 사용할 수 있다.
데이터 교환을 위해 필수적으로 지원돼야 하며, 초기 아이폰을 제외하고는 모두 지원된다.
HID(Human Interface Device Profile)

각종 키보드, 마우스, 게이밍 디바이스, 태블릿 등을 지원하는 프로파일이다. 아이폰3GS 이상, iOS4 이상부터 지원되기 시작했다. 이 프로파일을 이용하면 블루투스 키보드를 아이폰이나 아이패드와 연결할 수 있다.

<화면 3> 일반적인 HID  연결 방식(출처 : www.blutooth.com)
블루투스에 대해 좀 더 자세히 알고싶다면 ‘http://ko.wiki pedia.org/wiki/블루투스’를 참고하길 바란다.
블루투스 프로그램 만들기
이제 블루투스의 특징을 이용해 Xcode 상에서 블루투스 프로그램을 만드는 방법을 알아보자.
블루투스의 특징을 활용한 아이폰 프로그램 중에 ‘Bump’라는 앱이 있다. 이 앱은 아이폰끼리 서로 부딪힐 때 지정된 정보들을(명함, 연락처, 사진, 캘린더) 진동과 함께 전달한다. 우리도 이렇게 아이폰끼리 서로 부딪혔을 때 정보를 교환할 수 있는 프로그램을 만들어보자.
GameKit에 대해
아이폰으로 블루투스 프로그램을 하려면 여러 가지 프레임워크 중에서 GameKit이라는 것을 사용해야 한다. 이름에 Game Kit라는 단어가 들어가 있어서 게임과 관련된 것이라고 생각할 수 있는데, 사실상 네트워크와 인터넷과 관련된 여러 가지 기능들을 모아둔 프레임워크라고 생각하면 된다.

<화면 4> GameKit
GameKit는 Game Center, Game Voice controller와 Peer-to-peer Connectivity 등을 담당하는 API를 제공해주는데, P2P 연결에 블루투스를 사용하게 된다.
GKSession 클래스가 ad-hoc 블루투스 또는 로컬 무선 네트워크를 관리해준다. 블루투스를 위해 GKPeerPickerController 객체를 사용해 다른 장치와의 연결 등을 담당한다.
세션의 연결
블루투스 장치들간의 연결은 <그림 1>과 같은 형태로 이뤄진다. 장치들은 Peer ID라는 정보를 갖고 있어 상대방과 연결하게 된다. 연결시에는 GKSession이라는 클래스를 사용한다.

<그림 1> 블루투스 장치들간의 연결
다른 블루투스 장치 탐색
장치를 서로 찾을 경우에도 session mode라는 초기화 방법을 이용한다. 만들게 될 프로그램이 서버로 동작할 수도 있고, 클라이언트로 동작할 수도 있다. 서로를 찾게 하는 동작은 동시에 발생시킬 수도 있고 차례대로 발생시킬 수도 있다. 서버 역할을 하게 되는 앱은 세션의 이름을 string이나 sessionID 형태로 다른 장치에게 알릴 수 있다. 이 때 클라이언트로 동작할 장치는 서버의 sessionID를 찾아서 페어링한다.

<그림 2> 다른 장치를 찾을 경우의 동작
GameKit을 사용하면 각각의 장치를 16대까지 지원할 수 있지만 최소한으로 줄이는 것이 탐색시간을 줄이는 방법이다.
내부적으로 장치를 서버로 동작시키려면 GKSessionMode Server나 GKSessionModePeer의 initWithSessionID: displayName:sessionMode:를 사용한다. availabe 속성을 YES/NO로 설정해 사용할 수 있다.
클라이언트가 연결하겠다는 신호를 보내면 session:did ReceiveConnectionRequestFromPeer: 메소드를 사용해 peerID를 받아 들이고 화면에 표시한다.

<화면 5> 장치 #01(좌)과 장치 #02
오류가 발생되면 acceptConnectionFromPeer:error: 메소드가 호출되고 denyConnectionFromPeer:가 호출된다. 성공적으로 호출되면 session:peer:didChangeState:가 호출될 것이다. 그 후에 diaplayNameForPeer:가 호출돼 아이폰 화면에 연결할 장치의 이름이 표시된다. 블루투스에서는 다른 장치로 연결할 때 연결을 허락받는 절차가 있는데, connectToPeer: withTimeout:이라는 메소드를 사용해 대기시간을 갖는다.
데이터의 교환
연결된 블루투스 장치간에는 데이터 교환 등을 할 수 있다. 데이터의 형식은 사용자가 정의한 어떠한 형태로든 가능하다. 이 때 서로 데이터를 교환하는 중에 오류가 발생하면 sendData ToAllPeers:withDataMode:error:라는 방식으로 주변의 모든 장치로 데이터를 보냈을 때의 에러를 검출할 수 있다.
데이터를 전송할 때 1000Byte 미만으로 보내는 것을 권장한다. 전달할 수 있는 가장 큰 용량의 데이터 사이즈는 87KB로 정의돼있다.
일단 전달된 데이터는 receiveData:fromPeer:inSession: context: 메소드를 사용해 주고 받는다.
연결끊기
작성한 앱이 연결을 끓을 준비가 됐다면 disconnectFrom AllPeers 메소드를 호출한다. disconnectTimeout 메소드를 사용해 끊어진다는 신호를 얼마나 기다릴 것인지 설정할 수도 있다. 연결이 완전히 끊어지면 session:peer:didChangeState: 메소드를 호출해 상태를 처리할 수 있다.
실제 프로그래밍 
이제 아이폰 상에서 동작하는 실제 프로그램을 만들어보자. 프로젝트의 이름은 BT로 한다.
1) View-based Application 템플릿으로 프로젝트를 생성한다(File | New Project...). 프로젝트의 이름은 srBluetoot로 한다.
2) Frameworks tree에서 마우스 오른쪽 버튼을 클릭해 Game Kit.framework를 추가한다.

<화면 6> GameKit.framework의 추가
3) 인터페이스 빌더를 사용해 화면을 <화면 7>과 같이 구성한다. 기능으로는 상대방 장치를 연결하도록 하는 <연결하기>, <연결끊기> 버튼과 메시지를 입력하는 TextField 박스, <메시지 전달하기> 버튼을 배치했다.

<화면 7> 인터페이스 빌더 UI  설계
4) 인터페이스 빌더에서 디자인한 버튼들을 코드로 연결한다. srBluetoothViewController.h 파일에 <리스트 1>과 같이 추가한다.
<리스트 1> GKSession과 UI 오브젝트의 선언
#import
#import
@interface srBluetoothViewController : UIViewController {
   GKSession *currentSession;
   IBOutlet UITextField *txtMessage;
   IBOutlet UIButton *sendMessage;
   IBOutlet UIButton *connect;
   IBOutlet UIButton *disconnect;
}
@property (nonatomic, retain) GKSession *currentSession;
@property (nonatomic, retain) UITextField *txtMessage;
@property (nonatomic, retain) UIButton *sendMessage;
@property (nonatomic, retain) UIButton *connect;
@property (nonatomic, retain) UIButton *disconnect;
-(IBAction) btnSend:(id) sender;
-(IBAction) btnConnect:(id) sender;
-(IBAction) btnDisconnect:(id) sender;
@end
먼저 #import 를 사용해 framework 헤더를 포함한다. 또한 GKSession 클래스의 포인터 객체로 currentSession를 생성한다. 그 후 각각의 UI 버튼과 TextField를 만든다. 각각의 오브젝트에는 retain 방식으로 메모리를 관리하도록 하는 @property 코드를 꼭 넣어줘야 한다. 그리고 버튼을 눌렀을 경우 메시지를 보내는 동작을 위해 btnSend: 함수를 만들었고, 연결을 위해 btnConnect:, 함수의 연결을 끊기 위해 btnDisconnect: 함수를 작성한다.
5) 인터페이스 빌더에서 각각의 오브젝트들과 해당 함수 등을 <화면 8>과 같이 연결한다.

<화면 8> 인터페이스 빌더 연결
6) 아이폰의 진동모드는 AudioToolbox라는 프레임워크를 사용한다. 진동을 위해 다음과 같이 AudioToolbox.framework를 추가한다.
#import
이 헤더를 포함시키고 실제로 사용할 때는 다음과 같은 코드를 사용한다.

AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

<화면 9> 진동을 위한 Audion Toolbox.framework 연결
7) 이제 블루투스로 연결된 아이폰을 서로 부딪쳤을 경우 서로 모션 이벤트를 전달하는 코드를 작성해야 한다. <리스트 2>와 같이 srBluetoothViewController.m에 작성한다.
<리스트 2> 모션 이벤트 전달
// 서로 부딪쳤을 경우 최초로 이벤트를 인식하기 위한 부분
- (void)viewDidAppear:(BOOL)animated {
   [super viewDidAppear:animated];
   [self becomeFirstResponder];
}
// 반응을 인지할 것인지의 여부 결정
(BOOL)canBecomeFirstResponder {
   return YES;
}
// 모션이 시작됐을 경우 진동과 함께 textField에 쓰여진 메시지를 블루투스로 연결된 다른 장치로 보내는 역할
(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
   NSData* data;
   NSString *str = [NSString stringWithString:txtMessage.text];
   data = [str dataUsingEncoding: NSASCIIStringEncoding];      
   [self mySendDataToPeers:data];      
}

8) 이번에는 GameKit에 해당하는 Delegate를 추가한다. GKS essionDelegate, GKPeerPickerControllerDelegate srBluetoothAppDelegate.h에 추가한다.
<리스트 3> Delegate 선언 추가
#import
#import
@class srBluetoothViewController;
@interface srBluetoothAppDelegate : NSObject {
   UIWindow *window;
   srBluetoothViewController *viewController;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet srBluetoothViewController *viewController;
@end
9) 처음으로 앱이 시작될 때 <연결하기>, <연결끊기> 버튼의 상태를 표시하기 위해 <리스트 4>와 같이 입력한다. 각각의 버튼들은 상태에 따라 보이거나 사라진다.
<리스트 4> 로딩시 버튼 상태 설정
(void)viewDidLoad {
  [connect setHidden:NO];
  [disconnect setHidden:YES];
  [super viewDidLoad];
}
10) TextField 메시지 박스와 현재 연결중인 세션의 메모리를 정리하기 위해 <리스트 5>와 같이 작성한다.
<리스트 5> TextField와 연결중인 세션의 메모리 정리
(void)dealloc {
  [txtMessage release];
  [currentSession release];
  [super dealloc];
}
11) 각각의 오브젝트들에 대해 synthesize 코드를 작성한다. @synthesize는 @property로 지정한 각각의 오브젝트와 반드시 쌍으로 작성돼야 한다.
<리스트 6> @property에 대응하는 @synthesize  코드
srBluetoothViewController.h
@synthesize currentSession; //GKSession
@synthesize sendMessage;
@synthesize txtMessage;
@synthesize connect;
@synthesize disconnect;
12) 실제 동작시 사용될 각각의 함수들을 살펴보자. 이번 예제를 위해 다음과 같이 크게 세 가지의 IBAction 함수를 만들었다.
첫 번째로 GKPeerPickerController를 사용해 <연결하기> 버튼을 클릭했을 경우 동작하는 함수가 있다. 여기서 picker. delegate를 자신으로 설정한다. 그리고 연결시 picker의 연결 타입을 결정한다. GKPeerPickerConnectionTypeOnline 또는 GKPeerPickerConnectionTypeNearby로 설정한다. GKPeer...Online 타입은 인터넷 연결이 된 상태의 네트워크 상황에서의 타입을 지정한 것이며, GKPeer...Nearby 타입은 블루투스 타입으로 주변에 있는 장치와 연결하는 방식을 말한다. 또한 이 함수에서 <연결하기>, <연결끊기> 버튼을 보이게 하거나 그렇게 하지 않거나 하는 설정을 한다. 
마지막으로 picker를 보이도록 한다(picker show).
<리스트 7> <연결하기> 버튼 클릭시 동작
(IBAction) btnConnect:(id) sender {
  picker = [[GKPeerPickerController alloc] init];
  picker.delegate = self;
  picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;
  [connect setHidden:YES];
  [disconnect setHidden:NO];  
  [picker show];
}
두 번째로 <연결끊기> 버튼이 있다. 이 버튼을 사용해 블루투스 연결을 끊을 수 있다. 현재 GKSession의 포인터 객체로 연결된 세션의 모든 장치(Peer)들을 끊는다(disconnectFrom AllPeers 사용). 다음으로 현재 연결된 세션의 메모리를 반환한다. 그리고 각각의 버튼들을 보이거나 보이지 않게 설정한다. 이 동작을 완료하면 서로 연결된 블루투스 장치들의 연결이 종료된다.
<리스트 8> <연결끊기> 버튼 클릭시 동작
(IBAction) btnDisconnect:(id) sender {
   [self.currentSession disconnectFromAllPeers];
   [self.currentSession release];
   currentSession = nil;
 
   [connect setHidden:NO];
   [disconnect setHidden:YES];
}
세 번째로 <메시지 전달하기> 버튼을 클릭하거나 두 대의 아이폰을 서로 충돌시켰을 경우에 발생하는 메시지 전달을 위한 동작을 만든다(이번 예제에서는 편의상 한글로 인코딩하지 않고 스트링을 ASCII로 보낸다). 스트링 객체는 data라는 이름으로 만든다. 또한 다른 장치로 메시지를 보내기 위해 mySendData ToPeers라는 함수를 하나 더 만든다.
<리스트 9> <메시지 전달하기> 버튼 클릭시 동작
(IBAction) btnSend:(id) sender
{
   NSData* data;
   NSString *str = [NSString stringWithString:txtMessage.text];
   data = [str dataUsingEncoding: NSASCIIStringEncoding];      
   [self mySendDataToPeers:data];      
}
mySendDataToPeers 함수는 NSData로 정의된 데이터가 현재 세션에 존재한다면 그것을 다른 연결된 블루투스 장치로 보내는 역할을 담당한다. 속성으로는 모든 장치로 데이터를 전달하도록 하는 sendDataToAllPeers라는 것과 데이터를 보낼 때 모드를 지정하는 withDataMode라는 것이 있다. 보내는 값으로 GKSendDataReliable, GKSendDataUnreliable이 있는데, 빠른 속도가 필요한 전달 모드에서는 전자를 사용하고, 좀 느려도 정확한 전달을 원할 때는 후자를 사용한다.
<리스트 10> 다른 장치로 데이터 전송
(void) mySendDataToPeers:(NSData *) data
{
   if (currentSession)
   [self.currentSession sendDataToAllPeers:data
   withDataMode:GKSendDataReliable
   error:nil];  
}
13) 이제 버튼 함수 이외에 사용되는 함수들을 알아보자.
- peerPickerController 함수는 세션 객체를 생성하고 델리게이트를 설정하는 동작들을 담당한다. 또한 데이터를 전송받았을 때 해당 데이터를 핸들링 할 수 있는 데이터 리시브 핸들러를 설정한다. picker 델리게이트도 이 함수에서 지정한다.
<리스트 11> 세션 객체 생성과 델리게이트 생성
(void)peerPickerController:(GKPeerPickerController *)picker
   didConnectPeer:(NSString *)peerID
   toSession:(GKSession *) session {
   self.currentSession = session;
   session.delegate = self;
   [session setDataReceiveHandler:self withContext:nil];
   picker.delegate = nil
   [picker dismiss];
   [picker autorelease];
}

<화면 10> Cancel 이벤트 발생
TextField를 벗어날 경우 OSK(On Screen Keyboard)를 사라지도록 하는 코드
<화면 11>을 보면 OSK가 화면에서 나와 있다. TextField에서 벗어나거나 ‘다음문장’이라는 키보드의 키가 눌렸을 때는 자동으로 OSK가 사라지도록 해야 바람직하다. 다음과 같은 순서로 코드를 넣으면 이를 해결할 수 있다. 
1)  srBluetoothViewController.h 파일에 키보드 액션으로 사용될 함수를 정의한다. TextField를 벗어날 경우와 MainView의 어느 곳이든 선택됐을 경우 가상 키보드가 내려가도록 해야 한다.
- (IBAction)goAwaykeyBoard:(id)sender;
- (IBAction)tabBackgroud:(id)sender;
2) 액션 함수를 다음과 같이 작성한다. 키보드를 첫 번째로 반응하도록 하는 Responder 함수를 호출하도록 설정하며, MainView의 아무 곳이든 눌려지면 역시 resignFirstResponder가 동작하도록 한다.
(IBAction)goAwaykeyBoard:(id)sender
{
 [sender resignFirstResponder];
}
IBAction)tabBackgroud:(id)sender
{
  [txtMessage resignFirstResponder];
}
3)  IB에서 <화면 11>과 같이 TextField에는 ‘Did End On Exit’라는 이벤트를 연결하고 tabBackground 함수는 ‘Control Touch Down’이라는 이벤트를 이어준다. 또한 이를 위해 MainView의 클래스 이름을 UIView에서 UIControl로 변경한다.

<화면 11> 가상 키보드를 위한 함수 연결
- peerPickerControllerDidCancel : 기기간의 연결을 취소했을 경우 동작하는 함수다. picker 델리게이트를 초기화하며, 메모리를 반환(release)한다. 또한 MainView가 있는 버튼의 상태를 설정해준다.
<리스트 12> 기기간의 연결 취소시 동작
(void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker
{
   picker.delegate = nil
   [picker autorelease];
 
   [connect setHidden:NO];
   [disconnect setHidden:YES];
}
<리스트 13> 연결된 장치 사이의 상태 체크
(void)session:(GKSession *)session
   peer:(NSString *)peerID
didChangeState:(GKPeerConnectionState)state {
   switch (state)
   {
      case GKPeerStateConnected:
         NSLog(@"connected");
         break;
      case GKPeerStateDisconnected:
         NSLog(@"disconnected");
         [self.currentSession release];
         currentSession = nil;
         
         [connect setHidden:NO];
         [disconnect setHidden:YES];
         break;
   }
}

<화면 12> 메시지 입력 화면(좌)과 메시지 수신 화면(메시지를 입력하면 메시지가 전달된 후 수신화면을 볼 수 있다)

다음으로 연결된 장치 상호간의 연결 상태를 체크하는 session 함수에 대해 알아보자. session 함수는 GameKit의 GKPublic Protocols.h라는 헤더에 정의돼있다. 장치간에 연결이 끊어지거나 다시 붙거나 하면 이 함수가 호출된다. 인자로 session 이름과 스트링으로 표시될 수 있는 peer, 변경된 상태를 표시해주는 didChangeState를 갖고 있다.
빌드한 후에 <연결하기>로 먼저 블루투스 장치를 연결하고 메시지를 입력한다. 블루투스 장비를 서로 부딪히면 <화면 11>과 같이 메시지가 전달된다.
이번 호에서는 코드를 중심으로 블루투스를 사용하는 각각의 장치가 어떤 방식으로 연결되고 데이터를 교환하는지에 대해 알아봤다. 현재 블루투스는 고속으로 데이터를 전송할 수 있는 프로파일 등이 준비되고 있으며 표준 역시 업그레이드 된 사양으로 발전하고 있다.
참고자료
1. iPadProgrammingGuide.pdf(http://developer.apple.com/library/ios/ documentation/General/Conceptual/iPadProgrammingGuide/iPadProgrammingGuide.pdf)
2. iPad Human Interface Guidelines(http://developer.apple.com/library/ios/ documentation/General/Conceptual/iPadHIG/iPadHIG.pdf)
3. Game Kit Programming Guide(http://developer.apple.com/library/ios/ #documentation/NetworkingInternet/Conceptual/GameKit_Guide/Introduction/Introduction.html)

기사 원문 보기

Wednesday, 19 October 2011

[안드로이드]마방진으로 배우는 자바 핵심 기술


프로그래머라면 누구나 한 번쯤은 안드로이드 프로그래밍을 생각해 보거나 또 직접 수행해 봤을 것이다. 특히 국내의 경우 자바 개발자가 많아 특히, JEE 개발을 많이 해본 사람들을 중심으로 안드로이드 프로그래밍을 선택하는 것을 많이 봤다. 또 대학생이나 자바를 전혀 배우지 않은 일반 사람들도 안드로이드 앱을 개발하려는 모습을 심심치 않게 볼 수 있다.

필자는 자신이 구상하고 있는 안드로이드 앱이 탄탄하게 구성되려면, 반드시 자바 핵심 기술에 대한 이해가 필수적이라고 강조한다. 따라서 이번 시간에는 알고리즘과 디자인 패턴을 적용한 자바 핵심 요소들로 기본적인 프로그래밍을 완성시켜 보고, 다음 시간에는 안드로이드 프로그래밍의 핵심이라고 할 수 있는 UI를 핸들링하는 방법을 살펴보겠다.

이어 웹앱을 이용해 커뮤니티 연동 앱을 한번 만들어보는 시간을 갖겠다. 만일 안드로이드 OS 셋팅과 에뮬레이터 작동에 대해 알고 있다면, 좀 더 빠른 시간 안에 안드로이드 앱을 구현할 수 있게 될 것이다.
마방진으로 보는 자바 핵심 기술프로그래밍의 기본은 알고리즘이다. 알고리즘을 쉽게 설명하기 위해 필자는 그림을 먼저 제시하고, 해당 그림의 소스 코드를 설명하는 식으로 진행하겠다.

<그림 1>은 마방진으로 자바의 핵심 기술을 표현한 것이다. 
<그림 1> 마방진으로 자바 핵실 기술 익히기
우선 마방진이란 무엇인지 알아보자. 마방진이란 기원전부터 전쟁에서 사용하던 진법 중 하나로, 동일한 인원으로 상대방이 어떤 방향에서 공격해도 방어할 수 있다는 특징을 가지고 있다.
수학에서는 이 마방진을 ‘2차원 배열형태 값에서 가로, 세로, 대각선 중 어느 곳으로 더해도 일정한 값이 나오게 하는 방법’이라고 정의하며, 크게 홀수와 짝수(일반항이 4*n , 4*n+2) 형태로 나눈다. 
<그림 2> 마방진의 정의와 종류
먼저 홀수 마방진에 대한 로직을 살펴보자. 
<그림 3> 홀수 마방진과 알고리즘
<리스트 1>과 같이 작성한 뒤 테스트해 보면, 실제 적용된 소스가 무엇인지 확인할 수 있다. 확인 방법은 <리스트 2>와 같다. 
<리스트 1> 홀수 마방진 소스 : OddMagicSquare.java
package kr.or.javacafe.magic;
public class OddMagicSquare {
 protected int[][] magic; // 전체 2차원 배열을 구성하기 위한 변수
 protected int top;  // x-1 < 0 or y-1 < 0 일 경우
                          // x = n-1 or y = n-1을 넣어주기 위한 값

 public OddMagicSquare(){ // 생성자 : 객체가 생성될 때 한번만 호출되는 것
  this(3);         // 홀수 마방진이므로 생성자에 argument가 없어도
                        // 홀수 마방진의 시작인 3으로 값을 초기화
 }

 public OddMagicSquare(int n){ // argument가 있는 객체 생성 시
  this.init(n);        // 값을 초기화 하는 init() 메소드 호출
 }

 public void init(int n){ // 객체 생성 시 입력받은 값으로 값을 초기화
  this.magic = new int[n][n];
  this.top = n-1;
 }

 public void make(){ // OddMagicSquare 로직 구현!!
  int x = 0;
  int y = top/2;
  magic[x][y] = 1;

  for (int i = 2; i <= (top+1)*(top+1) ; i++) {
   int tempX = x;
   int tempY = y;
 
   if (x-1<0) {
    x = top;
   }else{
    x--;
   }
 
   if (y-1<0) {
    y = top;
   }else{
    y--;
   } 
   if (magic[x][y]!=0) {
    x = tempX + 1;
    y = tempY;
   }
   magic[x][y]=i;
  }
 }
 public void print(){ // 만들어진 OddMagic Square 출력
  for (int i = 0; i < magic.length; i++) {
   for (int j = 0; j < magic[i].length; j++) {
    System.out.print(magic[i][j]+"\t");
   }
   System.out.println();
  }
 }
 public boolean isCheck(){ // 마방진이 제대로 만들어졌는지 확인하는 메소드
  boolean isCheck = true;
  int count = top+1;
  int[] mcheck = new int[2*count+2]; // 검증해야 할 값 전체
                                          // 가로 n개, 세로 n개, 대각선 2개
  for (int i = 0; i < magic.length; i++) {
   for (int j = 0; j < magic[i].length; j++) {
                   // 가로 mcheck[0] = magic[0][0]+magic[0][1]+magic[0][2]
                          mcheck[i] += magic[i][j];
                   // 세로 mcheck[3] = magic[0][0]+magic[1][0]+magic[2][0]
    mcheck[i+count] += magic[j][i];
                  // ‘\’ 대각선 mcheck[6] = magic[0][0]+magic[1][1]+magic[2][2]
    if (i==j) {
     mcheck[2*count] += magic[i][j];
    }
                 // '/' 대각선 mcheck[6] = magic[0][0]+magic[1][1]+magic[2][2]
    if (i+j==count-1) {
     mcheck[2*count+1] += magic[i][j];
    }
   }
  }
                // mcheck [2*count+2]의 방에 있는 값 중 하나라도 틀리면
                // false를 리턴 !!
  for (int i = 1; i < mcheck.length; i++) {
   if (mcheck[0]!=mcheck[i]) {
    isCheck = false;
    break;
   }
  }
  return isCheck;
 }// isCheck
}// class
<리스트 2> 홀수 마방진 확인 소스 : MagicMain.java
package kr.or.javacafe.magic;
public class MagicMain{
   public static void main(String[] args){
      OddMagic om = new OddMagic(); // 객체생성
      om.make(); // 2차원 배열 만들기
      om.print(); // 만들어진 2차원 배열 출력하기
      System.out.println(om.isCheck()); // 만들어진 홀수 마방진이 맞는지 확인
                                       // true or flase
   }
}

홀수 마방진을 살펴봤으니 이제 짝수 마방진인 4 마방진과 6 마방진을 알아보겠다. 아마 4 마방진과 6 마방진을 알아보고 나면 공통적으로 사용되는 메소드와 그렇지 않은 메소드가 무엇인지를 알 수 있게 될 것이다. 이를 통해 OOP(Object Oriented Programming)를 적용할 때 클래스를 나누는 방법과 패턴을 적용하는 방법 중 어떤 것이 더 좋은지 파악할 수 있게 된다.

4 마방진은 크게 두 가지로, 기본 흐름과 역 흐름이 있다. 우리는 이 두 가지 알고리즘을 메소드로 구현해 보겠다. 기본 흐름은 1~16(n)까지의 숫자를 순서대로 넣어주면 되고, 역 흐름은 기본 흐름과 반대로 1~16까지의 숫자를 거꾸로 넣으면 된다.

그러나 역 흐름은 특정 영역에만 적용시켜야 한다. 역 흐름을 적용하려면 <그림 4>와 같이 x와 y로 영역을 나눠 해당 값을 대입하는 것이 핵심 포인트다. 그래서 먼저 x 범위를 구한 뒤 y 영역을 구하고, 두 범위가 겹치는 부분에 넣고자 하는 값을 역순으로 입력하면 역 흐름 4 마방진이 완성된다.

<그림 4>, <그림 5>, <그림 6>을 보면 4 마방진에 대한 내용을 쉽게 이해할 수 있을 것이다.
<그림 4> 4 마방진 1단계
<그림 5> 4 마방진 2단계
<그림 6> 4 마방진 3단계
이를 완성하면 <리스트 3>과 같은 로직을 구할 수 있다.
<리스트 3> 4 마방진 소스 : FourMagicSquare.java
package kr.or.javacafe.magic;
public class FourMagicSquare{
  // 공통적으로 구현이 적용되는 부분
  public FourMagicSquare(){ this(4); }// 생성자에 argument를 넣지 않아도 기본값 4를 입력
  public FourMagicSquare(int n){} // 생성자에 argument를 입력하면 호출되는 생성자
  public void init(int n){} // 값을 초기화
  public void print(){} // 4 마방진 출력
  public boolean isCheck(){} // 4 마방진 검증
  // 4 마방진 로직구현
  public void make(){
 makeRight();
 makeLeft();
  }
  // 4 마방진 기본 흐름 순서대로 1~ 16까지 숫자 넣기
  public void makeRight() {
 int count = top+1;
 for (int i = 0; i < count; i++) {
  for (int j = 0; j < count; j++) {
   magic[i][j]+=i*count+j+1;
  }
 }
  }
  // 4 마방진의 역 흐름의 조건을 찾아 원하는 숫자 넣기
  public void makeLeft() {
 int count = top+1;
 for (int i = 0; i < count; i++) {
  for (int j = 0; j < count; j++) {
    if( (i>=0 && i=count/4*3 && i      if (j>=count/4 && j   magic[i][j] = count*count-(i*count+j);
        }//if
      }else{
        if ((j>=0 && j=count/4*3 && j   magic[i][j] = count*count-(i*count+j);
       }//if
    }// if~else
  }//inner for
 }// outter for
  }// makeLeft
} //class

4 마방진에 이어 6 마방진을 살펴보겠다. 6 마방진의 로직은 조금 복잡한 편이다. 그래서 그림을 먼저 보고 이해하는 것이 좋다(<그림 7>, <그림 8> 참조).
<그림 7> 6 마방진 1단계
<그림 8> 6 마방진 2단계
이를 토대로 6 마방진을 구현한 것이 <리스트 4>이다. 
<리스트 4> 6 마방진 소스 : SixMagicSquare.java
package kr.or.javacafe.magic;
public calss SixMagicSquare{
  // 공통적으로 구현이 적용되는 부분
  public SixMagicSquare(){ this(6); }//생성자에 argument를 넣지 않아도 기본값 6을 입력
  public SixMagicSquare(int n){} // 생성자에 argument를 입력하면 호출되는 생성자
  public void init(int n){} // 값을 초기화
  public void print(){} // 6 마방진 출력
  public boolean isCheck(){} // 6 마방진 검증
  // 6 마방진 로직 구현하는 메소드
  public void make(){
 makeA(); // A 영역 만들기
 makeB();  // B 영역 만들기
 makeCD(); // A:0->3, 3->0으로 변환해 C로, B:1->2. 2->1로 변환해 D로
 multiples(); // A, B, C, D 각 영역에 n/2 * n/2를 곱한다.
 addABCD(); // OddMagicSquare(n/2)로 만들어진 값을 A, B, C, D에 더한다.
  }
  public void makeA() {
 int count = top+1;
 for (int i = 0; i < count/2; i++) {
  for (int j = 0; j < count/4; j++) {
   if (i==count/4) {
    magic[i][j+1]=3;
   }else{
    magic[i][j]=3;
   }
  }
 }
  }// makeA
  public void makeB() {
 int count = top+1;
 for (int i = 0; i < count/2; i++) {
  for (int j = 0; j < count/2; j++) {
   magic[i][j+count/2]=1;
  }
 }
 for (int i = 0; i < count/2; i++) {
  for (int j = 0; j < count/2-(count/4-1); j++) {
   magic[i][j+count/2]=2;
  }
 }
  }// makeB
  public void makeCD() {
 int count = top+1;
 for (int i = 0; i < count/2; i++) {
  for (int j = 0; j < count/2; j++) {
   if (magic[i][j]==0) {
    magic[i+count/2][j]=3;
   }else if(magic[i][j]==3){
    magic[i+count/2][j]=0;
   } 
   if(magic[i][j+count/2]==2){
    magic[i+count/2][j+count/2]=1;
   }else if (magic[i][j+count/2]==1) {
    magic[i+count/2][j+count/2]=2;
   }
  }
 }
  }//makeCD
  public void multiples() {
 int count = top +1;
 for (int i = 0; i < count; i++) {
  for (int j = 0; j < count; j++) {
   magic[i][j] *= count/2 * count/2;
  }
 }
  }// multiples
  public void addABCD() {
 int count = top+1;
 OddMagicSquare odd = new OddMagicSquare(count/2);
 odd.make();
 int[][] oddM = odd.getMagic();

 for (int i = 0; i < count/2; i++) {
  for (int j = 0; j < count/2; j++) {
   magic[i][j] += oddM[i][j];
   magic[i][j+count/2] += oddM[i][j];
   magic[i+count/2][j] += oddM[i][j];
   magic[i+count/2][j+count/2] += oddM[i][j];  
  }
 }
  }// addABCD
}//class

이렇게 만들어진 클래스를 클래스 다이어그램(Class Diagram) 형태로 나타낸 것이 <그림 9>와 <그림 10>이다.
<그림 9> 홀수 마방진 클래스 다이어그램
<그림 10> 4 마방진과 6 마방진 클래스 다이어그램
6 마방진까지 구현할 수 있다면 3부터 시작해 어떤 수를 넣어도 마방진을 이룰 수 있는 클래스를 만들 수 있다. 지금까지 살펴본 홀수 마방진, 4 마방진, 6 마방진을 연계시켜 만능 마방진을 만들어 보겠다. 만능 마방진을 만드는 단계는 다음과 같다.
1. abstract class 구현(공통적인 부분처리)
2. 계층 구조(hierarchy) 만들기
3. 인터페이스 선언
4. 팩토리 패턴(factory pattern)을 적용해 진행

abstract class 구현(공통적인 부분처리)을 어떻게 구성할지 그림으로 살펴보겠다(<그림 11>, <그림 12> 참조).
<그림 11> 만능 마방진 만들기 1단계
<그림 12> 만능 마방진 만들기 2단계
앞서 만들어 본 ‘OddMagicSquare’, ‘FourMagicSquare’, ‘SixMagicSquare’ 소스에는 공통적으로 ‘void init(int n)’, ‘void print()’, ‘boolean isCheck()’이 들어있는 걸 알 수 있다. 이 메소드는 어디서든 하는 일이 똑같기 때문에, 각 클래스에서도 그 역할 그대로 수행하고 있다. 그래서 이 소스를 담아두는 클래스를 하나 만들어 상속받게 했다.

또 void make() 메소드는 공통적으로 들어가지만 각 클래스 별로 로직이 다르게 구현되기 때문에, abstract class에서 선언만 하고 구현하지는 않았다. 왜냐하면 abstract class는 abstract 메소드 즉, 구현되지 않고 선언만 되는 메소드가 하나 이상 있어야 되기 때문이다(<리스트 5> 참조). 
<리스트 5> 공통적인 코드 적용 소스 : Magic.java
abstract class Magic{
   public int top;
   public int[][] magic;
   public void init(int n){}; // 구현
   public void print(){}; // 구현
   public boolean isCheck(){}; // 구현
   public abstract void make(); // 선언만
}

다음은 계층 구조(hiearachy) 만들기다.

우리는 <리스트 6>에서 계층 구조를 만들기 위해 꼭 필요한 내용들로만 구성했다는 점을 확인할 수 있다. 공통적으로 적용되는 메소드나 멤버 필드는 미리 구현한 클래스를 가져다 사용하면 되기 때문이다.
<리스트 6> 계층 구조 소스 : Magic.java
  class OddMagicSquare extends Magic{
 public OddMagicSquare(){ this(3); }
 public OddMagicSquare(int n){ super.init(n); }
 public void make(){ // 원소스 그대로 적용 }
  }
  class FourMagicSquare extends Magic{
 public FourMagicSquare(){ this(4); }
 public FourMagicSquare(int n){ super.init(n); }
 public void make(){ // 원소스 그대로 적용 }
  }
  class SixMagicSquare extends Magic{
 public SixMagicSquare(){ this(6); }
 public SixMagicSquare(int n){ super.init(n); }
 public void make(){ // 원소스 그대로 적용 }
  }

이 경우 메소드가 하나라도 없으면 올바르게 동작하지 않기 때문에 그 부분을 강요하는 인터페이스를 선언해 보겠다. 인터페이스는 내용이 안에 포함되는 것이 아니기 때문에 간단하게 선언만 하는 형태로 구성한다.
<리스트 7> 인터페이스 소스 : IMagic.java
interface IMagic{
   public void init(int n);
   public void make();
   public void print();
   public boolean isCheck();
}

인터페이스로 선언된 내용을 상속(implements) 받으면 반드시 구현해야 한다. 따라서 꼭 필요한 메소드만 인터페이스로 선언하면 된다. 이 때 계층 구조를 만들고 싶다면 다음 소스를 추가하면 된다.
  abstract class Magic implements IMagic {   ...   }
이 소스를 추가할 때 꼭 필요한 메소드인 경우 인터페이스에 선언(IMagic)한다. 이를 상속 받은 abstract class(Magic)는 공통적인 것은 구현하지만, 다시 구현해야 하는 부분은 선언만 한다. 최종적으로 상속하는 홀수(Odd)나 짝수(Four, Six) 마방진에서 로직만 구현하면, 어떠한 숫자를 대입해도 마방진을 구성할 수 있게 된다.

하지만 특정 숫자를 넣었을 때 그 숫자에 해당하는 마방진이 만들어지게 하려면 클래스를 하나 더 생성해야 한다. 다시 말해 메소드 하나를 호출시켜 원하는 숫자에 따라 홀수 또는 4, 6 마방진이 만들어지도록 해야 한다.

이를 위해 팩토리 패턴을 사용한다. 팩토리 패턴은 하나의 재료를 넣으면 일정한 기준에 따라 제품으로 만드는 패턴이다. 물론 반드시 팩토리 패턴을 사용하지 않아도 된다. 하지만 검증된 패턴을 사용하는 것이 가장 효과적이다. 팩토리 패턴까지 이용해 완성시킨 만능 마방진 소스가 <리스트 8>이다. 
<리스트 8> 완성된 만능 마방진 소스
package kr.or.javacafe.magic;
public class MagicFactory {
 public static IMagic imagic;
 public static IMagic factory(int n){ // n이라는 어떤 숫자를 넣는다.
  if (n%2!=0) {
                        // n이 홀수이면 OddMagicSquare가 실행
   imagic = new OddMagicSquare(n);
  }else if(n%4==0){
                        // n이 4의 배수이면 FourMagicSquare가 실행
   imagic = new FourMagicSquare(n);
  }else{
                       // n이 홀수도 아니고 4의 배수도 아니면 SixMagicSquare가 실행
   imagic = new SixMagicSquare(n);
  }
  return imagic;
 }
}// class

<그림 13> 만능 마방진 만들기 3단계
만들어진 만능 마방진을 사용해 보자. 
<리스트 9> 만능 마방진 확인 소스
   package kr.or.javacafe.magic;
   class MagicMain{
       IMagic imagic = MagicFactory.factory(10);
       imagic.make();
       imagic.print();
       System.out.println(imagic.isCheck());
   }

지금까지 어떤 수를 넣어도 마방진을 생성할 수 있는 프로그램을 작성해 봤다. 프로그램은 로직과 데이터의 결합 형태로 작성된다. 따라서 로직이 명확하게 정리돼 있어야, 내가 사용하는 데이터를 가공해 원하는 목적을 수행시킬 수 있다.
정리하며
무엇이든 기본이 전제 된 다음에 지금 유행하는 프레임워크나 패턴을 적용시켜야 좀 더 좋은 결과를 얻을 수 있다. 안드로이드 프로그래밍도 마찬가지다. 자바에 대한 기본기가 탄탄하다면 좀 더 쉽고 안드로이드 플랫폼에 최적화된 프로그램을 만들 수 있다. 다음 시간에는 UI와 이벤트 핸들링에 대해 알아보겠다. 안드로이드 프로그래밍이 즐거운 코딩 작업이 되길 바란다.

Friday, 31 December 2010

iPhone Dev By maniacdev





Game Dev - "Sheep Dog"







1. Introduction to Mac OS X and Cocoa Touch





2. Using Objective-C, Foundation Framework




3. Custom Classes, Memory Management, and ObjC Properties





4. Interface Builder, Controls, Target-Action




5. Views and Drawing, Animations




6. View Controller Basics




7. Navigation Controllers




8. Table Views





9. Dealing with Data: User Defaults, SQLite, Web Services




10. Performance and Threading




11. Text Input, Presenting Content Modally




12. Address Book: Putting People in Your App




13. Debugging Tips, Searching, Notifications, KVC/KVO




14. Touch Events and Multi-Touch




15. iPhone APIs: Location, Accelerometer & Camera, Batteries




16. Audio APIs, Video Playback, Displaying Web Content, Settings




17. Creating New Expressive Social Mediums on the iPhone




18. Unit Testing, Localization & More

iPhone Dev Game by 71squared


iPhone Game Development Tutorial 1 - Part 1



iPhone Game Development Tutorial 1 - Part 2



iPhone Game Development Tutorial 1 - Part 3



iPhone Game Development Tutorial 1 - Part 4



iPhone Game Development Tutorial 2 - Part 1




iPhone Game Development Tutorial 2 - Part 2




iPhone Game Development Tutorial 2 - Part 3




iPhone Game Development Tutorial 2 - Part 4




iPhone Game Development Tutorial 2 - Part 5




iPhone Game Development Tutorial 2 - Part 6




iPhone Game Development Tutorial 2 - Part 7




iPhone Game Development Tutorial 2 - Part 8




iPhone Game Development Tutorial 2 - Part 9

가장 많이 본 글