Saturday, September 22, 2018

Using WKURLSchemeHandler for wkwebview cross domain requests

One issue to limit ios app to use WKWebView is cross domain requests, basically html file loaded from local file system cannot send requests to remote server, and vice versa, html file loaded from remote server cannot load js files from local file system.

Apple introduced WKURLSchemeHandler in iOS 11 to solve this issue, however, it still has some problem:
1. only custom scheme can be handled by WKURLSchemeHandler, so well-known scheme, such as http, https cannot be registered and handled by WKURLSchemeHandler.

2. in order to let WKURLSchemeHandler to handle xmlhttprequest, the root html file must also be loaded by the same scheme. If the root html is loaded by scheme of file://, http:// or https://, then calling xmlhttprequest from javascript with custom scheme like myscheme:// does not work with the registered WKURLSchemeHandler.

3. Due to item 2, the only option left is always loading the root html file using custom scheme. When the root html file is loaded using custom scheme, such as myscheme://, then the testing shows the <script> element can load javascript file from any remote servers using https:// or http:// scheme. However, when javascript sends xmlhttprequest to a remote server using http:// or https:// scheme, the request will fail due to the cross domain limitation, as from wkwebview's point of view, those requests are from different schemes.

In order to solve the issue 3, one option is, after loading the root html using the custom scheme, then intercepting and override all xmlhttprequests in javascript side. So for all regular schemes, such as file://, https://, http://, always prefix "wk", so they will become wkfile://, wkhttp://, wkhttps://. Finally using WKURLSchemeHandler to register all the custom schemes used by the app, and then in the custom handler, either loading the content from local file system, or from remote server using NSURLSession.

Another option is customizing the web server's response if you have control over it. So that it can set Access-Control-Allow-Origin header in response to allow custom scheme and origin, although I have not verified that in testing. (To be updated later)




Friday, September 21, 2018

NSURLSession may automatically retry the webview request started before app entering background

As decribed in http://www.openradar.me/39508158in ios 11.3 when app enters background, UIWebView will drop the current open connection, and as a result, the connection will get an connection lost error when enters the foreground again.

However the testing shows the behavior is different depending on how the testing is done. A recent testing on ios 11.4.1 device shows, when app enters background, the UIWebView's open xhr connection for a GET request is kept open, so uiwebview in the application does not get the connection close error when entering background or returning to foreground again. 

But, the server response for this original request is ignored somehow, and is not received by UIWebView's xhr request. 

Later, when the application enter foreground again, the fiddler shows NSURLSession automatically clone the original request, and send the new request to server. Once the server response for the new request is received by device, it is sent back to UIWebView's original xhr request for the UIWebView to process. 

So from the application's point of view, the original request is succeeded after the app enters background and returns to foreground again.

This behavior is described at Apple document 
 https://developer.apple.com/library/archive/qa/qa1941/_index.html

Tuesday, September 4, 2018

Working with WKHTTPCookieStore

1. Share the cookie between multiple WKWebView instances
When sharing the cookies between multiple WKWebView instances, all instances need to set to the same WKProcessPool. Without doing this, when calling WKHTTPCookieStore.getCookie method from the second wkwebview instance, the callback completion block will not be called until the webview goes into background.
So setting process pool to the same value for multiple WKWebView instances is really for calling the getCookies' completion block, it is not for using a shared WKHTTPCookieStore object for all the webview instances.

2. observe WKWebView Cookie change events
When adding cookie change observer of WKHTTPCookeStoreObserver to WKHTTPCookieStore, be sure the observer instance is retained by the app before adding it to cookie store, as cookie store does not maintain a strong reference to the observer. As a result, adding the observer to cookie store is not enough, the app must need to retain the observer before is removed from the cookie store.

In addition, when setting multiple wkwebview to use the same process pool instance, somehow the WKHTTPCookieStoreObserver stops working, and could not receive the cookie change events.

3. cookie synchronization change in ios 12
In ios 11, there is a bug to prevent cookie sync between wkhttpcookie store and NShttpcookie store. Basically, after calling setCookie to set the cookie in NSHttpCookie store, it will not update the existing cookie, so wkhttpcookie store cannot synchronize its cookie items to nshttpcookie store. This has been fixed in ios 12, so we can implement the function to keep nshttpcookie store and wkhttpcookie store stay synchronized.

Saturday, August 25, 2018

Understanding iOS NSNotification






iOS notification center delivers notifications to observers synchronously. In other words, when posting a notification, control does not return to the poster until all observers have received and processed the notification. 
To send notifications asynchronously use a notification queue, which is described in Notification Queues.
In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.

This is similar to how javascript addEventListener and dispatchEvent works, so when dispatchEvent is called, the method will not return to caller until all added event listeners returns from their event handler call back methods.
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent

Wednesday, August 15, 2018

Utility functions for ios NSUserDefault

1. Dump all items in NSUserDefault


NSArray *keys = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys];

for(NSString* key in keys){
        NSLog(@"%@ : %@",key, [[NSUserDefaults standardUserDefaults] valueForKey:key]);
}



2. Observe changes in NSUserDefault


- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
  [[NSUserDefaults standardUserDefaults] addObserver:self
                                            forKeyPath:@"mykey"
                                               options:NSKeyValueObservingOptionNew
                                               context:NULL];

    self.viewController = [[MainViewController alloc] init];
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)observeValueForKeyPath:(NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context
{
    if([keyPath isEqual:@"mykey"])
    {
       NSLog(@"SomeKey change: %@", change);
    }
}

3. Settings bundle with NSUserDefault
Application can use ios Settings app to allow user to configure settings. However, settings put into Settings.bundle root.plist will not be automatically picked up when app reads settings from NSUserDefault. The settings only apply to NSUserDefault after user change the value from settings app.
So if user has not change a setting from ios setting app, read the key from nsuserdefault will return nil, which means the key does not exist in NSUserDefault.
One way to overcome this issue is calling NSUserDefault regsiterDefault method, this will set the initial values in NSUserDefault for all the keys in NSDictionary parameter. 

Friday, August 3, 2018

How to revert from iOS 12 beta to iOS 11

Recently a device was upgraded to 12 beta for testing, but as the device was previously used by other developers, so there is no backup available for restoring the device to ios 11.

Tried few things mentioned in web, but unfortunately none of them works.

Finally, find a simple way to do so, by just deleting the ios 12 beta installation profile from device settings app at Settings->general->Profile & Device Management, restore the device using iTune. It will restore the device to a new ios 11.4.1 setup.

Monday, July 16, 2018

Convert iOS app bundle (.app file) to ipa file

When xcode builds a project, it generates an app bundle as output. In order to install the app bundle to iOS device, the app bundle file needs to be converted to a ipa file.

The following steps can be used to convert app bundle to ipa file
1. Create a folder named as "Payload"
2. drag and drop the app bundle file (myapp.app file) into Payload folder. You can get the .app file from xcode derived folder
3. Compress the payload folder to a zip file
4. Rename the zip file to ipa file