iOS용 드라이버 SDK 시작하기

Driver SDK는 드라이버 앱에 통합되는 라이브러리입니다. 운전자의 위치, 경로, 남은 거리, 예상 도착시간을 사용하여 Fleet Engine을 업데이트합니다. 또한 Navigation SDK와 통합되어 운전자에게 세부 경로 안내 내비게이션 안내를 제공합니다.

최소 시스템 요구사항

  • 휴대기기에서 iOS 14 이상을 실행해야 합니다.
  • Xcode 버전 15 이상
  • 기본 요건

    이 가이드에서는 앱에서 이미 Navigation SDK를 구현하고 Fleet Engine 백엔드가 설정되어 사용 가능하다고 가정합니다. 그러나 예시 코드에서는 Navigation SDK를 설정하는 방법의 샘플을 제공합니다.

    또한 Google Cloud 프로젝트에서 iOS용 Maps SDK를 사용 설정하고 API 키 가져오기를 해야 합니다.

    액세스하기

    Google Workspace 고객인 경우 온보딩 중에 google-maps-platform-sdk-users@workspacedomain.com와 같은 작업공간 그룹을 만들고 Google에 이름을 제공합니다. 이것이 권장되는 방법입니다. 그러면 올바른 CocoaPods 저장소에 대한 액세스 권한을 부여하는 허용 목록에 작업공간 그룹이 추가됩니다. 액세스 권한이 필요한 사용자 이메일 및 서비스 계정 이메일이 이 목록에 포함되어 있는지 확인합니다.

    조직에서 작업공간 그룹을 만들 수 없는 경우 이러한 아티팩트에 액세스해야 하는 사용자 및 서비스 계정 이메일 목록을 Google에 전송합니다.

    로컬 개발

    로컬 개발의 경우 Cloud SDK로 로그인하는 것만으로 충분합니다.

    gcloud

    gcloud auth login
    

    로그인하는 데 사용하는 이메일이 Workspace 그룹의 구성원이어야 합니다.

    자동화 (빌드 시스템 또는 지속적 통합)

    권장사항에 따라 자동화 호스트를 설정합니다.

    • 프로세스가 Google Cloud 환경 내에서 실행되는 경우 자동 사용자 인증 정보 감지를 사용합니다.

    • 그렇지 않으면 서비스 계정 키 파일을 호스트 파일 시스템의 안전한 위치에 저장하고 GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 적절하게 설정합니다.

    사용자 인증 정보와 연결된 서비스 계정 이메일이 Workspace Goup의 구성원이어야 합니다.

    프로젝트 구성

    CocoaPods를 사용하여 드라이버 SDK를 구성할 수 있습니다.

    CocoaPods 사용

    CocoaPods를 사용하여 드라이버 SDK를 구성하려면 다음 항목이 필요합니다.

    • CocoaPods 도구: 이 도구를 설치하려면 터미널을 열고 다음 명령어를 실행합니다. shell sudo gem install cocoapods자세한 내용은 CocoaPods 시작 가이드를 참고하세요.
    1. 드라이버 SDK용 Podfile을 만들고 이를 사용하여 API와 종속 항목을 설치합니다. 프로젝트 디렉터리에 Podfile이라는 파일을 만듭니다. 이 파일은 프로젝트의 종속 항목을 정의합니다. Podfile을 수정하고 종속 항목을 추가합니다 다음은 종속 항목을 포함하는 예입니다.

      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      
    2. Podfile을 저장합니다. 터미널을 열고 Podfile이 포함된 디렉터리로 이동합니다.

      cd <path-to-project>
      
    3. pod install 명령어를 실행합니다. 그러면 Podfile에 지정된 API와 함께 해당 종속 항목이 설치됩니다.

      pod install
      
    4. Xcode를 닫은 다음 프로젝트의 .xcworkspace 파일을 열어 (더블클릭) Xcode를 실행합니다. 지금부터는 .xcworkspace 파일을 사용하여 프로젝트를 열어야 합니다.

    알파/베타 SDK 버전

    iOS용 드라이버 SDK의 알파 또는 베타 버전을 구성하려면 다음 항목이 필요합니다.

    • CocoaPods 도구: 이 도구를 설치하려면 터미널을 열고 다음 명령어를 실행합니다.

      sudo gem install cocoapods
      

      자세한 내용은 CocoaPods 시작 가이드를 참고하세요.

    • Google 액세스 목록에 있는 개발 계정 SDK 알파 및 베타 버전의 포드 저장소는 공개 소스가 아닙니다. 이 버전에 액세스하려면 Google 고객 엔지니어에게 문의하세요. 엔지니어는 개발자의 개발 계정을 액세스 목록에 추가한 후 인증을 위한 쿠키를 설정합니다.

    프로젝트가 액세스 목록에 표시되면 포드에 액세스할 수 있습니다.

    1. iOS용 드라이버 SDK용 Podfile을 만들고 이를 사용하여 API와 종속 항목을 설치합니다. 프로젝트 디렉터리에 Podfile이라는 파일을 만듭니다. 이 파일은 프로젝트의 종속 항목을 정의합니다. Podfile을 수정하고 종속 항목을 추가합니다 다음은 종속 항목을 포함하는 예입니다.

      source "https://cpdc-eap.googlesource.com/ridesharing-driver-sdk.git"
      source "https://github.com/CocoaPods/Specs.git"
      
      target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
        pod 'GoogleRidesharingDriver'
      end
      
    2. Podfile을 저장합니다. 터미널을 열고 Podfile이 포함된 디렉터리로 이동합니다.

      cd <path-to-project>
      
    3. pod install 명령어를 실행합니다. 그러면 Podfile에 지정된 API와 함께 해당 종속 항목이 설치됩니다.

      pod install
      
    4. Xcode를 닫은 다음 프로젝트의 .xcworkspace 파일을 열어 (더블클릭) Xcode를 실행합니다. 지금부터는 .xcworkspace 파일을 사용하여 프로젝트를 열어야 합니다.

    XCFramework 설치

    XCFramework는 드라이버 SDK를 설치하는 데 사용하는 바이너리 패키지입니다. 이 패키지는 M1 칩셋을 사용하는 머신을 포함한 여러 플랫폼에서 사용할 수 있습니다. 이 가이드에서는 Driver SDK가 포함된 XCFramework를 프로젝트에 수동으로 추가하고 Xcode에서 빌드 설정을 구성하는 방법을 보여줍니다.

    SDK 바이너리 및 리소스를 다운로드합니다.

    1. 압축된 파일의 압축을 풀어 XCFramework 및 리소스에 액세스합니다.

    2. Xcode를 시작하고 기존 프로젝트를 열거나 새 프로젝트를 만듭니다. iOS를 처음 사용하는 경우 새 프로젝트를 만들고 iOS 앱 템플릿을 선택합니다.

    3. 프레임워크 그룹이 아직 없는 경우 프로젝트 그룹 아래에 이 그룹을 만듭니다.

    4. 다운로드한 gRPCCertificates.bundle 파일을 Xcode 프로젝트의 최상위 디렉터리로 드래그합니다. 메시지가 표시되면 필요한 경우 항목 복사를 선택합니다.

    5. 드라이버 SDK를 설치하려면 GoogleRidesharingDriver.xcframework 파일을 프레임워크, 라이브러리 및 삽입된 콘텐츠 아래의 프로젝트로 드래그합니다. 메시지가 표시되면 필요한 경우 항목 복사를 선택합니다.

    6. 다운로드한 GoogleRidesharingDriver.bundle를 Xcode 프로젝트의 최상위 디렉터리로 드래그합니다. 메시지가 표시되면 Copy items if needed를 선택합니다.

    7. 프로젝트 탐색기에서 프로젝트를 선택하고 애플리케이션의 대상을 고릅니다.

    8. 빌드 단계 탭을 열고, 바이너리를 라이브러리와 연결에서 다음 프레임워크와 라이브러리가 아직 없다면 추가합니다.

      • Accelerate.framework
      • AudioToolbox.framework
      • AVFoundation.framework
      • CoreData.framework
      • CoreGraphics.framework
      • CoreLocation.framework
      • CoreTelephony.framework
      • CoreText.framework
      • GLKit.framework
      • ImageIO.framework
      • libc++.tbd
      • libxml2.tbd
      • libz.tbd
      • LocalAuthentication.framework
      • OpenGLES.framework
      • QuartzCore.framework
      • SystemConfiguration.framework
      • UIKit.framework
      • WebKit.framework
    9. 특정 대상 대신 자신의 프로젝트를 선택하고 빌드 설정 탭을 엽니다. 기타 링커 플래그 섹션에서 디버그 및 릴리스 모두를 위한 ‑ObjC를 추가합니다. 이러한 설정이 표시되지 않으면 빌드 설정 표시줄에서 필터를 기본에서 전체로 변경합니다.

    승인 및 인증 구현

    드라이버 앱에서 업데이트를 생성하고 Fleet Engine 백엔드로 전송하면 요청에 유효한 액세스 토큰이 포함되어야 합니다. 이러한 요청을 승인하고 인증하기 위해 드라이버 SDK는 GMTDAuthorization 프로토콜에 따라 객체를 호출합니다. 객체는 필요한 액세스 토큰을 제공하는 역할을 합니다.

    앱 개발자는 토큰이 생성되는 방식을 선택합니다. 구현을 통해 다음 작업을 할 수 있어야 합니다.

    • HTTPS 서버에서 액세스 토큰(JSON 형식)을 가져옵니다.
    • 토큰을 파싱하고 캐시합니다.
    • 토큰이 만료되면 새로고침합니다.

    Fleet Engine 서버에서 예상하는 토큰에 대한 자세한 내용은 승인을 위한 JSON 웹 토큰 (JWT) 만들기를 참조하세요.

    제공업체 ID는 Google Cloud 프로젝트 ID와 동일합니다. 자세한 내용은 Fleet Engine Deliveries API 사용자 가이드를 참고하세요.

    다음 예에서는 액세스 토큰 제공자를 구현합니다.

    #import "SampleAccessTokenProvider.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    // SampleAccessTokenProvider.h
    @interface SampleAccessTokenProvider : NSObject<GMTDAuthorization>
    @end
    
    static NSString *const PROVIDER_URL = @"INSERT_YOUR_TOKEN_PROVIDER_URL";
    
    // SampleAccessTokenProvider.m
    @implementation SampleAccessTokenProvider{
      // The cached vehicle token.
      NSString *_cachedVehicleToken;
      // Keep track of the vehicle ID the cached token is for.
      NSString *_lastKnownVehicleID;
      // Keep track of when tokens expire for caching.
      NSTimeInterval _tokenExpiration;
    }
    
    - (void)fetchTokenWithContext:(nullable GMTDAuthorizationContext *)authorizationContext
                       completion:(nonnull GMTDAuthTokenFetchCompletionHandler)completion {
      if (!completion) {
        NSAssert(NO, @"%s encountered an unexpected nil completion.", __PRETTY_FUNCTION__);
        return;
      }
    
      // Get the vehicle ID from the authorizationContext. This is set by the Driver SDK.
      NSString *vehicleID = authorizationContext.vehicleID;
      if (!vehicleID) {
        NSAssert(NO, @"Vehicle ID is missing from authorizationContext.");
        return;
      }
    
    // Clear cached vehicle token if vehicle ID has changed.
      if (![_lastKnownVehicleID isEqual:vehicleID]) {
        _tokenExpiration = 0.0;
        _cachedVehicleToken = nil;
      }
      _lastKnownVehicleID = vehicleID;
    
      // Clear cached vehicle token if it has expired.
      if ([[NSDate date] timeIntervalSince1970] > _tokenExpiration) {
        _cachedVehicleToken = nil;
      }
    
      // If appropriate, use the cached token.
      if (_cachedVehicleToken) {
        completion(_cachedVehicleToken, nil);
        return;
      }
      // Otherwise, try to fetch a new token from your server.
      NSURL *requestURL = [NSURL URLWithString:PROVIDER_URL];
      NSMutableURLRequest *request = 
                              [[NSMutableURLRequest alloc] initWithURL:requestURL];
      request.HTTPMethod = @"GET";
      // Replace the following key values with the appropriate keys based on your
      // server's expected response.
      NSString *vehicleTokenKey = @"VEHICLE_TOKEN_KEY";
      NSString *tokenExpirationKey = @"TOKEN_EXPIRATION";
      __weak typeof(self) weakSelf = self;
      void (^handler)(NSData *_Nullable data, NSURLResponse *_Nullable response,
                      NSError *_Nullable error) =
          ^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
            typeof(self) strongSelf = weakSelf;
            if (error) {
              completion(nil, error);
              return;
            }
    
            NSError *JSONError;
            NSMutableDictionary *JSONResponse =
                [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&JSONError];
    
            if (JSONError) {
              completion(nil, JSONError);
              return;
            } else {
              // Sample code only. No validation logic.
              id expirationData = JSONResponse[tokenExpirationKey];
              if ([expirationData isKindOfClass:[NSNumber class]]) {
                NSTimeInterval expirationTime = ((NSNumber *)expirationData).doubleValue;
                strongSelf->_tokenExpiration = [[NSDate date] timeIntervalSince1970] + expirationTime;
              }
              strongSelf->_cachedVehicleToken = JSONResponse[vehicleTokenKey];
              completion(JSONResponse[vehicleTokenKey], nil);
            }
        };
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *mainQueueURLSession =  
           [NSURLSession  sessionWithConfiguration:config delegate:nil
    delegateQueue:[NSOperationQueue mainQueue]];
    NSURLSessionDataTask *task = [mainQueueURLSession dataTaskWithRequest:request completionHandler:handler];
    [task resume];
    }
    
    @end
    

    DeliveryDriverAPI 인스턴스 만들기

    GMTDDeliveryVehicleReporter 인스턴스를 가져오려면 먼저 providerID, vehicleID, driveContext, accessTokenProvider를 사용하여 GMTDDeliveryDriverAPI 인스턴스를 만들어야 합니다. providerID는 Google Cloud Project ID와 동일합니다. 또한 드라이버 API에서 직접 GMTDDeliveryVehicleReporter 인스턴스에 액세스할 수 있습니다.

    다음 예시에서는 GMTDDeliveryDriverAPI 인스턴스를 만듭니다.

    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    - (void)viewDidLoad {
      NSString *vehicleID = @"INSERT_CREATED_VEHICLE_ID";
      SampleAccessTokenProvider *accessTokenProvider = 
                                    [[SampleAccessTokenProvider alloc] init];
      GMTDDriverContext *driverContext = 
         [[GMTDDriverContext alloc] initWithAccessTokenProvider:accessTokenProvider
                                                     providerID:PROVIDER_ID 
                                                  vehicleID:vehicleID 
          navigator:_mapView.navigator];
    
      GMTDDeliveryDriverAPI *deliveryDriverAPI = [[GMTDDeliveryDriverAPI alloc] initWithDriverContext:driverContext];
    }
    

    원하는 경우 VehicleReporter 이벤트를 수신 대기합니다.

    GMTDDeliveryVehicleReporterlocationTrackingEnabled가 YES이면 주기적으로 차량을 업데이트합니다. 이러한 주기적 업데이트에 응답하기 위해 모든 객체는 GMTDVehicleReporterListener 프로토콜을 준수하여 GMTDDeliveryVehicleReporter 이벤트를 수신할 수 있습니다.

    다음과 같은 이벤트를 처리할 수 있습니다.

    • vehicleReporter:didSucceedVehicleUpdate

      백엔드 서비스가 차량 위치 및 상태 업데이트를 성공적으로 수신했다고 드라이버 앱에 알립니다.

    • vehicleReporter:didFailVehicleUpdate:withError

      리스너에 차량 업데이트 실패를 알립니다. 위치 추적이 사용 설정되어 있는 한 GMTDDeliveryVehicleReporter는 계속해서 최신 데이터를 Fleet Engine 백엔드로 전송합니다.

    다음 예에서는 이러한 이벤트를 처리합니다.

    SampleViewController.h
    @interface SampleViewController : UIViewController<GMTDVehicleReporterListener>
    @end
    
    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView;
    }
    
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      [ridesharingDriverAPI.vehicleReporter addListener:self];
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didSucceedVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate {
      // Handle update succeeded.
    }
    
    - (void)vehicleReporter:(GMTDDeliveryVehicleReporter *)vehicleReporter didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate withError:(NSError *)error {
      // Handle update failed.
    }
    
    @end
    

    위치 추적 사용 설정

    위치 추적을 사용 설정하려면 앱에서 GMTDDeliveryVehicleReporterlocationTrackingEnabledYES로 설정하면 됩니다. 그러면 GMTDDeliveryVehicleReporter는 위치 업데이트를 자동으로 전송합니다. GMSNavigator가 내비게이션 모드이고 (대상이 setDestinations를 통해 설정된 경우) locationTrackingEnabledYES로 설정된 경우 GMTDDeliveryVehicleReporter는 경로와 도착예정시간 업데이트도 자동으로 전송합니다.

    이러한 업데이트 중에 설정된 경로는 내비게이션 세션 중에 운전자가 이동하는 경로와 동일합니다. 따라서 배송 추적이 제대로 작동하려면 -setDestinations:callback:를 통해 설정된 경유지가 Fleet Engine 백엔드에 설정된 목적지와 일치해야 합니다.

    다음 예에서는 위치 추적을 사용 설정합니다.

    SampleViewController.m
    #import “SampleViewController.h”
    #import “SampleAccessTokenProvider.h”
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    static NSString *const PROVIDER_ID = @"INSERT_YOUR_PROVIDER_ID";
    
    @implementation SampleViewController {
     GMSMapView *_mapView; 
    }
    
    - (void)viewDidLoad {
      // ASSUMES YOU IMPLEMENTED HAVE THE SAMPLE CODE UP TO THIS STEP.
      deliveryDriverAPI.vehicleReporter.locationTrackingEnabled = YES;
    }
    
    @end
    

    기본적으로 보고 간격은 10초이지만 보고 간격은 locationUpdateInterval로 변경할 수 있습니다. 지원되는 최소 업데이트 간격은 5초입니다. 지원되는 최대 업데이트 간격은 60초입니다. 더 자주 업데이트하면 요청 속도가 느려지고 오류가 발생할 수 있습니다.

    위치 업데이트 사용 중지

    앱이 차량의 위치 업데이트를 사용 중지할 수 있습니다. 예를 들어 운전자 교대 근무가 끝나면 앱에서 locationTrackingEnabledNO로 설정할 수 있습니다.

      _vehicleReporter.locationTrackingEnabled = NO
    

    update_mask 오류 처리

    GMTDDeliveryVehicleReporter가 차량 업데이트를 전송하면 마스크가 비어 있을 때 update_mask 오류가 발생할 수 있으며 일반적으로 시작 후 첫 번째 업데이트에 발생합니다. 다음 예는 이 오류를 처리하는 방법을 보여줍니다.

    Swift

    import GoogleRidesharingDriver
    
    class VehicleReporterListener: NSObject, GMTDVehicleReporterListener {
      func vehicleReporter(
        _ vehicleReporter: GMTDVehicleReporter,
        didFail vehicleUpdate: GMTDVehicleUpdate,
        withError error: Error
      ) {
        let fullError = error as NSError
        if let innerError = fullError.userInfo[NSUnderlyingErrorKey] as? NSError {
          let innerFullError = innerError as NSError
          if innerFullError.localizedDescription.contains("update_mask cannot be empty") {
            emptyMaskUpdates += 1
            return
          }
        }
        failedUpdates += 1
      }
    
      override init() {
        emptyMaskUpdates = 0
        failedUpdates = 0
      }
    }
    
    

    Objective-C

    #import "VehicleReporterListener.h"
    #import <GoogleRidesharingDriver/GoogleRidesharingDriver.h>
    
    @implementation VehicleReporterListener {
      NSInteger emptyMaskUpdates = 0;
      NSInteger failedUpdates = 0;
    }
    
    - (void)vehicleReporter:(GMTDVehicleReporter *)vehicleReporter
      didFailVehicleUpdate:(GMTDVehicleUpdate *)vehicleUpdate
                 withError:(NSError *)error {
      for (NSError *underlyingError in error.underlyingErrors) {
        if ([underlyingError.localizedDescription containsString:@"update_mask cannot be empty"]) {
          emptyMaskUpdates += 1;
          return;
        }
      }
      failedUpdates += 1
    }
    
    @end