ekeren ekeren - 9 months ago 39
Objective-C Question

How do A/B testing platforms replace Objective-C assets on the fly?

Leanplum, Apptimize and other A/B testing platforms for iOS have the ability to download assets (nib files ,images, etc...) from the web and replace them at runtime.

The naive approach will be to download the new assets and replace them in the resource bundle directory, but it is impossible to write files to the resource directory because of permissions.

The question is, what techniques does these A/B testing platforms use to replace assets at runtime?


After reading the symbols on leanplum static library file (using nm) it seems that they are Swizzling cocoa file system API's.

for example: (an example line from

nm -m leanplum.a

-[NSBundle(LeanplumExtension) leanplum_URLForResource:withExtension:]

By using otool I can print the implementation:

-[NSBundle(LeanplumExtension) leanplum_URLForResource:withExtension:]:
0000000000000069 pushq %rbp
000000000000006a movq %rsp, %rbp
000000000000006d pushq %r15
000000000000006f pushq %r14
0000000000000071 pushq %r13
0000000000000073 pushq %r12
0000000000000075 pushq %rbx
0000000000000076 subq $0x18, %rsp
000000000000007a movq %rcx, %rbx
000000000000007d movq %rdi, 0xffffffffffffffc8(%rbp)
0000000000000081 movq %rdx, %rdi
0000000000000084 callq _objc_retain
0000000000000089 movq %rax, %r14
000000000000008c movq %rbx, %rdi
000000000000008f callq _objc_retain
0000000000000094 movq %rax, 0xffffffffffffffd0(%rbp)
0000000000000098 movq _originalMainBundle(%rip), %rcx
000000000000009f movq "+[NSBundle(LeanplumExtension) leanplum_mainBundle]"(%rcx), %rdi
00000000000000a2 movq 0x4487(%rip), %rsi
00000000000000a9 movq _objc_msgSend(%rip), %r12
00000000000000b0 movq %r14, %rdx
00000000000000b3 movq %rax, %rcx
00000000000000b6 callq *%r12
00000000000000b9 movq %rax, %rdi
00000000000000bc callq _objc_retainAutoreleasedReturnValue
00000000000000c1 movq %rax, %r13
00000000000000c4 movq _skippedFiles(%rip), %rax
00000000000000cb movq "+[NSBundle(LeanplumExtension) leanplum_mainBundle]"(%rax), %rbx
00000000000000ce movq 0x4463(%rip), %rsi
00000000000000d5 movq %r13, %rdi
00000000000000d8 callq *%r12
00000000000000db movq %rax, %rdi
00000000000000de callq _objc_retainAutoreleasedReturnValue
00000000000000e3 movq %rax, %r15
00000000000000e6 movq 0x4453(%rip), %rsi
00000000000000ed movq %rbx, %rdi
00000000000000f0 movq %r15, %rdx
00000000000000f3 callq *%r12
00000000000000f6 movb %al, %bl
00000000000000f8 movq %r15, %rdi
00000000000000fb callq _objc_release
0000000000000100 testb %bl, %bl
0000000000000102 je 0x115
0000000000000104 movq %r13, %rdi
0000000000000107 callq _objc_retain
000000000000010c movq %rax, %r15
000000000000010f movq 0xffffffffffffffd0(%rbp), %rbx
0000000000000113 jmp 0x13b
0000000000000115 movq 0x4414(%rip), %rsi
000000000000011c movq 0xffffffffffffffc8(%rbp), %rdi
0000000000000120 movq %r14, %rdx
0000000000000123 movq 0xffffffffffffffd0(%rbp), %rbx
0000000000000127 movq %rbx, %rcx
000000000000012a callq *_objc_msgSend(%rip)
0000000000000130 movq %rax, %rdi
0000000000000133 callq _objc_retainAutoreleasedReturnValue
0000000000000138 movq %rax, %r15
000000000000013b movq %r13, %rdi
000000000000013e callq _objc_release
0000000000000143 movq %rbx, %rdi
0000000000000146 callq _objc_release
000000000000014b movq %r14, %rdi
000000000000014e callq _objc_release
0000000000000153 movq %r15, %rdi
0000000000000156 addq $0x18, %rsp
000000000000015a popq %rbx
000000000000015b popq %r12
000000000000015d popq %r13
000000000000015f popq %r14
0000000000000161 popq %r15
0000000000000163 popq %rbp
0000000000000164 jmpq _objc_autoreleaseReturnValue

  1. Can someone verify my findings?

  2. I wonder how they get this entire list of API covered?

  3. What happens if I open an image with fopen or other C lib?

  4. How can I decipher the output of otool?

Answer Source

The question is, what techniques does these A/B testing platforms use to replace assets at runtime?

An educated guess is that they use method swizzling to swap the implementations of standard methods (e.g. [NSBundle URLForResource:withExtension:]) with those of their own versions of those methods (e.g. [NSBundle(LeanplumExtension) leanplum_URLForResource:withExtension:]). This means that your code uses the same method that you would otherwise, but you get different behavior -- in this case, the URL that's returned depends on which version of the resource the A/B testing framework decides to present to the user.

I wonder how they get this entire list of API covered?

By working carefully. The number of methods used to load resources isn't unmanageable.

What happens if I open an image with fopen or other C lib?

I expect that the framework wouldn't be able to swap resources in such a case. There are lots of ways to load data, and the framework can't possibly anticipate all of them. But if you're using an A/B framework, you presumably want the resources to be replaced as appropriate for your testing, so it doesn't make a lot of sense to try to defeat the framework.

How can I decipher the output of tool?

For the case you've shown, learn to read assembly language. (otool has a lot of options, though, and they don't all produce assembly.)