(소스 주소 : https://github.com/minjoongkim/iOS-Pattern)



팩토리 패턴이란?

객체 생성을 처리하는 패턴입니다. 


어떠한 경우에 사용하는가?

예를들어 책이라는 클래스가 있고, 

그 하위에 만화책, 소설책이라는 클래스가 있습니다.


만화책, 소설책은 책이라는 큰 범위안에 각자가 해야할 메소드들이 따로 있습니다.


어떠한 정보를 받아서 클래스로 생성할때, 매번 if문으로 만화책인지 소설책인지 판단해서 클래스를 만들게 되면 번거롭고 틀릴수도 있기 때문에 이 부분을 띄어서(나눠서) 팩토리 메소드를 만들게 됩니다.

그렇게 되면 객체를 생성할때마다 팩토리 메소드에 객체생성을 하도록 지시하면 객체를 생성해서 반환하게 됩니다.



팩토리 패턴을 사용하게 되면?

이렇게 되면, 객체생성의 버그를 줄일수 있고 유연성을 증가시킬수 있습니다.



우선 book 클래스와 하위 클래스를 먼저 살펴보겠습니다.


부모가 될 Book 클래스


Book.h

#import <Foundation/Foundation.h>


@interface Book : NSObject {

    NSString *name;

    int price;

    NSString *category;

}


-(void)setName:(NSString*)bookName;

-(NSString*)getName;

-(void)setPrice:(int)bookprice;

-(int)getPrice;

-(void)setCategory:(NSString*)bookCategory;

-(NSString*)getCategory;

-(void)bookInfo;


@end


Book.m

#import "Book.h"


@implementation Book


-(void)setName:(NSString*)bookName {

    name = bookName;

}

-(NSString*)getName {

    return name;

}

-(void)setPrice:(int)bookprice {

    price = bookprice;

}

-(int)getPrice {

    return price;

}


-(void)setCategory:(NSString*)bookCategory {

    category = bookCategory;

}

-(NSString*)getCategory {

    return category;

}


-(void)bookInfo {

    NSLog(@"%@ : %@ book price is %d", category, name, price);

}


@end



Book 클래스의 하위 클래스 ComicBook


ComicBook.h


#import <Foundation/Foundation.h>

#import "Book.h"

@interface ComicBook : Book


-(id)init:(NSString*)bookName price:(int)bookprice;

@end



ComicBook.m


#import "ComicBook.h"


@implementation ComicBook


-(id)init:(NSString*)bookName price:(int)bookprice {

    self = [super init];

    if (self) {

        [self setName:bookName];

        [self setPrice:bookprice];

        [self setCategory:@"Comic"];

    }

    return self;

}


@end



Book 클래스의 하위 클래스 NovelBook


NovelBook.h


#import <Foundation/Foundation.h>

#import "Book.h"

@interface NovelBook : Book

-(id)init:(NSString*)bookName price:(int)bookprice;

@end



NovelBook.m


#import "NovelBook.h"


@implementation NovelBook


-(id)init:(NSString*)bookName price:(int)bookprice {

    self = [super init];

    if (self) {

        [self setName:bookName];

        [self setPrice:bookprice];

        [self setCategory:@"Novel"];

    }

    return self;

}


@end



이제 이 클래스들을 이용해 객체를 생성해보겠습니다.


팩토리 패턴 없이 객체를 생성할 때,


Book *book;

NSString *bookCategory = @"Comic";

if([bookCategory isEqualToString:@"Comic"]) {

    book = [[ComicBook alloc]init:@"SlamDunk" price:5000];

}else if([bookCategory isEqualToString:@"Novel"]) {

    book = [[NovelBook alloc]init:@"HarryPotter" price:13000];

}


[book bookInfo];


만약 Book을 상속받는 클래스가 많다면 코드마다 판단해서 객체를 넣기엔 너무나 많은 조건문이 들어가고, 

객체 생성하는 부분이 한군데가 아니라면 그 모든곳을 다 고쳐줘야 하는 번거로움이 있고, 가독성이 떨어집니다.


그래서 이번엔 팩토리 클래스를 만들고 객체를 생성해보겠습니다.



팩토리 클래스 BookFactory


BookFactory.h


#import <Foundation/Foundation.h>

#import "Book.h"


@interface BookFactory : NSObject

+(Book*)makeBook:(NSString*)bookType name:(NSString*)bookName price:(int)bookPrice;

@end


BookFactory.m


#import "BookFactory.h"

#import "NovelBook.h"

#import "ComicBook.h"


@implementation BookFactory


+(Book*)makeBook:(NSString*)bookType name:(NSString*)bookName price:(int)bookPrice{

    if([bookType isEqualToString:@"Comic"]) {

        return [[ComicBook alloc] init:bookName price:bookPrice];

    }else if([bookType isEqualToString:@"Novel"]) {

        return [[NovelBook alloc] init:bookName price:bookPrice];

    }else {

        return nil;

    }

}


@end



팩토리 패턴으로 객체를 생성할 때,


Book *bookUseFactoryComic = [BookFactory makeBook:@"Comic" name:@"SlamDunk" price:5000];

[bookUseFactoryComic bookInfo];


Book *bookUseFactoryNovel = [BookFactory makeBook:@"Novel" name:@"HarryPotter2" price:13000];

[bookUseFactoryNovel bookInfo];


조건문으로 객체를 생성하던 부분이 클래스로 빠져나가서 객체생성을 캡슐화하고 유연성을 증가 시킬수 있습니다.



'Developer > Pattern' 카테고리의 다른 글

iOS 아키텍처 VIPER  (1) 2018.06.22
[Object-c]DecoratorPattern - 데코레이터 패턴  (0) 2016.04.20
[Object-c]FactoryPattern - 팩토리 패턴  (0) 2016.04.19

WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  0개가 달렸습니다.
secret





간단하게 비디오만 재생시키는 샘플입니다.


1. .h 파일

@property (strong, nonatomic) NSURL *videoURL;

@property (strong, nonatomic) AVPlayerViewController *avVideoController;


2. Play Video

-(void) playMovie{

    

    self.avVideoController = [[AVPlayerViewController alloc] init];

    AVPlayer *player = [AVPlayer playerWithURL:[self localMovieURL]];

    self.avVideoController.player = player;

    

    [self.avVideoController.view setFrame:CGRectMake (0, 0, self.view.frame.size.width, self.view.frame.size.height)];

    [self.view addSubview:self.avVideoController.view];

    

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(videoPlayBackDidFinish:)

    name:AVPlayerItemDidPlayToEndTimeNotification

    object:[self.avVideoController.player currentItem]];

    

    [player play];

    

}


-(NSURL *)localMovieURL

{

    NSURL *theMovieURL = nil;

    NSBundle *bundle = [NSBundle mainBundle];

    if (bundle)

    {

        NSString *moviePath = [bundle pathForResource:@"IMG_0075" ofType:@"MOV"];

        if (moviePath)

        {

            theMovieURL = [NSURL fileURLWithPath:moviePath];

        }

    }

    return theMovieURL;

}


3. DidFinishNotification

- (void)videoPlayBackDidFinish:(NSNotification *)notification {

    

    [[NSNotificationCenter defaultCenter]removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

    

    [self.avVideoController.view removeFromSuperview];

    self.avVideoController = nil;

    

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Video Playback" message:@"Just finished the video playback. The video is now removed." preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *okayAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];

    [alertController addAction:okayAction];

    [self presentViewController:alertController animated:YES completion:nil];

    

}


WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  1개가 달렸습니다.
  1. 잘보고갑니다 2016.04.08 10:02
    좋은정보 잘 배워갑니다 감사합니다
secret





Contacts.framework 는 iOS9 부터 기존 ABAddressBook.framework 를 대체합니다.


샘플프로젝트는 깃허브에 올려놓았습니다.

https://github.com/minjoongkim/iOS9-Contacts.framework-AddressBook-Sample



1. 주소록 불러오기

CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if( status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted)
{
    NSLog(@"access denied");
}
else
{
    //Create repository objects contacts
    CNContactStore *contactStore = [[CNContactStore alloc] init];

    //Select the contact you want to import the key attribute  ( https://developer.apple.com/library/watchos/documentation/Contacts/Reference/CNContact_Class/index.html#//apple_ref/doc/constant_group/Metadata_Keys )

    NSArray *keys = [[NSArray alloc]initWithObjects:CNContactIdentifierKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey, CNContactPhoneNumbersKey, CNContactViewController.descriptorForRequiredKeys, nil];

    // Create a request object
    CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
    request.predicate = nil;

    [contactStore enumerateContactsWithFetchRequest:request
                                              error:nil
                                         usingBlock:^(CNContact* __nonnull contact, BOOL* __nonnull stop)
     {
         // Contact one each function block is executed whenever you get
         NSString *phoneNumber = @"";
         if( contact.phoneNumbers)
             phoneNumber = [[[contact.phoneNumbers firstObject] value] stringValue];

         NSLog(@"phoneNumber = %@", phoneNumber);
         NSLog(@"givenName = %@", contact.givenName);
         NSLog(@"familyName = %@", contact.familyName);
         NSLog(@"email = %@", contact.emailAddresses);


         [contactList addObject:contact];
     }];

    [contactTableView reloadData];
}

2. 피커뷰로 불러오기

// Create a new contact view
CNContactViewController *contactController = [CNContactViewController viewControllerForContact:contact];
contactController.delegate = self;
contactController.allowsEditing = YES;
contactController.allowsActions = YES;

// Display the view
[self.navigationController pushViewController:contactController animated:YES];

3. 주소록 추가하기

-(void)saveContact:(NSString*)familyName givenName:(NSString*)givenName phoneNumber:(NSString*)phoneNumber {
    CNMutableContact *mutableContact = [[CNMutableContact alloc] init];

    mutableContact.givenName = givenName;
    mutableContact.familyName = familyName;
    CNPhoneNumber * phone =[CNPhoneNumber phoneNumberWithStringValue:phoneNumber];

    mutableContact.phoneNumbers = [[NSArray alloc] initWithObjects:[CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberiPhone value:phone], nil];
    CNContactStore *store = [[CNContactStore alloc] init];
    CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
    [saveRequest addContact:mutableContact toContainerWithIdentifier:store.defaultContainerIdentifier];

    NSError *error;
    if([store executeSaveRequest:saveRequest error:&error]) {
        NSLog(@"save");
        [self reloadContactList];
    }else {
        NSLog(@"save error");
    }
}

4. 주소록 업데이트 하기

-(void)updateContact:(CNContact*)contact memo:(NSString*)memo{
    CNMutableContact *mutableContact = contact.mutableCopy;

    mutableContact.note = memo;

    CNContactStore *store = [[CNContactStore alloc] init];
    CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
    [saveRequest updateContact:mutableContact];

    NSError *error;
    if([store executeSaveRequest:saveRequest error:&error]) {
        NSLog(@"save");
    }else {
        NSLog(@"save error : %@", [error description]);
    }
}

5. 주소록 삭제하기

-(void)deleteContact:(CNContact*)contact {
    CNMutableContact *mutableContact = contact.mutableCopy;

    CNContactStore *store = [[CNContactStore alloc] init];
    CNSaveRequest *deleteRequest = [[CNSaveRequest alloc] init];
    [deleteRequest deleteContact:mutableContact];

    NSError *error;
    if([store executeSaveRequest:deleteRequest error:&error]) {
        NSLog(@"delete complete");
        [self reloadContactList];
    }else {
        NSLog(@"delete error : %@", [error description]);
    }

}

6. 주소록 상세보기

-(void)loadContactView:(CNContact*)contact {
    // Create a new contact view
    CNContactViewController *contactController = [CNContactViewController viewControllerForContact:contact];
    contactController.delegate = self;
    contactController.allowsEditing = YES;
    contactController.allowsActions = YES;

    // Display the view
    [self.navigationController pushViewController:contactController animated:YES];
}



WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  2개가 달렸습니다.
  1. 김련호 2016.02.26 09:36
    안녕하세요,

    샘플 코드 잘 보았습니다^^ 많은 도움이 되었네요~

    제 코드에서 뭔가 문제가 있는 줄 알았는데, Github에 올려두신 샘플에서도 동일한 문제가 있는 부분이 있어서 댓글 남깁니다.

    연락처 Detail에 들어가서 Share Contact를 눌렀을 때 상단 네비게이션 바가 사라지는 현상이 있는데
    이 부분 때문에 골치네요...

    ABPersonViewcontroller를 iOS 9 이상에서 쓰면 네비게이션 바가 사라지는건 버그로 리포팅이 된것같은데,
    CNContactViewController로 대체하면 해결될줄알았으나 해결이 안되는 것 처럼 보이네요...
    iOS 기본 연락처는 아무 문제가 없는데...흠...
    • 그런문제가 있네요.
      잠깐 봤는데, 아무래도 ios버그인거 같네요.
      지금 잠깐 생각해본 해결법으로는

      timer를 돌려서 계속해서 네비게이션의 상태를 체크합니다.
      그리고 네비게이션이 hide되었다면 show를 해주고..
      타이머를 쓰게되면 퍼포먼스상 문제가 있을수 있지만, 그렇게 무거운 동작을 하는게 아니라 괜찮다고 생각합니다.
      이거 보시고 참고하시고, 나중에 좋은정보 있으면 또 알려주세요.
      -(void)loadContactView:(CNContact*)contact {
      // Create a new contact view
      [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(navigationCheck:) userInfo:nil repeats:NO];
      CNContactViewController *contactController = [CNContactViewController viewControllerForContact:contact];
      contactController.delegate = self;
      contactController.allowsEditing = YES;
      contactController.allowsActions = YES;

      // Display the view
      [self.navigationController pushViewController:contactController animated:YES];
      }

      -(void)navigationCheck:(id*)id {
      if(self.navigationController.navigationBarHidden == true) {
      [self.navigationController setNavigationBarHidden:NO];
      }
      [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(navigationCheck:) userInfo:nil repeats:NO];

      }
secret





sample : https://github.com/minjoongkim/UIAlertController-for-ios8


안녕하세요 iOS8에서 UIAlertView를 사용하면 경고창이 떠서 UIAlertController에 대한 간단한 샘플입니다.

1,2,3번은 UIAlertControllerStyleAlert의 샘플이고, 4번은 UIAlertControllerStylerActionSheet입니다. 1,2번의 타입에 preferredStyle만 바꿔주시면 완성됩니다.



1. 기본 AlertView





UIAlertController *alertController = [UIAlertController

                alertControllerWithTitle:@"simpleAlert"

                      message:@"UIAlertControllerStyleAlert"

                          preferredStyle:UIAlertControllerStyleAlert];


[self presentViewController:alertController animated:YES completion:nil];



2. 버튼이 있는 AlertView




UIAlertController *alertController = [UIAlertController

                alertControllerWithTitle:@"simpleAlert"

                      message:@"UIAlertControllerStyleAlert"

                          preferredStyle:UIAlertControllerStyleAlert];


UIAlertAction *cancelAction = [UIAlertAction

                               actionWithTitle:@"Cancel"

                               style:UIAlertActionStyleCancel

                               handler:^(UIAlertAction *action)

                               {

                                   NSLog(@"Cancel action");

                               }];


UIAlertAction *okAction = [UIAlertAction

                           actionWithTitle:@"OK"

                           style:UIAlertActionStyleDefault

                           handler:^(UIAlertAction *action)

                           {

                               NSLog(@"OK action");

                           }];


UIAlertAction *resetAction = [UIAlertAction

                              actionWithTitle:@"Reset"

                              style:UIAlertActionStyleDestructive

                              handler:^(UIAlertAction *action)

                              {

                                  NSLog(@"Reset action");

                              }];

[alertController addAction:resetAction];

[alertController addAction:cancelAction];

[alertController addAction:okAction];


[self presentViewController:alertController animated:YES completion:nil];



3. TextInputAlert




-(IBAction)textFieldAlert:(id)sender{

    UIAlertController *alertController = [UIAlertController

                  alertControllerWithTitle:@"TextInputAlert"

                                    message:@"Plane and secure text input"

                            preferredStyle:UIAlertControllerStyleAlert];


    [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField)

     {

         [textField addTarget:self

                       action:@selector(alertTextFieldDidChange:)

             forControlEvents:UIControlEventEditingChanged];

         textField.placeholder = @"LoginPlaceholder

     }];

    

    [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField)

     {

         textField.placeholder =@"PasswordPlaceholder"

         textField.secureTextEntry = YES;

     }];

    

    UIAlertAction *okAction = [UIAlertAction

      actionWithTitle:@"OK"

            style:UIAlertActionStyleDefault

            handler:^(UIAlertAction *action)

            {

                UITextField *login = alertController.textFie cds.firstObject;

                UITextField *password = alertController.textFields.lastObject;

                NSLog(@"login = %@", login.text);

                NSLog(@"password = %@", password.text);

            }];


    okAction.enabled = NO;

    [alertController addAction:okAction];

  [self presentViewController:alertController animated:YES completion:nil];

}

- (void)alertTextFieldDidChange:(UITextField *)sender

{

    UIAlertController *alertController = (UIAlertController *)self.presentedViewController;

    if (alertController)

    {

        UITextField *login = alertController.textFields.firstObject;

        UIAlertAction *okAction = alertController.actions.lastObject;

        okAction.enabled = login.text.length > 2;

    }

}


참고로 TextField가 들어가게 되면 UIAlertControllerStyleActionSheet 타입을 이용할수 없습니다. 만약 이용하게 되면 아래와 같은 에러가 납니다.


*** Assertion failure in -[UIAlertController addTextFieldWithConfigurationHandler:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.30.14/UIAlertController.m:434                                                                               

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Text fields can only be added to an alert controller of style UIAlertControllerStyleAlert'                                                                                 




4. ActionSheet







UIAlertController *alertController = [UIAlertController

                alertControllerWithTitle:@"simpleAlert"

                      message:@"UIAlertControllerStyleAlert"

                          preferredStyle:UIAlertControllerStyleActionSheet];


UIAlertAction *cancelAction = [UIAlertAction

                               actionWithTitle:@"Cancel"

                               style:UIAlertActionStyleCancel

                               handler:^(UIAlertAction *action)

                               {

                                   NSLog(@"Cancel action");

                               }];


UIAlertAction *okAction = [UIAlertAction

                           actionWithTitle:@"OK"

                           style:UIAlertActionStyleDefault

                           handler:^(UIAlertAction *action)

                           {

                               NSLog(@"OK action");

                           }];


UIAlertAction *resetAction = [UIAlertAction

                              actionWithTitle:@"Reset"

                              style:UIAlertActionStyleDestructive

                              handler:^(UIAlertAction *action)

                              {

                                  NSLog(@"Reset action");

                              }];

[alertController addAction:resetAction];

[alertController addAction:cancelAction];

[alertController addAction:okAction];


[self presentViewController:alertController animated:YES completion:nil];




WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  0개가 달렸습니다.
secret





1.Build Phases -> Link Binary With Libraries 에서

CoreSpotlight.framework 추가하기.


2. 설명이미지



3. 코드 작성

#import <CoreSpotlight/CoreSpotlight.h>

#import <MobileCoreServices/MobileCoreServices.h>


-(void)addSportlightSearchItem:(NSString*)title desc:(NSString*)desc keywords:(NSArray*)keywords isHaveImageData:(BOOL)imageDataAvailable imageData:(NSData*)imageData {

    CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString*)kUTTypeImage];

    attributeSet.title = title;

    attributeSet.contentDescription = desc;

    attributeSet.keywords = keywords;

    if(imageDataAvailable) {

        attributeSet.thumbnailData = imageData;

    }else {

        attributeSet.thumbnailData = NULL;

    }


    CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:desc domainIdentifier:desc attributeSet:attributeSet];

    [[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:@[item] completionHandler:^(NSError * __nullable error) {

        if(!error) {

            NSLog(@"등록완료");

        }

    }];

}




WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  0개가 달렸습니다.
secret






1.Build Phases -> Link Binary With Libraries 에서

Contacts.framework 추가하기.


2. 코드 작성

-(void)loadContactList {

    CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];

    if( status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted)

    {

        NSLog(@"access denied");

    }

    else

    {

        //Create repository objects contacts

        CNContactStore *contactStore = [[CNContactStore alloc] init];

        

        //Select the contact you want to import the key attribute  ( https://developer.apple.com/library/watchos/documentation/Contacts/Reference/CNContact_Class/index.html#//apple_ref/doc/constant_group/Metadata_Keys )

        

        NSArray *keys = [[NSArray alloc]initWithObjects:CNContactGivenNameKey,CNContactFamilyNameKey,CNContactPhoneNumbersKey,CNContactImageDataKey, CNContactImageDataAvailableKey, nil];

        

        // Create a request object

        CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];

        request.predicate = nil;

        

        NSMutableArray  *contactArray = [[NSMutableArray alloc] init];

        [contactStore enumerateContactsWithFetchRequest:request

                                                  error:nil

                                             usingBlock:^(CNContact* __nonnull contact, BOOL* __nonnull stop)

         {

             // Contact one each function block is executed whenever you get

             NSString *phoneNumber = @"";

             if( contact.phoneNumbers)

                 phoneNumber = [[[contact.phoneNumbers firstObject] value] stringValue];

             

             NSLog(@"phoneNumber = %@", phoneNumber);

             NSLog(@"givenname = %@", contact.givenName);

             NSLog(@"familyname = %@", contact.familyName);

             

             [contactArray addObject:contact];

         }];

    }

}




WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  0개가 달렸습니다.
secret





Object-C Style

CGFloat red, green, blue, alpha;

red = 1.0f;

CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, alpha);


//or

CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor redColor] CGColor]);


Swift Style

let myUIColor = UIColor.purpleColor()

var r:CGFloat, g:CGFloat, b:CGFloat, a:CGFloat = 0

myUIColor.getRed(&r, green: &g, blue: &b, alpha: &a)

CGContextSetRGBStrokeColor(c, r, g, b, a)


// or

CGContextSetStrokeColorWithColor(myUIColor.CGColor);



WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  0개가 달렸습니다.
secret





단순히 모바일웹만 보여주는 앱은 리젝대상입니다.
꼭 앱으로 만들어야 하는 명확한 이유가 있어야 합니다. 


Apple의 리뷰 가이드라인이 있지만 하이브리드 앱, 웹앱에 대해서 명확한 규정은 있지 않기 때문에 Apple의 리뷰어가 누가 되는지에 따라서도 많이 달라집니다.
하지만 사람들의 경험으로 웹앱이 통과하기 위해 몇가지 암묵적인 규칙이 있다고 합니다.


1. 푸쉬를 써야한다(푸쉬 뿐만아니라, 연락처, 위치정보 등 앱으로만 수행할 수 있는 기능이 꼭 들어가야 합니다.)
2. 네트워크 연결이 되지 않은 상태에서 실행했을때도 뭔가가 있어야 한다
     - 처음시작할때 인트로가 필요할테고, 네트워크 접속 오류 메세지 화면이 꼭 필요합니다.
3. 웹으로는 회원가입이 불가능하게 해야한다.
     - 꼭 애플의 앱에서 회원가입이 가능하게 해야합니다. 
     - 기획자들이 웹의 PV를 높이기 위해 웹에서만 가입하게 한 서비스가 리젝 당한 경우가 있었습니다.


이 세가지 내용을 충족시켜준다면 무리없이 앱스토어에 등록이 될것으로 예상됩니다.
하지만 애플의 리뷰는 사람이 하니깐, 리뷰어에게 직접 메일이나 전화를 통해 위의 기능이 빠지더라도 꼭 애플의 앱스토어에 등록 되어야 할 이유를 설명하면 통과될 가능성도 있습니다.



WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  0개가 달렸습니다.
secret





iOS9으로 업데이트 되면서, HTTP로 접속을 하거나, 인증되지 않은 HTTPS

즉, 정상적인 SSL이 아닌 곳으로 이동이나 webView를 띄우면 아래와 같은 에러가 나게 됩니다.

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)


자세한 내용은 아래에 링크에서 확인하면 됩니다. 아래는 WWDC 2015 발표되었던 ATS에 관해 잘 설명하고 있는 링크입니다.


http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/



App Transport Security에 대해 자세히 설명해보겠습니다.



App Transport Security는?


App Transport Security(이후 ATS)는 iOS 9.0또는 OS X 10.11 이상 유효하며, 응용프로그램과 웹 서비스간의 안전한 연결을 위해 사용할 수 있습니다.


ATS가 활성화되면 HTTP를 통해 통신을 할 수 없습니다. 또한 Apple에서 권장하는 요구 사항을 충족하지 않는 연결은 강제로 연결 실패 처리됩니다. 예를 들어, Apple 권장 요구 사항을 충족하지 않는 Web 페이지를 WKWebView 에서 열려고 하면 페이지로드는 실패합니다. 그 때의 NSError의 내용은 다음과 같습니다.


Error Domain = NSURLErrorDomain Code = -1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection"UserInfo = {_ WKRecoveryAttempterErrorKey =, NSErrorFailingURLStringKey = http : //www.hoge.jp/, NSErrorFailingURLKey = http : //www.hoge.jp/, NSLocalizedDescription = The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.}



이와 같이 에러가 나므로 ATS를 사용하지 않으려면 info.plist에 예외설정을 해야합니다.



Info.plist에 예외 설정하는 방법



Info.plist용 키를 확인하면 다음과 같습니다.



NSAppTransportSecurity (Dictionary)

  • NSExceptionDomains (Dictionary)
    • NSAllowsArbitraryLoads (Bool)
    • <domain-name-for-exception-as-string> (Dictionary)
      • NSExceptionMinimumTLSVersion (String)
      • NSExceptionRequiresForwardSecrecy (Bool)
      • NSExceptionAllowsInsecureHTTPLoads (Bool)
      • NSRequiresCertificateTransparency (Bool)
      • NSIncludesSubdomains (Bool)
      • NSThirdPartyExceptionMinimumTLSVersion (String)
      • NSThirdPartyExceptionRequiresForwardSecrecy (Bool)
      • NSThirdPartyExceptionAllowsInsecureHTTPLoads (Bool)


2개의 방법이 있는데, 

전체의 HTTP를 허용하는 방법과 도메인마다 설정해서 허용하는 방법이 있습니다.


1. 전체의 HTTP를 허용하는 방법(비추천이라고 합니다)

<key> NSAppTransportSecurity </ key> <dict> <key> NSAllowsArbitraryLoads </ key> <true /> </ dict>





2. ATS를 제외시킬 도메인을 Info.plist에 기재하는 방법


<key> NSAppTransportSecurity </ key> <dict> <key> NSExceptionDomains </ key> <dict> <key> www.xxx.com </ key> <dict> <key> NSTemporaryExceptionAllowsInsecureHTTPLoads </ key> <true /> </ dict> </ dict> </ dict>





몇가지 더 설정을 알아 보겠습니다.


  • NSExceptionMinimumTLSVersion: TLS 최소 버전을 문자열로 입력합니다. 아래 값들 중 하나를 넣을 수 있거나 생략할 수 있습니다.
    • TLSv1.0
    • TLSv1.1
    • TLSv1.2 (생략할 경우의 기본값)
  • NSExceptionRequiresForwardSecrecy: forward secrecy 라는 비밀키 암호화와 관련된 설정입니다.
  • NSExceptionAllowsInsecureHTTPLoads: HTTPS(SSL) 연결이 아니더라도 통신을 허용할 것인가를 YES 혹은 NO로 설정 할 수 있습니다.
  • NSIncludesSubdomains: 이 사이트의 하부도메인들에도 이 설정을 적용할 것인가를 YES 혹은 NO로 설정 할 수 있습니다.
  • NSThirdPartyExceptionMinimumTLSVersion: 써드파티 TLS 버전을 입력 할 수 있습니다.
  • NSThirdPartyExceptionRequiresForwardSecrecy: 역시 써드파티 Forward Secrecy 설정할 수 있습니다.
  • NSThirdPartyExceptionAllowsInsecureHTTPLoads: 역시나 써드파티 HTTPS 연결 강제를 설정합니다.




아래는 다음과 관련된 애플의 링크입니다.

https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html





WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  1 , 댓글  2개가 달렸습니다.
  1. 안녕하세요- ^^ 글 잘 보았습니다.
    제가 필요한 내용인것 같은데 혹시 퍼가도 될런지요?
  2. spicytomato 2018.02.07 11:36
    감사합니다. 유익했습니다. ㅎㅎ
secret





어플리케이션의 설정창을 코드로 띄우는 방법


카메라가 꼭 필요한 앱인데, 카메라 허용을 하지 않았을 경우, 

Alert창으로 "일반>설정에서 카메라를 허용해주세요" 보단,

아래의 코드를 이용해 직접 설정창으로 이동하게 해주면 훨씬 편리하게 기능을 수락하도록 유도하기 편할것 같다.


iOS8부터 사용이 가능하다.


if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {

    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];

    [[UIApplication sharedApplication] openURL:url];

}



이 코드를 실행하면, 이 앱의 설정으로 바로 이동한다



WRITTEN BY
블로blow
iOS 개발자 생활이야기

트랙백  0 , 댓글  0개가 달렸습니다.
secret