Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DSL with Swift #233

Open
scisci opened this issue Dec 2, 2015 · 24 comments
Open

DSL with Swift #233

scisci opened this issue Dec 2, 2015 · 24 comments

Comments

@scisci
Copy link

scisci commented Dec 2, 2015

Would be great to have support for the aspect-oriented DSL style tracking in Swift.

I can get tracked screens and some events to come through, but for events, some selectors don't seem to work and can't figure out how to properly type the Properties callback.

The following is working for me for now:

let trackedEvents =  [
        [
            ARAnalyticsClass: AccountEditVC.self,
            ARAnalyticsDetails: [
                [
                    ARAnalyticsEventName: "Account Edit Save Button",
                    ARAnalyticsSelectorName: "saveChangesBtnTapped:"
                ],
            ]
        ]
   ]

let trackedScreens = [
     [
        ARAnalyticsClass: FeaturesVC.self,
        ARAnalyticsDetails: [[ARAnalyticsPageName: "Slideshow"]]
      ]
]

Can't figure out how to do properties though. And sometimes selectors that aren't IBOutlets are never called.

@orta
Copy link
Owner

orta commented Dec 18, 2015

@ashfurrow - this needs the vars / selectors /classes to be classed as dynamic / @objc in swift right?

@ashfurrow
Copy link
Collaborator

Correct; subclasses of NSObject don't need @objc since it is already implied, but properties still must be marked as dynamic.

@scisci
Copy link
Author

scisci commented Dec 18, 2015

Thanks for letting me know. So all of my tests have been on UIViewControllers so they do inherit from NSObject. However, regular old methods that aren't IBActions weren't being swizzled. Do I need to declare them like this:

public dynamic func foobar()

Also how would one do the ARAnalyticsDetails to add event properties? I couldn't figure out the closure signature.

Thanks for the help.

@ashfurrow
Copy link
Collaborator

That's strange, that should work. Can you double-check that you're following the DSL structure strictly? It's the most common source of problems. The closure is the same as Objective-C:

(controller: MyViewController, parameters: NSArray) -> NSDictionary

@timbroder
Copy link

I think I'm close getting Swift to work with the DSL. Screen tracking is working, but I don't think I'm hooking into the event selectors correctly.

Can anyone spot what I'm missing here?

        let trackedEvents =  [
            [
                ARAnalyticsClass: LoadingViewController.self,
                ARAnalyticsDetails: [
                    [
                        ARAnalyticsEventName: "LandingRegisterButtonTapped",
                        ARAnalyticsSelectorName: "tapRegister:sender:"
                    ],
                    [
                        ARAnalyticsEventName: "LandingLoginButtonTapped",
                        ARAnalyticsSelectorName: "tapLogin:sender:"
                    ],
                    [
                        ARAnalyticsEventName: "QuickTest",
                        ARAnalyticsSelectorName: "quicktest:"
                    ],
                ]
            ],
        ]

        let trackedScreens = [
            [
                ARAnalyticsClass: LoadingViewController.self,
                ARAnalyticsDetails: [[ARAnalyticsPageName: "Landing"]]
            ],
            [
                ARAnalyticsClass: RegisterViewController.self,
                ARAnalyticsDetails: [[ARAnalyticsPageName: "Registration"]]
            ],
            [
                ARAnalyticsClass: LoginViewController.self,
                ARAnalyticsDetails: [[ARAnalyticsPageName: "Login"]]
            ]
        ]


        ARAnalytics.setupWithAnalytics(providers, configuration: [
                ARAnalyticsTrackedEvents: trackedEvents,
                ARAnalyticsTrackedScreens: trackedScreens
        ])
class LoadingViewController: UIViewController {
    @IBAction func tapLogin(sender: AnyObject) {
        self.quicktest()
    }

    @IBAction func tapRegister(sender: AnyObject) {
        ARAnalytics.event("manualtapRegister", withProperties: ["property.testing": "123456"])
        self.quicktest()

    }

    dynamic func quicktest() {
        print("hi")
    }

}

Much appreciated!

@orta
Copy link
Owner

orta commented Feb 12, 2016

"tapLogin:sender:" implies a function like: func tapLogin(thing:Thing, sender:OtherThing) - you want "tapLogin:"

same for the rest of them, and quickTest: has no arguments, so it's just quickTest

@timbroder
Copy link

Thanks. I did have that at first, I should have specified. But then quicktest: should have worked, no?

I've updated, but still issues. I've also tried just "quicktest"

        let trackedEvents =  [
            [
                ARAnalyticsClass: LoadingViewController.self,
                ARAnalyticsDetails: [
                    [
                        ARAnalyticsEventName: "LandingRegisterButtonTapped",
                        ARAnalyticsSelectorName: "tapRegister:"
                    ],
                    [
                        ARAnalyticsEventName: "LandingLoginButtonTapped",
                        ARAnalyticsSelectorName: "tapLogin:"
                    ],
                    [
                        ARAnalyticsEventName: "QuickTest",
                        ARAnalyticsSelectorName: "quicktest:"
                    ],
                ]
            ],
        ]

@timbroder
Copy link

Some more info. I don't think it's the selectors.

In addEventAnalyticsHook, the RSSWReplacement block is never called when trying to swizzle alloc on my VC

It does get through to the originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType); line in static void swizzle(Class classToSwizzle, SEL selector, RSSwizzleImpFactoryBlock factoryBlock) but never seems to fire

So then I thought it might be the way I'm instantiating LoadingViewController in the storyboard, So I added a definition for RegisterViewController which I instantiate in code

In that example, I do see getting into NSObjectRACSignalForSelector but failing if (targetMethod == NULL) and getting to class_replaceMethod with the right selector string

Also, on this class though, I never get into the RSSWReplacement block

I was able to verify my selector strings if I can figure this part out

        let loadingVC = LoadingViewController()
        let canRegister = loadingVC.respondsToSelector(Selector("tapRegister:"))
        let canLogin = loadingVC.respondsToSelector(Selector("tapLogin:"))
        let canTest = loadingVC.respondsToSelector(Selector("quicktest"))

        print("canRegister \(canRegister)")
        print("canRegister \(canLogin)")
        print("canRegister \(canTest)")

        let registerVC = RegisterViewController()
        let canPhoto = registerVC.respondsToSelector(Selector("tapAddPicture:"))

        print("canPhoto \(canPhoto)")

@shwetsolanki
Copy link

unable to figure out a proper way to add ARAnalyticsProperties callback

let someClassEvents: NSDictionary = [
            ARAnalyticsClass : NSClassFromString("ViewController")!,
            ARAnalyticsDetails : [
                ARAnalyticsEventName : "SomeEvent",
                ARAnalyticsSelectorName : "viewDidLoad",
                ARAnalyticsProperties : {(controller: UIViewController, parameters: NSArray) -> NSDictionary in
                    return ["somekey" : "somevalue"]
                }

            ]
        ]

I am getting the following error on the closure. Any suggestions?

Contextual type 'AnyObject' cannot be used with dictionary literal

@orta
Copy link
Owner

orta commented Mar 1, 2016

Remove the : NSDictionary - the examples above don't have them.

@shwetsolanki
Copy link

After removing : NSDictionary, I am getting error on the return statement

'(dictionaryLiteral: (NSCopying, AnyObject))' is not convertible to '(dictionaryLiteral: (NSString, NSString)...)'

@shwetsolanki
Copy link

Could you help us with a Swift Example, if possible?

@ashfurrow
Copy link
Collaborator

"viewDidLoad" should be "viewDidLoad:" because it has a parameter. Can you try that?

@ashfurrow
Copy link
Collaborator

Oh wait, no it doesn't >.<

@shwetsolanki
Copy link

@ashfurrow the error is on compile time.
@orta I have updated to the following, now the error changes

let someClassEvents = [
            ARAnalyticsClass : NSClassFromString("ViewController")!,
            ARAnalyticsDetails : [
                ARAnalyticsEventName : "SomeEvent",
                ARAnalyticsSelectorName : "viewDidLoad",
                ARAnalyticsProperties : {(controller: AnyObject!, parameters:[AnyObject]!) -> NSDictionary in

                    var someDict = NSMutableDictionary()
                    someDict["someKey"] = "someValue"
                    return someDict
                }
            ] as NSDictionary
        ]

Error is while inserting the block to the dictionary.

Value of type '(AnyObject!, [AnyObject]!) -> NSDictionary' does not conform to expected dictionary value type 'AnyObject'

@shwetsolanki
Copy link

I got it working by adding another ObjC class called AREvents in my project.

@implementation AREvents
+(NSDictionary *)eventWithName:(NSString *)name selector:(NSString *)selector properties:(NSDictionary *(^)(id, NSArray *))propertiesBlock
{
    return @{
             @"event" : name,
             @"selector" : selector,
             @"properties" : propertiesBlock
             };
}
@end

I tried importing ARDSL.h to use the externs, but was getting some error, will resolve that later. This serves my needs as of now.

@ashfurrow
Copy link
Collaborator

We had a similar issue with putting Swift closures in NSDictionaries. Our solution involves an unsafeBitCast from the closure to AnyObject, which makes the compiler happy but you need to be careful. We wrapped the whole thing up into this abstraction, so we would call toBlock() on the closure when it had to be turned into an AnyObject for NSDictionary use. I think that solution would work here.

Ideally we'd provide some mechanism in ARAnalytics to do this for you, but that would mean introducing the first Swift code into it, which has implications around which versions of iOS we can support, etc. Maybe we could put something in another library? Open to suggestions on that. In the meantime @icios, try that out and see if it works 🍰

@orta
Copy link
Owner

orta commented Mar 22, 2016

OK, this caught me, after some debugging I've figured out that the alloc function isn't called when you initialise view controllers from swift, so:

    Sale *sale = [Sale modelWithJSON:@{}];
    SaleViewModel *model = [[SaleViewModel alloc] initWithSale:sale saleArtworks:@[]];
    id viewController = [[AuctionInformationViewController alloc] initWithSaleViewModel:model];
    [self pushViewController:viewController animated:YES];

sets the VC up for analytics

    func userDidPressInfo(titleView: AuctionTitleView) {
        let auctionInforVC = AuctionInformationViewController(saleViewModel: saleViewModel)
        auctionInforVC.titleViewDelegate = self
        [...]

Does not.

@ashfurrow
Copy link
Collaborator

Whoa.

@GabrielCartier
Copy link

Any news for an update of the DSL for Swift?

@orta
Copy link
Owner

orta commented Jun 17, 2016

No, we've been adding them inline in swift ATM due to time constraints, you're welcome to have a think / PR about it though

@GabrielCartier
Copy link

Yea, I'll check if I can come up with something. Is it even possible to do it in Swift or would it only work with classes inheriting from NSObject?

@ashfurrow
Copy link
Collaborator

Only NSObject is possible I'm afraid.

@GabrielCartier
Copy link

Ok, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants