Instantly share code, notes, and snippets.

Embed
What would you like to do?
This was nice and fast... but it takes up WAY TOO MUCH space. And it's annoying to write.
(I like the way I can just step into the function that calls the delegate for debugging)
What's a better way to not make me call respondsToSelector in the middle of my code? Some proxy?
struct {
unsigned int delegateWillDisplayDocument:1;
unsigned int delegateDidDisplayDocument:1;
unsigned int delegateDidShowPageView:1;
unsigned int delegateDidRenderPageView:1;
unsigned int delegateDidChangeViewMode:1;
unsigned int delegateDidTapOnPageView:1;
unsigned int delegateDidTapOnAnnotation:1;
unsigned int delegateShouldDisplayAnnotation:1;
unsigned int delegateViewForAnnotation:1;
unsigned int delegateAnnotationViewForAnnotation:1;
unsigned int delegateWillShowAnnotationView:1;
unsigned int delegateDidShowAnnotationView:1;
unsigned int delegateDidLoadPageView:1;
unsigned int delegateWillUnloadPageView:1;
unsigned int delegateDidEndPageScrollingAnimation:1;
unsigned int delegateDidEndZoomingAtScale:1;
unsigned int delegateWillShowControllerAnimated:1;
unsigned int delegateDidShowControllerAnimated:1;
} delegateFlags_;
- (void)setDelegate:(id<PSPDFViewControllerDelegate>)delegate {
if (delegate != delegate_) {
delegate_ = delegate;
delegateFlags_.delegateWillDisplayDocument = [delegate respondsToSelector:@selector(pdfViewController:willDisplayDocument:)];
delegateFlags_.delegateDidDisplayDocument = [delegate respondsToSelector:@selector(pdfViewController:didDisplayDocument:)];
delegateFlags_.delegateDidShowPageView = [delegate respondsToSelector:@selector(pdfViewController:didShowPageView:)];
delegateFlags_.delegateDidRenderPageView = [delegate respondsToSelector:@selector(pdfViewController:didRenderPageView:)];
delegateFlags_.delegateDidChangeViewMode = [delegate respondsToSelector:@selector(pdfViewController:didChangeViewMode:)];
delegateFlags_.delegateDidTapOnPageView = [delegate respondsToSelector:@selector(pdfViewController:didTapOnPageView:info:coordinates:)];
delegateFlags_.delegateDidTapOnAnnotation = [delegate respondsToSelector:@selector(pdfViewController:didTapOnAnnotation:page:info:coordinates:)];
delegateFlags_.delegateShouldDisplayAnnotation = [delegate respondsToSelector:@selector(pdfViewController:shouldDisplayAnnotation:onPageView:)];
delegateFlags_.delegateViewForAnnotation = [delegate respondsToSelector:@selector(pdfViewController:viewForAnnotation:onPageView:)];
delegateFlags_.delegateAnnotationViewForAnnotation = [delegate respondsToSelector:@selector(pdfViewController:annotationView:forAnnotation:onPageView:)];
delegateFlags_.delegateWillShowAnnotationView = [delegate respondsToSelector:@selector(pdfViewController:willShowAnnotationView:onPageView:)];
delegateFlags_.delegateDidShowAnnotationView = [delegate respondsToSelector:@selector(pdfViewController:didShowAnnotationView:onPageView:)];
delegateFlags_.delegateDidLoadPageView = [delegate respondsToSelector:@selector(pdfViewController:didLoadPageView:)];
delegateFlags_.delegateWillUnloadPageView = [delegate respondsToSelector:@selector(pdfViewController:willUnloadPageView:)];
delegateFlags_.delegateDidEndPageScrollingAnimation = [delegate respondsToSelector:@selector(pdfViewController:didEndPageScrollingAnimation:)];
delegateFlags_.delegateDidEndZoomingAtScale = [delegate respondsToSelector:@selector(pdfViewController:didEndPageZooming:atScale:)];
delegateFlags_.delegateWillShowControllerAnimated = [delegate respondsToSelector:@selector(pdfViewController:willShowController:embeddedInController:animated:)];
delegateFlags_.delegateDidShowControllerAnimated = [delegate respondsToSelector:@selector(pdfViewController:didShowController:embeddedInController:animated:)];
}
}
- (void)delegateWillDisplayDocument {
if (delegateFlags_.delegateWillDisplayDocument) {
[self.delegate pdfViewController:self willDisplayDocument:self.document];
}
}
- (void)delegateDidDisplayDocument {
if(delegateFlags_.delegateDidDisplayDocument) {
[self.delegate pdfViewController:self didDisplayDocument:self.document];
}
}
(etc, etc)
@jonsterling

This comment has been minimized.

Show comment
Hide comment
@jonsterling

jonsterling Jun 4, 2012

So, if you don't want to use these structs, you can use a proxy; you could probably even get the proxy to cache the information. But it will still be slightly slower than this.

If you don't want to go that route, you can at least clean this up by using a compound literal; also, don't prepend every single field with delegate. It's not necessary, and makes you type more.

// first, give your struct a real type, you'll see why in a second
struct _delegate_flags_t { ... } _delegateResponds;

// then, assign to it when needed
_delegateResponds = (_delegate_flags_t) { 
  .viewForAnnotation = [delegate respondsToSelector:@selector(pdfViewController:viewForAnnotation:onPageView)];
  // ...
};

jonsterling commented Jun 4, 2012

So, if you don't want to use these structs, you can use a proxy; you could probably even get the proxy to cache the information. But it will still be slightly slower than this.

If you don't want to go that route, you can at least clean this up by using a compound literal; also, don't prepend every single field with delegate. It's not necessary, and makes you type more.

// first, give your struct a real type, you'll see why in a second
struct _delegate_flags_t { ... } _delegateResponds;

// then, assign to it when needed
_delegateResponds = (_delegate_flags_t) { 
  .viewForAnnotation = [delegate respondsToSelector:@selector(pdfViewController:viewForAnnotation:onPageView)];
  // ...
};
@Daij-Djan

This comment has been minimized.

Show comment
Hide comment
@Daij-Djan

Daij-Djan Jun 4, 2012

wrap in a C preprocessor macro
(can't write it off the top of my head but the call could be just like a function)

OR

make it an inlined function (better to debug + cleaner) or a category on nsobject


e.g. id performSelectorIfAvailable(id target, SEL selector, id args, ...)

Daij-Djan commented Jun 4, 2012

wrap in a C preprocessor macro
(can't write it off the top of my head but the call could be just like a function)

OR

make it an inlined function (better to debug + cleaner) or a category on nsobject


e.g. id performSelectorIfAvailable(id target, SEL selector, id args, ...)

@jverkoey

This comment has been minimized.

Show comment
Hide comment
@jverkoey

jverkoey Jun 4, 2012

Out of genuine curiousity, how much of a performance improvement does respondsToSelector: caching provide?

jverkoey commented Jun 4, 2012

Out of genuine curiousity, how much of a performance improvement does respondsToSelector: caching provide?

@jonsterling

This comment has been minimized.

Show comment
Hide comment
@jonsterling

jonsterling Jun 4, 2012

I'd be interested in hearing about that too. I tend to make these gigantic structs not because I care about caching, but rather because it's rather more elegant to check a flag than to litter my code with super-ugly -respondsToSelector: messages. And because I've learnt to be cautious with proxies.

jonsterling commented Jun 4, 2012

I'd be interested in hearing about that too. I tend to make these gigantic structs not because I care about caching, but rather because it's rather more elegant to check a flag than to litter my code with super-ugly -respondsToSelector: messages. And because I've learnt to be cautious with proxies.

@steipete

This comment has been minimized.

Show comment
Hide comment
@steipete

steipete Jun 4, 2012

I did a few tests back then, but it's not a big performance hit. My reasons were much more to have a consistent jump function (and partly because I've seen Apple doing it)

But writing this stuff manually is rather annoying. Is there an example somewhere how proxy forwarding would look?

Owner

steipete commented Jun 4, 2012

I did a few tests back then, but it's not a big performance hit. My reasons were much more to have a consistent jump function (and partly because I've seen Apple doing it)

But writing this stuff manually is rather annoying. Is there an example somewhere how proxy forwarding would look?

@jonsterling

This comment has been minimized.

Show comment
Hide comment
@jonsterling

jonsterling Jun 4, 2012

I'm sure there is, but it might be slightly different under ARC. You can't use -forwardingTargetForSelector:, because if you return nil from there, it will just try to use -forwardInvocation:. Basically:

  1. Implement -methodSignatureForSelector: or whatever, using your target as a base.
  2. Implement -forwardInvocation:. If your target responds to the selector, set the return value of the invocation to your target's address. If the target doesn't respond, set the return value to nil. You'll probably need to fiddle around with ownership qualifiers here (like __autoreleasing). Someone who has done this more recently than I have can probably help if you have trouble.

But remember! This is a whole new layer you have to debug. So, it's pretty cool to have, but make sure you're committed to making it perfect.

jonsterling commented Jun 4, 2012

I'm sure there is, but it might be slightly different under ARC. You can't use -forwardingTargetForSelector:, because if you return nil from there, it will just try to use -forwardInvocation:. Basically:

  1. Implement -methodSignatureForSelector: or whatever, using your target as a base.
  2. Implement -forwardInvocation:. If your target responds to the selector, set the return value of the invocation to your target's address. If the target doesn't respond, set the return value to nil. You'll probably need to fiddle around with ownership qualifiers here (like __autoreleasing). Someone who has done this more recently than I have can probably help if you have trouble.

But remember! This is a whole new layer you have to debug. So, it's pretty cool to have, but make sure you're committed to making it perfect.

@steipete

This comment has been minimized.

Show comment
Hide comment
@steipete

steipete Jun 4, 2012

uhh... I really don't wanna mess around with NSInvocation, that would be orders of magnitude slower.
Isn't there a new way, what if we just do the respondsToSelector check in forwardingTargetForSelector and decide if we return the delegate target, and don't do anything in -forwardInvocation:? But still, I'm not sure if I wanna go there. Maybe a macro/inline function is the cleaner way.

Owner

steipete commented Jun 4, 2012

uhh... I really don't wanna mess around with NSInvocation, that would be orders of magnitude slower.
Isn't there a new way, what if we just do the respondsToSelector check in forwardingTargetForSelector and decide if we return the delegate target, and don't do anything in -forwardInvocation:? But still, I'm not sure if I wanna go there. Maybe a macro/inline function is the cleaner way.

@steipete

This comment has been minimized.

Show comment
Hide comment
@steipete

steipete Jun 4, 2012

Also I'm not sure if I fully grasp the compound literal trick - I can build the struct on the fly? struct _delegate_flags_t { ... }

Owner

steipete commented Jun 4, 2012

Also I'm not sure if I fully grasp the compound literal trick - I can build the struct on the fly? struct _delegate_flags_t { ... }

@jonsterling

This comment has been minimized.

Show comment
Hide comment
@jonsterling

jonsterling Jun 4, 2012

  1. Like I said, you can't use -forwardingTargetForSelector:, because if nothing happens there, the runtime will try -forwardInvocation:.
  2. The compound literal is so that you can build the struct in one pass, without having to set all of its values individually. So, the following are equivalent:
// this is retarded:
point.x = 1.f;
point.y = 3.f;

// this is awesome:
point = (CGPoint) { .x = 1.f, .y = 3.f };

jonsterling commented Jun 4, 2012

  1. Like I said, you can't use -forwardingTargetForSelector:, because if nothing happens there, the runtime will try -forwardInvocation:.
  2. The compound literal is so that you can build the struct in one pass, without having to set all of its values individually. So, the following are equivalent:
// this is retarded:
point.x = 1.f;
point.y = 3.f;

// this is awesome:
point = (CGPoint) { .x = 1.f, .y = 3.f };
@Daij-Djan

This comment has been minimized.

Show comment
Hide comment
@Daij-Djan

Daij-Djan Jun 5, 2012

a macro or a simple function or method would look good and could even use the caching (don't know If Id want that though)...

anyways :) id be interested in hearing what you finally decide on

Daij-Djan commented Jun 5, 2012

a macro or a simple function or method would look good and could even use the caching (don't know If Id want that though)...

anyways :) id be interested in hearing what you finally decide on

@macguru

This comment has been minimized.

Show comment
Hide comment
@macguru

macguru Jun 5, 2012

I prefer to check the respondsToSelector in place. Generally rather dislike such caches. I usually don't have that much delegate methods though...

However, you could build a proxy class that assembles method implementations automatically using runtime calls. That'd be extremely elegant, albeit a bit of work and probably shooting with canons on pigeons. You could open-source it afterwards though ;)

macguru commented Jun 5, 2012

I prefer to check the respondsToSelector in place. Generally rather dislike such caches. I usually don't have that much delegate methods though...

However, you could build a proxy class that assembles method implementations automatically using runtime calls. That'd be extremely elegant, albeit a bit of work and probably shooting with canons on pigeons. You could open-source it afterwards though ;)

@hatfinch

This comment has been minimized.

Show comment
Hide comment
@hatfinch

hatfinch Aug 1, 2012

No proxy tricks, but FWIW I use ids directly:

struct
{
    id willDisplayDocumentDelegate;
    id didDisplayDocumentDelegate;
    // etc.
} _delegates;

- (void)setDelegate:(id <PSPDFViewControllerDelegate>)delegate
{
    if (delegate)
    {
        if ([delegate respondsToSelector:@selector(pdfViewController:willDisplayDocument:)])
            _delegates.willDisplayDocumentDelegate = delegate;

        if ([delegate respondsToSelector:@selector(pdfViewController:didDisplayDocument:)])
            _delegates.didDisplayDocumentDelegate = delegate;

        // etc.
    }
    else
        memset(_delegates, 0, sizeof(_delegates));
}

- (void)delegateWillDisplayDocument
{
    [_delegates.willDisplayDocumentDelegate pdfViewController:self willDisplayDocument:self.document];
}

- (void)delegateDidDisplayDocument
{
    [delegates_.didDisplayDocumentDelegate pdfViewController:self didDisplayDocument:self.document];
}

// etc.

hatfinch commented Aug 1, 2012

No proxy tricks, but FWIW I use ids directly:

struct
{
    id willDisplayDocumentDelegate;
    id didDisplayDocumentDelegate;
    // etc.
} _delegates;

- (void)setDelegate:(id <PSPDFViewControllerDelegate>)delegate
{
    if (delegate)
    {
        if ([delegate respondsToSelector:@selector(pdfViewController:willDisplayDocument:)])
            _delegates.willDisplayDocumentDelegate = delegate;

        if ([delegate respondsToSelector:@selector(pdfViewController:didDisplayDocument:)])
            _delegates.didDisplayDocumentDelegate = delegate;

        // etc.
    }
    else
        memset(_delegates, 0, sizeof(_delegates));
}

- (void)delegateWillDisplayDocument
{
    [_delegates.willDisplayDocumentDelegate pdfViewController:self willDisplayDocument:self.document];
}

- (void)delegateDidDisplayDocument
{
    [delegates_.didDisplayDocumentDelegate pdfViewController:self didDisplayDocument:self.document];
}

// etc.
@MichaelHackett

This comment has been minimized.

Show comment
Hide comment
@MichaelHackett

MichaelHackett Mar 11, 2014

I like your approach, @hatfinch, but should the zeroing of the _delegates table not should happen first, and unconditionally, as the if statements only set the fields for methods that are present in the new delegate. If there was a previous setting for a field and the new delegate doesn't implement that method, the old value would still be present (causing the old delegate to be called for some methods).

Also, I believe that object refs in a struct are no longer allowed under ARC, but (__weak) ivars could work just as well.

MichaelHackett commented Mar 11, 2014

I like your approach, @hatfinch, but should the zeroing of the _delegates table not should happen first, and unconditionally, as the if statements only set the fields for methods that are present in the new delegate. If there was a previous setting for a field and the new delegate doesn't implement that method, the old value would still be present (causing the old delegate to be called for some methods).

Also, I believe that object refs in a struct are no longer allowed under ARC, but (__weak) ivars could work just as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment