Ios 什么会使UIViewController暂时失去响应?

Ios 什么会使UIViewController暂时失去响应?,ios,objective-c,swift,sprite-kit,storekit,Ios,Objective C,Swift,Sprite Kit,Storekit,我的应用程序有四个视图控制器(VC): 1) 主页:在SKView上有SpriteKit动画。在这个VC上轻扫,用户就可以进入创作VC(下一步) 2) 创作:有一个菜单(UITableView)。该菜单允许用户访问ViewGames和Store(如下) 3) ViewGames:包含UICollectionView和带有关闭按钮的导航栏。这将一次显示一个UICollectionViewCell,并允许用户滑动到下一个单元格。每个单元格在SKView上都有一个SpriteKit动画,还有三个按钮

我的应用程序有四个视图控制器(VC):

1) 主页:在SKView上有SpriteKit动画。在这个VC上轻扫,用户就可以进入创作VC(下一步)

2) 创作:有一个菜单(UITableView)。该菜单允许用户访问ViewGames和Store(如下)

3) ViewGames:包含UICollectionView和带有关闭按钮的导航栏。这将一次显示一个UICollectionViewCell,并允许用户滑动到下一个单元格。每个单元格在SKView上都有一个SpriteKit动画,还有三个按钮

4) 商店:有一个应用内购买商店,用户界面实现为UITableView。在本次讨论中,我使用的唯一功能是SKProductsRequest,以获取表视图中显示的产品列表

问题:在ViewGames VC中,在某些情况下,集合视图中第二个单元格和后续单元格的UI运行非常缓慢。例如,大约比正常速度慢10倍。SKView中的动画速度非常慢。四个按钮(三个在采集视图单元上,一个在导航栏上)运行非常缓慢。通常情况下,它们根本不会响应,您必须多次点击它们。刷到下一个单元格的响应类似——如果有的话,反应也很慢。(如果我使用刷卡返回第一个单元格,第一个单元格也会有类似的响应,但最初它不会出现此问题)

重现问题。好消息是,在我的应用程序中,重现这个问题是非常一致的。以下是产生它的原因:

启动应用程序>滑动进入创作>使用菜单进入商店> 退出存储以返回创作>退出创作返回主页>转到 到创作>转到查看游戏

其他方面:

A) 如果我退出查看游戏,返回创作,然后重新进入查看游戏,这个问题也是一样的

B) 应用程序的其他部分没有表现出UI响应的这种迟钝

C) 如果在View Games VC中获得此行为后,我现在退出ViewGames返回创作,重新进入商店,返回创作,然后返回ViewGames,问题就会消失

D) 此问题仅在iOS9、iOS9.1和iOS9.2(测试版)上出现。它不会出现在iOS8.4上。(都在物理设备上运行;我还没有尝试模拟器)。我最初使用的是Xcode 7.0.1,但现在使用的是Xcode 7.2测试版,问题依然存在。我的应用程序针对iOS8及以上版本

E) 如果我启动应用程序并转到创作,然后查看游戏,则不会出现此问题


问题:什么会使部分UI运行缓慢,但只是暂时的


迄今为止探索的途径:

(i) 我已经在时间分析工具中查看了这个应用程序,但是没有看到任何看起来像它的吸收时间的东西

(ii)应用程序只有一个部分在进行网络交互,那就是商店。产品获取成功,并显示该信息

(iii)我现在最好的猜测是这与内存使用有关。当症状出现时,从Authoring UICollectionView的单元1到单元2使用的RAM量似乎至少稍大一些(出现问题的情况下为0.4到0.9MB;未出现问题的情况下为0.3MB)

(iv)在该应用程序的开发历史中,当我准备向苹果提交v1.0时,我出现了一次内存泄漏,表现出了其中一些症状。然而,在我的记忆中,内存泄漏只影响了SpriteKit动画,影响了所有SpriteKit动画(在主页和创作VC上),并且不是暂时的。你必须重新启动应用程序才能绕过它

(v) 我已经使用仪器/漏洞/分配对应用程序进行了相当多的研究。有一些漏洞,但它们似乎来自苹果的框架,而不是我的

(vi)我在dealloc/deinit方法中放置了断点和日志消息,所有的主要类似乎都在释放(例如VC、集合视图及其单元格)


更新1:11/4/15;下午3:47 MST:该问题与ViewGames SpriteKit动画无关。我刚刚在ViewGames UICollectionViewCell中禁用了动画,问题仍然存在。刷卡和按下按钮的反应仍然迟钝。当然,这些单元仍然有一个SKView/SKScene

更新2:11/4/15;下午3:55 MST:我刚刚禁用了商店外的产品提取(使用SKProductsFetch)问题就会消失显著缩小问题范围

更新3:11/4/15;MST:6:10pm:产品提取到位,但SKProductsFetch对象的委托设置为nil,问题不会发生需要注意的是,作为类构造一部分的完成处理程序(称为fetchProductsCompletion)也被设置为nil

更新4:11/4/15;MST:6:10pm:在产品获取到位的情况下,如果SKProductsFetch具有非nil委托,但将fetchProductsCompletion设置为nil,则不会出现问题

如果您想完全冻结应用程序,可以使用sleep(amountTime)。

或者使用runAfterDelay(amountTime){}来进行延迟。

我找到了解决该问题的方法。我不知道它为什么有效。但是,我将发布一些代码以提供上下文

这是我在商店取货时使用的方法:

// Not calling this from init, so that we can make sure there is a network connection, and only give a single error/alert if there is no network connection.
private func initiateProductFetch(done:(success:Bool)->()) {
    let productIds = self.productsInfo!.productIds()
    if nil == productIds {
        Assert.badMojo(alwaysPrintThisString: "No product Ids!")
    }

    /* Bug #C34, #C39.
    10/30/15; I was having a memory retention issue in regards to the fetchProducts completion handler. The SMIAPStore instance wasn't getting deallocated until the *next* time the store was presented because the store delegate was retaining a reference to the completion handler, which had a strong reference to self (SMIAPStore).
    At first I tried to resolve this by having [unowned self] in the following, but that fails with an exception. Not sure why. And having [weak self] also causes self! to fail-- "fatal error: unexpectedly found nil while unwrapping an Optional value". Why?
    The only way I've found to work around this is to have a selfCopy as below, which is nil'ed out in the completion handler. Seems really clumsy. For consistency sake, and safety sake, I'm also using this technique in the other self.storeDelegate usages in this class.
    */

    var selfCopy:SMIAPStore? = self

    self.storeDelegate?.fetchProducts(productIds!) {
        (products:[SKProduct]?, error:NSError?) in

        Log.msg("\(products)")

        if (products != nil && products!.count > 0 && nil == error) {
            // No error.
            selfCopy!.productsInfo!.products = products
            Log.msg("Done fetching products")
            done(success:true)
        }
        else {
            // Show an error. The VC will not be displayed by now. Returning the error with the done call back will not allow it to be displayed.

            // It seems possible the products are empty and error is nil (at least this occurs in my debug case above).
            var message = ""
            if error != nil {
                message = error!.localizedDescription
            }

            let alert = UIAlertView(title: "Couldn't get product information from Apple!", message: message, delegate: nil, cancelButtonTitle: SMUIMessages.session().OkMsg())
            selfCopy!.userMessageDetails = UserMessage.session().showAlert(alert, ofType: UserMessageType.Error) { buttonNumber in
                done(success:false)
            }
        }

        selfCopy = nil
    }
}
存储委托方法(我的构造)如下所示:

// Call this first to initiate the fetch of the SKProduct info from Apple
public func fetchProducts(productIdentifiers:[String], done: (products:[SKProduct]?, error:NSError?) ->()) {
    self.requestDelegateUseType = .ProductsRequest
    self.productsRequest = SKProductsRequest(productIdentifiers: Set(productIdentifiers))
    self.productsRequest!.delegate = self
    self.fetchProductsCompletion = done
    self.productsRequest!.start()
}
下面是SKProductsRequest的委托方法:

// Seems like this delegate method gets called *before* the requestDidFinish method. Don't really want to rely on that behavior though.
public func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
    Assert.If(.ProductsRequest != self.requestDelegateUseType, thenPrintThisString: "Didn't have a product request")

    if 0 == response.invalidProductIdentifiers.count {

        /*
        #if DEBUG
            // Debugging-- simulate an error fetching products.
            self.fetchProductsCompletion!(products: nil, error: nil)
            return
        #endif
        */

        self.fetchProductsCompletion?(products: response.products, error: nil)
        //self.fetchProductsCompletion = nil
    }
    else {
        let message = "Some products were invalid: \(response.invalidProductIdentifiers)"
        self.fetchProductsCompletion!(products: nil, error: NSError.create(message))
    }
}

上面给出的代码确实存在问题。使用fetchProductsCompletion处理程序后,当我将其设置为nil时,问题就消失了。有什么想法吗?

您可以让不同的任务同步运行