Languages/Objective-C2011.02.08 11:02
뷰에 그려지는 내용은, 사실 CALayer 객체 위에 그려집니다. 뷰에 대해서 animation을 적용할 수 있듯이, CALayer 객체에 대해서도 animation을 적용할 수 있습니다.

CALayer에 대한 reference를 찾아보시면 아시겠습니다만, CALayer 객체에는 여러 개의 애니메이션 가능한 프라퍼티들이 있습니다. CALayer는 key-value coding을 지원하기 때문에, 키 값을 사용해 이들 프라퍼티에 애니메이션을 지정할 수 있습니다. 

이런 애니메이션은 적용하기도 간단합니다. 물론 복잡한 애니메이션을 구현하려면 좀 심각한 코딩을 해야 하겠습니다만, 레이어 하나 위에 이미지를 그리고 그 이미지가 깜빡거리게 만드는 정도는 대단히 간단하게 처리할 수 있습니다. 아래의 코드를 보시죠.

UIImage* img = [UIImage imageNamed: @"testimg.png"];

CALayer* sub = [CALayer layer];

sub.contents = img.CGImage;

sub.frame = ...

[self.layer addSublayer:sub];

CABasicAnimation* ani = [CABasicAnimation animationWithKeyPath:@"opacity"];

ani.duration = 2.0;

ani.repeatCount = HUGE_VALF;

ani.autoreverses = YES;

ani.fromValue = [NSNumber numberWithFloat:0.0];

ani.toValue = [NSNumber numberWithFloat:1.0];

ani.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];

[sub addAnimation:ani forKey:@"opacity"];


우선 새 레이어를 하나 만들구요. 그 위에 이미지를 얹습니다. 그런 다음 뷰의 backing layer 위에 해당 레이어를 얹습니다. 그런 다음 애니메이션 객체를 하나 만들어서 해당 애니메이션이 어떻게 실행될 지 지정한 다음에 해당 애니메이션을 새로 만든 레이어에 붙입니다.

유의해서 볼 부분은, 해당 에니메이션이 CALayer의 어떤 프라퍼티에 적용될 것인지를 적용하는 부분입니다. CALayer에는 opacity라는 프라퍼티가 있습니다. 이 프라퍼티는 레이어가 얼마나 투명한지를 결정하는 프라퍼티입니다. 위의 애니메이션은 그 지속 시간(duration)이 2초이며, 영원히(HUGE_VALF) 반복됩니다. opacity 값은 0.0에서 1.0으로 변했다가 다시 원래 상태로 돌아갑니다(autoreverse = YES). 

따라서 위에서 만들어 붙인 레이어에 그려진 이미지는 주기적으로 나타났다 사라지는 것 처럼 보이게 됩니다. 간단하죠?

레이어를 실제로 조작하는 것은 UIView를 가지고 프로그래밍하는 것에 비해 저수준(low-level) 연산처럼 보이기도 합니다만, 잘만 쓰면 뷰를 새로 만들지 않고도 프로그래밍할 수 있기 때문에, 성능 문제를 개선하는 데도 도움을 줄 수 있습니다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 나그네

    sub.contents = img.CGImage 부분에서 Cannpt convert CGImage to objc_object in argument passing 이 떠요 ㅠㅠ

    2011.04.22 20:54 신고 [ ADDR : EDIT/ DEL : REPLY ]
  2. 부따부따

    제가 깜박이는 애니메이션이 특정 버튼을 누르면 시작하고,

    이 버튼에 대한 프로세스가 끝나면 원래 상태로 돌아가는 것을 구현 하려고 하는데요

    실시간 영상을 처리하는 가운데 overlay에 대해 해당 애니메이션을 지정해줬는데

    좀처럼 쉽게되질 않네요 도움 주시면 감사하겠습니다.

    2011.07.15 17:36 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Objective-C2011.02.08 09:34
보통 비동기적으로 프로그래밍 한다고 하면 많은 분들이 쓰레드 프로그래밍을 떠올립니다. 네. pThread같이 비교적 알기쉬운 쓰레드 메커니즘을 사용하면 아주 간단하게 비동기적 프로그래밍을 할 수 있죠. 그런데 iPhone이나 iPad는 좀 더 간단한 메커니즘을 제공합니다. 쓰레드 프로그래밍을 단 한줄도 하지 않고서도, 시스템이 제공하는 쓰레드의 도움을 받아 비동기 프로그래밍을 할 수 있죠. 그리고 이렇게 프로그래밍 하면 프로그램을 구성하는 클래스 사이에 의존 관계를 꽤 간단히 끊어버릴 수 있습니다.

결론부터 이야기하자면 NSNotification을 이용하자는 것인데요. 프로그램 내의 한 모듈에서 다른 모듈로 어떤 사건의 발생을 통지하여, 보내는 쪽 클래스의 실행 궤적과 받는 쪽의 실행 궤적이 비동기적으로 동시 수행될 수 있도록 하자는 것이죠. 

NSNotification을 이용할 때 많은 분들이 [NSNotificationCenter defaultCenter]로 얻어낸 NSNotificationCenter 객체의 postNotification 메소드를 호출하는 것을 떠올리시는데, NSNotificationCenter의 클래스 레퍼런스에도 나와 있습니다만, NSNotificationCenter를 직접 이용하면 동기적인 처리밖에 안됩니다. 

A notification center delivers notifications to observers synchronously.

무슨 이야긴가 하면, postNotification을 호출하면 해당 notification에 observer로 등록한 객체의 callback 루틴이 수행 종료될 때 까지 그 자리에서 가만히 대기하고 있게 된다는 것입니다. 그러니, 비동기적으로 프로그래밍해서 어느 정도의 병행성(concurrency)을 얻고자 한다면 이렇게 프로그래밍을 하면 안되죠. 

따라서, NSNotificationQueue를 이용해야 합니다.

Notification을 날리는 쪽에서는 다음과 같이 하면 됩니다.

[[NSNotificationQueue defaultQueue] enqueueNotification:[NSNotification notificationWithName:@"dataLoad" object:nilpostingStyle:NSPostWhenIdle];


위의 코드는 NSNotificationQueue에 "dataLoad"라는 이름의 NSNotification 객체를 넣는 코드입니다. 이 객체는 언제 notification observer에게 전달될까요? manual에 따르면, 'run loop가 idle할 때'로 되어 있습니다. NSPostWhenIdle로 지정했으니까요. 

Notification을 받을 쪽 코드는 NSNotificationCenter를 사용할 때와 같습니다. 다음과 같이 하면, "dataLoad"라는 이름의 Notification이 떴을 때 _dataLoad 메소드가 callback으로 수행되도록 할 수 있습니다.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_loadData) name:@"dataLoad" object:nil];


한가지 주의할 것은 Notification을 queue에 집어넣을 때 NSPostNow라고 하면 비동기적으로 notification이 날아가는 게 아니라 동기적으로 처리된다는 점이에요. reference에 실린 아래의 글을 인용하는 것으로, 이번 글은 마치도록 하죠.

A notification queued with NSPostNow is posted immediately after coalescing to the notification center. You queue a notification with NSPostNow (or post one with postNotification:) when you do not require asynchronous calling behavior. For many programming situations, synchronous behavior is not only allowable but desirable: You want the notification center to return after dispatching so you can be sure that observing objects have received and processed the notification. Of course, you should use enqueueNotification... with NSPostNow rather than use postNotification: when there are similar notifications in the queue that you want to remove through coalescing.


물론, 이런 질문도 있을 수 있겠어요. 그럼 그냥 NSPostNow를 써서 큐에 넣는 대신, NSNotificationCenter의 postNotification 메소드를 부르면 되지 않느냐? 그런데 그렇게 하면 유사 이벤트 병합기능을 써먹을 수 없게 되죠. NSNotificationQueue는 (위에도 적혀 있습니다만) 같은 이름의 이벤트가 큐에 들어오면 합쳐버리거든요.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 좋은글 잘 읽었습니다.
    그런데, nsthread 에서는 NSPostNow 외에는 호출이 안되네요..
    혹시 가능한 방법을 찾을 수 있겠습니까?

    2011.02.21 18:27 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Objective-C2011.01.04 18:46
이건 비단 MPMoviePlayerController에 국한된 이야기는 아닐 수 있습니다. 다만 이 문제가 '비동기적'인 오류를 유발할 수 있으며, 디버깅이 굉장히 까다로운 문제를 야기할 수 있다는 점을 지적하고 싶습니다. 생각해보면 매우 단순한 문제인데, 깨닫기가 어렵죠. 거기다 iOS 3.2버전과 iOS 4.0 버전에서 동작 방식이 다릅니다. (특히 시뮬레이터에서는 더더욱요.)

간단히 문제를 살펴보죠. 제가 MoviePlayer라는 UIView 클래스의 하위 클래스를 만들었다고 치겠습니다. 이 클래스의 생성자 안에서 MPMoviePlayerController의 객체를 하나 만든 다음에, 이 객체를 사용해서 영화를 재생하기 전에 다음과 같은 짓을 했다고 해 보죠.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(movieStopped) 
                                                              name:MPMoviePlayerPlaybackDidFinishNotification 
                                                             object:moviePlayer];

이렇게 하면, 영화 재생이 끝나면 self, 그러니까 MoviePlayer 객체의 movieStopped 메소드가 호출됩니다. Notification 메커니즘 덕분이죠. 여기서 moviePlayer는 MPMoviePlayerController 객체입니다.

그런데, iOS 3.2에서는 영화가 정상적으로 재생이 끝난 경우에만 notification이 발생하고, 재생 도중에 moviePlayer에 대해서 [moviePlayer stop]을 날린 경우에 대해서는 Notification이 발생하지 않았었어요.

하지만 iOS 4.0에서는 이야기가 좀 다릅니다. [moviePlayer stop]한 경우에도 Notification이 발생하거든요. (실제 장비에서는 모르겠지만 적어도 시뮬레이터에서는 발생합니다.)

그러니, [moviePlayer stop]을 코드에 넣으려고 하고 있다면, 다음과 같이 해 주어야 합니다. 아니면 movieStopped 메소드 안에 removeObserver를 호출하는 부분을 두거나요. 그래야 안전합니다.

[[NSNotificationCenter defaultCenter] removeObserver:self];

[moviePlayer stop];


왜 그럴까요? 만약에 [moviePlayer stop]을 날린 이후에 MoviePlayer 객체를 dealloc했다고 해 보죠. 그러면 아까 addObserver 할 때 인자로 넘겼던 self가 온데간데 없이 메모리에서 사라지게 되거든요. 그러니 removeObserver를 해주지 않으면 사라진 객체에게 NSNotification이 날아가게 되는 것이죠.

그러면 어떻게 되나요?

(잠시 침묵)

네. 사라진 객체가 메시지를 받기 때문에 프로그램이 쭈욱 뻗습니다. (죽는단 소리죠.)

생각해보면 간단하기 짝이 없는데, 몇 시간의 삽질 과정이 없으면 깨닫기 힘든 경우도 많습니다. 이 글이 여러분의 삽질을 조금이라도 줄여주었으면 좋겠군요. :-)


신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Objective-C2011.01.04 16:19
[이전 글에 이어서..]

그럼 새로운 UIView 애니메이션은 어떻게 써먹느냐. Ruby 같은 프로그래밍 언어를 보면 함수를 실행할 때 그 함수에 다른 함수 바디를 동적으로 만들어서 넘길 수도 있도록 허용하고 있는데요. Objective-C에서도 이제 그런 식의 프로그래밍이 가능합니다. 우선 developer.apple.com에서 퍼온 API 설명부터 한번 보죠.

animateWithDuration:animations:completion:

Animate changes to one or more views using the specified duration and completion handler.

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
Parameters
duration

The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them.

animations

A block object containing the changes to commit to the views. This is where you programmatically change any animatable properties of the views in your view hierarchy. This block takes no parameters and has no return value. This parameter must not be NULL.

completion

A block object to be executed when the animation sequence ends. This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called. If the duration of the animation is 0, this block is performed at the beginning of the next run loop cycle. This parameter may be NULL.

Discussion

This method performs the specified animations immediately using the UIViewAnimationOptionCurveEaseInOut and UIViewAnimationOptionTransitionNone animation options.

For example, if you want to fade a view until it is totally transparent and then remove it from your view hierarchy, you could use code similar to the following:


이 함수는 static method이고, 첫 번째 인자로는 Animation 지속시간, 두 번째 인자로는 애니매이션이 수행되는 동안 행해질 작업에 대한 코드 블럭, 그리고 세 번째 인자로는 애니메이션이 끝나면 실행될 코드 블럭이 넘어갑니다.

대충 어떻게 써먹느냐 하면...

[UIView animatedWithDuration:0.2
            animations:^{view.alpha = 0.0;}
            completion:^(BOOL finished){ 
                               if ( finished)
                                  [view removeFromSuperView];
                             }];

위의 코드는 0.2 초 에 걸쳐, 어떤 뷰를 서서히 투명하게 만들고, 투명화 작업이 끝나면 그 뷰를 부모 뷰에서 떼 버리는 코드입니다. 블럭의 시작은 ^으로 표시하고, 인자의 목록은 () 안에, 블럭 코드 바디는 {} 안에 둔다는 것을 알 수 있습니다.

이렇게 코딩하면 앞서 봤던 [UIView beginAnimations], [UIView commitAnimations]를 통해 구현한 경우보다 코드 사이즈가 줄어듭니다. 애니메이션 종료시 호출될 메소드를 따로 구현해 둘 필요가 없거든요. 블럭 문법에 익숙한 분들에게는 코드도 훨씬 더 깔끔해 보이죠.



신고
Posted by 이병준

소중한 의견, 감사합니다. ^^