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

댓글을 달아 주세요