本文介绍了如何Apptimize \ Optimizely在iOS上工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图找出一些关于在幕后操作UI元素的实现,直接从Apptimize或Optimizely的Web控制台。



更具体地说,我想理解以下内容:



1)客户端代码(iOS)如何将视图层次结构发送到Web服务器在这种情况下,当您在Web控制面板上选择任何UI元素时,它会立即显示在iOS客户端上?



我看到FLEX例如,视图层次结构,但我不明白iphone客户端知道哪个视图是在Web仪表板中选择。



2)此外,在Apptimize中,我可以从Web信息中心选择任何UI元素,更改其文本或颜色,并会立即在应用程序中更改。不仅如此,没有添加任何代码,只需要有SDK。



我所做的更改(文字,背景颜色等)将保留应用程序的所有未来会话。如何实现?



我想他们正在使用某种反射,但是如何让它为所有用户和所有未来会话?客户端代码如何找到合适的UI元素?它是如何工作的UITableViewCell?



3)是否可能检测每次UIViewController加载?即在每个viewDidLoad上获取回调?



查看以下屏幕截图:





解决方案

我不知道,确定的答案,所以这里是我的(希望)有教育的猜测:



感谢运行时环境实际上并不难使用 b
$ b

  // 
// ABController.m
// ABTestPrototype
//
//创建由Manuel Meyer于12.05.15。
//版权所有(c)2015 Manuel Meyer。版权所有。
//

#importABController.h

#import< Aspects / Aspects.h>
#import< OCFWebServer / OCFWebServer.h>
#import< OCFWebServer / OCFWebServerRequest.h>
#import< OCFWebServer / OCFWebServerResponse.h>


#import< objc / runtime.h>
#importUIViewController + Updating.h
#importUIView + ABTesting.h


@import UIKit;

@interface ABController()
@property(nonatomic,strong)OCFWebServer * webserver;
@end
@implementation ABController


void _ab_register_ab_notificaction(id self,SEL _cmd)
{
[[NSNotificationCenter defaultCenter] addObserver: self selector:NSSelectorFromString(@ab_notifaction:)name:@ABTestUpdateobject:nil];
}


void _ab_notificaction(id self,SEL _cmd,id userObj)
{
NSLog(@UPDATE%@,self);
}


+(instancetype)sharedABController {
static dispatch_once_t onceToken;
static ABController * abController;
dispatch_once(& onceToken,^ {

OCFWebServer * server = [OCFWebServer new];

[server addDefaultHandlerForMethod:@GET
requestClass :[OCFWebServerRequest class]
processBlock:^ void(OCFWebServerRequest * request){
OCFWebServerResponse * response = [OCFWebServerDataResponse responseWithText:[[[UIApplication sharedApplication] keyWindow] listOfSubviews]];
[request respondWith :response;
}];

[server addHandlerForMethod:@GET
pathRegex:@/ color / [0-9] {1,3} 0-9] {1,3} / [0-9] {1,3} /
requestClass:[OCFWebServerRequest class]
processBlock:^(OCFWebServerRequest * request){
NSArray * comps = request.URL.pathComponents;
UIColor * c = [UIColor colorWithRed:^ {NSString * r = comps [2]; return [r integerValue] / 255.0;}()
green:^ {NSString * g = comps [3]; return [g integerValue] / 255.0;}()
blue:^ {NSString * b = comps [4]; return [b integerValue] / 255.0;}()
alpha:1.0];

[[NSNotificationCenter defaultCenter] postNotificationName:@ABTestUpdateobject:c];
OCFWebServerResponse * response = [OCFWebServerDataResponse responseWithText:[[[UIApplication sharedApplication] keyWindow] listOfSubviews]];
[request respondWith:response];
}];

dispatch_async(dispatch_queue_create(。,0),^ {
[server runWithPort:8080];
});

abController = [[ABController alloc] initWithWebServer:server];
});
return abController;
}

- (instancetype)initWithWebServer:(OCFWebServer *)webserver
{
self = [super init];
if(self){
self.webserver = webserver;
}
return self;
}


+(void)load
{
class_addMethod([UIViewController class],NSSelectorFromString(@ab_notifaction: _ab_notificaction,v @:@);
class_addMethod([UIViewController class],NSSelectorFromString(@ab_register_ab_notificaction),(IMP)_ab_register_ab_notificaction,v @:);

dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(。00001 * NSEC_PER_SEC)),dispatch_get_main_queue(),^ {
[self sharedABController];
}
[UIViewController aspect_hookSelector:@selector(viewDidLoad)
withOptions:AspectPositionAfter
usingBlock:^(id< AspectInfo> aspectInfo){

dispatch_async(dispatch_get_main_queue b $ b ^ {
UIViewController * vc = aspectInfo.instance;
SEL selector = NSSelectorFromString(@ab_register_ab_notificaction);
IMP imp = [vc methodForSelector:selector];
void(* func)(id,SEL)=(void *)imp; func(vc,selector);
});
} error:NULL];

[UIViewController aspect_hookSelector:NSSelectorFromString(@ab_notifaction:)
withOptions:AspectPositionAfter
usingBlock:^(id< AspectInfo> aspectInfo,NSNotification * noti){

dispatch_async(dispatch_get_main_queue(),
^ {
UIViewController * vc = aspectInfo.instance;
[vc updateViewWithAttributes:@ {@backgroundColor:noti.object}];
});
} error:NULL];
}
@end






  // 
// UIViewController + Updating.m
// ABTestPrototype
//
//由Manuel Meyer于12.05.15创建。
//版权所有(c)2015 Manuel Meyer。版权所有。
//

#importUIViewController + Updating.h

@implementation UIViewController(更新)
- (void)updateViewWithAttributes:(NSDictionary * )attributes
{
[[attributes allKeys] enumerateObjectsUsingBlock:^(NSString * obj,NSUInteger idx,BOOL * stop){

if([obj isEqualToString:@backgroundColor ]){

[self.view setBackgroundColor:attributes [obj]];
}
}];
}
@end






完整代码:


I'm trying to figure out a few things about the implementation going on "behind the scene" for manipulating UI elements on the fly, straight from the web console on Apptimize or Optimizely.

More specifically, I want to understand the following:

1) How does the client code (iOS) send the view hierarchy to the web-server in such a way that when you choose any UI element on the web dashboard it immediately shown on the iOS client?

I saw FLEX for example, and how it manage to get the view hierarchy, but I don't understand how the iphone client "knows" which view is picked in the web dashboard.

2) Moreover, in Apptimize I can choose any UI element from the web dashboard, change its text or color and it will immediately change in the app. Not only that, without adding any code, just by having the SDK.

The changes I make (text, background color, etc) will remain for all the future sessions of the app. How can this be implemented?

I'm guessing they are using some sort of reflection, but how can they get it to work for all users and for all future sessions? how does the client code find the right UI element? and how does it work on UITableViewCell?

3) Is it possible to detect every time a UIViewController is loaded? i.e. get a callback on each viewDidLoad? if so, how?

See some screenshots below:

解决方案

I wonder the same and couldn't find a definite answer, so here is my (hopefully) educated guess:

Thanks to the runtime environment it is actually not that hard to use Aspect-Orientated-Programming (AOP) in Cocoa(-Touch), in which rules are written to hook in in other classes method calls.

If you google for AOP and Objective-C, several libraries pop up that wrap the runtime code nicely.

For example steinpete's Aspect library:

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];

This method call

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error {
    return aspect_add((id)self, selector, options, block, error);
}

calls aspect_add()

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
    NSCParameterAssert(self);
    NSCParameterAssert(selector);
    NSCParameterAssert(block);

    __block AspectIdentifier *identifier = nil;
    aspect_performLocked(^{
        if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
            AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
            identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
            if (identifier) {
                [aspectContainer addAspect:identifier withOptions:options];

                // Modify the class to allow message interception.
                aspect_prepareClassAndHookSelector(self, selector, error);
            }
        }
    });
    return identifier;
}

which again calls several other quite frightening looking functions that do the heavy lifting in the runtime

static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
    NSCParameterAssert(selector);
    Class klass = aspect_hookClass(self, error);
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, it not already copied.
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        if (![klass instancesRespondToSelector:aliasSelector]) {
            __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
            NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
        }

        // We use forwardInvocation to hook in.
        class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
    }
}

including method-swizzling.


It is easy to see that here we have a tool that will allow us to send the current state of an app to re-build this in a web-page but also to manipulate objects in an existing code.
Of course this is just a starting point. You will need a web service that assembles the app and sends it to the users.


Personally I never used AOP for such a complex task, but I used it for teaching all view controllers tracking capabilities

- (void)setupViewControllerTracking
{
    NSError *error;
    @weakify(self);
    [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id < AspectInfo > aspectInfo) {
                                   @strongify(self);
                                   UIViewController *viewController = [aspectInfo instance];
                                   NSArray *breadCrumbs = [self breadCrumbsForViewController:viewController];

                                   if (breadCrumbs.count) {
                                       NSString *pageName = [NSString stringWithFormat:@"/%@", [breadCrumbs componentsJoinedByString:@"/"]];
                                       [ARAnalytics pageView:pageName];
                                   }
                               } error:&error];
}


update

I played a bit and was able to create a prototype. If added to a project, it will changes all view controllers background color to blue and after 5 seconds all living view controllers background to orange, by using AOP and dynamic method adding.

source code: https://gist.github.com/vikingosegundo/0e4b30901b9498ae4b7b

The 5 seconds are triggered by a notification, but it is obvious that this could be a network event.


update 2

I taught my prototype to open a network interface and accept rgb values for the background.
Running in simulator this would be

http://127.0.0.1:8080/color/<r>/<g>/<b>/
http://127.0.0.1:8080/color/50/120/220/

I use OCFWebServer for that

//
//  ABController.m
//  ABTestPrototype
//
//  Created by Manuel Meyer on 12.05.15.
//  Copyright (c) 2015 Manuel Meyer. All rights reserved.
//

#import "ABController.h"

#import <Aspects/Aspects.h>
#import <OCFWebServer/OCFWebServer.h>
#import <OCFWebServer/OCFWebServerRequest.h>
#import <OCFWebServer/OCFWebServerResponse.h>


#import <objc/runtime.h>
#import "UIViewController+Updating.h"
#import "UIView+ABTesting.h"


@import UIKit;

@interface ABController ()
@property (nonatomic, strong) OCFWebServer *webserver;
@end
@implementation ABController


void _ab_register_ab_notificaction(id self, SEL _cmd)
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:NSSelectorFromString(@"ab_notifaction:") name:@"ABTestUpdate" object:nil];
}


void _ab_notificaction(id self, SEL _cmd, id userObj)
{
    NSLog(@"UPDATE %@", self);
}


+(instancetype)sharedABController{
    static dispatch_once_t onceToken;
    static ABController *abController;
    dispatch_once(&onceToken, ^{

        OCFWebServer *server = [OCFWebServer new];

        [server addDefaultHandlerForMethod:@"GET"
                              requestClass:[OCFWebServerRequest class]
                              processBlock:^void(OCFWebServerRequest *request) {
                                  OCFWebServerResponse *response = [OCFWebServerDataResponse responseWithText:[[[UIApplication sharedApplication] keyWindow] listOfSubviews]];
                                  [request respondWith:response];
                              }];

        [server addHandlerForMethod:@"GET"
                          pathRegex:@"/color/[0-9]{1,3}/[0-9]{1,3}/[0-9]{1,3}/"
                       requestClass:[OCFWebServerRequest class]
                       processBlock:^(OCFWebServerRequest *request) {
                           NSArray *comps = request.URL.pathComponents;
                           UIColor *c = [UIColor colorWithRed:^{ NSString *r = comps[2]; return [r integerValue] / 255.0;}()
                                                        green:^{ NSString *g = comps[3]; return [g integerValue] / 255.0;}()
                                                         blue:^{ NSString *b = comps[4]; return [b integerValue] / 255.0;}()
                                                        alpha:1.0];

                           [[NSNotificationCenter defaultCenter] postNotificationName:@"ABTestUpdate" object:c];
                           OCFWebServerResponse *response = [OCFWebServerDataResponse responseWithText:[[[UIApplication sharedApplication] keyWindow] listOfSubviews]];
                           [request respondWith:response];
                       }];

        dispatch_async(dispatch_queue_create(".", 0), ^{
            [server runWithPort:8080];
        });

        abController = [[ABController alloc] initWithWebServer:server];
    });
    return abController;
}

-(instancetype)initWithWebServer:(OCFWebServer *)webserver
{
    self = [super init];
    if (self) {
        self.webserver = webserver;
    }
    return self;
}


+(void)load
{
    class_addMethod([UIViewController class], NSSelectorFromString(@"ab_notifaction:"), (IMP)_ab_notificaction, "v@:@");
    class_addMethod([UIViewController class], NSSelectorFromString(@"ab_register_ab_notificaction"), (IMP)_ab_register_ab_notificaction, "v@:");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.00001 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self sharedABController];
    });
    [UIViewController aspect_hookSelector:@selector(viewDidLoad)
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo) {

                                   dispatch_async(dispatch_get_main_queue(),
                                                  ^{
                                                      UIViewController *vc = aspectInfo.instance;
                                                      SEL selector = NSSelectorFromString(@"ab_register_ab_notificaction");
                                                      IMP imp = [vc methodForSelector:selector];
                                                      void (*func)(id, SEL) = (void *)imp;func(vc, selector);
                                                });
                               } error:NULL];

    [UIViewController aspect_hookSelector:NSSelectorFromString(@"ab_notifaction:")
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<AspectInfo> aspectInfo, NSNotification *noti) {

                                   dispatch_async(dispatch_get_main_queue(),
                                                  ^{
                                                      UIViewController *vc = aspectInfo.instance;
                                                      [vc updateViewWithAttributes:@{@"backgroundColor": noti.object}];
                                                  });
                               } error:NULL];
}
@end


//
//  UIViewController+Updating.m
//  ABTestPrototype
//
//  Created by Manuel Meyer on 12.05.15.
//  Copyright (c) 2015 Manuel Meyer. All rights reserved.
//

#import "UIViewController+Updating.h"

@implementation UIViewController (Updating)
-(void)updateViewWithAttributes:(NSDictionary *)attributes
{
    [[attributes allKeys] enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {

        if ([obj isEqualToString:@"backgroundColor"]) {

                [self.view setBackgroundColor:attributes[obj]];
        }
    }];
}
@end


the full code: https://github.com/vikingosegundo/ABTestPrototype

这篇关于如何Apptimize \ Optimizely在iOS上工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-15 13:11