İOS 5'te Hızlı ve Verimli Temel Veri İçe Aktarma Uygulama


101

Soru : Alt bağlamımın, NSFetchedResultsController'ımı kullanıcı arabirimini güncelleştirmesi için tetiklemesi için ana bağlamda kalıcı olan değişiklikleri görmesini nasıl sağlayabilirim?

Kurulum şu şekildedir:

Çok sayıda XML verisi indiren ve ekleyen bir uygulamanız var (yaklaşık 2 milyon kayıt, her biri kabaca normal bir metin paragrafı boyutunda) .sqlite dosyasının boyutu yaklaşık 500 MB olur. Bu içeriğin Çekirdek Verilere eklenmesi zaman alır, ancak verilerin veri deposuna artımlı olarak yüklenirken kullanıcının uygulamayı kullanabilmesini istersiniz. Görünmez olmalı ve kullanıcı için büyük miktarda verinin hareket ettiği, yani takılmalar, titremeler yok: Tereyağı gibi kaydırmalar. Yine de, uygulama daha kullanışlıdır, ona daha fazla veri eklenir, bu nedenle verilerin Core Data deposuna eklenmesini sonsuza kadar bekleyemeyiz. Kodda bu, içe aktarma kodunda böyle bir koddan kaçınmak istediğim anlamına gelir:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];

Uygulama yalnızca iOS 5 olduğundan desteklemesi gereken en yavaş cihaz iPhone 3GS'dir.

Şu ana kadar mevcut çözümümü geliştirmek için kullandığım kaynaklar şunlardır:

Apple'ın Temel Veri Programlama Kılavuzu: Verileri Verimli Bir Şekilde İçe Aktarma

  • Belleği düşük tutmak için Otomatik Yayın Havuzlarını kullanın
  • İlişkiler Maliyeti. Düz olarak içe aktarın, ardından sonunda ilişkileri düzeltin
  • Yardım edebilirseniz sorgulama, işleri O (n ^ 2) bir şekilde yavaşlatır
  • Toplu İçe Aktarma: kaydedin, sıfırlayın, boşaltın ve tekrarlayın
  • İçe aktarma sırasında Geri Alma Yöneticisini kapatın

iDeveloper TV - Temel Veri Performansı

  • 3 Bağlam kullanın: Ana, Ana ve Sınırlama bağlam türleri

iDeveloper TV - Mac, iPhone ve iPad Güncellemesi için Temel Veri

  • PerformBlock ile çalıştırmak diğer kuyruklarda tasarruf sağlar, işleri hızlandırır.
  • Şifreleme işleri yavaşlatır, yapabiliyorsanız kapatın.

Marcus Zarra tarafından Çekirdek Verilerdeki Büyük Veri Kümelerini İçe Aktarma ve Görüntüleme

  • Geçerli çalışma döngüsüne zaman tanıyarak içe aktarmayı yavaşlatabilirsiniz, böylece kullanıcı için işler sorunsuz hale gelir.
  • Örnek Kod, büyük içeri aktarmalar yapmanın ve UI'yi duyarlı tutmanın mümkün olduğunu, ancak 3 bağlam ve diske eşzamansız kaydetme kadar hızlı olmadığını kanıtlıyor.

Mevcut Çözümüm

NSManagedObjectContext'in 3 örneği var:

masterManagedObjectContext - Bu, NSPersistentStoreCoordinator'a sahip olan ve diske kaydetmekten sorumlu olan bağlamdır. Bunu, kaydettiklerimin eşzamansız ve bu nedenle çok hızlı olması için yapıyorum. Bunu başlangıçta şöyle oluşturuyorum:

masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];

mainManagedObjectContext - Bu, kullanıcı arayüzünün her yerde kullandığı bağlamdır. MasterManagedObjectContext öğesinin bir alt öğesidir. Bunu şöyle yaratıyorum:

mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];

backgroundContext - Bu bağlam, XML verilerini Core Data'ya aktarmaktan sorumlu olan NSOperation alt sınıfımda oluşturulur. Bunu işlemin ana yönteminde oluşturuyorum ve oradaki ana bağlamla ilişkilendiriyorum.

backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];

Bu aslında çok çok hızlı çalışıyor. Sadece bu 3 bağlam kurulumunu yaparak içe aktarma hızımı 10 kattan fazla artırabildim! Dürüst olmak gerekirse, buna inanmak zor. (Bu temel tasarım, standart Temel Veri şablonunun bir parçası olmalıdır ...)

İçe aktarma işlemi sırasında 2 farklı şekilde kaydediyorum. Arka planda kaydettiğim her 1000 öğe:

BOOL saveSuccess = [backgroundContext save:&error];

Daha sonra, içe aktarma işleminin sonunda, görünüşte, değişiklikleri ana bağlam dahil olmak üzere diğer alt bağlamlara iten ana / üst bağlamı kaydederim:

[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];

Sorun : Sorun , görünümü yeniden yükleyene kadar kullanıcı arayüzümün güncellenmemesidir.

NSFetchedResultsController kullanarak veri beslenen bir UITableView ile basit bir UIViewController var. İçe aktarma işlemi tamamlandığında, NSFetchedResultsController üst / ana bağlamda hiçbir değişiklik görmez ve bu nedenle kullanıcı arabirimi, benim görmeye alıştığım gibi otomatik olarak güncellenmez. UIViewController'ı yığından çıkarırsam ve tekrar yüklersem tüm veriler orada olur.

Soru : Alt bağlamımın, NSFetchedResultsController'ımı kullanıcı arabirimini güncelleştirmesi için tetiklemesi için ana bağlamda kalıcı olan değişiklikleri görmesini nasıl sağlayabilirim?

Uygulamayı kapatan aşağıdakileri denedim:

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    NSError *error = nil;
    BOOL saveSuccess = [masterManagedObjectContext save:&error];

    [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == mainManagedObjectContext) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
        return;
    }

    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

26
Şimdiye kadarki en iyi hazırlanmış, en çok hazırlanmış soru için +1000000. Benim de bir cevabım var ... Bunu yazmak birkaç dakika alacak ...
Jody Hagins

1
Uygulamanın asıldığını söylediğinizde nerede? Ne yapıyor?
Jody Hagins

Uzun bir süre sonra bunu gündeme getirdiğim için üzgünüm. Lütfen "Düz içe aktarın, sonra ilişkileri düzeltin" ne anlama geldiğini açıklar mısınız? İlişki kurmak için o nesnelerin hafızada olması gerekmiyor mu? Sizinkine çok benzer bir çözüm uygulamaya çalışıyorum ve bellek ayak izini azaltmak için gerçekten biraz yardım kullanabilirim.
Andrea Sprega

Bu makalenin ilki ile bağlantılı Apple Dokümanlarına bakın. Bunu açıklıyor. İyi şanslar!
David Weiss

1
Gerçekten güzel bir soru ve kurulumunuzla ilgili verdiğiniz açıklamadan birkaç düzgün numara aldım
djskinner

Yanıtlar:


47

Muhtemelen ana MOC'yi de adım adım kaydetmelisiniz. O MOC'nin kaydetmek için sonuna kadar beklemesinin anlamı yok. Kendi iş parçacığı vardır ve hafızayı düşük tutmaya da yardımcı olur.

Sen yazdın:

Daha sonra, içe aktarma işleminin sonunda, görünüşte, değişiklikleri ana bağlam dahil olmak üzere diğer alt bağlamlara iten ana / üst bağlamı kaydederim:

Yapılandırmanızda, her ikisi de "ana" olarak atanan iki alt öğeniz (ana MOC ve arka plan MOC) var.

Bir çocuktan tasarruf ettiğinizde, değişiklikleri ebeveyne doğru iter. Bu MOC'nin diğer alt öğeleri, bir sonraki getirme gerçekleştirdiklerinde verileri göreceklerdir ... kendilerine açıkça bildirilmez.

Böylece, BG kaydettiğinde verileri MASTER'a gönderilir. Ancak MASTER kaydedinceye kadar bu verilerin hiçbirinin diskte olmadığını unutmayın. Ayrıca, MASTER diske kaydedinceye kadar yeni öğeler kalıcı kimliklere sahip olmayacaktır.

Senaryonuzda, DidSave bildirimi sırasında MASTER kaydetmeden birleştirerek verileri MAIN MOC'ye çekiyorsunuz.

Bu işe yaramalı, bu yüzden "asılı" nerede olduğunu merak ediyorum. Ana MOC iş parçacığında kanonik şekilde çalışmadığınızı not edeceğim (en azından iOS 5 için değil).

Ayrıca, muhtemelen yalnızca ana MOC'deki değişiklikleri birleştirmekle ilgileniyorsunuz (kaydınız zaten sadece bunun için gibi görünse de). Kaydetme üzerine güncelleme bildirimini kullanacak olsaydım, bunu yapardım ...

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}

Şimdi, asılmayla ilgili asıl sorununuz ne olabilir ... Ana kişiyi kaydetmek için iki farklı çağrı gösterirsiniz. ilki kendi performBlock'unda iyi korunur, ancak ikincisi değildir (gerçi bir performBlock'ta saveMasterContext'i çağırıyor olabilirsiniz ...

Ancak, bu kodu da değiştirirdim ...

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}

Ancak, MAIN'in MASTER'ın çocuğu olduğuna dikkat edin. Yani değişiklikleri birleştirmek zorunda kalmamalıdır. Bunun yerine, ustadaki DidSave'i izleyin ve sadece yeniden ayarlayın! Veriler zaten ebeveyninizde oturuyor, sadece onu istemenizi bekliyor. Verilerin ilk etapta ebeveynde olmasının faydalarından biri budur.

Dikkate alınması gereken başka bir alternatif (ve sonuçlarınızı duymak isterim - bu çok fazla veri) ...

Arka plan MOC'yi MASTER'ın çocuğu yapmak yerine, onu MAIN'in çocuğu yapın.

Bunu al. KŞ her kaydettiğinde, otomatik olarak ANA'ya itilir. Şimdi, ANA, kaydetme çağrısı yapmak zorundadır ve sonra ana birim kaydetmeyi çağırmak zorundadır, ancak tüm bunlar, ana birim diske kaydedene kadar işaretçileri taşımaktır.

Bu yöntemin güzelliği, verilerin arka plandaki MOC'den doğrudan uygulamalarınızın MOC'sine gitmesidir (daha sonra kaydedilmek için geçer).

Geçiş için bir miktar ceza vardır , ancak tüm ağır kaldırma, diske çarptığında MASTER'da yapılır. PerformBlock ile master üzerindeki bu tasarrufları atarsanız, ana iş parçacığı isteği gönderir ve hemen geri döner.

Lütfen nasıl gittiğini bana bildirin!


Mükemmel cevap. Bugün bu fikirleri deneyeceğim ve ne keşfettiğimi göreceğim. Teşekkür ederim!
David Weiss

Harika! Bu mükemmel çalıştı! Yine de, MASTER -> MAIN -> BG önerinizi deneyeceğim ve bu performansın nasıl çalıştığını göreceğim, bu çok ilginç bir fikir gibi görünüyor. Harika fikirler için teşekkürler!
David Weiss

4
PerformBlockAndWait'i performBlock olarak değiştirmek için güncellendi. Bunun neden kuyruğumda tekrar ortaya çıktığından emin değilim, ama bu sefer okuduğumda, açıktı ... neden daha önce bıraktığımdan emin değilim. Evet, performBlockAndWait yeniden giriyor. Ancak, bunun gibi iç içe geçmiş bir ortamda, eşzamanlı sürümü bir ana bağlamdan bir alt bağlamda çağıramazsınız. Bildirim, kilitlenmeye neden olabilen üst bağlamdan gönderilebilir (bu durumda gönderilir). Umarım bu, gelip bunu daha sonra okuyanlar için açıktır. Teşekkürler David.
Jody Hagins

1
@DavidWeiss MASTER -> MAIN -> BG'yi denediniz mi? Bu tasarım modeliyle ilgileniyorum ve sizin için uygun olup olmadığını öğrenmeyi umuyorum. Teşekkür ederim.
nonamelive

2
MASTER -> MAIN -> KŞ kalıbı ile ilgili sorun, KŞ içeriğinden getirdiğinizde, MAIN'den de alınacak ve bu,
UI'yi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.