개발과삶2008/10/13 00:52
  개발자의 입장에서 정리한 지식을 글로 옮기는 것이라 이론적인 것보다는 실제 동작하는 코드를 바탕으로 글을 쓰고 있습니다. 부족한 부분이 있더라도 이점 양해를 부탁드리겠습니다.


   웹사이트의 RSS를 수집하거나 웹2.0 사이트들이 제공하는 Open API를 이용하여 개발하려면 HTTP/HTTPS와 같이 널리 알려진 송수신 프로토콜을 이용하게 되는데, iPhone 역시 고수준의 API를 제공하기 때문에 일반적인 경우에는 BSD Socket을 이용하여 저수준의 입출력을 할 필요는 없다. 즉, NSURLRequest나 NSMutableURLRequest를 이용하여 헤더, 쿠키를 지원하면서 HTTP/HTTPS 통신을 손쉽게 할 수 있다.

   HTTP 프로토콜은 Request를 하고 Response를 받는 형태로 서버와 클라이언트 간의 정보를 교환하는 프로토콜이다. iPhone의 API에서도 서버에 데이터를 요청하기 위해 NSURLRequest(또는 NSMutableURLRequest)를 초기화를 하고 이를 NSURLConnection를 경유하여 전송하고 NSURLResponse로 서버가 송신하는 헤더를 받아오고 NSURLConnection의 delegate 메소드로 결과 데이터나 에러처리를 하게 된다.

  더 자세한 내용은 Apple에서 제공하는 URL Loading System Guide를 참고하고 간단한 예제를 만들면서 코드가 어떻게 실행이 되는지 살펴보겠다.

// url 문자열을 이용하여 HTTP로 웹사이트에 접속
- (BOOL)requestUrl:(NSString *)url {
// URL 접속 초기화
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
                                                                                                         cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                                                                    timeoutInterval:15.0];

// GET 방식
[request setHTTPMethod:@"GET"];

// POST 방식은
// [request setHTTPMethod:@"POST"]; // POST로 선언하고
// [request setHTTPBody:[bodyString dataUsingEncoding:NSUTF8StringEncoding]]; // 전송할 데이터를 변수명=값&변수명=값 형태의 문자열로 등록

// 헤더  추가가 필요하면 아래 메소드를 이용
// [request setValue:value forHTTPHeaderField:key];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (connection) {
    self.receivedData = [[NSMutableData data] retain]; // 수신할 데이터를 받을 공간을 마련
    return YES;
}

return NO;
}

   위의 메소드를 호출하면 비동기로 통신이 되기 때문에 접속만 이상이 없다면 호출 즉시 YES가 반환된다. 현 상태에서는 결과 데이터를 받을 수는 없다. 비동기 통신이라면 결국 콜백함수를 통해 수신된 데이터가 발생할 때 수신 데이터를 처리할 수 있다. 그렇다면, 콜백함수를 알아보겠다. 물론, Cocoa에서는 델리게이트 메소드라는 용어가 맞겠다.

  Cocoa에서 제공하는 NSURLConnection 관련 주요 델리게이트 메소드는 아래와 같다. 물론, 이 외에도 몇 개가 더 존재하나 기본적으로 아래의 것만 구현해도 개발하는 데 큰 문제는 없었다.

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse {
[receivedData setLength:0];
self.response = aResponse;
}
   이 메소드가 호출되는 시점은 서버로의 요청에 대한 반응이라 보면 되겠다. NSURLResponse가 반환되는데 이 과정에서 서버가 클라이언트에 보내는 헤더 정보를 미리 받아볼 수 있다. 이 의미는 앞으로 가져오게될 컨텐츠의 종류(mime type)과 문자셋(char-set)을 미리 확인하고 후에 데이터를 모두 받으면 적절한 디코딩(decode)을 할 수 있도록 하는 데 있다. 예를들어, 서버측에서 UTF-8로 발신을 했는데, 클라이언트측에서는 EUC-KR로 해석하려고 한다면 깨진 데이터만 얻을 뿐이다. 또한, 서버에서 보내는 데이터가 이미지 데이터라면 이미지로 해석하지 않는다면 쓸모없는 데이터가 될 것이다.
   수신되는 데이터는 NSMutableData인 receivedData에 넣게 되는데 여기서는 데이터를 수신받을 준비가 끝났으니 초기화하라는 이야기이다. 또한, 현재 NSURLResponse를 데이터 수신후에 이용해야하기 때문에 인스턴스 변수로 임시 저장한다(self.response = aResponse). NSMutableData의 메모리 할당은 requestUrl 메소드에서 이미 하였다.  

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
   네트워크 상의 데이터는 한 번에 모든 데이터가 전송됨을 보장받을 수 없다. 그래서 수신이 완료될 때까지 지속적으로 받아와야 하는 데, 이 메소드가 그 역할을 한다. 수신되는 데이터가 있을 때마다 receivedData에 추가하는 것이 그것이다.

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"Error : ", [error localizedDescription]);
}
   데이터가 전송되다가 네트워크 장애와 같은 에러가 발생할 수 있다. 그런 에러가 발생하면 위의 메소드가 호출이 된다. 위 메소드를 이용하여 에러처리를 할 수 있다. 자원을 해제하고 경고창을 띄운후 루틴을 종료하는 처리를 하면 된다.

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
data = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
}
   데이터를 모두 수신하게되면 이 메소드가 호출되는데 여기서는 수신한 NSMutableData를 적절한 형태의 데이터로 변환하면 된다. 여기서는 HTML 웹페이지를 수신하려 하므로 NSString으로 변환한다. 물론, 문자셋은 헤더로 부터 수신한 문자셋을 참조하여 변환하면 된다. 여기서는 UTF-8로 변환하게 되는데, 국내 사이트의 경우에는 대부분 EUC-KR 일 것이다. 그러나 Apple에서 제공하는 어떠한 문서를 찾아봐도 EUC-KR 에 대한 상수값을 찾을 수 없었다. NSUTF8StringEncoding 대신 -2147481280 를 입력하면 국내 사이트도 정상적으로 볼 수 있을 것이다. 인코딩 상수값에 대한 테이블은 http://limechat.net/rubycocoa/wiki/?NSStringEncoding 여기를 참조하기 바란다. 참고로, 일본어 문자셋 변환은 Apple 문서에서 찾을 수 있었다.

   추가로 위에서 설명하지 않은 POST 방식과 헤더를 추가해서 전송하는 방법을 설명한 후 마치도록 하겠다. POST 방식의 요청은 GET 과 비슷하나 URL과 함께 key=value에 기반한 데이터도 함께 전송한다는 차이점이 있다.  POST 방식은 [request setHTTPMethod:@"POST"]; 로 초기화하고 [request setHTTPBody:[bodyString dataUsingEncoding:NSUTF8StringEncoding]]; 으로 전송할 데이터를 입력하는데 전송할 데이터(여기서는 bodyString이라는 NSString)는 변수명=값&변수명=값 형태의 문자열로 구성해야한다. 물론 값은 기본적으로 urlencode 처리를 해야한다. 헤더는 [request setValue:value forHTTPHeaderField:key]; 와 같이 key에 대한 값을 필요한 개수만큼 추가하면 된다.

   지금까지 간략하게 설명을 했으나 부족한 것이 많을 것이다. 힌트 정도만 보고 찾아서 직접 코딩해보는 것이 더 도움이 된다고 생각해서이기도 하지만 상세한 튜터리얼을 만들 시간이 부족한 이유가 더 크다. URL Loading System Guide에 더 자세한 설명이 있으니 꼭 참조하기를 바란다. 참고로, 동기 전송으로도 통신을 처리할 수 있으나 UI가 동작하는 쓰레드에서는 사용할 수 없었다.


-- KIDG에 등록한 글
Posted by 종이비행기

티스토리 툴바