티스토리에서 워드프레스닷컴으로 이사합니다. 

이사하는김에 글도 정리했구요. 기술관련글은 혹시나 해서 남겨놓습니다.

가끔 티스토리에 글을 올릴질 모르겠으나 거의 워드프레스를 이용할 것 같네요.


이사한 블로그 주소입니다.

http://dolfalf.wordpress.com

Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

먼저 기본적인 GCD의 기능에 대해서 알아보자.


GCD는 태스크를 저장하는 큐(디스패치큐)를 이용하여 동작하며 3종류의 큐가 정의 되어 있다.

첫번째 글에도 잠깐 언급하였지만 간단히 다시 한번 설명하자면,


직렬큐:태스크를 추가한순으로 실행되는 큐

dispatch_queue_create("jp.hateblo.shin.LABEL_NAME", NULL)


병렬큐:추가된태스크를 병렬로 실행하는 큐 (4개의 글로벌 병렬큐가 이미 준비되어있다.)

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)


메인큐:추가된태스크가 메인스레드에서 실행되는큐. ( UI작업은 이큐에서 동작해야함.)

큐의 취득은 dispatch_get_main_queue()


각각의 큐는 코드도 서로 다르지만 사용하는방법은 같다.


태스크에 등록할 때에는dispatch_async 나 dispatch_sync의 두가지 방법이 있다. 태스크의 등록할 시점에 동기로 실행할지 비동기일지를 지정한다.


직렬큐라면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 2);

        });

        // 여기서 queue 쌓인 태스크가 전부 끝날때까지 메인스레드는 대기한다.

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 3);

        });

    }

    return 0;

}


실행 결과출력.

2015-03-17 16:05:50.864 TestApp3[5693:1456773] Done: 3

2015-03-17 16:05:50.864 TestApp3[5693:1457020] Done: 1

2015-03-17 16:05:50.864 TestApp3[5693:1457019] Done: 2




병렬큐를 구현한다면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 2);

        });

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 3);

        });

        // 여기서 queue 병렬처리 하고 샆다면..

    }

    return 0;

}


dispatch_sync를 사용함으로써 큐에 쌓인 태스크가 종료될때까지 대기하므로 메인스레드에서 별도의 처리를 할 수 없게된다.

이런경우에는 디스패치 그룹을 사용하면 된다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_group_t group = dispatch_group_create();

        

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 1);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 2);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 3);

        });

        

        // 이지점에서 쌓인큐를 종료됨과 동시에 호출된다.

        // dispatch_group_wait 포함 되지 않는다.

        dispatch_group_notify(group, queue, ^{

            NSLog(@"All tasks are done!");

        });

        

        NSLog(@"do something...");

        [NSThread sleepForTimeInterval:1];

        NSLog(@"waiting...");

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

        // 여기서 약간 기다려주지 안흥면 notify처리전에 프로그램의 끝나버리므로..약간 딜레이를

        [NSThread sleepForTimeInterval:1];

    }

    return 0;

}



2015-03-17 16:06:22.744 TestApp3[5717:1461092] do something...

2015-03-17 16:06:23.749 TestApp3[5717:1461092] waiting...

2015-03-17 16:06:25.744 TestApp3[5717:1461228] Done: 3

2015-03-17 16:06:25.744 TestApp3[5717:1461229] Done: 2

2015-03-17 16:06:25.744 TestApp3[5717:1461232] Done: 1

2015-03-17 16:06:25.744 TestApp3[5717:1461232] All tasks are done!





dispatch_group_async를 태스크에 등록을 하면 필요할 경우에 dispatch_group_wait로 큐에 쌓인 태스크가 종료되는것을 기다릴 수 있다.

dispatch_group_notify은 옵션이지만 종료처리에 이용할 수 있다.


디스패치와 세마포어


GCD는 세마포어를 이용할 수도 있다. (세마포어란 전글에도 설명했듯이 배타제어를 위한 방법중 하나이다.)

물론 직렬큐를 잘만 이용한다면 크리티컬섹션을 이용한 세마포어가 필요없겠지만 어쨋든 직력큐와 같은 효과를 낼 수 있다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); //열쇠갯수

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:2]; // do something

            NSLog(@"Finish!");

            dispatch_semaphore_signal(semaphore); //열쇠갯수 늘림

        });

        

        [NSThread sleepForTimeInterval:1]; // do something

        NSLog(@"waiting...");

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //열쇠반환될때 까지 기다림

        

    }

    return 0;

}





2015-03-17 16:06:59.677 TestApp3[5746:1465642] waiting...

2015-03-17 16:07:00.677 TestApp3[5746:1465821] Finish!



GCD에 대해선 언급한 것 이외에도 너무나도 많은 기능들이 있으니 애플문서를 참고하면 될듯하다. 











Posted by 악당잰 트랙백 0 : 댓글 4

댓글을 달아 주세요

  1. addr | edit/del | reply BlogIcon 魔術士マジク 2015.03.20 23:47

    검은건 글씨요 흰건 바탕페이지라....ㅠㅠ

    • addr | edit/del 악당잰 2015.03.21 12:56

      에이 ~ 다 알면서~

  2. addr | edit/del | reply 김기봉 2015.03.24 14:46

    병렬큐에서
    dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 2);
    });
    dispatch_sync(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 3);
    });

    에서 dispatch_sync랑 dispatch_async를 다르게 준건 무슨 의도로 주신거예요?

  3. addr | edit/del | reply BlogIcon 악당잰 2015.03.24 17:30 신고

    마지막에 싱크를 안해주면 메소드가 끝나버리니까 확인이 안되잖어. 그래서 동기로 3초준거지

저번 스레드글에 이어서 NSOperation에 대해서 조금 덧붙여 이야기 해보고려고 한다. 자세한 내용은 애플 개발자문서에 너무도 자세히 나와 있으므로 여기서는 그냥 이렇게 움직인다라는 설명정도만 하려고 한다.




Cocoa에서는 스레드 병렬처리를 위한 고수준 인터페이스를 제공하고 있다. 뭐냐하면 바로  GCD와NSOperation/NSOperationQueue이 두가지 이다.

이 두가지 방법으로 멀티스레드처리가 가능한데 이 방법의 장점은 직렬큐를 이용하거나(GCD의 경우) 큐의 태스크처리를 지정하는 방법등으로 간단히 골치아픈 데드락을 피할 수 있다. ( 다만 리얼타임처리 요하는 경우에는 스레드를 이용하는편이 낫다. 아주 빠른속도의 처리를 위한 특수한 경우가 해당될듯 하다.)





NSOperation/NSOperationQueue은 내부적으로는 GCD로 구현되어 있다고 한다. 결론적으로 NSOperation/NSOperationQueue는 GCD의Objective-C랩퍼 클래스 인 것이다.

GCD에 비교해서NSOperation/NSOperationQueue가 고수준처리가 가능하다는것은 이전 글에서도 잠깐 언급했지만 얻는만큼 잃는것도 있는법. 그대신 그만큼 오버해드가 발생한다고 한다. 오버해드가 발생할만한 코드를 짜본 적이 없으므로 아직은 잘 모르겠다.

아무튼 그런점의 유념해서 개발시에 GCD를 사용할 것인지를 판단해야 할것이다.


그럼 먼저 NSOperation/NSOperationQueue 에 대해서 설명을 하자면,


NSOperation/NSOperationQueue의 사용방법



NSOperation/NSOperationQueue의 기본적인 개념은 태스크(NSOperation)를 큐(NSOperationQueue)에 추가하여 실행하는 방식으로 설계되어 있다.

코드를 보는편이 이해가 빠를지도..아래의 코드를 보자.


#import <Foundation/Foundation.h>


NSBlockOperation* createAnOperation()

{

    // NSBlockOperation 복수의 블록을 가질 있고 그것을 병렬로 실행 시킬수 있다.

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 1");

    }];

    [op addExecutionBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 2");

    }];

    [op addExecutionBlock: ^{

        [NSThread sleepForTimeInterval:1];

        NSLog(@"Done: 3");

    }];

    return op;

}


int main(int argc, char* argv[])

{

    @autoreleasepool {

        NSArray *ops = @[createAnOperation(), createAnOperation()];

        NSOperationQueue *queue = [[NSOperationQueue allocinit];

        // 여기서 오퍼레이션을 직렬로 실행하기 위해서 오퍼레이션 동시실행수를 1 설정

        [queue setMaxConcurrentOperationCount:1];

        // waitUntilFinishedYES 하면 대기한다.

        [queue addOperations:ops waitUntilFinished:NO];

        // 큐에 직접 블록오퍼레이션을 추가하는 것도 가능하다.

        [queue addOperationWithBlock: ^{

            NSLog(@"All done!");

        }];

        // 전부 오퍼레이션이 끝날때까지 대기.

        [queue waitUntilAllOperationsAreFinished];

    }

    return 0;

}


결과는 아래와 같이 출력된다.

2015-03-17 16:04:47.026 TestApp3[5652:1449633] Done: 1

2015-03-17 16:04:47.026 TestApp3[5652:1449632] Done: 2

2015-03-17 16:04:47.026 TestApp3[5652:1449638] Done: 3

2015-03-17 16:04:48.032 TestApp3[5652:1449632] Done: 3

2015-03-17 16:04:48.032 TestApp3[5652:1449638] Done: 1

2015-03-17 16:04:48.032 TestApp3[5652:1449633] Done: 2

2015-03-17 16:04:48.032 TestApp3[5652:1449632] All done!




NSBlockOperation는 표준으로 준비되어 있는 NSOperation의 자식클래스이다. 설명에는 NSBlockOperation만 예를 들었지만 NSInvocationOperation이라는 Target, Selector를 지정해 줄 수 있는 타입도 있다. 또한 NSOperation를 상속받아 커스텀할 수 있도 있다. 더 알고싶다면 애플 개발자 문서를 참조하자.


NSOperationQueue은 기본적으로 GCD라는 병렬큐이다. 위의 예에서는 setMaxConcurrentOperationCount:1로 설정함으로써 직렬큐처럼(태스크가 하나만 처리되므로 결과적으로는 직렬큐) 사용할 수도 있다. 그와 반대로 복수개의 오퍼레이션을  추가하여 이것을 병렬로도 실행 할 수 있다. 


하나 더 유용한 기능이 있는데 NSOperation은 오퍼레이션간에 의존관계를 설정하는 것도 가능하다. 위의 코드는 직렬관계의 오퍼레이션을 동시에 실행하는수를 제한했지만 아래의 코드는 의존관계를 이용하여 실행하는것도 가능하다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        NSOperation *op1 = [NSBlockOperation blockOperationWithBlock: ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: 1");

        }];

        NSOperation *op2 = [NSBlockOperation blockOperationWithBlock: ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: 2");

        }];

        [op2 addDependency: op1]; // op2op1 의존

        NSOperationQueue *queue = [[NSOperationQueue alloc] init];

        [queue addOperations:@[op1, op2] waitUntilFinished:NO];

        [queue waitUntilAllOperationsAreFinished];

    }

    return 0;

}


여기서는 설명하지 못했지만 각각의 오퍼레이션의 우선도를 설정한다던지, 완료시에 콜백으로 불려지는등등 블록을 설정하는것도 가능하다. 실제로 이 클래스를 사용해보면 NSThread를 이용하는것보다 훨씬 간편하다는것을 알수 있다.

게다가 멀티스레드에서 제일 골치아픈 배타적제어 문제도 직렬큐를 이용하면 간단히 해결할 수 있으므로 일석이조라고 할 수 있겠다.




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

스레드는 어떨때 사용할까?


iOS라는 앱에 한정하지 않더라도 어플리케이션이라면 일반적으로 스레드를 사용한다.  보통은 이런경우에 사용하지 않을까 한다.


- 시간이 오래걸리는 작업일 경우

- 동시에 여려작업을 처리하고 싶을경우

- 상태를 계속 감시해야 할 핸들러가 필요할 경우

- 기타....


앱의 경우라면 보통 웹상에 있는 이미지 파일등 다운로드라던가 데이터 파싱등에 사용된다. 이런종류의 처리는 GitHub만 둘러봐도 성능좋은 오픈소스로 많이 공개되어 있기 때문에 굳이 직접 만들 필요까지 없지만.

그러나 아주 가끔 재수없게도 만들어 써야 할 때도 있는데 바로 이번 프로젝트가 그랬다.


이왕 만들어야 되는것이므로 스레드의 개념부터 다시 정리해 보기로 했다. 대학교때 전공으로 배운내용임에도 불구하고 처음 배우는것과 같은 기분이다.




그럼 Objective-C에서 스레드를 어떻게 만들까?

스레드 만드는 방법은 여러가지가 있다. 대표적인것을 꼽아보자면,



NSOperation 

고수준의 편리한 API제공한다.(KVO 키 감시, operation cancel제어등..)

일반 스레드보다 약간 처리속도에 손실이 있다고 한다. 그럼에도 불구하고 편리한 메소드들은 이러한 단점을 커버하기 충분하다.


GCD 

- 블록으로 구현되어 있어 간단하게 사용가능하고 또한 코드 가독성도 좋다.

- 저수준 스레드 구현가능되기에 속도면에서 좋다고 한다. 다만, 상태감시, 오퍼레이션 캔슬 등등 다 직접 만들어야 한다는 귀찮음을 동반한다.


performSelector~:

셀렉터를 이용한 옛날방식이다. GCD를 이용하는것과 거의 같다. 다만 빌드시 ARC 경고가 뜨기도 하는걸로 봐선 ARC와의 궁합이 좋지 않은것 같다. 개인적으로 비추천.


Timer..

간단한 처리는 타이머로 해결 가능하지만 UI이외의 용도로 사용은 글쎄... 좀 불안하다.





스레드를 만들기전에 먼저 알아야할 기초지식



동기처리, 비동기처리

동기란 순차적으로 처리 비동기란 순차적으로 처리되지 않는거다. 예를들어 메소드안에 소스들이 위에서 아래로 순서대로 처리가 되면 동기이고 여기저기 순서없이 처리된다면 비동기이다.


대표적으로 아래의 것들은 비동기처리이다.

딜리게이트(delegate), 셀렉터(@selector), 블록(block), 노티피케이션(Notification)


GCD의 경우는 이런것이다.

dispatch_sync(...), dispatch_async(...)




큐(Queue) 

스레드가 작업을 하기위한 태스크이다. 여러종류가 있는데 우선 직렬큐(serial Queue), 동시큐(Concurrent Queue) 타입으로 나누어진다.


직렬큐는 하나씩 순서대로 처리가 되는 큐를 말한다. (FIFO)

동시큐는 동시에 여러개의 처리가 되는 큐이다.


위에서 설명한 큐는 요렇게 정의 할 수 있다.


//동시큐라면 이렇게..

dispatch_queue_t concurrent_queue = dispatch_queue_create("jp.test.kjcode", DISPATCH_QUEUE_CONCURRENT);

    

//직렬큐라면 이렇게 선언

dispatch_queue_t serial_queue = dispatch_queue_create("jp.test.kjcode", DISPATCH_QUEUE_SERIAL);




또 구조적으로는 메인큐, 글로벌큐 와 프라이빗큐가 있다.


메인큐(Main dispatch queue)는 UI를 일반적으로 제어하는 큐로써 보통 아무것도 정의하지 않으면 이 큐에서 돌아가게 된다. 

그러므로 무거운처리를 아무처리없이 메소드에 넣으면 화면이 멈추는 현상이 발생하기도 하는데 메인스레드에서 작업이 동작하기 때문이다. 그와 반대로 메인스레드가 아닌 스레드에서 UI작업을 하게되면 UI가 원하는대로 갱신되지 것도 경험했을 것이다.

대표적인 예로 노티피게이션 콜백 메소드등이 후자의 경우이다. 이와같은 경우 UI처리는 보통 메인스레드로 큐를 지정해 주어야 한다. (노티피케이션 콜백 메소드는 메인스레드가 아니므로 UI처리를 할 경우 반드시 GCD를 이용하여 메인스레드로 넘겨줘야 한다.)



글로벌큐는 메인큐와 다른 별도의 큐이다. 글로벌큐 취득 메소드를 이용하여 큐를 얻어오면 되는데 처리우선도라던가 백그라운드로 처리할거라든가를 지정할 수 있어 편리하다. 큐의 종류는 동시큐(Concurrent Queue)이다.

요런식으로 얻어올 수있다.


//처리 우선도를 보통(디폴트)으로 지정하여 취득

dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);



사용자정의 큐(Private Queue)는 개발자가 정의 할 수 있는 큐로서 직렬큐, 동시큐 등으로 지정해 줄수 있다.


//사용자 정의큐의 경우는 identify를 지정한다.

dispatch_queue_t concurrent_queue = dispatch_queue_create("jp.test.kjcode", DISPATCH_QUEUE_CONCURRENT);

    


* 덧붙여 설명하자면 NSOperationQueue는 동시큐(Concurrent Queue)이다.




동기처리, 비동기처리 (sync, async)


- 디자인패턴에 의한 비동기처리는 다음과 같은 것들이 있다.

딜리게이트(delegate), 셀렉터(@selector), 블록(block), 노티피케이션(Notification)


- 큐를 이용한 비동기처리 방법은 GCD로 가능하다.

dispatch_sync(...), dispatch_async(...) 

동일한 큐 내에서 동기 비동기를 의미한다. 헷갈리면 안됨. 중요!




데드락(교착상태) 

이상태가 제일 shit!한 상태이다. 어느 스레드도 접근할 수 없는 꼬인 상태인데 보통 화면이 먹통이되어 버린다. (뭐 당연한 얘기지만)


이런 상태는 서로 다른 스레드가 같은 값을 동시에 액서스할때 발생한다.


예를들어 당구장에 당구를 치다가 화장실을 가기위해 당구장 주인에게 열쇠를 받아 화장실에서 볼일을 봤다고 치자. 만약 일을 본 사람이 열쇠를 깜빡 두고 왔다면 어떻게 될까? 열쇠가 없으므로 다음사람은 화장실에 갈수 없게된다. 이런상태를 데드락(교착상태)이라고 한다.


배타적제어

위에서 설명한 데드락을 회피하기 위해 스레드에서도 우선순위를 정하여 순차적으로 접근할 필요가 있다. 어떤 스레드가 어떤 변수에 값을 읽거나 쓰고 있다면 다른 스레드들은 끝날때 까지 대기하고 있다가 처리하는식의 제어를 말한다. 훌륭한 수학자들이 배타적제어에 대한 여러가지 알고리즘을 만들었는데 이해하는건 어려우니 용어만 이라도 알아두자. 


- 크리티컬섹션(임계영역): 데이타의 접근하기 위한 통로?

- 뮤텍스, 세마포어, 인터락...: 배타적 데이타 접근방식


더 자세한 걸 알고 싶다면 위키에서 찾아보도록 하자.





Ojbective-C에서의 배타적제어 방법


위에서 설명한 이론적 베타적 제어를 Ojbective-C에선 어떻게 구현되는지 알아보자.


인터락 함수(Interlocked Family Of Function)(잠금걸쇠가 있는 화장실) 


화장실에 들어간 후 문을 잠근다. 일을 다보고 난 후 문을 열고 나오면 다음사람이 화장실에 들어간다. 이럴경우 한명만 화장실 변기를 사용할 수 있고 중복사용(둘이 같이 화장실에 들어가는?)을 회피할 수 있다.


화장실변기를 변수라고 생각하고 사람을 스레드라고 가정한다면 이게 바로 배타적제어이다.


Objective-C에서는 프로퍼티에 atmoic이라는 지시자를 지정해 줌으로써 간단히 구현이 가능한데 CPU레벨에서의 작업단위(32비트단위인거 같음)에 한해서 1개의 스레드만을 허용하는 방식이다.

그러므로 SInt64이거나 클래스인스턴스 이런건 변수는 하나이지만 CPU처리단위로서는 작업이 나누어져 버리므로 배타제어가 불가능 하다.

이방법으로는 CG돌림자 구조체들(CGPoint,CGRect...)과 location구조체, 스칼라변수(int, float, BOOL)등을 사용할 수 있다.





크리티컬섹션

동기화를 관리하는 영역이다. 화장실에 비유하자면 화장실 열쇠를 가지고 있는 당구장 주인이라 할 수 있다.




크리티컬섹션->뮤텍스방식

열쇠가 하나인 화장실이다. 당구장 주인에게 열쇠를 받아 화장실을 가고 일을본 후에 다시 열쇠를 돌려줘야 한다. 그럼 다음사람이 열쇠를 받아 화장실에 갈수 있고... 방식이라고 생각하면 된다.


Objective-C로 구현한다면 sythroinzed, NSLock, NSRecursiveLock 등으로 구현 할 수 있다.




크리티컬섹션->세마포어방식

열쇠가 여러개 있는 화장실이다. 만약 열쇠가 3개라면 세사람까지 동시에 화장실에서 일을 볼수 있다.

Objective-C에선 아래와 같은 방법으로 선언한다. (보면 알겠지만 GCD를 이용하고 있다.)

dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);






그럼 위에서 설명한 배타적제어를 구현한 소스를 보자.


인터락 함수(Interlocked Family Of Function)(잠금걸쇠가 있는 화장실) 


@property (atomic,readwrite) CGPoint location;


이걸로 끝.




크리티컬섹션->뮤텍스


@synchroized를 이용하여 간단히 구현할 수 있다.


- (void)addItem:(id)object {

    

    // self 키로 락을 . 어디선가 self 락을 건경우 락이 해제될 때까지 여기서 기다리게됨.

    @synchronized (self) {

        [_mutableItems addObject:object];

    }


}


- (void)removeItem:(id)object

{

    //self 키로 락을 . 어디선가 self 락을 건경우 락이 해제될 때까지 여기서 기다리게됨.

    @synchronized (self)

    {

        [_mutableItems removeObject:object];

    }

    

}



이런방법으로 @synchroized에 락을 걸 키를 지정하여 사용할 수도 있다지만 위의 방법으로도 충분할 거 같은 생각이 든다.

@implementation Toliet

{

    

    NSObject* _objectForLock;

}


- (id)init

{

    self = [super init];

    

    if (self)

    {

        _objectForLock = [[NSObject alloc] init];

    }

    

    return self;

}


- (void)addItems:(id)object {

    

    @synchronized (_objectForLock) {

        //이런 방식으로 키를 생성해서 제어 있음.

    }

}


@end




NSLock 을 이용한 방법도 있다.

@interface Toliet () {

    

    NSLock* _lock;

}


@end


@implementation Toliet


@synthesize item = _item;


- (id)init

{

    self = [super init];

    

    if (self)

    {

        _lock = [[NSLock alloc] init];

    }

    

    return self;

}



- (NSObject* )item {

    

    // 락을 걸어 다른스레드로부터 접근을 막음. 다른스레드는 여기서 대기하게됨.

    [_lock lock];

    

    @try

    {

        return _item;

    }

    @finally

    {

        //락을 해제

        [_lock unlock];

    }

}


- (void)setItem:(NSObject *)item {

    // 락을 걸어 다른스레드로부터 접근을 막음. 다른스레드는 여기서 대기하게됨.

    [_lock lock];

    

    @try

    {

        _item = [item copy];

    }

    @finally

    {

        //락을 해제

        [_lock unlock];

    }

}



데드락을 방지하기 위하여 락을 걸때 아래와 같이 시간을 지정해 줄수도 있다.


- (NSObject* )item {

    

//    //락을 걸어 다른스레드로부터 접근을 막음. 다른스레드는 여기서 대기하게됨.

//    [_lock lock];

    

    //만에 하나 처리응답이 없을경우 데드락이 발생되므로 시간을 지정하여 락을 해제할 수도 있다.

    NSDate *timeout  = [NSDate dateWithTimeIntervalSinceNow:30.f];

    [_lock lockBeforeDate:timeout];

    

    @try

    {

        return _item;

    }

    @finally

    {

        //락을 해제

        [_lock unlock];

        

    }

}




또한 NSRecursiveLock 이라는 것도 있다. 사용방법은 위의 NSLock와 같다. 재귀락 개념이라는데 동일 스레드에서 락이 발생했을 경우 락을 걸지 않고 그냥 통과한다고 한다. (해보지 않아서 잘 모르겠음)





세마포어


@interface Toliet2 () {

    

    //세마포어 이용을 위해 먼저 dispatch_semaphore_t 인스턴스를 준비

    dispatch_semaphore_t _semaphore;

}


@end


@implementation Toliet2


@synthesize item = _item;


- (id)init

{

    self = [super init];

    

    if (self)

    {

        //세마포어의 초기화할때 열쇠는 동일스레드, 타스레드 관계없이 제한하므로 주의할것.

        // 화장실 열쇠는 한개라능...

        _semaphore = dispatch_semaphore_create(1);

    }

    

    return self;

}


- (NSObject *)item

{

    NSObject *result;

    

#if 0

    // 열쇠를 가져올때가지 기다림...영원히..

    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

#endif

    

    //그러나 영원히 기다릴수 없는법. 그러면 데드락이 결러버리므로 적정시간이 지나면 락을 풀게 수도 있다.

    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 1000*30);

    dispatch_semaphore_wait(_semaphore, timeout);

    

    //열쇠 받고나서 화장실

    result = [_item copy];

    

    // 화장실 갔다왔으니 열쇠반납.

    dispatch_semaphore_signal(_semaphore);

    

    return result;

    

}


- (void)setItem:(NSObject *)item

{

    

    // 열쇠를 가져올때가지 기다림...영원히..

    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

    

    _item = item;


// 열쇠반납.

    dispatch_semaphore_signal(_semaphore);

    

}


@end








에구구 간단할 줄 알았는데 생각보다 꽤 길어져서 아무래도 나누어서 정리해야겠다.

다음번 스레드 이야기는 아래의 내용을 써볼 예정이다.


[iOS]Objective-C로 NSOperation으로 스레드 구현하기(2)

[iOS]Objective-C로 GCD를 이용한 스레드를 구현하기(3)





참고링크

https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ConcurrencyandApplicationDesign/ConcurrencyandApplicationDesign.html#//apple_ref/doc/uid/TP40008091-CH100-SW1







Posted by 악당잰 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply 호링턴 2016.04.15 18:26

    덕분에 스레드 뭐 사용할지 감이 딱오내요

최근 앱을 만들때 중요도에서는 떨어지지만 반드시 들어가는 기능을 꼽는다면 튜토리얼(Help) 화면일 것이다.

아무도 중요하게 생각안하는 이 화면이 릴리스 거의 막판이되면 디자인때문에 종종 귀찮아지곤 한다.

예를 들어 "이 앱에 대해서 자세히 알고 싶으시면 여기를 클릭하세요" 라는 문구에 "여기"를 클릭하면 화면이동을 하게 한다던지 사파리로 넘겨준다는가 하는 단순한  것들이다.


이런기능들을 예전엔 UIWebView로 구현했었다. (네이티브 화면이동의 경우엔 딜리게이트를 이용하여 후킹하는 방법으로)


그러나 링크기능때문에 UIWebView를 사용한다는건 마치 "Hello World"를 출력하기 위해 멀티스레드로 코드를 짜는것과 같이 미련한 짓이라는 생각이 들어 간단한 방법을 찾아보기로 했다.






역시나! 구현하는 방법이 있었다.


첫번재 방법. UILabel을 이용한 링크 구현이다.

지금까지 조사한 결과 모양만 흉내가능하고 실제 링크터치 이벤트는 못만드는듯 하다.


NSString *text = @"한지민짱! 그럼 여길 클릭하세요.";

    NSRange find_range = [text rangeOfString:@"여길"];

    linkLabel.userInteractionEnabled = YES;

    NSMutableAttributedString *textString = [[NSMutableAttributedString alloc] initWithString:text

                                                                                   attributes:@{ NSForegroundColorAttributeName:[UIColor blackColor],

                                                                                                 NSFontAttributeName:[UIFont systemFontOfSize:17.f]}];

    NSMutableAttributedString *linkString = [[NSMutableAttributedString alloc]

                                             initWithString:[text substringWithRange:find_range]

                                             attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:24.f],

                                                          NSLinkAttributeName:@"native://popuplink/?touched"}];

    

    

    //UILabl 이용하는 방법..그러나 클릭안됨.

    [textString replaceCharactersInRange:find_range withAttributedString:linkString];

     linkLabel.attributedText = textString;


두번째 방법은 UITextView로 링크 구현하는 것이다. 물론 delegate를 이용하면 UIWebView처럼 이벤트 후킹이 가능하다.


    NSString *text = @"한지민짱! 그럼 여길 클릭하세요.";

    NSRange find_range = [text rangeOfString:@"여길"];

    linkLabel.userInteractionEnabled = YES;

    NSMutableAttributedString *textString = [[NSMutableAttributedString allocinitWithString:text

                                                                                   attributes:@{ NSForegroundColorAttributeName:[UIColor blackColor],

                                                                                                 NSFontAttributeName:[UIFont systemFontOfSize:17.f]}];

    NSMutableAttributedString *linkString = [[NSMutableAttributedString alloc]

                                             initWithString:[text substringWithRange:find_range]

                                             attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:24.f],

                                                          NSLinkAttributeName:@"native://popuplink/?touched"}];

    

    [textString replaceCharactersInRange:find_range withAttributedString:linkString];

     linkTextView.attributedText = textString;


//UITextView 이용한 방법. 클릭이벤트 발생됨.

    linkTextView.scrollEnabled = NO;

    linkTextView.editable = NO;

 linkTextView.selectable = YES;

    linkTextView.textContainer.lineFragmentPadding = 0;

    linkTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);

    linkTextView.delegate = self;

    linkTextView.attributedText = textString;

    

    //사이즈 맞춤.

    [linkTextView sizeToFit];

    

    //사이즈를 맞추기 위해 이거 메소드를 호출해줘야 한다고함.

    [linkTextView layoutIfNeeded];

    [linkTextView setTextContainerInset:UIEdgeInsetsMake(0, 0, 0, 0)];

    

    //텍스트뷰는 텍스트를 넣어둔후 폰트설정을 해야 적용된다능...

    linkTextView.font = [UIFont systemFontOfSize:13.f];

}



#pragma mark - UITextView delegate methods

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange

{

    NSLog(@"%s", __FUNCTION__);

    

    NSString *scheme = [url scheme];

    NSString * query = [url query];

    NSURL *path = [url absoluteURL];

    NSLog(@"scheme : %@   query : %@   path : %@ ", scheme , query, path);

    

    

    return NO;

}








돌려보니 잘 된다. 웹뷰쓰는것보다는 기분상 더 좋아 보인다.




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

많은경우 여러개의 리턴값을 받기위해 참조를 이용한 값전달 받기를 한다. 이러한 방식은 에러를 전달할 때 가장 많이 사용되므로 아마도 익숙할 것이다. 


보통 이런방식으로..


//output parameter를 사용한 메소드

- (BOOL)huga:(NSString*)s error:(NSError**)outError {


    if (outError) {

        *outError = [NSError errorWithDomain:@"domine" code:-1 userInfo:nil];

        return NO;

    }

    return YES;

}



//사용하기

NSError *error = nil;

BOOL success = [self huga:@"hahaha" error:&error];



그런데 유심히보면 포인터를 두개를 사용한 참조를 이용하는데 왜 그럴까? 그 이유를 내 나름대로 해석하자면 이렇다.

만약 hoge라는 메소드를 만들고 출력 파라메타를 이용한다면 이런식으로 구현할 것이다.


- (void)hoge:(NSString **)outValue {

    *outValue = @"Kjcode!";

}


//값을 줄때 요렇게..

NSString *str = nil;

[obj hoge:&str];




이런식으로 구현된 소스는 컴파일러에서 아래와 같이 해석한다고 한다. 


//complier code.

- (void)hoge:(NSString *__autoreleasing *)value {

    *value = @"Test";




//complier code.

NSString *__strong s = nil;

NSString *__autoreleasing temp = str;

[obj hoge:&temp];

str = temp;




이 이야기는 참조로 넘어가는 값을 __autoreleasing을 해줌으로 인하여 ARC를 적용시켜주는 것이다.

뿐만아니라 시각적으로도 개발자가 포인터에 값을 넣어주어야 하기때문에 반환되는 파라메터라는 것을 금방 눈치챌 수 있을것이다.


만약 포인터 딸랑 하나로 output파라메타를 사용하는 메소드를 만들었다면 문제가 생길소지가 다분하다.

예를 들어 플젝 개발자가 경험이 부족하거나 아니면 경험자라도 메소드안을 꼼꼼히 보지 않는다면 아무생각없이 그 파라메터를 strong프로터피에 값을 전달시킬수도 있을것이다.

그럼 그순간 순환참조가 일어날 것이고 영원히 메모리에서 젖은낙엽처럼 붙어있게 될것이고 짜증나는 야근을 경험할지도 모른다.



휴~ 지금 내가 하는 플젝에서도 출력파라메터를 잔뜩 사용해놓구선 메모리를 잔뜩 잡아먹는 코드를 만들어놔서 고생중인데 물론.. 그 당사자는 이미 그만둔 상태이므로 리팩토링은 남은자의 몫이 되어 버렸다.


암튼 개고생중. (여기서 "개"라는 표현은 욕이 아닌 강조를 위한 최상급 표현임.)



참고사이트

https://developer.apple.com/library/ios/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html




Posted by 악당잰 트랙백 0 : 댓글 2

댓글을 달아 주세요

  1. addr | edit/del | reply 김기봉 2015.02.03 08:26

    형 이부분
    //complier code.
    NSString *__strong s = nil;
    NSString *__autoreleasing temp = str;
    [obj hoge:&temp];
    str = temp;

    에서
    NSString *__strong s = nil; -> str = nil; 이죠?

앱개발자가 혼자서 개발 할 경우에 고민되는 문제중 하나가 서버개발이다. 그런문제를 한방에 해결해 줄수 있는 서비스가 있는데 그게 바로  Parse라는 서비스이다.

https://parse.com/



물론 앱개발쪽에 관심이 있는 개발자라면 다 아는곳이겠지만, 노티피케이션 서버라던가 API서버, 클라우드등등.. 여러가지 기능을 쉽게 구현해준다.







제공해 주는 많은 기능중에 이번에 포스팅할 내용은 Core(데이타베이스)이다. Parse에 도큐먼트와 튜토리얼을 보면 너무나도 자세히 설명되어 있지만 간단히 소개를 하자면,



모델클래스(PFObject)의 인스턴스를 생성하는것 만으로도 테이블이 생성되며 그인스턴스를 save..라는 메소드를 이용하여 저장할 수 있다. (모델과 테이블에 영속화 되어 있는개념이다.)


게다가 사전(NSDictionary)처럼 사용하면 된다.

PFObject *campSite = [PFObject objectWithClassName:@"campSites"];

campSite[@"name"] = @"テストコード";

campSite[@"access"] = @"hogehoge";

campSite[@"address"] = @"fugafuga";

    

[campSite saveInBackground];



그리고 저장된 데이타를 검색하는 여러가지 메소드를 지원해 주는데 이것또한 직관적으로 알기 쉽게 되어 있다.


PFQuery *query1 = [PFQuery queryWithClassName:@"CampSites"];

[query1 whereKey:@"access" equalTo:@"Dan Stemkoski"];

    

    [query1 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

        if (!error) {

            // The find succeeded.

            NSLog(@"Successfully retrieved %d scores.", (int)objects.count);

            // Do something with the found objects

            for (PFObject *object in objects) {

                NSLog(@"name[%@]", object[@"name"]);

            }

        } else {

            // Log details of the failure

            NSLog(@"Error: %@ %@", error, [error userInfo]);

        }

    }];


위의 샘플소스는 도큐먼트에 나와있는 방법으로 코딩한 것이다.

그러나 한가지 불만스러운 점이 있었는데 문자열을 키로 사용한다는것이 뭔가 이펙티브하지 않다는 생각이 들었다. 뭔가 방법이 있을텐데.. PFObject를 상속받아 각각의 매핑된 모델클래스를 만들수 있을것 같다는 강력한 의구심이 생겼다.


어쨋든 Stackflow와 Parse블로그 등등 조금 둘러보니 역시나 이펙티브한 방법이 있었다.





먼저 테이블에 매핑될 모델클래스를 만들어준다.

클래스를 만들때에 PFSubclassing라는 프로토콜을 선언해 줘야 한다.


#import <Parse/Parse.h>


@interface CampSites : PFObject<PFSubclassing>


+ (NSString *)parseClassName;


@property (retain) NSString *name;


@end



#import "CampSites.h"

#import <Parse/PFObject+Subclass.h>


@implementation CampSites 


@dynamic name;


+ (NSString *)parseClassName {

    return @"CampSites";

}


@end



그다음 모델을 이용하여 테이블 생성 및 데이타 저장을 할 수 있다.

//effective!!

CampSites *obj = [CampSites object];

obj.name = @"effecitve!!!";


[obj saveInBackground];



검색한 결과도 모델로 넘어온다. 한번 해보자.


PFQuery *query1 = [PFQuery queryWithClassName:[CampSites parseClassName]];

    

[query1 findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {

    if (!error) {

        // The find succeeded.

        NSLog(@"Successfully retrieved %d scores.", (int)objects.count);

        // Do something with the found objects

        for (CampSites *object in objects) {

            NSLog(@"name[%@]", object.name);

        }

    } else {

        // Log details of the failure

        NSLog(@"Error: %@ %@", error, [error userInfo]);

    }

 }];



한가지 주의해야 할 점은 이 모델클래스를 사용하기전에 반드시 등록을 해줘야 한다는 점이다.

한번만 등록을 해주면 되므로 AppDelegate.m 에 코딩해 주자.

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

    // Override point for customization after application launch.

     [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

    

    //regist PFObject subClassing

    [CampSites registerSubclass];

    

    return YES;

}


근데 모델을 등록해주려면 AppDelegate.m에다가 모델을 써줘야 하고 임포트까지 해줘야 하는데 이러면 AppDelegate가 너무 지저분해지잖아! 참을수 없다.



각각의 모델클래스에서 사용되어지기전에 한번만 실행되도록 바꾸어 주자.

AppDelegate에 있는 등록소스를 각각의 모델클래스로 옮겨준다.

#import "CampSites.h"

#import <Parse/PFObject+Subclass.h>


@implementation CampSites 


@dynamic name;

// 모델클래스가 로드되면 클래스를 먼저 등록해준다. dispatch_once를 이용하여 한번만 등록되도록 한다.

+ (void)load {

    __weak typeof(self) weakSelf = self;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        [weakSelf registerSubclass];

    });

}


+ (NSString *)parseClassName {

    return @"CampSites";

}


@end





확인해본결과 잘 동작하는것을 알수 있다.






더자세한 내용을 공부하려면 아래의 도큐먼트를 참조하면된다.


https://parse.com/docs/kr/ios_guide#objects




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

앱 개발시 국제화 대응을 생각한다면 국가별언어처리와 국가별 시간처리를 미리 생각해 둘 필요가 있다.

특히 시간처리경우 서버 클라이언트 환경 혹은 다른 디바이스간의 통신환경일 경우 GMT를 계산해 넣지 않으면 데이타의 시간이 뒤죽박죽 되기 때문이다.

이번프로젝트에서도 이런 상황이 또 발생하여 대규모의 리팩토링을 했기에 이번에 확실히 정리해 두려고 한다.




이 이슈에 대하서 먼저 이해해야 할 두가지 단어가 있다. 


타임존(TimeZone)
말그대로 시간대 지역이다. 지구상의 모든국가는 나라,지역별로 시간대를 가지고 있는데 인간이 생활편의를 위한 시간개념이다. 예를들어 오전 10시면 해가 떠있는 아침이고 어둑어둑 해지면 오후 6시면 저녁이라는 시간개념이라고 해두자.

표준시(GMT)
지구상에서 기준이 되는 시간이다. 인간은 생활하는데 각자 편리하게 시간대를 정했지만 기준이 없다면 어느시간이 늦는지 빠른지 알수 없게 된다. 그래서 만든것이 표준시(GMT)이다.(일것이다.) 예를 들어 동경은 표준시보다 9시간 시차가 있으므로 GMT+09:00 의 형태로 표현 할 수 있다.

그럼 코딩에 세계에서는 어떻게 활용되는지 보자.

NSDate
NSDateはGMT에서 1970년1월1일 00:00:00부터 경과한 초를 날짜로 관리한다. 그러므로 타임존이 적용되지 않은 시간인것이다. 그러니까 당연히 날짜에 관련된 메소드도 없다. getYear, getMinute, getWeekday등등.. 간단히 NSDate는 GMT 시간이라고 알아두면 되겠다.


NSDate에서 타임존이 적용된 날짜를 얻는 방법은?
타임존이 적용된 날짜를 얻기 위해선 물론 현재 시간대(Timezone)의 달력이 달력이 있어야 한다. 

그것을 코드로 표현하면 

NSDate *date = [NSDate date];

NSCalendar *calendar = [NSCalendar currentCalendar];

NSUInteger flags = NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay;

NSDateComponents *comps =[calendar components:flags fromDate:date];

[comps setCalendar:calendar];

//    NSDate data1 = comp.date;



이렇게 얻은 데이타는 언어포멧에 맞추어 출력을 할 수도 있다.


NSString *dateTimeString = [NSString stringWithFormat:@"%d%d%d %d%d%d",

                                comps.year, comps.month, comps.day,

                                comps.hour, comps.minute, comps.second];




그러나 위의 방법 보다는  NSDateFormatter 로컬라이즈를 적용하여 문자열을 얻는 방법이 더 유용하다.


NSDate *date = [NSDate date];

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

[dateFormatter setDateFormat:@"yyyy/M/d H:mm"];

NSString *dateString = [dateFormatter stringFromDate:date];

      

NSDate *date = [NSDate date];

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

// 카렌다를 생략하면 언어설정에 의해 양력이외에 표기로 되는수도 있으므로 꼭 설정해 주자. 

// 예를들어 일본의 경우 연호를 사용한 일본달력이 되는 수도 있다.


dateFormatter.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

dateFormatter.dateStyle = NSDateFormatterLongStyle;

dateFormatter.timeStyle = NSDateFormatterShortStyle;

NSString *dateString = [dateFormatter stringFromDate:date];



덧붙여 블루투스 장비의 경우는 Unix시간을 쓰는 경우가 많은데 이경우에는 아래와 같은 코드로 변환해 주면된다. - Unix시간도 GMT기준이다.


//NSDate → UNIX시간

NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];

NSLog(@"timestamp: %f", timestamp);

    

//UNIX시간 → NSDate

NSTimeInterval interval = [timestamp doubleValue] / 1000; // ms -> sec

NSDate* expiresDate = [NSDate dateWithTimeIntervalSince1970:interval];

NSLog(@"expiresDate: %@", expiresDate);

    

//NSDate → NSString

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];

NSString *dateString = [dateFormatter stringFromDate:expiresDate];

NSLog(@"dateString: %@", dateString);





이번프로젝트의 경우는 CoreData에 저장하는 데이타는 모조리 UnixTime(UInt64)으로 변환하여 저장하고  표시할 경우에만 타임존을 적용하였다. 

모두 유틸클래스로 공통화하는 리팩토링도 같이했는데 다수의 개발자가 작업하는 플젝에서는 엉뚱한 짓을 못하게 하는것도 중요한것 같다.


Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

Assets Catalog를 사용하고 있는 앱안에서 아이콘 이미지(AppIcon라고 되어 있는 놈)를 사용하고 싶을 경우엔 아래와같이 해주면 될줄 알았다..


UIImage *appIcon = [UIImage imageNamed"AppIcon"];


그러나 값을 확인하니 정확하게 nil을 반환하는것이 아닌가..




스택플로우 검색결과 이미지사이즈를 포함한 네이밍 문자열을 넘겨줘야 한다는 사실을 알았다.

요렇게..


UIImage *appIcon = [UIImage imageNamed:@"AppIcon40x40"];



참고한곳.

http://stackoverflow.com/questions/22808416/how-to-get-uiimage-of-appicon




나만몰랐나?

Posted by 악당잰 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply BlogIcon 차동수 2015.02.03 09:47

    저도 몰랐어요!

작년에 만든 앱(Hakenman)에 기능 몇개와 버그를 대응한 후 새해첫날에 업데이트 레뷰를 제출했었다.

그러나 생각치도 않던 리젝(메타데이터)이 되어 이유를 읽어보니 사용방법을 동영상으로 보내라는 내용이었다. (게다가 요구하는 동영상화면은 예전부터 있던 기능이었다능...)





정말 기능 설명도 필요없는 간단한 앱인데 말이지... 진정한 갑질은 애플이 아닐까..

어쩔수 없이 동영상을 만들어야 했는데 기왕 만드는거 앱프리뷰 동영상을 만들어 버리기로 했다.

일단 작업전에 앱프리뷰에 대해 사전조사를 한 결과 아래와 같은 정보를 얻을 수 있었다.


App Previews로컬라이즈 안됨. (하나 등록하면 전부 적용되버림)
그러나 표시할지 말지 국가별로 제한이 가능함.
3.5인치 이외에 디바이스 별로 사이즈 맞추어서 동영상을 올려야함. 사이즈가 맞지 않으면 에러남.

동영상은 화면캡춰영상 이어야 함. (사람손이 보이거나하면 안됨.)
음악, 자막, 나레이션 등은 OK.
동영상은 30초 이내, 그리고 30fps.


참고링크.

http://www.apptamin.com/blog/app-previews/








이제 위에 조건이 맞게 동영상을 만들어야 하는데 상당히 까다롭다. 다행히 저 귀찮은 작업들을 어느정도 해소해 줄 몇가지 툴을 찾을 수 있었는데 그중에서도 전혀 디자인초보인 개발자가 간단히 작업할 수 있는 AppShow라는 툴을 찾았다.


AppShow링크.

http://appshow.techsmith.com


물론 무료인데다가 인터페이스가 직관적이다. 게다가 위의 애플에서 요구하는 귀찮은 조건들을 한방에 클리어 할 수 있는 툴이다.








어쨋든 이걸 이용하여 만들어 보았다. 만져본 소감으로는 상당히 다루기 쉬웠고 Movie정도를 다룰수준이면 메뉴얼도 필요없을 수준이다.

조금 아쉬운점은 각각의 동영상 씬추가는 간단하지만 동영상에 텍스트를 넣거나 편집하는 기능은 없었다.

이 문제를 궁여지책으로 키노트(Keynote)로 슬라이드를 만든 다음 그걸 이미지로 출력해서 동영상에 삽입하는 꼼수를 썻다. 


요렇게..








게다가 만든 동영상은 디바이스별로 사이즈를 조정하여 출력이 가능하기에 전부 iTuens Connect에 무사히 등록까지 마쳤다. 

어쨋든 까칠한 애플덕분에 3시간정도 시간을 소비하기는 했지만 쓸모있는 경험을 할 수 있었다.








마지막으로 Hakenman Priview 영상은 유투브에 업로드 링크.









Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요