准确计算从文件到UIImageView

时间:2015-05-12 14:49:47

标签: ios swift uiimageview

我正在尝试测量将大型照片(JPEG)从文件加载到iOS 8.0上的UIImageView所花费的时间。

我目前的代码:

import UIKit
class ViewController: UIViewController {
    @IBOutlet weak var imageView: UIImageView!
    @IBAction func loadImage(sender: UIButton) {
        if let imageFile = NSBundle.mainBundle().pathForResource("large_photo", ofType: "jpg") {
            // start our timer
            let tick = Tick()
            // loads a very large image file into imageView
            // the test photo used is a 4608 × 3456 pixel JPEG
            // using contentsOfFile: to prevent caching while testing timer
            imageView.image = UIImage(contentsOfFile: imageFile)
            // stop our timer and print execution time
            tick.tock()
        }
    }
}

class Tick {
    let tickTime : NSDate
    init () {
        tickTime = NSDate()
    }
    func tock () {
        let tockTime = NSDate()
        let executionTime = tockTime.timeIntervalSinceDate(tickTime)
        println("[execution time]: \(executionTime)")
    }
}

当我在我的测试设备(第5代iPod touch)上加载非常大的图像(4608 x 3456 JPEG)时,我可以看到执行时间约为2-3秒并阻止主线程。这可以通过以下事实来观察:UIButton在这段时间内保持突出显示状态,并且没有其他UI元素允许交互。

因此我希望我的计时功能能够报告~2-3秒的时间。但是,它会报告毫秒的时间 - 例如:

[execution time]: 0.0116159915924072

在加载图片之前,此tick.tock()会将消息打印到控制台。这让我感到困惑,因为在加载图像之后,主线程似乎被阻止了

这让我提出以下问题:

  • 如果图像是在后台异步加载的话,那么 为什么用户交互/主线程被阻止?
  • 如果图像正在主线程上加载,为什么会这样 在图像之前,tick.tock()功能打印到控制台 显示?

2 个答案:

答案 0 :(得分:1)

您在这里测量的内容分为两部分:

从磁盘加载图像:

UIImage(contentsOfFile: imageFile)

将图像从JPEG解压缩到要显示的位图:

imageView.image = ....

第一部分涉及实际从磁盘(磁盘I / O)检索压缩的JPEG数据并创建UIImage对象。 UIImage对象保存对压缩数据的引用,直到需要显示它为止。只有在它准备好呈现到屏幕的那一刻它才会将图像解压缩成位图以显示(在主线程上)。

我的猜测是你的计时器只捕获磁盘加载部分,并且解压缩发生在下一个runloop上。大小的图像解压缩可能需要一段时间,可能是狮子的时间份额。

如果您想明确衡量解压缩所需的时间,您需要手动完成,方法是将图像绘制到屏幕外的上下文中,如下所示:

let tick = Tick()

// Load the image from disk
let image = UIImage(contentsOfFile: imageFile)

// Decompress the image into a bitmap
var newImage:UIImage;
UIGraphicsBeginImageContextWithOptions(image.size, true, 0);
image.drawInRect(CGRect(x:0,y:0,width:image.size.width, height:image.size.height))
newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

tick.tock()

我们正在复制将image分配给imageView.image

时发生的解压缩

在处理此大小的图像时保持UI响应的一个方便技巧是将整个过程踢到后台线程。这很有效,因为一旦您手动解压缩图像,UIKit就会检测到这一点并且不会重复该过程。

// Switch to background thread
dispatch_async(dispatch_get_global_queue(Int(DISPATCH_QUEUE_PRIORITY_DEFAULT.value), 0)) {

    // Load the image from disk
    let image = UIImage(contentsOfFile: imageFile)

    // Ref to the decompressed image
    var newImage:UIImage;

    // Decompress the image into a bitmap
    UIGraphicsBeginImageContextWithOptions(image.size, true, 0);
    image.drawInRect(CGRect(x:0,y:0,width:image.size.width, height:image.size.height))
    newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // Switch back to main thread
    dispatch_async(dispatch_get_main_queue()) {

        // Display the decompressed image
        imageView.image = newImage
    }
}

免责声明:此处的代码尚未在Xcode中进行过全面测试,但如果您决定使用它,则99%正确。

答案 1 :(得分:0)

我会尝试使用单元测试来计算时间,因为XCTest框架提供了一些很好的性能测量工具。我认为这种方法可以解决延迟加载问题......虽然我不是100%就可以了。

func testImagePerformance() {
    let date = NSDate()

    measureBlock() {
        if let imageFile = NSBundle.mainBundle().pathForResource("large_photo", ofType: "jpg") {
            imageView.image = UIImage(contentsOfFile: imageFile)
        }
    }
}

(暂且不说,你提到加载会阻止主应用程序线程......你应该考虑使用NSOperationQueue来确保不会发生......你可能已经知道:{{3 }})