Пара слов об отладке приложений с App Store

в 6:27, , рубрики: App Store, iOS, marmalade, разработка под iOS, метки: , ,

Несмотря на то, что эмулятор, поставляемый в составе инструментальной среды Marmalade достаточно удобен и позволяет выполнить отладку всех мыслимых и немыслимых ситуаций, существует ряд задач с которыми он справиться не может. К таким задачам относится, например, отладка покупки продуктов из разрабатываемого приложения (через App Store или Android Market). Тестирование самой покупки обязательно должно выполняться на устройстве, но, часто бывает, что с покупкой связана логика приложения, которую было бы неплохо отладить под эмулятором. О том, как это сделать, ниже:


В качестве примера, рассмотрим приложение s3e/s3eIOSAppStoreBilling, поставляемое в составе examples к Marmalade. Если его запустить, то все что нам удастся увидеть — это модальное окно, с сообщением об ошибке «This example only works on iPhone with a test store set up for in-app purchase». Это конечно правильно, но нам то хочется отладить приложение до того, как мы его разместим на IPhone. Сделать это совсем не сложно. Мы разработаем заглушку, имитирующую (в отладочной сборке) поведение S3E iOS App Store Billing. Начнем с добавления наших файлов в mkb-файл:

#!/usr/bin/env mkb
files
{
    s3eIOSAppStoreBilling.cpp
    AppStoreStub.h
    AppStoreStub.cpp
}
...

Сам код заглушки примитивен. В h-файле для каждой функции App Store Billing мы доопределяем заглушку с суффиксом «Stub» и в случае если сборка отладочная, подменяем вызовы директивой #define:

#ifndef _APPSTORESTUB_H_
#define _APPSTORESTUB_H_

#include <string.h>
#include "s3eTypes.h"
#include "s3eIOSAppStoreBilling.h"

#if defined IW_DEBUG
#define s3eIOSAppStoreBillingAvailable   
               s3eIOSAppStoreBillingAvailableStub
#define s3eIOSAppStoreBillingGetInt  
               s3eIOSAppStoreBillingGetIntStub
#define s3eIOSAppStoreBillingInit 
               s3eIOSAppStoreBillingInitStub
#define s3eIOSAppStoreBillingStart 
               s3eIOSAppStoreBillingInitStub
#define s3eIOSAppStoreBillingTerminate 
               s3eIOSAppStoreBillingTerminateStub
#define s3eIOSAppStoreBillingStop 
               s3eIOSAppStoreBillingTerminateStub
#define s3eIOSAppStoreBillingRequestProductInformation  
               s3eIOSAppStoreBillingRequestProductInformationStub
#define s3eIOSAppStoreBillingCancelProductInformationRequests 
               s3eIOSAppStoreBillingCancelProductInformationRequestsStub
#define s3eIOSAppStoreBillingRequestPayment 
               s3eIOSAppStoreBillingRequestPaymentStub
#define s3eIOSAppStoreBillingCompleteTransaction 
               s3eIOSAppStoreBillingCompleteTransactionStub
#define s3eIOSAppStoreBillingRestoreCompletedTransactions 
               s3eIOSAppStoreBillingRestoreCompletedTransactionsStub
#endif

s3eBool   s3eIOSAppStoreBillingAvailableStub();
int32     s3eIOSAppStoreBillingGetIntStub(s3eIOSAppStoreBillingProperty property);
s3eResult s3eIOSAppStoreBillingInitStub(s3eProductInformationCallbackFn infoCallback,  
                                        s3ePaymentTransactionUpdateCallbackFn updateCallback,  
                                        void* userData);
void      s3eIOSAppStoreBillingTerminateStub();
s3eResult s3eIOSAppStoreBillingRequestProductInformationStub(const char** productIdentifiers,  
                                        uint32 numProductIdentifiers);
void      s3eIOSAppStoreBillingCancelProductInformationRequestsStub();
s3eResult s3eIOSAppStoreBillingRequestPaymentStub(s3ePaymentRequest* paymentRequest);
s3eResult s3eIOSAppStoreBillingCompleteTransactionStub(s3ePaymentTransaction*  transaction,  
                                        s3eBool finalise);
s3eResult s3eIOSAppStoreBillingRestoreCompletedTransactionsStub();
void      appStoreStubUpdate();
#endif	// _APPSTORESTUB_H_

Реализация этих функций также не содержит ничего сверхестественного:

#include "AppStoreStub.h"

#define PRODUCT_INFO_REQUESTED                               0x0001
#define TRANSACTION_REQUESTED                                0x0002

s3eProductInformationCallbackFn productInfoCallback          = NULL;
s3ePaymentTransactionUpdateCallbackFn transactionCallback    = NULL;
void* billingData                                            = NULL;

s3eProductInformation productInformation;
s3ePaymentTransaction transaction;
s3eTransactionReceipt receipt;
int eventMask = 0;

s3eBool s3eIOSAppStoreBillingAvailableStub() {
	return S3E_TRUE;
}

int32 s3eIOSAppStoreBillingGetIntStub(s3eIOSAppStoreBillingProperty property) {
	switch (property) {
		case S3E_IOSAPPSTOREBILLING_CAN_MAKE_PAYMENTS:
			return 1;
		default:
			return 0;
	}
}

s3eResult s3eIOSAppStoreBillingInitStub(s3eProductInformationCallbackFn infoCallback,  
                                        s3ePaymentTransactionUpdateCallbackFn updateCallback,  
										void* userData) {
	productInfoCallback = infoCallback;
	transactionCallback = updateCallback;
	billingData = userData;
	return S3E_RESULT_SUCCESS;
}

void s3eIOSAppStoreBillingTerminateStub() {}

s3eResult s3eIOSAppStoreBillingRequestProductInformationStub(const char** productIdentifiers,  
															 uint32 numProductIdentifiers) {
    if (numProductIdentifiers != 1) return S3E_RESULT_ERROR;
	memset(&productInformation, 0, sizeof(productInformation));
	strcpy(productInformation.m_ProductID, *productIdentifiers);
	strcpy(productInformation.m_LocalisedTitle, *productIdentifiers);
	strcpy(productInformation.m_LocalisedDescription, *productIdentifiers);
	strcpy(productInformation.m_FormattedPrice, "0.00");
	strcpy(productInformation.m_PriceLocale, "0.00");
	productInformation.m_Price = 0;
	productInformation.m_ProductStoreStatus = S3E_PRODUCT_STORE_STATUS_VALID;
    eventMask |= PRODUCT_INFO_REQUESTED;
	return S3E_RESULT_SUCCESS;
}

void s3eIOSAppStoreBillingCancelProductInformationRequestsStub() {}

s3eResult s3eIOSAppStoreBillingRequestPaymentStub(s3ePaymentRequest* paymentRequest) {
	memset(&transaction, 0, sizeof(transaction));
	transaction.m_TransactionStatus = S3E_PAYMENT_STATUS_PURCHASED;
	transaction.m_Request = paymentRequest;
	transaction.m_Retain = S3E_FALSE;
	transaction.m_TransactionReceipt = &receipt;
	strcpy(transaction.m_TransactionID, "1");
	strcpy(transaction.m_OriginalTransactionID, "1");
	memset(&receipt, 0, sizeof(receipt));
	receipt.m_ReceiptSize = 0;
	eventMask |= TRANSACTION_REQUESTED;
	return S3E_RESULT_ERROR;  // ???
}

s3eResult s3eIOSAppStoreBillingCompleteTransactionStub(s3ePaymentTransaction*  transaction,  
													   s3eBool finalise) {
	return S3E_RESULT_SUCCESS;
}

s3eResult s3eIOSAppStoreBillingRestoreCompletedTransactionsStub() {
	return S3E_RESULT_SUCCESS;
}

void appStoreStubUpdate() {
#if defined IW_DEBUG
	if (eventMask != 0) {
		if (eventMask & PRODUCT_INFO_REQUESTED) {
			productInfoCallback(&productInformation, billingData);
		}
		if (eventMask & TRANSACTION_REQUESTED) {
			transactionCallback(&transaction, billingData);
		}
		eventMask = 0;
	}
#endif
}

Здесь следует упомянуть о функции appStoreStubUpdate. Дело в том, что App Store Billing API использует асинхронные вызовы для работы с магазином. Это означает, что вызовы callback функций должны выполняться вне контекста вызова функций запроса информации о продукте s3eIOSAppStoreBillingRequestProductInformation и совершения покупки s3eIOSAppStoreBillingRequestPayment. Эти вызовы мы будем выполнять в appStoreStubUpdate, вызывая последнюю из update-функции Maramalade-приложения.

Также, может вызывать вопросы код возврата s3eIOSAppStoreBillingRequestPaymentStub. Честно говоря, мне самому непонятен этот момент. По логике вещей, при успешном выполнении она должна возвращать S3E_RESULT_SUCCESS (равное 0), но в примере используется следующая проверка корректности формирования запроса:

            if (s3eIOSAppStoreBillingRequestPayment(&g_PaymentRequest))
                SetStatus("Purchasing %s...", g_ProductID2);
            else
                SetStatus("Purchasing %s FAILED", g_ProductID2);

То есть, ожидается ненулевое значение. По всей видимости, разработчики примера допустили ошибку, но на этот момент стоит обратить внимание, при отладке приложения на iPhone.

Последнее, о чем следует упомянуть, это чек. В s3eIOSAppStoreBillingRequestPaymentStub мы формируем пустой чек:

    ...
	memset(&receipt, 0, sizeof(receipt));
	receipt.m_ReceiptSize = 0;
    ...

Но это, возможно, не вполне корректно, если приложение должно верифицировать чек или использовать какую-то информацию, содержащуюся в нем (например дату покупки). О том как строится корректный чек можно прочитать здесь. Здесь описано как выполнить верификацию чека.

Осталось совсем немного. В код примера мы добавляем #include AppStoreStub.h (он обязательно должен идти последним) и вызов appStoreStubUpdate, для эмуляции асинхронных вызовов:

bool ExampleUpdate()
{
    appStoreStubUpdate();
    ...

Скомпилировав пример и запустив его под отладчиком, мы можем убедиться, что теперь мы можем запрашивать информацию о продукте и совершать покупки, совершенно не беспокоя при этом App Store.

Автор: GlukKazan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js