我的应用程序有四个视图控制器(VC):
1)主页:在SpriteKit上有一个SKView动画。在这个VC上滑动一下,用户可以继续编写VC (next)。
2)创作:有一个菜单(一个UITableView)。菜单允许用户访问ViewGames和Store (如下所示)。
3) ViewGames:包含一个UICollectionView,还有一个带关闭按钮的导航条。这每次只显示一个UICollectionViewCell,并允许用户滑动到下一个单元格。每个单元格在一个SpriteKit上都有一个SKView动画,还有三个按钮。
4) Store:有一个应用程序内购买商店,UI实现为UITableView。为了本讨论的目的,我使用的唯一特性是SKProductsRequest来获取表视图中显示的产品列表。
问题:在ViewGames VC中,在某些情况下,收集视图中的第二个和后续单元格的UI操作非常慢。例如,大约比正常慢10倍。SKView中的动画非常慢。这四个按钮(三个在集合视图单元格上,一个在导航条中)操作非常慢。通常情况下,他们根本不会回应,而且你必须敲几次。滑动到下一个细胞的反应也是相似的--如果有反应的话,速度也很慢。(如果我使用滑动回到第一个单元格,第一个单元也具有类似的响应性,但最初它没有显示出这个问题)。
复制问题。好消息是,在我的应用程序中,复制这个问题是非常一致的。下面是将产生它的内容:
启动app >点击进入创作>使用菜单转到商店>退出商店返回到创作>退出创作回到家>去创作>去创作>去查看游戏。
一些其他方面:
( A)如果我退出View,返回到创作,然后重新进入View,这个问题是相同的。
( B)应用程序的其他部分都没有显示UI响应迟缓。
C)如果在视图游戏VC中获得这种行为之后,我现在退出ViewGames回到创作,重新进入商店,回到创作,然后返回到ViewGames,那么问题就解决了。
这个问题只出现在iOS9,is 9.1,iOS9.2 (beta)上。它不会发生在iOS8.4上。(全部运行在物理设备上,我还没有试过模拟器)。我最初使用的是Xcode 7.0.1,但现在使用的是Xcode 7.2测试版,问题仍然是一样的。我的应用程序是针对iOS8和更高版本的。
( E)如果我启动这个应用程序,然后转到创作,那么ViewGames,这个问题就不会发生。
问题:是什么使UI的一部分缓慢运行,但只是暂时的?
迄今探索的途径:
(i)我在时间剖面仪上看过这个应用程序,但没有看到任何看上去像它吸收时间的东西。
(ii)应用程序中只有一部分在进行网络交互,那就是商店。产品获取成功,并显示该信息。
(三)我目前的猜测是,这与内存使用有关。当出现症状时,似乎至少有更多的RAM用于从编写UICollectionView的单元单元到单元格2(在出现问题的情况下使用0.4到0.9MB;在不出现问题的情况下使用0.3MB )。
(iv)在应用程序的开发历史上,当我准备向苹果提交1.0版时,我的内存泄漏显示了其中的一些症状。然而,据我所知,内存泄漏只影响SpriteKit动画,影响所有SpriteKit动画(包括家庭动画和创作VC动画),而不是临时的。你必须重新启动应用程序才能绕过它。
(v)我使用了很多工具/泄漏/配置来查看这个应用程序。有一些漏洞,但似乎是来自苹果的框架,而不是我的。
(vi)我在dealloc/deinit方法中放置了断点和日志消息,所有主要类似乎都在释放(例如VC、集合视图及其单元格)。
Update1:11/4/15;3:47 to :问题与ViewGames SpriteKit动画无关。我刚刚禁用了ViewGames UICollectionViewCell中的动画,问题仍然发生。滑动和按下按钮的反应仍然会出现迟缓。当然,细胞仍然有SKView/SKScene。
Update2:11/4/15;3:55pm MST:我刚刚禁用了从商店中提取(使用SKProductsFetch)的产品。和问题消失了!!大大缩小了问题的范围!
Update3:11/4/15;6:10 is :当产品fetch就位,但是将SKProductsFetch对象的委托设置为零时,就不会出现问题!还需要注意,作为类构造的一部分的完成处理程序(称为fetchProductsCompletion)也被设置为零。
Update4:11/4/15;6:10 11 MST:具有产品获取位置,并具有SKProductsFetch的非零委托,但是当fetchProductsCompletion设置为零时,不会出现问题!
发布于 2015-11-05 01:24:53
我找到了解决这个问题的办法。我不知道为什么会起作用。但是,我会发布一些代码来给出上下文。
这是我在商店中用来获取产品的方法:
// 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处理程序后将其设置为零时,问题就消失了。知道为什么吗?
发布于 2015-11-04 21:52:26
如果你想完全冻结应用程序,你可以使用睡眠(AmountTime)。
或者使用runAfterDelay(amountTime){}来延迟。
https://stackoverflow.com/questions/33532360
复制相似问题