Get notifications when an NSData/CFData object is being read?

169 Views Asked by At

I'm using an API that takes a CFData object, and I need to generate the data on the fly, which is a time consuming operation. The CFData is read by the consumer in a random, non-contiguous manner, and it rarely actually needs the entire length of the data -- it just needs certain random pieces of it.

I would like to improve performance by only actually generating the bits that are requested by the consumer, as they are requested.

Is there any way to subclass CFData/NSData such that I would get callbacks as data chunks are being read and generate them on the fly?

Update: Unfortunately the consumer is 3rd party code and so other classes aren't an option, unless they're somehow magically compatible with CFData.

3

There are 3 best solutions below

0
On BEST ANSWER

I don't think you're going to like the answers you get here. You seem to be cornered with respect to options.

What we know about your situation

  1. The client's code cannot accept a different type / class
  2. CFData is a CoreFoundation C-type and doesn't have a runtime to play with
  3. The client probably uses CoreFoundation symbols to read this data
    1. If you're lucky, it's through the Foundation APIs in Obj-C which has a runtime
  4. CFData wraps a void * to data, but has some padding at the beginning (likely a struct) that'll be difficult to mimic if you tried to create a CFData proxy object

It's going to be very difficult to accomplish what you want without some clever, un-maintainable, and overall dangerous symbol hackery I won't even bother to get into.

1
On

Perhaps you're wanting something more like NSInputStream:

NSInputStream is a subclass of NSStream that provides read-only stream functionality.

NSInputStream is “toll-free bridged” with its Core Foundation counterpart, CFReadStreamRef. For more information on toll-free bridging, see Toll-Free Bridging.

Subclassing Notes

NSInputStream is an abstract superclass of a class cluster consisting of concrete subclasses of NSStream that provide standard read-only access to stream data. Although NSInputStream is probably sufficient for most situations requiring access to stream data, you can create a subclass of NSInputStream if you want more specialized behavior (for example, you want to record statistics on the data in a stream).

0
On

CFDataRef and NSData are toll-free bridged. That mean, you can pass NSData instance into code, that expect CFData.

NSData is a class cluster. That mean, you can subclass it, and implement only primitive methods and initializers. For NSData primitive methods are:

@property (readonly) NSUInteger length;
@property (readonly) const void *bytes NS_RETURNS_INNER_POINTER;

You must also implement some init... method, that will initialize your instance.

All other NSData functionality is provided in categories. You still can override any method and provide your own implementation.

As an option, you can start from NSData subclass that will delegate any methods to internal NSData instance, which you can store in property. In this way you can observe 3rd party code behavior and investigate which methods you need to override.

Still, you will need a lot of luck to be able to implement what you want. But it may be possible.

As an example, I've created following dummy NSData subclass:

@interface MyData : NSData
@property (nonatomic, strong) NSData *innerData;
@end

@implementation MyData

- (id)initWithData:(NSData *)data {
    if (self = [super init]) {
        _innerData = data;
    }
    return self;
}

- (NSUInteger)length {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    return self.innerData.length;
}

- (const void *)bytes {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    return self.innerData.bytes;
}

@end

When I invoke following code:

NSData *originalData = [NSData dataWithBytes:"hello" length:5];
MyData *myData = [[MyData alloc] initWithData:originalData];
NSLog(@"%ld", CFDataGetLength((CFDataRef)myData));

length method of my subclass is called.