hotfixing ios apps with javascript

Post on 12-Apr-2017

177 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Hotfixing iOS appswith Javascript

Sergio Padrino Recio

About me…

• Sergio Padrino (@sergiou87)

• Started working on iOS on 2010

• Worked at Tuenti as iOS engineer (2012-2013)

• Worked at Fever as iOS Lead engineer (2014)

• Working at Plex since July 2014 – Current iOS Team Lead

About Plex

About Plex

Example case

Example case

• Submit to Apple.

• Wait for review: 7 days.

• In review: 3 hours.

• Release!

Example caseWTF??

8 days later…

We are all Peter

Example case• Extreme case, workflow full of flaws:

• Coder failed.

• Code reviewer failed.

• Testers failed.

• Apple… didn’t say anything.

Plex: The Monster

• Too many moving parts:

• Plex Media Server version

• User network setup

• Interoperability with other Plex players

• Audio/Video/Subtitle format

Fixes are usually quick, but…

Apple App Review process• Used to be 1 week.

• Now reduced to 1-3 days.

• Still not reliable…

• Reviewer testing the wrong binary.

• App rejected because… Apple 😒

• Christmas holidays.

• Sometimes just gets longer.

appreviewtimes.com

Can we improve this?

Can we improve this?

• Android and Web apps can be updated at any time.

• Native iOS apps need to bypass Apple review:

• Use a remote configuration (GroundControl).

• Embedded web pages can be updated.

• Or…

rollout.io• Service to hotfix apps without Apple review:

• Surround method with try…catch

• Replace method argument or return value

• No-op a method

• Basic scripting

rollout.io• Main problems (for us):

• Their platform has the ability to change our app remotely. If they’re compromised… 😱

• “Expensive” post-build script to upload dSym.

• Our dSym is massive and they had trouble processing it 😆

DIY• Can we do it ourselves… better?

• Recent pain working on Apple TV app:

• Too much Javascript 😖

• Objective-C magic

• My own solution… SPHotPatch

plex.tv

DIY

Configuration file

(hotfixes)

Plex for iOS

MAGIC✨

But… how??

But… how??

Method Swizzling!

Method SwizzlingAn Example

- (void)doSomethingWrong { self.array[self.array.count];}

Method SwizzlingAn Example

- (void)doSomethingWrong { self.array[self.array.count];} - (void)safe_doSomethingWrong { @try { [self safe_doSomethingWrong]; } @catch(…) { } }

Method SwizzlingAn Example

- (void)doSomethingWrong { self.array[self.array.count];} - (void)safe_doSomethingWrong { @try { [self safe_doSomethingWrong]; } @catch(…) { } }

INFINITERECURSION?!

Method SwizzlingAn Example

- (void)doSomethingWrong { self.array[self.array.count];} - (void)safe_doSomethingWrong { @try { [self safe_doSomethingWrong]; } @catch(…) { } }

INFINITERECURSION?!

doSomethingWrong

Method SwizzlingAn Example

array[array.count]

safe_doSomethingWrong

@try { [self safe_doSomethingWrong];} @catch(…) {}

doSomethingWrong

Method SwizzlingAn Example

array[array.count]

safe_doSomethingWrong

@try { [self safe_doSomethingWrong];} @catch(…) {}

safe_doSomethingWrongdoSomethingWrong

@try { [self safe_doSomethingWrong];} @catch(…) {}

Method SwizzlingAn Example

array[array.count]

doSomethingWrong

@try { [self safe_doSomethingWrong];} @catch(…) {}

Method SwizzlingAn Example

array[array.count]

safe_doSomethingWrong

Where is my Javascript??

• JavascriptCore since iOS 7.

• Run JS scripts from Objective-C.

• Bridging: call Objective-C code from JS.

• More Objective-C runtime magic.

Javascript Core

• Run Javascript scripts from Objective-C:

Bridging

• Invoke Objective-C code from Javascript:

MyCrashyClass

doSomethingWrong

Combine all that…

array[array.count]

MyCrashyClass

safe_doSomethingWrongdoSomethingWrong

JSContext *context = …[context evaluate:hotfixString]

Combine all that…

array[array.count]

doSomethingWrong

MyCrashyClass

JSContext *context = …[context evaluate:hotfixString]

…fixed!

array[array.count]

safe_doSomethingWrong

More Objective-C runtime?

• “Proxy” object that gives access to Obj-C stuff from JS.

More Objective-C runtime?

• Box method parameters to JSValue.

• Unbox return value from JSValue ⚠

• A lot of boilerplate to support as many types as possible.

• Took some “inspiration” from OCMockito.

Unboxing return value…IMP newImp = imp_implementationWithBlock(^(id self, ...) { va_list args; // Box parameters from args and prepare script NSString *script = ...; JSValue *result = [context evaluateScript:script]; if ([result isString]) return [result toString]; else if ([result isNumber]) return [result toInt32];}

Unboxing return value…

• Method parameters are easy: variadic arguments.

• There is no “wild card” for return types.

• The only option… override forwardInvocation:

• NSInvocation is the key!

Unboxing return value…

IMP newImp = imp_implementationWithBlock(^(id self, NSInvocation *inv) { // Box parameters from invocation and prepare script NSString *script = ...; JSValue *result = [context evaluateScript:script]; if (inv.methodSignature.methodReturnType == ‘@’) [inv setReturnValue:[result toObject]]; else if (inv.methodSignature.methodReturnType == ‘i’) [inv setReturnValue:[result toInt32]];}

MyCrashyClass

doSomethingWrong

Real Life™

array[array.count]

forwardInvocation:

original implementation

MyCrashyClass

ORIGdoSomethingWrongdoSomethingWrong

_objc_msgForward

Real Life™

forwardInvocation:

original implementation

array[array.count]

MyCrashyClass

ORIGdoSomethingWrongdoSomethingWrong

_objc_msgForward

Real Life™

forwardInvocation:

original implementation

array[array.count]

MyCrashyClass

ORIGdoSomethingWrongdoSomethingWrong

_objc_msgForward

Real Life™

forwardInvocation:

original implementation

array[array.count]

ORIGforwardInvocation:

JSContext *context = …[context evaluate:hotfixString]

MyCrashyClass

ORIGdoSomethingWrongdoSomethingWrong

_objc_msgForward

Real Life™

array[array.count]

ORIGforwardInvocation:forwardInvocation:

JSContext *context = …[context evaluate:hotfixString] original implementation

DEMO

SPHotPatch• Share it with friends to show off…

• …until someone tells me about JSPatch

• Open Source

• Better JS syntax (pre-processing)

• Extensions to use C stuff (CoreGraphics…)

JSPatch

JSPatch in Plex• Remote configuration file declares available patches.

• Patches belong to a:

• App version.

• App build.

• Patch channel (default: production).

JSPatch in Plex

• Multiple “channels” allow:

• Testing hotfixes before “releasing” them.

• Create custom patches for users if needed.

JSPatch in Plex

JSPatch in Plex• More features:

• Safe patching: if the app crashes before the patch can be downloaded and applied, next time the whole patching process will be synchronous.

• Skip patching in the next run.

• Clear last patch.

Things you can do

Hotfixingof course

Gather data

• For bugs hard/impossible to reproduce.

• Create specific patch channel for affected users.

• Deploy patches for those users.

• Ask them for feedback in the forums.

Gather data

• Example 1:

• Video stalls and stuttering.

• Patches to log more info.

• Patches to change different settings of the video player.

Gather data

• Example 2:

• Weird AutoLayout crash on old devices.

• Crash stacktrace impossible to read: all Apple code.

• Patches to change different bits of broken layout.

Rewrite the whole app in JS

Rewrite the whole app in JS

Things you CAN’T fix

Things you CAN’T fixseriously…

• Virtually nothing?

• You REALLY can write your whole app with JSPatch!

• Create extensions for C stuff you need.

• Apply patches as soon as you can.

• At Plex, we leave out +load methods.

What about Swift?

• Depends on Objective-C runtime so…

• Only works with NSObject.

• No structs or primitive types.

• No classes not inheriting from NSObject.

What about Apple?3.3.2 Except as set forth in the next paragraph, an Application may not download or install executable code. Interpreted code may only be used in an Application if all scripts, code and interpreters are packaged in the Application and not downloaded. The only exceptions to the foregoing are scripts and code downloaded and run by Apple's built-in WebKit framework or JavascriptCore, provided that such scripts and code do not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store

What about Apple?

• Just Javascript code that runs in JavascriptCore.

• Small fixes, not changing the whole app.

Security

• Avoid downloading patches from unknown sources.

• Don’t run JS scripts without a valid signature.

• Only allow to sign patches to a handful of people.

Good practices

• Don’t abuse JS patching. It’s just your safe net.

• Establish a proper workflow to catch bugs before the release.

• Test, test, test. Automate as much as you can.

Good practices• QA should find nothing. If they do, it should be a big

thing.

• If all the above fails and the bug has a huge impact, hotfix it.

• JS hotfixes should be reviewed and tested too.

• Immediately after, submit another build. Never rely on those hotfixes.

Questions?

Thank you!

top related