Reputation: 1174
The title says it, I'd like to ask why the following does not work, as it should imho.
// ViewController.m
#import "B.h"
...
@implementation ViewController
{
B *bInstance;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
bInstance = [[B alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[bInstance setBlockCalled:^(NSDictionary *dict) {
NSLog(@"%@", dict[@"key"]);
}];
[self.view addSubview:bInstance];
}
return self;
}
// B.h
#import <UIKit/UIKit.h>
@interface B : UIView
@property (nonatomic, copy) void (^blockCalled)(NSDictionary *);
@end
// B.m
#import "B.h"
#import "A.h"
@implementation B
{
A *aInstance;
void (^blockCalled)(NSDictionary *);
}
@synthesize blockCalled;
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
aInstance = [[A alloc] initWithFrame:frame];
[aInstance setBlockCalled:blockCalled];
[self addSubview:aInstance];
}
return self;
}
@end
// A.h
#import <UIKit/UIKit.h>
@interface A : UIView
@property (nonatomic, copy) void (^blockCalled)(NSDictionary *);
@end
// A.m
#import "A.h"
@implementation A
{
void (^blockCalled)(NSDictionary *);
}
@synthesize blockCalled;
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:CGRectMake(0, 0, 100, 100)];
[button setBackgroundColor:[UIColor redColor]];
[button addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) buttonClicked
{
blockCalled(@{@"key":@"value"});
}
@end
What I want to do is 'traverse up the view hiearchy', and as far as I see it, I'm assigning a block variable with the same arguments, so I'd expect it to work. Any reason why this is a wrong idea?
EDIT: added more complete example as of when this issue might happen.
EDIT2: added MCVE, which I've tested.
After I've checked the MCVE, the code crashes on the line blockCalled(@{@"key":@"value"});
in A.m because the blockCalled is nil.
Updated question: I'd like to know why calling [aInstance setBlockCalled:blockCalled]
doesn't set the blockCalled in A, as it seems to me to be the same as
[aInstance setBlockCalled:^(NSDictionary *dict)
{
__strong typeof (self) strongSelf = self;
strongSelf.blockCalled(dict);
}];
Upvotes: 1
Views: 146
Reputation: 2165
@implementation ViewController
{
B *bInstance;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
bInstance = [[B alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[bInstance setBlockCalled:^(NSDictionary *dict) {
NSLog(@"%@", dict[@"key"]);
}];
[self.view addSubview:bInstance];
}
At the moment of initializing bInstance
, you haven't set the block yet. It means
aInstance = [[A alloc] initWithFrame:frame];
[aInstance setBlockCalled:blockCalled];
is called before
[bInstance setBlockCalled:^(NSDictionary *dict) {
NSLog(@"%@", dict[@"key"]);
}];
You should override the block setter in B and call it on A.
// B.m
-(void)setBlockCalled(void(^)(NSDictionary*))passedBlock{
[a setBlockCalled:passedBlock];
}
Upvotes: 1
Reputation: 437552
The code in your question (a) uses ivars and @synthesize
statements that are unnecessary; and (b) this code snippet is insufficient to reproduce the crash you describe.
Having said that, there are two possible source of crashes that are suggested by the code sample in the question, namely: (a) the code fails to remove the observer if A
is deallocated; and (b) it really should double-check to make sure that blocks are non-nil before trying to call them.
But, consider the following:
// A.h
@interface A : UIView
@property (nonatomic, copy) void (^blockCalled)(NSDictionary *dict);
@end
// A.m
@implementation A
- (instancetype) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"kNotification" object:nil];
}
return self;
}
- (void)handleNotification:(NSNotification *)notification {
// never just call `blockCalled`; always check to see if not null
if (self.blockCalled) {
// everything is good, so let's call the block
self.blockCalled(notification.userInfo);
}
}
- (void)dealloc {
// never just `addObserver`; make sure to remove the observer when this is deallocated
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"kNotification" object:nil];
}
@end
// B.h
@interface B : UIView
@property (nonatomic, copy) void (^blockCalled)(NSDictionary *dict);
@property (nonatomic, strong) A *aInstance;
@end
// B.m
@implementation B
- (void) someMethod {
// !!!!! why this crashes the app when blockCalled on aInstance is called:
[self.aInstance setBlockCalled:self.blockCalled];
// but this does not crash when the same happens
__weak typeof (self) weakSelf = self;
self.aInstance.blockCalled = ^(NSDictionary *dict) {
__strong typeof (self) strongSelf = weakSelf;
// again, never just call `blockCalled`; always check to see if not null
if (strongSelf.blockCalled) {
strongSelf.blockCalled(dict);
}
};
}
@end
Bottom line, when I fix the two obvious sources of crashes (failing to remove observer and failing to check to make sure the block was non-nil before calling it), and test it with logical scenario, it seems to work fine:
- (void)viewDidLoad {
[super viewDidLoad];
B *b = [[B alloc] init];
b.blockCalled = ^(NSDictionary *dict) {
NSLog(@"%@", dict);
};
A *a = [[A alloc] init];
b.aInstance = a;
[b someMethod];
[self.view addSubview:a];
[self.view addSubview:b];
NSDictionary *dict = @{@"foo": @"bar", @"baz": @"qux"};
[[NSNotificationCenter defaultCenter] postNotificationName:@"kNotification" object:nil userInfo:dict];
}
So, assuming one of these two issues wasn't the source of the problem, you must provide MCVE that we can use to reproduce the crash you describe.
Upvotes: 0