Block does not react to outside property change - objective-c

After some questions here I decided to run a server like this:
#implementation Server
-(id)init
{
if (self = [super init])
{
shouldRun = true;
__block Server* blocksafeSelf = self; // should prevent retain cycle
myServer = ^() {
dispatch_async(dispatch_get_main_queue(), ^{
NSString* currentText = controller.output.text;
controller.outputTextField.text = [currentText stringByAppendingString:#"Server ready. \n"];
});
while (blocksafeSelf.shouldRun) {
int bytes_received = recvfrom(...);
if (bytes_received > 0) {
switch (TYPE OF RECEIVED PACKET) {
case 1: {
dispatch_async(dispatch_get_main_queue(), ^{
NSString* currentText = controller.outputTextField.text;
controller.outputTextField.text = [currentText stringByAppendingString:#"S: Received Typ 1 "];
});
[blocksafeSelf methodToDealWithPacket:(PACKETTYPE*) buffer];
break;
}
...
default: {
...
}
}
}
}
return self;
}
#synthesize shouldRun;
the corresponding .h file:
#interface Server : NSObject {
ServerBlock myServer;
....
}
#property BOOL shouldRun;
....
#end
Now I have a ViewController, which has the Server as a property and following method in its implementation:
// called when the user clicks the stop button
- (IBAction) clickedStop
{
if(theServer != nil) {
theServer.shouldRun = false;
}
}
When I click the stop button however, the server does not exit his while loop. Why?
edit: Here is, how the server is started in the ViewController:
// called when the user clicks the start button
- (IBAction) clickedStart
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
if (...) { // all needed data has been entered
// start server here
if(theServer == nil) {
theServer = [[SNPTServer alloc] initWithHost:serverIP.text AndPort: [serverPort.text intValue] AndViewController:self];
dispatch_async(queue, theServer.myServer);
}
}
}
As you see, I have altered the constructor above a bit.

My answer will only work if the body of your while loop looks similar to the following (I will assume it does)
while (blocksafeSelf.shouldRun) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
In this case, just setting shouldRun to NO is not enough. That while loop is running on a different thread/runLoop so an event must happen to cause the run loop to pop so that the condition is checked again.
The easiest way to do that is to set shouldRun to NO from the thread that server is running on. I would do something like this:
myServer = ^() {
self.runThread = [NSThread currentThread]; // runThread declared elsewhere
while (blocksafeSelf.shouldRun) {
....
}
}
And then when you want to cancel it do:
- (IBAction) clickedStop
{
if ([NSThread currentThread] == theServer.runThread) {
if(theServer != nil) {
theServer.shouldRun = NO;
}
} else if (theServer.runThread) {
[self performSelector:_cmd
onThread:theServer.runThread
withObject:nil
waitUntilDone:NO];
}
}

Related

NSOperation Queue executes only first operation and stop executing after that

I am trying to execute custom operations in NSOperation Queue,
But the problem is
it executes only the first operation in queue and not execute the rest of them.
Please resolve this issue.
here is my code:
for (ContentMediaModel *uploadMedia in mediaArray)
{
AmazonUpload *customOperation = [[AmazonUpload alloc] initWithData:uploadMedia];
[operationQueue addOperation:customOperation];
}
here is my custom Operation class:
#implementation AmazonUpload
-(AmazonUpload *)initWithData:(ContentMediaModel *)uploadMedia
{
if (self = [super init])
{
self.uploadMedia=uploadMedia;
executing = YES;
finished = NO;
}
return self;
}
-(void)start
{
NSLog(#"** operation starts **");
[self main];
}
-(void)main
{
if ([self isCancelled])
{
NSLog(#"** operation cancelled **");
}
NSLog(#"** operation executing **");
[[AmazonS3Manager sharedInstance] uploadFile:self.uploadMedia.media.filename showLoader:NO];
}
-(BOOL)isConcurrent
{
return YES;
}
-(BOOL)isExecuting
{
return executing;
}
-(BOOL)isFinished
{
return finished;
}
#end

Objective-c block synchronizing data

I'm developing a program that gets feed from Instagram. I'm really new to objective-c so please be patient with my question. I have a button that calls a method to get the feeds, but it is a block so when the method finishes sometimes the block still continues loading images. The problem occurs when I change the user. I clear the media array but the method fired fills the array with old user data. I tried to use a property userchanged but it seems that it cancels the current user data too. How should I synchronize these methods? I can't figure it out.
#property (nonatomic, copy) NSMutableArray *mediaArray;
#property (nonatomic, assign) BOOL currentUserChanged;
- (void)getInstagramSelfFeed
{
if (self.currentPaginationInfo.nextMaxId != self.requestedPaginationInfo)
{
self.requestedPaginationInfo = self.currentPaginationInfo.nextMaxId;
[[InstagramEngine sharedEngine] getSelfFeedWithCount:15 maxId:self.currentPaginationInfo.nextMaxId success:^(NSArray *media, InstagramPaginationInfo *paginationInfo){
self.currentPaginationInfo = paginationInfo;
if ([self checkIfCurrentUserChanged])
{
return;
}
[self.mediaArray addObjectsFromArray:media];
[[NSNotificationCenter defaultCenter] postNotificationName:#"finishGettingSelfFeed" object:self];
} failure:^(NSError *error) {
NSLog(#"Request Self Feed Failed %#", error);
}];
}
}
- (void)setCurrentUser:(User *)currentUser
{
if (_currentUser != currentUser)
{
_currentUser = currentUser;
[[InstagramEngine sharedEngine] setAccessToken:self.currentUser.accessToken];
[self.mediaArray removeAllObjects];
if ([self.currentViewController isKindOfClass:[TimelineViewController class]])
{
self.currentUserChanged = YES;
}
MenuController *menuController = [MenuController sharedManager];
[menuController updateAllViews];
}
}
- (User *)currentUser
{
return _currentUser;
}
- (BOOL)checkIfCurrentUserChanged
{
if (self.currentUserChanged)
{
self.currentUserChanged = false;
return YES;
}
return NO;
}
OK, I solved the problem. Now I'm sending the user that made the request and compare both.
- (void)getInstagramSelfFeed: (User *)user
{
if (self.currentPaginationInfo.nextMaxId != self.requestedPaginationInfo)
{
self.requestedPaginationInfo = self.currentPaginationInfo.nextMaxId;
self.executingGetSelfFeed = true;
[[InstagramEngine sharedEngine] getSelfFeedWithCount:15 maxId:self.currentPaginationInfo.nextMaxId success:^(NSArray *media, InstagramPaginationInfo *paginationInfo){
self.currentPaginationInfo = paginationInfo;
if (user != self.currentUser)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"gettingSelfFeedCancelled" object:self];
return;
}
[self.mediaArray addObjectsFromArray:media];
[[NSNotificationCenter defaultCenter] postNotificationName:#"finishGettingSelfFeed" object:self];
} failure:^(NSError *error) {
NSLog(#"Request Self Feed Failed %#", error);
}];
}
}
A big problem is that you have a mutable array, and it is a "copy" property. That's just wrong. When you access self.mediaArray, a copy of the instance variable is made, and then you modify the copy. The instance variable itself is never modified.

App crashes after running a method once

Probably a dumb mistake, but I just need to know why my app shuts down after I press the button. It gives the desired answer for a second or two and then shuts down. Why? Then it takes me to this:
1{
2 #autoreleasepool {
3 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
4 }
5}
Line 3 is shaded in green and says "Thread 1: signal SIGBART" on the right side.
This is the code:
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
//dismiss the keyboard
[textField resignFirstResponder];
return YES;
}
#synthesize go;
#synthesize start;
#synthesize calc;
#synthesize answer;
#synthesize input;
#synthesize count;
- (void)updateTipTotals
{
int fuzzy = [input.text intValue];
//handle divide by 0
if (fuzzy %3 == 0 && fuzzy % 7 == 0) {
answer.text = #"Fuzzy Ducky";
}else {
if (fuzzy% 7 == 0) {
answer.text = #"Ducky";
}
else {
if (fuzzy % 3 == 0) {
answer.text = #"Fuzzy";
}else {
answer.text = input.text;
}
}
}
}
-(IBAction)calcTouchDown:(id)sender{
[self updateTipTotals];
}
- (void)viewDidLoad
{
[super viewDidLoad];
//this informs the text fields the controller is their delegate
input.delegate = self;
start.delegate = self;
Try setting an exception breakpoint in Xcode before running. It should then highlight the line of code actually causing the crash.

Singleton method not getting called in Cocos2d

I am calling a Singleton method that does not get called when I try doing this.
I get no errors or anything, just that I am unable to see the CCLOG message.
Under what reasons would a compiler not give you error and not allow you to call a method?
[[GameManager sharedGameManager] openSiteWithLinkType:kLinkTypeDeveloperSite];
The method is defined as follows:
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType"); //I NEVER SEE THIS MESSAGE
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
HERE IS THE CODE TO MY SINGLETON:
#import "GameManager.h"
#import "MainMenuScene.h"
#implementation GameManager
static GameManager* _sharedGameManager = nil;
#synthesize isMusicON;
#synthesize isSoundEffectsON;
#synthesize hasPlayerDied;
+(GameManager*) sharedGameManager
{
#synchronized([GameManager class])
{
if (!_sharedGameManager)
{
[[self alloc] init];
return _sharedGameManager;
}
return nil;
}
}
+(id)alloc
{
#synchronized ([GameManager class])
{
NSAssert(_sharedGameManager == nil,#"Attempted to allocate a second instance of the Game Manager singleton");
_sharedGameManager = [super alloc];
return _sharedGameManager;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil)
{
//Game Manager initialized
CCLOG(#"Game Manager Singleton, init");
isMusicON = YES;
isSoundEffectsON = YES;
hasPlayerDied = NO;
currentScene = kNoSceneUninitialized;
}
return self;
}
-(void) runSceneWithID:(SceneTypes)sceneID
{
SceneTypes oldScene = currentScene;
currentScene = sceneID;
id sceneToRun = nil;
switch (sceneID)
{
case kMainMenuScene:
sceneToRun = [MainMenuScene node];
break;
default:
CCLOG(#"Unknown Scene ID, cannot switch scenes");
return;
break;
}
if (sceneToRun == nil)
{
//Revert back, since no new scene was found
currentScene = oldScene;
return;
}
//Menu Scenes have a value of < 100
if (sceneID < 100)
{
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad)
{
CGSize screenSize = [CCDirector sharedDirector].winSizeInPixels;
if (screenSize.width == 960.0f)
{
//iPhone 4 retina
[sceneToRun setScaleX:0.9375f];
[sceneToRun setScaleY:0.8333f];
CCLOG(#"GM: Scaling for iPhone 4 (retina)");
}
else
{
[sceneToRun setScaleX:0.4688f];
[sceneToRun setScaleY:0.4166f];
CCLOG(#"GM: Scaling for iPhone 3G or older (non-retina)");
}
}
}
if ([[CCDirector sharedDirector] runningScene] == nil)
{
[[CCDirector sharedDirector] runWithScene:sceneToRun];
}
else
{
[[CCDirector sharedDirector] replaceScene:sceneToRun];
}
}
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType");
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
-(void) test
{
CCLOG(#"this is test");
}
#end
Perhaps sharedGameManager is returning nil? This won't cause an error.
Edit: The issue is in the new code you posted:
if (!_sharedGameManager) {
[[self alloc] init];
return _sharedGameManager;
}
return nil;
If _sharedGameManager is non-nil, this will return nil. You want:
if (!_sharedGameManager) {
[[self alloc] init];
}
return _sharedGameManager;
Check this Cocoa With Love article on how to make a proper singleton.

TTModel load method not being called

Issue
Description
The following code results in load method of TTModel not being called. I've stepped it through the debugger, as well as stepping through the TTCatalog application. The only difference between the two that I can see, is that when the catalog sets it's DataSource in the createModel method of the controller, TTModel's load method gets called, whereas mine does not.
About the Code
I've commented the specific areas of code, to tell what they should be doing and what problem is happening, but I included all of it for completion's sake.
You should look at specifically
PositionsController's createModel method
PositionsList's load method
Those are the areas where the issue is, and would be the best place to start.
Code
PositionsController : TTTableViewController
- (id)initWIthNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.title = #"Positions";
self.variableHeightRows = NO;
self.navigationBarTintColor = [UIColor colorWithHexString:#"1F455E"];
}
return self;
}
//This method here should result in a call to the PositionsList load method
- (void)createModel
{
PositionsDataSource *ds = [[PositionsDataSource alloc] init];
self.dataSource = ds;
[ds release];
}
- (void)loadView
{
[super loadView];
self.view = [[[UIView alloc] initWithFrame:TTApplicationFrame()] autorelease];
self.tableView = [[[UITableView alloc] initWithFrame:TTApplicationFrame() style:UITableViewStylePlain] autorelease];
self.tableView.backgroundColor = [UIColor colorWithHexString:#"E2E7ED"];
self.tableView.separatorColor = [UIColor whiteColor];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
//self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
//Override UITableViewDelegate creation method, so we can add drag to refresh
- (id<TTTableViewDelegate>) createDelegate {
TTTableViewDragRefreshDelegate *delegate = [[TTTableViewDragRefreshDelegate alloc] initWithController:self];
return [delegate autorelease];
}
PositionsDataSource : TTListDataSource
#implementation PositionsDataSource
#synthesize positionsList = _positionsList;
-(id)init
{
if (self = [super init])
{
_positionsList = [[PositionsList alloc] init];
self.model = _positionsList;
}
return self;
}
-(void)dealloc
{
TT_RELEASE_SAFELY(_positionsList);
[super dealloc];
}
-(void)tableViewDidLoadModel:(UITableView*)tableView
{
self.items = [NSMutableArray array];
}
-(id<TTModel>)model
{
return _positionsList;
}
#end
PositionsList : NSObject <TTModel>
#implementation PositionsList
//============================================================
//NSObject
- (id)init
{
if (self = [super init])
{
_delegates = nil;
loaded = NO;
client = [[Client alloc] init];
}
return self;
}
- (void)dealloc
{
TT_RELEASE_SAFELY(_delegates);
[client dealloc];
[super dealloc];
}
//==============================================================
//TTModel
- (NSMutableArray*)delegates
{
if (!_delegates)
{
_delegates = TTCreateNonRetainingArray();
}
return _delegates;
}
-(BOOL)isLoadingMore
{
return NO;
}
-(BOOL)isOutdated
{
return NO;
}
-(BOOL)isLoaded
{
return loaded;
}
-(BOOL)isEmpty
{
//return !_positions.count;
return NO;
}
-(BOOL)isLoading
{
return YES;
}
-(void)cancel
{
}
//This method is never called, why is that?
-(void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more
{
//This method is not getting called
//When the PositionsController calls self.datasource, load should be called,
//however it isn't.
[_delegates perform:#selector(modelDidStartLoad:) withObject:self];
[client writeToServer:self dataToSend:#""];
}
-(void)invalidate:(BOOL)erase
{
}
#end
Short answer: return NO instead of YES for isLoading in your PositionList.
For a longer explanation:
If you dig through the Three20 source, you'll find that setting the dataSource on a view controller sets the model, refreshing the model and potentially calling load. Here is the code called when the TTModelViewController refreshes:
- (void)refresh {
_flags.isViewInvalid = YES;
_flags.isModelDidRefreshInvalid = YES;
BOOL loading = self.model.isLoading;
BOOL loaded = self.model.isLoaded;
if (!loading && !loaded && [self shouldLoad]) {
[self.model load:TTURLRequestCachePolicyDefault more:NO];
} else if (!loading && loaded && [self shouldReload]) {
[self.model load:TTURLRequestCachePolicyNetwork more:NO];
} else if (!loading && [self shouldLoadMore]) {
[self.model load:TTURLRequestCachePolicyDefault more:YES];
} else {
_flags.isModelDidLoadInvalid = YES;
if (_isViewAppearing) {
[self updateView];
}
}
}
Your PositionList object is returning YES for isLoading and NO for isLoaded. This means that Three20 thinks your model is loading when it isn't. You may also need to implement shouldLoad if it doesn't return YES by default.