1

I have a MKPolyline subblass which I want to implement NSCoding, i.e.

@interface RSRoutePolyline : MKPolyline <NSCoding>

I asked a question on the best way to encode the c-array and got an excellent answer. However, there is no init method defined on MKPolyline, i.e. there is no other way to give it data other than its class method polylineWithPoints:points.

Is this code where my comment is ok?

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    MKMapPoint *points = self.points;
    NSUInteger pointCount = self.pointCount;

    NSData *pointData = [NSData dataWithBytes:points length:pointCount * sizeof(MKMapPoint)];
    [aCoder encodeObject:pointData forKey:@"points"];
    [aCoder encodeInteger:pointCount forKey:@"pointCount"];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    NSData* pointData = [aDecoder decodeObjectForKey:@"points"];
    NSUInteger pointCount = [aDecoder decodeIntegerForKey:@"pointCount"];

    // Edit here from @ughoavgfhw's comment
    MKMapPoint* points = (MKMapPoint*)[pointData bytes];

    // Is this line ok?
    self = (RSRoutePolyline*)[MKPolyline polylineWithPoints:points count:pointCount];

    return self;
}
Community
  • 1
  • 1
Robert
  • 37,670
  • 37
  • 171
  • 213
  • 2
    Your `memcpy` is in the wrong direction. The destination goes first. And you could just use `[pointData bytes]` instead of allocating and copying the data. – ughoavgfhw Feb 16 '13 at 19:36
  • @ughoavgfhw Thanks, I have edited my answer.. Is the line: `MKMapPoint* points = (MKMapPoint*)[pointData bytes];` what you ment? – Robert Feb 16 '13 at 19:43
  • __bridge_retained needed? I get confused with woking with c bridges. – Robert Feb 16 '13 at 19:49
  • 1
    No, a normal `__bridge` will suffice I believe. I addressed this in http://stackoverflow.com/questions/14854521/where-and-how-to-bridge/14855784#14855784 – WDUK Feb 16 '13 at 19:53
  • I have had 3 good answers... composition, categories and overriding property. -I dont know what to do now :) – Robert Feb 16 '13 at 20:07
  • Whatever you feel's best within the context you're working in. The answer to the "Is it ok not to invoke [super init] in a custom init method?" question is No, and they're the viable alternatives :) – WDUK Feb 16 '13 at 20:14

3 Answers3

2

It's dirty not to call [super init], and it doesn't bode well with my idea of good programming. Without calling super yourself, it isn't a true subclass; just a bastardization of composition that relies on a side effect of calling a convenience constructor. Saying this, I believe your method described will work OK, but it goes against the grain of good Objective-C programming and its conventions.

What I would suggest is to use MKPolyLine as an MKPolyLine instance, and use a category to add the extra bells and whistles you need. As for adding extra instance variables and such, you can use associated objects. An introduction to this concept can be found here, and this SO question addresses the use of them with categories: How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?

Community
  • 1
  • 1
WDUK
  • 18,870
  • 3
  • 64
  • 72
  • Good points, however using a subclass is usefully for me since I have more than one type of MKPolyLine on my mapView and so I can use `[overlay isKindOfClass...` in `viewForOverlay:` do distinguish between them. – Robert Feb 16 '13 at 19:54
  • In that case, is it possible for you to use container objects, or follow a particular protocol to achieve what you need, instead of subclassing `MKPolyLine`? I'm not sure of the context of where this is being used. Just because inheriting is convenient, doesn't mean it's the correct thing to do. – WDUK Feb 16 '13 at 20:01
2

You should call an init method on any subclass of NSObject. Since MKPolyline is an NSObject, you should init it.

But MKPolyline has no methods and no init. This is Objective C's was of telling you that you can't subclass it.

Instead, as WDUK suggested, define your own class. It keeps track of your list point points, and manages NSCoding to save and restore them as needed.

 @interface RSPolyline: NSObject<NSCoding>

 - (id) initWithPoints: (NSArray*) points;
 - (id) initWithCoder:(NSCoder *)aDecoder;
 - (void) encodeWithCoder:(NSCoder *)aCoder;

 - (MKPolyline*) polyLine;

 @end

Your class can generate a polyline on request, perhaps caching the result if performance is an issue.

As a rule, don't reach for inheritance first. When you want to extend and improve a class, think first of composition.

Mark Bernstein
  • 2,090
  • 18
  • 23
1

While it is generally allowed to create and return a different object in an init method, there are three problems with that line (explained below). Instead of this, I would suggest overriding the points and pointCount properties so that you can return values stored in an instance variable, and call the super implementation there if the instance variable is empty. Then, your initializer just sets these instance variables so that they will be used.

- (MKMapPoint *)points {
    if(myPointsIvar == NULL) return [super points];
    else return myPointsIvar;
}
// similarly for pointCount

The first problem is that you are creating a new object, but not releasing the old one, which means you are leaking it. You should store the result in a different variable, then release self, then return the result (you don't need to store it in self).

Second, polylineWithPoints:count: returns an autoreleased object, but initWithCoder: should return a retained one. Unless there is another retain on it, it could be deallocated while you are still using it.

If these were the only problems, you could solve both like this:

MKPolyline *result = [MKPolyline polylineWithPoints:points count:pointCount];
[self release];
return [result retain];

However, there is a third problem which cannot be solved so easily. polylineWithPoints:count: does not return a RSRoutePolyline object, and the object it returns may not be compatible with your subclass's methods (e.g. it probably won't support NSCoding). There really isn't a way to fix this, so you can't use polylineWithPoints:count:.

ughoavgfhw
  • 39,734
  • 6
  • 101
  • 123
  • For what it's worth, `[RSPolyline polylineWithPoints:...]` actually returns an instance of the subclass. Not sure if I'd rely on that though, given that the method signature doesn't indicate this by having a return type of `id`... – omz Feb 16 '13 at 20:08