Saturday, September 27, 2014

Swift: Fun with operators and delegation (updated)

Apple's new programming language has a quite extensive bag of tricks to make you more productive.
One of the more controversial ones is the ability to define custom operators.

Apple's official guide has a short overview of custom operators and is recommended reading.

Let's look at an example of how operators can be useful (misused?) in the context of iOS programming.

How would you read this?

pickerView --> myDelegate

Read on to confirm your hypothesis.

Pick me up


It is quite common to setup your view controller as delegate as well as data source for components like UITableView, UIPickerView, UICollectionView etc.

In Storyboard you simply drag the delegate/datasource connector to your view controller and you are done.
This is all fine and dandy, but becomes smelly if you are using two or more views of the same type,
for example, two UIPickerViews.

Looking at the solutions that were proposed on StackOverflow, a common trick is to either use tags or compare outlets in the common implementation of the delegate or data source methods like this:

// returns the number of 'columns' to display.
    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        if (pickerView == picker1) {
             return 4
        } else if (pickerView == picker2) {
             return 1
        }
// or using switch
//        switch pickerView {
//            case timer: return 4
//            default: // you need a default case
//                return 1
//        }
    }
    

Might get ugly fast. Wouldn't it be nice to have two delegates/data sources. One for each view, that is still somehow part of your view controller? Also, wouldn't it be nice to easily move this logic to a different view controller, if necessary?

I didn't find an elegant way to solve this in Storyboard.
(UPDATE: Someone pointed out I can add any kind of object to a Storyboard and wire those as delegate/data source. That's cool! I still would like to have my view controller code in one place and can't use inner classes in Storyboard)

Also, there's no common protocol implemented by UIViews that indicate they have delegate/datasource properties to set.

Here's a solution that uses inner classes to act as delegates/data sources as well as a new operator that connects views with their delegates/data sources just for funsies.

First the new custom operator which takes care of setting up the delegation chain.

import UiKit

infix operator --> { associativity left }
func --> (left: NSObject, right: NSObject) {
    if (left.respondsToSelector("delegate")) {
        left.setValue(right, forKey: "delegate")
    }
    if (left.respondsToSelector("dataSource")) {
        left.setValue(right, forKey: "dataSource")
    }
}

This creates a new infix operator '-->' which takes care of delegation for you.
It uses key-value coding offered by NSObject to determine if the left hand side of the operator supports a delegate and/or data source and then assigns the right hand side accordingly.
The operator is defined with left associativity and the default precedence value (100).

Note that the current version of Swift doesn't offer much in terms of reflection or metadata programming. Luckily NSObject offers the necessary machinery behind the scenes for us. Also note how selector strings are implicitly converted to Selector objects (since Selector implements StringLiteralConvertible)!

Next up is a delegate/data source for UIPickerView. Note that it has to inherit from NSObject.

    class PickerDelegate : NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
        func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
            return 1
        }
        func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return 10
        }
        func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
            return "Pick me \(row)"
        }
    }

Now consider adding one or more of those as inner classes to your UIViewController.
In order to wire everything together, two things are missing:
  1. Creating an instance of the delegate
  2. Setting up the delegate/data source chain in viewDidLoad
...
   @IBOutlet weak var picker: UIPickerView! // hook this up via Storyboard
   // 1
   let pickerDelegate = PickerDelegate()

   //2
   override func viewDidLoad() {
        super.viewDidLoad()
        picker --> pickerDelegate   
    }
    ...
Note that if you don't store a reference to the delegate in your view controller, the garbage collector might throw it away since UIViews are not hanging on to them (the property is marked as unowned(unsafe)). The same is true for the datasource property.

I also attached a complete storyboard that shows the general idea.

Advantages

  • Clear separation of concerns. If two or more views of the same type are associated with your ViewController, you will enjoy not having to write if-else construct or switch statements
  • Easy to refactor. If you decide to move your views to a different view controller, it's easy to move the delegate. If you have it in its own top-level file, it is even easier (than using inner classes)
  • Expressive syntax that indicates calls going from the UIView to a delegate/data-source

Disadvantages

  • Code in operator is relying on reflection.
  • Not much is gained and regular code is type-safe:
  • picker.delegate = pickerDelegate
       picker.datasource = pickerDelegate
       
  • Custom operators are global. Beware of clashes.

Note that I'm still pretty new to Swift development and especially Xcode 6. Let me know if you've found more elegant solutions.

But, but it isn't type safe! - Protocol composition to the rescue


Yes, key-value programming is not type safe.
And unfortunately, there is no protocol defined for UIViews that have a delegate and or datasource.

In order to make the code of the operator type-safe, we will have to define the --> function for every UIView that supports those patterns.
Here's an example for UIPickerView.

   func --> (left:UIPickerView, right: protocol<UIPickerViewDelegate,UIPickerViewDataSource>) {
       left.delegate = right
       left.dataSource = right
   }

Notice the usage of protocol composition for the right hand side of the operator.


And here's the link to the Playground.

Saturday, January 25, 2014

So you are having trouble with Webstart on Java 7u51?

The Joys of Java Webstart and Java 7u51

Well, it seemed like a great idea back in 2004 to adopt Java Webstart for our enterprise solution.
10 years later, we are still using it and Oracle has done it's best to make sure we have to make substantial changes to our desktop app to comply with the latest security baseline.

Starting with Java 7u45 and maximizing the pain with 7u51, there are new restrictions for what Oracle calls RIAs (applets and Webstart applications), that will affect you if you are in a similar situation, especially if your customers are setting the Java security settings in the Java Control panel to High, Very High, Ultra High or Insanely High I Won't Run Anything That Starts With CA FE BA BE.

Here are the stumbling blocks you will encounter and how to solve them.

#0 I don't wanna change my stuff


In this case, good luck convincing the IT department of your Fortune 100 customer to use Exception Site Lists (which may or may not work. In our testing you had to clear out the Webstart cache for every start) or Deployment Rule Sets (yeah, good luck with telling their IT to copy a signed JAR file into  c:\Windows\Sun\Java\Deployment for each desktop).
Deployment Rule Sets do work, but someone needs to pay for a proper certificate for the DeploymentRuleSet.jar signature. Which brings us to ...

#1 You need a proper code-signing certificate

Gone are the times where an innocent self-signed certificate will get you access to the user's local machine. You will have to cough up the cash to have your certificate signed.

From the dozens of vendors our there, chose one that has a root certificate in the JRE's cacerts keystore.

keytool -v -list -keystore /Library/Java/Home/lib/security/cacerts

Here's a tool to get you started that will create a new keystore as well as a certificate signing request (CSR) to send to the code signer.

Note that I don't endorse any of those vendors. They are greedy bastards, making you pay on a yearly basis for a one-time activity. Where else is that ok? Right, if you have children :)

I would also strongly advise to get on the phone with the code signing authority. The information you provided in the Common Name and Company Name field is likely not correct and if you change that information you need to create another CSR. You can save a lot of hassle by getting this out of the way before you submit your CSR.

If you do have to change the information in your key, some vendors offer to re-key your key quickly. If you did everything correct, importing the signed certificate into your keystore should not throw an error message.

Damn. I get an error message running keytool.

keytool -import -trustcacerts -alias server -file your_domain_name.p7b -keystore your_site_name.jks 
 
Well, check the certificate in the file provided with the one in the keystore. The easiest way to do this is to use Keystore Explorer, an excellent little tool that saved me from losing my sanity.
You might have to re-issue a CSR or you might have to insist on a SHA-1 key from the signer.

If all goes well, you have a nice new keystore with a certificate that you should use to sign all JAR files referenced by your JNLP file.

Oh no, you have Bouncy Castle JARs in there that are already signed?
No worries, sign them with your signature as well. The trick how to do it is here!

#2 You need a main JAR and new entries in your META-INF/MANIFEST.MF


Declare one of your JAR files as main in the JNLP file:

<jar href="lib/yourMain.jar" main="true"/>

In yourMain.jar, add a Permissions entry:

Permissions: all-permissions

You can add a Codebase entry as well, but often times the codebase isn't known in advance.
It's ok. You can specify it in the JNLP file:

<jnlp ...  codebase="http://enterprisesoftwareforzewin.com">

Luckily you don't need to add the Permission header to all of your JARs, just the main one.

#3 You need to say good-bye to your old property entries in the JNLP file

This one took the most doing. In its wisdom, Oracle decided that most of the system properties you use are not OK anymore.

Now have fun finding those pieces in your code which read those properties.
Some properties are considered safe, but you'll probably have to rename all your properties and prefix them with either jnlp. or javaws. :

<property name="jnlp.var.dir" value="${user.home}/.mylittleconfigdirectory"/>

That is a particular joy if you use 3rd party libraries that don't expect that.
Like log4j. If you used <property name="log4j.configuration" value="..../log.cfg"/> in your JNLP file, say hello to configuring Log4j in your main method manually!
Especially painful if your log.cfg referenced other system properties.

Here's what I had to do:
String url = System.getProperty("jnlp.log4j.configuration");
      if (url != null) {
          try {
              Properties p = new Properties();
              URL logURL = new URL(url);
              InputStream in = logURL.openStream();
              p.load(in);
              // replace log.dir with jnlp.log.dir
              p.setProperty("log.dir", System.getProperty("jnlp.log.dir"));
              in.close();
               PropertyConfigurator.configure(p);
            } catch (Exception e) {
                System.err.println("Unable to configure log4j with "+ url);
                e.printStackTrace();
            }

Haven't touched the code of our Launcher class since 2004. Joy! Code Archeology!

Trick: If you are unsure what properties are passed to your app, use jconsole to connect to the launcher process (you can do it while it shows you a dialog telling you how bad your app sucks and that it can't run it).

#4 You are done. Nope, you are not. Signing the JNLP file (is optional)


There's much confusion about this.
Here's what will happen if you run your Webstart app now without JNLP signing.
Your users will get a warning message with a nice 'Run' button and your app will start nevertheless.
That happens with Medium, High and Very High security settings.



If you get other error messages, you screwed something up earlier.
Set the security setting to Medium which gives you slightly more informative error messages.

Signing your JNLP file is easy if your JNLP file never changes.
This link describes the process. There is some wiggle room with attributes like codebase in which case you want to use an APPLICATION_TEMPLATE.JNLP template.
Put this stuff in your main JAR file in the right directory (and sign it of course).

If your JNLP file is created dynamically with different resources, you are out of luck buddy. And I'm too, because we use the horrors of JSP to create a JNLP file just for you.

In that case, consider creating a new single main class that starts your usual main class, and just use this class along with the APPLICATION_TEMPLATE.JNLP to start your app. You can probably dynamically create that JAR file and sign it on the fly. (Probably not a good idea to have your keystore file on your webserver though)
You could even try to use an <extension> tag with the rest of your app's resources.

#5 Test and test some more with the different JREs


Goes without saying. You might even have to tell your users to delete their temporary files in the Java Control panel.


Since I'm feeling all 2004ish now, I'll ask you all to share this on MySpace.