4

I create new tabbed view project in xcode, in appdelegate I created a protocol

.h file

@protocol myProtocol <NSObject>
-(void)myProtocolMethodOne;
@end
.
.
.
@property (weak) id<myProtocol> mypDelegate;

.m file

@synthesize mypDelegate;
.
.
.
//Inside didFinishLaunchingWithOptions
[mypDelegate myProtocolMethodOne];

In firstViewController & secondViewController (both are displayed as two different tab) I did this in both

AppDelegate *ad = (AppDelegate*)[[UIApplication sharedApplication]delegate];
    [ad setMypDelegate:self];
.
.
.
-(void)myProtocolMethodOne
{
    NSLog(@"1st VC");
    [[self tabBarItem]setBadgeValue:@"ok"];
}

The code is working perfectly but only secondViewController is responding.

I am looking for a broadcasting and listener kind of mechanism using delegates not notifications.

I searched a lot but did find any solutions except this but code is advance for me to understand, so I am taking a step by step approach to understand this by starting form a simple project. Please help me regarding this. How both viewcontrollers can respond to delegate at same time, what should I do?

newacct
  • 119,665
  • 29
  • 163
  • 224
S.J
  • 3,063
  • 3
  • 33
  • 66
  • Only one object (at a time) can be the delegate, that's why it's not working. A broadcasting and listening mechanism IS what notifications are. Why don't you want to use that? – rdelmar Feb 09 '13 at 05:32
  • @rdelmar notification do not return values, and for full detail please visit the link I provided. First I was also thinking why not notification but after working on xmppframework I got the answer. – S.J Feb 09 '13 at 05:34
  • Why not create an array of delegate objects? – overboming Feb 09 '13 at 06:16
  • @overboming please provide a code, how can I use delegate array in my current code that I shared in my question. Thank a lot for replying. – S.J Feb 09 '13 at 21:51

3 Answers3

5

Instead of delegates you might consider something that looks like the Visitor pattern.

@interface MyVisitor : NSObject < myProtocol >

-(void)addAcceptor:(id < myProtocol >)acceptor

@end


@implementation

-(void)myProtocolMethodOne {
    [_acceptors enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *break){ 
        [obj performSelector:_sel];
    }];
}

// etc etc ... obviously you have to handle return values if you're getting these


@end
iluvcapra
  • 9,436
  • 2
  • 30
  • 32
  • _acceptors is still an array should that be declared as weak? secondly performselector doesn't work really well with multiple arguments – hariszaman May 11 '18 at 08:53
5

Since delegate is a simple variable, assigning to it overwrites the value rather than adding to it. You could convert it to an array, but because NSArray keeps strong references to the objects within it you need to deal with potential circular references. (A circular reference in this case is that two objects own each other. Since they're both owned by someone, neither will be freed. Even though they only own each other. Wikipedia has more. But the typical pattern in Objective-C is to make all delegates weak for this reason.)

Instead of delegates, you may wish to consider using NSNotificationCenter notifications.

Instead of 1:1, they're 1:any (including 0 without special considerations). The idea is that one object posts a notification, and the objects that are interested in it observe it. Each object can pick which events they're interested in.

There's a few steps you'll need to perform:

  1. Remove all the delegate code you've written.
  2. Agree on a notification name.
  3. Register the objects that will respond to the notification. (This is where you set the delegate.)
  4. Handle the notification.
  5. Post the notification (where you previously called the delegate).
  6. Unregister the objects when they're destroyed.

What you would do is agree on a key, probably in a constant.

Keys.h:

extern NSString *MethodOneNotification;

Keys.m:

NSString *MethodOneNotification = @"MethodOneNotification";

Then register in firstViewController and secondViewController like this somewhere like viewDidLoad:

[[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(methodOneNotification:) object:nil];

Provide a handler in both firstViewController and secondViewController for the selector:

- (void)methodOneNotification:(NSNotification *)notification {
    NSLog(@"%@ got the notification!", [self class]);
}

Call the notification where you previously called the delegate:

[[NSNotificationCenter defaultCenter] postNotificationName:MethodOneNotification
     object:nil];

And in both firstViewController and secondViewController, you'll want to remove the notification registration (certainly in dealloc):

- (void)dealloc {
    [[NSNotification defaultCenter] removeObserver:self name:MethodOneNotification
     object:nil];
    // [super dealloc] if you're not using ARC, but you should be
}

Within the notification handler, you can access the notification's sender as notification.object. If you need to pass information along with the notification, you can use a different variant of postNotification: that accepts a NSDictionary, then you can access the dictionary as notification.userInfo.

If you need to return values to the object that posted the messages, you'll have to send them back by sending messages to the poster (which you have access to as notification.object). For instance:

- (void)methodOneNotification:(NSNotification *)notification {
    AppDelegate *appDelegate = notification.object;
    [appDelegate returningValue:1];
}

Here, obviously, AppDelegate would need to define and handle -(void)returningValue:(int)value.

You'll need to keep the return value on the class instance. Of course, if you have multiple returns possible, you'll need to collect those in returningValue: with an array. But at least you've skipped circular references.

The other way to solve this is with blocks. That would double the size of this answer, though. :) Bottom line, though: The delegate pattern is the wrong pattern for this problem. Luckily, the others are easy to pick up.

Steven Fisher
  • 44,462
  • 20
  • 138
  • 192
  • Thanks for replying, please tell me what to do if I also have to return values. – S.J Feb 09 '13 at 23:26
  • Updated answer with one way to do so. – Steven Fisher Feb 09 '13 at 23:34
  • you are welcome :) ... please explain in detail regarding this "but then you need to deal with potential circular references." – S.J Feb 09 '13 at 23:36
  • I provided a bit of context, but I don't want to drag the answer too far off the main thrust of it. Is that sufficient? – Steven Fisher Feb 09 '13 at 23:41
  • yes it is, I also thank you for helping me, please read this you will like it https://github.com/robbiehanson/XMPPFramework/wiki/MulticastDelegate , you are more experienced you will get it quickly, but I am having difficulty understanding this and thats what I am pursuing :) – S.J Feb 09 '13 at 23:46
4

In your case is enough to hold an array with all delegates, by holding an array of delegates, possibly as a private property, and allowing to add/remove delegates:

@interface AppDelegate() // .h file

@property (nonatomic,strong,readwrite) NSMutableArray* delegates;

@end 

// .m file

- (void) addDelegate: (id<MyProtocol>) delegate // By convention the first letter should be capital.
{
    // Optional code you may need to execute before adding it.
    [delegates addObject: delegate];
}

I leave to you the removeDelegate method, it's very simple to implement.

How to call the delegates methods

It's enough to call the selector on every object:

[delegates makeObjectsPerformSelector: myProtocolMethodOne];

If you need to take return values it's better to do it this way:

NSMutableArray* returnValues= [NSMutableArray new];
for(id<MyProtocol> delegate in delegates)
{
    id result= [delegate myProtocolMethodTwo]; // Method returning a value
    [returnValues addObject: result];
} 

How to add the delegates

In every controller (up to N times) you should be able to add it:

AppDelegate *ad = (AppDelegate*)[[UIApplication sharedApplication]delegate];
[ad addDelegate:self];

Additional problem: You may want to have weak delegates, but a NSArray holds strong references. Here you can find a nice solution for this:

NSArray of weak references (__unsafe_unretained) to objects under ARC

It basically says to use NSValue to store weak reference using the valueWithNonretainedObject method.

Community
  • 1
  • 1
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • my confusion point is how it will work, in appDelegates its like [mypDelegate myProtocolMethodOne]; means who ever implements the protocol should respond so only one is responding. Please help me out in this. – S.J Feb 09 '13 at 23:19
  • I added this part under "How to call the delegates methods". – Ramy Al Zuhouri Feb 09 '13 at 23:27
  • if I iterate its like [objectAtIndex0InArray myProtocolMethodOne] ? – S.J Feb 09 '13 at 23:32
  • You can't ignore the circular reference problem by breaking the circle in `dealloc`: if there's a circular reference the `dealloc` method will never be called. That's why it's a problem at all. :) – Steven Fisher Feb 25 '13 at 01:23
  • Following Ramy's answer, it's a good idea to store references to the delegates in a data structure, and you would solve the strong reference issues by using an `NSHashTable`. See the implementation here: http://arielelkin.github.io/articles/objective-c-multicast-delegate/ – Eric Jun 17 '14 at 00:33