1

I have a class called "MyUploadClient" for handling video upload task. "MyUploadClient" has a protocol called "MyUploadClientDelegate". Someone use "MyUploadClient" to upload video needs to comfort to this protocol, and the delegate may be more than one, so I make a property called 'delegateSet' to store the delegates. The method '-invokeDelegateItemsWithAction:withObjects:...' is used to wrap the delegate method and invoke when it needs to.

But the delegate method "-uploadTask:progressChange:" has a parameter which type is float, the app crash when it execute "va_arg(args, id)".

To solve this problem, I change the parameter from float to NSNumber, but the app still crashed when execute "[invocation invoke]" with error "EXC_BAD_ACCESS".

Why did this happen and how can I fix this problem?

Relative code In file: MyUploadClient.h

@protocol MyUploadClientDelegate <NSObject>
- (void)startUploadTask:(MyVideo *)video;
- (void)didUploadTask:(MyVideo *)video error:(NSError * __nullable)error;
- (void)uploadTask:(NSString *)videoId progressChange:(float)progress;
……
@end
@interface MyUploadClient : NSObject
+ (instancetype)sharedClient;
- (void)addDelegate:(id<MyUploadClientDelegate>)delegate;
- (void)removeDelegate:(id<MyUploadClientDelegate>)delegate;
……
@end

Relative code In file: MyUploadClient.m

@interface MyUploadClientDelegateItem : NSObject
@property (nonatomic, weak) id<MyUploadClientDelegate> delegate;
- (instancetype)initWithDelegate:(id<MyUploadClientDelegate>)delegate;
@end

@implementation MyUploadClientDelegateItem
- (instancetype)initWithDelegate:(id<MyUploadClientDelegate>)delegate {
    self = [super init];
    if (self) {
        [self setDelegate:delegate];
    }
    return self;
}
@end

@interface MyUploadClient ()
@property (nonatomic, strong) NSMutableSet *delegateSet;
@end

@implementation MyUploadClient
- (NSMutableSet *)delegateSet {
    if (!_delegateSet) {
        _delegateSet = [NSMutableSet set];
    }
    return _delegateSet;
}
- (void)addDelegate:(id<MyUploadClientDelegate>)delegate {
    for (MyUploadClientDelegateItem *item in self.delegateSet) {
        if (delegate == item.delegate) {
            return ;
        }
    }
    MyUploadClientDelegateItem *delegateItem = [[MyUploadClientDelegateItem alloc] initWithDelegate:delegate];
    [self.delegateSet addObject:delegateItem];
}
- (void)removeDelegate:(id<MyUploadClientDelegate>)delegate {
    MyUploadClientDelegateItem *deleteItem = nil;
    for (MyUploadClientDelegateItem *item in self.delegateSet) {
        if (delegate == item.delegate) {
            deleteItem = item;
        }
    }
    if (deleteItem) {
        [self.delegateSet removeObject:deleteItem];
    }
}
- (void)startUploadWithVideo:(MyVideo *)video {
……
[self invokeDelegateItemsWithAction:@selector(startUploadTask:) withObjects:video, nil];
……
}
- (void)uploadProgressChanged:(float)progress videoId:(NSString *)videoId {
……
    [self invokeDelegateItemsWithAction:@selector(uploadTask:progressChange:) withObjects:videoId, progress, nil];
……
}
- (void)invokeDelegateItemsWithAction:(SEL)action withObjects:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION {
    NSMutableArray *deleteItems = [NSMutableArray arrayWithCapacity:0];
    NSSet *copySet = [NSSet setWithSet:self.delegateSet];
    for (MyDelegateItem *item in copySet) {
        if (item.delegate && [item.delegate respondsToSelector:action]) {
            NSMethodSignature *sigOfAction = [(id)item.delegate methodSignatureForSelector:action];
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sigOfAction];
            [invocation setTarget:item.delegate];
            [invocation setSelector:action];

            if (firstObj) {
                NSInteger argsIndex = 2;
                va_list args;
                va_start(args, firstObj);

                id argObject = firstObj;
                while (argObject) {
                    if ([argObject isKindOfClass:[NSValue class]]) {
                        void *value;
                        [argObject getValue:&value];
                        [invocation setArgument:&value atIndex:argsIndex];
                    } else {
                        [invocation setArgument:&argObject atIndex:argsIndex];
                    }
                    argObject = va_arg(args, id);
                    argsIndex++;
                }
                va_end(args);
            }
            [invocation invoke];
        }
        if (!item.delegate) {
            [deleteItems addObject:item];
        }
    }
    for (id obj in deleteItems) {
        [self.delegateSet removeObject:obj];
    }
}
@end
MissYasiky
  • 33
  • 4
  • 1
    You need to provide more details and explain what you've tried. Lots of unanswered questions in your question. You say you pass an `NSNumber` but test for an `NSValue`, that the value is a `float` but attempt to store it in a variable of type `void *` (`getValue` expects a byte *buffer*), etc. Have you used the debugger and stepped through? Do you see the `float` value you expect in `value`? Where is the call to `invokeDelegateItemsWithAction` and what is in `self.delegateSet`? TL;DR: help people to help you.You can edit your question to provide these details and someone will undoubtedly help. – CRD Apr 29 '19 at 06:17
  • @CRD Thank you for your remind, I have provide more details in my answer. – MissYasiky Apr 29 '19 at 07:40
  • Make sure you call `invokeDelegateItemsWithAction` with the arguments it expects: objects and `NSValue` with a `void*` size value. – Willeke Apr 29 '19 at 10:05
  • See [Multiple Delegates in Objective C](https://stackoverflow.com/questions/1382241/multiple-delegates-in-objective-c). – Willeke Apr 29 '19 at 10:06

0 Answers0