Today Widget Extension 中 "Unable to load" 太频繁了

Too often "Unable to load" in Today Widget Extension

我正在为我的应用制作一个“今日”小部件。我的小部件包含一个带有 10 个单元格的 UITableView。 (每个单元格的高度为50pt。) 功能简单。如果我触摸单元格上的按钮,从 sqlite 重新加载数据库并将它们显示在单元格上。它在模拟器和 iPhone 4s、5、5s、6 上运行良好,但只有 iPhone6+ 除外。我确实删除了小部件并再次添加了 10 多次,但这对我没有帮助。我确实检查了内存和僵尸。但这稳定在 ~10M 以下并且没有泄漏。我该如何解决我的问题?

iPhone 6+ 非常大的屏幕,所以widget的宽度也很大。 而且widget的最大高度比iPhone5.

大很多

因此,

iPhone6+ 上的小部件由于其分辨率,使用的视频内存比 iPhone5 多 3~4 倍。加上一些动画,会增加很多。

但是,iOS 允许小部件占用少量内存,以便在显示小部件时保持应用程序的活动状态。 (未记录实际限制)。

在我的实验中,使用实现 drawRect 的自定义视图 100% 会杀死 iPhone6+ 上的小部件,即使它什么都不做。 (空实现)

我认为这是错误。我的另一个推论是整个 rect 将被缓存以用于核心动画,它会消耗内存。然后 iOS 杀死它。

真正的问题是 iPhone6+ 与 iPhone5 具有相同数量的 RAM。

很简单:

  • iPhone6+ 上的小部件需要更多内存。
  • 但是,小部件内存限制与 iPhone5 相同,实际上,它更糟糕,因为在后台运行的应用程序需要比 iPhone5 更多的内存。和其他小部件。

我可以肯定这是苹果的硬件设计失误

最可悲的是,多年后会有iPhone6+用户。 由于 iPhone 6+ 用户,开发人员无法提供丰富的小部件。

好的,伙计们,你们不喜欢我之前的回答。我会重试。

考虑以下条件:

+-------------------+
| Table View        |
|+-----------------+|
|| Cell            ||
||+---------------+||
||| UILabel       |||
||+---------------+||
|+-----------------+|
+-------------------+

起初table的逻辑宽度是:

  • 320pt iPhone 5(纵向)
  • 414pt 在 iPhone 6+

内容比例因子为:

  • 0.5 对于 iPhone 5
  • 0.33 对于 iPhone 6+

1pt x 1pt 的实际像素为:

  • 对于 iPhone 5,(1/0.5)^2 = 4
  • 对于 iPhone 6+ (1/0.33)^2 = 9

那么,如果 UILabel 适合整个设备宽度并且它的高度为 44(Apple 的 HIG 的最小可点击高度),那么实际像素为:

  • 320 * 44 * 4 = 56,320 设备像素 iPhone 5
  • 414 * 44 * 9 = iPhone 6+
  • 的 163,944 个设备像素

因此,iPhone6+ 需要比 iPhone5 大 3 倍的缓冲区来绘制 UILabel。

但是,当widget的内存使用量超过10MB(通过无数次实验估算)时,就会出现内存错误。同样的限制适用于这两种设备。 Apple 未记录此限制。

请记住,在开发 iOS8 扩展程序时,您只能使用设备内存的 1%,以防止杀死后台应用程序。这也是目前支持图片编辑扩展的应用很少的主要原因。

无论如何,因此,需要 UI 的扩展很容易在 iPhone 6+ 上崩溃,因为每个 UI 元素所需的内存量取决于大小和内容规模。

自定义绘图会导致同样的问题,因为它需要缓冲区来绘制以优化动画和渲染。缓冲区的分辨率和大小在 iPhone6+.

上要大得多

此外,通知中心本身也存在错误(泄漏),即使只是 Hello World 小部件(Xcode 模板附带)也会在每次显示和隐藏时不断增加内存消耗。当最终达到 10MB 时,它会崩溃并重新加载。这就是小部件有时会闪烁的原因。如果某个小部件连续崩溃 3 次怎么办,iOS 永久禁用该小部件并显示 "Unable to Load".

那么,在这样恶劣的条件下,我们能做些什么呢,我为这个问题制定了一些规则。

  1. 不要使用实现 [drawRect:] 的自定义视图,除非它非常小。
  2. 使视图(具有自己的内容,尤其是标签)尽可能小。
  3. 不要使用背景图片。

点击空白区域到 select 单元格

这是与内存错误相关的 Widget 非常常见的问题,所以我也在写这个:

小部件层次结构中的所有视图往往具有透明背景(UIClearColor),这意味着要使单元格可点击非常困难。因为当用户触摸空白区域时,不会对小部件进行完整的命中测试。 (自定义命中测试只有在至少没有一个透明的superview时才能应用)有一些解决方案:

  1. 使 UI 标签的宽度适合单元格:不要这样做,它会在 iPhone6+ 或更宽的高分辨率设备上消耗更多内存。
  2. 在自定义单元格上实现空 [drawRect:]。不要,这是用于点击透明控件的非常简单的解决方案,但是它需要绘图缓冲区。请记住,每个设备的缓冲区大小都会不同。

我可以在不消耗额外内存的情况下使它工作的唯一解决方案是将小部件背景颜色设置为具有 0.01 alpha 的黑色。 (它使命中测试有效)

+-------------------+
| Table View        |- backgorund-color: (0, 0, 0, 0.01)
|+-----------------+|
|| Cell            ||
||+---------+      ||
||| UILabel +------++- make it small as possible as you can
||+---------+      ||
|+-----------------+|
+-------------------+

记住只有背景颜色的容器视图不会占用缓冲内存。

我喜欢,但很多时候扩展会崩溃(无法加载)只是因为self.preferredContentSize 设置不正确。

我通过在 TodayViewController class:

中放置一个变量 "currentHeight" 解决了这个问题
var currentHeight : CGFloat = 0.0
@IBOutlet var containerView: UIView! (the storyboard today extension viewcontroller view)

override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        ... (load custom xib inside my containerView)
        self.currentHeight = containerView.frame.size.height
        self.preferredContentSize = CGSizeMake(self.view.frame.size.width,currentHeight)
    }

func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)) {
        // Perform any setup necessary in order to update the view.

        // If an error is encountered, use NCUpdateResult.Failed
        // If there's no update required, use NCUpdateResult.NoData
        // If there's an update, use NCUpdateResult.NewData
        ... (do whatever you want in order to update)
        self.preferredContentSize = CGSizeMake(self.view.frame.size.width,currentHeight)
        completionHandler(NCUpdateResult.NewData)
        ...
    }

编辑扩展方案, 选择 运行 动作, 在信息部分, 将可执行文件设置为 'Ask on Launch'

这似乎解决了问题,扩展每次都运行,不知道它是如何工作的,但这让我的工作顺利进行。