Can I avoid explicitly casting objects with a common subclass? - iphone

I have an iPodLibraryGroup object and Artist and Album both inherit from it.
When it comes to my view controllers though I find that I'm duplicating lots of code, for example I have an ArtistListViewController and and AlbumListViewController even though they're both doing basically the same thing.
The reason I've ended up duplicating the code is because these view controllers each refer to either an Artist object or al Album object and I'm not sure how to set it up so that one view controller could handle both — these view controllers are mainly accessing methods that that the objects have in common from iPodLibraryGroup.
As an example, to hopefully make this clearer consider this code in AlbumListViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Album *album = nil;
album = [self albumForRowAtIndexPath:indexPath inTableView:tableView];
…
if (!album.thumbnail)
{
[self startThumbnailDownload:album forIndexPath:indexPath inTableView:tableView];
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
else
{
cell.imageView.image = album.thumbnail;
}
return cell;
}
This is essentially completely repeated (along with a hell of a lot more repeated code) in ArtistListViewController just so that I can typecast the local variable as an Artist instead of an Album.
Is there a way to not explicitly need to set Artist or Album here so that the same code could work for any object that is a child of iPodLibraryGroup?

Refactor your code so you have a generic ListViewController that operates on iPodLibraryGroup objects, and have ArtistListViewController & AlbumListViewController both inherit from it.
Push all the common parts up to the generic ListViewController, and have your Artist/Album controllers only implement/override the methods that need to behave differently.

Based on a suggestion received on twitter I created a protocol on iPodLibraryGroup as follows:
#protocol iPodLibraryGroup <NSObject>
#required
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain, readonly) NSString *sort_name;
…
- (void)downloadImageFromURL:(NSURL *)image_url;
- (void)cancelImageDownload;
- (void)downloadImageCannotStart;
#end
#interface iPodLibraryGroup : NSObject <iPodLibraryGroup> {
…
}
Then within my view controllers, instead of declaring pointers to declared Artists or Albums I use the syntax:
id <iPodLibraryGroup> source;
The only issue I encountered was when calling NSObject methods, I would get a compiler warning:
This issue is discussed in How to typecast an id to a concrete class dynamically at runtime? and I resolved it by casting my "source" reference as an (iPodLibraryGroup *) before calling NSObject methods, for example:
[(iPodLibraryGroup *)source addObserver:self forKeyPath:#"pendingResponseForDetailedInfo" options:0 context:nil];

Related

Sending data from NSObject to UIViewController

I have been working on this problem for close to 4 days now.
I am at the point where I think its not so much a problem with my code, but the structure of my application that is causing the issue.
I am trying to implement protocols and delegates to get an array from one NSObject(class) to a ViewController.
my code is pretty much line by line copied from this tutorial the only differences are in the face I have ARC turned on so have had to replace (nonatomic, retain) to (strong) and have not used dealloc :)
so with that being said its still not passing the data back to the viewcontroller. (highly annoying) I have tried dozens of different combinations of solutions that I have had help with and nothing has worked. This has lead me to believe that maybe there is an error in the structure of my application or the way things have been initialized etc, which I will attempt to explain now.
When my viewcontroller with tableview loads the viewdidload method called the delegate of my parser class, then once the first cell of the tableview has loaded it called my connection class and tells it to download some data from the server.
Inside my connection class I use NSURLConnection delegates from the apple library, in the delegate method connectionDidFinishLoading the data that has been downloaded is passed over to my parser class (however this is where i think its going wrong because i declare the object again.. which i think is where things are going amiss)
this is how I call my parser class from my connection class.
parserClass *myparser = [[EngineResponses alloc] init];
[myparser ReciveResponse:receivedData];
then once the data is in my parser class it gets parsed and then I try to pass the data across to my viewcontroller.. but its never accessing that delegate method that I set up.
Hopefully this is where the problem is because I just dont know where else I am going wrong.
what do you think?
UPDATE: heres my code -
ViewController.h
#import "EngineResponses.h" //delegates & protocols
interface SearchViewController : UITableViewController <PassParsedData> {
//delegates to parser class
EngineResponses *engineResponses;
//..
ViewController.m
#import "EngineResponses.h"
//this is where I set up the delegate/protocol for the parser class
- (void)viewDidLoad
{
[super viewDidLoad];
//..
engineResponses = [[EngineResponses alloc] init];
[engineResponses setMydelegate:self];
//..
}
//this is where i set up and call the connection class
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
if(indexPath.section == 0){
//..
if (indexPath.row == 0){
EngineRequests *engineRequests = [[EngineRequests alloc] init];
[engineRequests initalizePacketVariables:0 startCode:#"myReg" activationCode:#"myAct" methodName:#"GetStuff"];
//..
}
#pragma - Reciver methods
- (void)sendArray:(NSArray *)array
{
ICMfgFilterArray = array;
[self.tableView reloadData];
}
EngineRequests.m
//connection delegates etc..
//then I pass the data from the connection delegates over to the parser class
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
EngineResponses *engineResponses = [[EngineResponses alloc] init];
[engineResponses ReciveResponse:receivedData];
}
EngineResponses.h
#protocol PassParsedData
- (void)sendArray:(NSArray *)array;
#end
//..
id <PassParsedData> mydelegate;
//..
#property (strong) id <PassParsedData> mydelegate;
EngineResponses.m
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
//..
[[self mydelegate]sendArray:filteredArray];
}
1
Allright. I will re-do it based on your updated code. To make it easy I copy your code and do the amendments.
ViewController.h
#import "EngineResponses.h" //delegates & protocols
interface SearchViewController : UITableViewController <PassParsedData> {
//delegates to parser class
EngineResponses *engineResponses;
EngineRequests *engineRequests;
//..
Explanation:
You are using ARC. If you define the pointer locally, as you did before, and to not
retain it - which you can't because of ARC - then it will be released directly after its
creation. You will have to keep at least one reference to the object.
Bare in mind that ARC means Automatic Reference Counting. As soon as there is no
reference to an object it will be released.
This proposal with the engineRequests object defined here only works while you
submit only one request at a time. If you have several requests, i.e. for more than one cell or
whatver, then you may go for a mutable array or mutable dictionary where you keep the requests while you use them.
ViewController.m
#import "EngineResponses.h"
//this is where I set up the delegate/protocol for the parser class
- (void)viewDidLoad
{
[super viewDidLoad];
//..
engineResponses = [[EngineResponses alloc] init];
[engineResponses setMydelegate:self];
engineRequests = [[EngineRequests alloc] init]; // Use instance variable instead of local variable
[engineRequests setEnineResponses:engineResponses];
//..
}
//this is where i set up and call the connection class
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
if(indexPath.section == 0){
//..
if (indexPath.row == 0){
[engineRequests initalizePacketVariables:0 startCode:#"myReg" activationCode:#"myAct" methodName:#"GetStuff"];
//..
}
#pragma - Reciver methods
- (void)sendArray:(NSArray *)array
{
ICMfgFilterArray = array;
[self.tableView reloadData];
}
Explanation: The engineRequets is now an instance varaible and should not be re-defined locally.
You could define a variable of the same name locally which would hide the instance variable. I think
in that case you get a compiler warning but that will work and will most probably confuse you.
Again, if you use more than one request at a time then this solution will not work!
EngineRequests.h
EngineResponses *engineResponses;
EngineRequests.m
#synthesize engineResponses;
//connection delegates etc..
//then I pass the data from the connection delegates over to the parser class
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//EngineResponses *engineResponses = [[EngineResponses alloc] init]; //This Object has already been created!
[engineResponses ReciveResponse:receivedData];
}
Explanation: Here, too, the reference to EngineResponses is now an instance variable, not a locally defined one. The object will not be newly created but it references to that very object that was created in the view controller. That is the one EngineResponses that 'knows' its view controller object and can therefore pass back the parsed data.
EngineResponses.h
#protocol PassParsedData
- (void)sendArray:(NSArray *)array;
#end
//..
id <PassParsedData> mydelegate;
//..
#property (strong) id <PassParsedData> mydelegate;
EngineResponses.m
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
//..
[[self mydelegate]sendArray:filteredArray];
}
... give it a try :)
Always check for nil objects. Sending a message to a nil object will do nothing and your app will continue. I bet this is the problem since you are locally allocing all over the place. Why dont you make the receivedata method a static method instead since it looks like you dont need these classes for more than a few moments for some calculations and parsing. Then nil objects wont be a factor.

How to create a proxy protocol like UIAppearance

I know how to create a protocol already but I'm wondering what would be the best practice to create a proxy protocol like Apple did for the UIAppearance protocol and the implementation on certain UI classes.
Why I want to do it this way? Because I already have a lot of UI classes and I would like to centralize the implementation of the code for changing color.
Maybe an odd question but my curiosity drove me to this point.
Thanks
Just make the proxy a static object and access it through class-level methods, the same way you'd implement a singleton, e.g.
#implementation MyClass
+ (MyProxyObject *)proxy
{
static MyProxyObject *sharedProxy = nil;
if (sharedProxy == nil)
{
sharedProxy = [[MyProxyObject alloc] init];
}
return sharedProxy;
}
#end
Then for any property of your class, e.g. textColor, just have your class use the value in [[self class] proxy].textColor instead of storing its own value. E.g.
#interface MyClass : UIView
#property (nonatomic, strong) textColor
#end
#implementation MyClass
- (UIColor *)textColor
{
return textColor ?: [[self class] proxy].textColor
}
#end
If you need a way to refresh your onscreen views immediately whenever a property on the proxy is changed, you could do that by having the proxy broadcast an NSNotification in its textColor setter method, and have all the instances observe that notification and call setNeedsDisplay on themselves when they receive it.

Object-oriented design question, iPhone

Sorry I'm still a noob and just learning to program as I go and want to start out on the right foot by learning good design up front. I am using the CLLocationManager and MKReverseGecoder to get my location. In my MKReverseGecoderDelegate method, I create my annotation to show on the MKMapView. In my callout, I use a detail disclosure indicator to bring up another UITableView that displays your current address nicely as opposed to looking at the little black callout bubble.
What is a good way for my DetailViewController (the UITableView) to get the data? Do I have my first class have ivars for address, state, zipcode. In my MKReverseGecoderDelegate, set those ivars when I get that information. (The reason I would think I would need ivars is because my method to get that information in the MKReverseGeocoderDelegate is separate from the displayDetailViewController). And then do I have my DetailViewController have those same values, and when I go to display the DetailViewController, set those same variables? It seems redundant.
Any help would be greatly appreciated. Thanks!
One option
Declare custom class inheriting NSObject like
#interface YourClassName : NSObject
{
NSString *address;
NSString *state;
NSString *zipcode;
}
#property(nonatomic, retain) NSString *address;
#property(nonatomic, retain) NSString *state;
#property(nonatomic, retain) NSString *zipcode;
#end
#implementation YourClassName
#synthesize address,state,zipcode;
-(void)dealloc
{
[super dealloc];
[address release];
[state release];
[zipcode release];
}
#end
//Create object of YourClassName and set values
YourClassName *objYourClassName = [[YourClassName alloc] init];
objYourClassName.address = #"YourValue";
objYourClassName.state = #"YourValue";
objYourClassName.zipcode = #"YourValue";
Pass this object to your DetailViewController by one method after creating method like
-(void)setDetailsForDetailViewController:(YourClassName*)pObjYourClassName
{
//self.objOfYourClassName is property declared in your detailviewcontroller.
self.objOfYourClassName = pObjYourClassName; //You can use self.objOfYourClassName to set values in TableViewController.
}
If you stuck any where let me know I would be glad to help you fix that.
If you are doing the reverse geocoding on demand, initialize the DetailViewController with the coordinate of the annotation. Something like this:
- (id)initWithCoordinate:(CLLocation*)location {
if (self = [super initWithNibName:#"DetailController" bundle:nil]) {
self.location = location;
}
return self;
}
This is a common pattern to create the controllers, because it makes it clear for the controller's user that the controller depends on a location parameter. The other alternatives (global variables, or a singleton) are not so clean because they hide information and make the controller harder to understand and unit test.
Then let the controller launch an asynchronous task to do the geocoding, set itself as delegate, and present the information when it's done.

Category-like extension for instance variables

Is there a way to somehow emulate category behavior for a class regarding to it's instance variables, not methods ?
I have a ClassA, and I want to keep its name after extending it with new methods AND ivars from other cllass (ClassB).
Of course, I can inherit ClassA, but resulting class will have different name.
For methods addition, it's not a problem - category would be a good solution.
UPDATE: ClassA used as file owner for a XIB, and these fields to be extended are IBOutlets. So I need them at build phase.
Since the iPhone uses the modern Objective-C runtime, you can use associative references to add data to instances without having to declare instance variables. See the documentation for objc_setAssociatedObject etc.
If you wrap the calls to the runtime in standard accessor methods, it will be very easy to use.
I've investigated this question playing around associative references (thanks to Ole), with methods static variables, methods swizzling, and finally come to this simple solution (no runtime stuff). I simply use "categorized" class only to return a pointer to a derived class, which of course can contain additional ivars. Doing so I achieve one unexpected benefit: I can call super's class methods, which is impossible when extending through categories.
Example of a class extension (tested):
ClassA+ClassB.h
#protocol _ClassB_Protocol
#optional // to avoid warnings
- (IBAction) onClick:(id)sender;
#property (nonatomic, retain) IBOutlet UIButton *aButton;
#end
#interface ClassA (_ClassA_Category) <_ClassB_Protocol>
#end
#interface ClassB: ClassA <_ClassB_Protocol> {
UIButton *aButton; // _ivar_ to add
}
#end
ClassA+ClassB.m
#implementation ClassA (_ClassA_Category)
// this will be called first on [ClassA alloc] or [ClassA allocWithZone:(NSZone *)zone]
+(id) alloc {
if ([self isEqual: [ClassA class]]) {
return [ClassB alloc];
} else {
return [super alloc];
}
}
#end
#implementation ClassB: ClassA
#synthesize aButton;
-(void) dealloc {
[aButton release];
[super dealloc]; // this is impossible for an ordinary category
}
- (void) onClick:(id)sender {
// some code here
}
#end
Now we have in the same time:
ClassB "extends" ClassA (category way);
ClassB inherits ClassA (ClassB can call ClassA methods);
ClassB can be accessed through ClassA name (category way)
I put Martin's example into a trivial app replacing ClassA with NSData, ClassB with XXData, and onClick with getIvar, and invoked it (Mac OS X 10.6.6, Xcode 4 Final) with:
NSData * data = [NSData data];
NSLog(#"%#", [data getIvar]);
It fails with "-[NSConcreteData getIvar]: unrecognized selector sent to instance" ..
It fails because "alloc" in the NSData category (which returns the pointer to the derived class) is not called by the above code. If, instead, "alloc" is called explicitly, as in:
NSData * data = [[NSData alloc] init];
NSLog(#"%#", [data getIvar]);
then all is well.

Objective C terminology: outlets & delegates

I'm having issues understanding the concept of outlets how the iPhone deals with events. Help! Delegates confuse me too. Would someone care to explain, please?
Outlets (in Interface Builder) are member variables in a class where objects in the designer are assigned when they are loaded at runtime. The IBOutlet macro (which is an empty #define) signals Interface Builder to recognise it as an outlet to show in the designer.
For example, if I drag out a button, then connect it to the aButton outlet (defined in my interface .h file), the loading of the NIB file at runtime will assign aButton the pointer to that UIButton instantiated by the NIB.
#interface MyViewController : UIViewController {
UIButton *aButton;
}
#property(nonatomic, retain) IBOutlet UIButton *aButton;
#end
Then in the implementation:
#implementation MyViewController
#synthesize aButton; // Generate -aButton and -setAButton: messages
-(void)viewDidAppear {
[aButton setText:#"Do Not Push. No, seriously!"];
}
#end
This eliminates the need to write code to instantiate and assign the GUI objects at runtime.
As for Delegates, they are event receiving objects used by another object (usually a generalised API class such as a table view). There's nothing inherently special about them. It's more of a design pattern. The delegate class may define several of the expected messages such as:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
...and the API object calls this message on the delegate when it wants to notify it of the event. For example:
-(void)update:(double)time {
if (completed) {
[delegate process:self didComplete:totalTimeTaken];
}
}
And the delegate defines the message:
-(void)process:(Process *)process didComplete:(double)totalTimeTaken {
NSString *log = [NSString stringWithFormat:#"Process completed in %2.2f seconds.", totalTimeTaken];
NSLog(log);
}
Such use might be:
Process *proc = [Process new];
[proc setDelegate:taskLogger];
[proc performTask:someTask];
// Output:
// "Process completed in 21.23 seconds."
A delegate is a object that another object can forward messages to. In other words, it's like when your mom told you to clean your room and you pawned it off on your little brother. Your little bro knows how to do the job (since you were too lazy to ever learn) and so he does it for you.