是什么让UIViewController暂时变得无法响应?

时间:2023-01-12 21:43:28

My app has four View Controllers (VC's):

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

1) Home: Has a SpriteKit animation on an SKView. A swipe on this VC lets the user proceed to the Authoring VC (next).

1)主页:在SKView上有一个SpriteKit动画。在此VC上滑动可让用户进入创作VC(下一个)。

2) Authoring: Has a menu (a UITableView). The menu lets the user access ViewGames and Store (below).

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

3) ViewGames: Contains a UICollectionView, and a nav bar with a close button. This presents a single UICollectionViewCell at a time, and lets the user swipe to proceed to the next cell. Each cell has a SpriteKit animation on a SKView, and also has three buttons.

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

4) Store: Has an in-app purchase store, with UI implemented as a UITableView. For the purpose of this discussion, the only feature I'm using is SKProductsRequest to fetch back a list of products which are displayed in the table view.

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

Problem: In the ViewGames VC, under some circumstances, the UI of the second and following cells in the collection view operate very slowly. E.g., about a factor of 10 slower than normal. The animation in the SKView is very slow. And the four buttons (three on the collection view cell, one in the nav bar) operate very slowly. Often they won't respond at all and you have to tap them several times. The swipe to go to the next cell responds similarly-- and slowly, if at all. (If I go back to the first cell using swiping, the first cell is also similarly responsive, but initially it doesn't exhibit this problem).

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

Reproducing the problem. The good news is, that in my app, reproducing this problem is very consistent. Here's what will produce it:

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

Launch app > Swipe to go to Authoring > Use menu to go to the Store > exit Store to return to Authoring > exit Authoring back to Home > go to Authoring > go to View Games.

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

Some other facets:

其他一些方面:

A) If I exit View Games, back to Authoring, and then re-enter View Games, this problem is the same.

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

B) No other parts of the app exhibit this sluggishness of UI response.

B)应用程序的其他任何部分都没有表现出UI响应的缓慢。

C) If, after getting this behavior in the View Games VC, I now exit ViewGames back to Authoring, re-enter the Store, go back to Authoring, and go back to ViewGames, the problem goes away.

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

D) This problem is only exhibited on iOS9, iOS9.1, iOS9.2 (beta). It doesn't occur on iOS8.4. (All running on physical devices; I've yet to try the simulator). I was using Xcode 7.0.1 initially, but am now using Xcode 7.2 beta and the problem remains the same. My app is targeted at iOS8 and above.

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

E) If I launch the app, and go to Authoring, then ViewGames, this problem doesn't occur.

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


Question: What can make part of the UI run sluggishly, but only temporarily?

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


Avenues explored so far:

到目前为止探索的途径:

(i) I've looked at this app in the Time Profiling Instrument, but can't see anything that looks like its soaking up time.

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

(ii) Only one part of the app is doing network interaction, and that's the Store. And the product fetch succeeds, and displays that info.

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

(iii) My best guess right now is that this is related to memory usage. When the symptoms appear, it appear that there is at least a somewhat greater amount of RAM used in going from cell 1 of the Authoring UICollectionView to cell 2 (0.4 to 0.9MB in cases where the problem appears; 0.3MB in cases where the problem does not appear).

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

(iv) In the development history of the app, as I was getting ready to submit v1.0 to Apple, I had a memory leak that exhibited some of these symptoms. However, to my recollection, that memory leak only affected the SpriteKit animations, affected all SpriteKit animations (both on the Home and Authoring VC's), and was not temporary. You had to restart the app to get around it.

(iv)在应用程序的开发历史中,当我准备将v1.0提交给Apple时,我有一个内存泄漏,表现出一些这些症状。但是,据我记忆,内存泄漏只会影响SpriteKit动画,影响所有SpriteKit动画(包括Home和Authoring VC),并不是暂时的。您必须重新启动应用才能绕过它。

(v) I've looked at the app quite a bit using Instruments/Leaks/Allocations. There are some leaks, but they appear to be from Apple frameworks, not mine.

(v)我使用Instruments / Leaks / Allocations看了很多应用程序。有一些泄漏,但它们似乎来自Apple框架,而不是我的。

(vi) I've put breakpoints and log messages in the dealloc/deinit methods and all of the primary classes seem to be deallocating (e.g., the VC's, and the collection view and it's cells).

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


Update1: 11/4/15; 3:47pm MST: The problem is not related specifically to the ViewGames SpriteKit animation. I just disabled the animation in the ViewGames UICollectionViewCell's and the problem still occurs. The sluggishness still happens for swipe and button press responses. Of course, the cells still have an SKView/SKScene.

Update1:​​11/4/15; 3:47 pm MST:问题与ViewGames SpriteKit动画无关。我刚刚在ViewGames UICollectionViewCell中禁用了动画,问题仍然存在。滑动和按钮按下响应仍然会出现迟缓。当然,细胞仍然有SKView / SKScene。

Update2: 11/4/15; 3:55pm MST: I just disabled the product fetch (which uses SKProductsFetch) out of the Store. AND the problem goes away!! Significant narrowing down of the issue!

Update2:11/4/15; 3:55 pm MST:我刚刚在商店中禁用了产品获取(使用SKProductsFetch)。问题消失了!!显着缩小了问题!

Update3: 11/4/15; 6:10pm MST: With the product fetch in place, but with the delegate of the SKProductsFetch object set to nil, the problem does not occur! It is also relevant to note that a completion handler (called fetchProductsCompletion) that was part of my class construction was also set to nil.

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

Update4: 11/4/15; 6:10pm MST: With the product fetch in place, and with a non-nil delegate for SKProductsFetch, but with fetchProductsCompletion set to nil, the problem does not occur!

Update4:11/4/15; MST:在产品获取到位的情况下,以及SKProductsFetch的非零委托,但fetchProductsCompletion设置为nil,问题不会发生!

2 个解决方案

#1


0  

You could use sleep(amountTime) if you want to freeze the app totatly.
Or use runAfterDelay(amountTime){} to make a delay.

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

#2


0  

I've found a work-around for the problem. I don't know why it works. But, I'll post some of the code to give a context.

我找到了解决这个问题的方法。我不知道为什么会这样。但是,我会发布一些代码来给出一个上下文。

This is the method I am using in the Store to fetch products:

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

// 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
    }
}

The store delegate method (my construction) looks like this:

商店委托方法(我的构造)如下所示:

// 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()
}

And here is the delegate method for the SKProductsRequest:

这是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))
    }
}

The code as given above does exhibit the problem. When I set the fetchProductsCompletion handler to nil after it is used, the problem goes away. Any ideas as to why?

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

#1


0  

You could use sleep(amountTime) if you want to freeze the app totatly.
Or use runAfterDelay(amountTime){} to make a delay.

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

#2


0  

I've found a work-around for the problem. I don't know why it works. But, I'll post some of the code to give a context.

我找到了解决这个问题的方法。我不知道为什么会这样。但是,我会发布一些代码来给出一个上下文。

This is the method I am using in the Store to fetch products:

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

// 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
    }
}

The store delegate method (my construction) looks like this:

商店委托方法(我的构造)如下所示:

// 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()
}

And here is the delegate method for the SKProductsRequest:

这是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))
    }
}

The code as given above does exhibit the problem. When I set the fetchProductsCompletion handler to nil after it is used, the problem goes away. Any ideas as to why?

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