我正在尝试通过UIWebView实现应用内购买:
index.html
<html><body>
<script>
function purchase() {
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "purchase");
document.documentElement.appendChild(iframe);
};
</script>
<br><br><br><button onclick="purchase()">Purchase!</button>
</body></html>
ViewController.m
#import "ViewController.h"
#import "PurchaseBackend.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
_webview.delegate = self;
[_webview loadHTMLString:htmlString baseURL:nil];
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
return YES;
} else {
PurchaseBackend *pb = [[PurchaseBackend alloc] init];
[pb purchaseClick];
return NO;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
PurchaseBackend.h
#import <StoreKit/StoreKit.h>
@interface PurchaseBackend : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
-(void)purchaseClick;
@end
PurchaseBackend.m
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "PurchaseBackend.h"
@implementation PurchaseBackend
#define productID @"com.organization.MonthSubscription"
- (void)purchaseClick{
NSLog(@"User clicked purchase button");
if([SKPaymentQueue canMakePayments]){
NSLog(@"User can make payments");
//If you have more than one in-app purchase, and would like
//to have the user purchase a different product, simply define
//another function and replace kRemoveAdsProductIdentifier with
//the identifier for the other product
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productID]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(@"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely due to parental controls
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(@"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(@"No products available");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (void)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch(transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self paid]; //you can add your code for what you want to happen when the user buys the purchase here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(@"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finish
if(transaction.error.code == SKErrorPaymentCancelled){
NSLog(@"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paid{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PAID"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(@"User Paid, state saved");
}
@end
结果
xcode控制台
2015-12-31 13:13:26.120 AppName[392:102559] User clicked purchase button
2015-12-31 13:13:26.144 AppName[392:102559] User can make payments
(lldb)
救命!
更新:
更新了ViewController.m
#import "ViewController.h"
#import "PurchaseBackend.h"
@interface ViewController ()
@end
@implementation ViewController
PurchaseBackend *purchaseBackend;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
_webview.delegate = self;
[_webview loadHTMLString:htmlString baseURL:nil];
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
return YES;
} else {
purchaseBackend = [[PurchaseBackend alloc] init];
[purchaseBackend purchaseClick];
return NO;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
多亏了有用的评论,我发现了内存崩溃的原因-在storekit框架仍在使用它的同时,释放了PurchaseBackend委托对象。我已经如上所述更新了
ViewController.m
,但是现在打印出来的不是“崩溃”,而是显示“用户可以付款”之后没有任何反应。我在didReceiveResponse
中放置了一个断点,但它不执行。 最佳答案
我想您可能有一个内存问题,“ purchaseBackend”对象似乎有机会捕获didReceiveResponse回调之前已被释放。
尝试为其添加一个强大的属性,并确保您的“ viewController”自身未释放。
为了避免这种问题,最好单单转换purchaseBackend
您可以看一下有关ARC的本教程,以便更好地了解如何在iOS中管理内存:Beginning ARC in iOS 5 Tutorial。