在arc中消失了self.delegate:EXC_BAD_ACCESS

时间:2014-03-11 14:08:45

标签: ios objective-c delegates refresh exc-bad-access

我有一个SurroundViewController(CollectionView),它显示从网络服务器加载的图像。如果单击图像,您将导航到DetailViewController(TableView),它会向图像显示其他信息。在NavigationController中都有Emebded(参见故事板图片)。

Storyboard of ViewController setup

从DetailViewController返回时,我在SurroundViewController中进行刷新时出现问题。然后它在EXC_BAD_ACCESS

上与performSelector崩溃

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];
    }];
}

我检查了调试控制台:

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]

似乎该对象不可用。我不明白的是。我在SurroundViewController中并通过pull-to-refresh激活刷新。所以我在Surround View上,对象应该可用......

如何修复此问题,应用程序在performSelector行中不会因EXC_BAD_ACCESS而崩溃?

以下是涉及问题的代码(必要部分):

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

2 个答案:

答案 0 :(得分:2)

您正在将DetailViewController设置为委托。当然,在取消分配后,您将获得EXC_BAD_ACCESS。 您应该使用通知代替委托共享实例。 - (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender- (void)removeObserver:(id)notificationObserver是您的朋友。

答案 1 :(得分:1)

在您的协议中,您将didLoadDoo设置为可选,

@protocol WebApiDelegate <NSObject>
@optional
-(void)didLoadFoo;
@end

因此您需要在委托中保护对该方法的调用

if ([self.delegate respondsToSelector:@selector(didLoadFoo)]) {
    [self.delegate performSelector:@selector(didLoadFoo)];
}

当您使用单身人士时

+(WebApi*)sharedInstance

如果你的singleton.delegate在你的代码中的其他地方(即你的detailVC中)发生变化,那么每个地方都会发生变化!

编辑:

经过多次检查后,现在我们知道WebApi.delegate是detailVC的变化,当我们从detailVC回来时会出现bug,因为在这一步中detailVC变为nil,当然还有WebApi.delegate。 因此,解决方案是在我们回到SurroundViewController时重置WebApi.delegate,我们可以这样做:

SurroundViewController.m

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.swebapi.delegate = self;
}