29

Is there any way to delegate to two objects at a time in Objective-C? I know that delegation pattern implies one response at a time and for multiple listeners and broadcasting there is notification center but notification does not return any value.

If I have a heavily network-based iOS project and need to delegate to multiple listeners and required to return values from them, in this scenario what approach should be the best?

Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
S.J
  • 3,063
  • 3
  • 33
  • 66
  • I didn't understand: one class that is a delegate of 2 services? Or 2 class that must be the delegate of 1 the same service (so the service has 2 delegates)? – Marco Pace Jan 11 '13 at 13:09
  • 1
    You can pack data in notifications using the `userInfo` option. – Guillaume Jan 11 '13 at 13:22
  • take it like this...that my A class is broadcaster and class B & C are listeners and B & C also need to return values to class A. Not one at a time like a normal obj-c delegate but both at a time. – S.J Jan 11 '13 at 13:23
  • The "broadcasting" is easy to handle with notifications. The returning of values "both at a time" is not so easy to handle -- it's not clear what you mean there. – Hot Licks Jan 11 '13 at 13:25
  • 3
    Dup of [this](http://stackoverflow.com/q/1382241/1354100) question, which has a great answer. – Bejmax Jan 11 '13 at 13:31
  • The thing that's unclear is returning the data. If you call two delegates at once, how do they both return data? What does that even mean? – Hot Licks Jan 11 '13 at 16:52
  • The accepted answer is incorrect. A delegate should be weak so this example is error prone or in best case it creates a retain cycle. The accepted answer has a strong reference to a set which, by design, also has strong references to its collection, which creates a retain cycle. The developer should not take care of deallocating the delegates manually since is error prone. The best answer IMHO is the one from @Nils. BUT the best way to deal with this design paradigm is to use NSNotifications instead. – Razvan Aug 24 '15 at 08:50

6 Answers6

32

In every class the delegate is one, so one delegate is informed about the event. But nothing forbids you to declare a class with a set of delegates.

Or use Observation instead. A class may be observed by multiple classes.

Example

As requested from the OP, since also some code would be useful, here is a way of doing it:

@interface YourClass()

@property (nonatomic, strong, readwrite) NSPointerArray* delegates;
// The user of the class shouldn't even know about this array
// It has to be initialized with the NSPointerFunctionsWeakMemory option so it doesn't retain objects

@end  

@implementation YourClass

@synthesize delegates;

...   // other methods, make sure to initialize the delegates set with alloc-initWithOptions:NSPointerFunctionsWeakMemory

- (void) addDelegate: (id<YourDelegateProtocol>) delegate
{
    [delegates addPointer: delegate];
}

- (void) removeDelegate: (id<YourDelegateProtocol>) delegate
{
    // Remove the pointer from the array
    for(int i=0; i<delegates.count; i++) {
        if(delegate == [delegates pointerAtIndex: i]) {
            [delegates removePointerAtIndex: i];
            break;
        }
    } // You may want to modify this code to throw an exception if no object is found inside the delegates array
}

@end

This is a very simple version, you can do it in another way. I don't suggest to make public the delegates set, you never know how it could be used, and you can get an inconsistent state, specially with multithreading. Also, when you add/remove a delegate you may need to run additional code, so that's why making the delegates set private.
You may also a lot of other methods like delegatesCount for example.

PS: The code has been edited to be a NSPointerArray instead of a NSMutableSet, because as stated in the comments a delegate should be held with a weak pointer to avoid retain cycles.

Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • 1
    interesting, i've never thought about this before. By applying what you've just stated i could set delegates to ten different actions and thereafter have a function that validates each and every delegate callback. +1 cuz i never thought about this. – Jens Bergvall Jan 11 '13 at 13:25
  • @RaRamy Al Zuhouri I created new tabbed view project in Xcode and created a protocol in app delegate, now both FirstViewController and SecondViewController implements the protocol but only SecondViewController is responding, I want both to respond. I am sorry but still confused after reading your reply. Thanks a lot for replying. – S.J Feb 09 '13 at 22:31
  • It depends on how you're written the code. Post the code editing the question please, or send it to me somehow. – Ramy Al Zuhouri Feb 09 '13 at 22:34
  • @RamyAlZuhouri please view my new question, this contains all details http://stackoverflow.com/questions/14785201/objective-c-multicasting-delegates – S.J Feb 09 '13 at 22:43
  • I answered, but the answer is very similar to this one, but it seems like you haven't understood it. If you need any further clarification you may explain it in chat. – Ramy Al Zuhouri Feb 09 '13 at 23:07
  • 1
    Small suggestion: use a NSMutableSet instead of a NSMutableArray to make sure that a delegate is added only once. You probably don't want to call a delegate more than once for one event. – Yvo Feb 23 '14 at 05:55
  • 2
    And don't forget to call removeDelegate in your deallocs. – Yvo Feb 23 '14 at 05:56
  • 2
    This answer is error-prone. You must call "removeDelegate" BEFORE the dealloc function. Since this mutable set will increase the retain count, you will have a strong reference to the delegate object. Therefore, the 'dealloc' function of the delegate will NEVER be called. Thus, you have to release it manually, somewhere else. Be careful, this answer will surely lead to hidden memory leaks this way. Use this implementation, instead, which uses simple plain - old pointers: https://github.com/sergeyzenchenko/MulticastDelegate/blob/master/GCDMulticastDelegate.h – csotiriou Nov 16 '14 at 10:57
  • 1
    instead of a [NSMutableSet set]; use [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; as it will hold a weak reference to the delegate objects and will avoid the retain cycle for them – Nav Mar 24 '15 at 10:14
  • A delegate should be weak so this example is error prone or in best case it creates a retain cycle. – Razvan Aug 24 '15 at 08:32
  • 1
    I modified the code so that now the delegates are an array of weak pointers. – Ramy Al Zuhouri Sep 22 '15 at 12:56
  • On addPointer I'm getting error Implicit conversion of Objective-C pointer type 'id' to C pointer type 'void * _Nullable' requires a bridged cast – Radek Wilczak Feb 05 '18 at 13:27
21

In addition to Ramys answer you could use a [NSHashTable weakObjectsHashTable] instead of a NSMutableSet. This would keep only a weak reference to your delegates and prevents you from running into memory leaks. You will get the same behavior you already know from standard weak delegates @property (nonatomic, weak) id delegate;

@interface YourClass()

@property (nonatomic, strong) NSHashTable *delegates;

@end  

@implementation YourClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        _delegates = [NSHashTable weakObjectsHashTable];
    }
    return self;
}

- (void) addDelegate: (id<YourDelegateProtocol>) delegate
{
    // Additional code
    [_delegates addObject: delegate];
}

// calling this method is optional, because the hash table will automatically remove the delegate when it gets released
- (void) removeDelegate: (id<YourDelegateProtocol>) delegate
{
    // Additional code
    [_delegates removeObject: delegate];
}

@end
Community
  • 1
  • 1
Nils
  • 319
  • 2
  • 5
  • You need to flesh out your answer so it doesn't depend on someone else's to be understood and followed. Please update your answer with the information, making sure to cite "Ramys" and any other(s). – Lizz May 15 '14 at 07:25
  • Best answer! Should be the accepted one for the question! But IMHO, the best way to tackle the problem posted by the OP should be to use `NSNotifications` instead of multiple delegates. – Razvan Aug 24 '15 at 08:37
  • @Razvan using notification produces alot os spegatti code which is not scalable – hariszaman Dec 14 '17 at 21:00
  • And also see the performance diff here https://stackoverflow.com/questions/17921155/nsnotificationcenter-vs-delegation-which-is-faster – hariszaman Dec 14 '17 at 21:33
3

If you're writing the function that will call the delegates, you can have as many as you want. But if you're using a class (that you can't change) that calls the delegates, then you can't have more delegates than the class supports.

You could, if it worked out for you, have one delegate call another. Set up the first delegate so it will call the second delegate (whose pointer is stored in the first delegate object). This can be simple, with it pre-defined as to which calls are "passed on", or quite complex, using the dynamic call mechanisms of Objective-C.

Hot Licks
  • 47,103
  • 17
  • 93
  • 151
3

Robbie Hanson wrote a multicast delegate implementation. Looks like what you need. He talks about it in more detail here, and how it is used in the XMPPFramework. He has some good discussion about one of the main problems which is how to handle the case where the multiple delegates implement a given method who's return value determines the class' behaviour (and the multiple delegates return different values). Relevant bits:

What is a MulticastDelegate?

The xmpp framework needs to support an unlimited number of extensions. This includes the official extensions that ship with the framework, as well as any number of extensions or custom code you may want to plug into the framework. So the traditional delegate pattern simply won't work. XMPP modules and extensions need to be separated into their own separate classes, yet each of these classes needs to receive delegate methods. And the standard NSNotification architecture won't work either because some of these delegates require a return variable. (Plus it's really annoying to extract parameters from a notification's userInfo dictionary.)

So a MulticastDelegate allows you to plug into the framework using the standard delegate paradigm, but it allows multiple classes to receive the same delegate notifications. The beauty of this is that you don't have to put all your xmpp handling code in a single class. You can separate your handling into multiple classes, or however you see fit.

jbat100
  • 16,757
  • 4
  • 45
  • 70
  • Yes Sir thats where I want to draw the attention, I am currently working on this framework but having some difficulties, so need to research things in depth for firm grip. I be grateful if you help me understand this. Thank You. – S.J Jan 12 '13 at 04:48
  • The discussions on concepts and implementation in the link are quite detailed so you should ask specific questions on what you don't understand. – jbat100 Jan 12 '13 at 12:37
3

One delegate can be setting for only one object but it's possible to store delegates in array. Variant of Ramy Al Zuhouri is good but I want to say that it may be a problem to release delegates from array because NSArray (like NSMutableArray) classes retain all added objects but delegate in most cases is an assign property without retainCount. Retaining the delegate can bring to consequences that class with delegate implementation will have retainCount + 1. Solution of this is store delegates in NSMutableArray like pointers to delegate methods. I'm using singletone class with delegate header.

//YourClass.h file
@protocol YourDelegateProtocol <NSObject>    
-(void)delegateMethod;    
@end


@interface YourClass : NSObject    
+(YourClass *)sharedYourClass;
- (void) addDelegate: (id<YourDelegateProtocol>) delegate;
- (void) removeDelegate: (id<YourDelegateProtocol>) delegate
@end



//YourClass.m file
@interface YourClass()
@property (nonatomic, retain) NSMutableArray *delegates;    
-(void)runAllDelegates;
@end

@implementation YourClass

@synthesize delegates = _delegates;        

static YourClass *sharedYourClass = nil;

+(YourClass *)sharedYourClass {
    if (!sharedYourClass || sharedYourClass == nil) {            
        sharedYourClass = [YourClass new];      
        sharedYourClass.delegates = [NSMutableArray array];
    }
    return sharedYourClass;
}

-(void)addDelegate: (id<YourDelegateProtocol>) delegate{

    NSValue *pointerToDelegate = [NSValue valueWithPointer:delegate];
    [_delegates addObject: pointerToDelegate];
}

-(void)removeDelegate: (id<YourDelegateProtocol>) delegate{

    NSValue *pointerToDelegate = [NSValue valueWithPointer:delegate];
    [_delegates removeObject: pointerToDelegate];
}      

-(void)runAllDelegates{
//this method will run all delegates in array
    for(NSValue *val in sharedYourClass.delegates){
        id<YourDelegateProtocol> delegate = [val pointerValue];
        [delegate delegateMethod];
    }
}

-(void)dealloc{
    sharedYourClass.delegates =nil;
    [sharedYourClass release], sharedYourClass =nil;
    [super dealloc];
}

@end





//YourClassWithDelegateImplementation.h file
#include "YourClass.h"
@interface YourClassWithDelegateImplementation : NSObject <YourDelegateProtocol>        
@end

//YourClassWithDelegateImplementation.m file       
@implementation YourClassWithDelegateImplementation
-(id)init{
    self = [super init];
    if(self){
        //...your initialization code
        [[YourClass sharedYourClass] addDelegate:self];     
    }
    return self;
}

-(void)delegateMethod{
//implementation of delegate
}

-(void)dealloc{    
    [[YourClass sharedYourClass] removeDelegate:self];
    [super dealloc];
}
@end
Stan
  • 122
  • 4
1

If you want to call callbacks for classes B and C from a class A with only one delegate, you could create a delegate wrapper DWrap which has references to the classes B and C. Then class A calls the callbacks on B and C through DWrap.

mgr
  • 342
  • 2
  • 7