Sunday, November 25, 2018

UnsafePointer and UnsafeMutablePointer

When calling objective c function from swift code, the objective c type needs mapping to swift type.

For primary type used as value type parameter, like int, NSString*, they can be mapped to primary type of swift, as Int32, String.

For NSObject inherited interface types, as they are always used as reference type (pointer), so they can also mapped to the corresponding swift types, or keep Objective C original type. For example, NSArray will map to [Any], NSDictionary will map to [HashTable: Any]. But NSMutableDictionary and NSMutableArray will keep the same type name in swift code.

For C struct when used as value type parameter, the same type name will be in swift code.

The thing gets complex when pointer are used for primary type and c struct.

When pointer is used, if the pointer is defined with const, which means the object pointed by the pointer cannot be changed, then the parameter type will mapped to UnsafePointer<Type>; otherwise, it will be mapped to UnsafeMutablePointer<Type>. The major difference between UnsafePointer and UnsafeMutablePointer is UnsafePointer.pointee is read-only, while UnsafeMutablePointer.pointee is writable.

Objective C

-(int) sump:(int*)vp1 add:(const int*)vp2;
maps to

func sump(_ vp1: UnsafeMutablePointer<Int32>!, add vp2:UnsafePointer<Int32>!) -> Int32


If an objective c function only takes a pointer parameter, and on swift side an swift object is passed to the function, then you can use helper method of withUnsafePointer or withUnsafeMutablePointer to simplify the logic. As shown below, the input parameter b is converted to UnsafePointer bb in the function block.


var  b : Int32 = 10
let result :Int32 = withUnsafePointer(to: b) { (bb: UnsafePointer<Int32>) -> Int32 in
      var t : Int32 = bb.pointee
      let mt : UnsafeMutablePointer<Int32>? = UnsafeMutablePointer<Int32>(&t)
  
      let m : Int32 = o.sump(mt, add:bb)
      return m
}

after the code block returns, b is still 10.


The similar function can also be called using withUnsafeMutablePointer, which will allow to update b's value if it is updated inside the objective c function as shown below

var  b : Int32 = 10
let result2 = withUnsafeMutablePointer(to: &b) { (bb: UnsafeMutablePointer<Int32>) -> Int32 in
          
    let mt : UnsafePointer<Int32>? = UnsafePointer<Int32>(bb)
  
    let m : Int32 = o.sump(bb, add:mt)
    return m
}

if sump function updates first parameter's value, then after the above code block returns, the variable b's value will be changed to the updated value.


Saturday, November 24, 2018

Manually create swift bridging header file in xcode project

In order to call objectiveC library from swift project, swift project needs a bridging header file to import the ObjectiveC header file, so swift code can get the functions signature defined in objectiveC library.

If swift project is already created when importing ObjectiveC, then you need manually create the bridging header file with the below steps:

1. in swift project, clicking add new file, and add a objective C coca file, for example, adding a default ViewController class
2. Xcode will show a dialog to ask you whether to create a bridging header file
3. Click Create bridging Header button to create the bridging header file
4. Once the bridge header file is created, in the swift project, delete the objective C .h and .m file by moving them to trash
5. In swift project settings general tabs' "linked Framework and Libraries" section, add the Objective C library project
6 update swift project Bridging Header file, to include the objectiveC header file. For example, if the ObjectiveC library is named as "ObjcLibrary", then add the below line

#import <ObjcLibrary/ObjcLibrary.h>


After importing the c .h file into the swift project, quite often you want to check the generated swift function signature based on c .h file. You can do so with the below steps
1. open the c version .h file
2. open the Assistant Editor window, and  then open the same c version .h file in it
3. click "Related items" button, and then select "Generated Interfaces", which will allow you to choose swift version for the generated swift .h file
Now, you can compare the c .h file and the generated swift interface side by side






Sunday, November 18, 2018

Handle ios inAppPurchase auto-renewing subscription without server side receipt validation

Instead of asking user to purchase your app before downloading it, inAppPurchase provides more options to allow user to purchase the app. In order to leverage ios inAppPurchase function, app developers need to integrate ios StoreKit into the application.

Auto renew subscription is a typical case for this purpose if you want to charge users periodically for using your app.

Two modules to be implemented in order to support auto renew inAppPurchase function
1. Get product information
Based on product id defined in app store connection, application can query the product information using SKProductsRequest object. In the didReceiveResponse delegate method, the product information (SKProduct) is available in the returned SKProductsResponse.products. User needs this information for making a purchase.

2. Purchase the subscription
2.1 In order to purchase a subscription, application first creates a SKPayment object based on SKProduct object returned in step 1, then call SKPaymentQueen.default.add(SKPayment:) method to start the purchase

2.2 StoreKit processes the purchase request submitted in SKPaymentQueue, and then inform the application about the purchase status and result in SKPaymentQueue's updatedTransactions method. To do that, application must add a queue observer (implementation of of SKPaymentTransactionObserver protocol) to get payment queue events.
SKPaymentQueue.default().add(self)

When purchased state is reported by updatedTransactions method, it indicates the purchase is finished, and the app should give user the permission to access the resource, and then call finishTransaction to remove the transaction from paymentQueue. Note finishTransaction should also be called even if the transaction failed.

func paymentQueue(_ queue: SKPaymentQueue,

                    updatedTransactions transactions: [SKPaymentTransaction]){
   switch transaction.transactionState {
      case .purchased:
         queue.finishTransaction(transaction)
         //give user permission
     case .restored:
          queue.finishTransaction(transaction)
         //restore the transaction and give user permission
     case .failed:
        queue.finishTransaction(transaction)
        //inform user the purchase failure
      }
    }
}

For auto-renewing subscription, the transaction date and related information can be stored in keychain, so if user deletes the app from device and then installed it again, the purchase record will still be there, so user does not need to restore the purchase record before using the app.

If user purchased the auto renew subscription and then switch to a different phone, then before let user to purchase the subscription, you should allow user to restore the subscription by calling the below method.
   SKPaymentQueue.default().restoreCompletedTransactions()
When the subscription gets restored on the new device, the application paymentQueue:updatedTransaction method will received the stored event. The original transaction is available in the transaction.origin property. The actual transaction date should come from transaction.origin.transactionDate property.

Note when the subscription is auto renewed, the application's paymentQueue:updatedTransaction  queue will be called after the AppStore automatically renew the subscription without user interaction. In this case, the transaction parameter contains the information for the current renew's information The transaction.origin contains the original initial transaction info. So transaction.transactionDate should be used to update the subscription date.

For simple application, it can just compare the current date with the transaction date extended with the subscription period, if the subscription is still within that period, then allow users to use the app. Otherwise, ask user to purchase or restore the subscription.

Choose inapppurchase product type:
1. users can purchase consumable products many times, but can only buy non-consumable project only once. If users try to buy non-consumable project again after previous purchase, iOS SDK will show an message box to user to prevent the purchase.
2. Although user can buy Non-Renewing Subscription more than once, but when user buy it second time, ios SDK will show a dialog saying "You've already purchased this subscription, tap buy to renew or extend it." This may cause confusion to user.
3. Auto-renewable subscription does not have a way for user to easily unsubscribe the product, this may cause concerns to users for using it. Although it is easy for application to handle renew as it automatically renewed by app store, and the application only needs to handle the renew transaction result.

Choose between free trial period and limited function before user buys 
Before users purchase your inapp purchase item, you may want to let users first experience your application. You can do this by either providing a free trial period, or you can limit only partial functions are available for users before they purchase the product.
Comparing with free trial period, it is relative easier to provide limited function for users, for example, you can only allow user to watch a single chapter of a book, or view few minutes of a movie. As with free trial period approach, you have to implement brand new logic in your application to manage the beginning and end of free trial period, and also properly handle the case when user delete and reinstall the app on the device. In addition, some inapppurchase product types may not support free trial period by App Store.

Prompt user for purchase result
When user subscribes an item, after the purchase is finished, iOS SDK will show a message box to user to report the success result. So application does not need to show a message box to report the result to user. However, if user restores an subscription to a device, after the restoring is finished, iOS SDK does not show anything to user, so application should show a message box to tell user the restoring has succeeded.

Saturday, November 17, 2018

Handle ios NSURLSession authentication request

Some notes about ios NSURLSession authentication

1. Session and task delegate
Although NSURLSessionTaskDelegate can be used to handle task specific event, there is no api to allow setting a task delegate for each individual data task when it is created.
Both NSURLSessionDelegate and NSURLSessionTaskDelegate is set when calling NSURLSession
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id<NSURLSessionDelegate>)delegate
method. That means the NSURLSessionDelegate and NSURLSessionTaskDelegate must be implemented in the same object if you need to handle both them in your app.

Note, both NSURLSessionDelegate and NSURLSessionTaskDelegate defines the didReceiveChallenge method for application to handle the authentication challenge.

For NSURLAuthenticationMethodServerTrust and NSURLAuthenticationMethodClientCertificate authentication methods, if didReceiveChallenge is implemented in NSURLSessionDelegate, then the session delegate will be called. Otherwise, the didReceiveChallenge method implemented in NSURLSessionTaskDelegate will be called.

For NSURLAuthenticationMethodDefault and NSURLAuthenticationMethodHTTPBasic authentication method, they will only be called on NSURLSessionTaskDelegate's didReceivedChallenge method, and if not implemented there, the request will fail and will not invoke the NSURLSessionDelegate's implementation.


2. Handle NSURLAuthenticationMethodServerTrust authentication
For NSURLAuthenticationMethodServerTrust, if you just want the same bebaviour of how Mobile Safari handles the server trust authentication, then it is better to just let ios default handler to handle it by calling the completionHandler with below parameter

completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil)
You only need to implement your customization logic if you want to trust all server cert or implement server certificate pin function.

3. Timeout for user interaction during authentication handling
When handling authentication challenge, you may want to show some UI to allow user interaction, such as trusting self sign server cert for server trust, or picking a client certificate for client certification challenge. 

However, this does not work for NSURLAuthenticationMethodServerTrust and NSURLAuthenticationMethodClientCertificate authentication methods. As for most server, if the authentication challenge's completion block is not called in few seconds, the server will abort the TCP connection, and then the client will get the same challenge again (the previousFailureCount is still 0 for the new challenge). At this moment, even if the app handles the second challenge properly, the original datatask request cannot finish, and the datatask's didCompleteWithError method is never called. So in order to handle the case, if the application gets the second pending request before the first one's completion block is called, the app should cancel the current data request, so the datatask's didCompleteWithError will be called to finish the data request.

As a result, in order to properly handle NSURLAuthenticationMethodServerTrust and NSURLAuthenticationMethodClientCertificate challenge, the application should avoid any user interaction, so that the app can finish the authentication and call the completion block in 1 or 2 seconds.

Note this behavior is different from NSURLAuthenticationMethodHTTPBasic (or NSURLAuthenticationMethodDefault) authentication methods. As for NSURLAuthenticationMethodHTTPBasic challenge, usually user needs to input the username and password, the test shows if the completion block is not called, the server will just wait without automatically aborting the connection, until it is called later.

This issue for not calling the completion block for NSURLAuthenticationMethodServerTrust and NSURLAuthenticationMethodClientCertificate challenge is even more serious for WKWebView's didReceiveAuthenticationChallenge method
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
If the completionHandler is not called in few seconds, then the app will just crash.

By the way, client certificate mutual authentication for wkwebview is supported in iOS 12.

Thursday, November 8, 2018

Understand cordova channel event handler

Javascript supports common event handling with addEventListener and dispatchEvent methods. The event is defined on a particular object, such as document or window object. This limits the case when different objects want to have a shared event subscription manager.

For this purpose, cordova implemented a channel module to manage the event. Different modules can create their own named events (channels), but those events are handled by a shared cordova channel object.

Modules need to first call the below method to access the shared cordava channel object
  var channel = require('cordova/channel'),

With the channel object, it can call create or createStick method to create a named channel (event).
   var namedChannel = channel.create('myChannel');

The create/createStick method are the only method exposed by channel module, which can be used to create named channel instance, this is similar to the Factory pattern. As all named channel lives in the same shared cordova channel object, be sure the name is unique when creating the named channel.

Although named channel all lives in the shared channel object, they are not visible to external caller, so the module which creates the named channel instance should keep a variable reference to it, usually the channels are saved in a channels dictionary with channel name as key, and channel object as value. The owner of the named channel object then exposes APIs to allow other modules to call the instance method on the namedChannel object to subscribe, unsubscribe, or fire the event on the named channel.

For the document object, cordova.js already created few cordova common events, like onDeviceReady, etc, so other modules can use them to get cordova notification events. However, in this case, caller can directly call the javascript addEventListener method to subscribe a event without going through the cordova channel APIs. The reason is Cordova.js already override the document.addEventListener method, so it checks if the matched event name is created by cordova channel API, then it will be handled by cordova channel module, otherwise, it will be handled by default javascript addEventListener method.

Monday, November 5, 2018

Update and re-sign apk file for android testing

Sometimes you gets an apk file from other developers, and need to update certain web resource files (AndroidManifest.xml, javascript, text, or image files), and then resign it to run some testing on your android device. The following steps can be used to do so.

1. download APKTool based on
https://ibotpeaches.github.io/Apktool/install/

2. Run the below command from terminal to extract origin.apk
apktool d /Users/i826633/Documents/Bugs/496624/origin.apk

3. update the web resource files in the extracted folder

4. run the below command to build the update
apktool b /Users/i826633/Documents/Bugs/496624/origin
this will generate a new apk file in /Users/i826633/Documents/Bugs/496624/origin/dist folder

5. for simplifying, rename generated apk file to updated.apk and copy it out to the same folder as original apk file

6. from terminal run the below command from the folder containing your updated apk file. You may need to update the path based on android studio version
/Users/i826633/Library/Android/sdk/build-tools/28.0.2/zipalign -v -p 4 updated.apk aligned.apk 

7 copy your android sign jks file to the same folder, and then resign the aligned apk with the below command. If you do not yet have a signing jks file, you can use the debug signing file created by android studio. It is under C:\Users\username\.android\debug.keystore, and the password is 'android'
/Users/i826633/Library/Android/sdk/build-tools/28.0.2/apksigner sign --ks debug.keystore--out signed.apk aligned.apk

8. run the below command to verify the signed apk is valid. If anything is wrong, it will report an error
/Users/i826633/Library/Android/sdk/build-tools/28.0.2/apksigner verify signed.apk

9. install the apk to device and run it on your device
adb install signed.apk


As a side note, to reload the webview content in android cordova webview, run the below code from Chrome inspector's javascript debugger
window.location.reload()

Thursday, November 1, 2018

Ignore whitespace when comparing changes from a github commit

Quite often we need to find what is changed in a git commit. The github shows the difference when clicking a commit in commit history. However, any whitespace change in the commit is also shown as difference.

In order to ignore the whitespace change, you can append w=1 in the url and then refresh the page.