Why use Message Forwarding - objective-c

Why would you use Message Forwarding in Objective-c. Also when using it, you need to call the Surragate method with performSelector? I am thinking you have a lot of knowledge when you are coding this, why would you need Message Forwarding. I am reading that this is Obj-c multiple inheritance - but I don't see it like this, it is just another object attached to an object.
Hope you know and have a pratical example
Thanks

See my discussion at the end of this section: http://www.apeth.com/iOSBook/ch25.html#_uitabbar
Here I have interposed my own object as the data source for one of Apple's table views. I don't know how the table view works (it's Apple's) and I don't know what their data source does (it's Apple's). So I substitute my own data source but I keep Apple's, and any messages that I don't handle that arrive at my data source are passed on to Apple's. Thus in a sense I have made my data source "inherit" from Apple's.
#implementation MyDataSource
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.originalDataSource respondsToSelector: aSelector])
return self.originalDataSource;
return [super forwardingTargetForSelector:aSelector];
}
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)sec {
// this is just to quiet the compiler
return [self.originalDataSource tableView:tv numberOfRowsInSection:sec];
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)ip {
// this is why we are here: my tweaks
UITableViewCell* cell = [self.originalDataSource tableView:tv cellForRowAtIndexPath:ip];
cell.textLabel.font = [UIFont fontWithName:#"GillSans-Bold" size:14];
return cell;
}
#end

Related

UITableViewDataSource and Multithreading

I'm running into index beyond bounds exception in one of my UITableViews and I think it could be down to some multithreading issues. Here's what I believe is happening:
I have a UITableView and it's data source is a regular NSMutableArray.
This NSMutableArray which is backing my UITableView is updated every couple of seconds with the contents of an API response.
After each update, UITableView's reloadData is being invoked to ensure that the user sees new data from the API server.
Sometimes a index beyonds bounds exception gets thrown.
Here's my code:
-(NSMutableArray*) currentBetEvents
{
return currentMarketId == nil ? [[BFOpenBetsModel sharedInstance] betEvents] : filteredBetEvents;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSArray *betEvents = [self currentBetEvents];
return [betEvents count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *betEvents = [self currentBetEvents];
id obj = [betEvents objectAtIndex:indexPath.section] // this is where it blows up
Basically, I get an exception while trying to access an object in the betEvents structure at index 0.
What I believe is happening is:
reloadData is called on the UITableView
numberOfSectionsInTableView: is invoked which returns a value > 0.
a rouge thread arrives and clears out the UITableView's data source.
cellForRowAtIndexPath: is invoked and it bombs.
Is there any way to ensure that this doesn't happen? Do I need to start using some primitive locks on the data source to ensure that it doesn't get updated while the table is being updated?
EDIT
Took another look at how the data structures returned by currentBetEvents can be altered and it looks like the filteredBets & betEvents can be cleared out as a result of the following code:
[[NSNotificationCenter defaultCenter] postNotificationName:kUserLoggedOutNotification object:nil];
This notification is posted whenever the user logs out. Whenever a user logs out of the app, I need to clear out the filteredBets and betEvents arrays. Is it possible that the following could happen:
reloadData is called on the UITableView
numberOfSectionsInTableView: is invoked which returns a value > 0.
User logs out which kicks off the notification & clears out the data structures.
cellForRowAtIndexPath: is invoked and it bombs.
Thanks,
Sean
Definitely sounds like a threading problem. You might try something like this:
// view controller
#synchronized([[BFOpenBetsModel sharedInstance] betEvents])
{
[self.tableView reloadData];
}
…
// data model
#synchronized(_betEvents) // or whatever the instance variable -betEvents returns is
{
[_betEvents addObject:whatever];
}

When does objectValueForTableColumn: get called?

I am currently going through the 3rd Edition of the Mac OSX Cocoa book from the Big Nerd Ranch guys and I am editing my program to include table views. Here the code from the AppController.m file where I have to implement the required protocol methods from the TableView:
-(id)init{
self = [super init];
voiceArray = [NSSpeechSynthesizer availableVoices];
speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
[speechSynth setDelegate:self];
return self;
}
-(int)numberOfRowsInTableView:(NSTableView *)tv{
NSLog(#"Getting number of rows in table view: %lu", [voiceArray count]);
return [voiceArray count];
}
-(id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
NSString *v = [voiceArray objectAtIndex:row];
NSDictionary *dict = [NSSpeechSynthesizer attributesForVoice:v];
NSLog(#"Voice Name %#", [dict objectForKey:NSVoiceName]);
return [dict objectForKey:NSVoiceName];
}
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
int row = [tableView selectedRow];
NSLog(#"row selected %d", row);
if(row == 1){
return;
}
NSString *selectedVoice = [voiceArray objectAtIndex:row];
[speechSynth setVoice:selectedVoice];
NSLog(#"new voice %# ", selectedVoice);
}
When the app first loads I get the following output:
2012-05-27 15:02:29.040 Speakline[42836:f03] Getting number of rows in
table view: 24 2012-05-27 15:02:29.042 Speakline[42836:f03] row
selected 2 2012-05-27 15:02:29.043 Speakline[42836:f03] new voice
com.apple.speech.synthesis.voice.Alex 2012-05-27 15:02:29.162
Speakline[42836:f03] Voice Name Agnes 2012-05-27 15:02:29.163
Speakline[42836:f03] Voice Name Albert
I want to make sure I understand fully what is going on here. In order to do this I have a couple of questions.
It looks like the numberOfRowsInTableView: method was automatically called after the init method. Is this correct?
How often does objectValueForTableColumn: get called? What events prompt that method to get called? Also, in the code there, the return statement confused me. Where exactly does this return value go?
As a side note they wanted me to connect the Outlets and the AppController via control+clicking (via the connections panel) and linking them in that way. What alternatives are there avaialble for connecting delegates and datasources to different kinds of views without doing this? I am assuming that adding NSTableViewDelegate in the controller header file might be one way. If you have the option of control+click connecting all your views to outlets and so on vs programmatically setting it all up is it just a matter of preference at this point? It just seems to me that in order to understand what is going on it might be better to just write the code yourself.
1) Both numberOfRowsInTableView and objectValueForTableColumn:Row: get called soon after the nib is unarchived (I'm not sure of the exact order of things), so the table view can be populated with data.
2) The loading of the table and reloading or adding new data as well as scrolling will cause objectValueForTableColumn:row: to be called (by the table view). It gets called for each row that is populated. The value goes to the table view, that's how it gets its data.
3) The other way to connect delegates is to do it in code with setDelegate:, but putting NSTableViewDelegate in the .h file does not do that -- that's just a promise to the compiler that you will implement some or all of the delegate messages.

How to pass data (arrays) between functions

I must use s7graphview library for draw simple histogram, and I've got custom function called
-(IBAction)histogram:(id)sender;. in this function every pixel from image is passed to array as RGB representation. then pixels are counted and I've got red, green and blue array. I can send to NSLog or something but problem is, when I try to send 3 arrays to - (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex;. both functions are in the same .m file, and I have no idea how to pass data between them, because when I write redArray, Xcode don't suggest me this name.
Since - (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex is a delegate method, it should be implemented in your class that is posing as a delegate to S7GraphView object. You don't call explicitly, you define it as such in your .m implementation:
- (NSArray *)graphView:(S7GraphView *)graphView yValuesForPlot:(NSUInteger)plotIndex
{
if ( plotIndex == <some index value> )
return redArray;
else
return nil;
}
I have no idea what plotIndex corresponds with your various color arrays, but you should get the idea.
When the S7GraphView object needs that data, it will invoke that delegate method.
This is not unlike implementing UITableViewDelegate and UITableViewDataSource methods. When a UITableView method -reloadData is invoked, it will call upon your view controller (presuming it is delegate/data source of the table) to supply UITableViewCell objects via
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = <... dequeue or created ... >.
/*
do some cell set up code based on indexPath.section and indexPath.row
*/
return cell;
}
Similar with S7GraphView I'm sure (I don't have the API to see all it does). In your IBAction method, you will probably be doing something like:
- (IBAction)histogram:(id)sender
{
// maybe you recalculate your red, green, and blue component arrays here and cache
// or maybe you calculate them when requested by the delegate method
// tell the S7GraphView it needs to update
// (not sure what the reload method is actually called)
[self.myS7GraphView reloadGraph];
}

Obtain a stringValue from NSTableView

I have a simple NSTableView which I have loaded with data from a NSMutableArray. When I select a row (entry) in the tableView and modify it, I can find the row index, what I cannot get is the edited entry out as a string so I can modify the array. I can find lots of information on selecting rows, etc., but not on how to get the actual modified string. I keep thinking this should be real simple. Help please.
Part of my code:
- (IBAction)toDoEdit:(id)sender // Accept the edited data
{
NSString *toDoItem = [[toDoTableCell:toDoTableView dataCellFoTableColumn:0 row:rowToBeEdited] stringValue];
// I get the error "dataCellForTableColumn' method cannot be found.
[toDoArray replaceObjectAtIndex:rowToBeDeleted withObject:toDoItem];
[toDoTableView reloadData];
[toDoTableView deselectRow:rowToBeDeleted];
}
~~~~~~~~~~~
// This method should return the cell value of the selected row
- toDoTableCell:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return toDoTableCell; // No errors here but toDoTableCell is nil.
}
The 'Add' data to tableView works, 'Delete' data from tableView works, I just cannot get the edited data out of tableView so I can reload the data with the corrections.
What you are looking for is an NSTableView Delegate method:
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
This will return the NSCell that is in the row and column that you specify. From the NSCell you should be able to extract the value that you need. Depending on how you are using your NSCell you would either call [cell stringValue] or [cell objectValue].
Try this:
– tableView:setObjectValue:forTableColumn:row:
in - NSTableViewDataSource Protocol Reference
--- Edited based on comment ---
Above method is called whenever user tries to edit a table row, it also provides user with changed value as parameter. If you are trying to edit the row in table itself then it should serve your purpose. You can simply check the objectValue obtained as parameter and verify if it is correct or not. In case it is incorrect you can modify the obtained value and set it in todoArray.
Briefly:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
// below case is an example, you can add your own
if([anObject isEqualToString:#"Incorrect"])
{
anObject = #"Correct";
}
// Considering todoArray is array of dictionary items containing keys as table-column identifiers
NSMutableDictionary *originalData = [todoArray objectAtIndex:rowIndex];
[originalData setValue:anObject forKey:[aTableColumn identifier]];
[toDoTableView reloadData];
}
To get the value being edited you can simply use this code in above method, before setting the new value:
NSString *editedValue = [[todoArray objectAtIndex:rowIndex] valueForKey:[aTableColumn identifier]];
Hope this helps :)
It is simple. Read up on Cocoa Bindings and NSArrayController.
Take a look at the NSTableView methods selectedColumn, selectedColumnIndexes, selectedRow and selectedRowIndexes. I guess they should provide you with the needed information.
Now you can query the model, i.e. the array, for the data you need.

Objective C - UITableViewCell crashing on scroll

I have a UITableViewCell with a method like this.
-(void) setupStore:(StoreModel *) store {
self.title.text = store.title; // crash here when scrolling
}
So that method is called from within a UIViewController class that contains the UITableView.
Something like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
[cell setupStore:[storesArray objectAtIndex:indexPath.row]];
...
}
That works when the table first loaded, but when I scroll the table, it crash with error EXC_BAD_ACCESS.
What could be causing that?
Please enlight.
Thanks,
Tee
Try to build your code with NSZombieEnabled = YES and report here what is happening. Give us the full error description.
http://cocoa-nut.de/?p=16
In general We will get EXC_BAD_ACCESS when we are trying to use a released object.
So you can check whether you are using any released object.
As you have mentioned that storesArray = [[[storesLocation alloc] init]retain]; there is no need to retain the object. Give a try by using this line
storesArray = [[storesLocation alloc] init];.
Also make sure that storemodel object exists by logging it in this method
-(void) setupStore:(StoreModel *) store
{
NSLog(#"store model %#",store);
}
You can go through the link
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html