먼저 기본적인 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

댓글을 달아 주세요

최근 앱을 만들때 중요도에서는 떨어지지만 반드시 들어가는 기능을 꼽는다면 튜토리얼(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

댓글을 달아 주세요

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

특히 시간처리경우 서버 클라이언트 환경 혹은 다른 디바이스간의 통신환경일 경우 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

댓글을 달아 주세요

단위를 포함한 텍스트를 표시할 경우 대게 숫자표시와 단위의 폰트색상이나 크기가 다르게 디자인 되는 경우가 많다.


이제까지는 라벨을 두개로 나누어(숫자, 단위)서 처리했는데 이런방식으로 할때 문제점은 숫자표시에서 자리수가 바뀔경우(동적으로) 단위 레이아웃을 조정해 줘야 하는 귀찮음이 생긴다.


그런데 어트리뷰트스트링을 사용하면 하나의 라벨에서 두가지 디자인을 대응할 수 있기에 편리하다. 예전부터 이 클래스는 알고 있었으나 여기까지 생각이 미치지 못해 응용을 하지 못했다능...



이미 만들어버린 부분은 어쩔수 없어도 다음부터는 이런 스타일을 카테고리화 해서 사용하면 유용할 것 같은 생각이 든다.





NSDictionary *valueAttribute = @{ NSForegroundColorAttributeName:[UIColor blackColor],

                                       NSFontAttributeName:[UIFont systemFontOfSize:16.0f]};


NSAttributedString *valueString = [[NSAttributedString alloc] initWithString:@"1234"

                                                              attributes: valueAttribute];

    


NSDictionary *unitAttribute = @{ NSForegroundColorAttributeName:[UIColor lightGrayColor],

                                                NSFontAttributeName:[UIFont boldSystemFontOfSize:12.0f] };

NSAttributedString *unitString = [[NSAttributedString alloc] initWithString:@"km"

                                                              attributes:unitAttribute];

    

    

NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] init];

[mutableAttributedString appendAttributedString:string1];

[mutableAttributedString appendAttributedString:string2];


//라벨에 텍스트를 설정

_label.attributedText = mutableAttributedString;


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

댓글을 달아 주세요

가끔씩 디자인 편의상 전면에 알파값을 준 투명한 뷰로 덮을 경우가 있다.

이러한 뷰에서 터치이벤트를 받지 않고 통과시켜 뒤에 있는 뷰가 동작할 수 있도로 하기 위해서는 아래와 같이 메소드를 오버라이드 해주면 된다.





- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    

    //자신의 뷰에서 터치이번트를 통과시키고 싶을경우에 요렇게..

    UIView *touchedView = [super hitTest:point withEvent:event];

    if([self isEqual:touchedView] == YES){

        return nil;

    }

    

    return touchedView;

}



메소드명이 테스트라.. 뭔가 했는데 이런용도가 있었다능...

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

댓글을 달아 주세요

  1. addr | edit/del | reply 김기봉 2015.01.26 14:09

    view.isUserInteractionEnabled = NO;
    이렇게 해당 뷰에 옵션을 줘도 통과가 된다고 알고 있어요.

iOS에서 애니메이션 효과를 줄수 있는 방법은 세가지가 있다. 


UIView를 이용한 애니메이션. 제일 간단하고 쓰기 편하다.

    [UIView animateWithDuration:0.5f animations:^() {

        //애니메이션 내용코딩


    } completion:^(BOOL finished) {

        //애니메이션 끝난 처리


    }];





CABasicAnimation을 이용한 애니메이션. UIView애니메이션 보다 다양한 옵션을 줄 수있다. 기본 제공해주는 애니메이션 옵션이 많으므로 구현하기도 편하다.


1.QuartzCore.framework를 프로젝트에 추가하고 QuartzCore/QuartzCore.h를 사용할 클래스에 임포트 해준다.


#import <QuartzCore/QuartzCore.h>


2.CABasicAnimation의 인스턴스를 생성하고 애니매이션을 등록해 준다.

animationWithKeyPath: 메소드를 사용해서CABasicAnimation 의 인스턴스를 생성한다. 생성할 때에 keyPath에 사용할 키를 지정한다.


예를 들어 이동하는 애니메이션의 경우 position프로퍼티를 지정해 준다.

CABasicAnimation *animation =

    [CABasicAnimation animationWithKeyPath:@"position"];



3.애니메이션 옵션을 설정한다. 

*옵션종류

 duration

 애니메이션 재생시간

 repeatCount

 반복횟수

 beginTime

 애니메이션 시작설정

애니메이션 시작을 늦추려면 CACurrentMediaTime() + 초

의 형식으로 지정해 준다.

 timingFunction

 애니메이션 속도 변화지정

 autoreverses

 애니메이션 종료 후 역순으로 실행할지를 지정

 

 


아래와 같이 사용하면 된다.

animation.duration = 2.5; 

animation.repeatCount = 1; 

animation.beginTime = CACurrentMediaTime() + 2;

animation.autoreverses = YES;

animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];



4.애니메이션 시작과 종료를 지정한다. 지정값은 키패스(keyPath)에 의해서 변한다.


 fromValue

 시작값

 toValue

종료값(절대치) 

 byValue

종료값 (상대치)


아래와 같은 형식으로 이용할 수 있다.

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];

animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];

animation.toValue = [NSValue valueWithCGPoint:CGPointMake(320, 480)];



5.작성한 애니메이션은 레이어에 추가할 수 있는데 그때에는 임의의 키패스(keyPath)를 지정한다.


[myView.layer addAnimation:animation forKey:@"testani"];


재생이 끝나면 처음상태로 되돌아 온다. 만약 애니매이션이 끝난상태 되돌아가지 않게 하려면 아래와 같이 코딩해 준다.

애니메이션이 끝난상태(completed)에서 아래의 코딩을 해준다.

animation.removedOnCompletion = NO;

animation.fillMode = kCAFillModeForwards;


CABasicAnimation사용예

/* 이동 */

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];

animation.duration = 2.5;

animation.repeatCount = 1;

animation.fromValue = [NSValue valueWithCGPoint:myView.layer.position];

animation.toValue = [NSValue valueWithCGPoint:CGPointMake(320, 480)];

[myView.layer addAnimation:animation forKey:@"move-layer"];


/* 회전 */

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];

animation.duration = 2.5;

animation.repeatCount = 1;

animation.fromValue = [NSNumber numberWithFloat:0.0]; 

animation.toValue = [NSNumber numberWithFloat:2 * M_PI]; //회전각도

[myView.layer addAnimation:animation forKey:@"rotate-layer"];


/* 확대, 축소 */

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];

animation.duration = 2.5;

animation.repeatCount = 1;

animation.autoreverses = YES;

animation.fromValue = [NSNumber numberWithFloat:1.0]; //1배

animation.toValue = [NSNumber numberWithFloat:2.0]; // 2배

[myView.layer addAnimation:animation forKey:@"scale-layer"];


복수개의 애니메이션처리를 할경우에는 아래와 같이 처리해 준다.


/* 애니메이션1(x축방향으로 이동) */

CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];

animation1.toValue = [NSNumber numberWithFloat:80];

 

/* 애니메이션2(z축으로 중심이동회전) */

CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];

animation2.fromValue = [NSNumber numberWithFloat:0.0]; //시작각도 

animation2.toValue = [NSNumber numberWithFloat:4 * M_PI]; //360도(90*4)

 

/* 애니메이션그룹 */

CAAnimationGroup *group = [CAAnimationGroup animation];

group.duration = 3.0;

group.repeatCount = 1;

group.animations = [NSArray arrayWithObjects:animation1, animation2, nil];

[myView.layer addAnimation:group forKey:@"move-rotate-layer"];



애니메이션 시작과 종료시 이벤트를 취득할 경우에는 델리게이트를 지정해 준다.

animation.delegate = self; 

 

딜리게이트 메소드

/**

 * 애니메이션 시작

 */

- (void)animationDidStart:(CAAnimation *)theAnimation

{

    NSLog(@"시작");

}

 

/**

 * 애니메이션 종료

 */

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag

{

    NSLog(@"종료");

}





마지막으로 CAKeyframeAnimation 이다. 전에 설명했던 애니메이션보다 더 세밀한 애니메이션을 지정할 수 있다.


CAAnimation를 사용하기 위해서는 <QuartzCore/QuartzCore.h> 프레임워크를 추가하고 해당 클래스에 임포트해 주어야 할 필요가 있다.


아래와 예제를 보자.



CAAnimation에 있는 Keyframe을 사용한다.


// scaleX와scaleY의 변화량을 키프레임(keyframe)형식으로 지정한다.

// array의갯수는 다음에 코딩할 timing과 숫자가 일치해야 한다.

NSArray *values = [NSArray arrayWithObjects:

                   [NSNumber numberWithFloat:0.1],

                   [NSNumber numberWithFloat:1.0],

                   [NSNumber numberWithFloat:1.0],

                   [NSNumber numberWithFloat:7.0],

                   nil];


// 투명도의 변화량을 키프레임(keyframe)형식으로 지정한다.

//array의갯수는 다음에 코딩할 timing과 일치 해야 한다.

NSArray *values2 = [NSArray arrayWithObjects:

                   [NSNumber numberWithFloat:0.1],

                   [NSNumber numberWithFloat:1.0],

                   [NSNumber numberWithFloat:1.0],

                   [NSNumber numberWithFloat:0.0],

                   nil];    


// Keyframe에 이용할 시간을 지정한다.(0.0~1.0)

// 0부터 1까지 지정하는걸 몰라서 한참 해맷는데.. 이걸로 CABasicAnimation에서 할수 없었던 세밀하게 속도조절이 가능하다.

NSArray *timing = [NSArray arrayWithObjects:

                   [NSNumber numberWithFloat:0.0],

                   [NSNumber numberWithFloat:0.25],

                   [NSNumber numberWithFloat:0.85],

                   [NSNumber numberWithFloat:1.0],

                   nil];


// 전체 애니메이션 시간

float duration = 1.5f;


// scaleX의keyframe의 애니메이션 지정

CAKeyframeAnimation *keyframe = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];

keyframe.values = values;

keyframe.keyTimes = timing;

keyframe.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

keyframe.duration = duration;


// scaleY의keyframe의 애니메이션 지정

CAKeyframeAnimation *keyframe2 = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];

keyframe2.values = values;

keyframe2.keyTimes = timing;

keyframe2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

keyframe2.duration = duration;


// 투명도 애니메이션 지정

CAKeyframeAnimation *keyframe3 = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];

keyframe3.values = values2;

keyframe3.keyTimes = timing;

keyframe3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

keyframe3.duration = duration;



// CATransaction를 이용해 3개의 애니메이션을 동시에 실행

[CATransaction begin];

[CATransaction setCompletionBlock:^{

    NSLog(@"animation end.");

    [animationView removeFromSuperview];

}];

[animationView.layer addAnimation:keyframe forKey:@"transform.scale.x"];

[animationView.layer addAnimation:keyframe2 forKey:@"transform.scale.y"];

[animationView.layer addAnimation:keyframe3 forKey:@"opacity"];

[CATransaction commit];


애니메이션은 commit하는 동시에 실행되며 CATransaction을 사용하지 않고 addAnimation만 사용했을경우  addAnimation을 호출한 시점에서 실행된다.



이외에도 상세한 설정을 할수도 있는데 그건 귀찮아서 나중에 정리...할 생각이다. 

아래 소스를 참조해서 연구하면 될듯.

https://github.com/nakiwo/KeyFrameSample


아래는 Core Animation 애플문서(일본어)

https://developer.apple.com/jp/devcenter/ios/library/documentation/CoreAnimation_guide.pdf

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

댓글을 달아 주세요

  1. addr | edit/del | reply 개발초보 2015.03.14 02:14

    효율은 어떻게되죠?

    왜인지는 모르겠지만 첫번째 방법으로 하는 것보다 2번째 방법이나 3번째 방법이 효율이 더 좋을것같은데.

    • addr | edit/del BlogIcon 악당잰 2015.03.14 12:02 신고

      코드작성면에서의 효율로 보자면 간단한 애니메이션(알파값조정, 프레임 위치조정)의경우 첫번째가 효율이 좋지 않을까 생각합니다.
      두번째 세번째는 다양한 애니메이션이 가능하므로 복잡한 애니메이션의 경우에 유리하겠죠.

개발중인 앱이 런타임중에 먹통이 되는 현상이 발생하여 디바이스 로그를 까봤더니 <Error>: Max open files: 78 라는 놈이 다량으로 출력되어 있었다.


앱이 먹통이 되는 문제가 이 에러랑 직접적인 관련은 없을거라 생각하면서도 저 찝찝한 에러로그 때문에 원인을 찾아보기로 했다.

가볍게 시작한 일이 하루종일 삽질이되고...

 모든문제를 해결해주는 전지전능한 스택플로우 마저도 명확한 답이 없었다. 있다 싶으면 케이스별로 다 답변도 달랐다.


찾아보면 찾아볼수록 심오한 Objective-C의 세계로 빠져들어 포기하려던 찰나 우연치 않게 해결할 수 있었다.

문제는 뚱딴지 같이 Provisioning설정..

빌드셋팅에서 프로비져닝을 정성것 다시 설정해주니 그 지저분한 에러가 싹 없어졌다.


하루종일 삽질해서 해결됬으니 다행이지만.. 아직도 먹통이되는 문제는 미해결.



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

댓글을 달아 주세요

지금 참가하고 있는 프로젝트가 위의 세개(구글드라이브, 스카이드라이브, 드롭박스)의 크라우드 드라이브를 연동하는 기능이 있는 앱이다.

덕분에 이 세개의 API를 한번에 경험할 수 있는 원치않는 기회가 생겼다.





기술적인 부분은 절대적으로 블로그의 글보다는 디벨롭페이지를 적극추천한다.

이유는 이들 API의 업데이트도 빠를 뿐더러 iOS도 주기적으로 업그레이드가 되면서 블로그에 있는 정보는 예전정보일 경우가 많기 때문이다.

예를들어 deprecated된 메소드도 있다던지 더 간단하게 구현하는 메소드가 생겼다던지 하는..


기본적으로 각 서비스 디벨로퍼 튜토리얼 페이지를 참고하여 기본골격을 만든다음 구체적인 처리에 대한 궁금함은 기술자 블로그나 스택플로우를 검색하는 식으로 하는것이 가장 효율적이라고 생각한다.


드롭박스 디벨로퍼 페이지

https://www.dropbox.com/developers


스카이드라이브 디벨로퍼 페이지

http://msdn.microsoft.com/en-US/live


구글드라이브 디벨로퍼 페이지

https://developers.google.com/drive/


개인적으로는 드롭박스API가 가장 직관적이고 쓰기 편했는데 그 이유는 폴더나 파일을 열거나 폴더일람을 보여주거나 할경우 Path라는 직관적인 키를 사용했기 때문이다.

한편, SkyDrive나 Google 드라이브의 경우는 각각 ObjectId, FolderId 등으로 파일, 폴더를 다루는 방식이었는데 조금 생소할 뿐이지 원리는 비슷하다.


기능면에서는 구글 드라이브가 가장 많은듯 했고 ..(그만큼 더 복잡했음) 드롭박스는 심플한 느낌에 있을건 대부분 있는 느낌.(iOS 를 개발하는 사람이라면 익숙한 방식) 스카이 드라이브는 뭐 그냥 그렇다. 

이글은 iOS기준에서 설명한 글이기 때문에 다른 운영체제에서는 다소 느낌이 다를 수도 있다.


기술적인 내용은 개발해 나아가면서 추가할 생각이다.




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

댓글을 달아 주세요

  1. addr | edit/del | reply 행인 2013.07.24 13:15

    저도 이번에 api연동을 해야하는 프로젝트의 일원이 되었는데요 ^_ㅠ
    많이 어렵나요??

    • addr | edit/del BlogIcon 악당잰 2013.07.30 22:58 신고

      프로그래밍 경험이 있으시면 어렵진 않구요. 이용할 서비스 API개발자페이지만 잘 이용하한다면 잘 될거라 생각합니다.

  2. addr | edit/del | reply 2014.02.10 09:20

    비밀댓글입니다