ios objective c - http 네트워크 통신 1


이번포스팅에서는 ios 네트워크 통신에 대해서 간단히 정리한다. 

상황

클라이언트: ios 폰

중간에서 컨트롤하는 녀석: 퍼사드

서버: json 데이터형식


ios 네트워크에서 퍼사드 디자인 패턴을 알아본다. 퍼사드란 클라이언트와 서버 사이에 위치하는 녀석으로 클라이언트(앱)의 수정과 배포를 획기적으로 수월하게 해주는 녀석이다. 아래 코드는 날씨어플 버젼 1에서 버젼2로 코드수정을 했고, 주식어플 버젼1에서 버젼2로 코드 수정을하고 업데이트를 했다. 퍼사드 패턴을 써서 얼마나 쉽고 간결하게 처리를 해줬는지 확인했으면 ok. 코드에 주석을 달아 놓았다.


네비게이션


appdelegate.h

//

//  AppDelegate.h

//  1NetWork

//

//  Created by service on 2018. 11. 13..

//  Copyright © 2018년 service. All rights reserved.

//


#import <UIKit/UIKit.h>


//탭바 컨트롤러 델리게이트 추가해주기

@interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>


//윈도우 뷰

@property (strong, nonatomic) UIWindow *window;


//탭바

@property (strong, nonatomic) UITabBarController *tabBarController;


//버젼별 url을 할당받을 객체

@property (strong, nonatomic) NSURL                    *urlForWeatherVersion1;

@property (strong, nonatomic) NSURL                    *urlForWeatherVersion2;

@property (strong, nonatomic) NSURL                    *urlForStockVersion1;

@property (strong, nonatomic) NSURL                    *urlForStockVersion2;


@end



appdelegate.m

//

//  AppDelegate.m

//  1NetWork

//

//  Created by service on 2018. 11. 13..

//  Copyright © 2018년 service. All rights reserved.

//


#import "AppDelegate.h"

#import "WeatherViewController.h"

#import "StockViewController.h"


@interface AppDelegate ()

@end


@implementation AppDelegate


@synthesize window = _window;

@synthesize tabBarController = _tabBarController;

@synthesize urlForStockVersion1 = _urlForStockVersion1;

@synthesize urlForStockVersion2 = _urlForStockVersion2;

@synthesize urlForWeatherVersion1 = _urlForWeatherVersion1;

@synthesize urlForWeatherVersion2 = _urlForWeatherVersion2;


//앱이 실행될때 초기화 작업해주기!

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //윈도우 초기화 해주기

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    

    //테이블뷰컨트롤러 초기화 해서 네비게이션 컨트롤러에 넣어주기

    UIViewController *weatherVC = [[UINavigationController alloc] initWithRootViewController:[[WeatherViewController alloc]init]];

    UIViewController *stockVC = [[UINavigationController alloc] initWithRootViewController:[[StockViewController alloc]init]];

    

    //탭바를 초기화 하고 탭바에 위에서 만든 두가지 컨트롤러를 넣어주었다.

    self.tabBarController = [[UITabBarController alloc]init];

    self.tabBarController.viewControllers = [NSArray arrayWithObjects:weatherVC,stockVC, nil];

    

    //윈도우의 최상위 뷰는 탭바다.

    self.window.rootViewController = self.tabBarController;

    

    //윈도우를 보여줘라.

    [self.window makeKeyAndVisible];

    

    NSLog(@"didFinishLaunchingWithOptions 호출되었습니다.");

    

    

    //인디케이터 로직 호출 해야됨 ~~!

    

    [self loadServiceLocator];

    return YES;

}


//서비스 로케이터 호출

-(void)loadServiceLocator {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        NSError *error = nil;

        

        //url을 넣으면 해당 데이터를 받는다

        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://api.myjson.com/bins/18ckhy"]

                                             options:NSDataReadingUncached

                                               error:&error];

        //성공

        if (error == nil) {

            //json 데이터 파싱해서 dictionary에 할당.

            NSDictionary *locationDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];

            

                if (error == nil){

                    //findURLForServiceNamed() 메서드에 각각의 파라미터를 넣으면 해당 url을 리턴해준다 : 위에서 받은 locationDictionary 데이터 넣어줌.

                    self.urlForWeatherVersion1 = [self findURLForServiceNamed:@"weather" versoin:1 inDictionary:locationDictionary];

                    self.urlForWeatherVersion2 = [self findURLForServiceNamed:@"weather" versoin:2 inDictionary:locationDictionary];

                    self.urlForStockVersion1 = [self findURLForServiceNamed:@"stockQuote" versoin:1 inDictionary:locationDictionary];

                    self.urlForStockVersion2 = [self findURLForServiceNamed:@"stockQuote" versoin:2 inDictionary:locationDictionary];


                }else{

                    //에러발생:url연결 되었지만 파싱은 안될때

                        NSLog(@" service locator를 파싱할 수 없습니다!. error: %@", error);

                        dispatch_async(dispatch_get_main_queue(), ^{

                            [[[UIAlertView alloc] initWithTitle:@"error"

                                                        message:@"서비스 로케이터를 파싱할수 없습니다."

                                                       delegate:nil

                                              cancelButtonTitle:@"ok"

                                              otherButtonTitles:nil]show];

                        });

                }

            

        }else{

            //url연결 되지 않을때

            NSLog(@"서비스 로케이터를 로드할 수 없습니다. error: %@", error);

            dispatch_async(dispatch_get_main_queue(), ^{

                [[[UIAlertView alloc] initWithTitle:@"Error"

                                            message:@"서비스 로케이터를 로드할 수 없습니다.  url 업데이트해야 되는거 아닌가요?"

                                           delegate:nil

                                  cancelButtonTitle:@"OK"

                                  otherButtonTitles:nil] show];

            });

        }

    });

}



-(NSURL*) findURLForServiceNamed:(NSString*)serviceName versoin:(NSInteger)versionNumber inDictionary:(NSDictionary*)locatorDictionary{

    //json형식의 파일에서 배열형식의 데이터를 읽어 온다.

    NSArray *service = [locatorDictionary objectForKey:@"services"];

    

    //딕셔너리 형식의 데이터 추출

    for (NSDictionary *serviceInfo in service) {

        NSString *name = [serviceInfo objectForKey:@"name"];

        NSInteger version = [[serviceInfo objectForKey:@"version"] intValue];

        

        

        //추출된 데이터와 파라미터로 넘어온 이름, 버젼을 비교해서 같으면 해당 url을 넘겨준다.

        if ([name caseInsensitiveCompare:serviceName] == NSOrderedSame && version == versionNumber) {

            return [NSURL URLWithString:[serviceInfo objectForKey:@"url"]];

        }

    }

    

    return nil;

}



- (void)applicationDidBecomeActive:(UIApplication *)application {

    //서비스 로케이터 로드

     NSLog(@"applicationDidBecomeActive 호출되었습니다.");

    [self loadServiceLocator];

}



- (void)applicationWillResignActive:(UIApplication *)application {

    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.

    // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.

}



- (void)applicationDidEnterBackground:(UIApplication *)application {

    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.

    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

}



- (void)applicationWillEnterForeground:(UIApplication *)application {

    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.

}





- (void)applicationWillTerminate:(UIApplication *)application {

    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.

}



@end


weathercontroller.h

//

//  WeatherViewController.h

//  1NetWork

//

//  Created by service on 2018. 11. 13..

//  Copyright © 2018년 service. All rights reserved.

//


#import <UIKit/UIKit.h>


@interface WeatherViewController : UITableViewController

@end


weathercontroller.m

//

//  WeatherViewController.m

//  1NetWork

//

//  Created by service on 2018. 11. 13..

//  Copyright © 2018년 service. All rights reserved.

//


#import "WeatherViewController.h"

#import "AppDelegate.h"


@interface WeatherViewController (){

    UISegmentedControl      *versionSelector;

    NSString                *v1_city;

    NSString                *v1_state;

    NSString                *v1_temperature;

    

    NSString            *v2_city;

    NSString            *v2_state;

    NSInteger            v2_temperature;

    NSString            *v2_conditions;

    

}


-(void)versionChanged:(id)sender;

-(void)loadVersion1Weather;

@end




@implementation WeatherViewController


//초기화 작업

-(id)init{

    self = [super initWithStyle:UITableViewStyleGrouped];

    if(self){

        //탭바의 아이템과 이미지를 초기화 하고 넘겨준다.

        self.tabBarItem.title = NSLocalizedString(@"Weather", @"Weather");

        self.tabBarItem.image = [UIImage imageNamed:@"weather"];

    }

    return self;

}



- (void)viewDidLoad {

    [super viewDidLoad];

    

    NSLog(@"WeatherViewController 호출되었습니다.");

    

    //version selector 설정 : (제일 상단에 네비게이션 바를 셋팅한다.)

    versionSelector = [[UISegmentedControl alloc] initWithItems:

                       [NSArray arrayWithObjects:

                        NSLocalizedString(@"Version 1", @"Version 1"),

                        NSLocalizedString(@"Veresion 2", @"Version 2"),nil]];

    versionSelector.segmentedControlStyle = UISegmentedControlStyleBar;

    versionSelector.selectedSegmentIndex = 0;

    

    [versionSelector addTarget:self

                        action:@selector(versionChanged:)

              forControlEvents:UIControlEventValueChanged];

    self.navigationItem.titleView = versionSelector;

}



//버젼 변경

-(void)versionChanged:(id)sender{

    [self.tableView reloadData];

}


//weather1 데이터 로드

-(void)loadVersion1Weather{

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication]delegate];

        

        if (appDelegate.urlForStockVersion1 != nil) {

            NSError *error = nil;

            //appDelegate의 urlForWeatherVersion1 변수가 해당 url을 가지고 있음.

            //넣어주고 데이터를 받는다.

            NSData *data = [NSData dataWithContentsOfURL:appDelegate.urlForWeatherVersion1

                                                 options:NSDataReadingUncached

                                                   error:&error];

            if (error == nil) {

                //딕셔너리에 넣어준다.

                NSDictionary *weatherDictionary = [NSJSONSerialization JSONObjectWithData:data

                                                                                  options:NSJSONReadingMutableLeaves

                                                                                    error:&error];

                if (error == nil) {

                    //딕셔너리에서 키값을 이용해서 데이터를 하나씩 추출한다.

                    v1_city = [weatherDictionary objectForKey:@"city"];

                    v1_state = [weatherDictionary objectForKey:@"state"];

                    v1_temperature = [weatherDictionary objectForKey:@"currentTemperature"];

                    

                    //update the table on the ui thread

                    dispatch_async(dispatch_get_main_queue(), ^{

                        //테이블을 리로드 해준다!

                        [self.tableView reloadData];

                    });

                    

                }else{

                    [self showParseError];

                }

            } else{

                [self showLoadError];

            }

        }else{

             [self showLoadError];

        }

        

    });

}


- (void)loadVersion2Weather {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];

        

        if (appDelegate.urlForWeatherVersion2 != nil) {

            NSError *error = nil;

            NSData *data = [NSData dataWithContentsOfURL:appDelegate.urlForWeatherVersion2

                                                 options:NSDataReadingUncached

                                                   error:&error];

            

            if (error == nil) {

                NSDictionary *weatherDictionary = [NSJSONSerialization JSONObjectWithData:data

                                                                                  options:NSJSONReadingMutableLeaves

                                                                                    error:&error];

                

                if (error == nil) {

                    v2_city = [weatherDictionary objectForKey:@"city"];

                    v2_state = [weatherDictionary objectForKey:@"state"];

                    v2_temperature = [[weatherDictionary objectForKey:@"currentTemperature"] intValue];

                    v2_conditions = [weatherDictionary objectForKey:@"currentConditions"];

                    

                    // update the table on the UI thread

                    dispatch_async(dispatch_get_main_queue(), ^{

                        [self.tableView reloadData];

                    });

                    

                } else {

                    NSLog(@"Unable to parse weather because of error: %@", error);

                    [self showParseError];

                }

                

            } else {

                [self showLoadError];

            }

            

        } else {

            [self showLoadError];

        }

    });

}


//로드할때 에러발생

-(void)showLoadError

{

    dispatch_async(dispatch_get_main_queue(), ^{

       [[[UIAlertView alloc] initWithTitle:@"error"

                                   message:@"unable to load weather data"

                                  delegate:nil

                         cancelButtonTitle:@"ok"

                         otherButtonTitles:nil] show];

        

    });

}


//파싱할때 에러 발생

-(void)showParseError

{

    dispatch_async(dispatch_get_main_queue(), ^{

        [[[UIAlertView alloc] initWithTitle:@"Error"

                                    message:@"Unable to parse weather data."

                                   delegate:nil

                          cancelButtonTitle:@"OK"

                          otherButtonTitles:nil] show];

    });

}



- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


#pragma mark - Table view data source


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

#warning Incomplete implementation, return the number of sections

    return 2;

}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

#warning Incomplete implementation, return the number of rows

    

    //색션이 0일때

    if (section == 0) {

        //세그먼트가 0이면 행의 갯수는 3

        if (versionSelector.selectedSegmentIndex == 0) {

            return 3;

        } else if(versionSelector.selectedSegmentIndex == 1) {

            return 4;

        }

        

        //색션이 2일때 refresh button

    } else if(section == 1){

        return 1;

    }

    

    return 0;

}



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = nil;

    

    //첫번째 섹션일때

    if(indexPath.section == 0){

        static NSString *infoCellId = @"infoCell";

        cell = [tableView dequeueReusableCellWithIdentifier:infoCellId];

        

        if (cell == nil) {

            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:infoCellId];

            cell.selectionStyle = UITableViewCellSelectionStyleNone;

        }

        

        //첫번째 섹션에 첫번째 세그먼트 : version1

        if(versionSelector.selectedSegmentIndex == 0){

            if (indexPath.row == 0) {

                cell.textLabel.text = NSLocalizedString(@"City", @"City");

                cell.detailTextLabel.text = v1_city;

            } else if(indexPath.row == 1){

                cell.textLabel.text = NSLocalizedString(@"State", @"State");

                cell.detailTextLabel.text = v1_state;

            }else if(indexPath.row == 2){

                cell.textLabel.text = NSLocalizedString(@"Temperature", @"Temperature");

                cell.detailTextLabel.text = [v1_temperature stringByAppendingString:@"\u00B0F"];

            }

        }

        //첫번째 섹션에 두번째 세그번트 : version2

     else if(versionSelector.selectedSegmentIndex == 1){

        

            // city

        if (indexPath.row == 0) {

            cell.textLabel.text = NSLocalizedString(@"City", @"City");

            cell.detailTextLabel.text = v2_city;

            

            // state

        } else if (indexPath.row == 1) {

            cell.textLabel.text = NSLocalizedString(@"State", @"State");

            cell.detailTextLabel.text = v2_state;

            

            // temperature

        } else if (indexPath.row == 2) {

            cell.textLabel.text = NSLocalizedString(@"Temperature", @"Temperature");

            cell.detailTextLabel.text = [NSString stringWithFormat:@"%i\u00B0F", v2_temperature];

            

            // conditions

        } else if (indexPath.row == 3) {

            cell.textLabel.text = NSLocalizedString(@"Conditions", @"Conditions");

            cell.detailTextLabel.text = v2_conditions;

        }

        

     }

        

        //두번째 섹션

    }else if(indexPath.section == 1){

        static NSString *buttonCellId = @"buttonCell";

        cell = [tableView dequeueReusableCellWithIdentifier:buttonCellId];

        if (cell == nil) {

            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:buttonCellId];

        }

        cell.textLabel.text = NSLocalizedString(@"Refresh Richmond, VA", @"Refresh Richmond, VA");

        cell.textLabel.textAlignment = UITextAlignmentCenter;

        cell.textLabel.textColor = [UIColor blueColor];

    }

    

    return cell;

}


-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    

    if (indexPath.section == 1) {

        

        //version1

        if (versionSelector.selectedSegmentIndex == 0) {

        

            [self loadVersion1Weather];

        //version2

        }else if(versionSelector.selectedSegmentIndex == 1){

            [self loadVersion2Weather];

        }

    }

}


@end


stock.h

//

//  StockViewController.h

//  1NetWork

//

//  Created by service on 2018. 11. 13..

//  Copyright © 2018년 service. All rights reserved.

//


#import <UIKit/UIKit.h>


@interface StockViewController : UITableViewController

@end



stock.m

//

//  StockViewController.m

//  1NetWork

//

//  Created by service on 2018. 11. 13..

//  Copyright © 2018년 service. All rights reserved.

//


#import "StockViewController.h"

#import "AppDelegate.h"

@interface StockViewController (){

    UISegmentedControl *versionSelector;

    NSString            *v1_symbol;

    NSString            *v1_name;

    NSNumber            *v1_currentPrice;

    

    NSString            *v2_symbol;

    NSString            *v2_name;

    NSNumber            *v2_currentPrice;

    NSNumber            *v2_openingPrice;

    NSString            *v2_percentageChange;

}

- (void)versionChanged:(id)sender;

- (void)loadVersion1Stock;

- (void)loadVersion2Stock;

@end


@implementation StockViewController



-(id)init{

    self = [super initWithStyle:UITableViewStyleGrouped];

    if(self){

        //탭바의 아이템과 이미지를 초기화 하고 넘겨준다.

        self.tabBarItem.title = NSLocalizedString(@"Stock Quote", @"Stock Quote");

        self.tabBarItem.image = [UIImage imageNamed:@"stock"];

        

        //변수 초기화

        v1_symbol = @"";

        v1_name = @"";

        v1_currentPrice = nil;

        

        v2_symbol = @"";

        v2_name = @"";

        v2_currentPrice = nil;

        v2_openingPrice = nil;

        v2_percentageChange = @"";

        

    }

    return self;

}


- (void)viewDidLoad {

    [super viewDidLoad];

    NSLog(@"StockViewController 호출되었습니다.");

    // Uncomment the following line to preserve selection between presentations.

    // self.clearsSelectionOnViewWillAppear = NO;

    

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.

    // self.navigationItem.rightBarButtonItem = self.editButtonItem;

}


- (void)versionChanged:(id)sender {

    [self.tableView reloadData];

}


- (void)loadVersion1Stock {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

       AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];

        if (appDelegate.urlForStockVersion1 != nil) {

            NSError *error = nil;

            NSData *data = [NSData dataWithContentsOfURL:appDelegate.urlForStockVersion1

                                                 options:NSDataReadingUncached

                                                   error:&error];

            

            if (error == nil) {

                NSDictionary *stockDictionary = [NSJSONSerialization JSONObjectWithData:data

                                                                                options:NSJSONReadingMutableLeaves

                                                                                  error:&error];

                

                if (error == nil) {

                    v1_symbol = [stockDictionary objectForKey:@"symbol"];

                    v1_name = [stockDictionary objectForKey:@"name"];

                    v1_currentPrice = [NSNumber numberWithFloat:[[stockDictionary objectForKey:@"currentPrice"] floatValue]];

                    

                    // update the table on the UI thread

                    dispatch_async(dispatch_get_main_queue(), ^{

                        [self.tableView reloadData];

                    });

                    

                } else {

                    NSLog(@"Unable to parse stock quote because of error: %@", error);

                    [self showParseError];

                }

                

            } else {

                [self showLoadError];

            }

            

        } else {

            [self showLoadError];

        }

        

    

    });

}


- (void)loadVersion2Stock {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];


        if (appDelegate.urlForStockVersion2 != nil) {

            NSError *error = nil;

            NSData *data = [NSData dataWithContentsOfURL:appDelegate.urlForStockVersion2

                                                 options:NSDataReadingUncached

                                                   error:&error];


            if (error == nil) {

                NSDictionary *stockDictionary = [NSJSONSerialization JSONObjectWithData:data

                                                                                options:NSJSONReadingMutableLeaves

                                                                                  error:&error];


                if (error == nil) {

                    v2_symbol = [stockDictionary objectForKey:@"symbol"];

                    v2_name = [stockDictionary objectForKey:@"name"];

                    v2_openingPrice = [stockDictionary objectForKey:@"openingPrice"];

                    v2_currentPrice = [stockDictionary objectForKey:@"currentPrice"];

                    v2_percentageChange = [stockDictionary objectForKey:@"percentageChange"];


                    // update the table on the UI thread

                    dispatch_async(dispatch_get_main_queue(), ^{

                        [self.tableView reloadData];

                    });


                } else {

                    NSLog(@"Unable to parse stock quote because of error: %@", error);

                    [self showParseError];

                }


            } else {

                [self showLoadError];

            }


        } else {

            [self showLoadError];

        }

    });

}

- (void)showLoadError {

    // inform the user on the UI thread

    dispatch_async(dispatch_get_main_queue(), ^{

        [[[UIAlertView alloc] initWithTitle:@"Error"

                                    message:@"Unable to load stock data."

                                   delegate:nil

                          cancelButtonTitle:@"OK"

                          otherButtonTitles:nil] show];

    });

}


- (void)showParseError {

    // inform the user on the UI thread

    dispatch_async(dispatch_get_main_queue(), ^{

        [[[UIAlertView alloc] initWithTitle:@"Error"

                                    message:@"Unable to parse stock data."

                                   delegate:nil

                          cancelButtonTitle:@"OK"

                          otherButtonTitles:nil] show];

    });

}


- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


#pragma mark - Table view data source


//색션 개수

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {


    return 2;

}



- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {


    // stock info

    if (section == 0) {

        

        // version 1 has 3 cells of information

        if (versionSelector.selectedSegmentIndex == 0) {

            return 3;

            

            // version 2 has 5 cells of information

        } else if (versionSelector.selectedSegmentIndex == 1) {

            return 5;

        }

        

        // refresh button

    } else if (section == 1) {

        return 1;

    }

    return 0;

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = nil;

    NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];

    currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle;

    

    // info group

    if (indexPath.section == 0) {

        static NSString *infoCellId = @"infoCell";

        cell = [tableView dequeueReusableCellWithIdentifier:infoCellId];

        

        if (cell == nil) {

            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:infoCellId];

            cell.selectionStyle = UITableViewCellSelectionStyleNone;

        }

        

        // version 1

        if (versionSelector.selectedSegmentIndex == 0) {

            

            // symbol

            if (indexPath.row == 0) {

                cell.textLabel.text = @"Symbol";

                cell.detailTextLabel.text = v1_symbol;

                

                // name

            } else if (indexPath.row == 1) {

                cell.textLabel.text = @"Name";

                cell.detailTextLabel.text = v1_name;

                

                // price

            } else if (indexPath.row == 2) {

                cell.textLabel.text = @"Price";

                cell.detailTextLabel.text = [currencyFormatter stringFromNumber:v1_currentPrice];

            }

            

            

            // version 2

        } else if (versionSelector.selectedSegmentIndex == 1) {

            

            // symbol

            if (indexPath.row == 0) {

                cell.textLabel.text = @"Symbol";

                cell.detailTextLabel.text = v2_symbol;

                

                // name

            } else if (indexPath.row == 1) {

                cell.textLabel.text = @"Name";

                cell.detailTextLabel.text = v2_name;

                

                // opening price

            } else if (indexPath.row == 2) {

                cell.textLabel.text = @"Opening Price";

                cell.detailTextLabel.text = [currencyFormatter stringFromNumber:v2_openingPrice];

                

                // current price

            } else if (indexPath.row == 3) {

                cell.textLabel.text = @"Current Price";

                cell.detailTextLabel.text = [currencyFormatter stringFromNumber:v2_currentPrice];

                

                // percentage change

            } else if (indexPath.row == 4) {

                cell.textLabel.text = @"Percent Change";

                cell.detailTextLabel.text = v2_percentageChange;

            }

        }

        

        // button group

    } else if (indexPath.section == 1) {

        static NSString *buttonCellId = @"buttonCell";

        cell = [tableView dequeueReusableCellWithIdentifier:buttonCellId];

        

        if (cell == nil) {

            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:buttonCellId];

        }

        

        cell.textLabel.text = @"Refresh AAPL";

        cell.textLabel.textAlignment = UITextAlignmentCenter;

        cell.textLabel.textColor = [UIColor blueColor];

    }

    

    return cell;

}



#pragma mark - UITableViewDelegate


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    

    if (indexPath.section == 1) {

        

        // version 1

        if (versionSelector.selectedSegmentIndex == 0) {

            [self loadVersion1Stock];

            

            // version 2

        } else if (versionSelector.selectedSegmentIndex == 1) {

            [self loadVersion2Stock];

        }

    }

}


@end


중간에서 조정해주는 퍼사드! (url은 직접 로컬에서 서버를 돌려서 실행해본다.)


{

  "services": [

    {

      "name": "stockQuote",

      "url": "https://api.myjson.com/bins/u25za",

      "version": 1

    },

    {

      "name": "stockQuote",

      "url": "https://api.myjson.com/bins/1cilqe",

      "version": 2

    },

    {

      "name": "weather",

      "url": "https://api.myjson.com/bins/pap52",

      "version": 1

    },

    {

      "name": "weather",

      "url": "https://api.myjson.com/bins/pw4qu",

      "version": 2

    }

  ]

}



날씨 버젼1 데이터

{

  "city": "Richmond",

  "state": "Virginia",

  "currentTemperature": "63"

}

날씨 버젼2 데이터

{

  "city": "Richmond",

  "state": "Virginia",

  "currentTemperature": 63,

  "currentConditions": "Mostly Cloudy"

}

주식 버젼1 데이터

{

  "symbol": "AAPL",

  "name": "Apple Inc.",

  "currentPrice": "-2.92%"

}

주식 버젼2 데이터

{

  "symbol": "AAPL",

  "name": "Apple Inc.",

  "openingPrice": 545.31,

  "currentPrice": 530.12,

  "percentageChange": "-2.92%"

}


잘돌아가는지 확인! 










ios objective c 코어오디오 다루기 6 - 오디오 파일 변환


//

//  main.m

//  6

//

//  Created by service on 2018. 11. 12..

//  Copyright © 2018년 service. All rights reserved.

//


/*

 - mp3 나 aac 와 같은 엔코드된 파일을 .caf 컨테이너 파일로 변환하기

 - 하나의 파일에서 데이터를 읽고, 메모리의 일부 패킷을 변환하고, 결과를 다른 파일에 쓰기

 - 오디오 파일 서비스 사용함.

 

 */


#import <Foundation/Foundation.h>

#import <AudioToolbox/AudioToolbox.h>

#import <CoreServices/CoreServices.h>

#define kInputFileLocation    CFSTR("/Users/service/Desktop/test_cbr.mp3")


typedef struct MyAudioConverterSettings

{

    AudioStreamBasicDescription       inputFormat;                  //입력 스트림의 데이터 형식

    AudioStreamBasicDescription       outputFormat;                 //출력 스트림의 데이터 형식

    

    AudioFileID                     inputFile;                      //입력 파일의 참조를 추적함

    AudioFileID                     outputFile;                     //출력 파일의 참조를 추적함

    

    UInt64                          inputFilePacketIndex;           //읽고 있는 현재 패킷의 수

    UInt64                          inputFilePacketCount;           //입력 파일의 전체 패킷 수

    UInt32                          inputFilePacketMaxSize;         //입력 패킷의 최대 크기

    AudioStreamPacketDescription    *inputFilePacketDescription;      //데이터를 읽을 때마다 입력 파일에서 읽어들이는 배열의 포인터

    void *sourceBuffer;

    

} MyAudioConverterSettings;


OSStatus MyAudioConverterCallback

(

    AudioConverterRef inAudioConverter,

    UInt32 *ioDataPacketCount,

    AudioBufferList *ioData,

    AudioStreamPacketDescription **outDataPacketDescription,

 void *inUserdata

);


void Convert(MyAudioConverterSettings *mySetting);








//에러 처리기 : 리턴값이 0이 아니면 에러메시지를 출력하고, 프로그램 종료

static void CheckResult(OSStatus result, const char *operation)

{

    if (result == noErr) {

        return;

    }

    

    char errorString[20];

    *(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(result);

    if (isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4])) {

        errorString[0] = errorString[5] = '\'';

        errorString[6] = '\0';

    } else

        // 포멧이 정수 값일때

        sprintf(errorString, "%d", (int)result);

    

    fprintf(stderr, "에러발생: Error: %s (%s)\n", operation, errorString);

    

    exit(1);

    

    

}

#pragma mark - audio converter -



OSStatus MyAudioConverterCallback(AudioConverterRef inAudioConverter,

                                  UInt32 *ioDataPacketCount,

                                  AudioBufferList *ioData,

                                  AudioStreamPacketDescription **outDataPacketDescription,

                                  void *inUserData){


    //사용자 데이터 객체를 myaudioConverterSettings 구조체로 형변환

    MyAudioConverterSettings *audioConverterSettings = (MyAudioConverterSettings *)inUserData;

    

    //실패했을 때 초기화. 오디오 버퍼를 0으로 초기화 할 수 있다

    ioData -> mBuffers[0].mData = NULL;

    ioData -> mBuffers[0].mDataByteSize = 0;

    

    

    

    //얼마나 많은 패킷을 읽을 지 계산

    //ioDataPacketCount는 최초에 Convert(0)에서 계산한 packetPerBuffer이지만, 입력의 마지막에 도달할 때 이것보다 적은 패킷이 남아 있다.

    //남아 있는 패킷의 수로 ioDataPacketCount를 재설정한다.

    //입력파일에서 읽을 수 있는 패킷의 수 결정

    //무엇이 남았는지 읽는다.

    

    if (audioConverterSettings->inputFilePacketIndex + *ioDataPacketCount > audioConverterSettings-> inputFilePacketCount) {

        *ioDataPacketCount = audioConverterSettings->inputFilePacketCount - audioConverterSettings->inputFilePacketIndex;

    }

        

        if(*ioDataPacketCount == 0 ){

            return noErr;

        }

        

        //읽을 데이터가 있는 경우 오디오 데이터를 받기 위한 버퍼를 준비할 필요가 있다.

        //설정 구조체가 가리키는 오래된 버퍼를 free 하고 (오래된 버퍼의 데이터는 이미 소비됐기 때문에)

        //읽으려고 하는 패킷의 수를 저장할 충분히 큰 새로운 버퍼를 할당한다.

        //채우고 변환할 버퍼 할당

        if (audioConverterSettings->sourceBuffer !=NULL) {

            free(audioConverterSettings->sourceBuffer);

            audioConverterSettings->sourceBuffer = NULL;

        }

        

        audioConverterSettings->sourceBuffer = (void *)calloc(1, *ioDataPacketCount * audioConverterSettings->inputFilePacketMaxSize);

        

        //할당된 버퍼를 가지고 소스 파일에서 읽은 패킷을 그 버퍼로 쓸 준비가 됐다.

        UInt32 outByteCount = 0;

        OSStatus result = AudioFileReadPackets(audioConverterSettings->inputFile,

                                               true,

                                               &outByteCount,

                                               audioConverterSettings->inputFilePacketDescription,

                                               audioConverterSettings->inputFilePacketIndex,

                                               ioDataPacketCount,

                                               audioConverterSettings->sourceBuffer);

        

        #ifdef MAC_OS_X_VERSION_10_7

                if (result == kAudioFileEndOfFileError && *ioDataPacketCount) result = noErr;

        #else

                if (result == eofErr && *ioDataPacketCount) result = noErr;

        #endif

                else if (result != noErr) return result;

        

        

    //읽은 것의 결과를 가지고 소스 파일 위치와 audiobuffer 멤버를 업데이트

     audioConverterSettings->inputFilePacketIndex += *ioDataPacketCount;

        

        ioData->mBuffers[0].mData = audioConverterSettings->sourceBuffer;

        ioData->mBuffers[0].mDataByteSize = outByteCount;

        

        if (outDataPacketDescription)

            *outDataPacketDescription = audioConverterSettings->inputFilePacketDescription;

        

        return result;

    

    

}



//최종적으로 audioFileWritePackets()로 전달될 수 있는 패킷 버퍼를 채우는 audioconverterfillcomplexbuffer()를 반복적으로 호출하는 동작을 한다.

//오디오 변환기 생성

void Convert(MyAudioConverterSettings *mySettings){

    

    AudioConverterRef audioConverter;

    CheckResult(

                AudioConverterNew(&mySettings ->inputFormat, &mySettings ->outputFormat, &audioConverter),

                "audioConverterNew failed");

    

    //패킷정보 배열을 얼마나 크게 할당해야 하는지 계산

    //형식이 가변비트율인지 여부, 버퍼가 적어도 하나의 패킷을 저장할 만큼 충분히 큰지 등이 해당한다.

    //패킷 버퍼 배열 크기와 가변 비트율 데이터의 버퍼당 패킷 수를 결정

    

    UInt32 packetsPerBuffer = 0;

    UInt32 outputBufferSize = 32 * 1024;

    UInt32 sizePerpacket = mySettings -> inputFormat.mBytesPerPacket;

    

    if(sizePerpacket == 0)

    {

        UInt32 size = sizeof(sizePerpacket);

        CheckResult(

                    AudioConverterGetProperty(audioConverter, kAudioConverterPropertyMaximumOutputPacketSize, &size, &sizePerpacket),

                    "couldn't get kAudioConverterPropertyMaximumOutputPacketsSize");

    

        // make sure the buffer is large enough to hold at least one packet

        if (sizePerpacket > outputBufferSize) {

            outputBufferSize = sizePerpacket;

            packetsPerBuffer = outputBufferSize / sizePerpacket;

            mySettings -> inputFilePacketDescription = (AudioStreamPacketDescription*)malloc(sizeof(AudioStreamPacketDescription) * packetsPerBuffer);

        }

        

    }else

    {   //참고:

        //가변 비트율 데이터는 mBytesPerPacket == 0을 가진 asbd에 의해서 표시됨

        //이 경우 변환기에서 최대 패킷 크기를 가진다. 그리고 그 값을 기본 버퍼 크기 (32kb)와 비교하고 버퍼에 적어도 하나의 패킷을 저장할 수 있다고 확신하는 둘 중에 큰것을 취한다.

        //그 후에 패킷 정보 배열을 처리해야 한다.

        //버퍼에 얼마나 많은 패킷이 들어갈지 결정할 수 있고, 그만큼의 audioStreamPacketDescriptions를 저장할 수 있는 충분한 공간을 할당한다.

        //고정 비트율 데이터의 버퍼당 패킷 결정하기

        packetsPerBuffer = outputBufferSize / sizePerpacket;

    }

    

    

    

    //오디오 변환 버퍼를 위해 메모리 할당

    UInt8 *outputBuffer = (UInt8 *)malloc(sizeof(UInt8) * outputBufferSize);

    

    //데이터를 변환하고 쓰기위한 반복문

    //출력파일의 어디에 있는지 기억하기 위한 카운터.

    UInt32 outputFilePacketPosition = 0;

    while (1) {

        


        //변환된 데이터를 받기 위해서 AudioBufferList 준비하기

        AudioBufferList convertedData;

        convertedData.mNumberBuffers = 1;

        convertedData.mBuffers[0].mNumberChannels = mySettings->inputFormat.mChannelsPerFrame;

        convertedData.mBuffers[0].mDataByteSize = outputBufferSize;

        convertedData.mBuffers[0].mData = outputBuffer;

     

        

        //수신할 준비가 된 패킷의 수를 전달하는 포인터를 사용한다.

        //반환될때 실제로 버퍼에 있는 변환된 패킷의 수를 얻는다.

        UInt32 ioOutputDataPackets = packetsPerBuffer;

        OSStatus error = AudioConverterFillComplexBuffer(

                                                         audioConverter,

                                                         MyAudioConverterCallback,

                                                         mySettings,                                                                                //콜백을 위한 사용자 데이터 포인터

                                                         &ioOutputDataPackets,                                                                      //패킷수

                                                         &convertedData,                                                                            //변환된 데이터를 수신할 포인터

                                                         (mySettings->inputFilePacketDescription ? mySettings->inputFilePacketDescription : nil));  //패킷정보의 배열을 받을 포인터

        

        if (error || !ioOutputDataPackets)

        {

            break;

        }

        

        //변환된 데이터를 오디오 파일에 작성

        //변환된 데이터를 출력파일에 작성

        CheckResult(

                    AudioFileWritePackets(mySettings->outputFile,                                                       //작성할 파일,

                                          FALSE,                                                                        //캐시설정

                                          ioOutputDataPackets,                                                          //작성할 크기

                                          NULL,                                                                         //패킷정보의배열(pcm출력 파일은 고정비트율,패킷정보를 제공x)

                                          outputFilePacketPosition / mySettings->outputFormat.mBytesPerPacket,          //첫번째 작성하는 패킷의 인덱스

                                          &ioOutputDataPackets,                                                         //쓰여진 패킷의수

                                          convertedData.mBuffers[0].mData),                                             //오디오 변환기에서 받은 오디오 데이터의 버퍼 제공

                    "couldn't write packets to file");

        

        //패킷을 파일에 쓴 이후에 패킷의 수를 각 pcm 패킷에 바이트로 곱함으로써 출력 위치를 업데이트 한다.

        outputFilePacketPosition += (ioOutputDataPackets * mySettings->outputFormat.mBytesPerPacket);

        

    }

    

    AudioConverterDispose(audioConverter);

    free(outputBuffer);

    

    

}



int main(int argc, const char * argv[]) {

   

    //MyAudioConverterSettings을 할당하고 입력파일을 위해 AudioFileID를 열고, 설정 구조체에 AudioFileID를 저장함으로써 시작한다.

    MyAudioConverterSettings audioConverterSetting = {0};

    

    

    //변환을 위한 소스 파일열기

    CFURLRef inputFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, kInputFileLocation, kCFURLPOSIXPathStyle, false);

    CheckResult(

                AudioFileOpenURL(inputFileURL,

                                 kAudioFileReadPermission,

                                 0,

                                 &audioConverterSetting.inputFile),

                "audioFileOpenURL failed");

    CFRelease(inputFileURL);

    

    //입력 파일이 열린채로 audiostreambascidescription을 얻음으로써 소스 오디오의 형식을 알게된다.

    //kAudioFilePropertyDataFormat을 추출하기 위해서 AudioFileGetProperty을 호출한다.

    //파일에서 부터 오디오 데이터 포멧(asbd) 얻기

    UInt32 propSize = sizeof(audioConverterSetting.inputFormat);

    

    CheckResult(

                AudioFileGetProperty(audioConverterSetting.inputFile,

                                     kAudioFilePropertyDataFormat,

                                     &propSize,

                                     &audioConverterSetting.inputFormat),

                "couldn't get file's data format");

    

    

    

    //소스 파일의 전체 패킷수와 가능한 최대 패킷의 크기 속성을 추출

    //패킷의 수는 얼마나 많은 읽기를 할 것인지 알게 하고, 최대 패킷의 크기는 audiofilereadpackets()에 적절하게 큰 버퍼를 할당한다.

    //입력 오디오 파일에서 패킷 수와 최대 패킷 크기 속성을 얻음

    

    propSize = sizeof(audioConverterSetting.inputFilePacketCount);

    CheckResult(

                AudioFileGetProperty(audioConverterSetting.inputFile,

                                     kAudioFilePropertyAudioDataPacketCount,

                                     &propSize,

                                     &audioConverterSetting.inputFilePacketCount),

                "couldn't gert file's packet count");

    

    propSize = sizeof(audioConverterSetting.inputFilePacketMaxSize);

    CheckResult(

                AudioFileGetProperty(audioConverterSetting.inputFile,

                                     kAudioFilePropertyMaximumPacketSize,

                                     &propSize,

                                     &audioConverterSetting.inputFilePacketMaxSize),

                "couldn't get file's max packet size");

    

    

    //출력 파일을 설정할 준비완료

    //일반적인 lpcm 형식을 설명하기 위해서 asbd 생성

    //16비트 샘플, 두개의 채널(스테레오), 빅엔디언의 샘플이 해당됨

    //파일을 생성하고, audiofileId를 얻기위해서 이 asbd를 audiofilecreateWithURL에 전달한다.

    

    audioConverterSetting.outputFormat.mSampleRate = 44100.0;

    audioConverterSetting.outputFormat.mFormatID = kAudioFormatLinearPCM;

    audioConverterSetting.outputFormat.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;

    

    audioConverterSetting.outputFormat.mBytesPerPacket = 4;

    audioConverterSetting.outputFormat.mFramesPerPacket = 1;

    audioConverterSetting.outputFormat.mBytesPerFrame = 4;

    audioConverterSetting.outputFormat.mChannelsPerFrame = 2;

    audioConverterSetting.outputFormat.mBitsPerChannel = 16;

    

    

    //출력 파일 생성

    CFURLRef outputFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("myoutputfile.aif"), kCFURLPOSIXPathStyle, false);

    CheckResult(

                AudioFileCreateWithURL(outputFileURL, kAudioFileAIFFType, &audioConverterSetting.outputFormat, kAudioFileFlags_EraseFile, &audioConverterSetting.outputFile),

                "audiofilecreatewithurl failed");

    

    CFRelease(outputFileURL);

    fprintf(stdout, "converting..\n");

    Convert(&audioConverterSetting);

    

    

    

    

cleanup:

    AudioFileClose(audioConverterSetting.inputFile);

    AudioFileClose(audioConverterSetting.outputFile);

    printf("done\r");

    return 0;

}


/*

 정리

 - main 에서 입력파일을 열었고, 형식과 패킷의 크기속성을 검사했으며, pcm형식을 선택하여 출력 파일을 생성하였다.

 - 패킷을 전송하기 위해 반복적으로 AudioBufferList를 설정하고 압축된 입력 데이터를 PCM으로 변환하기 위해서 AudioConverterFillComplexBuffer()를 호출하는 Converter() 함수를 작성했다.

 - 그리고 AudioFileWritePackets() 로 출력 파일에 작성했다.

 - 변환기의 입력은 개발자가 작성한 콜백 함수에서 오고, 이때 콜백 함수는 임력 파일에서 반복적으로 읽고, AudioBufferList에 전달된 오디오 데이터와 패킷 정보를 제공한다.

 

 */


AVFoundation 으로 오디오 플레이어 만들기


1. mp3 파일 넣어주기, view객체 지정


대략 이런 모습


2.위에서 만든 뷰객체들을 컨트롤러에 연결시켜 준다.(avfoundation import 하고 아래 두개의 매서드를 선언해준다.)


//

//  ViewController.h

//  avFoundationMusic

//

//  Created by MacBookPro on 10/11/2018.

//  Copyright © 2018 MacBookPro. All rights reserved.

//


#import <UIKit/UIKit.h>

#import <AVFoundation/AVFoundation.h>

//델리게이트 선언

@interface ViewController : UIViewController <AVAudioPlayerDelegate>

- (IBAction)playPauseAudio:(id)sender;

- (IBAction)stopAudio:(id)sender;

- (IBAction)adjustVolume:(id)sender;

- (IBAction)progressSliderChanged:(id)sender;




@property (weak, nonatomic) IBOutlet UISlider *progressSlider;


@property (weak, nonatomic) IBOutlet UISlider *volumeSlider;



@property (weak, nonatomic) IBOutlet UIButton *playPauseButton;


@property (weak, nonatomic) IBOutlet UILabel *currentTimeLabel;


@property (weak, nonatomic) IBOutlet UILabel *durationLabel;



@property (strong,nonatomic) AVAudioPlayer *audioPlayer;

@property (strong, nonatomic) NSTimer *sliderTimer;


-(NSString *)stringFromInterval : (NSTimeInterval)interval;

-(void) updateSlider;


@end


3.위에서 선언한 메서드 구현하기

//

//  ViewController.m

//  avFoundationMusic

//

//  Created by MacBookPro on 10/11/2018.

//  Copyright © 2018 MacBookPro. All rights reserved.

//


#import "ViewController.h"


@interface ViewController ()


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

    

    //mp3 경로

    NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]pathForResource:@"abc" ofType:@"mp3"]];

    NSError *error;

    

    //audioplayer 초기화

    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];

    

    if (error) {

        NSLog(@"fehler : %@", error.localizedDescription);

    }else{

        

        //위임자는 자기자신

        self.audioPlayer.delegate= self;

        self.progressSlider.value = 0.0;

        self.volumeSlider.value = 0.5;

        //현재오디오 진행 시간을 보여주는 lable = [audioPlayer에서 보여주는 시간 가공해서 보여주기] 

        self.durationLabel.text = [self stringFromInterval:self.audioPlayer.duration];

        

        //mp3의 전체 길이가 1시간을 넘지 않는다면

        if (self.audioPlayer.duration <= 3600) {

            self.currentTimeLabel.text = [NSString stringWithFormat:@"00:00"];

        }else{

            self.currentTimeLabel.text = [NSString stringWithFormat:@"0:00:00"];

        }

        

        //준비완료

        [self.currentTimeLabel sizeToFit];

        [self.audioPlayer prepareToPlay];

    }


}



//audioPlayer.duration 을 가공해서 stirng으로 리턴

-(NSString *)stringFromInterval : (NSTimeInterval)interval{

    

    NSInteger ti = (NSInteger)interval;

    

    int sec = ti % 60;

    int min = (ti / 60) % 60;

    int hour = (ti / 3600);

    if (ti <= 3600) {

        return [NSString stringWithFormat:@"%02d:%02d",min,sec];

    }

    return [NSString stringWithFormat:@"%d:%02d:%02d",hour,min,sec];

    

}



-(void) updateSlider{

    self.progressSlider.value = self.audioPlayer.currentTime;

    self.currentTimeLabel.text = [self stringFromInterval:self.audioPlayer.currentTime];

    NSLog(@"updateSlider : %f  : %@" ,self.audioPlayer.currentTime ,[self stringFromInterval: self.audioPlayer.currentTime]);

}


- (IBAction)playPauseAudio:(id)sender {

     NSLog(@"self.audioPlayer.playing0 : %c", self.audioPlayer.playing);

    //플레이가 안되고 있으면

    if (!self.audioPlayer.playing) {

        NSLog(@"self.audioPlayer.playing1 : %c", self.audioPlayer.playing);

        self.progressSlider.maximumValue = self.audioPlayer.duration;

        //updateSlider 메서드를 1초간격으로 호출

        self.sliderTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateSlider) userInfo:nil repeats:YES];

        

        //오디오 진행바 변경됐을때 이벤트 감지

        [self.progressSlider addTarget:self action:@selector(progressSliderChanged:) forControlEvents:UIControlEventValueChanged];

        

        //실행

        [self.audioPlayer play];

        [self.playPauseButton setTitle:@"pause" forState:UIControlStateNormal];

    }else{

        //정지시키기

        NSLog(@"self.audioPlayer.playing2 : %c", self.audioPlayer.playing);

        [self.audioPlayer pause];

        [self.playPauseButton setTitle:@"play" forState:UIControlStateNormal];

    }

    

}


//정지 버튼

- (IBAction)stopAudio:(id)sender {

    //오디오가 플레이 중이면 정지시킨다

    if (self.audioPlayer.isPlaying) {

        [self.audioPlayer stop];

    }

    

    [self.audioPlayer setCurrentTime:0.0];

    [self.sliderTimer invalidate];

    self.progressSlider.value = 0.0;

    

    //오디오 총길이가 1시간보다 적을경우

    if (self.audioPlayer.duration <= 3600) {

        self.currentTimeLabel.text = [NSString stringWithFormat:@"00:00"];

    } else {

        self.currentTimeLabel.text = [NSString stringWithFormat:@"0:00:00"];

    }

    

    [self.currentTimeLabel sizeToFit];

    [self.playPauseButton setTitle:@"play" forState:normal];

    

}


//불륨 버튼

- (IBAction)adjustVolume:(id)sender {

    if (self.audioPlayer != nil) {

        self.audioPlayer.volume = self.volumeSlider.value;

    }

}


//진행바가 변경이 될때

- (IBAction)progressSliderChanged:(id)sender {

    [self.audioPlayer stop];  //오디오 정지

    [self.audioPlayer setCurrentTime:self.progressSlider.value];

    [self.audioPlayer prepareToPlay];   //준비

    [self.audioPlayer play];            //실행

}



-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{

    if (flag) {

        [self stopAudio:nil];

    }

}


-(void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error{

    

    NSLog(@"decodierfehler: %@" ,  error.localizedDescription);

}


- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player{

    //audio player wird unterbrochen

}



- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags{

    if (flags == AVAudioSessionInterruptionOptionShouldResume && self.audioPlayer != nil) {

        [self.audioPlayer play];

    }

}








- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


@end




ios objective c 코어오디오 다루기 5 - 재생기능


/*

 오디오 큐는 오디오 스트림을 처리하기 위해서 버퍼의 큐를 사용한다.

 즉 애플리케이션은 큐에 버퍼를 제공함으로써 설정하고,

 큐는 개발자 코드가 실행돼야 하는 시점에 개발자 코드의 콜백 함수로 버퍼를 전달한다.

 

 버퍼크기를 가늠하고, 매직쿠키를 관리하는 것은 녹음하기와 비슷하지만 얍축된 오디오 데이터의 버퍼를 기술하는

 AudioStreamPacketDescription을 사용한다.

 

 

 */

#define kPlaybackFileLocation CFSTR("/Users/service/Library/Developer/Xcode/DerivedData/4-biuofoahmxwspaaibakhduwwgnmi/Build/Products/Debug/output2.caf")

#import <Foundation/Foundation.h>

#import <AudioToolbox/AudioToolbox.h>


void CalculateBytesForTime (AudioFileID inAudioFile, AudioStreamBasicDescription inDesc, Float64 inSeconds, UInt32 *outBufferSize, UInt32 *outNumPackets);



//하나는 재생, 하나는 채워지고, 하나는 지연되는 경우에 사용

#define kNumberPlaybackBuffers 3


//재생 오디오 큐 콜백을 위한 사용자 정보 구조체

typedef struct MyPlayer{

    

    AudioFileID                         playbackFile;       //출력파일 참조

    SInt64                              packetPosition;     //출력파일의 현재 패킷 인덱스

    UInt32                              numPacketsToRead;   //파일에서 부터 읽기 위한 패킷의 수

    AudioStreamPacketDescription          *packetDescs;       //버퍼를 읽기 위한 패킷 디스크립션의 배열

    Boolean                             isDone;

    

} MyPlayer;




// 제네릭 에러 핸들러. - 만약 에러가 0이 아니면 , 에러메시지를 출력하고 프로그램을 종료 한다.

static void CheckError(OSStatus error, const char *operation)

{

    if (error == noErr)

    {

        return;

    }

    

    char errorString[20];

    //  4 문자 코드로 나타날 때 확인

    *(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(error);

    // 4바이트의 오류 코드가 정확한 문자로 구성됐는지 여부를 검사하기 위해서 isprint() 함수 사용

    if(isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4]))

    {

        errorString[0] = errorString[5] = '\'';

        errorString[6] = '\0';

    }else

    {

        //그렇지 않으면 정수로 취급 - 결과가 네 문자 코드가 아닌경우 괄호의 값은 -50

        sprintf(errorString, "%d", (int)error);

        fprintf(stderr, "오류 발생 : Error: %s (%s) \n",operation, errorString);

        exit(1);

    }

    

}


//큐의 버퍼 크기를 계산하고, 각 버퍼에 몇 개의 패킷을 읽을 수 있는지 알게하는 함수

void CalculateBytesForTime (AudioFileID inAudioFile, AudioStreamBasicDescription inDesc, Float64 inSeconds, UInt32 *outBufferSize, UInt32 *outNumPackets)

{

    

    // we need to calculate how many packets we read at a time, and how big a buffer we need.

    // we base this on the size of the packets in the file and an approximate duration for each buffer.

    //

    // first check to see what the max size of a packet is, if it is bigger than our default

    // allocation size, that needs to become larger

    UInt32 maxPacketSize;

    UInt32 propSize = sizeof(maxPacketSize);

    CheckError(AudioFileGetProperty(inAudioFile, kAudioFilePropertyPacketSizeUpperBound,

                                    &propSize, &maxPacketSize), "couldn't get file's max packet size");

    

    static const int maxBufferSize = 0x10000; // limit size to 64K

    static const int minBufferSize = 0x4000; // limit size to 16K

    

    if (inDesc.mFramesPerPacket) {

        Float64 numPacketsForTime = inDesc.mSampleRate / inDesc.mFramesPerPacket * inSeconds;

        *outBufferSize = numPacketsForTime * maxPacketSize;

    } else {

        // if frames per packet is zero, then the codec has no predictable packet == time

        // so we can't tailor this (we don't know how many Packets represent a time period

        // we'll just return a default buffer size

        *outBufferSize = maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize;

    }

    

    // we're going to limit our size to our default

    if (*outBufferSize > maxBufferSize && *outBufferSize > maxPacketSize)

        *outBufferSize = maxBufferSize;

    else {

        // also make sure we're not too small - we don't want to go the disk for too small chunks

        if (*outBufferSize < minBufferSize)

            *outBufferSize = minBufferSize;

    }

    *outNumPackets = *outBufferSize / maxPacketSize;

}


//오디오 파일에서 오디오 큐로 매직 쿠기 복사 - 파일에서 쿠키 속성을 얻고, 재생 큐에 설정한다.

static void MyCopyEncoderCookieToQueue(AudioFileID theFile, AudioQueueRef queue ) {

    UInt32 propertySize;

    OSStatus result = AudioFileGetPropertyInfo (theFile, kAudioFilePropertyMagicCookieData, &propertySize, NULL);

    if (result == noErr && propertySize > 0)

    {

        Byte* magicCookie = (UInt8*)malloc(sizeof(UInt8) * propertySize);

        CheckError(AudioFileGetProperty (theFile, kAudioFilePropertyMagicCookieData, &propertySize, magicCookie), "get cookie from file failed");

        CheckError(AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, magicCookie, propertySize), "set cookie on queue failed");

        free(magicCookie);

    }

}




#pragma mark - audio queue -


static void MyAQOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer)

{

    MyPlayer *aqp = (MyPlayer*)inUserData;

    if (aqp->isDone) return;

    

    // read audio data from file into supplied buffer

    UInt32 numBytes;

    UInt32 nPackets = aqp->numPacketsToRead;

    //파일에서 읽기 위한 함수 AudioFileReadPackets

    CheckError(AudioFileReadPackets(aqp->playbackFile,

                                    false,

                                    &numBytes,

                                    aqp->packetDescs,

                                    aqp->packetPosition,

                                    &nPackets,

                                    inCompleteAQBuffer->mAudioData),

               "AudioFileReadPackets failed");

    

    // enqueue buffer into the Audio Queue

    // if nPackets == 0 it means we are EOF (all data has been read from file)

    if (nPackets > 0)

    {

        inCompleteAQBuffer->mAudioDataByteSize = numBytes;

        AudioQueueEnqueueBuffer(inAQ,

                                inCompleteAQBuffer,

                                (aqp->packetDescs ? nPackets : 0),

                                aqp->packetDescs);

        aqp->packetPosition += nPackets;

    }

    else

    {

        CheckError(AudioQueueStop(inAQ, false), "AudioQueueStop failed");

        aqp->isDone = true;

    }

}






int main(int argc, const char * argv[]) {


    //MyPlayer구조체 할당

    MyPlayer player = {0};

    //입력을 위한 오디오 파일 열기

   CFURLRef myFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,

                                  kPlaybackFileLocation,

                                  kCFURLPOSIXPathStyle,

                                  false);

    

    CheckError(AudioFileOpenURL(myFileURL,

                                kAudioFileReadPermission,

                                0,

                                &player.playbackFile), "audio file open url failed");

    

    CFRelease(myFileURL);

    

    

    //오디오 파일에서 asbd(오디오 데이터 포멧)획득

    AudioStreamBasicDescription dataFormat;

    UInt32 propSize = sizeof(dataFormat);

    CheckError(AudioFileGetProperty(player.playbackFile,

                                    kAudioFilePropertyDataFormat,

                                    &propSize,

                                    &dataFormat), "couldn't get file's data format");

    

    

    //dataFormat을 가졌기 때문에 AudioQueueNewOutput()함수로 재생을 위한 오디오 큐를 생성할 준비가 됐음.

    //녹음큐를 설정하는 방법과 같다.

    AudioQueueRef queue;

    CheckError(AudioQueueNewOutput(&dataFormat,

                                   MyAQOutputCallback,

                                   &player,

                                   NULL,

                                   NULL,

                                   0,

                                   &queue), "audioQueueNewOutput failed");

    

    //재생 버퍼 크기와 패킷의 수를 계산하기 위한 편의 함수 호출

    UInt32 bufferByteSize;

    //읽을 파일, asbd, 초단위 버퍼기간을 취하고, 적절한 버퍼 크기와 각 콜백에서 파일로 부터 읽을 오디오 패킷수를 나타내는 변수를 설정한다.

    CalculateBytesForTime(player.playbackFile, dataFormat,  0.5, &bufferByteSize, &player.numPacketsToRead);

   

    

    //패킷 정보 배열에 메모리 할당

    //pocketDescs는 파일에서 읽은 각 오디오 버퍼의 내용을 나타내는 패킷 정보의 배열이다.

    //패킷정보가 필요한지 여부는 오디오 형식이 가변(패킷정보가 필요함) 비트율인지 고정(패킷정보가 필요치 않음)인지에 달렸다

    bool isFormatVBR = (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0);

    if (isFormatVBR)

        player.packetDescs = (AudioStreamPacketDescription*)malloc(sizeof(AudioStreamPacketDescription) * player.numPacketsToRead);

    else

        player.packetDescs = NULL;

    

    

    //재생을 위해서 오디오 파일의 속성으로써 매직쿠키를 읽고 , 큐에 작성

    MyCopyEncoderCookieToQueue(player.playbackFile, queue);

    

    

    //녹음할때는 빈 버퍼를 큐에 전달했고, 콜백에서 채워진 버퍼를 받았다.

    //재생할때는 콜백에서 빈 버퍼를 받고, 재생된 오디오로 그것을 채워야한다.

    //재생 버퍼를 할당하고, 큐에 넣는다.

    AudioQueueBufferRef    buffers[kNumberPlaybackBuffers];

    player.isDone = false;

    player.packetPosition = 0;

    int i;

    for (i = 0; i < kNumberPlaybackBuffers; ++i)

    {

        CheckError(AudioQueueAllocateBuffer(queue, bufferByteSize, &buffers[i]), "AudioQueueAllocateBuffer failed");

        

        // manually invoke callback to fill buffers with data

        MyAQOutputCallback(&player, queue, buffers[i]);

        

        // EOF (the entire file's contents fit in the buffers)

        if (player.isDone)

            break;

    }

    

    

    //재생 오디오 큐 시작 - 이 시점에서 오디오 큐는 재생할 준비가 된 오디오 세 개의 오디오 데이터의 버퍼를 가진다.

    //재생을 시작할때, 큐는 각 버퍼의 내용을 재생하고, 파일에서 새로운 오디오로 버퍼를 다시 채우기 위해서 콜백함 수를 호출한다.

    CheckError(AudioQueueStart(queue, NULL), "AudioQueueStart failed");

    

    // and wait

    printf("Playing...\n");

    do

    {

        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, false);

    } while (!player.isDone /*|| gIsRunning*/);

    

    

    //큐가 버퍼에 있는 오디오를 재생하도록 보장하기 위해 지연

    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, false);

    

    

    

    //오디오 큐와 오디오 파일 해제

    player.isDone = true;

    CheckError(AudioQueueStop(queue, TRUE), "AudioQueueStop failed");

    

cleanup:

    AudioQueueDispose(queue, TRUE);

    AudioFileClose(player.playbackFile);

    

    

    return 0;

}



/*

 출력

 Playing...

 Program ended with exit code: 0

 */


ios objective c 코어오디오 다루기 4 - 녹음기능


//개발자가 오디오 큐를 생성할때 (녹음할때)입력장치에서 캡쳐된 오디오의 버퍼를 애플리케이션에 전달하거나

//(재생할때 )버퍼를 채울것을 요구하기 위한 콜백함수를 제공한다.

//맥의 기본입력장치에서 녹음을 하고 , 캡쳐된 오디오를 파일에 쓴다.




//사용할 오디오 형식과 녹음을 할 파일을 설정

//오디오 큐를 생성

//큐를 시작

//큐를 중지

//파일을 닫는 등의 해제 작업



#import <Foundation/Foundation.h>

#import <AudioToolbox/AudioToolbox.h>


#define kNumberRecordBuffers 3


//오디오 큐 콜백을 녹음하기 위한 사용자 정보 구조체

typedef struct MyRecorder

{

    AudioFileID     recordFile;     //output파일 참조

    SInt64          recordPacket;   //output 파일에서 현재 packet index

    Boolean         running;        //큐가 실행되고 있는지 여부를 추적하는 불리언값

} MyRecorder;



OSStatus MyGetDefaultInputDeviceSampleRate(Float64 *outSampleRate);



#pragma mark - utility functions -



// 제네릭 에러 핸들러. - 만약 에러가 0이 아니면 , 에러메시지를 출력하고 프로그램을 종료 한다.

static void CheckError(OSStatus error, const char *operation)

{

    if (error == noErr)

    {

        return;

    }

    

    char errorString[20];

    //  4 문자 코드로 나타날 때 확인

    *(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(error);

    // 4바이트의 오류 코드가 정확한 문자로 구성됐는지 여부를 검사하기 위해서 isprint() 함수 사용

    if(isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4]))

    {

        errorString[0] = errorString[5] = '\'';

        errorString[6] = '\0';

    }else

    {

        //그렇지 않으면 정수로 취급 - 결과가 네 문자 코드가 아닌경우 괄호의 값은 -50

        sprintf(errorString, "%d", (int)error);

        fprintf(stderr, "오류 발생 : Error: %s (%s) \n",operation, errorString);

        exit(1);

    }

    

}





//기본 입력 디바이스의 샘플률을 구한다.

//오디오 하드웨어 서비스에서 현재 오디오 입력 장치 얻기 - 참고: ios에서는 오디오 하드웨어 서비스는 존재하지 않는다. 아이폰에서 하드웨어 샘플율을 얻기 위해서 10장에서 다루는 오디오 세션 서비스를 사용한다.

OSStatus MyGetDefaultInputDeviceSampleRate(Float64 *outSampleRate)

{

    OSStatus error;

    AudioDeviceID deviceID = 0;

    

    //입력 디바이스를 얻는다.

    AudioObjectPropertyAddress propertyAddress;

    UInt32 propertySize;

    propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;

    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;

    propertyAddress.mElement = 0;

    propertySize = sizeof(AudioDeviceID);

    error = AudioHardwareServiceGetPropertyData(

                                                kAudioObjectSystemObject,

                                                &propertyAddress,

                                                0,

                                                NULL,

                                                &propertySize,

                                                &deviceID);

    if(error){return error;}

    

    //입력장치의 샘플율 얻기

    propertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate;

    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;

    propertyAddress.mElement = 0;

    propertySize = sizeof(Float64);

    error = AudioHardwareServiceGetPropertyData(deviceID,

                                                &propertyAddress,

                                                0,

                                                NULL,

                                                &propertySize,

                                                outSampleRate);

    

    return error;

}



//asbd를 위한 녹음 버퍼 크기 계산

static int MyComputeRecordBufferSize(const AudioStreamBasicDescription *format, AudioQueueRef queue, float secods)

{


    int packets, frames, bytes;

    //각 버퍼에 몇개의 프레임이 있는지 알 필요가 있다.

    frames = (int)ceil(secods * format -> mSampleRate);

    

    if (format->mBytesPerFrame > 0 )

    {

        bytes = frames * format->mBytesPerFrame;

    }else

    {

        //고정된 패킷 크기

        UInt32 maxPacketSize;

        if (format->mBytesPerPacket > 0)

        {

            maxPacketSize = format ->mBytesPerPacket;

        }else{

            

            //가능한 가장 큰 패킷 크기 획득

            UInt32 propertySize = sizeof(maxPacketSize);

            CheckError(AudioQueueGetProperty(queue,

                                             kAudioConverterPropertyMaximumOutputPacketSize,

                                             &maxPacketSize,

                                             &propertySize),

                       "couldn't get queue's maximum output packet size");

            

        }

        //얼마나 많은 패킷을 가지는지?

        if(format->mFramesPerPacket >0)

        {

            packets = frames / format->mFramesPerPacket;

        }else

        {   // 최악의 경우 패킷에 하나의 프레임

            packets = frames;

            

        

            if (packets == 0 )          // 오류검사

            {

                packets = 1;

                bytes = packets * maxPacketSize;

            }

        

        }

    }

    return bytes;

}




//오디오 큐의 매직 쿠키를 오디오 파일에 복사

static void MyCopyEncoderCookieToFile(AudioQueueRef queue, AudioFileID theFile)

{

    UInt32 propertySize;

    

    OSStatus result = AudioQueueGetPropertySize(queue,

                                                kAudioConverterCompressionMagicCookie,

                                                &propertySize);

    if(result == noErr && propertySize >0)

    {

        

        //쿠키 데이터를 패치 하고 얻기,

        Byte *magicCookie = (Byte *)malloc(propertySize);

        CheckError(AudioQueueGetProperty(queue,

                                         kAudioQueueProperty_MagicCookie,

                                         magicCookie,

                                         &propertySize),

                                        "get audio queue's magic cookie");

        //매직쿠키를 출력파일에 셋팅

        CheckError(AudioFileSetProperty(theFile,

                                        kAudioFilePropertyMagicCookieData,

                                        propertySize,

                                        magicCookie),

                                        "set audio fhile's magic cookie");

        free(magicCookie);

    }

}


#pragma mark - audio queue -

//오디오 큐 콜백 함수, 인풋버퍼가 다 찼을때 호출된다.


static void MyAQInputCallback(void *inUserData,

                              AudioQueueRef inQueue,

                              AudioQueueBufferRef inBuffer,

                              const AudioTimeStamp *inStartTime,

                              UInt32 inNumPackets,

                              const AudioStreamPacketDescription *inPacketDesc)

{

    

    MyRecorder *recorder = (MyRecorder *)inUserData;

    

    //inNumPackets이 0 보다 크면, 버퍼는 오디오 데이터를 얻는다.

    // in the format we specified (AAC)

    //캡쳐된 패킷을 오디오 파일에 쓴다.

    if (inNumPackets >0)

    {

        // 파일에 패킷을 쓴다.

        CheckError(AudioFileWritePackets(recorder->recordFile,

                                         FALSE,

                                         inBuffer->mAudioDataByteSize,

                                         inPacketDesc,

                                         recorder->recordPacket,

                                         &inNumPackets,

                                         inBuffer->mAudioData),

                                        "AudioFileWritePackets failed");

        //패킷 인덱스 증가

        recorder -> recordPacket += inNumPackets;

    

        

        //사용된 버퍼를 다시 큐에 넣음

        if(recorder->running)

        {

            CheckError(AudioQueueEnqueueBuffer(inQueue,

                                               inBuffer,

                                               0,

                                               NULL),

                                                "AudioQueueEnqueueBuffer failed");

        }

    }

}


// 기본장치에서 입력을 캡쳐하기 위한 오디오 큐를 생성하고,

//사용자가 중지하기 전까지 계속 실행하고, 마지막에 모든것을 해제하는 것이다.

int main(int argc, const char * argv[]) {


    //오디오 큐를 위해 MyRecorder 구조체와 ASBD를 생성

    MyRecorder recorder = {0};

    AudioStreamBasicDescription recordFormat = {0};

    memset(&recordFormat, 0, sizeof(recordFormat));

    

    

    //출력 데이터 포멧을 acc로 설정(AAC 로 녹음을 하길 원한다)

    recordFormat.mFormatID = kAudioFormatMPEG4AAC;

    recordFormat.mChannelsPerFrame = 2;

    

    // 인풋디바이스의 샘플율을 얻는다.

    // we use this to adapt the output data format to match hardware capabilities

    MyGetDefaultInputDeviceSampleRate(&recordFormat.mSampleRate);

    

    

    //mBytesPerPacket과 같은 ASBD 필드 중의 몇가지는 엔코딩 형식의 세부사항에 의존하고, 가변적일 수 있다.(개발자가 알 수 없음)

    //pcm외에 다른 형식에 대해서 채울 수 있는 것을 채우고, 코어 오디오가 나머지를 처리하게 됨.

    //AudioFormatGetProperty로 asbd 채우기

    UInt32 propSize = sizeof(recordFormat);

    CheckError(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,

                                      0,

                                      NULL,

                                      &propSize,

                                      &recordFormat),

                                    "AudioFormatGetProperty Filed");

    

    //위에 코드까지 형식 설정완료함(^)

    

    //입력을 위한 오디오 큐 생성

    AudioQueueRef queue = {0};

    CheckError(AudioQueueNewInput(&recordFormat,            //asbd(녹음할 형식)

                                  MyAQInputCallback,        //callback

                                  &recorder,               //user data 포인터

                                  NULL,                     //run loop

                                  NULL,                     //run loop mode

                                  0,                        //flags (always 0)

                                  &queue),                  //audioQueueRef를 수신하는 포인터를 나타냄

                                 "AudioQueueNewInput failed");

    

    

    //오디오 큐에 채워진 asbd 추출

    //코어 오디오가 큐를 위해서 코덱을 준비하기 전까지 어떤필드들을 체워야 할지 모르기 때문에 아래 코드 필요

    //이제 좀더 세부적인 asbd를 가지고 캡쳐된 파일을 녹음할 파일을 생성할 수 있다.

    UInt32 size = sizeof(recordFormat);

    CheckError(AudioQueueGetProperty(queue,

                                     kAudioConverterCurrentOutputStreamDescription,

                                     &recordFormat,

                                     &size),

                                    "couldn't get queue's format");

    

    

    //출력을 위한 오디오 파일 생성

    CFURLRef myFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,

                                                       CFSTR("./output2.caf"),

                                                       kCFURLPOSIXPathStyle,

                                                       false);

    CFShow(myFileURL);

    CheckError(AudioFileCreateWithURL(myFileURL,

                                      kAudioFileCAFType,

                                      &recordFormat,

                                      kAudioFileFlags_EraseFile,

                                      &recorder.recordFile), "audiofileCreateWithURL failed");

    CFRelease(myFileURL);

    

    //큐는 매직 쿠키도 제공한다. 매직 쿠키는 주어진 코덱이 유일하고, asbd가 이미 처리하지 않은 데이터의 불명확 블록이다.

    //매직 쿠키를 처리하는 편의 함수 호출

    MyCopyEncoderCookieToFile(queue, recorder.recordFile);

    

    //allocate and enqueue buffers

    //녹음의 경우 큐는 캡쳐된 오디오로 이런 버퍼를 채우고 콜백함수로 전달한다.

    //MyComputeRecordBufferSize는 asbd, 오디오 큐, 버퍼 기간 초를 취하고, 최적의 크기를 반환한다.

    int bufferByteSize = MyComputeRecordBufferSize(&recordFormat, queue, 0.5);// enough bytes for half a second

    //버퍼할당과 큐에 삽입

    int bufferIndex;

    for(bufferIndex = 0; bufferIndex < kNumberRecordBuffers; ++bufferIndex)

    {

        AudioQueueBufferRef buffer;

        CheckError(AudioQueueAllocateBuffer(queue, bufferByteSize, &buffer), "audioqueueAllocateBuffer failed");

        CheckError(AudioQueueEnqueueBuffer(queue, buffer, 0, NULL), "audioQueueEnqueuBuffer failed");

    }

    

    

    // start the queue. this function return immedatly and begins

    // invoking the callback, as needed, asynchronously.

    // 오디오 큐 시작

    recorder.running = TRUE;

    CheckError(AudioQueueStart(queue, NULL), "audio Queue start failed");

    

    //and wait

    //녹음을 계속하ㅏ기 위해서 stdin에서 대기

    printf("recording, press <return> to stop : \n");

    getchar();

    

    //end recording

    //사용자가 녹음을 완료했을 때 큐를 중지하여 동작을 중지할 수 있다.

    printf("*recording done * \n");

    recorder.running = FALSE;

    CheckError(AudioQueueStop(queue, TRUE),"audioqueue stop failed" );

    

    //매직 쿠키 편의 함수 재호출

    // a codec may update its magic cookie at the end of an encoding session

    // so reapply it to the file now

    MyCopyEncoderCookieToFile(queue, recorder.recordFile);

    

    

    //오디오 큐와 오디오 파일을 해제

cleanup:

    AudioQueueDispose(queue, TRUE);

    AudioFileClose(recorder.recordFile);



    return 0;

}








/*

 로그내용

 2018-11-07 14:55:35.592512+0900 4[1511:319917] 317:  ca_debug_string: inPropertyData == NULL

 <CFURL 0x102095010 [0x7fffa6835980]>{string = ./output2.caf, encoding = 134217984

 base = <CFURL 0x1020a0e20 [0x7fffa6835980]>{string = file:///Users/service/Library/Developer/Xcode/DerivedData/4-biuofoahmxwspaaibakhduwwgnmi/Build/Products/Debug/, encoding = 134217984, base = (null)}}

 recording, press <return> to stop :

 

 녹음파일이 생성되는중..............................

 

 *recording done *

 Program ended with exit code: 0


 지정해준 경로에 녹음파일이 생성된다.

 */









ios objective c 코어오디오 다루기 3 - 데이터 구조체


코어오디오는 오디오 데이터 패킷을 스트림으로 본다. 

AudioStreamBasicDescription 구조체는 샘플율, 하나의 채널에 비트의 수, 프레임에 채널의 수등 데이터 구조를 묘사하는 메타데이터를 포함한다. 스트림의 ASBD에 관해 중요한점은 오디오 데이터 형식의 구현 세부사항이기 때문이다. 예로 파일이나 네트워크 스트림과 같은 어떤 소스에서 데이터를 읽을 때 코어 오디오의 여러 부분은 ASBD 값을 채운다. ASBD의 구조체 이름은 m으로 시작한다.


//

//  main.m

//

//

//  Created by MacBookPro on 06/11/2018.

//  Copyright © 2018 MacBookPro. All rights reserved.

//


#import <Foundation/Foundation.h>

#import <AudioToolbox/AudioToolbox.h>

int main(int argc, const char * argv[]) {

    

    //kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat의 속성을 사용하기 위해서 AudioFileTypeAndFormatID 구조체를

    //코어오디오에 전달해야 한다.

    AudioFileTypeAndFormatID fileTypeAndFormat;

    fileTypeAndFormat.mFileType = kAudioFileAIFFType;

    fileTypeAndFormat.mFormatID = kAudioFormatLinearPCM;

    //fileTypeAndFormat.mFileType = kAudioFileMP3Type;

    //fileTypeAndFormat.mFormatID = kAudioFormatMPEG4AAC; //aac 데이터를 mp3파일에 넣을 수 없다.

    

    //코어오디오 호출에서 결과 코드를 받기위한 변수

    OSStatus audioErr = noErr;

    //정보를 추출하기 전에 확인할 정보의 크기를 담을 변수 infoSize

    UInt32 infoSize = 0;

    

    //전역정보 속성을 얻는것은 속성의 크기를 미리 알아내고, UInt32 포인터에 저장하기 위함

    audioErr = AudioFileGetGlobalInfoSize(kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat,

                                          sizeof(fileTypeAndFormat),

                                          &fileTypeAndFormat,

                                          &infoSize);

    if(audioErr != noErr)

    {

       UInt32 err4cc = CFSwapInt32HostToBig(audioErr);

        NSLog(@"%4.4s", (char*)&err4cc);

    }

    

    

    assert(audioErr == noErr);      //aac 데이터를 mp3파일에 넣으면 fmt? 에러 발생

    

    //AudioFileGetGlobalInfoSize 호출은 실제로 전역 속성을 얻을 때 수신할 데이터의 양을 알려준다.

    //그 속성을 보관하기 위해서 메모리를 할당할 필요가 있다.

    AudioStreamBasicDescription *asbds = malloc(infoSize);

    

     //AudioFileTypeAndFormatID, 버퍼크기, 버퍼자체를 전달함.

    audioErr = AudioFileGetGlobalInfo(kAudioFileGlobalInfo_AvailableStreamDescriptionsForFormat,

                                      sizeof(fileTypeAndFormat),

                                      &fileTypeAndFormat,

                                      &infoSize,

                                      asbds);

    

    

    

    assert(audioErr == noErr);

    

    //속성 호출이 AudioStreamBasicDescription을 제공하고, 데이터의 크기를 asbd의 크기로 나눔으로써 배열의 길이를 가늠할 수 있다.

    int asbdCount = infoSize / sizeof(AudioStreamBasicDescription);

    for (int i = 0; i < asbdCount; i ++) {

        //네문자로된 코드 숫자형식을 읽기 가능한 네개의 문자로 변경하기 위함. 엔디언 교환으로 실행함

        UInt32 format4cc = CFSwapInt32HostToBig(asbds[i].mFormatID);

        //mFormatID의 엔디언 교환 표현을 보기 좋게 출력함.

        NSLog(@"%d : mFormatId : %4.4s, mFormatFlags : %d, mBitsPerChannel: %d",

              i,

              (char*)&format4cc,

              asbds[i].mFormatFlags,

              asbds[i].mBitsPerChannel);

    }

    

    free(asbds);

    

    return 0;

}




//1.결과

 //fileTypeAndFormat.mFileType = kAudioFileAIFFType;

//2018-11-06 21:57:17.243110+0900 3[932:67326] 0 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 8

//2018-11-06 21:57:17.243354+0900 3[932:67326] 1 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 16

//2018-11-06 21:57:17.243372+0900 3[932:67326] 2 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 24

//2018-11-06 21:57:17.243387+0900 3[932:67326] 3 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 32

/*

    aiff 가 pcm 형식에서 비트 깊이가 다른 형태만 지원함으로써 다양성이 부족함을 보여준다.

 

 

 2.변경

 아래코드로 변경했을때

 fileTypeAndFormat.mFileType = kAudioFileWAVEType;

 2018-11-06 22:13:11.776434+0900 3[981:92111] 0 : mFormatId : lpcm, mFormatFlags : 8, mBitsPerChannel: 8

 2018-11-06 22:13:11.777009+0900 3[981:92111] 1 : mFormatId : lpcm, mFormatFlags : 12, mBitsPerChannel: 16

 2018-11-06 22:13:11.777053+0900 3[981:92111] 2 : mFormatId : lpcm, mFormatFlags : 12, mBitsPerChannel: 24

 2018-11-06 22:13:11.777104+0900 3[981:92111] 3 : mFormatId : lpcm, mFormatFlags : 12, mBitsPerChannel: 32

 2018-11-06 22:13:11.777119+0900 3[981:92111] 4 : mFormatId : lpcm, mFormatFlags : 9, mBitsPerChannel: 32

 2018-11-06 22:13:11.777134+0900 3[981:92111] 5 : mFormatId : lpcm, mFormatFlags : 9, mBitsPerChannel: 64

 

 wav 파일이 다른 스타일의 pcm을 취함을 보여줌. wav파일은 항상 리틀엔디언 pcm으을 설정하고, 이는 형식이 정수 샘플에만 국한되지는 않는다.

 

 

 3.아래코드로 변경

 fileTypeAndFormat.mFileType = kAudioFileCAFType;

 2018-11-06 22:16:37.847672+0900 3[998:99577] 0 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 8

 2018-11-06 22:16:37.847877+0900 3[998:99577] 1 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 16

 2018-11-06 22:16:37.847899+0900 3[998:99577] 2 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 24

 2018-11-06 22:16:37.847929+0900 3[998:99577] 3 : mFormatId : lpcm, mFormatFlags : 14, mBitsPerChannel: 32

 2018-11-06 22:16:37.847950+0900 3[998:99577] 4 : mFormatId : lpcm, mFormatFlags : 11, mBitsPerChannel: 32

 2018-11-06 22:16:37.847977+0900 3[998:99577] 5 : mFormatId : lpcm, mFormatFlags : 11, mBitsPerChannel: 64

 2018-11-06 22:16:37.847998+0900 3[998:99577] 6 : mFormatId : lpcm, mFormatFlags : 12, mBitsPerChannel: 16

 2018-11-06 22:16:37.848017+0900 3[998:99577] 7 : mFormatId : lpcm, mFormatFlags : 12, mBitsPerChannel: 24

 2018-11-06 22:16:37.848036+0900 3[998:99577] 8 : mFormatId : lpcm, mFormatFlags : 12, mBitsPerChannel: 32

 2018-11-06 22:16:37.848055+0900 3[998:99577] 9 : mFormatId : lpcm, mFormatFlags : 9, mBitsPerChannel: 32

 2018-11-06 22:16:37.848073+0900 3[998:99577] 10 : mFormatId : lpcm, mFormatFlags : 9, mBitsPerChannel: 64

 caf는 여러가지 형식을 취하고, 정수와 부동소수점 샘플과 양과 음의 정수를 모두 사용한다.이는 aiff와 wav가 제공하는 하나를 제외한 모든 형식을 지원한다.

 

 

 4.아래코드로 변경

 fileTypeAndFormat.mFormatID = kAudioFormatMPEG4AAC;

 2018-11-06 22:20:17.493762+0900 3[1031:106537] 0 : mFormatId : aac , mFormatFlags : 0, mBitsPerChannel: 0

 aac가 caf 파일에 유요한 형태임을 알 수 있다.

 

 

 

 5.아래코드로 변경

 fileTypeAndFormat.mFileType = kAudioFileMP3Type;

 fileTypeAndFormat.mFormatID = kAudioFormatMPEG4AAC;

 오류발생 : fmt?

 aac데이터를 mp3 파일에 넣을 수 없다.(파일형식과 데이터 형식은 다르다.)

 */


 ios objective c 코어오디오 다루기 2 - AudioStreamBasicDescription으로 다양한 주파수의 음원만들기




#import <AudioToolbox/AudioToolbox.h>

#import <Foundation/Foundation.h>


#define SAMPLE_RATE 44100    // 초당 44,100 샘플이나 44.1kHz 샘플율

#define DURATION 5.0    //얼마나 많은 오디오의 초를 만들기를 원하는지

 //#define FILENAME_FORMAT @"%0.3f-square.aif"    // 삐익~~~  티비 신호 나갔을때 음

 #define FILENAME_FORMAT @"%0.3f-saw.aif"

//#define FILENAME_FORMAT @"%0.3f-sine.aif" //파일이름 넣기  //솔음으로 쭉~



int main(int argc, const char * argv[]){

    

    //인자값이 존재하지 않으면

    if(argc < 2)

    {

        printf("usage: catonefileGenerator n \n(where n is tone is Hz)");

        return -1;

    }

    

    //생성하길 원하는 음색 주파수의 보동소수점 수

    double hz =  atof(argv[1]);

    assert(hz > 0);

    

    NSLog(@"generating %f hz tone",hz);

     //파일에 대한 경로 생성

    NSString *fileName = [NSString stringWithFormat:FILENAME_FORMAT, hz];

    NSString *filePath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent: fileName];

    NSURL *fileURL = [NSURL fileURLWithPath: filePath];

    NSLog (@"path: %@", fileURL);

    

    //오디오 파일을 만들기 위해서 파일이 포함하는 오디오의 설명을 제공

    //prepare the format

    //오디오 스트림의 가장 광범위한 특성정의: 얼마나 많은 채널을 가졌는지, 형식은 무엇인지 비트율 등등

    AudioStreamBasicDescription asbd;

    //무엇인가를 설정하기 전에 항상 0으로 초기화

    memset(&asbd, 0, sizeof(asbd));

    

    //파일에 작성할 데이터를 나타내기 위해서 AudioStreamBasicDescription의 개별 필드를 사용해야 한다.(여기서는 44100 데이터율을 가지는 하나의 채널 pcm인 스트림을 나타낸다.)

    //16비트 샘플을 사용하기 때문에 각 프레임은 2바이트가 됨

    asbd.mSampleRate = SAMPLE_RATE;

    asbd.mFormatID = kAudioFormatLinearPCM;

    asbd.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;

    asbd.mChannelsPerFrame = 1;

    asbd.mFramesPerPacket = 1;

    asbd.mBitsPerChannel = 16;

    asbd.mBytesPerFrame = 2;

    asbd.mBytesPerPacket = 2;

    

    //설정한 url에 작성할 준비가 된 AudioFileID를 만들라고 코어오디오에 요청

    AudioFileID audioFile;

    OSStatus audioErr = noErr;

    audioErr = AudioFileCreateWithURL((__bridge CFURLRef)fileURL, kAudioFileAIFFType, &asbd, kAudioFileFlags_EraseFile, &audioFile);

    

    

    assert(audioErr == noErr);

    

    //초당 sample_rate 샘플에서 소리의 duration 초에 얼마나 많은 샘플이 필요한지 계산한다.

    long maxSampleCount = SAMPLE_RATE * DURATION;

    //카운터 변수

    long sampleCount = 0;

    

    UInt32 bytesToWrite = 2;

    //얼마나 많은 샘플이 파장에 있는지 알아야 하기 때문에 파형을 만드는 샘플을 위한 값을 계산할 수 있다.

    double wavelengthInSamples = SAMPLE_RATE / hz;

    NSLog(@"wavelengthInSameple = %f", wavelengthInSamples);

    

    while (sampleCount < maxSampleCount) {

        for (int i = 0; i < wavelengthInSamples; i++) {

            

            

             // square wave

/*             SInt16 sample;

             if (i < wavelengthInSamples/2) {

             sample = CFSwapInt16HostToBig (SHRT_MAX);

             } else {

             sample = CFSwapInt16HostToBig (SHRT_MIN);

             }

             audioErr = AudioFileWriteBytes(audioFile,

             false,

             sampleCount*2,

             &bytesToWrite,

             &sample);

             assert (audioErr == noErr);

            sampleCount++ ;

   */

    

            

             // saw wave

            //인텔 cpu에서 구동된다, 따라서 cpu의 표현에서 빅엔디언으로 바이트를 변경. 이전 맥인 power pc 같은 빅엔디언 cpu에서도 동작한다.

             SInt16 sample = CFSwapInt16HostToBig (((i / wavelengthInSamples) * SHRT_MAX *2) -

             SHRT_MAX);

            //샘플을 계산한 값을 저장하기

             audioErr = AudioFileWriteBytes(audioFile,

             false,

             sampleCount*2,

             &bytesToWrite,

             &sample);

             assert (audioErr == noErr);

            //새로운 데이터를 파일의 증가된 위치에 작성

             sampleCount++;

             

             

            

            // sine wave

            /*

            SInt16 sample =  CFSwapInt16HostToBig((SInt16)SHRT_MAX * sin(2 * M_PI * (i/wavelengthInSamples)));

            audioErr = AudioFileWriteBytes(audioFile, false, sampleCount*2, &bytesToWrite, &sample);

            assert(audioErr == noErr);

            

             sampleCount++ ;

             */

        }

    }

    

    audioErr = AudioFileClose(audioFile);

    assert(audioErr == noErr);

    NSLog(@"wrote %ld samples " , sampleCount);

    


    

    return 0;

}



//출력

/*

 2018-11-06 16:49:33.249486+0900 2[1772:475724] generating 4545.000000 hz tone

 2018-11-06 16:49:33.250084+0900 2[1772:475724] path: file:///Users/service/Library/Developer/Xcode/DerivedData/2-ghsrtbjzgtcjceddqdfgtajtvyql/Build/Products/Debug/4545.000-saw.aif

 2018-11-06 16:49:33.250454+0900 2[1772:475724] wavelengthInSameple = 9.702970

 2018-11-06 16:49:34.043224+0900 2[1772:475724] wrote 220500 samples

 Program ended with exit code: 0

 해당 경로에 위에서 만든 톤의 파일이 만들어 진다.

 */



ios objective c 코어오디오 다루기 1



오디오 파일을 불러와서 속성 정보를 출력하는 간단한 로직이다.

코어오디오 api를 사용하기 위해서 AudioToolbox 라이브러리를 추가해줘야한다.

(왼쪽 네비게이션에서 프로젝트 아이콘 클릭 -> Build Phasses -> Link Binary With Libraries -> + 버튼 클릭 -> 찾아서 추가해줌)


//

//  main.m

//  1

//

//  Created by service on 2018. 11. 6..

//  Copyright © 2018년 service. All rights reserved.

//

/*

 파일을 열고,

 메타데이터에 대한 버퍼 할당

 메타데이터를 갖는다.

 */

#import <Foundation/Foundation.h>

#import <AudioToolbox/AudioToolbox.h>

//파일경로 : "~/Music/iTunes/iTunes Media/Music/Unknown Artist/Unknown Album/test_cbr.mp3"

//editschema 를열고 위의 파일 경로를 인자로 넣어준다.

//첫번째 문자열 : 실행파일의 이름, 두번째 문자열: 오디오 파일의 경로

int main(int argc, const char * argv[]) {

    


    @autoreleasepool {

        //오디오 파일의 경로 확인

        if (argc < 2) {

            printf("usage: CAMetadata /full/path/to/audiofile\n");

            return -1;

        }

        

        //경로가 있다면 여러가지 애플의 프레임워크에서 사용하는 nsstring 으로 변환.홈디렉토리의 단축형인 틸드 문자를 ~/music/ 같이 수용

        NSString *audioFilePath = [[NSString stringWithUTF8String:argv[1]] stringByExpandingTildeInPath];

        

        //오디오 파일 api는 파일 경로의 url 표현으로 동작한다.

        NSURL *audioURL =  [NSURL fileURLWithPath:audioFilePath];

        

        NSLog(@"audioURL - 경로 : %@" , audioURL);

      

        //코어 오디오는 오디오 파일 객체를 참조하는 audioFileID형식을 사용하기때문에 지역변수 선언

        AudioFileID audioFile;

        //코어 오디오 함수는 ossstatus형인 반환 값을 통해서 성공,실패전달. noErr(0값)을 제외한 다른 상태는 오류

        OSStatus theErr = noErr;

        

        //첫번째 코어오디오 함수: CFURLRef , 파일권한 플래그, 파일형식 힌트, AudioFileID의 포인터를 인자로 받는다.

        theErr = AudioFileOpenURL((__bridge CFURLRef)audioURL, kAudioFileReadPermission, 0, &audioFile);

        NSLog(@"theErr - 값 : %d" , (int)theErr);    //0

        //오류나면 종료

        assert(theErr == noErr);

        

        

        //파일의 메타데이터를 얻기위해서 kAudioFilePropertyInfoDictionary호출 할것이다.

        //사전에 반환되는 메타데이터를 위한 메모리를 할당할 지역변수 선언

        UInt32 dictionarySize = 0;

        //필요한 크기를 얻기위해 audiofileid, 속성, 결과를 받을 포인터, 속성이 쓰기 가능한 여부를 나타내는 플래그 변수를 전달.

        theErr = AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyInfoDictionary, &dictionarySize, 0);

        assert(theErr == noErr);

        

        

        CFDictionaryRef dictionary;

        //속성을 요청할 준비가 됨.

        theErr = AudioFileGetProperty(audioFile, kAudioFilePropertyInfoDictionary, &dictionarySize, &dictionary);

        assert(theErr == noErr);

        

        

        NSLog(@"dictionary 값출력 : %@", dictionary);

        CFRelease(dictionary);

        theErr = AudioFileClose(audioFile);

        assert(theErr == noErr);

        

        

        /*

         출력내용:

         2018-11-06 10:35:46.640373+0900 1[703:115853]

            audioURL - 경로 : file:///Users/service/Music/iTunes/iTunes%20Media/Music/Unknown%20Artist/Unknown%20Album/test_cbr.mp3

         2018-11-06 10:35:46.641179+0900 1[703:115853]

            dictionary 값출력 : {

                "approximate duration in seconds" = "52.86";

            }

         

         

         */

        

    }

    

    

    

    

    

    

    return 0;

}

목표

node js 로 서버를 만들고 nssession을 이용해서 통신하기


순서

1.nods js 로 서버를 구동시킨다.

2.xcode storyboard 뷰를 만든다.

3.Viewcontroller.h 에 NSURLConnectionDelegate를 상속받는다. 

4.Viewcontroller 에 nssession 을이용해서 서버와 통신을 한다.






node js 서버

(node js 로 구성한 초간단 서버다. get,post 방식으로 접속했을때 아래 로직을 타고, 이름과 비밀번호를 json방식으로 리턴하는 로직이다.

자세한건 node js  카테고리 참조.)

// 라우터를 설정합니다.
app.get('/test', function (request, response) {
// 응답합니다.
response.json({username:request.query.username,password:request.query.password});
});
app.post('/test', function (request, response) {
// 변수를 선언합니다.
var username = request.body.username;
var password = request.body.password;
response.json({username:username,password:password});


});

서버를 실행하면


실행되는것을 확인하고 웹브라우저에 한번 입력해본다.


이로써 초간단 서버가 만들어 졌다. xcode로 view를 만들고 viewcontroller부분을 만들어보자.

storyboard.main



viewcontroller.h

이 파일에 위에 스토리보드에서 만든 엘리먼트들을 연결했다. 잘확인~!

viewcontroller.m


//

//  ViewController.m

//  Http

//

//  Created by 위피아 on 26/10/2018.

//  Copyright © 2018 위피아. All rights reserved.

//


#import "ViewController.h"


@interface ViewController ()


@end



@implementation ViewController{


    NSMutableData *mutableData;

    

#define URL @"http://10.47.212.74:3000/test"

    #define NO_CONNECTION @"no connection"

    #define NO_VALUES @"please enter parameter values"

    

}

@synthesize username,password,serverResponse;


- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

}


- (IBAction)sendDataUsingGet:(id)sender {

    [self sendDataToServer:@"GET"];

}



- (IBAction)sendDataUsingPost:(id)sender {

    [self sendDataToServer:@"POST"];

}


//버튼 클릭 했을때

- (void)sendDataToServer:(NSString *)method{

    //입력한 이름과 비밀번호 할당

    NSString *usrname = username.text;

    NSString *pass = password.text;

    

    //값이 존재하면

    if(usrname.length > 0 && pass.length >0){

        serverResponse.text = @"getting response from server";

        

        if([method isEqualToString:@"GET"]){

            //get 방식일때

            NSString *getURL = [NSString stringWithFormat:@"%@?username=%@&password=%@",URL,usrname,pass];


            NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];

            [request setURL:[NSURL URLWithString:getURL]];

            [request setHTTPMethod:@"GET"];

            

            NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

            [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

                

                if (data!=nil)

                {

                    //NSString *requestReply = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

                    //NSLog(@"Request reply: %@", requestReply);

                    NSDictionary* json = [NSJSONSerialization

                                          JSONObjectWithData:data

                                          options:kNilOptions

                                          error:&error];

                    

                    //                    for (NSDictionary *project in json[@"projects"]) {

                    //                        NSLog(@"Project name: %@", project[@"name"]);

                    //                    }

                    

                    NSLog(@"username %@",  [json objectForKey:@"username"]);

                    NSLog(@"password %@", [json objectForKey:@"password"]);

                    

                }

                else

                {

                    NSLog(@"error");

                    

                }

                

                

                

            }] resume];

            

        }else{

            

            //전송방식이 post일때~!

            

            // 기본 구성에 URLSession 생성

            NSURLSessionConfiguration *defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];

            NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultSessionConfiguration];

            

            // request URL 설정

            NSURL *url = url = [NSURL URLWithString: URL];

            NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];

            

            // UTF8 인코딩을 사용하여 POST 문자열 매개 변수를 데이터로 변환

            NSString *postParams = [NSString stringWithFormat:@"username=%@&password=%@",usrname,pass];

            NSData *postData = [postParams dataUsingEncoding:NSUTF8StringEncoding];

            

            // 셋팅

            [urlRequest setHTTPMethod:@"POST"];

            [urlRequest setHTTPBody:postData];

            

            // dataTask 생성

            NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

                if (data!=nil)

                {

                    NSDictionary* json = [NSJSONSerialization

                                          JSONObjectWithData:data

                                          options:kNilOptions

                                          error:&error];


//                    for (NSDictionary *project in json[@"projects"]) {

//                        NSLog(@"Project name: %@", project[@"name"]);

//                    }

                    

                    NSLog(@"username %@",  [json objectForKey:@"username"]);

                    NSLog(@"password %@", [json objectForKey:@"password"]);

                    

                }

                else

                {

                    NSLog(@"error");

                   

                }

                

            }];

            

            // request 종료

            [dataTask resume];

            

        }


    }else

    {

        serverResponse.text = NO_VALUES;

    }

    

}



@end


각 소스 설명은 주석을 달아 놓았다. 기기를 빌드해서 실행해보자~ 잘됨!


  1. 2018.10.30 09:19

    비밀댓글입니다

Ios 그림파일을 텍스트로 바꾸기



참고

1.

https://github.com/gali8/Tesseract-OCR-iOS/wiki/Using-Tesseract-OCR-iOS


2.

https://github.com/tesseract-ocr/tessdata/tree/bf82613055ebc6e63d9e3b438a5c234bfd638c93




순서


1 페이지에서 해당 소스를 pod 인스톨 한다.


전환할 텍스트가 있는 그림파일을 프로젝트에 넣어준다.


프로젝트에 tessdata 라는 이름의 폴더를 만들어주고 해당 언어의 파일을 넣어준다.

(해당언어의 파일은 참고 2. 페이지에서 다운로드 가능하다.)


샘플 소스를 viewdidload 부분에 적는다.


변환할 사진을 보여줄 uiviewimage 객체 하나


변환된 text 파일을 뿌려줄 uitextview 객체 하나를 만들어서 iboutlet으로 연결시켜놓는다.


실행해서 확인






  1. 2018.10.30 09:23

    비밀댓글입니다

+ Recent posts

티스토리 툴바