iOS WatchKit / Swift - 在 WKInterfaceTable 行中延迟加载图像
iOS WatchKit / Swift - Lazy Loading images in WKInterfaceTable rows
在我的 Apple Watch 应用程序中,我有一个 table 大约有 25 行,每行都有一些文本和需要从互联网加载的图像。类似于 Instagram 风格的提要,但这些是每张约 8k 的个人资料图片。
当我根据传入的 JSON 数据构建 table 时,我所做的一切都是正确的,利用 WatchKit 的内置图像缓存来减少不必要的网络流量。
应用程序 "works" 和图像显示正确。但问题是视图需要 10-20 秒才能准备好与用户进行交互。在这 10-20 秒结束之前,滚动变慢,并且在所有图像加载完成之前按钮不会执行任何操作。
我想要的是在用户向下滚动时延迟加载图像的方法。
我已经尝试过实施分页解决方案,但这也有其缺点,并不能完全解决我原来的问题。
如果我只填充文本位(没有图像),table 会立即显示并准备好进行交互。
***更新:
Mike Swanson 在下面的回答非常彻底,确实解决了我的大部分问题。呆滞和阻塞已经消失。
我想 post 最终代码,供遇到类似问题的任何其他人使用。具体来说,我想展示我是如何实现后台调度的。
我现在也在主线程中临时将图像设置为占位符以显示 activity。一旦实际图像加载并传输到手表,它就会被替换。我还改进了图像缓存键,以便在上传新图像时丢失。
新代码:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
i = 0
for p in self.profiles {
if let profileId = p["id"] as? NSNumber {
var profileIdStr = "\(profileId)"
let row = self.profilesTable.rowControllerAtIndex(i) as! WatchProfileRow
if let hasImage = p["has_image"] as? NSNumber {
let profileImageCacheKey = "\(profileIdStr)-\(hasImage)"
// if the image is already in cache, show it
if self.device.cachedImages[profileImageCacheKey] != nil {
row.profileImageThumbnail.setImageNamed(profileImageCacheKey)
// otherwise, get the image from the cdn and cache it
} else {
if let imageHost = self.imageHost as String! {
var imageURL = NSURL(string: NSString(format: "%@%@-thumbnail?version=%@", imageHost, profileId, hasImage) as String)
var imageRequest = NSURLRequest(URL: imageURL!)
NSURLConnection.sendAsynchronousRequest(imageRequest, queue: NSOperationQueue.mainQueue()) {
(response, data, error) -> Void in
if error == nil {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
dispatch_async(backgroundQueue, {
self.device.addCachedImageWithData(data, name: profileImageCacheKey)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
row.profileImageThumbnail.setImageNamed(profileImageCacheKey)
})
})
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
i++
}
原始代码:
i = 0
for p in self.profiles {
if let profileId = p["id"] as? NSNumber {
var profileIdStr = "\(profileId)"
let row = self.profilesTable.rowControllerAtIndex(i) as! WatchProfileRow
if let hasImage = p["has_image"] as? NSNumber {
// if the image is already in cache, show it
if self.device.cachedImages[profileIdStr] != nil {
row.profileImageThumbnail.setImageNamed(profileIdStr)
// otherwise, get the image from the cdn and cache it
} else {
if let imageHost = self.imageHost as String! {
var imageURL = NSURL(string: NSString(format: "%@%@-thumbnail?version=%@", imageHost, profileId, hasImage) as String)
var imageRequest = NSURLRequest(URL: imageURL!)
NSURLConnection.sendAsynchronousRequest(imageRequest, queue: NSOperationQueue.mainQueue()) {
(response, data, error) -> Void in
if error == nil {
var image = UIImage(data: data)
row.profileImageThumbnail.setImage(image)
self.device.addCachedImage(image!, name: profileIdStr)
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
i++
}
首先,WatchKit 中没有方法来确定WKInterfaceTable
中的当前位置。这意味着您使用的任何逻辑都必须解决这个问题。
其次,看起来您正在发送一个异步网络请求,然后在您有数据时使用 setImage
。 setImage
在将图像传输到设备之前将图像编码为 PNG,在您现有的代码中,这是在主线程上发生的。如果您不需要 PNG 图片,我会考虑使用 setImageData
发送 JPEG 编码数据,以进一步减少需要传输到 Watch 的字节数。
最后,您似乎在重复传输图像。一次 setImage
,两次 addCachedImage
。收到图像后,我建议分派到后台线程,然后使用 WKInterfaceDevice
的 addCachedImageWithData
(可以安全地在后台线程上使用)发送数据。然后,在使用 setImageNamed
.
设置现在缓存的图像之前分派回主线程
在我的 Apple Watch 应用程序中,我有一个 table 大约有 25 行,每行都有一些文本和需要从互联网加载的图像。类似于 Instagram 风格的提要,但这些是每张约 8k 的个人资料图片。
当我根据传入的 JSON 数据构建 table 时,我所做的一切都是正确的,利用 WatchKit 的内置图像缓存来减少不必要的网络流量。
应用程序 "works" 和图像显示正确。但问题是视图需要 10-20 秒才能准备好与用户进行交互。在这 10-20 秒结束之前,滚动变慢,并且在所有图像加载完成之前按钮不会执行任何操作。
我想要的是在用户向下滚动时延迟加载图像的方法。
我已经尝试过实施分页解决方案,但这也有其缺点,并不能完全解决我原来的问题。
如果我只填充文本位(没有图像),table 会立即显示并准备好进行交互。
***更新: Mike Swanson 在下面的回答非常彻底,确实解决了我的大部分问题。呆滞和阻塞已经消失。
我想 post 最终代码,供遇到类似问题的任何其他人使用。具体来说,我想展示我是如何实现后台调度的。
我现在也在主线程中临时将图像设置为占位符以显示 activity。一旦实际图像加载并传输到手表,它就会被替换。我还改进了图像缓存键,以便在上传新图像时丢失。
新代码:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
i = 0
for p in self.profiles {
if let profileId = p["id"] as? NSNumber {
var profileIdStr = "\(profileId)"
let row = self.profilesTable.rowControllerAtIndex(i) as! WatchProfileRow
if let hasImage = p["has_image"] as? NSNumber {
let profileImageCacheKey = "\(profileIdStr)-\(hasImage)"
// if the image is already in cache, show it
if self.device.cachedImages[profileImageCacheKey] != nil {
row.profileImageThumbnail.setImageNamed(profileImageCacheKey)
// otherwise, get the image from the cdn and cache it
} else {
if let imageHost = self.imageHost as String! {
var imageURL = NSURL(string: NSString(format: "%@%@-thumbnail?version=%@", imageHost, profileId, hasImage) as String)
var imageRequest = NSURLRequest(URL: imageURL!)
NSURLConnection.sendAsynchronousRequest(imageRequest, queue: NSOperationQueue.mainQueue()) {
(response, data, error) -> Void in
if error == nil {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
dispatch_async(backgroundQueue, {
self.device.addCachedImageWithData(data, name: profileImageCacheKey)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
row.profileImageThumbnail.setImageNamed(profileImageCacheKey)
})
})
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
i++
}
原始代码:
i = 0
for p in self.profiles {
if let profileId = p["id"] as? NSNumber {
var profileIdStr = "\(profileId)"
let row = self.profilesTable.rowControllerAtIndex(i) as! WatchProfileRow
if let hasImage = p["has_image"] as? NSNumber {
// if the image is already in cache, show it
if self.device.cachedImages[profileIdStr] != nil {
row.profileImageThumbnail.setImageNamed(profileIdStr)
// otherwise, get the image from the cdn and cache it
} else {
if let imageHost = self.imageHost as String! {
var imageURL = NSURL(string: NSString(format: "%@%@-thumbnail?version=%@", imageHost, profileId, hasImage) as String)
var imageRequest = NSURLRequest(URL: imageURL!)
NSURLConnection.sendAsynchronousRequest(imageRequest, queue: NSOperationQueue.mainQueue()) {
(response, data, error) -> Void in
if error == nil {
var image = UIImage(data: data)
row.profileImageThumbnail.setImage(image)
self.device.addCachedImage(image!, name: profileIdStr)
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
} else {
row.profileImageThumbnail.setImageNamed("profile-placeholder")
}
}
i++
}
首先,WatchKit 中没有方法来确定WKInterfaceTable
中的当前位置。这意味着您使用的任何逻辑都必须解决这个问题。
其次,看起来您正在发送一个异步网络请求,然后在您有数据时使用 setImage
。 setImage
在将图像传输到设备之前将图像编码为 PNG,在您现有的代码中,这是在主线程上发生的。如果您不需要 PNG 图片,我会考虑使用 setImageData
发送 JPEG 编码数据,以进一步减少需要传输到 Watch 的字节数。
最后,您似乎在重复传输图像。一次 setImage
,两次 addCachedImage
。收到图像后,我建议分派到后台线程,然后使用 WKInterfaceDevice
的 addCachedImageWithData
(可以安全地在后台线程上使用)发送数据。然后,在使用 setImageNamed
.