Sebagian besar proyek iOS sebagian atau seluruhnya beralih ke Swift. Swift adalah bahasa yang hebat, dan dengan itu terletak masa depan pengembangan iOS. Tetapi bahasa ini terkait erat dengan toolkit, dan ada kelemahan pada toolkit Swift.
Kompiler Swift masih memiliki bug yang menyebabkannya mogok atau menghasilkan kode yang salah. Swift tidak memiliki ABI yang stabil. Dan, yang sangat penting, proyek Swift telah berlangsung terlalu lama.
Dalam hal ini, proyek yang ada mungkin lebih menguntungkan untuk melanjutkan pengembangan pada Objective-C. Dan Objective-C tidak seperti dulu!
Dalam seri artikel ini, kami akan menunjukkan fitur yang berguna dan peningkatan Objective-C, yang dengannya menjadi jauh lebih menyenangkan untuk menulis kode. Setiap orang yang menulis di Objective-C akan menemukan sesuatu yang menarik untuk diri mereka sendiri.

let
dan var
Objective-C tidak lagi perlu menentukan jenis variabel secara eksplisit: dalam Xcode 8, ekstensi bahasa __auto_type
muncul, dan sebelum inferensi tipe Xcode 8 tersedia di Objective-C ++ (menggunakan kata kunci auto
dengan munculnya C ++ 0X).
Pertama, let
tambahkan makro let
and var
:
#define let __auto_type const #define var __auto_type
Jika Anda biasa menulis const
setelah pointer ke kelas Objective-C, itu adalah kemewahan yang tidak dapat diterima, tetapi sekarang deklarasi implisit const
(via let
) telah diterima begitu saja. Perbedaannya terutama terlihat ketika menyimpan blok ke variabel.
Bagi kami sendiri, kami mengembangkan aturan untuk menggunakan let
dan var
untuk mendeklarasikan semua variabel. Bahkan ketika sebuah variabel diinisialisasi ke nil
:
- (nullable JMSomeResult *)doSomething { var result = (JMSomeResult *)nil; if (...) { result = ...; } return result; }
Satu-satunya pengecualian adalah ketika Anda perlu memastikan bahwa suatu variabel diberi nilai di setiap cabang kode:
NSString *value; if (...) { if (...) { value = ...; } else { value = ...; } } else { value = ...; }
Hanya dengan cara ini kita akan mendapatkan peringatan kompiler jika kita lupa memberikan nilai ke salah satu cabang.
Dan akhirnya: untuk menggunakan let
dan var
untuk variabel tipe id
, Anda perlu menonaktifkan peringatan auto-var-id
(tambahkan -Wno-auto-var-id
ke "Bendera Peringatan Lain" di pengaturan proyek).
Nilai pengembalian jenis blok otomatis
Hanya sedikit orang yang tahu bahwa kompiler dapat menyimpulkan tipe nilai pengembalian blok:
let block = ^{ return @"abc"; };
Sangat nyaman. Terutama jika Anda menulis kode reaktif menggunakan ReactiveObjC . Tetapi ada sejumlah batasan di mana Anda harus secara eksplisit menentukan jenis nilai pengembalian.
Jika ada beberapa return
dalam blok yang mengembalikan nilai dari tipe yang berbeda.
let block1 = ^NSUInteger(NSUInteger value){ if (value > 0) { return value; } else {
Jika ada return
di blok yang mengembalikan nil
.
let block1 = ^NSString * _Nullable(){ return nil; }; let block2 = ^NSString * _Nullable(BOOL flag) { if (flag) { return @"abc"; } else { return nil; } };
Jika blok harus mengembalikan BOOL
.
let predicate = ^BOOL(NSInteger lhs, NSInteger rhs){ return lhs > rhs; };
Ekspresi dengan operator pembanding di C (dan karenanya di Objective-C) bertipe int
. Oleh karena itu, lebih baik untuk menjadikannya aturan untuk selalu secara eksplisit menentukan BOOL
jenis pengembalian.
Generik dan for...in
Dalam Xcode 7, generik (atau lebih tepatnya, generik ringan) muncul di Objective-C. Kami harap Anda sudah menggunakannya. Tetapi jika tidak, Anda dapat menonton sesi WWDC atau membacanya di sini atau di sini .
Bagi kami sendiri, kami telah mengembangkan aturan untuk selalu menentukan parameter umum, bahkan jika itu adalah id
( NSArray<id> *
). Dengan demikian, mudah untuk membedakan antara kode lama di mana parameter generik belum ditentukan.
Dengan makro let
dan var
, kami berharap bahwa kami dapat menggunakannya dalam for...in
loop:
let items = (NSArray<NSString *> *)@[@"a", @"b", @"c"]; for (let item in items) { NSLog(@"%@", item); }
Tetapi kode seperti itu tidak dikompilasi. Kemungkinan besar, __auto_type
tidak didukung for...in
, karena for...in
hanya bekerja dengan koleksi yang menerapkan protokol NSFastEnumeration
. Dan untuk protokol di Objective-C tidak ada dukungan untuk obat generik.
Untuk memperbaiki kelemahan ini, cobalah membuat makro foreach
Anda. Hal pertama yang terlintas dalam pikiran: semua koleksi di Foundation memiliki properti objectEnumerator
, dan makro mungkin terlihat seperti ini:
#define foreach(object_, collection_) \ for (typeof([(collection_).objectEnumerator nextObject]) object_ in (collection_))
Tetapi untuk NSDictionary
dan NSMapTable
metode protokol NSMapTable
NSFastEnumeration
atas kunci, bukan nilai-nilai (Anda perlu menggunakan keyEnumerator
, bukan objectEnumerator
).
Kami perlu mendeklarasikan properti baru yang hanya akan digunakan untuk mendapatkan tipe dalam typeof
ekspresi:
@interface NSArray<__covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) ObjectType jm_enumeratedType; @end @interface NSDictionary<__covariant KeyType, __covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) KeyType jm_enumeratedType; @end #define foreach(object_, collection_) \ for (typeof((collection_).jm_enumeratedType) object_ in (collection_))
Sekarang kode kami terlihat jauh lebih baik:
Cuplikan untuk Xcode foreach (<#object#>, <#collection#>) { <#statements#> }
Generik dan copy
/ mutableCopy
Tempat lain di mana pengetikan tidak tersedia di Objective-C adalah metode -mutableCopy
dan -mutableCopy
(serta -copyWithZone:
dan -mutableCopyWithZone:
metode, tetapi kami tidak memanggilnya secara langsung).
Untuk menghindari kebutuhan gips eksplisit, Anda bisa mendeklarasikan ulang metode dengan tipe kembali. Misalnya, untuk NSArray
deklarasi akan menjadi:
@interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy; - (NSMutableArray<ObjectType> *)mutableCopy; @end
let items = [NSMutableArray<NSString *> array];
warn_unused_result
Karena kita telah mendeklarasikan ulang -mutableCopy
dan -mutableCopy
, akan lebih baik untuk menjamin bahwa hasil dari pemanggilan metode ini akan digunakan. Untuk melakukan ini, warn_unused_result
atribut warn_unused_result
.
#define JM_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
@interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy JM_WARN_UNUSED_RESULT; - (NSMutableArray<ObjectType> *)mutableCopy JM_WARN_UNUSED_RESULT; @end
Untuk kode berikut, kompiler akan menghasilkan peringatan:
let items = @[@"a", @"b", @"c"]; [items mutableCopy];
overloadable
Sedikit yang tahu bahwa Dentang memungkinkan Anda untuk mendefinisikan kembali fungsi dalam bahasa C (dan karenanya di Objective-C). Menggunakan atribut overloadable
, Anda dapat membuat fungsi dengan nama yang sama, tetapi dengan berbagai jenis argumen atau dengan nomor yang berbeda.
Fungsi yang tidak dapat ditimpa tidak dapat berbeda hanya dalam jenis nilai pengembalian.
#define JM_OVERLOADABLE __attribute__((overloadable))
JM_OVERLOADABLE float JMCompare(float lhs, float rhs); JM_OVERLOADABLE float JMCompare(float lhs, float rhs, float accuracy); JM_OVERLOADABLE double JMCompare(double lhs, double rhs); JM_OVERLOADABLE double JMCompare(double lhs, double rhs, double accuracy);
Ekspresi kotak
Kembali pada tahun 2012, pada sesi WWDC 413, Apple memperkenalkan literal untuk NSNumber
, NSArray
dan NSDictionary
, serta ekspresi kotak. Anda dapat membaca lebih lanjut tentang literal dan ekspresi kotak dalam dokumentasi Dentang .
Menggunakan literal dan ekspresi kotak, Anda bisa dengan mudah mendapatkan objek yang mewakili angka atau nilai Boolean. Tetapi untuk mendapatkan objek yang membungkus struktur, Anda perlu menulis beberapa kode:
Metode dan properti pembantu didefinisikan untuk beberapa kelas (seperti metode +[NSValue valueWithCGPoint:]
dan properti CGPointValue
), tetapi ini masih tidak CGPointValue
ekspresi kotak!
Dan pada 2015, Alex Denisov membuat tambalan untuk Dentang, memungkinkan Anda untuk menggunakan ekspresi kotak untuk membungkus struktur apa pun di NSValue
.
Agar struktur kami mendukung ekspresi kotak, Anda hanya perlu menambahkan atribut objc_boxable
untuk struktur.
#define JM_BOXABLE __attribute__((objc_boxable))
typedef struct JM_BOXABLE JMDimension { JMDimensionUnit unit; CGFloat value; } JMDimension;
Dan kita dapat menggunakan sintaks @(...)
untuk struktur kita:
let dimension = (JMDimension){ ... }; let boxedValue = @(dimension);
Anda masih harus -[NSValue getValue:]
struktur kembali melalui metode -[NSValue getValue:]
atau metode kategori.
CoreGraphics mendefinisikan ekspresi makro, CG_BOXABLE
, dan kemas sendiri sudah didukung untuk struktur CGPoint
, CGSize
, CGVector
dan CGRect
.
Untuk struktur lain yang umum digunakan, kami dapat menambahkan dukungan untuk ekspresi kotak sendiri:
typedef struct JM_BOXABLE _NSRange NSRange; typedef struct JM_BOXABLE CGAffineTransform CGAffineTransform; typedef struct JM_BOXABLE UIEdgeInsets UIEdgeInsets; typedef struct JM_BOXABLE NSDirectionalEdgeInsets NSDirectionalEdgeInsets; typedef struct JM_BOXABLE UIOffset UIOffset; typedef struct JM_BOXABLE CATransform3D CATransform3D;
Kata majemuk majemuk
Konstruksi bahasa lain yang bermanfaat adalah majemuk literal . Literal majemuk muncul di GCC sebagai ekstensi bahasa, dan kemudian ditambahkan ke standar C11.
Jika sebelumnya, setelah memenuhi panggilan ke UIEdgeInsetsMake
, kami hanya bisa menebak lekukan apa yang akan kami dapatkan (kami harus menonton deklarasi fungsi UIEdgeInsetsMake
), kemudian dengan literal majemuk, kode tersebut berbicara sendiri:
Akan lebih nyaman menggunakan konstruksi semacam itu ketika beberapa bidang nol:
(CGPoint){ .y = 10 }
Tentu saja, dalam literal majemuk, Anda tidak hanya dapat menggunakan konstanta, tetapi juga ekspresi apa pun:
textFrame = (CGRect){ .origin = { .y = CGRectGetMaxY(buttonFrame) + textMarginTop }, .size = textSize };
Cuplikan untuk Xcode (NSRange){ .location = <#location#>, .length = <#length#> } (CGPoint){ .x = <#x#>, .y = <#y#> } (CGSize){ .width = <#width#>, .height = <#height#> } (CGRect){ .origin = { .x = <#x#>, .y = <#y#> }, .size = { .width = <#width#>, .height = <#height#> } } (UIEdgeInsets){ .top = <#top#>, .left = <#left#>, .bottom = <#bottom#>, .right = <#right#> } (NSDirectionalEdgeInsets){ .top = <#top#>, .leading = <#leading#>, .bottom = <#bottom#>, .trailing = <#trailing#> } (UIOffset){ .horizontal = <#horizontal#>, .vertical = <#vertical#> }
Nullability
Dalam Xcode 6.3.2, anotasi nullability muncul di Objective-C. Pengembang Apple menambahkannya untuk mengimpor API Objective-C ke Swift. Tetapi jika sesuatu ditambahkan ke bahasa, maka Anda harus mencoba memasukkannya ke dalam layanan Anda. Dan kami akan menjelaskan bagaimana kami menggunakan nullability dalam proyek Objective-C dan apa saja batasannya.
Untuk menyegarkan kembali pengetahuan Anda, Anda dapat menonton sesi WWDC .
Hal pertama yang kami lakukan adalah mulai menulis NS_ASSUME_NONNULL_END
/ NS_ASSUME_NONNULL_END
di semua file .m
. Agar tidak melakukan ini dengan tangan, kami tambalan templat file langsung di Xcode.
Kami juga mulai menetapkan nullability dengan benar untuk semua properti dan metode pribadi.
Jika kami menambahkan makro NS_ASSUME_NONNULL_BEGIN
/ NS_ASSUME_NONNULL_END
ke file .m
ada, kami segera menambahkan nullable
, null_resettable
, dan _Nullable
di seluruh file.
Semua peringatan penyusun nullability yang berguna diaktifkan secara default. Tetapi ada satu peringatan ekstrem yang ingin saya sertakan: -Wnullable-to-nonnull-conversion
(diatur dalam "Bendera Peringatan Lain" di pengaturan proyek). Compiler menghasilkan peringatan ini ketika variabel atau ekspresi dengan tipe nullable secara implisit dilemparkan ke tipe nonnull.
+ (NSString *)foo:(nullable NSString *)string { return string;
Sayangnya, untuk __auto_type
(dan karena itu let
dan var
), peringatan ini tidak berfungsi. Jenis yang disimpulkan melalui __auto_type
membuang anotasi nullability. Dan, dilihat dari komentar pengembang Apple di rdar: // 27062504 , perilaku ini tidak akan berubah. Telah diamati secara eksperimental bahwa menambahkan _Nullable
atau _Nonnull
ke __auto_type
tidak memengaruhi apa pun.
- (NSString *)test:(nullable NSString *)string { let tmp = string; return tmp;
Untuk menekan nullable-to-nonnull-conversion
kami menulis makro yang "memaksa membuka bungkus". Ide diambil dari RBBNotNil
macro. Tetapi karena perilaku __auto_type
dimungkinkan untuk menyingkirkan kelas bantu.
#define JMNonnull(obj_) \ ({ \ NSCAssert(obj_, @"Expected `%@` not to be nil.", @#obj_); \ (typeof({ __auto_type result_ = (obj_); result_; }))(obj_); \ })
Contoh menggunakan makro JMNonnull
:
@interface JMRobot : NSObject @property (nonatomic, strong, nullable) JMLeg *leftLeg; @property (nonatomic, strong, nullable) JMLeg *rightLeg; @end @implementation JMRobot - (void)stepLeft { [self step:JMNonnull(self.leftLeg)] } - (void)stepRight { [self step:JMNonnull(self.rightLeg)] } - (void)step:(JMLeg *)leg {
Perhatikan bahwa pada saat penulisan, peringatan nullable-to-nonnull-conversion
tidak berfungsi dengan nullable-to-nonnull-conversion
: kompiler belum memahami bahwa variabel nullable
, setelah memeriksa ketidaksetaraan, nil
dapat diartikan sebagai nonnull
.
- (NSString *)foo:(nullable NSString *)string { if (string != nil) { return string;
Dalam kode Objective-C ++, Anda bisa mengatasi batasan ini dengan menggunakan if let
construct, karena Objective-C ++ memungkinkan deklarasi variabel dalam ekspresi if
.
- (NSString *)foo:(nullable NSString *)stringOrNil { if (let string = stringOrNil) { return string; } else { return @""; } }
Tautan yang bermanfaat
Ada juga sejumlah macro dan kata kunci yang lebih terkenal yang ingin saya sebutkan: kata kunci @ tersedia Kami juga menyarankan Anda melihat perpustakaan libextobjc lainnya .
Apa lagi yang harus dibaca → Kode untuk artikel diposting di intisari .
Kesimpulan
Pada bagian pertama artikel, kami mencoba untuk berbicara tentang fitur utama dan peningkatan bahasa sederhana, yang sangat memudahkan penulisan dan dukungan kode Objective-C. Pada bagian selanjutnya, kami akan menunjukkan bagaimana Anda dapat lebih meningkatkan produktivitas Anda menggunakan enum seperti pada Swift (mereka juga kelas kasus; mereka juga tipe data aljabar , ADT) dan kemungkinan menerapkan metode pada tingkat protokol.