ManagedObjectContext либо равен нулю, либо выборка не эффективна.

У меня есть приложение, состоящее из нескольких представлений, в которых я обрабатываю объекты Core Data, обычно используя NSTableViews, подключенные к контроллерам массива с помощью привязок самым простым и обычным способом. Однако одно из представлений не использует обычные привязки, потому что объекты этого объекта должны быть обработаны (некоторые строки используются для создания других строк) и представлены в NSTableView на основе представления, поэтому им требуется промежуточный массив для их упаковки для моих нужд. В графическом интерфейсе я использую привязки с «objectValue:key_name» (ключи из нового массива, состоящего из обработанных данных).

В других представлениях и сущностях моего приложения, когда я нажимаю значок на панели инструментов, отображается конкретное представление, а изменения графа объектов автоматически отображаются в табличных представлениях благодаря привязкам.

Когда я отображаю представление, связанное с конкретной обработкой, обновления явно не видны сразу, и я должен запустить (пустую) функцию, которая программно выполняет новую выборку, обрабатывает результат и обновляет массив, заполняющий табличное представление на основе представления.

Вот проблема: табличное представление правильно заполняется при запуске приложения, потому что я вызываю функцию в awakeFromNib. Если я хочу обновить представление таблицы, я могу сделать это, нажав кнопку, связанную с IBAction, которая вызывает функцию обновления. Это также работает. Но когда я возвращаюсь к представлению с помощью значка на панели инструментов, оно не работает, хотя вызов этого представления также вызывает функцию обновления (это означает, что в классе AppDelegate я вызываю функцию, написанную в конкретном классе контроллера представления ).

Сначала произошел сбой, и отладчик показал, что manageObjectContext равен нулю. Это неожиданно, потому что он работает правильно, когда функция вызывается из awakeFromNib или из IBAction. Поискав в сети объяснения, я обнаружил, что мне нужно было явно ссылаться на контекст управляемого объекта приложения. Итак, я добавил код для этого, и теперь он больше не падает, но ничего не делает. Функция по-прежнему отлично работает при вызове из awakeFormNib или из IBAction, но не имеет никакого эффекта при вызове диспетчером представлений через щелчок на панели инструментов.

Что я пропустил? Спасибо за вашу помощь. Вот код конкретного контроллера представления:

//////// OpplogViewController.h

#import <Cocoa/Cocoa.h>
#import "ManagingViewController.h"
@interface OpplogViewController : ManagingViewController {
IBOutlet NSTableView *opplogTableView;
IBOutlet NSArrayController *opplogController;
}
@property (strong) NSMutableArray *allOpps;
- (IBAction)refreshOpp:(id)sender;
- (void)fetchTasks:(NSString *)entityName;
@end



//////// OpplogViewController.m

#import "OpplogViewController.h"
@interface OpplogViewController ()
@end

@implementation OpplogViewController
@synthesize allOpps = _allOpps;
- (id)init {
    self = [super initWithNibName:@"OpplogViewController" bundle:nil];
    if (!self) {
        return nil;
    }
    [self setTitle:NSLocalizedString(@"Opplogview", @"")];
    _allOpps = [[NSMutableArray alloc] init];
    return self;
}

- (void)awakeFromNib {
    [self fetchTasks:@"Opplog"];
    // Opplog is the database entity to be fetched
}

- (IBAction)refreshOpp:(id)sender {
    [self fetchTasks:@"Opplog"];
}

// below is the function called to fetch data and update the table view
- (void)fetchTasks:(NSString *)entityName {
    // below: 2 lines added to avoid crash because context is nil
    id delegate = [[NSApplication sharedApplication] delegate];
    self.managedObjectContext = [delegate managedObjectContext];
    // below, we reset the array used to populate the table view
    [_allOpps removeAllObjects];
    NSFetchRequest *requestOpp = [[NSFetchRequest alloc] init];
    NSPredicate *predicate = ...some predicate... ;
    [requestOpp setEntity:[NSEntityDescription entityForName:entityName inManagedObjectContext:self.managedObjectContext]];
    [requestOpp setPredicate:predicate];
    NSError *error = nil;
    NSArray *fetchResult = [self.managedObjectContext executeFetchRequest:requestOpp error:&error];
    for (id obj in fetchResult) {
        // processing the data
        // creating a dictionary with resulting data
        NSDictionary *anOpp = ... ;
        // populating the array with the dictionaries
        [_allOpps addObject:anOpp];
    }
[_allOpps sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES]]];
[opplogController setContent:_allOpps];
[opplogTableView reloadData];
}

@end

Это AppDelegate, который вызывает функцию обновления в классе контроллера представления Opplog, если мы вернемся к этому конкретному представлению:

////// AppDelegate.h
#import <Cocoa/Cocoa.h>
@class ManagingViewController;
@interface AppDelegate : NSObject <NSApplicationDelegate> {
    NSMutableArray *viewControllers;
    IBOutlet NSToolbar *toolBar;
}
@property (unsafe_unretained) IBOutlet NSWindow *window;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
- (IBAction)changeViewController:(id)sender;
- (void)displayViewController:(ManagingViewController *)vc;


////// AppDelegate.m
#import "AppDelegate.h"
#import "OpplogViewController.h"
// also importing the controller class header of the app's other views
// ...
@implementation AppDelegate
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize managedObjectContext = _managedObjectContext;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// nothing here, was generated by Xcode
}

-(id)init {
    self = [super init];
    viewControllers = [[NSMutableArray alloc] init];
    ManagingViewController *vc;

    vc = [[OpplogViewController alloc] init];
    [vc setManagedObjectContext:[self managedObjectContext]];
    [viewControllers addObject:vc];
    // below, repeating the same as paragraph above, for each view of the app
    // ...

    return self;
}

-(void)awakeFromNib {
    [self displayViewController:viewControllers[0]];
    [toolBar setSelectedItemIdentifier:NSLocalizedString(@"ViewOpplog", @"")];
    // because the Opplog view is the view which must be displayed on startup
}

-(void)displayViewController:(ManagingViewController *)vc {
    BOOL ended = [_window makeFirstResponder:_window];
    if (!ended) {
        NSBeep();
        return;
    }
    NSView *v = [vc view];
    [_window setContentView:v];
}

-(IBAction)changeViewController:(id)sender {
    // switching views is an opportunity to save the context
    NSError *error = nil;
    [[self managedObjectContext] save:&error];
    // now managing the view switch using toolbar icons
    // the sender is the clicked toolbar item
    NSInteger i = [sender tag];
    ManagingViewController *vc = viewControllers[i];

    // We go back to the Opplog view using the first toolbar icon
    // which has a tag=0 and we call the function that gives us trouble.
    // This call is supposed to update the data on the Opplog view
    // when it will be loaded.
    if (i == 0) {
        OpplogViewController *pendingController = [[OpplogViewController alloc] init];
        [pendingController fetchTasks:@"Opplog"];
    }
    [self displayViewController:vc];
}

А это код классаManagingViewController.

////// ManagingViewController.h
#import <Cocoa/Cocoa.h>
@interface ManagingViewController : NSViewController {
    NSManagedObjectContext *__strong managedObjectContext;
}
@property (strong) NSManagedObjectContext *managedObjectContext;
@end


////// ManagingViewController.m
#import "ManagingViewController.h"
@interface ManagingViewController ()
@end

@implementation ManagingViewController
@synthesize managedObjectContext;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
return self;
}
@end

person Community    schedule 11.03.2014    source источник


Ответы (1)


Порядок отправки объектов -awakeFromNib не определен, поэтому возможно, что ваше сообщение -awakeFromNib отправляется до того, как стек основных данных станет доступен. Возможно, вы захотите использовать другой метод, например, когда -representedObject контроллера представления установлен или сообщает ему и сообщает ему об обновлении, когда вызывается -applicationDidFinishLaunching: вашего делегата приложения (и стек определенно доступен).

Изменить на основе комментариев

В вашем методе -changeViewController вы выделяете/инициируете новый экземпляр OpplogViewController, а затем просите его извлечь. Похоже, вы просто хотите вернуться к контроллеру представления с индексом 0, который является экземпляром OpplogViewController, который вы создали изначально. В данном случае, я полагаю, вы хотели сказать:

if (i == 0)
  [(OppLogViewController *)vc fetchTasks:@"Opplog"];

Вместо того, чтобы создавать новый экземпляр и отправлять ему сообщения, а затем немедленно сливать его, вы хотите отправить сообщение тому, который вы изначально создали и сохранили. Другими словами, вы говорите не тому парню, чтобы он принес.

Несколько дополнительных советов: обратите внимание на то, что я упомянул о -awakeFromNib и -init, так как понимание этого имеет решающее значение. Из протокола NSNibAwaking документы:

Поскольку порядок создания экземпляров объектов из архива не гарантируется, ваши методы инициализации не должны отправлять сообщения другим объектам в иерархии. Сообщения другим объектам можно безопасно отправлять из awakeFromNib — к этому времени гарантируется, что все объекты разархивированы и инициализированы (хотя, конечно, не обязательно пробуждены).

person Joshua Nozzi    schedule 13.03.2014
comment
Спасибо за ответ. Но проблема не в том, что функция вызывается в -awakefromNib при запуске. Это происходит только тогда, когда я хочу вернуться к этому представлению, используя значок панели инструментов, который отправляет сообщение от AppDelegate для вызова функции в другом контроллере представления (см. 2 последние строки кода, которые я опубликовал). Любой другой способ вызова функции работает хорошо. - person ; 14.03.2014
comment
Только что провел дальнейшее тестирование с использованием NSLog во многих местах, чтобы показать объекты массива, и я вижу, что содержимое обновляется правильно, но arrayController не обновляется с помощью setContent, а представление таблицы не обновляется при перезагрузке. Меня это полностью озадачивает, так как функция определенно требует обновления контроллера массива и перезагрузки таблицы ... так почему же она не обновляется в конкретной ситуации, хотя выполняется одна и та же функция?! - person ; 14.03.2014
comment
Вы уверены, что экземпляр вашего контроллера и его пользовательский интерфейс такие же, как тот, который вы выделяете/инициализируете в этом конкретном методе? Используйте отладчик: установите точки останова, запустите их, затем сравните указатели (адреса памяти), чтобы убедиться, что то, что вы видите на экране, действительно является экземпляром контроллера, которым вы его считаете. - person Joshua Nozzi; 14.03.2014
comment
С NSLog он возвращал (null) содержимое контроллера массива. Использование точек останова показало, что это действительно должны быть разные экземпляры, поскольку контроллер массива даже не находится в памяти. Для других вызовов функции она вернула адрес памяти, а в случае, когда у меня возникли проблемы, она вернула ноль. Теперь мне понадобится помощь с кодом, чтобы исправить это, потому что я никогда раньше этого не делал и понятия не имею, как это сделать. - person ; 17.03.2014
comment
Чтобы понять, что происходит, требуется более полный листинг кода (включая включение методов, в которых вызываются вещи). Похоже, вы отправляете сообщения не тому контроллеру в тот или иной момент. Пример того, что нам нужно: А это часть кода в AppDelegate, которая вызывает функцию обновления в другом классе:... - Это неполная картина, так как я понятия не имею, откуда этот код вызывается или что еще может создать/обращаться к экземпляру OpplogViewController. Нужно НАМНОГО больше подробностей. - person Joshua Nozzi; 18.03.2014
comment
Спасибо Джошуа. Я отредактировал свой вопрос, чтобы опубликовать дополнительный код для AppDelegate, а также класс ManagementViewController, поскольку он используется AppDelegate. - person ; 18.03.2014
comment
Ура! Теперь он отлично работает. Так просто, но я не мог понять это сам. Большое спасибо за ваше время и советы. Извините, я не могу проголосовать за ваш ответ, мой статус новичка пока не позволяет мне это сделать. - person ; 18.03.2014