1. 이전 포스팅 확인하기

 

https://growingsaja.tistory.com/912

 

[Objective-C] 앱 만들기 입문 - 16 : 앱 스크롤로 화면 이동한 후 화면 데이터가 다시 불러와질 경우

1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/911 [Objective-C] 앱 만들기 입문 - 15 : 싱글톤 적용 및 기타 코드 정리 1. 이전 포스팅 확인하기 https://growingsaja.tistory.com/901 [Objective-C] 앱 만들기 입문

growingsaja.tistory.com

 

 

 

 

 

2. API Call 기능을 싱글톤으로 구현하는 것에 대한 설명

 

 싱글톤 패턴을 사용하여 Objective-C에서 API 호출 기능을 구현하는 것은 일반적인 방법입니다.

 

 - 싱글톤 패턴을 사용할 때 장점들은 다음과 같습니다

  a. 전역 접근: 싱글톤 객체를 사용할 경우, API 호출 관련 코드를 전역으로 접근 가능하므로 편리합니다. 각기 다른 클래스 간에 동일한 인스턴스를 공유할 수 있습니다.

  b. 메모리 및 자원 효율: 싱글톤 인스턴스는 애플리케이션 내에서 한 번만 생성되고 재생성되지 않기 때문에 메모리 및 기타 자원을 절약할 수 있습니다.

  c. 일관성: API 호출 시 서버 연결 정보, 인증토큰, 및 핸들러와 같은 일관된 설정을 사용하도록 쉽게 관리할 수 있습니다.

 

 - 싱글톤 패턴을 사용할 때 주의할 점들은 다음과 같습니다.

  a. 멀티 스레딩: 싱글톤의 멀티스레딩 처리가 적절하지 않으면 동시 요청이 발생할 때 크래시나 정보 누수와 같은 문제가 발생할 수 있습니다. 하지만 동기화 메커니즘과 디스패치 큐(dispatch queue)를 적절하게 사용하여 관리하는 것으로 해결할 수 있습니다.

  b. 테스트 용이성: 싱글톤 패턴은 객체 간의 의존성이 높아져 단위 테스트가 어려워질 수 있습니다. 따라서 의존 주입과 같은 개념을 사용하여 객체 간의 의존성을 관리해야 합니다.

 

 이러한 장단점을 고려하여, 사용하려는 시나리오에 따라 싱글톤 패턴의 적합성을 결정할 수 있습니다. 일반적으로 싱글톤 패턴을 사용하여 API 호출 기능을 구현하는 것은 추천할만한 옵션입니다. 하지만 개발 중인 애플리케이션의 요구사항과 제약사항을 고려하여 처리 방식을 선택해야 합니다.

 

 

 

 

 

3. APIManager 파일 작성

 

// vim APIManager.h

// CRTConnect라는 이름의 클래스를 선언하고, NSObject 클래스를 상속받습니다. NSObject는 모든 Objective-C 클래스에서 사용되는 기본 클래스입니다.
@interface APIManager : NSObject

// 선언된 메서드를 사용해 API 호출을 하고, 결과를 completionHandler로 반환합니다.
// fetchDataFromAPI:withCompletionHandler: 라는 메서드를 선언합니다. 이 메서드는 다음과 같이 정의되어 있습니다:
// 매서드 자체 반환값: void, 반환값이 없습니다.
// 매개변수1: (NSString *)apiURL: 호출할 API의 URL을 넘겨줍니다. 이는 문자열 (NSString)로 전달됩니다.
// 매개변수2: withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler: 결과를 반환할 때 호출할 completionHandler 블록입니다. 이 블록은 두 가지 인자를 받습니다:
// 매개변수2-1: NSDictionary *jsonResponse: API에서 반환된 JSON 데이터를 NSDictionary 형식으로 변환한 객체입니다.
// 매개변수2-2: NSError *error: API 호출 중 발생한 오류를 포함하는 NSError 객체입니다. 오류가 없는 경우, 이 값은 nil입니다.
+(instancetype)sharedInstance;
- (void)fetchDataFromAPI:(NSString *)apiURL withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler;

// CRTConnect 클래스의 인터페이스 선언을 종료합니다.
@end

// 이 클래스는 CRTConnect 객체를 사용하여 API를 호출하고, 호출 결과를 처리할 수 있는 기능을 제공하도록 설계되어 있습니다. NSData를 사용하여 API를 호출한 후, 결과를 딕셔너리 형태로 파싱하고 completionHandler에 전달하는 구현이 필요합니다.

 

// vim APIManager.m

// Foundation 프레임워크를 임포트합니다. Foundation은 많은 기본적인 Objective-C 클래스와 인터페이스를 포함하고 있습니다.
#import <Foundation/Foundation.h>
#import "APIManager.h"

@implementation APIManager

+(instancetype)sharedInstance {
    static APIManager *sharedInstance = nil;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    
    return sharedInstance;
}

- (void)fetchDataFromAPI:(NSString *)apiURL withCompletionHandler:(void (^)(NSDictionary *jsonResponse, NSError *error))completionHandler {
    // NSURLSession을 사용해 API 호출을 준비합니다.
    NSURL *url = [NSURL URLWithString:apiURL];
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            // 네트워크 오류 발생시 completionHandler에 에러 정보를 전달합니다.
            NSLog(@"Error: %@", error.localizedDescription);
            completionHandler(nil, error);
            return;
        } else {
            NSError *jsonError;
            // JSON 데이터를 NSDictionary 객체로 변환합니다.
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
            
            if (jsonError) {
                // JSON 파싱 오류 발생시 completionHandler에 에러 정보를 전달합니다.
                completionHandler(nil, jsonError);
                NSLog(@"JSON parsing error: %@", jsonError.localizedDescription);
                return;
            } else {
                // 정상적으로 파싱된 경우 completionHandler에 결과를 전달합니다.
                completionHandler(jsonResponse, nil);
            }
        }
    }];
    
    // URL 세션 태스크를 시작합니다.
    [dataTask resume];
}

@end

 

 

 

 

 

4. 활용 예제

 

 - 변경 전 주요 코드

// 데이터 가져오기 및 뷰 업데이트 코드
- (void)updateCardData {
    
    // **************************************** [Start] api 콜 준비 **************************************** //
    CRTConnect* tryApiCall = [[CRTConnect alloc] init];
    // **************************************** [Start] Binance 데이터 가져오기 **************************************** //
    NSString *apiURL = @"https://api.binance.com/api/v3/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // Binance는 api return data가 array
            NSArray* resultOfApi = (NSArray *)jsonResponse;
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                if ([price floatValue] >= 1000) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-6)];
                } else if ([price floatValue] >= 100) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-5)];
                } else if ([price floatValue] >= 1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-4)];
                } else if ([price floatValue] >= 0.1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-3)];
                } else if ([price floatValue] >= 0.01) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-2)];
                } else if ([price floatValue] >= 0.001) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-1)];
                } else {
                    // price값 그대로 가져다씁니다.
                }

                self->popularSpotPriceList[@"Binance"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Binance 데이터 가져오기 **************************************** //
    // **************************************** [Start] Bybit 데이터 가져오기 **************************************** //
    apiURL = @"https://api.bybit.com/spot/quote/v1/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // result 안의 value만 추출
            NSArray* resultOfApi = jsonResponse[@"result"];
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                self->popularSpotPriceList[@"Bybit"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Bybit 데이터 가져오기 **************************************** //
    // 메인 스레드에서만 UI 업데이트 수행
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateView];
    });
}

 

 - 변경 후 주요 코드

// 데이터 가져오기 및 뷰 업데이트 코드
- (void)updateCardData {
    
    // **************************************** [Start] api 콜 준비 **************************************** //
    APIManager* tryApiCall = [APIManager sharedInstance];
    // **************************************** [Start] Binance 데이터 가져오기 **************************************** //
    NSString *apiURL = @"https://api.binance.com/api/v3/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // Binance는 api return data가 array
            NSArray* resultOfApi = (NSArray *)jsonResponse;
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                if ([price floatValue] >= 1000) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-6)];
                } else if ([price floatValue] >= 100) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-5)];
                } else if ([price floatValue] >= 1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-4)];
                } else if ([price floatValue] >= 0.1) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-3)];
                } else if ([price floatValue] >= 0.01) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-2)];
                } else if ([price floatValue] >= 0.001) {
                    price = [price substringWithRange:NSMakeRange(0, price.length-1)];
                } else {
                    // price값 그대로 가져다씁니다.
                }

                self->popularSpotPriceList[@"Binance"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Binance 데이터 가져오기 **************************************** //
    // **************************************** [Start] Bybit 데이터 가져오기 **************************************** //
    apiURL = @"https://api.bybit.com/spot/quote/v1/ticker/price";
    [tryApiCall fetchDataFromAPI:apiURL withCompletionHandler:^(NSDictionary *jsonResponse, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error.localizedDescription);
        } else {
            // 여기에서 jsonResponse를 가공 한 후 앱에서 사용하실 수 있습니다.
            // result 안의 value만 추출
            NSArray* resultOfApi = jsonResponse[@"result"];
            
            // api로 받은 데이터 깔끔한 dictionary로 가공하기
            for (int i=0; i<resultOfApi.count; i++) {
                NSString *symbol = resultOfApi[i][@"symbol"];
                NSString *price = resultOfApi[i][@"price"];
                self->popularSpotPriceList[@"Bybit"][symbol] = price;
            }
        }
    }];
    // **************************************** [End] Bybit 데이터 가져오기 **************************************** //
    // 메인 스레드에서만 UI 업데이트 수행
    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateView];
    });
}

 

 

 

 

+ Recent posts