primers

At the end of 2016 AD, the beginning of 2017, A travel products in the Internet company, product managers crazy mention A/BTest demand, so that the division of the program apes talk about AB color change, evil product managers make the program apes are terrified, suffering… Ahem, I digress.

Recently, the team has done a lot of BUSINESS requirements for AB tests, and as these requirements become more and more common, we have had to upgrade our code organization to accommodate or better maintain our code on these requirements. Therefore, this paper mainly expounds some ideas and thoughts of the business team in doing AB Test.

A/B Test

What is A/B Test?

Since product managers are crazy about A/B tests, we need to figure out what A/B tests are. Why are product managers so obsessed with A/B tests?

A/B Test is to make two plans (for example, two websites and app pages) for the same goal, and let some users use Plan A and the other use plan B. The usage of users is recorded to see which plan is closer to the Test result, and the conclusion is confirmed to be credible in the promotion to all traffic.

Note the bold words in the above paragraph, which will be the core value of AB Test.

In fact, A/B Test is the controlled experiment that we often do in chemistry experiment class in middle school. This kind of controlled experiment is transferred to the Internet. By changing the experimental group with A single variable and comparing with the original control group, we can see which scheme can improve user experience (conversion rate) by comparing data indicators.

What are the advantages of AB Test (for the product)?

Advantages 1. Grayscale release

Grayscale publishing refers to the smooth transition between black and white. A/B Test is A grayscale release method, in which some users continue to use A and some users start to use B. If users have no objection to B, the scope will be gradually expanded and all users will be migrated to B. Grayscale publishing can ensure the stability of the whole system, and problems can be found and adjusted at the beginning of the grayscale to ensure its impact.

Advantage 2. Reversible scheme

Reversible, is similar to the previous released gray, not just gray level control is stronger, when we found that the experimental group after release plan appeared serious fault, or comparative data volume six to one, you can switch back to the original amount of all the control group, to ensure the stability of the online environment, does not affect the normal use of the user.

At this point, there is more possibility of trial and error for products. Considering that in the previous era of lack of dynamic App, the release of App was like water poured out by the married daughter, which would never return. After updating the released product, users could not go back to the previous version. From this point on, product managers love A/B tests!

Advantage 3. Data-driven

Data-driven, which I think is crucial, in the current era of big data with user data as the business soil, a product driven by data will be able to more sonorous support for the full release of this product, is also an important trump card for product managers to promote new solutions. Before launching a new product, either call it a reference to a competitor (no objection to copying, copying is the fastest way to catch up with competitors, but not the way to surpass them), or open your imagination and think that a new solution or interactive experience will lead to more conversions. There is no data for this method. Only after the project is launched can we determine whether the target is really achieved as the product manager expected.

Through A/B Test, it can determine which scheme is superior in A short time by comparing the data of illumination and experimental group under the condition that the normal operation of the line is not completely affected, so that the product conversion rate can be improved in A short time. This is where the product manager persuades the boss and demonstrates the value of his ability! So, love!

Things for development engineers to focus on

Ask the product manager

Before doing the AB Test, here are some questions to ask the product manager:

  1. What is the goal?
  2. What is version AB?
  3. What is the sample size?
  4. How do users stream?
  5. How long is the test?
  6. How to measure effectiveness?

This is actually the point of the bold text in the preceding paragraph. Of course, there are some issues that the server needs to be concerned about, such as questions 3 and 4.

So what are the concerns of client development?

What is the goal?

First question, what is the goal? What is the purpose of A/B Test? This is what we need to ask. For the client, A/B Test requires the client to maintain two sets of code for the same business. Is it really worth it? There may be times when the arm doesn’t work as well as the leg, but there may be some requirements that don’t require an A/B Test under your analysis. For example, a competitor has already made a plan for a long time (don’t tell me I’m not confident enough to copy it), or a UI change is clearly better than the previous plan, etc.

What is the A/B Test version? How long is the test?

Second question, what is version A/B Test? How long is the test? In fact, these two questions are to confirm when the A/B Test scheme will go online and when it will go offline. We need to be clear about the time of offline, because during this period, we need to maintain two sets of code, and in the context of App Size is so tight and everyone is trying to slim down, the large or need of your installation package is the reason why users do not choose your product from the beginning! For A/B Test scheme, the code will be deleted as soon as it is written. The timing of deleting the code depends on when the A/B Test scheme is offline and how long it will be left for QA Test engineers to Test after deleting the code, which should be arranged.

How to measure effectiveness?

Say “this is costing” at least 5 times a day on some developments. In this way, the r&d cost is deducted vigorously, and the demand with low value and low profit is cut down as far as possible, and the demand with unclear profit is arranged behind. It is equivalent to saving 2-3 development engineers on the basis of almost constant output. This is also the key to maintaining teams for the long term, by streamlining at the source rather than demanding superhuman programmers.

How to measure the effect is to judge whether the demand is a project with low value and low profit or unclear. We all want to make valuable things, not just features ready to be cut off at any time. We hope that product managers dare to think and think about it!

IOS A/B Test solution Exploration

All right, finished with the product, let’s get down to business. Since the original set of code has two kinds of logic, or two kinds of UI style, it needs to be removed from the original logic, the inevitable result is more than an if statement, that if the judgment place is more, we also like if, if, if, if, I…. As the saying goes: write business code, move a good brick is a programmer’s basic requirements. Next, let’s talk about xiao Sheng’s exploration process of A/B Test scheme.

Program Exploration History

First, let’s briefly introduce the business background of this exploration:

This section describes the A/B Test solution

  • A Scheme online scheme, full;
  • Plan B, which applies to one of the cases of A, is A subset of Plan A;
  • Non-standard A/B Test is only A transition, because plan A is the full plan and cannot be dropped, and Plan B is part of A.

In iOS, the Delegate and DataSource protocol functions in the typical UITabelView are divided into A/B schemes.

The basic function A over B

As I said, plan A is A full plan, so the switch here will have A default plan. However, this writing method is really too low. Every function called has to judge A/B once, which affects efficiency for the time being, and also causes A pit in maintenance. When I see the function list page in the second picture, I feel that the head is too large, which also leads to the excessively large Controller. So this scheme is not desirable.

Method selects child + dictionary, cache type A/B

Due to the dynamic nature of The Runtime in Objective-C, we can store the method selection subcache in A dictionary. When we need to determine the call point of scheme A/B, we can obtain the method cache dictionary of the corresponding scheme. During the call, we only need to call the corresponding cache dictionary. And of course what we need to do here is extend the NSObject class – (id)performSelector: SEL, aSelector withObject: id object; Enable it to support multiple parameter passing.

- (id)fperformSelector:(SEL)selector withObjects:(NSArray *)objects { NSMethodSignature *methodSignature = [[self class]  instanceMethodSignatureForSelector:selector];if(methodSignature == nil)
    {
        @throw [NSException exceptionWithName:@"Throw exception error" reason:@"There is no method, or the method name is wrong." userInfo:nil];
        return nil;
    }
    else
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
        [invocation setTarget:self];
        [invocation setSelector:selector]; / / the number of parameters in the signature method, internal contains a self and _cmd, so parameters starting from the third NSInteger signatureParamCount = methodSignature. NumberOfArguments - 2; NSInteger requireParamCount = objects.count; NSInteger resultParamCount = MIN(signatureParamCount, requireParamCount);for (NSInteger i = 0; i < resultParamCount; i++) {
            id  obj = objects[i];
            [invocation setArgument:&obj atIndex:i+2]; } [invocation invoke]; // Return value handler id callBackObject = nil;if(methodSignature.methodReturnLength)
        {
            [invocation getReturnValue:&callBackObject];
        }
        returncallBackObject; }}Copy the code

This is just A little bit better than the last one, which is that we don’t judge A over B in every function, we just judge it once. But it still doesn’t solve the problem that controllers are too big to gracefully scale. It also introduces the extra overhead of doing Runtime message forwarding, and the awkwardness of having to type the return value of performSelector.

The strategy pattern of design pattern

As shown in the figure, the method that needs to be divided into A/B is abstracted into A protocol through the policy mode, and then A policy parent class is abstracted to follow this protocol, and its two A/B subclasses also follow this protocol. In this way, the Controller only needs to initialize the corresponding policy class at the point where it determines the call of A/B policy. Through the parent class pointer to call the protocol method of the child class, to achieve the execution of A/B function. In this way, object-oriented inheritance and polymorphic mechanism is adopted to complete A perfect A/B function execution, AB policy can be switched freely, avoiding the use of multiple conditions judgment, while meeting the open and closed principle, open to expansion (add new policy class), closed to modify.

Protocol Protocol dispenser used in the A/B Test scheme

Protocol distribution can be simply understood as handing over protocol proxies to multiple object implementations, similar to multicast delegation.

Protocol proxy is frequently used in development, and developers often encounter a problem — continuous event transmission. For example, in order to isolate encapsulation, developers may often pull the TableView’s delegate or datesource out of a separate object, while other objects (such as VC) need to fetch delegate events only through a secondary pass of the event. Is there an easier way? The protocol distributor comes in handy.

Since the multicast delegate message distribution can be implemented, the specified receiver of the message distribution, is not A/B Test messages are distributed as A/B?

Give your reader with dry, LJFABTestProtocolDispatcher dispenser is an agreement, through the tool can easily realize distributed protocol events to multiple implementers, call what implementers and can specify. The most common UITableViewDelegate and UITableViewDataSource protocol, for example, can be very easy to send through LJFABTestProtocolDispatcher distributed to multiple objects, and can specify A/B plan execution, the specific Demo for reference.

The principle of analytic

The principle is not complicated, the Protocol Dispatcher does not implement the Protocol, it only needs to distribute the corresponding Protocol event to different implementers. How is distribution implemented?

The NSObject object responds to unimplemented Selector calls primarily through the following functions

  • Scheme 1: Dynamic parsing
    + (BOOL)resolveInstanceMethod:(SEL)sel;
    + (BOOL)resolveClassMethod:(SEL)sel;
    Copy the code
  • Scheme 2: Fast forwarding
    / / return object implements the method of forward - (id) forwardingTargetForSelector: (SEL) aSelector OBJC_AVAILABLE (10.5, 2.0, 9.0, 1.0);Copy the code
  • Scheme 3: Slow forwarding
    / / function signature - (NSMethodSignature *) methodSignatureForSelector aSelector: (SEL) / / function calls - forwardInvocation: (NSInvocation (void) *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
    Copy the code

So, the Protocol Dispatcher can pass A call to the Protocol Selector to the Implemertor, and the Implemertor implements the Selector function, and the A/B calls that are actually specified, The subscripts of all implementer organizations need to be passed in to specify the invocation

/** The Protocol Dispatcher can pass a call to the Protocol Selector to the Implemertor. The Implemertor implements the Selector function */ - (void)for forwardInvocation:(NSInvocation *)anInvocation {SEL aSelector = anInvocation.selector;if(! ProtocolContainSel(self.prococol, aSelector)) { [super forwardInvocation:anInvocation];return;
    }
    
    if (self.indexImplemertor)
    {
        for (NSInteger i = 0; i < [self.implemertors count]; i++)
        {
            ImplemertorContext *implemertorContext = [self.implemertors objectAtIndex:i];
            if(i == self.indexImplemertor.integerValue && [implemertorContext.implemertor respondsToSelector:aSelector]) { [anInvocation invokeWithTarget:implemertorContext.implemertor]; }}}else
    {
        for (ImplemertorContext *implemertorContext in self.implemertors)
        {
            if([implemertorContext.implemertor respondsToSelector:aSelector]) { [anInvocation invokeWithTarget:implemertorContext.implemertor]; }}}}Copy the code

Design is the key

How to distribute only calls to the Protocol Selector function is the key to the design, the system provides a function

objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod)
Copy the code

You can use the following methods to determine whether a Selector belongs to a Protocol

struct objc_method_description MethodDescriptionForSELInProtocol(Protocol *protocol, SEL sel) {
    struct objc_method_description description = protocol_getMethodDescription(protocol, sel, YES, YES);
    if (description.types) {
        return description;
    }
    description = protocol_getMethodDescription(protocol, sel, NO, YES);
    if (description.types) {
        return description;
    }
    return (struct objc_method_description){NULL, NULL};
}
 
BOOL ProtocolContainSel(Protocol *protocol, SEL sel) {
    return MethodDescriptionForSELInProtocol(protocol, sel).types ? YES: NO;
}
Copy the code

Also, the protocol dispenser is not a singleton, but a local variable. How do you prevent a local variable from being released late? Here uses an idea of “self-release”, see the source code:

- (instancetype)initWithProtocol:(Protocol *)protocol
            withIndexImplemertor:(NSNumber *)indexImplemertor
                  toImplemertors:(NSArray *)implemertors
{
    if(self = [super init]) { self.prococol = protocol; self.indexImplemertor = indexImplemertor; NSMutableArray *implemertorContexts = [NSMutableArray arrayWithCapacity:implemertors.count]; [implemertors enumerateObjectsUsingBlock:^(id implemertor, NSUInteger idx, BOOL * _Nonnull stop){ ImplemertorContext *implemertorContext = [ImplemertorContext new];  implemertorContext.implemertor = implemertor; [implemertorContexts addObject:implemertorContext]; Why is a ProtocolDispatcher attribute associated"Self-release"The ProtocolDispatcher is not a singleton, but a local variable that is emitted when the Protocol Implemertor is released. // The key must be random. Otherwise, when there are two dispensers, the key will be overwritten, causing the first dispenser to be released. So key = _cmd doesn't work. void *key = (__bridge void *)([NSString stringWithFormat:@"%p",self]);
            objc_setAssociatedObject(implemertor, key, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }];
        self.implemertors = implemertorContexts;
    }
    return self;
}
Copy the code

Matters needing attention

Protocol dispensers use functions that need to know how to handle functions that return values, such as

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
Copy the code

As we know, in iOS, the result of a function execution is stored in register R0, and the last one overwrites the first one. Therefore, when you encounter a function that returns a result, the function executed after the result returns the final value.

Thank you

Protocol dispenser. I am not the first Protocol dispenser, but I was inspired to use Protocol dispenser in A/B Test after reading this article. Thanks to the author and the open source community.

Explore A/B Test components in the business module

With the increasing number of A/B Test codes, the A/B Test component in the service module is nothing more than to facilitate the A/B Test codes of the up and down services, improve work efficiency, and make writing and deleting codes A happy thing.

There are a lot of articles about iOS componentization on the web, but instead of reinventing the old tricks, you can search for some definitions and experiences about componentization.

Now that the entire client has been componentized, can A business programmer who is not an architecture group try to solve A/B Test componentization within A business module? Most of iOS componentization revolves around Cocoapods, so let’s start by asking a few technical questions in the context of Cocoapods iOS highly componentized framework.

Can different static libraries with the same architecture be merged?

This problem is mainly based on the current entire client architecture, each line of business provides its own static libraries to the shell project, we will merge the same static libraries of different architectures most of the time (when packaging), can different static libraries of the same architecture be merged?

The answer is yes.

To merge the same static library from different schemas, use the following command:

  • View the CPU architectures supported by static libraries
    Lipo - info libname. A. (or libname. Framework/libname)Copy the code
  • Merge static libraries
    Lipo-create Static inventory store path 1 Static inventory store path 2... -output Indicates the storage path after consolidationCopy the code
  • Static library split
    Lipo Source file path n/A THIN CPU architecture name N/A output File path after splittingCopy the code

So what about merging different static libraries with the same architecture?

A static library file, also known as a document file, is a collection of.o files. In Linux (Unix) use the tool “AR” to maintain and manage it. Its members are.o files. In addition to the.o file, there is a special member named __.symdef. It contains valid symbols (function names, variable names) defined by all members of the static library. Therefore, when a member is added to the library, member __.symdef needs to be updated accordingly, otherwise all symbols defined in the added member will not be located by the linker. The command to complete the update is:

ranlib libname.a
Copy the code

For example, we have two static libraries libflight. a and libhotel. a, merged into one libflight_hotel. a.

  • Extract lib.a from the same schema. Flight.a static library

    lipo -info Flight.a
    Copy the code

    You can see:

    Input file /Users/f.li/Desktop/ libflight. a is not a fat file non-fat file: /Users/f.li/Desktop/ Different static library merge with the same architecture/libflight. a is architecture: x86_64Copy the code

    LibFlight. A is not a fat file and libFlight. A is architecture: x86_64

    Fat file = x86_64; fat file = x86_64; fat file = x86_64

    Of course, if it is a Fat file, we need to pull out the library of the same platform architecture.

    lipo libFlight.a -thin x86_64 -output libFlight.a
    Copy the code

    This will fetch libflight.a for the x86_64 architecture.

  • View the list of files contained in the library.

    Ar -t /Users/f. I /Desktop/ Different static libraries merged with the same architecture/libflight.a __.SYMDEF SORTED flight.oCopy the code

    See that libflight. a has two files, __.SYMDEF SORTED and flight.o

  • Unzip the object file.

    ~libFlight_o ar xv /Users/f.li/Desktop/libFlight.a
    x - __.SYMDEF SORTED
    x - Flight.o
    Copy the code

    This gives you __.SYMDEF SORTED and flight. o files in the libFlight_o folder. Similarly, get __.symdef SORTED and hotel.o in the libHotel_o folder

  • Merge, repackage. Move __.symdef SORTED, flight. o, and hotel. o into the libFlight_Hotel_o folder. Repackage the object file;

    ar rcs libFlight_Hotel.a /Users/f.li/Desktop/libFlight_Hotel_o/*o
    Copy the code

    This gives you libflight_hotel.a.

  • Update __.symdef file. In fact, we added hotel. o to libflight. a. Finally, we need to update __.symdef.

    ranlib libFlight_Hotel.a
    Copy the code

    If the header file is included, then the header file is also included in the project using libflight_hotel. a.

But obviously that’s too much trouble.

Xcode subproject?

Xcode sub-project, in fact, is to help us in a project with Git submodule for module development. Get your head together.

  • Create a target(Flight_Hotel_Project) as the parent project of the Application’s Xcode project. And git.
  • Create a tagget(Flight_SubProject) for the Static Library’s Xcode project and git it.
  • Add a Git subModule for the parent project. See Git for details.
  • Drag the child project folder into the parent project.
  • Add flight_subproject. a to the parent project’s link binary with library
  • Add header file search paths in the parent project’s Header Search paths$(SRCROOT)/Flight_SubProject/Flight_SubProject, where the $(SRCROOT) macro represents your project file directory.
  • Compile and run.

This is a return to the state of the previous architecture, where decoupling cannot be invoked and interdependence is high.

What is the concept of Cocoapods’ subspecs? Does subspec have its own git repository? Is it a child of POD?

The answer is that the subspec is not a separate code base, but is compiled separately, resulting in a production with pod.

Why ask Cocoapods Subspecs? Because after componentization based on the Cocoapods architecture, the business provides static library types of PODS externally.

The source type is subspec. When importing a POD, you can choose to import the subspec directory or set the podSpec default subsepc. Subspect can also have dependencies between them.

What is the ultimate solution?

The internal split of the line of business can be made into multiple PODS, and finally provide a POD that depends on all the components of the pod inside the business. In this way, the external architecture packaging is not affected, and the line of business can also be flexibly modified.

Finally, the pod, which relies on all the internal components of the business, also provides a static library. The internal components of the business pod do not need to provide a static library, but will also have a separate Git.

Of course, such A/B Test componentization scheme within the business is currently in the exploratory stage, because the code volume of our A/B Test does not reach the point that we need to split it, and all this stage is still in the technical expansion and research stage.

summary

There are so many explorations about iOS A/B Test at present. A/B Test is indeed A good solution for products, especially reversible and data-driven. Of course, A/B Test is viewed from the perspective of development. Since it is a product-friendly solution, our code should be current, after all, technology is for business.

Some time ago when I was watching Sunny’s live broadcast, I talked about the advanced speed of iOS development

Pure daily development < pure reading, blog < experiment, Demo < blog < systematic sharing and discussion < provide a complete open source solution

Just before his own advanced speed to write blogs, in different sections of the recent half a year in the team has launched a wave of technology sharing and team blog, hope to be able to share to systemic, discussion and a complete open source solution both high segmentation points, the combined with the latest business and their own some of the ideas and practices, completed a blunt points to try, hope in the way of impact points YueZhanYueYong!