1. openUrl 을 이용하는 방법
 
- (void) callWithOpenURL:(NSString *)phoneNumber {
    NSURL *url = [NSURL URLWithString:[@"tel://" stringByAppendingString:phoneNumber]];
    [[UIApplication sharedApplication] openURL:url];
}

* 단점 : 전화가 끝나면, 현재 앱이 아닌, 전화앱으로 변경이 된다.

2. WebView를 이용하는 방법
- (void) callWithWebView:(NSString *)phoneNumber {
    NSURL *url = [NSURL URLWithString:[@"tel://" stringByAppendingString:phoneNumber]];
    // ! memory leak 
    UIWebView *callWebview = [[UIWebView alloc] init];
    [callWebview loadRequest:[NSURLRequest requestWithURL:url]];
}
* 장점 : 전화가 끝나면 현재앱으로 돌아온다.
* 단점 : 전화를 걸것인지에 대한 팝업이 한번더 뜬다 

 



예제 이미지 - "Local and Push Notification Programming Guide" 문서에서 발췌




Local Notification 이 무엇인가 ?

Local Notification 이전에, Push Notification 이라는 기능이 있었습니다.
개발사의 서버로부터 Apple Server 로 Notification을 발송하면, Apple Server 에서 iPhone 으로 Notification 을 "Push" 해주는 기능입니다.

Push Notification이 개발사마다 서버를 갖추어야 하고, 이래저래 세팅할것도 많습니다.
정말 필요한 경우가 아니라면 ( 예 : 카카오톡, 마이피플 ) Push Notification을 지원하는 일은 쉬운일이 아닙니다.
서버비용도 발생하고요,

그에 대안(!?) 으로 Local Notification 이라는 기능을 iOS 4.0 부터 지원하기 시작했습니다.

iPhone 내부에서, Notification 을 Scheduling 해주면, Apple Push Server의 도움 없이, Notification Alert을 발생시킬수가 있습니다.
Alram App 같은 것을 만들때 유용하게 사용할 수 있을것 같습니다 ^^

Local Notification이 없던 시절에는 반쪽짜리 Alarm App ( 항상 앱을 켜두어야 하는 ) 밖에 개발할 수 없었지요.


Local Notification Scheduling 하기.


1. UILocalNotification Object를 생성합니다. 

2. fireDate설정

3. 필요하다면 아래의 Property 를 세팅해줍니다.

// alerts
@property(nonatomic,copy) NSString *alertBody;      // defaults to nil. pass a string or localized string key to show an alert
@property(nonatomic) BOOL hasAction;                // defaults to YES. pass NO to hide launching button/slider
@property(nonatomic,copy) NSString *alertAction;    // used in UIAlert button or 'slide to unlock...' slider in place of unlock
@property(nonatomic,copy) NSString *alertLaunchImage;   // used as the launch image (UILaunchImageFile) when launch button is tapped

// sound
@property(nonatomic,copy) NSString *soundName;      // name of resource in app's bundle to play or UILocalNotificationDefaultSoundName

// badge
@property(nonatomic) NSInteger applicationIconBadgeNumber;  // 0 means no change. defaults to 0

4. UIApplication 의 Local Notifiactoin Method들을 통해 scheduling을 합니다.

@interface UIApplication (UILocalNotifications)

- (void)presentLocalNotificationNow:(UILocalNotification *)notification __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);

- (void)scheduleLocalNotification:(UILocalNotification *)notification __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);  // copies notification
- (void)cancelLocalNotification:(UILocalNotification *)notification __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
- (void)cancelAllLocalNotifications __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);
@property(nonatomic,copy) NSArray *scheduledLocalNotifications __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);         // setter added in iOS 4.2

@end


Sample Code

- (IBAction) pressPushNow:(id)sender {
    UILocalNotification *localNotif = [[UILocalNotification alloc] init];
    localNotif.fireDate = [[NSDate date] addTimeInterval:10];
    localNotif.timeZone = [NSTimeZone defaultTimeZone];
    localNotif.alertBody = @"AlertBody";
    localNotif.alertAction = @"AlertAction";
    localNotif.soundName = UILocalNotificationDefaultSoundName;
    localNotif.applicationIconBadgeNumber = 1;
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
    [localNotif release];
}

Sample Screen Shot



자~~알 나옵니다 :)

 


원래 Unit Test 가 없던 프로젝트에, Unit Test를 집어 넣은 후 undefined symbol error에 봉착했습니다 ;ㅂ;

Test Code에서 사용하고 있는 Real Code를 찾을 수 없는 에러 인데..


위와 같이, Unit Test Target에 App Target이 Dependency 가 걸려 있음에도 불구하고 에러를 내뱉더군요 ;ㅂ;

구글님.. 아니, stackoverflow 님은 역시 모든것을 알고 계시더군요 ㅎㅎ

http://stackoverflow.com/questions/5783294/link-error-while-building-a-unit-test-target

범인은 바로 !!!


이녀석입니다. 

이 옵션이 뭐냐하면,  compile 된 excutable file 에서 symbol을 찾지 못하게 하는 옵션입니다.
그래서 Unit Test Target에서 symbols not found 에러가 발생한것이지요 ~



Unit Test를 실행시킬 Debug Profile 에서만 No 를 걸어 줍니다.
그러면 에러가 사라집니다 ㅎㅎ


그림 1.



Corner에 라운드가 처리된 ImageView가 필요해서, 만들게 되어 이러게 공유 합니다 ^^

구글링 해보면, 이런저런 기법들이 많이 나오는데요, 복잡한 해법들이 많은데,
저는 UIBeizerPath라는 녀석을 이용해서, 간단히 구현 하는 방법을 소개 해볼까 합니다.

아직 UIImageView를 이용하면 가장 좋을것 같은데, 방법을 따로 못찾아서, UIView를 상속 받아서, 구현하였습니다.
혹시 아시는 분 있으면 공유! 부탁드립니다 !

RoundRectedImageView.h

#import <Foundation/Foundation.h>

@interface RoundRectedImageView : UIView {

}

@property (nonatomic,retain) UIImage *image;

-(id) initWithImage:(UIImage *) aImage;

@end


RoundRectedImageView.m

#import "RoundRectedImageView.h"


@implementation RoundRectedImageView

@synthesize image = mImage;

-(id) initWithImage:(UIImage *) aImage {

    

    self = [super initWithFrame:CGRectMake(0, 0, aImage.size.width, aImage.size.height)];

    self.image = aImage;

    self.backgroundColor = [UIColor clearColor];

    return self;

}


-(void) drawRect:(CGRect)rect {

    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(16.0, 16.0f)];

    [path addClip];

    [self.image drawInRect:rect];

}

-(void) dealloc {

    self.image = nil;

    [super dealloc];

@end


코드양이 별로 없어서, 설명도 간략하겠군요 :)
drawRect: Method만 살펴 보시면 됩니다.

1. Round가 있는 사각형 UIBezierPath Object를 만든다.
2. addClip 을 호출하여, 앞으로 그려질때, clipping 되도록한다.
3. UIImage를 그려준다.



이렇게 해주면 그림.1 의 우측 그림과 같이 RoundRect 된 ImageView가 됩니다. ^^



 

Abandoned Memory ?

흔히 얘기하는 Leaked Memory 는 reference가 존재 하지 않는 잃어버린 Memory를 이야기한다. 

그럼 Abandoned Memory는 무엇일까?

Abandoned Memory는 Reference가 존재하지만 사용하지 않는 Memory 이다.

Abandoned Memory
는 Profiler가 찾을 수 없기 때문에, Debugging하기가 매우 어렵다.
허나, Instrument 의 Heap Shot기능을 이용하면,
비교적 쉽게 Debugging이 가능하다. Abandoned Memory를 찾아 낼수 있다.

Heapshot의 기능? 

Heapshot의 기능은 Heapshot간의 Memory Allocation Diff 정보를 제공한다는것이다.

HeapShot#0 -> Do some task -> HeapShot#1

이런식으로 흘러 갔을때, HeapShot#0와 HeapShot#1의 Memory allocation Diff가 0 bytes가 되는것이 이상적이라 할수 있겠다.

현실에선, Cache등의 이유로 Diff가 0가 되는일은 거의 없겠다.

허나 HeapShot이 Diff를 제공하기에 Task 후에 살아 남은 Memory Block들을 한눈에 볼수 있고,
내가 의도지하지 않은 Memory Block을 비교적 쉽게 찾아 낼 수 있다.

HeapShot 예제


위의 Sample Program은 2 Button이 존재하며, Memory Leak을 유발하는 Button 하나와 아무것도 하지 않느 버튼 하나가 존재한다.

-(IBAction) pressLeak:(id)aSender {

    [[NSArray alloc] initWithObjects:@"abcdef",@"abcdef",@"abcdef",@"abcdef", nil];

}


Leak Button을 Touch 하면 아무의미 없이 NSArray Object하나를 만든다. 


Profile 모드로 앱을 실행시키고, Instrument Template은 "Allocation"을 선택한다.

좌측 메뉴의 MarkHeap을 눌러 Heap shot을 찍을 수 있다.

아래 그림은 Heapshot을 찍은후의 Heapshot List이다.


Heap Growth Column이 Heapshot을 찍었을 당시의 이전 HeapShot과의 Memory Diff라고 보면 된다.
Memory Leak을 보여주는것이 아니라, Memory Allocation Diff를 보여주는것이기때문에,
Heapshot에 기록되는 모든 Memory가 Leak 혹은 Abandoned 되었다고 판별할 수 없다.

필자는 Heapshot4와 Heapshot8을 찍기 직전에 "Leak" button을 Touch 했다.

 
Heapshot8을 펼쳐 보면 예상과의 달리 Object들이 많이 생성되었다.
아마 Touch Handling을 위해 Framework단에서 생성하는 Object들일것이다.

위의 Test Code를 보면 NSArray를 만들었기에,  유일한 NSArray Object의 생성주기를 보자.(Instrument 우측 View에 나타난다.)


우측에 보면, Object가 만들어진 Stack Trace가 노출되고, Framework 코드가 아닌, 내가 작성한 코드는 Bold로 Highlight된다.
해당 부분을 Double Click 하면 코드도 볼수 있다.


생각보다 Smart한 방법은 아니고, 사람에 의존적인 방식이다.
허나 Diff를 제공하여, 살아 남은 Object 를 추적할 수 있기에, 매우 유용하다.

Memory 문제에 자유로운 개발자는 아마 없을터,
Leaked Memory가 아니라, Abandoned Memory 를 찾아 내는데는  꽤나 유용한 Tip이라고 생각한다.
 

요새 CoreData 관련 공부에 몰두 하고 있습니다.
이런저런것들을 보고 있는데, CoreData Version Up과 LightWeight Migration에 관련 챕터를보았는데,

Modeling 을 변경했을때 자동으로 Data Migration을 해준다는것입니다 ! 
 
그래서 한번 해보기로 했습니다.

아래는 WWDC 2010 의 "Mastering CoreData" Session의 Slide 캡처 화면입니다.

 
음 샬라샬라~
기존의 모델을 유지하고, Version  올려 주고, Option만 주면 된다는 간략한 내용이군요

기존의 자료 ( Apple의 Core Data Programming Guide 마저도... ) Xcode3기준으로 작성되어 약간의 Editor 사용법이 다르지만, 어렵지는 않습니다.
진행 순서는 아래와 같습니다.

1. 버전 추가
2. Attribute renaming
3. 소스 변경
4. Model Current Version 변경
5. 옵션 추가

1. 버전 추가

 위와 같이 Model Editor에서 Editor->Add Model Version 클릭 해주시면 Version 생성 다이얼로그가 나오고는데 적당한 이름을 사용하시면 됩니다.

2.  Attribute renaming
attribute의 이름을 변경합니다. 저는 createdDate라는 Attribute를 createdDate2로 변경했습니다.
예제를 rename 으로 한데는 이유가 있겠죠, 

rename의 경우 original attribute name을 core data 쪽에 알려줘야합니다.

entity를 선택한후 Data Model Inspector의 "Renaming ID"에 원래 이름을 적어줍니다 저 같은 경우 createdDate가 되겠군요


3. Class 변경

class 의 변수 이름을 createdDate 에서 createdDate2로 변경해줍니다.
Xcode내의 refactoring 기능을 이용하면 한번에 수정가능합니다.

단, NSSortDescriptor나 NSPredicate에서 문자열로 사용한 경우에는 수동으로 찾아서 변경해주어야합니다.

 

 4. DataModel 현재 버전 변경하기


위에서 보시는 바와 같이, Version을 추가하여, 새로 만들었지만, 현재는 예전의 Model을 사용하도록 되어 있습니다. 
Model 파일의 Root(Locations.xcdatamodelId)를 선택한후 File Inspector를 열어서 현재 버전을 세팅 하실수 있습니다.

 변경후의 모습은 다음과 같겠군요


 5. NSPersistentStoreCoordinator Open Option설정하기

기본적으로는 Migration기능이 비활성화 되어 있기 때문에, lightweight migration기능을 사용하기 위해선, 몇가지 옵션을 설정해주어야 합니다.

NSInferMappingModelAutomaticallyOption 과 
NSMigratePersistentStoresAutomaticallyOption 입니다.

코드는 아래와 같겠군요

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES],NSInferMappingModelAutomaticallyOption,
                             nil];
 
    NSError *error = nil;
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {}


 
이렇게 하고 실행하시면, 수정된 Model Class로 이전의 Data까지 사용가능하게 됩니다.

별로 어려운건 아닙니다만, 이래저래 개념적으로 확인해야 할것들이 많군요

CoreData는 살펴볼수록 괜찮은 녀석인것 같습니다 ^^ 

+ Recent posts