iOS2013. 12. 1. 16:01

일반적으로 cURL을 이용하여 HTTP 프로토콜 데이터를 POST 방식으로 보내는 PHP 함수는 다음과 같습니다.

  1. function https_post($uri$postdata)  
  2. {  
  3.     $ch = curl_init($uri);  
  4.     curl_setopt($ch, CURLOPT_POST, true);  
  5.     curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);  
  6.     $result = curl_exec($ch);  
  7.     curl_close($ch);  
  8.     return $result;  
  9. }  

하지만, 이 방식은 결과값이 전달 될 때 까지 블럭(block) 된다는 것이 문제입니다. 따라서 결과값이 불필요한 경우에는 요청을 보내고 잊어버리면 그만입니다. 아쉽게도 PHP의 cURL 라이브러리는 비동기 처리를 지원하지 않습니다. 따라서 직접 소켓을 오픈하여 요청을 보내야 합니다.

  1. function curl_request_async($url$params$type='POST')  
  2. {  
  3.     foreach ($params as $key => &$val)  
  4.     {  
  5.         if (is_array($val))  
  6.             $val = implode(','$val);  
  7.         $post_params[] = $key.'='.urlencode($val);  
  8.     }  
  9.     $post_string = implode('&'$post_params);  
  10.   
  11.     $parts=parse_url($url);  
  12.   
  13.     if ($parts['scheme'] == 'http')  
  14.     {  
  15.         $fp = fsockopen($parts['host'], isset($parts['port'])?$parts['port']:80, $errno$errstr, 30);  
  16.     }  
  17.     else if ($parts['scheme'] == 'https')  
  18.     {  
  19.         $fp = fsockopen("ssl://" . $parts['host'], isset($parts['port'])?$parts['port']:443, $errno$errstr, 30);  
  20.     }  
  21.   
  22.     // Data goes in the path for a GET request  
  23.     if('GET' == $type)  
  24.         $parts['path'] .= '?'.$post_string;  
  25.   
  26.     $out = "$type ".$parts['path']." HTTP/1.1\r\n";  
  27.     $out.= "Host: ".$parts['host']."\r\n";  
  28.     $out.= "Content-Type: application/x-www-form-urlencoded\r\n";  
  29.     $out.= "Content-Length: ".strlen($post_string)."\r\n";  
  30.     $out.= "Connection: Close\r\n\r\n";  
  31.     // Data goes in the request body for a POST request  
  32.     if ('POST' == $type && isset($post_string))  
  33.         $out.= $post_string;  
  34.   
  35.     fwrite($fp$out);  
  36.     fclose($fp);  
  37. }  

위 코드는 인터넷에 부유 중인 것을 HTTPS (HTTP Secure) 프로토콜도 지원하도록 수정 한 것입니다. 이 방법을 이용하면 전송 할 데이터를 소켓으로 방출 한 후에 곧바로 함수가 리턴됩니다. 즉, 데이터를 받기 위한 추가적인 처리를 프로세스가 감당 할 필요가 없다는 것을 의미합니다.

만약 빠른 응답 속도를 보장 받는 것이 최우선 과제라면 다음과 같이 프로세스를 백그라운드로 실행 하게 만들 수도 있겠습니다. 단, 아래 코드는 POSIX 계열에서만 사용 가능합니다.

  1. function curl_post_async($uri$params)  
  2. {  
  3.         $command = "curl ";  
  4.         foreach ($params as $key => &$val)  
  5.                 $command .= "-F '$key=$val' ";  
  6.         $command .= "$uri -s > /dev/null 2>&1 &";  
  7.         passthru($command);  
  8. }  

이 방식은 지체 없이 curl 프로세스를 백그라운드로 구동하기 때문에 소요 시간이 거의 없다고 볼 수 있습니다.

'iOS' 카테고리의 다른 글

apple push notification 쉽고 간단하게 구축하는 easy apns  (0) 2013.07.10
셀 높이 조절  (0) 2013.06.25
앱 정보 가져오기 (앱이름, 버전)  (0) 2013.06.19
스토리보드 커스텀 셀 (공개)  (0) 2013.06.12
유니코드 출력  (0) 2013.02.01
Posted by 다오나무
iOS2012. 9. 18. 15:10

지난 강좌에서는 동기식으로 이미지를 가져오는 것을 구현했습니다. 하지만 동기식으로 데이터를 가져올 경우 용량이 큰 데이터에서 앱이 멈춰버리는 현상을 발견할 수 있습니다. 이처럼 앱이 멈추는것과 같은 현상을 제거하기 위해서는 비동기식으로 데이터를 가져와야합니다. 이번 강좌에서는 비동기식으로 데이터를 가져오는 방법, 그중에서 NSThread를 이용하는 방법에 대해서 알아보겠습니다.


 비동기식으로 데이터를 읽어온다면 앱의 사용자에게 '지금은 데이터를 로딩하는 중입니다.' 라는 것을 알리기 위한 방법이 필요합니다. 그 중에서 가장 많이 사용되는 방법은 Indicator를 사용하는 방법입니다. (데이터를 로딩하면 빙글빙글 돌아가는 그것입니다.) 프로젝트의 헤더에서 아래와 같이 구성해준 후 인터페이스 빌더에서 연결해줍니다.





 본 프로젝트에서는 뷰가 로드됨과 동시에 이미지를 읽어온다고 했지만, 개발자의 사정에 맞춰서 적당한 순간에 데이터를 로드하면 되겠습니다.



 우선 뷰가 로드되고 동시에 데이터를 읽어오므로 indicator를 start 해줍니다. 대부분의 사항은 상단의 코드와 주석을 봐도 충분히 이해 될 것이라고 생각합니다. 이 방법의 기본 개념은 '같은 스레드에서 이미지를 로딩하면 데이터가 로드되는 코드에서 앱이 멈춰버리고 코드가 진행되질 않으니 데이터를 로딩하는 부분만 다른 스레드에서 실행되게 하자' 입니다. 그래서 이처럼 데이터를 로드하는 부분만 따로 메서드로 구현해줍니다.




 데이터를 로드하는 부분에서 이처럼 데이터를 로드하고 이미지를 세팅하는 것 까지 구현해줍니다. 이제 앱을 실행시키면 Indicator가 돌아가다가 이미지의 로드가 끝나게 되면 Indicator가 사라지고 이미지가 화면에 출력됩니다. 단지 데이터를 읽어오는 부분만 리팩토링하여 스레드로 실행시키기만 하면 동기식 데이터 로드가 비동기식으로 전환 되는 것입니다.

Posted by 다오나무
iOS2012. 8. 27. 17:52

UITableView에서 각 cell 마다 이미지를 로드하거나 특정 시점이 아닌 비동기적으로 이미지를 로드하기 위한 방법이 필요할 때가 있다. 이미지 로드가 완료될때까지 기다렸다가 다음 프로세스를 진행하게 되면 UI가 멈추거나 인터렉티브한 프로그램을 만드는데 많은 제약이 생기기 때문이다. 이러한 이유로 이전부터 비동기 형식으로 이미지를 로드하는 방법과 예제가 블로그와 책에 소개가 많이 되어왔다. 이러한 비동기 방식으로 이미지 로드는 iPhone 로컬의 이미지를 로드하기보다 원격지에 있는 URL을 이용해서 이미지를 로드할때 그 필요성이 더 필요하다. 네트워크를 통해서 이미지를 가져온다는 것은 바이너리 이미지를 로드하는 시간보다 네트워크에서 데이터를 전송하는 시간이 더 많이 걸리기 때문이다. 만약 원격 이미지를 비동기 방식으로 로드하지 않게 되면 UI가 멈추어 버리기 때문에 사용자들은 앱이 죽었다고 생각하거나 답답해서 그 앱을 두번다시 사용하지 않을지도 모른다.

비동기 방식으로 이미지를 로드하는 예제들
http://developer.apple.com/library/ios/#samplecode/LazyTableImages/index.html
http://www.markj.net/iphone-asynchronous-table-image/
http://gazapps.com/wp/2011/06/29/asynchronous-images-on-ios/ 
http://avishek.net/blog/?p=39

위에서 사용하는 방법은 NSURLConnection을 이용해서 delegate method를 이용하는 방법을 보통 사용한다. delegate pattern은 작업 처리를 하는 객체에게 위임을하거나 비동기적으로 객체에 메소드를 호출할때 매우 유용한 방법이다. 하지만 delegate를 사용하면 delegate 메소드가 필요하게 되고 delegate 메소드 안에서 처리할 작업을 정의해 두어야 한다. 

이미지를 비동기 방식으로 로드하고 비동기 방식으로 로드하면서 외부에서 그 이미지를 처리하는 메소드까지 선언해줄수 있는 방법으로 block과 GCD (grand centeral dispatch)를 이용하는 방법을 참조하였다. block은 Python이나 Ruby 등에서 사용하는 closure 개념과 비슷하다. Block은 C 레벨의 문법과 런타임 특징이다. 이것은 C의 기본 함수와 비슷하지만 실행가능한 코드를 추가할 수가 있고 stack 또는 heap 메모리를 바인딩하는 변수를 포함할 수 있다. 이러한 특징 대문에 Block은 실행되는 동안에 어떠한 행위를 위해서 데이터의 상태를 유지할 수 있는 특성을 가진다. Block에 관해서는 다시 한번 Block의 주제에 관해서 다시 포스팅을 할 예정이다. GCD는 애플에서 개발한 기술로써 멀티코어 프로세서에 최적화된 어플리케이션을 지원하기 위해서 만들어진 기술이다. GCD는 병렬처리와 쓰레드 풀을 기반한 기술이다. GCD에 대한 상세한 내용역시 다른 포스팅으로 준비 중이다. Block과 GCD를 이용하면 기존에 사용하던 방법(delegate로 처리하는 방법)과는 다르게 원하는 방법을 구현할 수 있을 것이라는 생각을 가지고 검색해서 다음 블로그를 찾을 수 있게 되었다.  http://www.geekygoodness.com/2011/06/17/uiimage-from-url-simplified/  이 블로그에서는 아주 간단한 설명만 남겨 두었지만 Block과 GCD를 이용해서 이미지를 로드하는 방법을 소스코드로 잘 표현해 주고 있다. Block은 iOS4 이상에서만 사용이 가능하다.

void UIImageFromURL( NSURL * URL, void (^imageBlock)(UIImage * image), void (^errorBlock)(void) )

{

    dispatch_asyncdispatch_get_global_queueDISPATCH_QUEUE_PRIORITY_DEFAULT0 ), ^(void)

                   {

                       NSData * data = [[NSData allocinitWithContentsOfURL:URL];

                       UIImage * image = [[UIImage allocinitWithData:data];

                       dispatch_asyncdispatch_get_main_queue(), ^(void){

                           if( image != nil )

                           {

                               imageBlock( image );

                           } else {

                               errorBlock();

                           }

                       });

                   });

}

 

이 코드를 iOS에서 사용하면 다음과 같은 warning이 나타난다.


이것은 UIImageFromURL가 미리 선언되어 있지 않아서 발생하는 경고인데 .h 파일 안에 미리 선언해주면 이 경고는 사라진다.

#import <UIKit/UIKit.h>


@interface ViewController : UIViewController


void UIImageFromURL( NSURL * URL, void (^imageBlock)(UIImage * image), void (^errorBlock)(void) );

@end


사용방법은 참조한 블로그에 나온 방법대로 사용하면 되는데 다음과 같이 사용하면 된다.

- (void)viewDidLoad

{

    [super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

    

    UIImageFromURL( [NSURL URLWithString:@"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331"], ^(UIImage * image )

    {

        [self.view addSubview:[[UIImageView allocinitWithImage:image]];

    }, ^(void){

        NSLog(@"%@",@"error!");

    });


}



우리는 이 코드를 좀더 Objective-C에 익숙한 메소드와 파라미터 방식으로 변경하고 싶다고 생각했다. Objective-C의 메소드 선언 방법은 개발할때 파라미터에 대한 이름과 타입을 참조하는데 더 유용하기 때문이다. 그래서 이 코드를 다음과 같이 변경하여 인스턴스 메소드로 만들어서 사용할 수 있다. 

#import <UIKit/UIKit.h>


@interface ViewController : UIViewController


//void UIImageFromURL( NSURL * URL, void (^imageBlock)(UIImage * image), void (^errorBlock)(void) );


- (void) loadAsyncImageFromURL:(NSURL *)url  imageBlock:(void (^) (UIImage *image))imageBlock errorBlock:(void(^)(void))errorBlock;

@end


- (void) loadAsyncImageFromURL:(NSURL *)url  imageBlock:(void (^) (UIImage *image))imageBlock errorBlock:(void(^)(void))errorBlock

{

    dispatch_asyncdispatch_get_global_queueDISPATCH_QUEUE_PRIORITY_DEFAULT0 ), ^(void)

                   {

                       NSData * data = [[NSData allocinitWithContentsOfURL:url];

                       UIImage * image = [[UIImage allocinitWithData:data];

                       dispatch_asyncdispatch_get_main_queue(), ^(void){

                           if( image != nil )

                           {

                               imageBlock( image );

                           } else {

                               errorBlock();

                           }

                       });

                   });

}


- (void)viewDidLoad

{

    [super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.


    [self loadAsyncImageFromURL:[NSURLURLWithString:@"https://t1.daumcdn.net/cfile/tistory/1349CD374EA43DFB2E"

                     imageBlock:^(UIImage *image){ 

                         [self.view addSubview:[[UIImageView allocinitWithImage:image]];

                     } 

                     errorBlock:^(void){

                         NSLog(@"%@"@"error!");

                     }];


    

//    UIImageFromURL( [NSURL URLWithString:@"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331"], ^( UIImage * image )

//    {

//        [self.view addSubview:[[UIImageView alloc] initWithImage:image]];

//    }, ^(void){

//        NSLog(@"%@",@"error!");

//    });



이 방법을 이용해서 테이블에 이미지를 비동기 방식으로 로드하는 예제를 작성하면 다음과 같이 될 것이다. 간단하게 테이블에 URL 문자열을 가지는 items 배열을 가지고 사용하였지만, 실제 사용할때는 이 데이터 역시 json이나 xml에서 받아와서 만들 것으로 예상이 된다. tableView:cellForRowAtIndexPath: 메소드에서 cell의 작업을 할때 우리는 비동기 방식으로 처리하는 이 코드로 작업을 처리할 수 있다. 
(이 예제는 정말 단순하고 간단한 예제 이다. 더욱 좋은 코드를 만들기 위한 방법을 이 포스트에서는 소개하지 않는다.)

- (void)viewDidLoad

{

    [super viewDidLoad];


    items = [[NSArray allocinitWithObjects:@"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331"

             @"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331",

             @"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331",

             @"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331",

             @"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331",

             @"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331",

             @"https://t1.daumcdn.net/cfile/tistory/186A3A384EEAE04331",

             nil];

}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    static NSString *CellIdentifier = @"Cell";

    

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {

        cell = [[UITableViewCell allocinitWithStyle:UITableViewCellStyleDefaultreuseIdentifier:CellIdentifier];

    }

    

    // Configure the cell...

    [self loadAsyncImageFromURL:[NSURL URLWithString:[items objectAtIndex:indexPath.row]] 

                     imageBlock:^(UIImage *image){ 


                         [[cell.contentView viewWithTag:999+indexPath.rowremoveFromSuperview];

                         

                         UIImageView *imageView = [[UIImageView allocinitWithImage:image];

                         imageView.frame = CGRectMake(04.0f.0f44.0f44.0f);

                         imageView.tag = 999+indexPath.row;

                         

                         [cell.contentView addSubview:imageView];

                     } 

                     errorBlock:^(void){

                         NSLog(@"%@"@"error!");

                     }];

    cell.textLabel.text = [items objectAtIndex:indexPath.row];

    return cell;

}

 


참조 원문 : http://www.geekygoodness.com/2011/06/17/uiimage-from-url-simplified/
코드의 저작권은 http://www.geekygoodness.com 에 있기 때문에 코드 사용시 원 저작권자에게 사용 요청을 받기 바랍니다.

Posted by 다오나무