【问题标题】:What can make a UIViewController temporarily become unresponsive?什么可以使 UIViewController 暂时变得无响应?
【发布时间】:2015-11-04 21:28:41
【问题描述】:

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

1) Home:在 SKView 上有 SpriteKit 动画。在此 VC 上滑动即可让用户继续到 Authoring VC(下一个)。

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

3) ViewGames:包含一个 UICollectionView 和一个带有关闭按钮的导航栏。这一次呈现一个 UICollectionViewCell,并让用户滑动以继续到下一个单元格。每个单元格在一个 SKView 上都有一个 SpriteKit 动画,并且还有三个按钮。

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

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

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

启动应用 > 滑动进入创作 > 使用菜单进入商店 > 退出商店返回创作 > 退出创作回到主页 > 去 到创作 > 转到查看游戏。

其他方面:

A) 如果我退出 View Games,回到创作,然后重新进入 View Games,这个问题也是一样的。

B) 应用的其他部分没有表现出这种迟缓的 UI 响应。

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

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

E) 如果我启动应用程序,然后转到 Authoring,然后是 ViewGames,则不会出现此问题。


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


迄今为止探索的大道:

(i) 我在 Time Profiling Instrument 中查看了这个应用程序,但看不到任何看起来像是在消耗时间的东西。

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

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

(iv) 在应用程序的开发历史中,当我准备向 Apple 提交 v1.0 时,我遇到了表现出其中一些症状的内存泄漏。然而,据我回忆,内存泄漏只影响了 SpriteKit 动画,影响了所有 SpriteKit 动画(在 Home 和 Authoring VC 上),并且不是暂时的。您必须重新启动应用才能绕过它。

(v) 我已经使用 Instruments/Leaks/Allocations 仔细研究了该应用程序。有一些漏洞,但它们似乎来自 Apple 框架,而不是我的。

(vi) 我在 dealloc/deinit 方法中放置了断点和日志消息,并且所有主要类似乎都在解除分配(例如,VC、集合视图和它的单元格)。


更新 1:2015 年 11 月 4 日;下午 3:47 MST:该问题与 ViewGames SpriteKit 动画无关。我刚刚禁用了 ViewGames UICollectionViewCell 中的动画,问题仍然存在。滑动和按钮按下响应仍然缓慢。当然,单元格仍然有一个 SKView/SKScene。

更新 2:2015 年 11 月 4 日;下午 3:55 MST:我刚刚禁用了商店中的产品获取(使用 SKProductsFetch)。 问题消失了!! 问题明显缩小!

更新 3:2015 年 11 月 4 日;下午 6:10 MST:产品获取到位,但 SKProductsFetch 对象的委托设置为 nil,问题不会发生!还需要注意完成处理程序(称为 fetchProductsCompletion ) 作为我的类构造的一部分也设置为 nil。

更新4:11/4/15;下午 6:10 MST:产品提取到位,SKProductsFetch 有一个非 nil 委托,但 fetchProductsCompletion 设置为 nil,问题不会发生!

【问题讨论】:

  • 您可以在后台或主线程中同步运行不同的任务,而不是异步运行,具体取决于您正在执行的操作。如果任务是同步的或在主线程上运行大型任务,这可能会导致它冻结
  • 这应该出现在 Instruments/Time Profile 中,对吧?在出现此问题时,我在 Instruments 中没有看到类似的内容。我也没有专门启动任何任务/线程。除非 StoreKit 在幕后开始做某事。使用 Instruments 诊断您在说什么的建议?

标签: ios objective-c swift sprite-kit storekit


【解决方案1】:

如果您想完全冻结应用程序,可以使用 sleep(amountTime)。
或者使用 runAfterDelay(amountTime){} 进行延迟。

【讨论】:

  • 感谢您的想法。是的,这些技术可能会导致这些症状。你能看看我提出的问题,看看你是否能在这个问题的背景下提出其他想法?
  • 可能会在你的代码中加入一些猜测数学问题
  • 什么是“猜数学题”?
  • 一个占用大量cpu的计算。不知道实际做。
【解决方案2】:

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

这是我在商店中用来获取产品的方法:

// 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 时,问题就消失了。关于为什么的任何想法?

【讨论】:

    猜你喜欢
    • 2011-03-04
    • 1970-01-01
    • 2021-12-24
    • 2012-12-06
    • 2019-12-03
    • 2021-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多