【问题标题】:vanished self.delegate in arc: EXC_BAD_ACCESS在弧中消失了 self.delegate:EXC_BAD_ACCESS
【发布时间】:2014-04-15 05:02:57
【问题描述】:

我有一个 SurroundViewController (CollectionView),它显示从网络服务器加载的图像。如果单击图像,您将被导航到 DetailViewController (TableView),它显示图像的附加信息。两者都嵌入在 NavigationController 中(参见故事板图像)。

当我从 DetailViewController 返回时,在 SurroundViewController 中进行刷新时,我的问题就开始了。然后它在performSelector 行上以EXC_BAD_ACCESS 崩溃

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 主动刷新。所以我在环绕视图中,并且该对象应该可用...

我该如何解决这个问题,即应用程序不会在 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

【问题讨论】:

  • 没有提到的方法,呈现detailviewcontroller。
  • @Cy-4AH 已更新,请参阅方法 collectionView:didSelectItemAtIndexPath: 和 prepareForSegue:sender:
  • // setup to transfer data 什么也没说
  • @Cy-4AH 添加了缺失的代码、singletonClass 和 DetailViewController。

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


【解决方案1】:

您将 DetailViewController 设置为委托。当然,在它被释放后你会得到 EXC_BAD_ACCESS。 对于共享实例,您应该使用通知而不是委托。 - (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender 和 - (void)removeObserver:(id)notificationObserver 是你的朋友。

【讨论】:

    【解决方案2】:

    在您的协议中,您将 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 回来时出现错误,因为在这一步 detailVC 变为 nil,当然 WebApi.delegate 也是。 所以,解决方案是当我们回到 SurroundViewController 时重置 WebApi.delegate,我们可以这样做:

    SurroundViewController.m

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

    【讨论】:

    • 哦,我在写答案时没有看到您添加了 detailVC!看一下,您正在更改您的 detailVC 中的 webApi 委托,因此当您返回时,该委托将为零,因为您的 detailVC 将被销毁。你找到了你的错误!最好不要在这里使用单例模式......以最小的努力解决这个问题的一种解决方案是在 SurroundViewController 的- (void) viewWillAppear:(BOOL)animated 中将您的 webApi 委托设置为 self(我没有说这很好,但应该可以)
    • workaround viewWillApear:animated 似乎有效。委托设置器听起来不错,从来没有想过。看看这是否有帮助。关于Have a look, you are changing your webApi delegate in your detailVC,它究竟是如何变化的。我虽然总是这样访问它。
    • @jerik,self.swebapi.delegate = self; 但是当您返回时,您的 WebApi 将引用已死的 DetailViewController。
    • 在 DetailViewController.m 你有 - (void)vinit { ... self.swebapi = [WebApi sharedInstance]; self.swebapi.delegate = self;...} 在那里你得到了 sharedInstance(与 SurroundViewController.m 相同)并将委托设置为 self(其中 self = detailVC )。如果您同意我的回答,请不要忘记将其标记为已接受;-D
    • 我想我明白了...只有一个控制器可以引用 WebApi 实例。这意味着,如果 SurroundViewController 拥有该实例,然后 DetailViewController 调用它,则它在 SurroundViewController 中将不再可用。正确的?我一直认为所有调用 Controller 都可以同时访问 WebApi。那么这将是不正确的。我在答案中尝试了您的代码,但它崩溃了。 cmets 中的代码在一个简短的测试中有效。明天我有更多时间 :) 请更新您的答案,我可以接受。
    猜你喜欢
    • 1970-01-01
    • 2014-08-04
    • 1970-01-01
    • 2021-10-21
    • 2012-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多