IOS block ui thread until JSON parse function in singleton class finishes - ios

I'm making an IOS application which should gather data on startup from a json feed. Basicaly the initial view controller is a loading screen. In the background, during that loading screen, it should instantiate my singleton class, and call a json feed to fill an array with objects. When this process is finished, it should automatically segue to my main menu.
Here's the weird thing. I've set this up and it works just fine, but only 4 out of 5 times. Sometimes it'll just go straight to the menu and load the feed in the background, which isn't what i want.
Can anyone find the culprit?
Here's my view controller for the loading screen:
#import "loadingViewController.h"
#import "Singleton.h"
#import "MBProgressHUD.h"
#interface loadingViewController ()
#end
#implementation loadingViewController
- (void)viewDidLoad {
[super viewDidLoad];
//[self performSegueWithIdentifier:#"launchSegue" sender:self];
// Do any additional setup after loading the view.
}
- (void)viewWillAppear:(BOOL)animated {
Singleton *singletonManager = [Singleton singletonManager];
[singletonManager loadJsonData];
}
- (void)viewDidAppear:(BOOL)animated {
[self performSegueWithIdentifier:#"launchSegue" sender:self];
}
- (void)doSegue
{
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
And this is my implementation in the singleton class:
implementation Singleton
#synthesize acts;
#pragma mark Singleton Methods
+ (id)singletonManager {
static Singleton *singletonMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singletonMyManager = [[self alloc] init];
});
return singletonMyManager;
}
- (id)init {
if (self = [super init]) {
//someProperty = [[NSString alloc] initWithString:#"Default Property Value"];
self.acts = [[NSMutableArray alloc] init];
}
return self;
}
- (BOOL) loadJsonData
{
NSString *urlString = #"http://wwww.paaspop.nl/json/gettimetable";
NSString *callback = #"api";
NSDictionary *parameters = #{#"callback": callback};
AFHTTPRequestOperationManager* manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy.allowInvalidCertificates = YES;
manager.responseSerializer = [CDLJSONPResponseSerializer serializerWithCallback:callback];
__block NSDictionary* response = nil;
AFHTTPRequestOperation* operation = [manager
GET: urlString
parameters: parameters
success:^(AFHTTPRequestOperation* operation, id responseObject){
response = responseObject;
[self parseJSONData:response];
}
failure:^(AFHTTPRequestOperation* operation, NSError* error){
NSLog(#"Error: %#", error);}
];
[operation waitUntilFinished];
return true;
}
-(void) parseJSONData:(id) JSON
{
NSArray *allActs = [JSON objectForKey:#"acts"];
for (NSDictionary* dict in allActs) {
//Create a pirate object where the json data can be stored
Act *act = [[Act alloc] init];
NSString *url;
//Get the JSON data from the dictionary and store it at the Pirate object
act.title = [dict objectForKey:#"title"];
act.subtitle = [dict objectForKey:#"add"];
act.jsontitle = [dict objectForKey:#"url_title"];
act.favorite = FALSE;
url = [dict objectForKey:#"photo"];
NSURL *imageURL = [NSURL URLWithString:[dict objectForKey:#"photo"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
act.photo = [self imageWithImage:[UIImage imageWithData:imageData] scaledToWidth:320];
NSLog(#"%f, %f",act.photo.size.width,act.photo.size.height);
//Add the pirates to the array
[self.acts addObject:act];
}
}
-(UIImage*)imageWithImage: (UIImage*) sourceImage scaledToWidth: (float) i_width
{
float oldWidth = sourceImage.size.width;
float scaleFactor = i_width / oldWidth;
float newHeight = sourceImage.size.height * scaleFactor;
float newWidth = oldWidth * scaleFactor;
UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight));
[sourceImage drawInRect:CGRectMake(0, 0, newWidth, newHeight)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (NSMutableArray *) getActs {
return self.acts;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}

I think you can use Notification Center for this job if you find blocks hard to work with.
You post a notification like this
[[NSNotificationCenter defaultCenter] postNotificationName:#"notificationName" object:nil];
after the for loop in your parseJSONData method.
In your main view controller you should add an observer in viewDidLoad like this
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodCalledWhenReady:)
name:#"notificationName"
object:nil];
Afterwards you just create methodCalledWhenReady in your main view controller which receives an NSNotificationObject(which you don't really need in this situation) like this
-(void) methodCalledWhenReady: (NSNotification *) notification
{
//this is where you place the code that you want to execute only after
you finished fetching the data in the singleton class, in your case:
[self performSegueWithIdentifier:#"launchSegue" sender:self];
}
You should check https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/index.html for more information regarding notifications.
Hope this helps.

There is nothing here to prevent it from going right to the new screen. Really it works 1 in 5 times and the other 4 are the oddities.
You have included MBProgressHud in your file but you are not doing anything with that. It would be better to leave the UI active then block the user from doing things and having the app appear un-responsive.
In the download class you are relying on the waitUntilFinished. This will wait for the network operation to complete and then return. This is not waiting for the json parsing and image manipulation to occur.
The AFHTTPRequest library is very outdated. It is a nice body of work but unless you have some very specific lower level needs it is far to involved. NSUrlSession is a newer tool from apple that very powerful and extremely simple to use.

I think that the best approach is to have your - (void)loadJsonData function accept a block that will be executed when the parsing operation is complete. That block can contain the code to perform a segue.

Related

Why block can't capture self?

I used a data object to store an array data and when the data load completes, I have a block callback. But the problem is that there are different instances in the two methods:
#implementation DWHomeData
- (instancetype)initWithDataLoadCompletion:(void (^)(BOOL))completion
DWHomeData *data = [DWHomeData new];
data.dwStatus = [#[] mutableCopy];
_completion = [completion copy];
[self loadStatusData];
return data;//<DWHomeData: 0x7fb481546860>
}
- (void)loadStatusData {
DWHomeParam *param = [DWHomeParam new];
[DWHomeTool fetchHomeStatusWithParam:param success:^(NSArray *statusArr) {
self.dwStatus = statusArr;//self address:<DWHomeData: 0x7fb481548b00>
_completion(YES);
} failure:^(NSError *error) {
}];
}
#end
My callback is:
- (void)viewDidLoad {
[super viewDidLoad];
_homeData = [[DWHomeData alloc] initWithDataLoadCompletion:^(BOOL success) {
[self.tableView reloadData];
}];//_homeData address:<DWHomeData: 0x7fb481546860>
}
It's because you are allocing it twice. The method new is just a wrapper for an alloc and an init.
So when you call [[DWHomeData alloc] initWith... you allocated memory for the first instance of DWHomeData.
Then, inside the initWith... method you are calling new which allocated memory for the second instance of DWHomeData and you return that second instance, but you call loadStatusData on the first instance.
The easiest solution would be to replace that new call with the standard:
self = [super init]; // no alloc
if (self) {
// initialize properties and call methods
}
return self;
Or you can do how I like to do all the time:
+ (instancetype)dataWithCompletion:(void (^)(BOOL))completion { // static method
DWHomeData *data = [DWHomeData new]; // alloc needed
if (data) {
[data loadStatusData];
}
return data;
}
and then call it without allocating:
_homeData = [DWHomeData dataWithCompletion:^(BOOL success) {
[self.tableView reloadData];
}];
so the alloc is wrapped inside the static init method and there is no need to call it outside.

Populating iOS table from Array

I have an array _locations which is populated from a server through a json query. I now want this array to populate a table that I have created, however it does not and it quits on [self.delegate itemsDownLoaded:_locations];.
If I look in the log I can see that _locations does get populated however, but it does not parse on that data.
Here is the full code for HomeModel2:
#import "HomeModel2.h"
#import "Location2.h"
#interface HomeModel2(){
NSMutableData *_downloadedData;
}
#end
#implementation HomeModel2
- (void) downLoadItems{
// Download the json file
NSURL *jsonFileUrl = [NSURL URLWithString:#"http://server.com/service_2.php"];
// Create the request
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:jsonFileUrl];
// Create the NSURLConnection
[NSURLConnection connectionWithRequest:urlRequest delegate:self];
}
#pragma mark NSURLConnectionDataProtocol Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// Initialize the data object
_downloadedData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the newly downloaded data
[_downloadedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Create an array to store the locations
NSMutableArray *_locations = [[NSMutableArray alloc] init];
// Parse the JSON that came in
NSError *error;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
// Loop through Json objects, create question objects and add them to our questions array
for (int i = 0; i < jsonArray.count; i++)
{
NSDictionary *jsonElement = jsonArray[i];
// Create a new location object and set its props to JsonElement properties
Location2 *newLocation = [[Location2 alloc] init];
newLocation.name = jsonElement[#"Name"];
newLocation.address = jsonElement[#"Address"];
newLocation.latitude = jsonElement[#"Latitude"];
newLocation.longitude = jsonElement[#"Longitude"];
// Add this question to the locations array
[_locations addObject:newLocation];
}
// Ready to notify delegate that data is ready and pass back items
if (self.delegate)
{
[self.delegate itemsDownLoaded:_locations];
}
}
#end
.. and this is the ViewController to publish the data:
#import "ViewController.h"
#import "Location2.h"
#interface ViewController (){
HomeModel2 *_homeModel;
NSArray *_feedItems;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Set this view controller object as the delegate and data source for the table view
self.listTableView.delegate = self;
self.listTableView.dataSource = self;
// Create array object and assign it to _feedItems variable
_feedItems = [[NSArray alloc] init];
// Create new HomeModel2 object and assign it to _homeModel variable
_homeModel = [[HomeModel2 alloc] init];
// Set this view controller object as the delegate for the home model object
_homeModel.delegate = self;
// Call the download items method of the home model object
[_homeModel downLoadItems];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)itemsDownLoaded:(NSArray *)items
{
// This delegate method will get called when the items are finished downloading
// Set the downloaded items to the array
_feedItems = items;
// Reload the table view
[self.listTableView reloadData];
}
#pragma mark Table View Delegate Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of feed items (initially 0)
return _feedItems.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Retrieve cell
NSString *cellIdentifier = #"BasicCell";
UITableViewCell *myCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Get the location to be shown
Location2 *item = _feedItems[indexPath.row];
// Get references to labels of cell
myCell.textLabel.text = item.address;
return myCell;
}
#end
Can you confirm that there is "location" objects after this piece of code:
// Set the downloaded items to the array
_feedItems = items;
or is the delegate method not even called?
i don't understand your wording...
I found the problem. I thick it all has to do with the delegate.self method. I read that using this in several places in your code is not the best thing and that the references easily can get messed up if you update and compile frequently. So i flushed all my old compiled data, and ran the app again from scratch and it worked!
I don't believe you ever define the delegate property, or a protocol for the delegate.
As such, you have no way of ensuring the view controller responds to the methods defined in the delegates protocol.
Can you see if the delegate method in the view controller ever gets called?
Instead of below code
if (self.delegate)
{
[self.delegate itemsDownLoaded:_locations];
}
Use
// Set the downloaded items to the array
_feedItems = [NSArray arrayWithArray:items];
// Reload the table view
[self.listTableView reloadData];
Or Change this line of code and check
Instead of
_feedItems = items;
Use
_feedItems = [NSArray arrayWithArray:items];
Hope it helps you...!

Memory continuously increase then app crash when i display image from document directory during scrolling UITableview

My Requirement is download all images in application memory and display it from local if its available.
Below is my code to access image from local and if its not available then it will download then display.
[cell.imgProfilePic processImageDataWithURLString:cData.PICTURE];
I have made custom UIImageView class
DImageView.h
#import <UIKit/UIKit.h>
#interface DImageView : UIImageView
#property (nonatomic, strong) UIActivityIndicatorView *activityView;
- (void)processImageDataWithURLString:(NSString *)urlString;
+ (UIImage *)getSavedImage :(NSString *)fileName;
#end
DImageView.m
#import "DImageView.h"
#define IMAGES_FOLDER_NAME #"DImages"
#implementation DImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{ }
return self;
}
- (void)dealloc
{
self.activityView = nil;
[super dealloc];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self initWithFrame:[self frame]];
}
return self;
}
- (void)processImageDataWithURLString:(NSString *)urlString
{
#autoreleasepool
{
UIImage * saveImg =[DImageView getSavedImage:urlString];
if (saveImg)
{
#autoreleasepool
{
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_async(callerQueue, ^{
#autoreleasepool{
[self setImage:saveImg];
}
});
}
}
else
{
[self showActivityIndicator];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
__block NSError* error = nil;
dispatch_async(downloadQueue, ^{
#autoreleasepool
{
NSData * imageData = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
if (!error)
{
dispatch_async(callerQueue, ^{
#autoreleasepool {
UIImage *image = [UIImage imageWithData:imageData];
[self setImage:image];
[self hideActivityIndicator];
[self saveImageWithFolderName:IMAGES_FOLDER_NAME AndFileName:urlString AndImage:imageData];
}
});
}
}
});
dispatch_release(downloadQueue);
}
}
}
- (void) showActivityIndicator
{
self.activityView = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
self.activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
self.activityView.hidesWhenStopped = TRUE;
self.activityView.backgroundColor = [UIColor clearColor];
self.activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self addSubview:self.activityView];
[self.activityView startAnimating];
}
- (void) hideActivityIndicator
{
CAAnimation *animation = [NSClassFromString(#"CATransition") animation];
[animation setValue:#"kCATransitionFade" forKey:#"type"];
animation.duration = 0.4;;
[self.layer addAnimation:animation forKey:nil];
[self.activityView stopAnimating];
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
}
- (void)saveImageWithFolderName:(NSString *)folderName AndFileName:(NSString *)fileName AndImage:(NSData *) imageData
{
#autoreleasepool{
NSFileManager *fileManger = [[NSFileManager defaultManager] autorelease];
NSString *directoryPath = [[NSString stringWithFormat:#"%#/%#",[DImageView applicationDocumentsDirectory],folderName] autorelease];
if (![fileManger fileExistsAtPath:directoryPath])
{
NSError *error = nil;
[fileManger createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
}
fileName = [DImageView fileNameValidate:fileName];
NSString *filePath = [[NSString stringWithFormat:#"%#/%#",directoryPath,fileName] autorelease];
BOOL isSaved = [imageData writeToFile:filePath atomically:YES];
if (!isSaved)DLog(#" ** Img Not Saved");
}
}
+ (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
+ (UIImage *)getSavedImage :(NSString *)fileName
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
fileName = [DImageView fileNameValidate:fileName];
NSFileManager * fileManger = [[NSFileManager defaultManager] autorelease];
NSString * directoryPath = [[NSString stringWithFormat:#"%#/%#",[DImageView applicationDocumentsDirectory],IMAGES_FOLDER_NAME] autorelease];
NSString * filePath = [[NSString stringWithFormat:#"%#/%#",directoryPath,fileName] autorelease];
if ([fileManger fileExistsAtPath:directoryPath])
{
UIImage *image = [[[UIImage imageWithContentsOfFile:filePath] retain]autorelease];
if (image)
return image;
else
return nil;
}
[pool release];
return nil;
}
+ (NSString*) fileNameValidate : (NSString*) name
{
name = [name stringByReplacingOccurrencesOfString:#"://" withString:#"##"];
name = [name stringByReplacingOccurrencesOfString:#"/" withString:#"#"];
name = [name stringByReplacingOccurrencesOfString:#"%20" withString:#""];
return name;
}
#end
Everything is working fine with smooth scrolling as well as asyncImage download in background.
The issue is when i scroll UITableview application memory is continuously increase and after some time i got Receive memory waring 2/3 time then application crash.
When i use AsyncImageView class that time memory not increase and its working fine. But due to app requirement i saved all images to Document Directory and display from it if its available.
i have tried with #autoreleasepool and release some variable but not getting success.
I appreciated if any one have the solution to manage memory management.
**ARC is off in my application.**
It's possible that UIImagePNGRepresentation returns non-autoreleased object - you can try to release it and see if that results in a crash. Obviously you are not releasing something, but nothing other than the image representation appears obvious.
A few other comments:
run your app in Instruments, using the ObjectAlloc tool, and it should be immediately obvious what objects are not dealloced. If you don't know Instruments, well, its time now to learn it.
you can 'track' objects and get a message when they are dealloced using ObjectTracker - however it was designed for ARC so you may need to tweak it. If you use it you would see a message when each of your objects are dealloced
when the table view is done with a cell, there is a delegate method that you can receive that tells you so, and you can then nil (release) and objects the cell retains
your use of downloadQueue is bizarre - create it once in your instance as an ivar, use it as you need, and in dealloc release it
you hide the activity spinner on the main queue, but don't start it on the main queue
you command the activity view to remove itself from its superview, but then look for in in the subviews and try to remove it there:
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
In the end, Instruments is what you want. You can read up more about it here, or just google and you will surely find a slew of blogs to read.
Yes Finally i have resolved it.
The code which is in Question is working fine now. but Without release some objects and #autoreleasepool block which is in code, memory was increase continuously during scroll UITableView.
From the Instrument i found that memory increase in UILableView and UIImageView. I am using Custom UITableViewCell and in that file i havnt implement dealloc method. So When i have implement dealloc method in UITableViewCell .m file and release & nil all object.
After that memory not increase during scroll TableView and its Resolved the issue.
As per my Understanding there is an issue in your "getSavedImage" Method you have to manage memory Manually instead of 'autorelease' so as My suggestion is use
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath]
and also release it after use of it. means after '[self setImage:saveImg];'
[saveImg release]
instead of this.
[[UIImage imageWithContentsOfFile:filePath] retain];
'Don't Use Autorelease because it has staying in memory until pool not drain' and just because of this you got an memory issue.

How to unarchive BOOL property with NSKeyedUnarchiver?

I have class for playing sound in app.
I implemented on/off switch(on GUI), for disabling and enablining sound play.
I am using BOOL property for that and this is working.
Now I am trying to implement saving that BOOL (is sound on/off) in file so that next time when app is started state is automatically restored.
For that I am using NSCoding protocol, archiving is working but I have problem with unarchiving.
My app will not start it will just show black screen.
This is my code, only part that I think it is important.
GTN_Sound.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h> // for playing sound
#interface GTN_Sound : NSObject <NSCoding>
#property(nonatomic, readwrite, unsafe_unretained) BOOL isSoundOn;
+ (id)sharedManager;
- (void)playWinSound;
- (void)playLoseSound;
#end
GTN_Sound.m
#pragma mark - NSCoding Methods
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.isSoundOn forKey:#"isSoundOn"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [GTN_Sound sharedManager];
if (self) {
_isSoundOn = [aDecoder decodeBoolForKey:#"isSoundOn"];
}
return self;
}
I think that code is so far so good ?
Continuation of GTN_Sound.m
#pragma mark - itemArchivePath Method
- (NSString *)itemArchivePath
{
// Make sure that the first argument is NSDocumentDirectory
// and not NSDocumentationDirectory
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
// Get the one document directory from that list
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingPathComponent:#"sound.archive"];
}
#pragma mark - custom seter Method
- (void)setIsSoundOn:(BOOL)theBoolean {
NSLog(#"My custom setter\n");
if(_isSoundOn != theBoolean){
_isSoundOn = theBoolean;
NSString *path = [self itemArchivePath];
[NSKeyedArchiver archiveRootObject:self toFile:path]; // this is doing save
}
}
It is done that for every time when switch on GUI is changed I do the savings.
This look fine from my side, because I am not expecting that user will change this many times.
Now the unarchiving comes and I thin that here are some problems.
#pragma mark - Singleton Methods
+ (id)sharedManager {
static GTN_Sound *sharedMyManager = nil;
// to be thread safe
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initPrivate];
});
return sharedMyManager;
}
- (instancetype)init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"Use +[GTN_Sound sharedManager]"
userInfo:nil];
return nil;
}
// Here is the real (secret) initializer
- (instancetype)initPrivate
{
self = [super init];
if (self) {
NSString *path = [self itemArchivePath]; // do as iVar, for futture
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
//_isSoundOn = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
}
return self;
}
I think that problem is in this line
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
But have no idea how to fix it.
I have read that when I am doing archiving, that I need to archive all properties of object.
Does this apply to private ivars also ?
Any help is appreciated.
Thanks
You can only archive NSObject inheriting objects. ie. NSNumber
[NSKeyedArchiver archiveRootObject:#YES toFile:path]; //to save
_isSoundOn = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] boolValue]; //to load

Need to capture screen without delay

So guys I'm using this function to take a picture of the UIWebView which loading an e-book and display it while the user render the pages
-(UIImage*)captureScreen:(UIView*) viewToCapture
{
UIGraphicsBeginImageContextWithOptions(viewToCapture.bounds.size, viewToCapture.opaque, 0.0);
[viewToCapture.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return viewImage;
}
but the problem there is a delay (0.5 second) happened to get the image .. when i test in the time profiler the instruments point to this line [viewToCapture.layer renderInContext:UIGraphicsGetCurrentContext()]; as the one which causing the delay .. so any advice , suggestion how could I come over this delay.
Thanks in advanced.
Notes :
1.Im using UIPageController for the book effect.
2.to check what I have tried please check this link
The best way of solving this is to use delegates. Meaning that if you tell a delegate method to make the image, it can do it in the background and tell your view "Hey, now I got you an image for XXX" (depending on how you implement it.
This way you can load your view with the e-books in it and just show a loader in the middle of the book with a default background. When the image is done, you update the view for the book with the correct image and remove the loader.
Much like how Apple's iBooks and any other good application does.
Example from one of my own projects (adapted to your need, yet for use in UITableViewController):
BookDelegate.h
#import <Foundation/Foundation.h>
#class Book;
#protocol BookDelegate <NSObject>
#optional
- (void)didRecieveImageForBook:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath;
#end
Book.h
#import <Foundation/Foundation.h>
#import "BookDelegate.h"
#interface Book : NSOperation <NSObject>
{
id <BookDelegate> delegate;
SEL didRecieveImageForBookSelector;
}
#property (strong, nonatomic) id delegate;
#property (assign) SEL didRecieveImageForBookSelector;
- (NSString*)getBookImageForBookId:(int)BookId externalRefference:(NSString*)url indexPath:(NSIndexPath*)indexPath;
- (id)delegate;
// Delegate methods
- (void)didRecieveImageForBook:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath;
#end
Book.m
#import "Book.h"
#import <objc/runtime.h>
#implementation Book
static char kAssociationKey;
#synthesize didRecieveImageForBookSelector;
#synthesize delegate;
- (id)init
{
if (self = [super init])
{
[self setDidRecieveImageForBookSelector:#selector(didRecieveImageForBook:indexPath:)];
}
return self;
}
#pragma mark -
#pragma mark The default delegate functions
- (void)didRecieveImageForBook:(NSString*)imagePath indexPath:(NSIndexPath*)indexPath
{
NSLog(#"********************************************************");
NSLog(#"*** PLEASE IMPLEMENT THE FOLLOWING DELEGATE FUNCTION ***");
NSLog(#"*** didRecieveImageForBook:indexPath: ***");
NSLog(#"********************************************************");
}
#pragma mark -
#pragma mark Function for fechting images
// This method is not adapted to what YOU need, but left my code here in case it might help you out.
- (NSString*)getBookImageForBookId:(int)bookId externalRefference:(NSString*)url indexPath:(NSIndexPath*)indexPath
{
NSString *ext = [[url lastPathComponent] pathExtension];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *imagePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#%d.%#", APP_BookIMAGEPEFIX, BookId, ext]];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:imagePath];
if (fileExists)
{
return imagePath;
}
else {
NSURL *theUrl = [NSURL URLWithString:url];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:theUrl];
[request setDidFinishSelector:#selector(BookImageFetched:)];
[request setDidFailSelector:#selector(processFailed:)];
[request setTimeOutSeconds:60];
[request setDownloadDestinationPath:imagePath];
[request setDelegate:self];
[request startAsynchronous];
objc_setAssociatedObject(request, &kAssociationKey, indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return #"";
}
}
- (void)BookImageFetched:(ASIHTTPRequest *)request
{
NSIndexPath *indexPath = objc_getAssociatedObject(request, &kAssociationKey);
NSString *imagePath = request.downloadDestinationPath;
[[self delegate] performSelector:self.didRecieveImageForBookSelector withObject:imagePath withObject:indexPath];
}
#pragma mark -
#pragma mark delegate functions
- (id)delegate
{
return delegate;
}
- (void)setDelegate:(id)newDelegate
{
delegate = newDelegate;
}
#pragma mark -
#end
You could use GCD - I noticed a a step missing from the GCD in the link you gave. This is what I use to asynchronously get an image and notify when its ready and it works fine:
dispatch_queue_t concurrent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(concurrent, ^{
__block UIImage *image = nil;
dispatch_sync(concurrent, ^{
//put code to grab image here
});
dispatch_sync(dispatch_get_main_queue(), ^{
//this gets called when the above is finshed
//you should also check if the image is nil or not
});
});
hope it helps
for the record - I use this for taking UIView snapshots and always try to put my target inside a parentview even if it is temporarily - it seems to speed it up.
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
not sure if it helps or if you are using the same approach. I hope you solve this soon :)

Resources