我正在尝试通过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


结果
ios - iOS UIWebView应用内购买无效-LMLPHP

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

10-08 17:34