iOS system crash when clicking on NSUserActivity search item

Hello,


I have created an NSUserActivity item using the code below. while the item gets indexed, selecting it causes my iPad mini 2 to crash. I'm not savvy enough to understand crash logs and what not, but I'm not getting any error messages as the crash happens before it returns to the app. iPad resets then Xcode complains the same way it does, when you force close the app while in debugging mode.


A few short comments:

- Indexing using CSSearchableItem does NOT cause any crashes

- In the case above, it was ONLY indexed using NSUserActivity WITHOUT CSSearchableItem

- This is a system crash and not an app crash. It happens as I click on a search ite, doesn't get anywhere close to my app before crash.

- Issue happens on iPad simulator as well.


On simulator I see the following error, between each error there is a time delay:

2015-09-15 19:10:23.071 StockSwipe[88611:3076569] Can't endBackgroundTask: no background task exists with identifier 7, or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.

2015-09-15 19:11:23.198 StockSwipe[88611:3076569] Can't endBackgroundTask: no background task exists with identifier 9, or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.

2015-09-15 19:12:23.379 StockSwipe[88611:3076569] Can't endBackgroundTask: no background task exists with identifier b, or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.

2015-09-15 19:12:59.608 StockSwipe[88611:3079415] Terminating since there is no system app.


System details:

HW: iPad mini 2

OS: iOS 9 GM


Code:

var nsUserActivityArray:[NSUserActivity] = []
@available(iOS 9.0, *)
func createNSUserActivity(title: String, companyName: String, contentDescription: String, image: NSData?, uniqueIdentifier: String, domainIdentifier: String) {

    let attributeSet:CSSearchableItemAttributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeImage as String)
    attributeSet.contentDescription = contentDescription

    let activity = NSUserActivity(activityType: domainIdentifier)
    activity.title = title
    activity.keywords = NSSet(array: [title, companyName, contentDescription, "Stocks", "Markets"]) as! Set<String>
    activity.userInfo = ["symbol": title, "companyName": companyName, "searchDescription": contentDescription]
    activity.contentAttributeSet = attributeSet

    activity.requiredUserInfoKeys = NSSet(array: ["title", "userInfo", "contentAttributeSet", "eligibleForSearch", "eligibleForPublicIndexing"]) as! Set<String>
    activity.eligibleForSearch = true
    activity.eligibleForPublicIndexing = true

    nsUserActivityArray.append(activity)
    activity.becomeCurrent()

    print("NSUserActivity created")
}

Accepted Reply

I don't want to go on a tangent of bug filing best practices, but the forums is not an official place for bugs to be reported. And the best results are gotten when the person with all the details and information about a bug report file it directly. In this particular case we do now understand the issue, and it's unfortunate that we didn't have a bug report about it during our seed period since we likely would have been able to fix it before iOS 9 was finished. This is precisely why we have seeds.


Regardless, the root of the issue is this:

    activity.requiredUserInfoKeys = NSSet(array: ["title", "userInfo", "contentAttributeSet", "eligibleForSearch", "eligibleForPublicIndexing"]) as! Set<String>


You're setting incorrect information in this set. requiredUserInfoKeys should contain a set of keys that exist in your userInfo dictionary. You're including other strings in there which shouldn't be there. If you change this to be:

       activity.requiredUserInfoKeys = NSSet(array: ["symbol", "companyName", "searchDescription"]) as! Set<String>


that will prevent the problem from occurring.

Replies

I have compiled a test project for demonstration purposes:

https://dl.dropboxusercontent.com/u/8061535/Test.zip

When you have a case where you write some code and it causes the phone to reset like this, please, please, PLEASE, file a bug report. No app should cause a reset like this to happen. Having a simple test case like this is incredibly useful as well. Please file the bug report at bugreport.apple.com and post the bug number here when you have it.

First off, this came to my attention a few days ago and the test case is in the above link. Second, I'm having so much trouble getting small pesky issues resolved and deal with a ton of other stuff I need to do before I release my update for iOS 9, I really have, for lack of better wording, no energy to do it. I spent DAYS trying to figure out NSUserActivity and restoring states fiasco.


I'm also surprised with the insistance from you guys to file bug reports. The issue is laid out here, copy it in to a report yourself. Or better yet, create a good relationship with developers so they are not exhausted with small issues and have time to file proper reports.


I'm willing to write a report once I have finished dealing with my own issues. In the meantime, ill exclude supporting search.

I don't want to go on a tangent of bug filing best practices, but the forums is not an official place for bugs to be reported. And the best results are gotten when the person with all the details and information about a bug report file it directly. In this particular case we do now understand the issue, and it's unfortunate that we didn't have a bug report about it during our seed period since we likely would have been able to fix it before iOS 9 was finished. This is precisely why we have seeds.


Regardless, the root of the issue is this:

    activity.requiredUserInfoKeys = NSSet(array: ["title", "userInfo", "contentAttributeSet", "eligibleForSearch", "eligibleForPublicIndexing"]) as! Set<String>


You're setting incorrect information in this set. requiredUserInfoKeys should contain a set of keys that exist in your userInfo dictionary. You're including other strings in there which shouldn't be there. If you change this to be:

       activity.requiredUserInfoKeys = NSSet(array: ["symbol", "companyName", "searchDescription"]) as! Set<String>


that will prevent the problem from occurring.

Just to point out you did go into a tangent. I agree with you that it should have been caught sooner but most people are trying to resolve a thousand pesky things and in my opinion have no time to file reports. it would be great if we could improve engagment and that way developers can resolve their issues quicker and file reports.


Back to the issue at hand, I have implemented your suggestion and it works as expected. Items are indexed and dont crash upon selection. I appreciate the assistance. I have one last issue related to this that I can't resolve. As I just mentioned, the items are indexed if you are done somewhere after a user did something.


But If I try to index them in a for loop during launch, none of them get indexed. Now I know NSUserActivity isnt meant to be mass indexed. In my case they are stock symbols that I query for the user, 50 at a time. So Instead of waiting for him to click them and then index it which works as mentioned above, I want to index them as they come in the query.


I'll only show the first half of the code. There are no errors, just no indexing happening. This is the same function we discussed and the code is in the initial question.


for (index,object) in extraSetOfObjects.enumerate() {
               
                let symbol = object.objectForKey("Symbol") as! String
                let company = object.objectForKey("Company") as! String
                let shortedObject: AnyObject? = object.objectForKey("Shorted")
                let longedObject: AnyObject? = object.objectForKey("Longed")
                /
               
                url = NSURL(string: "https:/
               
                chartRequest = NSURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 10.0)
                let queue = NSOperationQueue.mainQueue()
               
                NSURLConnection.sendAsynchronousRequest(chartRequest, queue: queue, completionHandler: { (response, chartdata, error) -> Void in
                   
                    if error == nil {

                            /
                            if #available(iOS 9.0, *) {
                              
                                createNSUserActivity(symbol, companyName: company, contentDescription: searchDescription, image: nil, uniqueIdentifier: symbol, domainIdentifier: "com.stockswipe.stocksQueried")
                              
                            } else {
                              
                                print("NSUserActivity is only available on iOS 9")
                            }

What happens if you use CoreSpotlight for this instead? In some other threads there had been reports of indexing a lot of NSUserActivities wouldn't let them be searchable but I don't think I ever heard whether that was because they're not being kept alive with a strong reference (which you appear to be doing with your nsUserActivityArray array) or if any bugs had been written about this. When I tried to reproduce it myself things seemed to work for me.


But again, I don't think this NSUserActivity direction is the right way to be going. I would encourage you to use CoreSpotlight directly.


I would also be curious to know if the createNSUserActivity is definitely getting called?

so you can put NSUserActivity in a for loop and get them indexed?


I can use CoreSpotlight and it works but they are not public. what I'm indexing are stock symbols like AAPL so it should be public as it applies to anyone looking for that symbol.


and yea createNSUserActivity() is being called as I'm getting the 50x "NSUserActivity created" as expected.


i even tried to put it on main thread or move it out of the NSURLConnection but still within the for loop and still no results.

I think you're misunderstanding what eligibleForPublicIndexing does. It will not take that information and make it publicly searchable. Publicly searchable content is only available through web markup, which you had previously indicated you weren't planning on doing.


I'd encourage you to read the App Search Programming Guide if you haven't already. In this particular case you'll want to focus in on the Index Activities and Navigation Points section.

as far as I read and what I heard through the presentations, eligibleForPublicIndexing allows it to be publicly indexed if it ranks well. are you sure about your statements?

Yes, completely positive. At WWDC we had talked about doing crowdsourcing of items that have been marked eligibleForPublicIndexing, but that behavior changed after the conference. Marking items that have webpageURLs can influence ranking, but those results are not being crowdsourced. The only information being sent to the public index is that association to the webpageURL, no data from the local device is sent to the public index (contrary to what was presented at WWDC).


We've made numerous updates to the documentation during the iOS 9 beta period. The final documentation for iOS 9 is now available in the developer library, and there are other documents available at developer.apple.com/ios/search.

Ah interesting. Ok Thanks for that info. Lots of people seem to think NSUserActivity is eligibleForPublicIndexing would allow their activity to be potentially pubicly indexed. So we need a webURL for data to be publically indexed? So eligibleForPublicIndexing needs to be tied to a webURL for it to have any effect?

So we need a webURL for data to be publically indexed?


Correct.


So eligibleForPublicIndexing needs to be tied to a webURL for it to have any effect?


Correct.


This would be another good moment for me to say that we also use bug reports for tracking changes in our documentation. For things like this that might be confusing or paint an incomplete picture, it's extremely helpful for us for people to file a bug report. They don't have to be long or detailed and really shouldn't take very long to file. 🙂