Bagaimana saya bisa menggunakan NSError di Aplikasi iPhone saya?

228

Saya sedang berupaya menangkap kesalahan di aplikasi saya, dan saya ingin menggunakannya NSError. Saya sedikit bingung tentang cara menggunakannya, dan bagaimana mengisinya.

Bisakah seseorang memberikan contoh tentang bagaimana saya mengisi kemudian menggunakan NSError?

Nic Hubbard
sumber

Jawaban:

473

Nah, apa yang biasanya saya lakukan adalah memiliki metode saya yang bisa error-out saat runtime mengambil referensi ke sebuah NSErrorpointer. Jika ada sesuatu yang salah dalam metode itu, saya dapat mengisi NSErrorreferensi dengan data kesalahan dan mengembalikan nol dari metode tersebut.

Contoh:

- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return nil;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

Kami kemudian dapat menggunakan metode seperti ini. Bahkan tidak perlu repot untuk memeriksa objek kesalahan kecuali metode mengembalikan nol:

// initialize NSError object
NSError* error = nil;
// try to feed the world
id yayOrNay = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!yayOrNay) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.

Kami dapat mengakses kesalahan localizedDescriptionkarena kami menetapkan nilai untuk NSLocalizedDescriptionKey.

Tempat terbaik untuk informasi lebih lanjut adalah dokumentasi Apple . Sangat bagus.

Ada juga tutorial yang bagus dan sederhana tentang Cocoa Is My Girlfriend .

Alex
sumber
37
ini adalah contoh paling lucu, pernah
ming Yeow
ini adalah jawaban yang cukup luar biasa, meskipun ada beberapa masalah di ARC dan casting idke a BOOL. Variasi yang kompatibel dengan ARC sedikit akan sangat dihargai.
NSTJ
6
@ TomJowett saya akan benar-benar marah jika kita akhirnya tidak bisa mengakhiri kelaparan dunia hanya karena Apple mendorong kita untuk pindah ke dunia ARC yang lebih baru.
Manav
1
tipe pengembaliannya bisa BOOL. Kembali NOjika terjadi kesalahan dan bukannya memeriksa nilai kembali, cukup periksa error. Jika nilterus maju, jika != nilmenanganinya.
Gabriele Petronella
8
-1: Anda benar-benar perlu memasukkan kode yang memverifikasi **errorbukan nol. Kalau tidak, program akan melempar kesalahan yang sama sekali tidak ramah dan tidak membuat jelas apa yang terjadi.
FreeAsInBeer
58

Saya ingin menambahkan beberapa saran lagi berdasarkan implementasi terbaru saya. Saya telah melihat beberapa kode dari Apple dan saya pikir kode saya berperilaku dengan cara yang sama.

Posting di atas sudah menjelaskan cara membuat objek NSError dan mengembalikannya, jadi saya tidak akan repot dengan bagian itu. Saya hanya akan mencoba menyarankan cara yang baik untuk mengintegrasikan kesalahan (kode, pesan) di aplikasi Anda sendiri.


Saya sarankan untuk membuat 1 tajuk yang akan menjadi ikhtisar semua kesalahan domain Anda (mis. Aplikasi, pustaka, dll.). Header saya saat ini terlihat seperti ini:

FSError.h

FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;

enum {
    FSUserNotLoggedInError = 1000,
    FSUserLogoutFailedError,
    FSProfileParsingFailedError,
    FSProfileBadLoginError,
    FSFNIDParsingFailedError,
};

FSError.m

#import "FSError.h" 

NSString *const FSMyAppErrorDomain = @"com.felis.myapp";

Sekarang ketika menggunakan nilai-nilai di atas untuk kesalahan, Apple akan membuat beberapa pesan kesalahan standar dasar untuk aplikasi Anda. Kesalahan dapat dibuat seperti berikut:

+ (FSProfileInfo *)profileInfoWithData:(NSData *)data error:(NSError **)error
{
    FSProfileInfo *profileInfo = [[FSProfileInfo alloc] init];
    if (profileInfo)
    {
        /* ... lots of parsing code here ... */

        if (profileInfo.username == nil)
        {
            *error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:nil];            
            return nil;
        }
    }
    return profileInfo;
}

Pesan kesalahan standar yang dihasilkan Apple ( error.localizedDescription) untuk kode di atas akan terlihat seperti berikut:

Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"

Di atas sudah cukup membantu bagi pengembang, karena pesan menampilkan domain tempat kesalahan terjadi dan kode kesalahan yang sesuai. Pengguna akhir tidak akan tahu kode kesalahan apa1002 artinya , jadi sekarang kita perlu mengimplementasikan beberapa pesan bagus untuk setiap kode.

Untuk pesan kesalahan kami harus mengingat lokalisasi (bahkan jika kami tidak segera mengimplementasikan pesan lokal). Saya telah menggunakan pendekatan berikut dalam proyek saya saat ini:


1) buat stringsfile yang akan mengandung kesalahan. File string mudah dilokalisasi. File tersebut dapat terlihat seperti berikut:

FSError.strings

"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."

2) Tambahkan makro untuk mengonversi kode integer ke pesan kesalahan yang dilokalkan. Saya telah menggunakan 2 makro dalam file Constants + Macros.h saya. Saya selalu menyertakan file ini di header awalan (MyApp-Prefix.pch ) untuk kenyamanan.

Konstanta + Makro.h

// error handling ...

#define FS_ERROR_KEY(code)                    [NSString stringWithFormat:@"%d", code]
#define FS_ERROR_LOCALIZED_DESCRIPTION(code)  NSLocalizedStringFromTable(FS_ERROR_KEY(code), @"FSError", nil)

3) Sekarang mudah untuk menampilkan pesan kesalahan yang ramah pengguna berdasarkan kode kesalahan. Sebuah contoh:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];
Wolfgang Schreurs
sumber
9
Jawaban bagus! Tetapi mengapa tidak menempatkan deskripsi lokal di kamus info pengguna di tempatnya? [NSError errorWithDomain: FSMyAppErrorDomain kode: FSProfileParsingFailedError userInfo: @ {NSLocalizedDescriptionKey: FS_ERROR_LOCALIZED_DESCRIPTION (error.code)}];
Richard Venable
1
Apakah ada tempat khusus di mana saya harus meletakkan file string? Dari FS_ERROR_LOCALIZED_DESCRIPTION () Saya mendapatkan hanya nomor (kode kesalahan).
huggie
@ Huggie: tidak begitu yakin apa yang Anda maksud. Saya biasanya meletakkan makro ini yang saya gunakan di seluruh aplikasi dalam file yang disebut Constants+Macros.hdan mengimpor file ini di header awalan ( .pchfile) sehingga tersedia di mana-mana. Jika Anda maksudnya Anda hanya menggunakan 1 dari 2 makro, itu mungkin berhasil. Mungkin konversi dari intke NSStringtidak terlalu penting, meskipun saya belum menguji ini.
Wolfgang Schreurs
@ Harry: ow, saya pikir saya mengerti Anda sekarang. String harus dalam file lokal ( .stringsfile), karena di situlah makro Apple akan terlihat. Baca tentang menggunakan di NSLocalizedStringFromTablesini: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Wolfgang Schreurs
1
@ Huggie: Ya, saya menggunakan tabel string lokal. Kode dalam makro FS_ERROR_LOCALIZED_DESCRIPTIONmemeriksa string localisable dalam file bernama FSError.strings. Anda mungkin ingin melihat panduan pelokalan Apple pada .stringsfile jika ini asing bagi Anda.
Wolfgang Schreurs
38

Jawaban bagus Alex. Salah satu masalah yang potensial adalah dereference NULL. Referensi Apple tentang Membuat dan Mengembalikan objek NSError

...
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];

if (error != NULL) {
    // populate the error object with the details
    *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
...
jlmendezbonini
sumber
30

Objektif-C

NSError *err = [NSError errorWithDomain:@"some_domain"
                                   code:100
                               userInfo:@{
                                           NSLocalizedDescriptionKey:@"Something went wrong"
                               }];

Cepat 3

let error = NSError(domain: "some_domain",
                      code: 100,
                  userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])
AlBeebe
sumber
9

Silakan lihat tutorial berikut

Saya harap ini akan membantu Anda tetapi sebelum Anda harus membaca dokumentasi NSError

Tautan ini sangat menarik yang saya temukan baru-baru ini ErrorHandling

Tirth
sumber
3

Saya akan mencoba merangkum jawaban hebat dari Alex dan poin jlmendezbonini, menambahkan modifikasi yang akan membuat semuanya ARC kompatibel (sejauh ini bukan karena ARC akan mengeluh karena Anda harus kembali id, yang berarti "objek apa pun", tetapi BOOLbukan objek Tipe).

- (BOOL) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        if (error != NULL) {
             // populate the error object with the details
             *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        }
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return NO;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

Sekarang alih-alih memeriksa nilai balik dari pemanggilan metode kami, kami memeriksa apakah errormasih nil. Jika tidak, kita punya masalah.

// initialize NSError object
NSError* error = nil;
// try to feed the world
BOOL success = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!success) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.
Gabriele Petronella
sumber
3
@ Gabriela: Apple menyatakan bahwa ketika menggunakan variabel tidak langsung untuk mengembalikan kesalahan, metode itu sendiri harus selalu memiliki beberapa nilai balik jika berhasil atau gagal. Apple mendesak pengembang untuk terlebih dahulu memeriksa nilai kembali dan hanya jika nilai baliknya entah bagaimana tidak valid untuk memeriksa kesalahan. Lihat halaman berikut: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/…
Wolfgang Schreurs
3

Pola desain lain yang saya lihat melibatkan menggunakan blok, yang sangat berguna ketika suatu metode dijalankan secara tidak sinkron.

Katakanlah kita memiliki kode kesalahan berikut yang ditetapkan:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

Anda akan mendefinisikan metode Anda yang dapat meningkatkan kesalahan seperti:

- (void)getContentsOfURL:(NSString *)path success:(void(^)(NSString *html))success failure:(void(^)(NSError *error))failure {
    if (path.length == 0) {
        if (failure) {
            failure([NSError errorWithDomain:@"com.example" code:MyErrorCodesEmptyString userInfo:nil]);
        }
        return;
    }

    NSString *htmlContents = @"";

    // Exercise for the reader: get the contents at that URL or raise another error.

    if (success) {
        success(htmlContents);
    }
}

Dan kemudian ketika Anda menyebutnya, Anda tidak perlu khawatir tentang mendeklarasikan objek NSError (penyelesaian kode akan melakukannya untuk Anda), atau memeriksa nilai yang dikembalikan. Anda bisa menyediakan dua blok: satu yang akan dipanggil ketika ada pengecualian, dan satu yang dipanggil saat berhasil:

[self getContentsOfURL:@"http://google.com" success:^(NSString *html) {
    NSLog(@"Contents: %@", html);
} failure:^(NSError *error) {
    NSLog(@"Failed to get contents: %@", error);
    if (error.code == MyErrorCodesEmptyString) { // make sure to check the domain too
        NSLog(@"You must provide a non-empty string");
    }
}];
Masuk akal
sumber
0

Yah itu sedikit keluar dari ruang lingkup pertanyaan tetapi jika Anda tidak memiliki opsi untuk NSError Anda selalu dapat menampilkan kesalahan tingkat rendah:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);
Mike.R
sumber
0
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

yang dapat saya gunakan NSError.defaultError()setiap kali saya tidak memiliki objek kesalahan yang valid.

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
Hemang
sumber