Shopify / mobile-buy-sdk-ios

Shopify’s Mobile Buy SDK makes it simple to sell physical products inside your mobile app. With a few lines of code, you can connect your app with the Shopify platform and let your users buy your products using Apple Pay or their credit card.
MIT License
452 stars 198 forks source link

Crash on all API calls #405

Closed cclogg closed 7 years ago

cclogg commented 8 years ago

Hey, so I added the mobile SDK (as static framework) to an app today, following the documentation guidelines. It looks like the BUYClient initializes but anything I do like get collection-1 or get a product by ID just crashes on this: screen shot 2016-11-09 at 6 14 20 pm screen shot 2016-11-09 at 6 14 53 pm And this is literally the only code I have: shopifyClient = [[BUYClient alloc] initWithShopDomain:@"XXXXXXXXXXX.myshopify.com" apiKey:@"XXXXXXXXXXXX" appId:@"X"]; [shopifyClient getProductById:@(XXXXXXXXX) completion:^(BUYProduct * _Nullable product, NSError * _Nullable error) { NSLog(@"hi"); // never reached }]; (Tested on iOS 10 simulator and iOS 10 phone). --> Similar crash happens with even the basic "getCollectionsPage:1" method.

Not sure what I am doing wrong... the online implementation we have works with our collection/products... Any help is appreciated.

bgulanowski commented 8 years ago

You need to step into block's code and determine why block(obj) returns nil. That will lead you back up the call stack where you will find some other object that was expected to exist but is actually nil.

Are there any other warnings or errors reported in your console? For example, did the program fail to find the file that defines the model?

-[BUYClient buy_objectsWithEntityName:JSONArray:] defines the block that is returning nil. Did you review its implementation. It looks like this:

- (NSArray<id<BUYObject>> *)buy_objectsWithEntityName:(NSString *)entityName JSONArray:(NSArray *)JSON
{
    NSEntityDescription *entity = [self.model entitiesByName][entityName];
    Class cls = entity.managedObjectClass;
    return [JSON buy_map:^id(NSDictionary *JSONDictionary) {
        return [[cls alloc] initWithModelManager:self JSONDictionary:JSONDictionary];
    }];
}

The block is one line. For that line to return nil, chances are hight that cls is Nil. Which suggested that entity is nil and therefore that model is nil. (The entityName in this case should be @"Product", which should map to the Product entity, represented by the BUYProduct class.

cclogg commented 8 years ago

Here is the full crash log statement: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil' *** First throw call stack: ( 0 CoreFoundation 0x000000010e5c634b __exceptionPreprocess + 171 1 libobjc.A.dylib 0x000000010db0221e objc_exception_throw + 48 2 CoreFoundation 0x000000010e4f738f -[__NSArrayM insertObject:atIndex:] + 1375 3 MyApp 0x000000010906f6eb __33-[NSArray(BUYAdditions) buy_map:]_block_invoke + 139 4 CoreFoundation 0x000000010e61ee2a -[__NSSingleObjectArrayI enumerateObjectsWithOptions:usingBlock:] + 58 5 MyApp 0x000000010906f5f0 -[NSArray(BUYAdditions) buy_map:] + 288 6 MyApp 0x000000010908ada9 -[BUYModelManager buy_objectsWithEntityName:JSONArray:] + 361 7 MyApp 0x00000001090937ba -[BUYModelManager(BUYProductInserting) insertProductsWithJSONArray:] + 74 8 MyApp 0x000000010908ce9d __53-[BUYClient(Storefront) getProductsByIds:completion:]_block_invoke + 253 9 MyApp 0x00000001090a0a94 __65-[BUYClient requestForURL:method:object:start:completionHandler:]_block_invoke_2 + 52 10 Foundation 0x000000010ceb02cd __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7 11 Foundation 0x000000010ceaffaf -[NSBlockOperation main] + 101 12 Foundation 0x000000010ceae6ac -[__NSOperationInternal _start:] + 672 13 Foundation 0x000000010ceaa5ef __NSOQSchedule_f + 201 14 libdispatch.dylib 0x000000010f6380cd _dispatch_client_callout + 8 15 libdispatch.dylib 0x000000010f6188d6 _dispatch_main_queue_callback_4CF + 406 16 CoreFoundation 0x000000010e58a4f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 17 CoreFoundation 0x000000010e54ff8d __CFRunLoopRun + 2205 18 CoreFoundation 0x000000010e54f494 CFRunLoopRunSpecific + 420 19 GraphicsServices 0x0000000110338a6f GSEventRunModal + 161 20 UIKit 0x000000010b625f34 UIApplicationMain + 159 21 MyApp 0x000000010900385f main + 111 22 libdyld.dylib 0x000000010f68468d start + 1 23 ??? 0x0000000000000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException

And this is where it seems to crash when I don't use an exception breakpoint: (still from the getProductById method call) screen shot 2016-11-10 at 1 44 23 pm

I don't really know what to do, this is all code inside the framework. To the best of my knowledge I have done everything from https://help.shopify.com/api/sdks/mobile-buy-sdk/ios/integration-guide/setup

P.S. in the docs, regarding static framework integration, it fails to mention that one should add the Passkit framework, otherwise iOS 9 has issues.

bgulanowski commented 8 years ago

We can't predict every possible problem, and we can't see your environment. Open source software requires a bit more from users. Please follow the advice I already provided.

cclogg commented 8 years ago

Heya, so I tested the sample obj-C app (with our shopify key) and that worked... however if I replace the dynamic linked framework with the static one, it also crashes in the same way. I hope I am making/adding the static framework properly... I open this Xcode project: screen shot 2016-11-10 at 4 00 20 pm Then I cmd+B: screen shot 2016-11-10 at 4 00 39 pm This gives the following: screen shot 2016-11-10 at 3 59 47 pm I then dragged that into my app project, and made sure it is in linked frameworks and libraries. I also added the linker flag -all_load.

cclogg commented 8 years ago

Also regarding your comment, cls is infact 0x0... when I debug the crash, here is what it looks like: screen shot 2016-11-10 at 4 09 48 pm I should point out though, that in that breakpoint, I printed out "JSON" and it had all valid data... but what you said about the model being nil is probably on the mark. I don't know beyond that, and why the static framework does it.

bgulanowski commented 8 years ago

I created a new (Swift) test app and dragged the built static framework into it (so that it copied the framework into the target directory). Then I imported the Buy module and added the following to application(didFinishLaunchingWithOptions):

        let client = BUYClient(shopDomain: "", apiKey: "", appId: "")
        let entity = client.modelManager.model.entitiesByName[BUYProduct.entityName()]
        print(entity?.managedObjectClass ?? "No Product class found")

And it printed BUYProduct. So things seem to be working normally.

The managed object model file is read from the Mobile Buy SDK.momd/Mobile Buy SDK.mom file in the framework bundle. You should confirm that is there. No reason it wouldn't, but that seems to be the problem.

I'm afraid you're going to have to trace the problem all the way to its source.

cclogg commented 8 years ago

Okay so I followed your steps with a new swift project and mine just printed "No Product class found". So something is up for me. Are you able to send me your built Buy.framework so I can try with that? Also my .mom file is there.

cclogg commented 8 years ago

Hey, just checking if you're able to send me that soon. Thanks!

bgulanowski commented 8 years ago

Buy.framework.zip

Sorry I missed your request. Here you go. If you're feeling ambitious you can use FileMerge or Kaleidoscope to compare this with yours and see if there is any real difference.

cclogg commented 8 years ago

Ok great, so I tried it and still no luck, which is maybe a good thing since it means there's something about my system or Xcode that is causing the issue. I also tried updating to Xcode 8.1 but nothing changed. I will let you know what I find when I do.

Also a git diff between our frameworks only had the imports being different, ie mine were like #import <Buy/BUYModelManager.h> and yours were #import "BUYModelManager.h"... but that didn't seem to affect anything.

bgulanowski commented 8 years ago

We changed the import declarations to use quotes instead of angle brackets. Angle brackets used to be the "right way" for frameworks, but since modules, it doesn't really matter. But angle brackets don't work with Swift modules. So we had to change it. That change will be in the 2.1 release, currently getting prepared.

Sorry that I don't have any particular ideas as to what is going on. My recommendation is to go deeper into the stack to find out why the JSON decoder can't find the right model class.

As previously mentioned, the model class comes from the entity, which is retrieved from the managed object model, which comes from the managed object model description file:

NSEntityDescription *entity = [self.model entitiesByName][entityName];
Class cls = entity.managedObjectClass;

What is self.model equal to? If the model exists, does it have entities? When the model is loaded the first time, does it find the definition file OK?

We find the model like this (BUYModelManager.m):

[NSManagedObjectModel mergedModelFromBundles:@[[NSBundle resourcesBundle]]];

and +resourcesBundle looks like this (same file):

+ (instancetype)resourcesBundle
{
#if COCOAPODS
    return [NSBundle bundleWithURL:[self resourcesBundleURL]];
#else
    return [self frameworkBundle];
#endif
}

Maybe that breaks on different OS versions or because of some other unexpected difference in the environment.

cclogg commented 8 years ago

Ok good news! I fixed it... but it's not the cleanest solution.

I tried poking around that code that you just mentioned, but this stack-overflow answer ended up being the solution: http://stackoverflow.com/a/29453132 I had to take the "Mobile Buy SDK.xcdatamodel" from the Mobile Buy SDK project, and put it into my project (plus make sure it is in compile sources!). After this, my project works with the static framework.

I am not sure if there is a cleaner way to somehow force it to work from inside the framework... because otherwise anyone using the static way will have to do this (I think).

bgulanowski commented 8 years ago

I think it has to do with how static frameworks work. Apologies: the last time I actually used one was a long time ago, and the static framework target here predates my joining the project.

Real frameworks (as of iOS 8, and on mac OS X) are included as-is within an app bundle. Static frameworks, however, are not. The executable is just a static library wrapped in the guise of a framework. The code is statically linked into the app executable and the framework bundle is not shipped in the app. So it requires handling resources (including mom files) specially.

Cocoapods generates a resources bundle. If you're doing the static linking yourself, you need to manually include the resources. We could have created an explicit resources bundle to include in targets and manually specify the static framework. It was an oversight. Unfortunately, it doesn't look like we'll be able to address that in the foreseeable future, but we can expand on the documentation to alert users to this issue.

cclogg commented 8 years ago

I see I see... yeah I like to use static frameworks because they keep things nice and simple. Thanks for all the help.

johnerck commented 7 years ago

Wow. Thank you @cclogg ( and @bgulanowski ) for working through this. I read the entire thread, my situation was 100% the exact same. I just copied the SDK's Mobile Buy SDK.xcdatamodeld to my project, and voilà, app doesn't crash, SDK works. Really glad we're able to use the static framework to keep things simple. Thanks.