Random EXC_BAD_ACCESS (code=1, address=0x4) when multithreading on iOS - ios

I have a case, when I get EXC_BAD_ACCESS (code=1, address=0x4) exception randomly, when calling method with multi threads. Same method, sometime error accure, sometimes don't. But my measurements show that at least 1 out of 5 runs will produce this error.
Firstly let me show a diagram how my code is organized, so it will be easier to read the code.
I have one View controller that handle the view and call server class (instance stored in AppDelegate class) for data. Data there is retrieved from server asynchronously and store in Core data DB and call CompleteHandler when it is done.
In between, delegate method in server is called to update progress percentage in ViewControllers.
After this is done, ViewController call build Model class that is build asynchronously, and read data from Core data DB.
In this step, when model is called to build, error occurs.
Second problem is that even that delegate method is called and right percentage is send to ViewController, progress bar is not updated.
This, and fact that error come randomly, let me believe that it has to be something wrong with threading and that some object is released to soon.
I try debug with Zombies, but nothing shows up.
CODE :
ViewController
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// THIS IS NOTIFICATIONS FROM MODEL
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(modelProgressUpdate:) name:ProcessUpdateNotification object:nil];
self.coreDataQueue = dispatch_queue_create("com.my.core.data", NULL);
[self download1];
}
#pragma mark - Progres bar
// METHOD THAT IS CALLED AFTER NSNOTIFICATION IS CALLED
- (void) modelProgressUpdate:(NSNotification * ) notification{
BINotificationsProcessUpdate * update = (BINotificationsProcessUpdate * )notification.object;
[self updateProgressWithPercent:update.percent];
}
- (void) modelPrograssCancel
{
[self.progressViewContainer layoutSubviews];
[UIView animateWithDuration:0.5 animations:^{
self.ProgressViewTopConstraint.constant = -15;
[self.progressViewContainer layoutSubviews];
[self.view layoutSubviews];
}];
self.ProgressView.progress = 0;
}
// METHODS THAT ARE CALLED WITH SERVER DELEGATES
- (void) cancelProgresView
{
[self modelPrograssCancel];
}
- (void) progressInProcentage1:(float)procentage{
__weak ViewController * weekSelf = self;
dispatch_async(dispatch_get_main_queue(), ^(void) {
[weekSelf updateProgressWithPercent:procentage];
});
}
- (void) progressInProcentage2:(float)procentage{
__weak ViewController * weekSelf = self;
dispatch_async(dispatch_get_main_queue(), ^(void) {
[weekSelf updateProgressWithPercent:procentage];
});
}
- (void) updateProgressWithPercent: (float) percent
{
if (self.ProgressViewTopConstraint.constant != 0) {
[self.progressViewContainer layoutSubviews];
[UIView animateWithDuration:0.5 animations:^{
self.ProgressViewTopConstraint.constant = 0;
[self.progressViewContainer layoutSubviews];
[self.view layoutSubviews];
}];
}
self.ProgressView.progress = percent;
}
#pragma mark - download data
-(void)download1{
__weak ViewController * weekSelf = self;
// build new NSManagedObjectContext
weekSelf.backgroundManagedObjectContext = [[NSManagedObjectContext alloc]init];
[weekSelf.backgroundManagedObjectContext setPersistentStoreCoordinator:[[ManagedContextHelper getManagedObjectContext] persistentStoreCoordinator]];
[[NSNotificationCenter defaultCenter] addObserver:weekSelf selector:#selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:weekSelf.backgroundManagedObjectContext];
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.serverConnection callGet1:weekSelf.server managedObjectContext:weekSelf.backgroundManagedObjectContext delegate:weekSelf withCompletionHandler:^(BIResponseObject *response) {
switch (response.responseType) {
case BIResponseTypeNoInternet:
case BIResponseTypeConnectionTimeOut:
[weekSelf cancelProgresView];
break;
case BIResponseTypeOK:
[weekSelf download2];
break;
default:
[weekSelf loadModel];
[weekSelf noServerWarrning];
break;
}
}];
}
-(void)download2{
__weak ViewController * weekSelf = self;
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.serverConnection callGet2:weekSelf.server managedObjectContext:weekSelf.backgroundManagedObjectContext delegate:weekSelf withCompletionHandler:^(BIResponseObject *response) {
switch (response.responseType) {
case BIResponseTypeFactTableOK: {
weekSelf.object.downlodedDate = [NSDate date];
[weekSelf.object save:weekSelf.backgroundManagedObjectContext];
[weekSelf loadModel];
[[ManagedContextHelper getManagedObjectContext] save:nil];
}
case BIResponseTypeConnectionTimeOut:
case BIResponseTypeNoInternet:
case BIResponseTypeFactTableError:
[weekSelf cancelProgresView];
break;
default:
[weekSelf cancelProgresView];
break;
}
}];
}
#pragma mark - LOAD MODEL
-(void)loadModel{
__weak ViewController * weekSelf = self;
dispatch_async(weekSelf.coreDataQueue, ^(void){
// build new NSManagedObjectContext
if (!weekSelf.backgroundManagedObjectContext) {
weekSelf.backgroundManagedObjectContext = [[NSManagedObjectContext alloc]init];
[weekSelf.backgroundManagedObjectContext setPersistentStoreCoordinator:[[ManagedContextHelper getManagedObjectContext] persistentStoreCoordinator]];
[[NSNotificationCenter defaultCenter] addObserver:weekSelf selector:#selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:weekSelf.backgroundManagedObjectContext];
}
MyUIObject * uiObject = [DataBase getUIObject:weekSelf.backgroundManagedObjectContext];
weekSelf.myObject = [[UIScrollObject alloc] initWith: uiObject];
weekSelf.myObject.delegate = weekSelf;
if (uiObject && weekSelf.myObject) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
self.myObject.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.myObject];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_myObject]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(_myObject)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(66)-[_myObject]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(_myObject)]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.myObject attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
[self.view sendSubviewToBack:self.myObject];
// Add model
[self.myObject createModel]; // THIS IS A LINE THAT I THINK IT BREAK,... logs are till here.
});
}
else {
[weekSelf dissmisView:weekSelf];
}
});
}
- (void) dissmisView: (ViewController * ) selfReference {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[selfReference cancelProgresView];
if (![selfReference.presentedViewController isBeingDismissed]) {
[selfReference dismissViewControllerAnimated:YES completion:nil];
}
});
}
#pragma mark - handling multi ManagedObjectContext
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we're on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:#selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
[[ManagedContextHelper getManagedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
...
#end
MODEL created
dispatch_async(self.model_queue, ^(void){
// Create Model in new thread
[self createPrimaryModel_newThread]; // inside here notifications are send to update progress bar.
/// Model is complete
[BINotifications notifyModelIsCompletedCreating:self];
});
BINotifications
+ (void) notifyProgressUpdateWithPercent: (float) percent andNotificationString: (NSString * ) notificationString {
dispatch_async(dispatch_get_main_queue(), ^(void) {
[[NSNotificationCenter defaultCenter] postNotificationName:ProcessUpdateNotification
object:[[BINotificationsProcessUpdate alloc]
initWithPercent:percent
description:notificationString]];
});
}
Server class
-(void)callGet2: (Server *) server managedObjectContext: (NSManagedObjectContext *)managedObjectContext delegate: (id) delegate withCompletionHandler:(void (^)(BIResponseObject * response))callback
{
id<BIServerConnectionDelegate> _delegate = delegate;
[_delegate progressInProcentage2:0.0];
NSString * method = [NSString stringWithFormat:#".../%i", ...];
[self callServerWithMethod:method server:server withCallBack:^(BIResponseObject *response) {
[_delegate progressInProcentage2:0.3];
// parse
response.responseType = [self parse2:response server:server managedObjectContext:managedObjectContext delegate:_delegate];
[self setResponseTitleAndMessage:response server:server];
callback(response);
}];
}
I know it is a lot, but I am really stuck with it.
Code is "cleaned", so name of functions,... are changed (I can't reveal real code.), so I know the names are not ok :D, but code is real.

Related

iOS: How to check if a variable has changed his value in another ViewController in a dispatch_async?

I would like to know how to check if a value changed in another ViewController that is running in background. I have that code in that Viewcontroller that is downloading in background, and it puts me the isSyncAutoOnGoing to NO when the download is finished.
ViewController.m
#interface ViewController ()
{
BOOL isSyncAutoOnGoing;
}
- (void) sync:(id) sender
{
isSyncAutoOnGoing = YES;
dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NUL
L);
dispatch_async(downloadQueue, ^{
// do our long running process here
ListOfValueSync * lovSync = [[ListOfValueSync alloc] init];
// Synchronization
BOOL ret = [lovSync getAllListOfValueAll];
// do any UI stuff on the main UI thread
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
[spinner removeFromSuperview];
isSyncAutoOnGoing = NO;
NSLog(isSyncAutoOnGoing ? #"sync Yes" : #"sync No");
if ([sender isKindOfClass:[UIButton class]])
{
self.loginField.text = #"";
(!ret) ? [self alertStatus:#"Can not synchronize" :#"Sync Failed!" :0] : [self alertStatus:#"Synchronization OK" :#"Sync OK" :0];
[sender setEnabled:TRUE];
}
});
});
}
}
Here is the FormViewController where I want to check when the isSyncAutoOnGoing is equal to NO. I know it is not the good way I think, and more, the value of isSyncAutoOnGoing is always equal to YES.
FormViewController.m
NSArray *viewControllers = [[self navigationController] viewControllers];
id obj;
for (int i = 0; i < [viewControllers count]; i ++)
{
obj = [viewControllers objectAtIndex:i];
if ([obj isKindOfClass:NSClassFromString(#"ViewController")])
{
BOOL isSyncAutoOnGoing = [[obj valueForKey:#"isSyncAutoOnGoing"] boolValue];
if (isSyncAutoOnGoing)
{
do{
[NSThread sleepForTimeInterval:1.0];
NSLog([[obj valueForKey:#"isSyncAutoOnGoing"] boolValue] ? #"isSyncAuto: YES" : #"isSyncAuto: NO");
}while([[obj valueForKey:#"isSyncAutoOnGoing"] boolValue]);
}
}
}
Any ideas?
Thanks in advance.
Suppose the array is in AppDelegate
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
self.allNewsItems = appDelegate.allNewsItems;
[appDelegate addObserver:self
forKeyPath:#"allNewsItems"
options:NSKeyValueObservingOptionNew
context:NULL];
and implement
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"allNewsItems"]){ if ([self isBeingPresented]){
[self.tableView reloadData]; }else{
self.mustReloadView = YES;
}
} }

Waiting for Network Connection To Complete before proceeding with Func?

I have a resume function that reestablishes a network connection before it gathers updated parameters for the app.
Issue is that it seems to be trying to get some parameters before the network link has been reestablished.
Is there a way i can pause until the connection is made?
The getParameters func is on a background thread using:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) { //code }
The initConnection is primary thread
Here is the func:
- (void)screenUnlocked {
[self initConnection];
CDCChannelCollectionView *scrollView;
if ([self channelView]) {
scrollView = [[self channelView] scrollView];
for (CDCChannelStrip* strip in [scrollView visibleCells]) {
[self getParameters:[NSNumber numberWithInteger:strip.channelNumber]];
}
}
}
edit:
Something like this?
- (void)initConnection: completion:(void (^)(void))completionBlock {
if (![self isDemo]) {
[self setTcpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:TCP]];
[[self tcpControl] setDelegate:self];
[self setUdpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:UDP]];
[[self udpControl] setDelegate:self];
if (successful) {
completionBlock();
}
}
}
For block syntax you can follow:
//Block funtion with void block return type.
- (void)initConnection:(void(^)(void))completionBlock {
if (![self isDemo]) {
[self setTcpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:TCP]];
[[self tcpControl] setDelegate:self];
[self setUdpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:UDP]];
[[self udpControl] setDelegate:self];
if (successful) {
//call completion when connection is made.
completionBlock();
}
}
}
calling function as:
//Calling initConnection funtion.
[self initConnection:^{
NSLog(#"complition success");
}];

Multipeer Connectivity kill session

I'm using a host/client approach to using MultiPeer Connectivity.
So, when a user his the disconnect button
-(IBAction)disconnect:(id)sender {
[_appDelegate.mcManager.session disconnect];
[_arrConnectedDevices removeAllObjects];
ConnectionsViewController *game = [self.storyboard instantiateViewControllerWithIdentifier:#"ConnectionsViewController"];
[self presentViewController:game animated:YES completion:nil];
}
Now, this works fine. From the hosts point of view it receives a disconnect message in the log. and the client moves to the new view controller. And the table is updated. with this.
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
if (_makeSureImHost) {
[_arrConnectedDevices addObject:peerDisplayName];
[_tblConnectedDevices performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
else {
[self sendMessageToHostWithMessage:#"deviceInfo"];
}
}
else if (state == MCSessionStateNotConnected){
if ([_arrConnectedDevices count] > 0) {
int indexOfPeer = (int)[_arrConnectedDevices indexOfObject:peerDisplayName];
[_arrConnectedDevices removeObjectAtIndex:indexOfPeer];
NSLog(#"%# Disconnected", peerDisplayName);
[_tblConnectedDevices performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
_tblConnectedDevices.frame = CGRectMake(_backgroundImage.frame.size.width / 2 - 150, self.backgroundImage.frame.size.height / 3, 300, 150);
}
}
}
}
END OF LOBBY VIEW CONTROLLER
START OF CONNECTION VIEW CONTROLLER
When a client presses to browse for local devices this runs
- (IBAction)browseForDevices:(id)sender {
[UIView animateWithDuration:0.5f
animations:^{
_searchButton.frame = CGRectMake(-100, self.backgroundImage.frame.size.height/2 + 60, 100, 35.0);
_hostButton.alpha = 0;
_modeLabel.alpha = 0;
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[_appDelegate mcManager] setupPeerAndSessionWithDisplayName:[UIDevice currentDevice].name];
[[_appDelegate mcManager] advertiseSelf:false];
[[_appDelegate mcManager] setupMCBrowser];
[[[_appDelegate mcManager] browser] setDelegate:self];
_appDelegate.mcManager.browser.maximumNumberOfPeers = 1;
_appDelegate.mcManager.browser.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentViewController:[[_appDelegate mcManager] browser] animated:YES completion:nil];
});
}
When a connection is established
-(void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController {
[_appDelegate.mcManager.browser dismissViewControllerAnimated:NO completion:^{
[self launchViewController];
}];
}
-(void)launchViewController {
LobbyViewController *lobby = [self.storyboard instantiateViewControllerWithIdentifier:#"LobbyViewController"];
[self presentViewController:lobby animated:NO completion:nil];
}
From this
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification {
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
[self browserViewControllerDidFinish:[[_appDelegate mcManager] browser]];
}
}
else if (state == MCSessionStateNotConnected){
if ([_arrConnectedDevices count] > 0) {
int indexOfPeer = (int)[_arrConnectedDevices indexOfObject:peerDisplayName];
[_arrConnectedDevices removeObjectAtIndex:indexOfPeer];
}
}
}
Now. When a connection is made for the first time. This all works flawlessly. It connects, loads the view, host starts the game, game works fine and data is transferred perfectly.
However, if you disconnect from the lobby. Get moved to the connection viewcontroller then browse for devices again. It will connect, however the lobby viewcontroller will NOT be in the view hierarchy and will close the browser and stay in the connection view controller.
Then, to top it off, the connection has been made. yet, when it receives a message from the host, it will send the response, twice.. or three times, or four, leading me to a dead end. The only thing I can presume is that the previous session is being remembered somehow from the "clients" point of view.
Now, steps I can take to avoid this mess. If I kill the app and relaunch it I can now connect again from the clients point of view. Which leads me to believe, the problem is on the clients end.
My problem is that I have to absolutely sort this out. So a disconnect will fully remove everything from the session. So they can reconnect again. And cannot rely on a message to tell the user to restart their application. It just cannot be.
Here's my entire MCManager.m file.
#implementation MCManager
-(id)init{
self = [super init];
if (self) {
_peerID = nil;
_session = nil;
_browser = nil;
_advertiser = nil;
}
return self;
}
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{
_peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
_session = [[MCSession alloc] initWithPeer:_peerID];
_session.delegate = self;
}
-(void)setupMCBrowser{
_browser = [[MCBrowserViewController alloc] initWithServiceType:#"chat-files" session:_session];
}
-(void)advertiseSelf:(BOOL)shouldAdvertise{
if (shouldAdvertise) {
_advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:#"chat-files"
discoveryInfo:nil
session:_session];
[_advertiser start];
}
else{
[_advertiser stop];
_advertiser = nil;
}
}
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
NSDictionary *dict = #{#"peerID": peerID,
#"state" : [NSNumber numberWithInt:state]
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"MCDidChangeStateNotification"
object:nil
userInfo:dict];
}
-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSDictionary *dict = #{#"data": data,
#"peerID": peerID
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"MCDidReceiveDataNotification"
object:nil
userInfo:dict];
}
-(void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress{
}
-(void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error{
}
-(void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID{
}
#end
#import <MultipeerConnectivity/MultipeerConnectivity.h>
#interface MCManager : NSObject <MCSessionDelegate>
#property (nonatomic, strong) MCPeerID *peerID;
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCBrowserViewController *browser;
#property (nonatomic, strong) MCAdvertiserAssistant *advertiser;
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName;
-(void)setupMCBrowser;
-(void)advertiseSelf:(BOOL)shouldAdvertise;
#end
If anyone knows what I'm doing wrong I'd much appreciate it. This is driving me nuts.
[[NSNotificationCenter defaultCenter] removeObserver:name:object:];
Has fixed all my problems. Hopefully helps some other people too.
With Swift, I end the multipeer session as follows (after completing checking the deinit of session tracker):
func stopSession() {
self.serviceBrowser.stopBrowsingForPeers()
self.serviceBrowser.delegate = nil
self.serviceAdvertiser.stopAdvertisingPeer()
self.serviceAdvertiser.delegate = nil
self.session.disconnect()
self.peerSessionIDs.removeAll()
self.session.delegate = nil
self.session = nil
self.multipeerConnectivityService = nil
self.serviceType = nil
}
In other words, everything that was registered and the initializer is de-initialized. I am doing this in reverse order, but I'm not sure if order is important here.

Return an array after a completion block has finished

I have the following function
- (NSArray*) calendarMonthView:(TKCalendarMonthView*)monthView marksFromDate:(NSDate*)startDate toDate:(NSDate*)lastDate{
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
_currentStart = startDate;
_currentEnd = lastDate;
if(appDelegate.internetActive){
Webservice *web = [[Webservice alloc]init];
[web fetchAppointmentsOnCompletionFor:startDate andEnd:lastDate OnCompletion:^(BOOL finished) {
if(finished){
[self generateRandomDataForStartDate:startDate endDate:lastDate];
// NOW return the self.dataArray
}
}];
}
return self.dataArray;
}
I can't figure out how I can return the self.dataArray when the completionblock has finished. Because my self.dataArray is filled inside the method generateRandomDataForStartDate:startDate . So at the moment the function always returns an empty array.
You should pass the completion handler block inside the argument. Make the return type to void.
Caller object will write below code:
[calenderView calendarMonthView:monthView marksFromDate:startDate toDate:lastDate completionHandler:^(NSarray *dataArray){
//Process data array over here
}];
- (void) calendarMonthView:(TKCalendarMonthView*)monthView marksFromDate:(NSDate*)startDate toDate:(NSDate*)lastDate completionHandler:(void (^)(NSArray*))completionBlock{
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
_currentStart = startDate;
_currentEnd = lastDate;
if(appDelegate.internetActive){
Webservice *web = [[Webservice alloc]init];
[web fetchAppointmentsOnCompletionFor:startDate andEnd:lastDate OnCompletion:^(BOOL finished) {
if(finished){
[self generateRandomDataForStartDate:startDate endDate:lastDate];
completionBlock(self.dataArray);
}
}];
}
completionBlock(self.dataArray);
}
In the caller code handle completion block with response array received as argumnet.
You don't need to return an array from this method, As you mentioned that your dataArray is filling under the generateRandomDataForStartDate:startDate Method, So you can process the filled array from that method, So your code will be,
- (void) calendarMonthView:(TKCalendarMonthView*)monthView marksFromDate:(NSDate*)startDate toDate:(NSDate*)lastDate{
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
_currentStart = startDate;
_currentEnd = lastDate;
if(appDelegate.internetActive){
Webservice *web = [[Webservice alloc]init];
[web fetchAppointmentsOnCompletionFor:startDate andEnd:lastDate OnCompletion:^(BOOL finished) {
if(finished){
[self generateRandomDataForStartDate:startDate endDate:lastDate];
// NOW return the self.dataArray
}
}];
}
}
And your method , which is populating the array, should return the modified array,
-(NSArray *)generateRandomDataForStartDate:(NSString *)startDate endDate:(NSString *)endDate {
// Your code here to populate and filling array
return self.dataArray;
}

MBProgressHUD not resetting

I'm using MBProgressHUD in our teaching App, which is tab bar navigated.
The user will come from a tableview via Urban Airship's storefront directly to the UA detail view.
Once buy is clicked I bring on HUD with
HUD = [[MBProgressHUD alloc] initWithWindow:[UIApplication sharedApplication].keyWindow];
[self.view.window addSubview:HUD];
It is using the showWhileExecuting statement.
It goes through three while statements to change from "Connecting" to "Downloading" to "Unpacking".
All working perfectly OK.
Here comes the problem...
The second time I do this the label text will not change. It is stuck on "Connecting".
I can see in the NSLog that it is going through the other loops.
On top of that, if I try to change the Mode, the app crashes.
This only happens the second time, and any subsequent uses. If I kill the App everything works again for the first time.
Looks to me that MBProgressHUD doesn't get reset when it's finished.
(ARC is used in the project)
Anyone with a solution?
Thanks
Edit:
- (void)showWithLabelDeterminate
{
HUD = [[MBProgressHUD alloc] initWithWindow:[UIApplication sharedApplication].keyWindow];
HUD.mode = MBProgressHUDModeIndeterminate;
[self.view.window addSubview:HUD];
HUD.delegate = self;
HUD.labelText = NSLocalizedString(#"Connecting","");
HUD.detailsLabelText = #" ";
HUD.minSize = CGSizeMake(145.f, 145.f);
HUD.dimBackground = YES;
[HUD showWhileExecuting:#selector(lessonDownloadProgress) onTarget:self withObject:nil animated:YES];
}
-(void)lessonDownloadProgress
{
DataManager *sharedManager = [DataManager sharedManager];
// HUD.mode = MBProgressHUDModeIndeterminate;
HUD.labelText = nil;
HUD.detailsLabelText = nil;
while ([sharedManager.downHUD floatValue] == 0.0f)
{
[self parentViewController];
NSLog(#"HUD lessonDownloadProgress: %f", HUD.progress);
HUD.labelText = NSLocalizedString(#"Connecting","");
HUD.detailsLabelText = #" ";
NSLog(#"Waiting for download to start");
// Wait for download to start
usleep(80000);
}
// Switch to determinate mode
// HUD.mode = MBProgressHUDModeDeterminate;
HUD.labelText = NSLocalizedString(#"DownLoading","");
HUD.progress = [sharedManager.downHUD floatValue];
while (HUD.progress < 1.0f && [sharedManager.cleanedUp isEqualToString:#"No"])
{
// [self parentViewController];
HUD.labelText = NSLocalizedString(#"Downloading","");
NSLog(#"HUD lessonDownloadProgress: %f", HUD.progress);
HUD.progress = [sharedManager.downHUD floatValue];
NSString *percent = [NSString stringWithFormat:#"%.0f", HUD.progress/1*100];
HUD.detailsLabelText = [percent stringByAppendingString:#"%"];
usleep(50000);
}
// Switch HUD while cleanUp
HUD.mode = MBProgressHUDModeIndeterminate;
while ([sharedManager.cleanedUp isEqualToString:#"No"])
{
[self parentViewController];
HUD.labelText = NSLocalizedString(#"Unpacking","");
HUD.detailsLabelText = #" ";
// wait for cleanup
NSLog(#"Waiting for clean up");
usleep(50000);
}
NSLog(#"++ Finished loops ++");
NSLog(#"Finished HUD lessonDownloadProgress: %f", HUD.progress);
[MBProgressHUD hideHUDForView:self.view animated:YES];
[HUD removeFromSuperview];
HUD.delegate = nil;
[HUD release];
HUD = nil;
}
I cannot spot the issue in the code you posted; but some refactoring might help.
Rather than polling the DataManager you could use KVO to observe properties on the DataManager and respond to those changes. ("Don't call us; we'll call you.) So here's a suggested approach if you want.
Your class interface:
#interface YourClass : UIViewController // or whatever your superclass is...
{
MBProgressHUD *_hud;
DataManager *_dataManager;
// your other ivars
}
#end
And in your implementation file...
#interface YourClass()
#property (nonatomic, retain) DataManager dataManager;
#end
Above I've declared your dataManager as a property so that we can observe it.
To start the download process we now have a method downloadLesson:
- (void)downloadLesson;
{
// show HUD and retain it (showHUDAddedTo:animated: returns autoreleased object)
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES] retain];
// observe properties on the dataManager
[self addObserver:self forKeyPath:#"dataManager.progress" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self forKeyPath:#"dataManager.cleanedUp" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self forKeyPath:#"dataManager.downHUD" options:NSKeyValueObservingOptionNew context:nil];
// begin your download here...
HUD.labelText = NSLocalizedString(#"Connecting", "");
HUD.detailsLabelText = #" ";
HUD.progress = self.dataManager.downHUD;
}
Now use KVO to update the appearance of the HUD:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
if( [keyPath isEqualToString:#"dataManager.cleanedUp"] )
{
if( [[[self dataManager] cleanedUp] isEqualToString:#"Yes"] )
{
[MBProgressHUD hideHUDForView:[[UIApplication sharedApplication] keyWindow] animated:YES];
[HUD release]; HUD = nil;
[self removeObserver:self forKeyPath:#"dataManager.progress"];
[self removeObserver:self forKeyPath:#"dataManager.cleanedUp"];
[self removeObserver:self forKeyPath:#"dataManager.downHUD"];
}
}
if( [keyPath isEqualToString:#"dataManager.downHUD"] )
{
// if the data manager updates progress, update our HUD
HUD.progress = self.dataManager.downHUD;
if( self.dataManager.downHUD == 0.0 )
// no progress; we're just connecting
HUD.labelText = NSLocalizedString(#"Connecting", "");
else if( self.dataManager.downHUD < 1.0 )
{
// progress >0.0 and < 1.0; we're downloading
HUD.labelText = NSLocalizedString(#"Downloading", "");
NSString *percent = [NSString stringWithFormat:#"%.0f%%", HUD.progress/1*100];
HUD.detailsLabelText = percent;
}
else
{
// progress == 1.0, but we haven't cleaned up, so unpacking
if( [[[self dataManager] cleanedUp] isEqualToString:#"No"] )
{
HUD.labelText = NSLocalizedString(#"Unpacking","");
HUD.detailLabelsText = #" ";
}
}
}
}
Alternatively, you could use notifications to do the updates, wherein the DataManager posts NSNotifications for which your view controller is registered. Or, if you were open to refactoring the DataManager you could use blocks to do the updates. All of these solutions avoid having to explicitly block your thread to poll the DataManager. Hope this helps.
Did you implement the delegate method?
- (void)hudWasHidden:(MBProgressHUD *)hud {
// Remove HUD from screen when the HUD was hidded
[HUD removeFromSuperview];
HUD = nil;
}
This is because the label is set up on init.Try this:
You should just add this method to the header file of MBProgressHud:
+ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view withText:(NSString *)text;
And implement it in the .m file as follows:
+ (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view withText:(NSString *)text
{
MBProgressHUD *hud = [[self alloc] initWithView:view];
hud.labelText = text;
[view addSubview:hud];
[hud show:YES];
return MB_AUTORELEASE(hud);
}
and call it wherever you want like:
[MBProgressHUD showHUDAddedTo:self.view withText:#"Loading..."];

Resources