GitHub PullRequestReviewCommentEvent at 2014-05-06 14:32:58
Repository: artsy.github.com
Actor: orta
Payload: {"comment":{"url":"https://api.github.com/repos/artsy/artsy.github.com/pulls/comments/12326999","id":12326999,"diff_hunk":"@@ -0,0 +1,233 @@\n+---\n+layout: post\n+title: \"Aspect-Oriented Programming and ARAnalytics\"\n+date: 2014-05-06 14:52\n+comments: true\n+categories: [iOS,Analytics,ARAnalytics]\n+author: Ash Furrow\n+github-url: https://www.github.com/AshFurrow\n+twitter-url: http://twitter.com/ashfurrow\n+blog-url: http://ashfurrow.com/\n+---\n+\n+Analytics are common in iOS applications. They help inform our decisions \n+about products. Since analytics are so common, Artsy developed a library called\n+[ARAnalytics](https://github.com/orta/ARAnalytics). This library provides a \n+single interface to many different backend analytics providers, freeing \n+developers from having to write code for each of the providers that they're\n+using. \n+\n+Let's consider a typical view controller on iOS. View controllers on iOS \n+represent the glue code between models and views. When a model changes, the view \n+controller updates the appearance of the UI. Similarly, when the UI is \n+interacted with by the user, the view controller updates the model. This is the\n+core of any standard iOS application. \n+\n+So let's say that a button is pressed. We'll handle that interaction in a \n+method called `buttonWasPressed:`. We'll want to update our model, but also to\n+track the analytics event. \n+\n+``` objc\n+- (void)buttonWasPressed:(id)sender\n+{\n+\tself.model.hearted = YES;\n+\n+\t[ARAnalytics event:@\"hearted\"];\n+}\n+```\n+\n+Simple enough, but consider that the analytics tracking code doesn't fall within\n+our definition of a view controller – the button handler just happens to be a\n+convenient place to put the tracking code. Also consider that *every single* \n+button handler is going to have to have similar code implemented. \n+\n+<!-- more -->\n+\n+## There has to be a better way. \n+\n+[Pete Steinberger](http://twitter.com/steipete) and [Orta Therox](http://twitter.com/orta)\n+where talking and the topic of [Aspect-Oriented Programming](http://en.wikipedia.org/wiki/Aspect-oriented_programming),\n+specifically in the context of analytics. AOP takes a look at the different \n+*conerns* of an application – logical, cohesive units of functionality. While \n+most programming paradigms, including those used with Objective-C, group and\n+encapsulate these concerns, there are some concerns that are \"cross-cutting\" \n+because they are involved through several other concerns. \n+\n+Analytics is such a cross-cutting concern. That makes it a prime target for \n+being abstracted away using AOP. Using [another blog post](http://albertodebortoli.github.io/blog/2014/03/25/an-aspect-oriented-approach-programming-to-ios-analytics/)  as an example, we set about [integrating an AOP-like DSL within ARAnalytics](https://github.com/orta/ARAnalytics/pull/74) \n+that would allow you to define all of your analytics in one spot. \n+\n+The interface would be simple. When providing your API keys to the various\n+backend services you'd like to use with ARAnalytics, you'd also provide a\n+dictionary specifying the classes you'd like us to \"hook into\". Whenever a \n+selector from an instance of the given class was invoked, we'd execute the \n+analytics event specified in the dictionary. \n+\n+Since Objective-C has a dynamic runtime, we could have swizzled the instance \n+methods on the classes you specified in the dictionary. This gets a little \n+tricky and represents a lot of work for us. We could directly swizzle the \n+instance methods on the classes in question, but wrapping parameters of variable \n+types and in various numbers becomes a chore. If we didn't get it done \n+perfectly, we'd risk introducing bugs into the entire application. \n+\n+I wrote a proof-of-concept of analytics using AOP with [ReactiveCocoa](http://reactivecocoa.io). \n+It worked, but was a little hacky since it involved the swizzling of `alloc`. \n+ReactiveCocoa is also a large framework to be included just for the sake of \n+analytics. Additionally, its interface exposed ReactiveCocoa's `RACTuple` class, \n+which smells like a leaky abstraction. \n+\n+## What could we do?\n+\n+Well, about the same time, Pete Steinberger open sourced a new framework just \n+for AOP called [Aspects](https://github.com/steipete/Aspects). Pete did all the\n+difficult work of swizzling methods with variable parameter lists, including\n+wrapping primitive parameters in values. \n+\n+Pete and I worked together to get Aspects working with ARAnalytics, removing our \n+dependency on ReactiveCocoa. \n+\n+## How to Use it\n+\n+Using ARAnalytics with the new DSL is super-easy. Just add either `ARAnalytics`\n+or `ARAnalytics/DSL` to your podfile, specifying a version of at least 2.6. Run \n+`pod install` and you're ready to get started. \n+\n+Since all of your analytics are going to be specified in one spot, and that spot\n+is going to get rather large, I'd recommend creating an Objective-C category on\n+your app delegate to set up all of your analytics. Then you can call this \n+`setupAnalytics` method when your app launches. \n+\n+``` objc\n+\n+#import \"ARAppDelegate.h\"\n+\n+@interface ARAppDelegate (Analytics)\n+\n+- (void)setupAnalytics;\n+\n+@end\n+\n+```\n+\n+``` objc\n+\n+\n+#import <ARAnalytics/DSL.h>\n+\n+@implementation ARAppDelegate (Analytics)\n+\n+- (void)setupAnalytics \n+{\n+\t[ARAnalytics setupWithAnalytics:@{\n+\t\t/* keys */\n+    } configuration:\n+    @{\n+    \tARAnalyticsTrackedEvents: @[\n+    \t\t@{\n+    \t\t\tARAnalyticsClass: MyViewController.class,\n+    \t\t\tARAnalyticsDetails: @[\n+    \t\t\t\t@{\n+    \t\t\t\t\tARAnalyticsEventName: @\"hearted\",\n+    \t\t\t\t\tARAnalyticsSelectorName: NSStringFromSelector(@selector(buttonWasPressed:)),\n+    \t\t\t\t}\n+    \t\t\t]\n+    \t\t}\n+    \t]\n+\t}];\n+}\n+\n+@end\n+\n+```\n+\n+Now our `buttonWasPressed:` method is *very* straightforward:\n+\n+``` objc\n+- (void)buttonWasPressed:(id)sender\n+{\n+\tself.model.hearted = YES;\n+}\n+```\n+\n+The view controller is now *only* responsible for what it should be responsible\n+for: mediating interactions between the view and the model. Awesome! Even \n+cooler, we can provide fine-grain control over which analytics events are\n+invoked and with what properties they are sent with. Let's take a look. \n+\n+``` objc\n+[ARAnalytics setupWithAnalytics:@{\n+\t/* keys */\n+} configuration:\n+@{\n+\tARAnalyticsTrackedEvents: @[\n+\t\t@{\n+\t\t\tARAnalyticsClass: MyViewController.class,\n+\t\t\tARAnalyticsDetails: @[\n+\t\t\t\t@{\n+\t\t\t\t\tARAnalyticsEventName: @\"hearted\",\n+\t\t\t\t\tARAnalyticsSelectorName: NSStringFromSelector(@selector(buttonWasPressed:)),\n+\t\t\t\t\tARAnalyticsEventProperties: ^NSDictionary *(MyViewController *controller, NSArray *parameters) {\n+                        UIButton *button = parameters.firstObject;\n+                        NSString *buttonTitle = [button titleForState:UIControlStateNormal];\n+                        return @{\n+                            @\"view_title\" : controller.title ?: @\"\",\n+                            @\"button_title\" : buttonTitle ?: @\"\",\n+                        };\n+                    },\n+                    ARAnalyticsShouldFire: ^BOOL(MyViewController *controller, NSArray *parameters) {\n+                        return /* selective disable firing of analytics */;\n+                    }\n+\t\t\t\t}","path":"source/_posts/2014-05-06-aspect-oriented-programming-and-aranalytics.markdown","position":180,"original_position":180,"commit_id":"3146094e4d3dc608f07fd791925acba461a06cdc","original_commit_id":"3146094e4d3dc608f07fd791925acba461a06cdc","user":{"login":"orta","id":49038,"avatar_url":"https://avatars.githubusercontent.com/u/49038?","gravatar_id":"f116cb3be23153ec08b94e8bd4dbcfeb","url":"https://api.github.com/users/orta","html_url":"https://github.com/orta","followers_url":"https://api.github.com/users/orta/followers","following_url":"https://api.github.com/users/orta/following{/other_user}","gists_url":"https://api.github.com/users/orta/gists{/gist_id}","starred_url":"https://api.github.com/users/orta/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/orta/subscriptions","organizations_url":"https://api.github.com/users/orta/orgs","repos_url":"https://api.github.com/users/orta/repos","events_url":"https://api.github.com/users/orta/events{/privacy}","received_events_url":"https://api.github.com/users/orta/received_events","type":"User","site_admin":false},"body":"syntax gone weird here","created_at":"2014-05-06T14:32:58Z","updated_at":"2014-05-06T14:32:58Z","html_url":"https://github.com/artsy/artsy.github.com/pull/48#discussion_r12326999","pull_request_url":"https://api.github.com/repos/artsy/artsy.github.com/pulls/48","_links":{"self":{"href":"https://api.github.com/repos/artsy/artsy.github.com/pulls/comments/12326999"},"html":{"href":"https://github.com/artsy/artsy.github.com/pull/48#discussion_r12326999"},"pull_request":{"href":"https://api.github.com/repos/artsy/artsy.github.com/pulls/48"}}}}