I have an issue with importing data to a coredata project with swift.
I'm importing a lot of images, which results in quite a big memory footprint (around 100 - 120MB). The problem is, that once the images are downloaded and imported, I save the managedObjectContext and reset it, but the memory is not released. If however, I send the app to the background, most of the memory is released (I end up with ~50MB).
I use the following CoreData setup:
one masterContext with PrivateQueueConcurrencyType
one mainContext with MainQueueConcurrencyType and masterContext as parentContext
the import runs on it's own context with PrivateQueueConcurrencyType and the mainContext as it's parentContext
the following method starts the import:
func downloadProductImages(completion: (error: NSError?) -> Void) {
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
var err: NSError? = nil
self.importBrandImages({ (error) -> Void in
if error != nil {
completion(error: error)
return
}
moc.save(nil)
self.importProductVariantThumbnails({ (error) -> Void in
if error != nil {
completion(error: error)
return
}
moc.save(nil)
moc.reset()
self.backgroundMOC = nil
completion(error: err)
})
})
}
}
}
and these methods download the images and save them in the database:
private func importBrandImages(completion: (error: NSError?) -> Void) {
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
var error: NSError? = nil
let brandsFetchReq = NSFetchRequest(entityName: "Brand")
// brand images
if let brands = moc.executeFetchRequest(brandsFetchReq, error: &error) as? [Brand] {
let imageQueue = TaskQueue()
autoreleasepool({ () -> () in
for brand in brands {
if let logoSrc = brand.logoSrc {
imageQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(logoSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
brand.logo = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
if let bgImgSrc = brand.bgImageSrc {
imageQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(bgImgSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
brand.bgImage = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
}
})
imageQueue.run(completion: { () -> Void in
moc.save(nil)
completion(error: error)
})
} else {
completion(error: error)
}
}
}
}
private func importProductVariantThumbnails(completion: (error: NSError?) -> Void) {
var err: NSError? = nil
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
let pVariantsFetchReq = NSFetchRequest(entityName: "ProductVariant")
if let variants = moc.executeFetchRequest(pVariantsFetchReq, error: &err) as? [ProductVariant] {
let importQueue = TaskQueue()
autoreleasepool({ () -> () in
for variant in variants {
if let thumbnailSrc = variant.thumbnailSrc {
importQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(thumbnailSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
variant.thumbnail = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
}
})
importQueue.run { () -> Void in
moc.save(nil)
self.importProductVariantImages({ (error) -> Void in
completion(error: error)
})
}
}
}
}
}
private func importProductVariantImages(completion: (error: NSError?) -> Void) {
var error: NSError? = nil
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
let pImagesFetchReq = NSFetchRequest(entityName: "ProductImage")
// product images
if let images = moc.executeFetchRequest(pImagesFetchReq, error: &error) as? [ProductImage] {
let importQueue = TaskQueue()
autoreleasepool({ () -> () in
for pImage in images {
if let imageSrc = pImage.imageSrc {
importQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(imageSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
pImage.imageData = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
}
})
importQueue.run(completion: { () -> Void in
moc.save(nil)
completion(error: error)
})
} else {
completion(error: error)
}
}
}
}
I have no idea, why the memory is not released. I used Instruments to find memory leaks, but it doesn't show any.
Your problem appears to be that ImageLoader contains a cache and is adding all of the images to it. If you don't need that functionality, and it doesn't really look like you do, then you should delete it and simplify the ImageLoader so it just downloads and returns the images.
Related
Hello and thanks in advance for your time.
In my code I am making various requests to AWSSQS which all return AWSTask. I have found working with these AWSTask objects to be very difficult while also trying to keep all the logic specific to AWS in a single class so I can easily switch to a different cloud service if need be.
Ideally, what I would like to do is execute a series of AWS tasks asynchronously in a serial fashion. Normally I would just add tasks to a custom Serial Dispatch Queue but since The AWSTask objects are themselves asynchronous tasks, I can't do that.
Here is a simple example that illustrates the problem I am having. It doesn't have any real world purpose but it does a good job illustrating the problem. Below, I have code to create a SQS queue, send a message to a SQS queue, receive a message from an SQS queue, and delete a SQS queue. Let's say I want to do those four things in a serial, asynchronous fashion. In other words, I want to make sure the previous task succeeded before attempting the next task.
ViewController
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
awsClass.runTest()
DispatchQueue.main.async {
print("Test Finished")
}
}
AwsClass
public func createQueue(){
guard let createQueueRequest = AWSSQSCreateQueueRequest() else{fatalError()}
createQueueRequest.queueName = "TestQueue"
sqs.createQueue(createQueueRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
self.queueUrl = task.result!.queueUrl!
print("created queue at: \(self.queueUrl!)")
}
return nil
})
}
public func deleteQueue(){
if queueUrl != nil {
guard let deleteQueueRequest = AWSSQSDeleteQueueRequest() else{fatalError()}
deleteQueueRequest.queueUrl = queueUrl
sqs.deleteQueue(deleteQueueRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("queue sucessfully deleted from \(self.queueUrl!)")
self.queueUrl = nil
}
return nil
})
}
else{
print("Queue has already been deleted")
}
}
public func sendMessage(messageData: String, toConnectId: String) {
guard let sendMessageRequest = AWSSQSSendMessageRequest() else{fatalError()}
sendMessageRequest.queueUrl = toConnectId
sendMessageRequest.delaySeconds = 0
sendMessageRequest.messageBody = messageData
sqs.sendMessage(sendMessageRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("successfully sent message to \(toConnectId)")
}
return nil
})
}
public func receiveMessage(){
guard let receiveMessageRequest = AWSSQSReceiveMessageRequest() else{fatalError()}
receiveMessageRequest.queueUrl = self.queueUrl
receiveMessageRequest.maxNumberOfMessages = 1
sqs.receiveMessage(receiveMessageRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
let message = (task.result?.messages?.first)!
print("successfully received message with body: \(message.body ?? "failed")")
}
return nil
})
}
public func runTest(){
let mySerialQueue = DispatchQueue(label: "mySerialQueue")
mySerialQueue.sync {
self.createQueue()
}
mySerialQueue.sync {
self.sendMessage(messageData: "test", toConnectId: "https://someUrl")
}
mySerialQueue.sync {
self.receiveMessage()
}
mySerialQueue.sync {
self.deleteQueue()
}
}
Since the AWSTasks are asynchronous with completion functions, the code quickly makes all four calls and then the completion functions are called whenever those tasks finish. Instead I want the completion function of the first task to finish before the next task begins.
The AWSTask objects are meant to be "chained" together.
Documentation can be found here: http://docs.aws.amazon.com/mobile/sdkforios/developerguide/awstask.html
A small example here:
sqs.createQueue(/* parameters */).continueWithSuccess(block: {(task) -> Void in
// Success
return sqs.sendMessage(/* parameters */)
}).continueWithSuccess(block: {(task) -> Void in
// Success
return sqs.receiveMessage(/* parameters */)
}).continueWithSuccess(block: {(task) -> Void in
// Success
return sqs.deleteQueue(/* parameters */)
})
Alright, so I found a solution to my question. It works exactly as desired but it does so in this nasty chain of completion functions. If anyone knows a more elegant solution, I am all ears!
ViewController
print("Starting Test")
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
atomConnector.runTest(completion: {
print("test finshed")
})
}
AwsClass
public func createQueue(completion: #escaping () -> Void){
guard let createQueueRequest = AWSSQSCreateQueueRequest() else{fatalError()}
createQueueRequest.queueName = "TestQueue"
sqs.createQueue(createQueueRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
self.queueUrl = task.result!.queueUrl!
print("created queue at: \(self.queueUrl!)")
completion()
}
})
}
public func deleteQueue(completion: #escaping () -> Void){
if queueUrl != nil {
guard let deleteQueueRequest = AWSSQSDeleteQueueRequest() else{fatalError()}
deleteQueueRequest.queueUrl = queueUrl
sqs.deleteQueue(deleteQueueRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("queue sucessfully deleted from \(self.queueUrl!)")
self.queueUrl = nil
completion()
}
})
}
else{
print("Queue has already been deleted")
}
}
public func sendMessage(messageData: String, toConnectId: String, completion: #escaping () -> Void) {
guard let sendMessageRequest = AWSSQSSendMessageRequest() else{fatalError()}
sendMessageRequest.queueUrl = toConnectId
sendMessageRequest.delaySeconds = 0
sendMessageRequest.messageBody = messageData
sqs.sendMessage(sendMessageRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("successfully sent message to \(toConnectId)")
completion()
}
})
}
public func receiveMessage(completion: #escaping () -> Void){
guard let receiveMessageRequest = AWSSQSReceiveMessageRequest() else{fatalError()}
receiveMessageRequest.queueUrl = self.queueUrl
receiveMessageRequest.maxNumberOfMessages = 1
sqs.receiveMessage(receiveMessageRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
let message = (task.result?.messages?.first)!
print("successfully received message with body: \(message.body ?? "failed")")
self.deleteMessage(receiptHandle: message.receiptHandle, completion: completion)
}
})
}
public func deleteMessage(receiptHandle: String?, completion: #escaping () -> Void){
guard let deleteMessageRequest = AWSSQSDeleteMessageRequest() else{fatalError()}
deleteMessageRequest.queueUrl = self.queueUrl
deleteMessageRequest.receiptHandle = receiptHandle
sqs.deleteMessage(deleteMessageRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("successfully deleted message with receiptHandle: \(receiptHandle)")
completion()
}
})
}
public func runTest(completion: #escaping () -> Void){
self.createQueue(completion: {
self.sendMessage(messageData: "test", toConnectId: "https://someUrl", completion: {
self.receiveMessage(completion: {
self.deleteQueue(completion: {
completion()
})
})
})
})
}
I am creating a seperate class to handle my Amazone S3 upload request. However, I am not too sure about the syntax to allow me to create a progress block before completion handler (as shown below in IBAction). Basically what I wish to achieve is inside my VC, I do the following:
#IBAction startUpload() {
let uploadPost = PostUpload(imageNSData: someNSData)()
uploadPost.uploadBegin {
// Some block here to grab the "progress_in_percentage" variable so I can use it on progress bar
{
// Some completion block when the request is completed and check if any error was returned
}
}
}
This is the structure of PostUpload class
class PostUpload {
var imageNSData: NSData!
init(imageNSData: NSData) {
self.imageNSData = imageNSData
}
func uploadBegin(completion:(success: Bool, error: NSError?) -> Void) {
// 1. Create upload request
let uploadRequest = AWSS3TransferManagerUploadRequest(
// Track progress through an AWSNetworkingUploadProgressBlock
uploadRequest?.uploadProgress = {[weak self](bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
let progress_in_percentage = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print(progress_in_percentage)
})
}
// 3. Upload to Amazone S3
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.upload(uploadRequest).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: { (task: AWSTask) -> AnyObject? in
if let error = task.error {
completion(true, error)
} else {
completion(true, nil)
}
return nil
})
}
}
Change your method to this:
func uploadBegin(progressUpdate: ((percent: Float) -> Void)? = nil, completion:(success: Bool, error: NSError?) -> Void) {
// 1. Create upload request
let uploadRequest = AWSS3TransferManagerUploadRequest(
// Track progress through an AWSNetworkingUploadProgressBlock
uploadRequest?.uploadProgress = {[weak self](bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
let progress_in_percentage = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
print(progress_in_percentage)
//Call this to update progress
progressUpdate?(progress_in_percentage)
})
}
// 3. Upload to Amazone S3
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.upload(uploadRequest).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: { (task: AWSTask) -> AnyObject? in
if let error = task.error {
completion(true, error)
} else {
completion(true, nil)
}
return nil
})
}
Usage:
uploadBegin { (success, error) in
//completion block
}
Or:
uploadBegin({ (percent) in
//Update Progress on UI
}) { (success, error) in
//completion block
}
You can add more than one closure in function parameter, in Your case just add progress callback closure as first param of function
func uploadBegin(uploadProgress:(percentage: Float) -> Void, completion:(success: Bool, error: NSError?) -> Void) {
// 1. Create upload request
let uploadRequest = AWSS3TransferManagerUploadRequest(
// Track progress through an AWSNetworkingUploadProgressBlock
uploadRequest?.uploadProgress = {[weak self](bytesSent:Int64, totalBytesSent:Int64, totalBytesExpectedToSend:Int64) in
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
let progress_in_percentage = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
// report progress here
uploadProgress(percentage: progress_in_percentage)
print(progress_in_percentage)
})
}
// 3. Upload to Amazone S3
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.upload(uploadRequest).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: { (task: AWSTask) -> AnyObject? in
if let error = task.error {
completion(true, error)
} else {
completion(true, nil)
}
return nil
})
}
use
uploadPost.uploadBegin({ (percentage) in
// progress handle here
}) { (success, error) in
// completion block
}
I have been using UIImageView+AFNetworking.swift in my application to download images, but after updating to Xcode 7, it gives me an error.
Here is the full code:
import UIKit
#objc public protocol AFImageCacheProtocol:class{
func cachedImageForRequest(request:NSURLRequest) -> UIImage?
func cacheImage(image:UIImage, forRequest request:NSURLRequest);
}
extension UIImageView {
private struct AssociatedKeys {
static var SharedImageCache = "SharedImageCache"
static var RequestImageOperation = "RequestImageOperation"
static var URLRequestImage = "UrlRequestImage"
}
public class func setSharedImageCache(cache:AFImageCacheProtocol?) {
objc_setAssociatedObject(self, &AssociatedKeys.SharedImageCache, cache, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN))
}
public class func sharedImageCache() -> AFImageCacheProtocol {
struct Static {
static var token : dispatch_once_t = 0
static var defaultImageCache:AFImageCache?
}
dispatch_once(&Static.token, { () -> Void in
Static.defaultImageCache = AFImageCache()
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationDidReceiveMemoryWarningNotification, object: nil, queue: NSOperationQueue.mainQueue()) { (NSNotification) -> Void in
Static.defaultImageCache!.removeAllObjects()
}
})
return objc_getAssociatedObject(self, &AssociatedKeys.SharedImageCache) as? AFImageCacheProtocol ?? Static.defaultImageCache!
}
private class func af_sharedImageRequestOperationQueue() -> NSOperationQueue {
struct Static {
static var token:dispatch_once_t = 0
static var queue:NSOperationQueue?
}
dispatch_once(&Static.token, { () -> Void in
Static.queue = NSOperationQueue()
Static.queue!.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount
})
return Static.queue!
}
private var af_requestImageOperation:(operation:NSOperation?, request: NSURLRequest?) {
get {
let operation:NSOperation? = objc_getAssociatedObject(self, &AssociatedKeys.RequestImageOperation) as? NSOperation
let request:NSURLRequest? = objc_getAssociatedObject(self, &AssociatedKeys.URLRequestImage) as? NSURLRequest
return (operation, request)
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.RequestImageOperation, newValue.operation, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
objc_setAssociatedObject(self, &AssociatedKeys.URLRequestImage, newValue.request, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
public func setImageWithUrl(url:NSURL, placeHolderImage:UIImage? = nil) {
let request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.addValue("image/*", forHTTPHeaderField: "Accept")
self.setImageWithUrlRequest(request, placeHolderImage: placeHolderImage, success: nil, failure: nil)
}
public func setImageWithUrlRequest(request:NSURLRequest, placeHolderImage:UIImage? = nil,
success:((request:NSURLRequest?, response:NSURLResponse?, image:UIImage, fromCache:Bool) -> Void)?,
failure:((request:NSURLRequest?, response:NSURLResponse?, error:NSError) -> Void)?)
{
self.cancelImageRequestOperation()
if let cachedImage = UIImageView.sharedImageCache().cachedImageForRequest(request) {
if success != nil {
success!(request: nil, response:nil, image: cachedImage, fromCache:true)
}
else {
self.image = cachedImage
}
return
}
if placeHolderImage != nil {
self.image = placeHolderImage
}
self.af_requestImageOperation = (NSBlockOperation(block: { () -> Void in
var response:NSURLResponse?
var error:NSError?
let data: NSData?
do {
data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: &response)
} catch let error1 as NSError {
error = error1
data = nil
} catch {
fatalError()
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if request.URL!.isEqual(self.af_requestImageOperation.request?.URL) {
let image:UIImage? = (data != nil ? UIImage(data: data!) : nil)
if image != nil {
if success != nil {
success!(request: request, response: response, image: image!, fromCache:false)
}
else {
self.image = image!
}
UIImageView.sharedImageCache().cacheImage(image!, forRequest: request)
}
else {
if failure != nil {
failure!(request: request, response:response, error: error!)
}
}
self.af_requestImageOperation = (nil, nil)
}
})
}), request)
UIImageView.af_sharedImageRequestOperationQueue().addOperation(self.af_requestImageOperation.operation!)
}
private func cancelImageRequestOperation() {
self.af_requestImageOperation.operation?.cancel()
self.af_requestImageOperation = (nil, nil)
}
}
func AFImageCacheKeyFromURLRequest(request:NSURLRequest) -> String {
return request.URL!.absoluteString
}
class AFImageCache: NSCache, AFImageCacheProtocol {
func cachedImageForRequest(request: NSURLRequest) -> UIImage? {
switch request.cachePolicy {
case NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData,
NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData:
return nil
default:
break
}
return self.objectForKey(AFImageCacheKeyFromURLRequest(request)) as? UIImage
}
func cacheImage(image: UIImage, forRequest request: NSURLRequest) {
self.setObject(image, forKey: AFImageCacheKeyFromURLRequest(request))
}
}
Error image:
Error 1:
objc_setAssociatedObject(self, &AssociatedKeys.SharedImageCache, cache, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN))
Error 2:
objc_setAssociatedObject(self, &AssociatedKeys.RequestImageOperation, newValue.operation, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
Error 3:
objc_setAssociatedObject(self, &AssociatedKeys.URLRequestImage, newValue.request, UInt(objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))
Does anyone has any idea how I can fix this?
The reason for this is exactly as the error states; that UInt cannot be created with an argument of type objc_AssociationPolicy.
objc_setAssociatedObject expects the policy parameter to be of type objc_AssociationPolicy, you don't need to convert it to UInt at all.
objc_setAssociatedObject(self, &AssociatedKeys.SharedImageCache, cache, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
All,
I have a simple class in the parse backend - which feeds a uitableview. The data is stored in Arrays. I guess because all parse data is done in the background sometimes data gets downloaded before others. I have a very mixed up tableview. With images in the wrong cell etc. Also the custom cells are sometimes not showing up at all and I need to do a refresh.
Here is my code that I used to download all the data from parse and add into the arrays.
Can you have a look please and suggest a way to do this.
Also how can I add a placeholder image , before the original image comes up.
Also this code is in my ViewDidLoad, would it be better in an earlier function, hopefully so i don't have to relsoaddata on the tableview..
var query = PFQuery(className:"TableViewData")
query.includeKey("EventLoc")
query.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
let thumbNail = object["backgroundImage"] as PFFile
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
dispatch_async(dispatch_get_main_queue()) {
let image = UIImage(data:imageData)
self.CellBackgroundImage.append(image!)
}
}
})
var VenueLocation = object["EventLoc"] as PFObject!
VenueLocation.fetchIfNeededInBackgroundWithBlock {
(VenueLocation: PFObject!, error: NSError!) -> Void in
dispatch_async(dispatch_get_main_queue()) {
let VenueLocationTitle = VenueLocation["EventLocation"] as NSString
self.EventLocationArray.append(VenueLocationTitle)
}
}
let eventiconimage = object["EventIcon"] as PFFile
eventiconimage.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
dispatch_async(dispatch_get_main_queue()) {
let image = UIImage(data:imageData)
self.EventIconImageArray.append(image!)
}
}
})
dispatch_async(dispatch_get_main_queue()) {
self.TitleArray.append(object["EventTitle"] as String)
self.EventPriceArray.append(object["EventPrice"] as String)
self.EventStartDate.append(object["EventStartDate"] as NSDate)
self.EventEndDate.append(object["EventEndDate"] as NSDate)
self.tableView.reloadData()
}
You need to use serial queue then your array data will be in order. Whats happening because of concurrent task the data is not appended in order
var backgroundQueue:dispatch_queue_t = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL)
var query = PFQuery(className:"TableViewData")
query.includeKey("EventLoc")
query.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
dispatch_async(backgroundQueue, { () -> () in
let thumbNail = object["backgroundImage"] as PFFile
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
dispatch_async(dispatch_get_main_queue()) {
let image = UIImage(data:imageData)
self.CellBackgroundImage.append(image!)
}
}
})
var VenueLocation = object["EventLoc"] as PFObject!
VenueLocation.fetchIfNeededInBackgroundWithBlock {
(VenueLocation: PFObject!, error: NSError!) -> Void in
dispatch_async(dispatch_get_main_queue()) {
let VenueLocationTitle = VenueLocation["EventLocation"] as NSString
self.EventLocationArray.append(VenueLocationTitle)
}
}
let eventiconimage = object["EventIcon"] as PFFile
eventiconimage.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
dispatch_async(dispatch_get_main_queue()) {
let image = UIImage(data:imageData)
self.EventIconImageArray.append(image!)
}
}
})
dispatch_async(dispatch_get_main_queue()) {
self.TitleArray.append(object["EventTitle"] as String)
self.EventPriceArray.append(object["EventPrice"] as String)
self.EventStartDate.append(object["EventStartDate"] as NSDate)
self.EventEndDate.append(object["EventEndDate"] as NSDate)
self.tableView.reloadData()
}
});
}
}
Today I upgraded Xcode 6 to beta 5 (from beta 1) and as you can imagine I found my previously perfectly working Swift app full of errors of all kind (well, a lot changed from beta 1). Of all errors, there is one I just can't figure out how to fix. It's related to swift closures, in particular the enumerationBlock argument of the .enumerateGroupsWithTypes method. Here is the code:
assetLib.enumerateGroupsWithTypes(ALAssetsGroupType(ALAssetsGroupSavedPhotos), usingBlock: {
(group: ALAssetsGroup?, stop: CMutablePointer<ObjCBool>) in
...
}, failureBlock: {
(error: NSError!) in
...
})
This did work perfectly in Swift (Xcode 6 beta 1). But now, I get 2 errors:
" 'UnsafeMutablePointer' is not a subtype of 'error type' "
" Use of undeclared type 'CMutablePointer' "
It was clear that CMutablePointer did not exist anymore, so I tried to modify the stop argument like:
..., stop: UnsafeMutablePointer<ObjCBool> ...
After this change, the second error obviously disappeared, but the first transformed in:
" Could not find an overload for 'init' that accepts the supplied arguments "
I even tried to change the UnsafeMutablePointer to a UnsafePointer, as suggested from this post.
EDIT:
Here is the full code of the enumerateGroupsWithTypes method:
assetLib.enumerateGroupsWithTypes(ALAssetsGroupType(ALAssetsGroupSavedPhotos), usingBlock: {
(group: ALAssetsGroup?, stop: UnsafeMutablePointer<ObjCBool>) in
if group != nil {
group!.setAssetsFilter(ALAssetsFilter.allPhotos())
group!.enumerateAssetsAtIndexes(NSIndexSet(index: group!.numberOfAssets()-1), options: nil, usingBlock: {
(result: ALAsset!, index: Int, stop: UnsafeMutablePointer<ObjCBool>) in
if result {
var alAssetRapresentation: ALAssetRepresentation = result.defaultRepresentation()
url = alAssetRapresentation.url()
}
})
}
else if group == nil {
assetLib.assetForURL(url, resultBlock: {
(asset: ALAsset!) in
if asset != nil {
var assetRep: ALAssetRepresentation = asset.defaultRepresentation()
var iref = assetRep.fullResolutionImage().takeUnretainedValue()
var image = UIImage(CGImage: iref)
imageView.image = image
self.view.addSubview(imageView)
let mask = CAShapeLayer()
mask.path = UIBezierPath(ovalInRect: CGRectMake(0, 0, 200, 200)).CGPath
mask.frame = CGPathGetPathBoundingBox(mask.path)
mapView.layer.mask = mask
self.view.addSubview(mapView)
}
}, failureBlock: {
(error: NSError!) in
NSLog("Error!", nil)
})
}
}, failureBlock: {
(error: NSError!) in
NSLog("Error!", nil)
})
Here is a for me working example: This code looks for the album „projectname“ and safe the image „in“ this album. If the album do not exists, the album will be created.
Attention: If there WAS an album with the SAME name. You can NOT create a album with this name once again. You must use a new name. In this example the projectname will extent with date and time.
By the way, Apples apps could create a album with the same name.
func saveImage(projectName : String) { // Return a new projectname
// in the var self.newProjectName if the old one could not created
if self.isSaved { // was here bevor
return
}
let library = ALAssetsLibrary() // This object will provide the access to to the library
// List over all groups in the PhotoDirectory
// ALAssetsGroupAll is the key to select the listed groups
// possible change to type:Album
library.enumerateGroupsWithTypes(ALAssetsGroupType(ALAssetsGroupAll),
usingBlock: {(group : ALAssetsGroup!, stop : UnsafeMutablePointer<ObjCBool>) in
if group != nil { // The listing of the directory content has found an object
// Did we search for this album?
if group.valueForProperty(ALAssetsGroupPropertyName).isEqualToString(projectName) {
stop.initialize(true) // Stop the enumeration thread
library.writeImageToSavedPhotosAlbum(self.cgImage, metadata: self.ciImage?.properties(),
completionBlock: {(assetUrl, error: NSError?) -> Void in
if let theError = error?.code {
lapApp.logger.addLog("saved image failed, first try \(error?.localizedDescription) code \(theError)")
} else {
library.assetForURL(assetUrl,
resultBlock: { (asset: ALAsset!) -> Void in
group.addAsset(asset)
self.isSaved = true
return // Stop this process and leave
}, failureBlock: {
(theError: NSError!) -> Void in
lapApp.logger.addLog("error occurred, image to album at the first try: \(theError.localizedDescription) ")
})
}
})
return // write image to the found album
} else {
// Album not found, enumeration will continue
}
}
else { // The album was not found, so we will create an album
if stop.memory.boolValue { // The enumeration will go over the end of the list. The stop-signal comes some time to late?
return
}
library.addAssetsGroupAlbumWithName(projectName,
resultBlock: {(group: ALAssetsGroup?) -> Void in
if let thegroup = group { // Check for a name conflict, possible was a album with the same name deleted. IOS8 will not create this album!
// The album was correct created, now we will add the picture to the album
library.writeImageToSavedPhotosAlbum(self.cgImage, metadata: self.ciImage?.properties(), completionBlock: {
(assetUrl, error: NSError?) -> Void in
if let theError = error?.code {
lapApp.logger.addLog("save image in new album failed. \(error?.localizedDescription) code \(theError)")
} else {
library.assetForURL(assetUrl,
resultBlock: { (asset: ALAsset!) -> Void in
thegroup.addAsset(asset)
self.isSaved = true
stop.initialize(true) // Stop the enumeration thread
return
}, failureBlock: {
(theError: NSError?) -> Void in
lapApp.logger.addLog("error occurred: \(theError?.localizedDescription)")
})
}
})
return
} else { // Name conflic with a deleted album.
// Work around: Create the Album with the Projectname an extend the name with Date and time
let formatter : NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "yy.MM.dd hh:mm:ss"
let extensionDate = formatter.stringFromDate(NSDate())
self.newProjectName = projectName + " " + extensionDate // This is the new projectname
library.addAssetsGroupAlbumWithName(self.newProjectName,
resultBlock: {(group: ALAssetsGroup?) -> Void in
if let theGroup = group {
library.writeImageToSavedPhotosAlbum(self.cgImage, metadata: self.ciImage?.properties(), completionBlock: {
(assetUrl, error: NSError?) -> Void in
if let theError = error {
lapApp.logger.addLog("save image with new album name failed. \(error?.localizedDescription) code \(theError) \(self.newProjectName)")
} else {
library.assetForURL(assetUrl, resultBlock: { (asset: ALAsset!) -> Void in
theGroup.addAsset(asset)
self.isSaved = true
stop.initialize(true) // Stop the enumeration thread
return
}, failureBlock: {
(theError: NSError?) -> Void in
lapApp.logger.addLog("error at write image in new album occurred: \(theError?.localizedDescription)")
})
}
})
} else {
lapApp.logger.addLog("Problem adding albums with the name \(self.newProjectName)")
}
},
failureBlock: {
(error:NSError?) -> Void in
lapApp.logger.addLog("Problem adding albums: \(error)")
})
}
},
failureBlock: {
(error:NSError?) -> Void in
lapApp.logger.addLog("Problem loading albums: \(error)")
})
}
}, failureBlock: { (error:NSError?) in lapApp.logger.addLog("Problem loading albums: \(error)") })
} // End SaveImage
NSLog("Error!", nil) is wrong and should be NSLog("Error!").
(This seems to confuse the Swift compiler and causes unrelated error messages.)