calabash/calabash-ios-server — pull request #97 by krukow (2014-11-23 23:19:09+00:00)
Title: Implement Shared UIA Element Channel

With iOS 8 there seems to be a race condition between UIAutomation and Calabash when writing to the shared preferences (NSUserDefaults). This means that occasionally we experience a missed read or write to the preferences from one of: the Preferences-based UIA channel, the UI Automation process itself or from the application itself writing to preferences (see, e.g.: https://github.com/calabash/calabash-ios/issues/585).

With this pull request, a new type of Channel for communicating with UIAutomation from the Calabash Server is defined. The channel is called "Shared Element" because it consists of an UIKit object (LPSharedUIATextField) which is accessible to both UIAutomation and the CalabashServer (LPUIASharedElementChannel).

The main benefits are

1. Avoid use of shared preferences (e.g., permanently Solves https://github.com/calabash/calabash-ios/issues/585).

2. Does not share anything with the application (private communication channel as opposed to NSUserDefaults).

3. As fast as using preferences.

4. Works on simulator and device.

5. Simpler implementation which uses fewer CPU resources (no spin-loop in Calabash server, uses delegate/callbacks instead).

The trick that is used is to create a synchronized UITextField visible to UIAutomation and LPUIASharedElementChannel. When Calabash wants to send a command to UIAutomation, the setText method is called. When UIAutomation wants to send the reply back setValue is also called on the corresponding UIATextField object.

At first I did not like this solution (it has been discussed before) because in order for UIAutomation to get a reference to the UIATextField, it must be in the view hiearchy (potentially messing with the application view). Also calling `setValue` on a text field from UIAutomation would cause the keyboard to appear.

However, I realized that we can detach the UITextField from the key window as soon as LPUIASharedElementChannel and UIAutomation has made an initial handshake. After the handshake the textfield is a secret shared object that is not accessible to the application. Also if we set the textfield as `enabled = 'NO' ` then the keyboard doesn't appear.

The high-level process is:

1. Lazily create the LPSharedUIATextField when the first UIAutomation command arrives. At this time we know that the key window must be initialized.

2. If this is the first command, do an initial handshake (`[LPSharedUIATextField synchronizeWithUIAutomation]` and await [LPSharedUIATextField isSynchronized]). Also, setup LPUIASharedElementChannel as the delegate of LPSharedUIATextField (the delegate will receive responses from UIAutomation) via a call-back (when [LPSharedUIATextField setText] is called).

3. LPUIASharedElementChannel serializes the command as JSON and calls the `[LPSharedUIATextField setText:]` method. It then asynchronously awaits a response.

4. UIAutomation spin-loops to get the request/command, executes it and writes the result (as JSON) back to LPSharedUIATextField using the `setValue` method of UIAutomation.

5. The `setValue ` method on UIA eventually triggers the `setText:` method of LPSharedUIATextField which parses the JSON can calls the delegate.

I've not done comprehensive testing but the initial results look good.

- [ ] We need to test for stability in long-running suites and interaction with other UIAutomation features.
- [ ] We also need to test on other iOS versions (I've only tested latests (iOS 8.1)).
- [ ] Code review (@jmoody)

## NOTE
Requires `run_loop 1.1.1.pre4` and using the new `:uia_strategy: :shared_element`.