iPhone Temel Verileri "Üretim" Hatası İşleme


85

Apple referansları tarafından sağlanan örnek kodda Core Data hatalarını nasıl işlemeniz gerektiğini gördüm. Yani:

NSError *error = nil;
if (![context save:&error]) {
/*
 Replace this implementation with code to handle the error appropriately.

 abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
 */
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

Ancak bunu nasıl uygulamanız gerektiğine dair hiçbir örnek vermeyin.

Yukarıdaki yöntemi gösteren bazı gerçek "üretim" koduna sahip olan (veya beni yönlendirebilecek) var mı?

Şimdiden teşekkürler Matt


8
+1 bu mükemmel bir soru.
Dave DeLong

Yanıtlar:


33

Uygulamanıza ve hatanın nerede oluştuğuna% 100 bağlı olduğu için kimse size üretim kodunu göstermez.

Şahsen ben oraya bir assert ifadesi koyuyorum çünkü bu hatanın% 99.9'u geliştirme aşamasında ortaya çıkacak ve orada düzelttiğinizde bunu üretimde görme olasılığınız çok düşük.

Kullanıcıya bir uyarı sunacağım iddiasından sonra, onlara kurtarılamaz bir hata oluştuğunu ve uygulamanın kapanacağını bildirin. Ayrıca, geliştiriciyle iletişime geçmelerini isteyen bir tanıtım yazısı da koyabilirsiniz, böylece bunu yaptığınızı umarız takip edebilirsiniz.

Bundan sonra, uygulamayı "kilitleyeceği" ve daha sonra sorunu izlemek için kullanabileceğinizi umduğumuz bir yığın izlemesi oluşturacağı için abort () 'u orada bırakırdım.


Marcus - Yerel bir sqlite veritabanı veya XML dosyasıyla konuşursanız iddialar iyi olsa da, kalıcı mağazanız bulut tabanlıysa daha sağlam bir hata işleme mekanizmasına ihtiyacınız var.
dar512

4
İOS Temel Veri kalıcı deponuz bulut tabanlıysa, daha büyük sorunlarınız vardır.
Marcus S. Zarra

3
Bazı konularda Apple'a katılmıyorum. Bir öğretim durumu (Apple) ile siperlerdeki (ben) arasındaki farktır. Akademik bir durumdan, evet kürtajları kaldırmalısınız. Gerçekte, asla mümkün olmasını hayal etmediğiniz durumları yakalamak için faydalıdırlar. Apple dokümantasyon yazarları, her durumun sorumlu olduğunu iddia etmeyi severler. % 99,999'u öyledir. Gerçekten beklenmedik şeyler için ne yaparsınız? Çöküyorum ve ne olduğunu öğrenebilmek için bir günlük oluşturuyorum. Kürtaj bunun için.
Marcus S. Zarra

1
@cschuff, bunların hiçbiri temel bir veri -save:çağrısını etkilemez . Tüm bu koşullar, kodunuz bu noktaya ulaşmadan çok önce gerçekleşir.
Marcus S. Zarra

3
Bu, kaydetmeden önce yakalanabilen ve düzeltilebilen beklenen bir hatadır. Verilerin geçerli olup olmadığını ve düzeltilip düzeltilmediğini Çekirdek Verilere sorabilirsiniz. Ayrıca, tüm geçerli alanların mevcut olduğundan emin olmak için bunu tüketim anında test edebilirsiniz. Bu, -save:çağrılmadan çok önce ele alınabilecek geliştirici düzeyinde bir hatadır .
Marcus S. Zarra

32

Bu, iPhone'daki doğrulama hatalarını işlemek ve görüntülemek için bulduğum genel bir yöntem. Ancak Marcus haklı: Muhtemelen mesajları daha kullanıcı dostu olacak şekilde değiştirmek istersiniz. Ama bu en azından size hangi alanın neden doğrulamadığını görmek için bir başlangıç ​​noktası verir.

- (void)displayValidationError:(NSError *)anError {
    if (anError && [[anError domain] isEqualToString:@"NSCocoaErrorDomain"]) {
        NSArray *errors = nil;

        // multiple errors?
        if ([anError code] == NSValidationMultipleErrorsError) {
            errors = [[anError userInfo] objectForKey:NSDetailedErrorsKey];
        } else {
            errors = [NSArray arrayWithObject:anError];
        }

        if (errors && [errors count] > 0) {
            NSString *messages = @"Reason(s):\n";

            for (NSError * error in errors) {
                NSString *entityName = [[[[error userInfo] objectForKey:@"NSValidationErrorObject"] entity] name];
                NSString *attributeName = [[error userInfo] objectForKey:@"NSValidationErrorKey"];
                NSString *msg;
                switch ([error code]) {
                    case NSManagedObjectValidationError:
                        msg = @"Generic validation error.";
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        msg = [NSString stringWithFormat:@"The attribute '%@' mustn't be empty.", attributeName];
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:  
                        msg = [NSString stringWithFormat:@"The relationship '%@' doesn't have enough entries.", attributeName];
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        msg = [NSString stringWithFormat:@"The relationship '%@' has too many entries.", attributeName];
                        break;
                    case NSValidationRelationshipDeniedDeleteError:
                        msg = [NSString stringWithFormat:@"To delete, the relationship '%@' must be empty.", attributeName];
                        break;
                    case NSValidationNumberTooLargeError:                 
                        msg = [NSString stringWithFormat:@"The number of the attribute '%@' is too large.", attributeName];
                        break;
                    case NSValidationNumberTooSmallError:                 
                        msg = [NSString stringWithFormat:@"The number of the attribute '%@' is too small.", attributeName];
                        break;
                    case NSValidationDateTooLateError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is too late.", attributeName];
                        break;
                    case NSValidationDateTooSoonError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is too soon.", attributeName];
                        break;
                    case NSValidationInvalidDateError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is invalid.", attributeName];
                        break;
                    case NSValidationStringTooLongError:      
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' is too long.", attributeName];
                        break;
                    case NSValidationStringTooShortError:                 
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' is too short.", attributeName];
                        break;
                    case NSValidationStringPatternMatchingError:          
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' doesn't match the required pattern.", attributeName];
                        break;
                    default:
                        msg = [NSString stringWithFormat:@"Unknown error (code %i).", [error code]];
                        break;
                }

                messages = [messages stringByAppendingFormat:@"%@%@%@\n", (entityName?:@""),(entityName?@": ":@""),msg];
            }
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Validation Error" 
                                                            message:messages
                                                           delegate:nil 
                                                  cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
            [alert release];
        }
    }
}

Zevk almak.


3
Bu kodda kesinlikle yanlış bir şey göremiyorum. Sağlam görünüyor. Kişisel olarak, bir iddia ile Temel Veri hatalarını halletmeyi tercih ediyorum. Henüz üretime giden birini görmedim, bu yüzden bunların her zaman potansiyel üretim hataları yerine geliştirme hataları olduğunu düşündüm. Bu kesinlikle başka bir koruma seviyesi olsa da :)
Marcus S. Zarra

2
Marcus, iddialar hakkında: Doğrulamalar açısından kodun KURU tutulması hakkında fikriniz nedir? Bence, doğrulama kriterlerinizi modelde (ait olduğu yerde) yalnızca bir kez tanımlamanız çok arzu edilir: Bu alan boş olamaz, bu alan en az 5 karakter uzunluğunda olmalı ve bu alan bu normal ifadeyle eşleşmelidir . Kullanıcıya uygun bir mesaj görüntülemek için gereken tüm bilgiler bu olmalıdır . MOC'yi kaydetmeden önce bu kontrolleri kodda tekrar yapmam bir şekilde bana pek uymuyor. Ne düşünüyorsun?
Johannes Fahrenkrug

2
Cevabımda olmadığı için bu yorumu hiç görmedim. Modele doğrulamayı koyduğunuzda bile, nesnenin doğrulamayı geçip geçmediğini kontrol etmeniz ve bunu kullanıcıya sunmanız gerekir. Saha düzeyinde (bu şifre kötü, vb.) Veya kaydetme noktasında olabilecek tasarıma bağlı olarak. Tasarımcının seçimi. Bunu genel uygulamanın bir parçası yapmazdım.
Marcus S. Zarra

1
@ MarcusS.Zarra Sanırım bunu asla anlamadınız çünkü doğru bir şekilde @ -senden bahsetmedim :) Sanırım tamamen kabul ediyoruz: Doğrulama bilgisinin modelde olmasını istiyorum , ancak doğrulamayı ne zaman tetikleyeceğine ve Doğrulama sonucunun nasıl ele alınacağı ve sunulacağı genel olmamalı ve uygulama kodundaki uygun yerlerde ele alınmalıdır.
Johannes Fahrenkrug

Kod harika görünüyor. Tek sorum, uyarıyı gösterdikten veya analizi kaydettikten sonra, Temel Veri bağlamını geri mi almalıyım yoksa uygulamayı iptal mi etmeliyim? Aksi takdirde, yeniden kaydetmeye çalıştığınızda kaydedilmemiş değişikliklerin aynı soruna neden olmaya devam edeceğini tahmin ediyorum.
Jake

6

Burada hiç kimsenin hatayı olması gerektiği gibi ele almadığına şaşırdım. Belgelere bakarsanız göreceksiniz.

Buradaki bir hatanın tipik nedenleri şunlardır: * Aygıtta yer kalmamış. * Kalıcı mağazaya, cihaz kilitlendiğinde izinler veya veri koruması nedeniyle erişilemez. * Mağaza, mevcut model sürümüne taşınamadı. * Ana dizin mevcut değil, oluşturulamıyor veya yazmaya izin vermiyor.

Bu yüzden, çekirdek veri yığınını kurarken bir hata bulursam, UIWindow'un rootViewController'ını değiştiririm ve kullanıcıya cihazlarının dolu olabileceğini veya güvenlik ayarlarının bu Uygulamanın çalışması için çok yüksek olduğunu açıkça söyleyen UI'yi gösteririm. Ayrıca onlara bir 'tekrar dene' düğmesi veriyorum, böylece çekirdek veri yığını yeniden denenmeden önce sorunu çözmeye çalışabilirler.

Örneğin, kullanıcı biraz depolama alanı boşaltabilir, Uygulamama dönebilir ve tekrar dene düğmesine basabilir.

İddialar? Gerçekten mi? Odada çok fazla geliştirici var!

Ayrıca, bir kaydetme işleminin bu nedenlerle nasıl başarısız olabileceğinden bahsetmeyen çevrimiçi eğitimlerin sayısına da şaşırdım. Bu nedenle, Uygulamanızda HERHANGİ BİR YERDE herhangi bir kaydetme etkinliğinin başarısız olabileceğinden emin olmanız gerekir, çünkü cihaz SADECE BU DAKİKA, Uygulamalar tasarruf tasarrufunuzla doldu.


Bu soru Çekirdek Veri yığınında kaydetme ile ilgilidir, Çekirdek Veri Yığını kurmakla ilgili değildir. Ancak başlığının yanıltıcı olabileceğini ve belki de değiştirilmesi gerektiğini kabul ediyorum.
valeCocoa

@ValeCocoa'ya katılmıyorum. Gönderi, üretimde tasarruf hatalarının nasıl ele alınacağıyla ilgili. Bir kez daha bakın.

@roddanash dediğim şey ... WtH! :) Cevabınıza bir kez daha bakın.
valeCocoa

Sen delisin kardeşim

İçeriği kaydederken oluşan hatalarla ilgili bir soruya kalıcı mağazayı örneklerken ortaya çıkabilecek hatalar için belgelerin bir kısmını yapıştırıyorsunuz ve çılgın olan ben miyim? Tamam…
valeCocoa

5

Bu ortak kaydetme işlevini çok daha iyi bir çözüm buldum:

- (BOOL)saveContext {
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        DDLogError(@"[%@::%@] Whoops, couldn't save managed object context due to errors. Rolling back. Error: %@\n\n", NSStringFromClass([self class]), NSStringFromSelector(_cmd), error);
        [self.managedObjectContext rollback];
        return NO;
    }
    return YES;
}

Bir kaydetme başarısız olduğunda bu, NSManagedObjectContext'inizi geri alacaktır, yani son kayıttan bu yana bağlamda gerçekleştirilen tüm değişiklikleri sıfırlayacaktır . Bu nedenle, yukarıdaki kaydetme işlevini kullanarak değişiklikleri her zaman mümkün olduğunca erken ve düzenli bir şekilde sürdürmek için dikkatli olmalısınız, aksi takdirde verileri kolayca kaybedebilirsiniz.

Veri eklemek için bu, diğer değişikliklerin devam etmesine izin veren daha gevşek bir varyant olabilir:

- (BOOL)saveContext {
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        DDLogError(@"[%@::%@] Whoops, couldn't save. Removing erroneous object from context. Error: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), object.objectId, error);
        [self.managedObjectContext deleteObject:object];
        return NO;
    }
    return YES;
}

Not: Burada oturum açmak için CocoaLumberjack kullanıyorum.

Bunun nasıl geliştirileceğine dair herhangi bir yorum, daha sonra hoş geldiniz!

BR Chris


Bunu başarmak için geri alma özelliğini kullanmaya çalıştığımda garip davranışlar alıyorum: stackoverflow.com/questions/34426719/…
malhal

Bunun yerine şimdi geri al kullanıyorum
malhal

2

@JohannesFahrenkrug'un yararlı yanıtının bir Swift versiyonunu hazırladım ve bu yararlı olabilir:

public func displayValidationError(anError:NSError?) -> String {
    if anError != nil && anError!.domain.compare("NSCocoaErrorDomain") == .OrderedSame {
        var messages:String = "Reason(s):\n"
        var errors = [AnyObject]()
        if (anError!.code == NSValidationMultipleErrorsError) {
            errors = anError!.userInfo[NSDetailedErrorsKey] as! [AnyObject]
        } else {
            errors = [AnyObject]()
            errors.append(anError!)
        }
        if (errors.count > 0) {
            for error in errors {
                if (error as? NSError)!.userInfo.keys.contains("conflictList") {
                    messages =  messages.stringByAppendingString("Generic merge conflict. see details : \(error)")
                }
                else
                {
                    let entityName = "\(((error as? NSError)!.userInfo["NSValidationErrorObject"] as! NSManagedObject).entity.name)"
                    let attributeName = "\((error as? NSError)!.userInfo["NSValidationErrorKey"])"
                    var msg = ""
                    switch (error.code) {
                    case NSManagedObjectValidationError:
                        msg = "Generic validation error.";
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        msg = String(format:"The attribute '%@' mustn't be empty.", attributeName)
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:
                        msg = String(format:"The relationship '%@' doesn't have enough entries.", attributeName)
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        msg = String(format:"The relationship '%@' has too many entries.", attributeName)
                        break;
                    case NSValidationRelationshipDeniedDeleteError:
                        msg = String(format:"To delete, the relationship '%@' must be empty.", attributeName)
                        break;
                    case NSValidationNumberTooLargeError:
                        msg = String(format:"The number of the attribute '%@' is too large.", attributeName)
                        break;
                    case NSValidationNumberTooSmallError:
                        msg = String(format:"The number of the attribute '%@' is too small.", attributeName)
                        break;
                    case NSValidationDateTooLateError:
                        msg = String(format:"The date of the attribute '%@' is too late.", attributeName)
                        break;
                    case NSValidationDateTooSoonError:
                        msg = String(format:"The date of the attribute '%@' is too soon.", attributeName)
                        break;
                    case NSValidationInvalidDateError:
                        msg = String(format:"The date of the attribute '%@' is invalid.", attributeName)
                        break;
                    case NSValidationStringTooLongError:
                        msg = String(format:"The text of the attribute '%@' is too long.", attributeName)
                        break;
                    case NSValidationStringTooShortError:
                        msg = String(format:"The text of the attribute '%@' is too short.", attributeName)
                        break;
                    case NSValidationStringPatternMatchingError:
                        msg = String(format:"The text of the attribute '%@' doesn't match the required pattern.", attributeName)
                        break;
                    default:
                        msg = String(format:"Unknown error (code %i).", error.code) as String
                        break;
                    }

                    messages = messages.stringByAppendingString("\(entityName).\(attributeName):\(msg)\n")
                }
            }
        }
        return messages
    }
    return "no error"
}`
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.