managedObjectContext е или нула, или извличането не е ефективно

Имам приложение, направено от няколко изгледа, на които боравя с обекти с основни данни, обикновено използвайки NSTableViews, свързани с контролери на масиви, използващи свързвания по най-простия и обичаен начин. Един от изгледите обаче не използва обичайните обвързвания, тъй като обектите на този обект трябва да бъдат обработени (някои низове, използвани за създаване на други низове) и представени в базиран на изглед NSTableView, така че те изискват междинен масив, който да ги пакетира за моите нужди. В графичния интерфейс използвам свързвания с "objectValue:key_name" (ключове от новия масив, съставен от обработени данни).

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

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

Ето го проблемът: табличният изглед се попълва правилно при стартиране на приложението, защото извиквам функцията в awakeFromNib. Ако искам да актуализирам табличния изглед, мога да го направя, като щракна върху бутон, свързан с IBAction, който извиква функцията за актуализиране. Работи също. Но когато се върна към изгледа с помощта на иконата на лентата с инструменти, това не работи, въпреки че извикването на този изглед също извиква функцията за актуализиране (което означава, че в класа AppDelegate извиквам функцията, написана в конкретния клас на контролера на изгледа ).

Първоначално се срина и дебъгерът разкри, че managedObjectContext е нула. Това е изненада, защото работи правилно, когато функцията се извика от 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, тъй като разбирането на това е критично. От протокола NNSibAwaking документи:

Тъй като редът, в който обектите се инстанцират от архив, не е гарантиран, вашите методи за инициализация не трябва да изпращат съобщения до други обекти в йерархията. Съобщенията до други обекти могат да се изпращат безопасно от 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, а също и класа ManagingViewController, тъй като се използва от AppDelegate. - person ; 18.03.2014
comment
Ура! Сега работи чудесно. Толкова просто, но не можах да го разбера сам. Благодаря ви много за отделеното време и съвет. Съжалявам, че не мога да гласувам за вашия отговор, статусът ми на начинаещ все още не ми позволява да направя това. - person ; 18.03.2014