Hotfixing iOS apps with Javascript

  • View
    142

  • Download
    3

Embed Size (px)

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 didnt 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

    http://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 trycatch

    Replace method argument or return value

    No-op a method

    Basic scripting

    http://rollout.io

  • rollout.io Main problems (for us):

    Their platform has the ability to change our app remotely. If theyre compromised

    Expensive post-build script to upload dSym.

    Our dSym is massive and they had trouble processing it

    http://rollout.io

  • 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 valueIMP 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 CANT fix

  • Things you CANT 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 tha