2

Our app works every time without a hitch on all iOS devices we've tested with (fresh install or update from xcode/adhoc production/debug, we've tried them all). But it is getting rejected in app review because it appears the ondemand resource never becomes available, even though the download of the resource completes without error.

We are accessing the resource in a blocking region of camera callback. If the resource is available we go ahead and use it, otherwise we do a beginAccessingResourcesWithCompletionHandler() and free callback block only after download is complete. The problem is app reviewer is saying it downloads (there's a progressbar for it) and then keeps asking to redownload over and over. Why would it not be available if it just successfully completed download (note there's no error)?

[request conditionallyBeginAccessingResourcesWithCompletionHandler:^(BOOL resourcesAvailable) {
if (resourcesAvailable)
{
    /* use the resource. */
    /* unblock the callback. done. */
}
else
{
    /* ask to download resource */
    [request beginAccessingResourcesWithCompletionHandler:^(NSError * _Nullable error) {
        if (error)
        {
            NSLog(@"%@", error);
            /* don't unblocked. return. will hang. */ 
        }
        /* unblock the callback. done. resource should be available next camera frame. */
    }];
} }];

Also it's not an out of storage issue. We have that covered and tested. Moreover the beginAccessingResourcesWithCompletionHandler() returns without error.

Hashman
  • 367
  • 1
  • 10
  • This is a trimmed down version of real code. It following all the samples on the web. You do a conditionallybegin, and if it fails you then do a beginAccess to download. And are you saying beginAccessingResourcesWithCompletionHandler() needs to be called from main thread only? Why? I haven't seen that mentioned anywhere. Also the completion handler returns without error. Shouldn't it return an error if something went wrong with the download due to not being in main thread? – Hashman Oct 12 '19 at 23:56
  • I do see in list 4-3 here they do beginAccess on main thread. https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/Managing.html – Hashman Oct 13 '19 at 00:12
  • I’m sending a revision to the store that puts the beginAccessing inside a main thread block. Not being able to reproduce it on a test device makes it difficult. Will update if that fixes it. – Hashman Oct 13 '19 at 16:31
  • Oh it’s just a very large code base. I can’t dump the whole thing, it would just be unreadable. The OnDemand resource is actually the weights for a neural network built in metal shaders that are being compiled when the camera buffer callback starts. Until access to the OnDemand resources and compiling the network isn’t complete, the camera buffer callbacks need to be skipped. This is done by simply exiting the camera callback if the network is not finished compiling yet (the first step of which is downloading said OnDemand resources). That’s all I mean by blocking the callback. – Hashman Oct 13 '19 at 18:11
  • Putting beginAccess inside main thread call didn't fix the problem. Still rejected in review because on their device it is somehow still not seeing resourcesAvailable, even after first beginAccess completes without error (it downloaded the resources successfully). – Hashman Oct 14 '19 at 17:49

2 Answers2

0

I'm going to guess that the problem is a failure to heed the warning in big letters in the documentation:

An NSBundleResourceRequest object can only be used for one successful resource request.

You cannot reuse a bundle resource request object. It has only two jobs: to download the resources (once), and to hold them in place (by persisting). Once you have succeeded in downloading the resources once, there are only two things you can do with this bundle resource request object (and you can do both of them):

  • Call endAccessingResources.

  • Throw the object away (or let it go out of scope or die with the termination of the app).

If you want to find out again, in the same run of the app, whether you still have these resources, you need to begin all over again with a new bundle resource request.

However, I would go further. As I've said in the comments, you seem to be misusing conditionallyBegin. There is no need to ask twice during the same run of the app whether you already have these resources, and there is no need to call conditionallyBegin to find that out, because when you say beginAccessing the right thing will happen: either you will start using the resources (if they are present) or you will download them and start using them (if they were not present). The only reason for ever using conditionallyBegin is if your logic would be different from that if it turned out that some resources were not present.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I’m redefining the NSBundleResourceRequest each iteration. I was reusing it though between the conditionallyBegin and beginAccess calls (I understand what you’re saying about beginAccess appearing to only be necessary, but it’s what Apple specifically advises). I changed it to define a new resourceRequest for the beginAccess call. Submitted to store and just got the reject, still failing on their test device, finishes successfully downloading, but the next access still sees it as unavailable. – Hashman Oct 15 '19 at 18:51
  • I’m out of ideas, what a finicky api. The Android version Of this was done months ago. sticking with TestFlight only for now as we haven’t seen this on any device but whatever the reviewer is using. – Hashman Oct 15 '19 at 18:52
0

After 9 submissions, this issue resolved for us. But unfortunately its not clear which change was essential to it getting resolved. Moreover it was near the 13.2.3 update too. Overall I'd make sure of these (note there are a lot of examples with up-votes on SO that do not follow these): 1) All beginAccess call are done on main thread. 2) Don't reuse the resourceRequest between conditionallyBeginAcces and beginAccess. (as pointed out by Matt). Redefine them from tag name each time. 3) Make sure resourceRequest are defined such that they do not go out of scope until the whole process is done. Its possible that reviewer devices have a background daemon that instantly deletes resources that are released (due to going out of scope). 4) if the odr is accessed inside a dispatch_sync() block, make sure that there's no path for concurrent beginAccess to be fired.

Hashman
  • 367
  • 1
  • 10