I did quite a bit of research on this, but I am having a mental block about my problem. I am working on Objective-C for an iOS app
Here's my set up:
The view controller gets a text from the view (user input), and passes that text to the MethodA of the model.
The MethodA in model works on the input text and gets an output (e.g. searches google for that text). It does the search using dispatch_async method which calls the selector to MethodB within model.
MethodB parses the output and puts all the results into a NSMutableArray
My Question Here: how do I pass that NSMutableArray back to view controller so I can show it on the view?
Sorry, if the answer to my question is very simple/obvious. I am new to Objective-C
Any time I want to do async processing and that stuff needs to get back into the UI somewhere, I do one of two things:
1. Use NSNotification to tell anyone who cares that the work is complete
2. Use a delegate property on the worker and a #protocol
1 NSNotification
The model object should document in it's .h file that it fires notifications when certain things happen; such as when a portion of the model has been updated. When the ViewController initializes the model object, have it set itself up as an observer of the documented notification, and implement a callback which updates the UI.
2 delegation and #protocol
Create a #protocol such as #protocol FooModelUpdateDelegate with a method properly named such as fooObjectDidUpdate:(Foo *)object;, and then the model class has a delegate property as id<FooModelUpdateDelegate> updateDelegate and the ViewController sets itself as that delegate, and I'm sure you can figure out the rest.
I guess passing along a delegate-object that respoons to a selector-method and calling this method with the processed data will be a good way to achieve the loosley coupled structure your program deserves. Are you familiar with this concept, or shall I dig up some code-samples for you?
UPDATE: Code samples
So, I would probably use the calling class, say MyViewController, to implement the callbackMethod, myCallbackMethod as follows:
-(void) myCallbakcMethod: NSMutableArray* array {
//DoWhatever with the returned data
}
The point is to get the result passed back to this method when the computation is finished.
So in your MyViewController where you call MethodA you pass along a reference to the delegate to handle the result, namly self.
//From this:
[MyModel methodA:param1]
//To:
[MyModel methodA:param1:self]
MyModels methodA and methodB would need to add a parameter (id)delegate and pass that along between the calls.
In methodB where the data myArray is ready, do the following call:
if([delegate respondsToSelector:#selector(myCallbackMethod:)]])
[observer performSelector:#selector(myCallbackMethod:) withObject:myArray];
In your view controller:
// your controller code
...
[myModel getSearchResult:searchKeyword receiver:self action:#selector(responseReceived:)];
}
- (void)responseReceived:(MyModel *)model
{
NSArray *searchResult = model.searchResult;
[model release], model = nil;
// some view actions, for instance update your table view
...
}
In your model:
...
- (id)getSearchResult:(NSString *)searchKeyword receiver:(id)aReceiver action:(SEL)receiverAction {
self.receiver = aReceiver;
self.action = receiverAction;
// some async actions
}
In async response receiver:
...
[self.receiver performSelector:self.action withObject:self];
}
Unless I'm misunderstanding your description it sounds like your "model" class is doing more than it should. In this case it's doing at least some of the work of your controller. My suggestion would be to fold methodA and methodB into the view controller (or another controller class). Method B could still set the NSMutableArray property of "model" instance, if that's essential (or skip that step if it's not).
-(void)methodA {
NSMutableArray *someArray = ...
[self methodB:someArray];
}
-(void)methodB:(NSMutableArray*)array {
NSLog(#"%#", array);
// do something with the array, like update the view
}
But if both are methods inside the view controller why not just update the view inside the method instead of passing it to another method?
Related
I'm new to Objective-C and I'm struggling to find the proper way to pass NSArray values from one NSView to another NSView. My code looks like this:
NSViewOne:
NSLog(#"%#", arrayValues); // 10, 20, 30
NSViewTwo *displayValues = [[NSViewTwo alloc] init];
[displayValues showValuesFromOne:arrayValues];
NSViewTwo:
- (void)showValuesFromOne:(NSArray *)sender {
NSArray *arrayValues = sender;
NSLog(#"%#", arrayValues);
}
In NSViewOne the array values show, in NSViewTwo I get (null).
There is a misconception:
First of all action methods are an entry point from the GUI to your code, not an exit point. The passed value is always the sender. You can use action methods from your code, but this is rarely useful. So -showValuesFromOne: makes no sense at all.
Second, it is unusual to pass a value from a view to another view. View communicates with controllers and vice versa. Therefore the code in view 1 should be in the controller and be executed by the view 1. Then the invoked controller code pushes the values to view 2. Do not use a action method for this, but a simple method.
The controller loads both nibs and therefore has outlets to both views. Therefore for the controller it is easy to communicate with both views. The threads converge in his hand.
I have an Objective C app and I am trying to follow the MVC guidelines laid out.
There is a point in my Model where user input is required before any further calculations can proceed.
Is there an elegant way I can request input from the controller without breaking MVC?
The only way I can think of doing this right now is just returning a value a nonzero value from the method in the model to the controller, and then having the control call a separate method to send the input to the model.
I was hoping there is some other way to pause the execution of the code in the model while it waits for input from the controller.
Here is some applicable code. I simplified most of it for readability.
Relevant ViewController function:
#implementation GameViewController
-(void)tap:(UITapGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateEnded) {
id view = gesture.view;
if ([view isKindOfClass:[GameBoardTileView class]]) {
GameBoardTileView *gameTileView = (GameBoardTileView *) view;
int row = gameTileView.row;
int col = gameTileView.col;
[self.game chooseTileAtRow:row column:col];
// Updates states of all game tiles after tapping one tile
[self updateTiles];
}
}
}
#end
Relevant Model function:
-(void)chooseTileAtRow:(int)row column:(int)col
{
// Retrieves the game tile at the spot just selected
GameBoardTile *tile = [self retrieveTileAtRow:row column:col];
// Makes sure the spot is empty
if (tile.companyType == -1) {
// If no company type is found generate a random company type
if (!tile.companyType) {
//NSLog(#"changing company type");
if ([self.chainsInPlay count]) {
/////////// Would like index to be given by the Controller instead of randomly generated
int index = [self generateRandomNumber:0 end:[self.chainsInPlay count]-1];
tile.companyType = [[self.chainsInPlay objectAtIndex:index] intValue];
[self.chainsInPlay removeObjectAtIndex:index];
}
}
}
}
What the view controller does is it keeps track of a grid of tiles that when an individual tile is tapped, it sends that row and column over to the Model using chooseTileAtRow:column:. The model retrieves a GameBoardTile at that row and column and gets the companyType. If the companyType does not exist (== 0), I would like it to be set. Currently it just generates a random number for testing purposes. Ideally, I would like this number to be passed in from the Controller and selected via user input.
You cannot "pause execution" or "wait"; don't even think that way.
It is perfectly reasonable for the model to have a property needsMoreInfo or to return false or nil from some data request or other method in order to say that it can't proceed without further information.
The controller could thus ask the model whether it needs more info, and supply it, before requesting the real calculation.
I think you have to split your model's method, so you can call different parts in your VC. It depends on your MVC logic. You can also use NotificationCenter or delegate pattern.
You should most likely be using the delegation pattern.
Essentially, a base class can define itself as a view controller's delegate, while instantiating the view controller. You use a protocol to ensure that the base class implements all the necessary methods. Your view controller can then blindly call methods on the delegate (often passing arguments).
I'm working through the 'Learning Cocos2d' book, and I'm stuck on something basic.
So, theres a parent Class: GameplayLayer. In it, there's an 'init' method which creates an instance of the main character in here - 'Viking'. Viking is a subclass of 'GameCharacter', which is a subclass of 'GameObject'.
#pragma mark INIT PLAYER
Viking *viking = [[Viking alloc]
initWithSpriteFrame:[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#"viking.png"]];
[viking setJoystick:leftJoystick];
[viking setFireButton:fireButton];
[viking setSecondaryButton:secondaryButton];
[viking setCollisionLayer:collidableLayer]; // send collision layer to player
[viking setPosition:ccp(screenSize.width * 0.35f, screenSize.height * 0.14f)];
[viking setHealth:100];
[sceneSpriteBatchNode addChild:viking z:1000 tag:kPlayerSpriteTagValue];
Now, Viking has an update method which is called every frame by GameplayLayer. It's parent class, GameObject also has this update method, which brings up an error message if it is accidentally called - "GameObject update should be overridden'.
So in my code, I'm calling the update method of 'Viking' with the following method:
#pragma mark UPDATE_METHOD
-(void) update:(ccTime)deltaTime
{
CCArray *listOfGameObjects =
[sceneSpriteBatchNode children];
for (GameObject *tempChar in listOfGameObjects) {
CCLOG(#"engine found %#",tempChar);
[tempChar updateStateWithDeltaTime:deltaTime
andListOfGameObjects:listOfGameObjects];
}
}
So, this is supposed to call the 'updateStateWithDeltaTime' method in Viking. But somehow, it's calling the parent method in GameObject which says 'updatestate should be overridden'. How do I override the parent method?
Many thanks,
Carl
You need to cast tempChar to a Viking.
for (GameObject *tempChar in listOfGameObjects)
{
[(Viking *) tempChar updateStateWithDeltaTime:deltaTime
andListOfGameObjects:listOfGameObjects];
}
Because you're doing a for loop with fast enumeration of GameObjects, the local variable assumes that all objects are GameObjects. You need to explicitly cast tempChar to a Viking so that the program knows which class to look for the method in.
As an interesting side note, if GameObject didn't have the same method as Viking did, you would be getting a warning in XCode telling you it couldn't find the method you're asking for (because it needs to know that every object this could be called on has it).
You might want to check that the object you're calling this on is the correct class (if you only want to call this on Viking objects). You'd add if ([GameObject isKindOfClass[Viking class]) above your update method call.
I currently have a class with 15 properties (and growing), and I'm finding myself having to call an update method every time one of those properties change.
Currently, I'm overriding every setter with a code like this:
-(void)setParameterName:(NSUInteger)newValue {
if (_param == newValue)
return;
_param = newValue;
[self redraw];
}
The method [self redraw]; being the key here.
Is there a better way to do it? Should I be using keyValue observers (the method observeValue:forKeyPath:ofObject:change:context:)?
Notes:
All properties (so far) are assign (mostly enum, NSUInteger, CGFloat and BOOL);
All those properties are set using bindings (method bind:toObject:withKeyPath:options:). Except when loading from the filesystem (which is not important, as I already call the drawing methods on every object after the loading is done);
The value changes are only for the current object. I do not need to be told when changes occur on other objects;
I have other properties that I don't need to watch the changes on it (because it will have no effect on my output and drawing the output is kinda time-consuming).
Thanks!
Since these properties are updated using bindings, which invoke -setValue:forKey:, you can override that method instead of writing custom setters:
+ (NSArray *) keysAffectingDrawing {
static NSArray *singleton;
if (!singleton)
singleton = [NSArray arrayWithObjects:
#"property1",
#"property2",
#"property3",
nil];
return singleton;
}
- (void) setValue:(id) value forKey:(NSString *) key {
[super setValue:value forKey:key];
if ([[CustomClass keysAffectingDrawing] containsObject:key]) [self redraw];
}
(I was first inclined recommend key-value observing but agree it's not the best solution here. I think the reason is in part that there's only one object, and in part because the design doesn't follow MVC. Usually in MVC an object that draws itself isn't the one with all the properties.)
(Added: Ahh, I see. The model is responsible for rendering the properties to a bitmap, and that's what -redraw does. That's fine MVC. To make it clearer, I recommend changing the name of the method from -redraw to something like -updateImage or -renderImage, since it doesn't actually do any drawing.)
You could use the Key-Value Observing to avoid repeating in all properties setter the method call, however i think that calling the method directly in the setter is not the wrong way to do it, and could even be faster ...
So I have this custom class with just a test method that does nslog. I am going to reuse this method many times in my app. The interface looks like this.
#interface TestViewController: UIViewController { CMImageMover * imageMover }
Then in the view did load I:
imageMover = [[CmImageMover alloc] init];
If I do:
[imageMover testMethod];
Right after the alloc and init it works in the viewDidLoad function but if I call it again from another function in the view controller nothing works and the class method does not get called.
What am I doing wrong here. Every other var I declare like NSArray/NSTimer, I do the say way and I am able to access and use it throughout my controller.
When you say "if I call it again from another function in the view controller nothing works" then first thing to check is what you are sending the testMethod. It could be nil, in which case nothing will happen. In objective C sending a message to nil does nothing. Add an NSLog to find out, e.g.
NSLog(#"imageMover object is: %#", imageOver);
[imageMover testMethod];
If the NSLog shows it is nil - or something crazy - then follow up what you are doing with the imageMover ivar.
You mention a class method in your question, but don't refer to it in your code snippets.
If you have defined testMethod as a class method it will, of course, fail if you send that message to an instance. (And it will fail noisily.) A class method would be introduced like this:
+ (void) testMethod
{
NSLog(#"CMImageMover testMethod called on Class");
}
An instance method would be introduced like this:
- (void) testMethod
{
NSLog(#"testMethod called on an instance of CMImageMover");
}
Apologies if this is all screamingly obvious to you and missing the point of the question. It's not that clear from your question where the issue lies.