質問

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).

Storyboard of ViewController setup

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
役に立ちましたか?

解決

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.

他のヒント

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;
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top