CS193P Cassini 代码在模拟器上运行但在设备上崩溃? "Message from debugger: Terminated due to Memory Error"
CS193P Cassini code runs on simulator but crashes on device? "Message from debugger: Terminated due to Memory Error"
我正在学习斯坦福大学的 CS193P 在线课程,做 ios 开发。
第 9 讲通过简单的 url UIImage
fetch 应用程序处理 UIScrollView
/ 委托。所述应用程序在模拟器中运行良好,但在尝试使用以下内容获取 img 后启动然后在实时设备 (iPhone5) 上崩溃:
Message from debugger: Terminated due to Memory Error
我回到我的代码中,重新阅读了有关委派的内容,搜索了 SO(我发现了一个类似的线程,我确保我的项目方案没有启用僵尸)。我更新了我的设备,我的编译器/os,我有点郁闷什么可能会阻止设备上的 运行...
class 示例可以从斯坦福大学 https://web.stanford.edu/class/cs193p/cgi-bin/drupal/system/files/sample_code/Cassini.zip 下载,但此代码的行为方式相同!这最初是为 ios 8.1 编写的,现在是 8.4,是否有任何已知问题?
图像视图控制器代码:
import UIKit
class ImageViewController: UIViewController, UIScrollViewDelegate
{
// our Model
// publicly settable
// when it changes (but only if we are on screen)
// we'll fetch the image from the imageURL
// if we're off screen when this happens (view.window == nil)
// viewWillAppear will get it for us later
var imageURL: NSURL? {
didSet {
image = nil
if view.window != nil {
fetchImage()
}
}
}
// fetches the image at imageURL
// does so off the main thread
// then puts a closure back on the main queue
// to handle putting the image in the UI
// (since we aren't allowed to do UI anywhere but main queue)
private func fetchImage()
{
if let url = imageURL {
spinner?.startAnimating()
let qos = Int(QOS_CLASS_USER_INITIATED.value)
dispatch_async(dispatch_get_global_queue(qos, 0)) { () -> Void in
let imageData = NSData(contentsOfURL: url) // this blocks the thread it is on
dispatch_async(dispatch_get_main_queue()) {
// only do something with this image
// if the url we fetched is the current imageURL we want
// (that might have changed while we were off fetching this one)
if url == self.imageURL { // the variable "url" is capture from above
if imageData != nil {
// this might be a waste of time if our MVC is out of action now
// which it might be if someone hit the Back button
// or otherwise removed us from split view or navigation controller
// while we were off fetching the image
self.image = UIImage(data: imageData!)
} else {
self.image = nil
}
}
}
}
}
}
@IBOutlet private weak var spinner: UIActivityIndicatorView!
@IBOutlet private weak var scrollView: UIScrollView! {
didSet {
scrollView.contentSize = imageView.frame.size // critical to set this!
scrollView.delegate = self // required for zooming
scrollView.minimumZoomScale = 0.03 // required for zooming
scrollView.maximumZoomScale = 1.0 // required for zooming
}
}
// UIScrollViewDelegate method
// required for zooming
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
private var imageView = UIImageView()
// convenience computed property
// lets us get involved every time we set an image in imageView
// we can do things like resize the imageView,
// set the scroll view's contentSize,
// and stop the spinner
private var image: UIImage? {
get { return imageView.image }
set {
imageView.image = newValue
imageView.sizeToFit()
scrollView?.contentSize = imageView.frame.size
spinner?.stopAnimating()
}
}
// put our imageView into the view hierarchy
// as a subview of the scrollView
// (will install it into the content area of the scroll view)
override func viewDidLoad() {
super.viewDidLoad()
scrollView.addSubview(imageView)
}
// for efficiency, we will only actually fetch the image
// when we know we are going to be on screen
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if image == nil {
fetchImage()
}
}
}
从数据(代表图像数据的文件格式)解压图像到屏幕会'eat'大量内存的问题的来源。
这是一篇关于 iOS 图片解压缩的非常好的文章 -> Avoiding Image Decompression Sickness
由于卡西尼应用程序中的所有图像都非常大(wave_earth_mosaic_3.jpg(9999×9999),pia03883-full.jpg(14400×9600))图像解压过程'eat'所有phone记忆。这会导致应用程序崩溃。
为了解决 Cassini 问题,我修改了代码并添加了小函数以将图像分辨率降低 2。
这是代码示例(代码固定为 Swift 2.0):
...
if imageData != nil {
// this might be a waste of time if our MVC is out of action now
// which it might be if someone hit the Back button
// or otherwise removed us from split view or navigation controller
// while we were off fetching the image
if let imageSource = UIImage(data: imageData!) {
self.image = self.imageResize(imageSource)
}
} else {
self.image = nil
}
...
func imageResize (imageOriginal:UIImage) -> UIImage {
let image = imageOriginal.CGImage
let width = CGImageGetWidth(image) / 2
let height = CGImageGetHeight(image) / 2
let bitsPerComponent = CGImageGetBitsPerComponent(image)
let bytesPerRow = CGImageGetBytesPerRow(image)
let colorSpace = CGImageGetColorSpace(image)
let bitmapInfo = CGImageGetBitmapInfo(image)
let context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo.rawValue)
CGContextSetInterpolationQuality(context, CGInterpolationQuality.High)
CGContextDrawImage(context, CGRect(origin: CGPointZero, size: CGSize(width: CGFloat(width), height: CGFloat(height))), image)
let scaledImage = UIImage(CGImage: CGBitmapContextCreateImage(context)!)
return scaledImage
}
所以现在应用程序加载所有图像而不会崩溃。
SWIFT 2.0 修复:
将此添加到 Info.plist 以允许 HTTP 加载
<key>NSAppTransportSecurity</key>
<dict>
<!--Include to allow all connections (DANGER)-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
我正在学习斯坦福大学的 CS193P 在线课程,做 ios 开发。
第 9 讲通过简单的 url UIImage
fetch 应用程序处理 UIScrollView
/ 委托。所述应用程序在模拟器中运行良好,但在尝试使用以下内容获取 img 后启动然后在实时设备 (iPhone5) 上崩溃:
Message from debugger: Terminated due to Memory Error
我回到我的代码中,重新阅读了有关委派的内容,搜索了 SO(我发现了一个类似的线程,我确保我的项目方案没有启用僵尸)。我更新了我的设备,我的编译器/os,我有点郁闷什么可能会阻止设备上的 运行... class 示例可以从斯坦福大学 https://web.stanford.edu/class/cs193p/cgi-bin/drupal/system/files/sample_code/Cassini.zip 下载,但此代码的行为方式相同!这最初是为 ios 8.1 编写的,现在是 8.4,是否有任何已知问题?
图像视图控制器代码:
import UIKit
class ImageViewController: UIViewController, UIScrollViewDelegate
{
// our Model
// publicly settable
// when it changes (but only if we are on screen)
// we'll fetch the image from the imageURL
// if we're off screen when this happens (view.window == nil)
// viewWillAppear will get it for us later
var imageURL: NSURL? {
didSet {
image = nil
if view.window != nil {
fetchImage()
}
}
}
// fetches the image at imageURL
// does so off the main thread
// then puts a closure back on the main queue
// to handle putting the image in the UI
// (since we aren't allowed to do UI anywhere but main queue)
private func fetchImage()
{
if let url = imageURL {
spinner?.startAnimating()
let qos = Int(QOS_CLASS_USER_INITIATED.value)
dispatch_async(dispatch_get_global_queue(qos, 0)) { () -> Void in
let imageData = NSData(contentsOfURL: url) // this blocks the thread it is on
dispatch_async(dispatch_get_main_queue()) {
// only do something with this image
// if the url we fetched is the current imageURL we want
// (that might have changed while we were off fetching this one)
if url == self.imageURL { // the variable "url" is capture from above
if imageData != nil {
// this might be a waste of time if our MVC is out of action now
// which it might be if someone hit the Back button
// or otherwise removed us from split view or navigation controller
// while we were off fetching the image
self.image = UIImage(data: imageData!)
} else {
self.image = nil
}
}
}
}
}
}
@IBOutlet private weak var spinner: UIActivityIndicatorView!
@IBOutlet private weak var scrollView: UIScrollView! {
didSet {
scrollView.contentSize = imageView.frame.size // critical to set this!
scrollView.delegate = self // required for zooming
scrollView.minimumZoomScale = 0.03 // required for zooming
scrollView.maximumZoomScale = 1.0 // required for zooming
}
}
// UIScrollViewDelegate method
// required for zooming
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return imageView
}
private var imageView = UIImageView()
// convenience computed property
// lets us get involved every time we set an image in imageView
// we can do things like resize the imageView,
// set the scroll view's contentSize,
// and stop the spinner
private var image: UIImage? {
get { return imageView.image }
set {
imageView.image = newValue
imageView.sizeToFit()
scrollView?.contentSize = imageView.frame.size
spinner?.stopAnimating()
}
}
// put our imageView into the view hierarchy
// as a subview of the scrollView
// (will install it into the content area of the scroll view)
override func viewDidLoad() {
super.viewDidLoad()
scrollView.addSubview(imageView)
}
// for efficiency, we will only actually fetch the image
// when we know we are going to be on screen
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if image == nil {
fetchImage()
}
}
}
从数据(代表图像数据的文件格式)解压图像到屏幕会'eat'大量内存的问题的来源。 这是一篇关于 iOS 图片解压缩的非常好的文章 -> Avoiding Image Decompression Sickness
由于卡西尼应用程序中的所有图像都非常大(wave_earth_mosaic_3.jpg(9999×9999),pia03883-full.jpg(14400×9600))图像解压过程'eat'所有phone记忆。这会导致应用程序崩溃。
为了解决 Cassini 问题,我修改了代码并添加了小函数以将图像分辨率降低 2。
这是代码示例(代码固定为 Swift 2.0):
...
if imageData != nil {
// this might be a waste of time if our MVC is out of action now
// which it might be if someone hit the Back button
// or otherwise removed us from split view or navigation controller
// while we were off fetching the image
if let imageSource = UIImage(data: imageData!) {
self.image = self.imageResize(imageSource)
}
} else {
self.image = nil
}
...
func imageResize (imageOriginal:UIImage) -> UIImage {
let image = imageOriginal.CGImage
let width = CGImageGetWidth(image) / 2
let height = CGImageGetHeight(image) / 2
let bitsPerComponent = CGImageGetBitsPerComponent(image)
let bytesPerRow = CGImageGetBytesPerRow(image)
let colorSpace = CGImageGetColorSpace(image)
let bitmapInfo = CGImageGetBitmapInfo(image)
let context = CGBitmapContextCreate(nil, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo.rawValue)
CGContextSetInterpolationQuality(context, CGInterpolationQuality.High)
CGContextDrawImage(context, CGRect(origin: CGPointZero, size: CGSize(width: CGFloat(width), height: CGFloat(height))), image)
let scaledImage = UIImage(CGImage: CGBitmapContextCreateImage(context)!)
return scaledImage
}
所以现在应用程序加载所有图像而不会崩溃。
SWIFT 2.0 修复:
将此添加到 Info.plist 以允许 HTTP 加载
<key>NSAppTransportSecurity</key>
<dict>
<!--Include to allow all connections (DANGER)-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>