How to append data to NSKeyedArchive? - file-io

I'm reading a file like so:
NSData *data = [NSData dataWithContentsOfFile:myPath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSData *imgData = [unarchiver decodeObjectForKey:#"myimage"];
if (imgData != nil) {
self.image = [[NSImage alloc]initWithData:imgData];
}else{
self.image = nil;
}
[unarchiver finishDecoding];
[unarchiver release];
What I'd like to do is to append image data for key #"myimage" to the same file if it doesn't exist.
How to go about it?

It seems that there's no way to "append" to NSKeyedArchive, so I just re-created it with the extra data added and have written over the original file.

NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"any name"];
[NSKeyedArchiver archiveRootObject:your object(example:singletonDataObjectref) toFile:archivePath];

Related

memory leaks issue while saving images to directory

Please try to understand my question.
i am picking images from phone library and saving into Documents Directory. But When I pick large number of images the utilised memory increases gradually and reach above of 400 mb then my app crash. Please if anybody can solve my problem what should I do? I'm new comer to Objective C. Any response will be appreciated.
here is my code
when Picker finish picking
- (void)agImagePickerController:(AGImagePickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info {
[self ShowLoadingView:#"Files Are Loading...."];
[self performSelectorInBackground:#selector(saveAllSelectedImages:) withObject:info];}
and then I save images to Directory
-(void) saveAllSelectedImages:(NSArray*)imagesArray{
for (int i=0; i<imagesArray.count; i++) {
ALAsset *asset = [imagesArray objectAtIndex:i];
ALAssetRepresentation *alassetRep = [asset defaultRepresentation];
NSDate *currentDate = [NSDate date];
NSString* DucPath = [[AppDelegate GetDocumentDirectoryPath] stringByAppendingPathComponent:#"Media"];
if (![[NSFileManager defaultManager] fileExistsAtPath:DucPath])
[[NSFileManager defaultManager] createDirectoryAtPath:DucPath withIntermediateDirectories:NO attributes:nil error:nil];
if ([[asset valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo])
{
long long DataSize = [alassetRep size];
Byte *buffer = (Byte*)malloc(DataSize);
NSUInteger buffered = (NSUInteger)[alassetRep getBytes:buffer fromOffset:0.0 length:alassetRep.size error:nil];
NSData *videoData = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
NSString* newVideoName = [NSString stringWithFormat:#"video_%d_%d.mov",(int)currentDate,i];
NSString* newVideoPath = [DucPath stringByAppendingPathComponent:newVideoName];
[videoData writeToFile:newVideoPath atomically:YES];
[pImageMediaArray addObject:newVideoName];
}
else
{
UIImage *image = [UIImage imageWithCGImage:[alassetRep fullResolutionImage]];
/************************************Full Resolution Images ******************************************/
NSData *imageData = UIImageJPEGRepresentation(image, 0.8);
image = nil;
NSString *originalPath = [NSString stringWithFormat:#"IMAGE_%d_%d.jpg",(int)currentDate,i];
NSString* pImagePath = [DucPath stringByAppendingPathComponent:originalPath];
[imageData writeToFile:pImagePath atomically:YES];
[pImageMediaArray addObject:originalPath];
}
/************************************Low Resolution Images ******************************************/
UIImage *image = [UIImage imageWithCGImage:[alassetRep fullResolutionImage]];
UIImage *thumbImage = [self imageWithImage:image scaledToSize:CGSizeMake(50, 50)];
NSData *thumbImageData = UIImageJPEGRepresentation(thumbImage, 0.8);
NSString *thumbOriginalPath = [NSString stringWithFormat:#"SMALL_IMAGE_%d_%d.jpg",(int)currentDate,i];
NSString* thumbImagePath = [DucPath stringByAppendingPathComponent:thumbOriginalPath];
NSLog(#"Image path At Save Time:%#",thumbImagePath);
[thumbImageData writeToFile:thumbImagePath atomically:YES];
[pMediaArray addObject:thumbOriginalPath];
}
[appDelegate setPMediaArray:pImageMediaArray];
[pGridView reloadData];
imagesArray = nil;
[imagesArray release];
[pImageMediaArray release];
[self performSelectorOnMainThread:#selector(closeLoadindView) withObject:nil waitUntilDone:YES];}
Byte *buffer = (Byte*)malloc(DataSize);
is not being freed?
I had the same exact same issue. What worked for me was to use an autorelease pool block when you save the image. This will free up the retain count and the garbage collection will release the memory appropriately instead of retaining those objects in memory until the containing loop is finished running.
Example: In the method that you are using to save the images add code that looks like this:
#autoreleasepool {
NSString *filePath = [[NSArray arrayWithObjects:self.imagePath, #"/", GUID, #".png", nil] componentsJoinedByString:#""];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
BOOL res = [imageData writeToFile:filePath atomically:YES];
imageData = nil;
}
You need to add the autoreleasepool for task that perform in the background. In the above code content of the saveAllSelectedImages should be written inside autoreleasepool, Otherwise memory won't be released.

Loading Images from JSON to Xcode

I having a bit difficulty loading images from a json file into UIImage - Table Cells in Xcode. I tried to load the images from the server into a NSArray then populating the table view UIImage cells. Is there something that I am missing here?
Image are located on a SQL server.
Thanks for the help.
Here is the server output from the PHP into Xcode. (cover_image)
(
"13497074790148.jpeg",
"13494650900147.png",
"13494606630147.png",
"13494605220147.jpeg",
"13494602920147.jpeg",
"13494601850147.jpeg",
"13491916300147.jpeg"
)
Here is the code in Xcode
NSArray *itemsimages = [[NSArray alloc]initWithArray:[results valueForKeyPath:#"cover_image"]];
self.itemImages = itemsimages;
Here is the code in table cells
UIImage *imageitm = [UIImage imageNamed: [self.itemImages objectAtIndex: [indexPath row]]];
cell.itmImage.image = imageitm;
return cell;
You don't have those images stored locally so it doesn't have any images to display. I suggest using SDWebImage to provide asyncronous image loading from remote location + caching mechanism.
-(void) viewDidLoad
{
NSURL *url = [NSURL URLWithString:#"YOUR URL"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSError *error;
NSMutableDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSMutableArray *img = [[NSMutableArray alloc]init];
NSArray *websiteDetails = (NSArray *) [json objectForKey:#"logos"];
for(int count=0; count<[websiteDetails count]; count++)
{
NSDictionary *websiteInfo = (NSDictionary *) [websiteDetails objectAtIndex:count];
imagefile = (NSString *) [websiteInfo objectForKey:#"image_file"];
if([imagefile length]>0)
{
NSLog(#"Imagefile URL is: %#",imagefile);
[img addObject:imagefile];
}
}
//NSarray listofURL
listofURL = img;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSURL *url = [NSURL URLWithString:[listofURL objectAtIndex:indexPath.row]];
NSData *image = [[NSData alloc] initWithContentsOfURL:url];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:image];
});
});
}
return cell;
}
You need to have a proper url in json response or you can store the common part of the url in the code itself and append it later with the image name returned from server.
I did as follows in the same condition
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSDictionary *dict = ((NSDictionary *) result)[#"result"];
NSString *url = dict[#"imageURL"];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
[_buttonImageView setImage:image forState:UIControlStateNormal];
where data is the response returned from server.

Can't figure out about saving files

I am trying to save my object to the file system on an iPad, but I seem to be doing something wrong. Here is how I have archived the object:
NSString *localizedPath = [self getPlistFilePath];
NSString *fileName = [NSString stringWithFormat:#"%#.plist", character.infoName];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:character];
fileName = [fileName stringByReplacingOccurrencesOfString:#" " withString:#"_"];
localizedPath = [localizedPath stringByAppendingPathComponent:fileName];
NSLog(#"File Path: %#", localizedPath);
if(data) {
NSError *writingError;
BOOL wasWritten = [data writeToFile:localizedPath options:NSDataWritingAtomic error:&writingError];
if(!wasWritten) {
NSLog(#"%#", [writingError localizedDescription]);
}
}
Now, this creates a plist file that I can see and read on the file system. When I try to use the following to unarchive it though:
NSError *error;
NSString *directory = [self getPlistFilePath];
NSArray *files = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:directory error:&error];
NSMutableArray *characters = [[NSMutableArray alloc]init];
for(NSString *path in files) {
if(![path hasSuffix:#"plist"]) {
continue;
}
NSString *fullPath = [directory stringByAppendingPathComponent:path];
NSData *data = [NSData dataWithContentsOfFile:fullPath];
IRSkillsObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data]; // get EXEC_BAD_ACCESS here...
[data release];
[characters addObject:object];
}
I get an EXEC_BAD_ACCESS error.
The IRSkillsObject conforms to the NSCoding protocol. You can see, I commented the line that I get the error on.
I am sure it's something I am doing wrong, but I just can't see it. I have tried to step through with the debugger (placing a break point in the initWithCoder: method of the object) but I don't get any errors then. In fact, it places the data in the object properly as I watch. But once it's done loading the data, it gives the error. I have tried using the retain method, but that doesn't help.
Any help that you can provide would be greatly appreciated!
You are releasing data without allocating it.
NSData *data = [NSData dataWithContentsOfFile:fullPath];
IRSkillsObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[data release];
So try this:
NSData *data = [[NSData alloc] initWithContentsOfFile:fullPath];
IRSkillsObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[data release];
When an EXEC_BAD_ACCESS error is found. Usually is because some data has been released but it is still needed in the code.
Maybe there is a property inside your IRSkillsObject not retained in -initWithCoder:

Objective-C Memory Management Alchemistry: Code causes a leak after being put in a class

I came to Objective-C from C++ so I still sometimes has problems understanding memory management of Objective-C. I have a following problem now - XCode Analyzer tells me that object *data causes leaks further in code.
- (void)loadSettings
{
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSData *data = [[NSMutableData alloc]
initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]
initForReadingWithData:data];
// object *data is no longer referenced at this point and has a retain count of +1 (object leaked)
AppData *settingsData = [unarchiver decodeObjectForKey:kDataKey];
if (nil != settingsData)
{
customerVoiceActive = settingsData.customerVoice;
}
[unarchiver finishDecoding];
[unarchiver release];
[settingsData release];
}
}
What drives me crazy is that exactly the same code (except naming) perfectly works and causes no leaks, being placed in applicationDidFinishLaunching:
//load app data
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *data = [[NSMutableData alloc]
initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]
initForReadingWithData:data];
AppData *appData = [unarchiver decodeObjectForKey:kDataKey];
bCustomerVoice=appData.customerVoice;
[unarchiver finishDecoding];
[unarchiver release];
[data release];
}
What's the difference?
In code sample 1) you release settingsData, while in codesample 2) you are releasing data . Changing that line should solve your issue.
You forgot to release NSData *data.
Add the following line after [settingsData release];:
[data release];
The "exact same" code contains an extra line:
[data release];
settingsData should NOT be released, because it was not obtained through a method that implies that you own the returned object. However, you MUST release data, because you allocated it yourself, and this implies ownership.

Getting Image from URL Objective C

I'm trying to get an image from an URL and it doesn't seem to be working for me. Can someone point me in the right direction?
Here is my code:
NSURL *url = [NSURL URLWithString:#"http://myurl/mypic.jpg"];
NSString *newIMAGE = [[NSString alloc] initWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:nil];
cell.image = [UIImage imageNamed:newIMAGE];
When I debug the newIMAGE string is nil so something isn't working there.
What you want is to get the image data, then initialize a UIImage using that data:
NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: #"http://myurl/mypic.jpg"]];
cell.image = [UIImage imageWithData: imageData];
[imageData release];
As requested, here's an asynchronous version:
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: #"http://myurl/mypic.jpg"]];
if ( data == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
// WARNING: is the cell still using the same data by this point??
cell.image = [UIImage imageWithData: data];
});
[data release];
});
Ok there's a couple of things wrong here:
The conversion from URL (url) to NSString (newImage) is incorrect, what the code actually does there is try to load the contents of "http://myurl/mypic.jpg" into the NSString.
The -imageNamed method takes a string that is a path of a local file, not a URL as an argument.
You need to use an NSData object as an intermediary, like in this example:
http://blogs.oreilly.com/digitalmedia/2008/02/creating-an-uiimage-from-a-url.html
the accepted answer asynchronous version worked very slow in my code. an approach using NSOperation worked light years faster. the code provided by Joe Masilotti --> objective - C : Loading image from URL? (and pasted below):
-(void) someMethod {
// set placeholder image
UIImage* memberPhoto = [UIImage imageNamed:#"place_holder_image.png"];
// retrieve image for cell in using NSOperation
NSURL *url = [NSURL URLWithString:group.photo_link[indexPath.row]];
[self loadImage:url];
}
- (void)loadImage:(NSURL *)imageURL
{
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(requestRemoteImage:)
object:imageURL];
[queue addOperation:operation];
}
- (void)requestRemoteImage:(NSURL *)imageURL
{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:imageURL];
UIImage *image = [[UIImage alloc] initWithData:imageData];
[self performSelectorOnMainThread:#selector(placeImageInUI:) withObject:image waitUntilDone:YES];
}
- (void)placeImageInUI:(UIImage *)image
{
[self.memberPhotoImage setImage:image];
}
In Swift 3 and 4
let theURL = URL(string:"https://exampleURL.com")
let imagedData = NSData(contentsOf: theURL!)!
let theImage = UIImage(data: imagedData as Data)
cell.theImageView.image = theImage
This will be done in the main thread.
And to perform the same in asynchronous/background thread
DispatchQueue.main.async(){
let theURL = URL(string:"https://exampleURL.com")
let imagedData = NSData(contentsOf: theURL!)!
let theImage = UIImage(data: imagedData as Data)
}
cell.theImageView.image = theImage
Updating upon Jim dovey answer,[data release] is no longer required because in the updated apple guidelines. Memory management is done automatically by ARC (Automatic counting reference) ,
Here is the updated asynchronous call,
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: #"your_URL"]];
if ( data == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
self.your_UIimage.image = [UIImage imageWithData: data];
});
});