Apakah Anda pernah menulis adaptor untuk Keychain atau NSUserDefaults? Mereka sepenuhnya terdiri dari setter dan getter yang sama. Saya mengusulkan untuk menulis logika sekali, menyediakan sisanya untuk runtime. Untuk implementasi, saya minta kucing.

Hai Dengan Anda lagi vdugnist dari FunCorp. Baru-baru ini, ketika menambahkan bidang baru ke adaptor Keychain, saya menemukan kesalahan saat menyalin kode dari metode tetangga.
Seperti apa implementasinya sebelumnya:
- (Credentials *)credentials { // implementation details } - (void)setCredentials:(Credentials *)credentials { // implementation details } - (NSDate *)firstLaunchDate { // implementation details } - (void)setFirstLaunchDate:(NSDate *)date { // implementation details }
Kode hanya disalin dari metode terdekat dan konstanta diubah di dalamnya. Jelas bahwa dengan pendekatan ini Anda dapat dengan mudah melakukan kesalahan. Setelah sedikit refactoring, ternyata mengeluarkan seluruh implementasi logika kelas dalam dua metode:
- (Credentials *)credentials { return [self objectFromKeychainForKey:@"credentials"]; } - (void)setCredentials:(Credentials *)credentials { [self setObject:credentials toKeychainForKey:@"credentials"]; } - (NSDate *)firstLaunchDate { return [self objectFromKeychainForKey:@"firstLaunchDate"]; } - (void)setFirstLaunchDate:(NSDate *)firstLaunchDate { [self setObject:firstLaunchDate toKeychainForKey:@"firstLaunchDate"]; } - (void)setObject:(id)obj toKeychainForKey:(NSString *)key {
Sudah lebih baik. Namun masih ada dua masalah lagi:
- Anda masih dapat disegel dalam konstanta string yang kami berikan ke metode;
- seluruh kelas akan terdiri dari metode dengan implementasi yang sama, hanya berbeda dalam argumen dari metode yang dipanggil.
Dan inilah bantuan bagi kita. Di Objective-C, ketika @ properti ditambahkan ke antarmuka kelas, setter, pengambil dan ivar dihasilkan secara otomatis. Dalam implementasi setter standar, nilainya ditulis dalam ivar, dan untuk pengambil, dibaca dari ivar. Agar metode ini tidak dihasilkan, dalam implementasi kelas Anda perlu menulis dinamis <nama bidang>. Kemudian, ketika mengakses bidang, kita akan mendapatkan pemilih yang tidak dikenal dikirim ke pengecualian instance.
Sebelum mengirim pengecualian, kelas akan +(BOOL)resolveInstanceMethod:(SEL)sel
dalam kasus properti instance atau +(BOOL)resolveClassMethod:(SEL)sel
dalam kasus properti kelas.
Di dalamnya, Anda dapat menambahkan implementasi metode dengan pemilih menggunakan class_addMethod
dan mengembalikan YES
jika semuanya berjalan lancar. Setelah itu, implementasi metode yang baru ditambahkan akan dipanggil untuk panggilan saat ini dan selanjutnya.
Untuk menambahkan metode dalam runtime Anda perlu pointer ke kelas yang metode akan ditambahkan, pemilih, blok implementasi dan tanda tangan metode. Dokumentasi untuk argumen terakhir dapat ditemukan di sini .
Saya segera memutuskan untuk membuat solusi untuk masalah saya di perpustakaan, sehingga properti kelas dan properti instance diproses dalam contoh. Contohnya menggunakan fungsi tambahan, implementasinya dapat ditemukan di sini .
+ (BOOL)resolveClassMethod:(SEL)sel { return [self resolveMethodFor:object_getClass(self) selector:sel]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return [self resolveMethodFor:self selector:sel]; } + (BOOL)resolveMethodFor:(id)target selector:(SEL)sel { if (!sel_isGetterOrSetter(target, sel)) { return NO; } objc_property_t property = propertyForSelector(target, sel); if (sel_isSetter(target, sel)) { SEL getterSel = sel_getterFromSetter(sel); dvPropertySetterBlock setterBlock = [self setterBlockForTarget:target getterSelector:getterSel]; IMP blockImplementation = imp_implementationWithBlock(setterBlock); char *methodTypes = copySetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } else { dvPropertyGetterBlock getterBlock = [self getterBlockForTarget:target getterSelector:sel]; IMP blockImplementation = imp_implementationWithBlock(getterBlock); char *methodTypes = copyGetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } return YES; } + (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; }
Dalam ahli waris, itu sudah cukup untuk mengesampingkan dua metode (blok getter dan blok setter), tambahkan @ properti ke antarmuka dan dinamis untuk implementasi. Di sini, misalnya, adalah implementasi adaptor untuk NSUserDefaults:
+ (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^(id blockSelf, id value) { [[NSUserDefaults standardUserDefaults] setObject:value forKey:NSStringFromSelector(getterSelector)]; }; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^id(id blockSelf) { return [[NSUserDefaults standardUserDefaults] objectForKey:NSStringFromSelector(getterSelector)]; }; }
Perpustakaan itu sendiri dapat ditemukan di github , dan saya siap menjawab pertanyaan Anda di komentar.