投稿文章,作者:龐海礁(博客)
ReactNative是Facebook開源的一種實現移動跨平台開發的解決方案,目前在業界得到廣泛應用,這裡有非常詳細的中文使用指南。本文主要分享RN源碼中一些值得大家學習或者借鑒的代碼或者編寫技巧等,供大家學習參考。
整個RN庫包含10多個工程,有興趣的童鞋可以下載源碼查看具體細節,在此不再展開。
宏定義巧用
整個ReactNative源碼工程中用到了大量的宏定義,包括RCT_EXTERN、RCT_NOT_IMPLEMENTED、RCT_EXPORT_METHOD以及RCT_EXPORT_MODULE等申明宏或者功能宏。通過宏定義的方式,可以非常方便嵌入功能代碼或者邏輯實現,重用代碼的同時又保持了代碼的整潔性
比如,ProtocolKit工程中,作者通過宏定義@defs將Protocol接口巧妙的實現在.h文件中,代碼簡介明了,又不失功能完整性。當然,RN工程中,RCT_NOT_IMPLEMENTED宏也有相似作用,實際項目中各位也可以嘗試通過宏定義實現一些常用功能模塊
關於iOS宏定義的文章有很多,在此推薦兩篇非常不錯的文章:RAC中必須要知道的宏、iOS宏的使用和技巧。
環境變量
iOS開發中,各位對#ifdef DEBUG應該非常熟悉,通過判斷該條件,可以區別當前運行環境是Debug環境還是Release環境。比如Release環境下通過重定義NSLog以屏蔽所有日志輸出
#ifdef DEBUG #define NSLog(...) NSLog(__VA_ARGS__) #else #define NSLog(...) {} #endif
進一步,是否可以考慮只在聯機調試環境下輸出日志?此時就涉及聯機調試環境的判斷,環境變量正好可以解決該問題
Xcode可以在不同環境下自定義環境變量Environment Variables,通過在運行環境Run中自定義變量CI_USE_PACKAGER,此時便可在項目代碼中通過getenv()函數判斷當前運行環境
if (getenv("CI_USE_PACKAGER")) { // to do... }
被忽略的硬鍵盤
相較於軟鍵盤文字符號的輸入,對於APP來說,硬鍵盤的應用開發似乎很容易被忽視,畢竟,通常情況下,硬鍵盤輸入只會出現在模擬器環境下。
iOS7以後,系統定義有硬鍵盤響應交互類UIKeyCommand,通過UIKeyCommand,APP能夠監聽硬鍵盤的特定輸入響應,比如Command+D等,當然,前提是APP需要首先監聽該輸入命令。
UIKeyCommand的使用非常簡單,當需要在特定場景觸發某一事件,但又不想影響界面顯示的時候,不妨試試UIKeyCommand,具體使用可以看看這篇文章。
_cmd
iOS官方文檔中,_cmd表示當前方法的selector,你可以通過下面代碼打印輸出當前函數名
NSLog(@"Current method: %@", NSStringFromSelector(_cmd));
當然,實際項目中,你也可以這樣使用
NSNumber *rootTag = objc_getAssociatedObject(self, _cmd) ?: @1; objc_setAssociatedObject(self, _cmd, @(rootTag.integerValue + 10), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
瞧,是不是有點意思!
kCFNull
相對於nil NSNull而言,kCFNull筆者接觸較少,kCFNull可以理解為NSNull單例對象
id null1 = (id)kCFNull; id null2 = [NSNull null];
打印地址
null1=(NSNull *)0x10426eaf0 null2=(NSNull *)0x10426eaf0
從上面測試結果可以看出它們其實指向同一地址, 可以簡單理解為 kCFNull === [NSNull null]
文本陰影NSShadow
APP開發中,程序猿可能經常需要在圖片或視頻上顯示文字,由於背景顏色跟文字顏色相近,導致文字看不清,比如時下火熱的直播彈幕顯示,為了確保文字顯示清晰,開發者一般會配上陰影或者文字描邊
給文本添加陰影描邊,系統提供有NSShadow類,可以這樣使用
NSShadow *shadow = [NSShadow new]; shadow.shadowOffset = CGSizeZero; shadow.shadowBlurRadius = 5.0f; shadow.shadowColor = [UIColor colorWithWhite:0.0f alpha:0.3f]; NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"www.olinone.com" attributes:@{NSShadowAttributeName: shadow, NSForegroundColorAttributeName: [UIColor whiteColor]}]; lbl.attributedText = attString;
實際效果是這樣的,shadowBlurRadius值越小,文本描邊越清晰
主線程判斷
判斷當前執行線程是否為主線程的方法有很多,比如
[NSThread isMainThread] pthread_main_np
在RN中,它是這樣的
BOOL RCTIsMainQueue() { static void *mainQueueKey = &mainQueueKey; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueKey, NULL); }); return dispatch_get_specific(mainQueueKey) == mainQueueKey; }
當然,由於無法查看NSThread內部實現機制,暫時無法了解孰優孰劣,不過,[NSThread isMainThread]貌似足矣!
volatile不簡單
在百科中,是這樣描述它的:就像大家更熟悉的const一樣,volatile是一個類型修飾符,它是被設計用來修飾被不同線程訪問和修改的變量。作為指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值
簡單說,被volatile修飾的變量是多線程安全的,其次,不會因為編譯器優化導致讀值出錯。關於編譯器編譯優化可以看看這篇文章。
iOS開發中確保多線程安全的方法有很多,原子操作、線程鎖、單線程執行等等,本人也寫過相關文章iOS開發多線程同步。
在RN中,通過volatile修飾符,巧妙實現了多線程取消操作
__block volatile uint32_t cancelled = 0; if (!cancelled) { // to do... } OSAtomicOr32Barrier(1, &cancelled);
通過原子性操作訪問被volatile修飾的cancelled對象即可保障函數只執行一次。想想大家熟悉的單例dispatch_once_t,現在讓你設計單例對象,你又會如何設計了?
+ (instancetype)sharedInstance { static RCTWebSocketManager *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [self new]; }); return sharedInstance; }
結構體Struct
說起Struct,不知各位對它印象如何?大學C課本中學過?NSObject類class原型貌似有講?
struct iOSDev { NSString *nickName; };
OC中一個簡單的結構體,在Swift中,Struct也可以這樣寫
struct iOSDev { var nickName : String func getBusinessCard() -> String { return "(nickName),幽默的iOS開發者!" }}; let iOSOlinone = iOSDev(nickName: "olinone") print(iOSOlinone.getBusinessCard())
getBusinessCard為結構體函數,是不是感覺很方便!其實OC中也可以這樣寫
struct iOSDev { NSString *nickName; NSString *getBusinessCard() { return [NSString stringWithFormat:@"%@,幽默的iOS開發者!", nickName]; }}; iOSDev iosDev = iOSDev{@"olinone"}; NSLog(@"%@", iosDev.getBusinessCard());
當然,為Struct添加函數並不是C語言特性,而是C++特性,因此,為了編譯通過,你需要將.m文件修改成.mm文件。
Struct有其使用的特殊場景,相較於Class,合理的使用Struct可以使代碼更加整潔。同時,為了適應Swift中Struct強大特性,可以試著在OC項目中嘗試Struct。