You are setting DetailViewController as delegate. Of course you will get EXC_BAD_ACCESS after it's deallocated.
You should use notifications instead delegates for shared instances.
- (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender
and - (void)removeObserver:(id)notificationObserver
are yours friends.
vanished self.delegate in arc: EXC_BAD_ACCESS
-
12-06-2023 - |
質問
I have a SurroundViewController (CollectionView) which shows images loaded from a webserver. If you click on the image you will be navigated to a DetailViewController (TableView) which shows additional information to the image. Both Emebded in a NavigationController (see storyboard image).
My problem start when I do a refresh in the SurroundViewController, when coming back from the DetailViewController. Then it crashes with EXC_BAD_ACCESS
on the performSelector
line
WebApi.m
-(void)getSurroundStream {
NSString *URLString = [NSString stringWithFormat:@"%@/%@/view/%f/%f", kApiHost, kApiPath, self.sshare.coordinate.longitude, self.sshare.coordinate.latitude];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[self setAuthHeader:manager];
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.sshare putViData:responseObject];
[self.delegate performSelector:@selector(didLoadFoo)]; // --> EXC_BAD_ACCESS
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.error vError:error message:operation.responseString url:URLString];
}];
}
I checked in the Debug Console:
2014-03-11 14:22:51.989 Foo[6923:60b] -[SurroundViewController refresh:] [Line 352] refreshing
2014-03-11 14:22:51.998 Foo[6923:60b] -[WebApi getSurroundImages] [Line 393] do surround composition
(lldb) po self.delegate
[no Objective-C description available]
Seems that the object which is not available. What I do not understand is. I am in the SurroundViewController and do activly a refresh by pull-to-refresh
. So I am on the Surround View and the object should be available...
How do I fix this issue, that the App does not crash with EXC_BAD_ACCESS at the performSelector line?
Here's the code which is involved with the issue (necessary parts):
SurroundViewController.h
#import <UIKit/UIKit.h>
#import "WebApi.h"
#import "DetailViewController.h"
#import "SingletonClass.h"
@interface SurroundViewController : UICollectionViewController <WebApiDelegate>
@property (nonatomic, strong) WebApi *swebapi;
@property (nonatomic, strong) SingletonClass *sshare;
@end
SurroundViewController.m
#import "SurroundViewController.h"
@interface SurroundViewController ()
@property (nonatomic, strong) UIRefreshControl *refresh;
@end
@implementation SurroundViewController
-(void)vinit {
self.sshare = [SingletonClass sharedInstance];
self.swebapi = [WebApi sharedInstance];
self.swebapi.delegate = self;
}
- (void)viewDidLoad
{
[self vinit];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[super viewDidLoad];
[self addRefresh];
[self.swebapi getSurroundImages]; // will call delegate didComposition
}
- (void)viewDidAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// cell configuration
}
-(void)addRefresh {
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];
self.refresh = refreshControl;
[self.collectionView addSubview:self.refresh];
}
-(void)refresh:(UIRefreshControl*)refresh {
refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Refreshing..."];
[self.swebapi getSurroundImages];
}
-(void)didLoadFoo {
[self.swebapi doComposition];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:@"toDetailView" sender:indexPath];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"toDetailView"]) {
DetailViewController *dvc = [segue destinationViewController];
NSIndexPath *indexPath = sender;
dvc.idx = [self getItemOfSection:indexPath];
dvc.detailData = [[self.sshare coItem:dvc.idx] mutableCopy];
}
}
- (int)getItemOfSection:(NSIndexPath *)indexPath {
return (int)indexPath.item + ((int)indexPath.section * 4);
}
@end
WebApi.h
#import "AFHTTPRequestOperationManager.h"
#import "Errors.h"
@class WebApi;
@protocol WebApiDelegate <NSObject>
@optional
-(void)didLoadFoo;
@end
@interface WebApi : AFHTTPRequestOperationManager <SingletonDelegate>
@property (assign, nonatomic)id<WebApiDelegate> delegate;
@property (nonatomic, strong) Errors *error;
+(WebApi*)sharedInstance;
-(void)getSurroundStream;
-(void)getSurroundImages;
@end
WebApi.m
#import "WebApi.h"
#define kApiHost @"http://sample.com"
#define kApiPath @"sample"
@implementation WebApi
-(WebApi*)initWithBaseURL:url {
self = [super init];
if (self != nil) {
self.sshare = [SingletonClass sharedInstance];
self.error = [[Errors alloc] init];
}
return self;
}
+(WebApi*)sharedInstance
{
static WebApi *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kApiHost]];
});
return sharedInstance;
}
-(void)getSurroundStream {
NSString *URLString = [NSString stringWithFormat:@"%@/%@/view/%f/%f", kApiHost, kApiPath, self.sshare.coordinate.longitude, self.sshare.coordinate.latitude];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[self setAuthHeader:manager];
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.sshare putViData:responseObject];
[self.delegate performSelector:@selector(didLoadFoo)]; // --> EXC_BAD_ACCESS
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.error vError:error message:operation.responseString url:URLString];
}];
}
-(void)getSurroundImages {
[self getSurroundStream];
}
@end
SingletonClass.h
#import <Foundation/Foundation.h>
@class Singleton;
@protocol SingletonDelegate <NSObject>
-(void)didRefreshToken;
@end
@interface SingletonClass : NSObject
@property (assign, nonatomic) id<SingletonDelegate> delegate;
@property (nonatomic, strong) NSMutableArray *viData;
@property (nonatomic, strong) NSMutableArray *coData;
@end
SingletonClasss.m
#import "SingletonClass.h"
@implementation SingletonClass
static SingletonClass *sharedInstance = nil;
// Get the shared instance and create it if necessary.
+ (SingletonClass *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
self.coData = [[NSMutableArray alloc] init];
self.viData = [[NSMutableArray alloc] init];
}
return self;
}
// We don't want to allocate a new instance, so return the current one.
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
// Equally, we don't want to generate multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
-(NSMutableDictionary *)coItem:(int)position {
NSAssert(self.coData.count > position, @"Position does not exists: coData.count: %lu > position: %d", (unsigned long)self.coData.count, position);
return self.coData[position];
}
@end
DetailViewController.h
#import <UIKit/UIKit.h>
#import "SingletonClass.h"
#import "WebApi.h"
@interface DetailViewController : UITableViewController <WebApiDelegate>
@property (nonatomic) int idx;
@property (nonatomic, strong) SingletonClass *sshare;
@property (nonatomic, strong) WebApi *swebapi;
@property (nonatomic, strong) NSMutableDictionary *detailData;
@end
DetailViewController.m
#import "DetailViewController.h"
@interface DetailViewController ()
@property (nonatomic, strong) NSArray *cellRows;
@end
@implementation DetailViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)vinit {
self.sshare = [SingletonClass sharedInstance];
self.swebapi = [WebApi sharedInstance];
self.swebapi.delegate = self;
NSAssert(self.detailData, @"detailData is not available");
}
- (void)viewDidLoad
{
[self vinit];
[self.navigationController setNavigationBarHidden:NO animated:NO];
[super viewDidLoad];
self.cellRows = @[@"cellLocation:", @"cellIntention:"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.cellRows.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"detailCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
SEL functionCall = NSSelectorFromString(self.cellRows[indexPath.row]);
[self performSelector:functionCall withObject:cell];
return cell;
}
- (void)cellLocation:(UITableViewCell*)cell {
// configuration of table cell
}
- (void)cellIntention:(UITableViewCell*)cell {
// configuration of table cell
}
@end
解決
他のヒント
In your protocol, you set didLoadDoo as optional,
@protocol WebApiDelegate <NSObject>
@optional
-(void)didLoadFoo;
@end
so you need to secure the call to that method in your delegate
if ([self.delegate respondsToSelector:@selector(didLoadFoo)]) {
[self.delegate performSelector:@selector(didLoadFoo)];
}
As you are working with a singleton
+(WebApi*)sharedInstance
if your singleton.delegate is change somewhere else in your code (i.e. in your detailVC), it is change everyWhere !
edit :
After some more check, now we know that WebApi.delegate is change in detailVC, and the bug appear when we are back from detailVC because at this step detailVC is becoming nil and of course WebApi.delegate also. So, the solution is to reset WebApi.delegate when we are back in SurroundViewController, and we can do that in :
SurroundViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.swebapi.delegate = self;
}