Native 模塊(iOS)

2019-08-14 14:21 更新

有時(shí)一個(gè)應(yīng)用程序需要訪問平臺(tái) API,React Native 并沒有相應(yīng)的封裝器。也許你想重用現(xiàn)有的一些 Objective——C 或 C++ 代碼,無需在 JavaScript 上重新實(shí)現(xiàn)?;蛘邔懸恍└咝阅埽嗑€程的代碼,如圖像處理、網(wǎng)絡(luò)堆棧,數(shù)據(jù)庫或渲染。

我們?cè)O(shè)計(jì) React Native,這樣可以為你寫真正的本地代碼,并且能夠訪問整個(gè)平臺(tái)。這是一個(gè)更高級(jí)的特性,且我們并不期望它成為通常開發(fā)過程的一部分,但是它的存在是至關(guān)重要的。如果 React Native 不支持你需要的本地特性,那么你應(yīng)該能夠自己構(gòu)建它。

這是一個(gè)更高級(jí)的指南,展示了如何構(gòu)建一個(gè)本地模塊。它假設(shè)讀者知道 Objective-C(Swift 還沒有支持)和核心庫(Foundation,UIKit)。

iOS 日歷模塊的例子

本指南將使用 iOS 日歷 API 的例子。假設(shè)我們希望能夠從 JavaScript 訪問 iOS 日歷。

Native 模塊只是一個(gè) Objectve-C 類,實(shí)現(xiàn)了 RCTBridgeModule 協(xié)議。如果你想知道,RCT 是 ReaCT 的一個(gè)簡稱。

    // CalendarManager.h
    #import "RCTBridgeModule.h"
    #import "RCTLog.h"
    @interface CalendarManager : NSObject <RCTBridgeModule>
    @end

React Native 不會(huì)向 JavaScript 公開任何 CalendarManager 方法,除非有明確的要求。幸運(yùn)的是有了 RCT_EXPORT,這會(huì)非常簡單:

    // CalendarManager.m
    @implementation CalendarManager
    - (void)addEventWithName:(NSString *)name location:(NSString *)location
    {
        RCT_EXPORT();
        RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
    }    @end

現(xiàn)在從你的 JavaScript 文件中,你可以像這樣調(diào)用方法:

    var CalendarManager = require('NativeModules').CalendarManager;
    CalendarManager.addEventWithName('Birthday Party', '4 Privet Drive, Surrey');

注意,導(dǎo)出的方法名稱是從 Objective-C 選擇器的第一部分中生成的。有時(shí)它會(huì)產(chǎn)生一個(gè)非慣用的 JavaScript 名稱(就像在我們的例子中的那個(gè))。你可以通過為 RCT_EXPORT 提供一個(gè)可選參數(shù)更改名字,如 RCT_EXPORT(addEvent)。

方法返回的類型應(yīng)該是 void。React Native 橋是異步的,所以向 JavaScript 傳遞結(jié)果的唯一方法是使用回調(diào)或 emitting 事件(見下文)。

參數(shù)類型

React Native 支持多種參數(shù)類型,可以從 JavaScript 代碼傳遞到 native 模塊:

  • 字符串型(NSString)

  • 數(shù)字型(NSInteger,float,double ,CGFloat,NSNumber)

  • 布爾型(BOOL,NSNumber)

  • 這個(gè)列表中任何類型的數(shù)組(NSArray)

  • 這個(gè)列表中任何類型的字符串鍵和值的映射(NSDictionary)

  • 函數(shù)(RCTResponseSenderBlock)

在我們的 CalendarManager 示例中,如果我們想把事件日期傳遞到 native,我們必須將它轉(zhuǎn)換成一個(gè)字符串或一個(gè)數(shù)字:

    - (void)addEventWithName:(NSString *)name location:(NSString *)location date:(NSInteger)secondsSinceUnixEpoch
    {
        RCT_EXPORT(addEvent);        NSDate *date = [NSDate dateWithTimeIntervalSince1970:secondsSinceUnixEpoch];
    }

隨著 CalendarManager.addEvent 方法變得越來越復(fù)雜,參數(shù)的數(shù)量將會(huì)增加。其中一些可能是可選的。在這種情況下對(duì)改變 API 一點(diǎn)來接受事件屬性的字典是值得考慮的,如:

    #import "RCTConvert.h"
    - (void)addEventWithName:(NSString *)name details:(NSDictionary *)details
    {
        RCT_EXPORT(addEvent);        NSString *location = [RCTConvert NSString:details[@"location"]]; // ensure location is a string
        ...
    }

并且從 JavaScript 調(diào)用它:

    CalendarManager.addEvent('Birthday Party', {
        location: '4 Privet Drive, Surrey',        time: date.toTime(),
        description: '...'
    })

注意:關(guān)于數(shù)組和映射

React Ntive 沒有為這些結(jié)構(gòu)中值的類型提供任何擔(dān)保。你的 native 模塊可能期望一個(gè)字符串?dāng)?shù)組,但如果 JavaScript 調(diào)用你的包含數(shù)字和字符串?dāng)?shù)組的方法,你會(huì)得到帶有 NSNumber 和 NSString 的 NSArray。檢查數(shù)組/映射值類型是開發(fā)人員的責(zé)任 (助手方法見 RCTConvert)。

回調(diào)

警告

本節(jié)比其他更具有實(shí)驗(yàn)性,圍繞回調(diào)我們沒有得到一組最佳實(shí)踐。

Native 模塊還支持一種特殊的參數(shù)——回調(diào)。在大多數(shù)情況下它是用來向 JavaScript 提供函數(shù)調(diào)用結(jié)果的。

    - (void)findEvents:(RCTResponseSenderBlock)callback
    {
        RCT_EXPORT();        NSArray *events = ...
        callback(@[[NSNull null], events]);
    }

RCTResponseSenderBlock 只接受一個(gè)參數(shù)——參數(shù)的數(shù)組傳遞給 JavaScript 的回調(diào)。在本例中,我們使用節(jié)點(diǎn)的慣例來為 error 和其他的——函數(shù)的結(jié)果設(shè)置第一個(gè)參數(shù)。

CalendarManager.findEvents((error, events) => {  if (error) {    console.error(error);
  } else {    this.setState({events: events});
  }
})

Native 模塊應(yīng)該只調(diào)用它的回調(diào)一次。然而,它可以將回調(diào)作為 ivar 存儲(chǔ)并稍后調(diào)用回調(diào)。這種模式通常用于包裝需要委托的 iOS 的 APIs。請(qǐng)看 RCTAlertManager。

如果你想向 JavaScript 傳遞 error ——如對(duì)象,使用 RCTUtils.h 的 RCTMakeError。

實(shí)現(xiàn) native 模塊

Native 模塊應(yīng)該沒有任何關(guān)于什么線程正在被調(diào)用的假設(shè)。React Native 在一個(gè)單獨(dú)的串行 GCD 隊(duì)列中調(diào)用 native 模塊方法,但這是一個(gè)實(shí)現(xiàn)細(xì)節(jié),可能會(huì)改變。如果 native 模塊需要調(diào)用 main-thread-only iOS API,它應(yīng)該在主隊(duì)列安排操作:

- (void)addEventWithName:(NSString *)name callback:(RCTResponseSenderBlock)callback
{
  RCT_EXPORT(addEvent);
  dispatch_async(dispatch_get_main_queue(), ^{
    // Call iOS API on main thread
    ...
    // You can invoke callback from any thread/queue
    callback(@[...]);
  });
}

同樣的方法,如果操作要很長時(shí)間才能完成,native 模塊不應(yīng)該阻塞。使用 dispatch_async 在后臺(tái)隊(duì)列中安排耗費(fèi)大的工作是一個(gè)好主意。

導(dǎo)出常量

Native 模塊可以在運(yùn)行時(shí)向 JavaScript 導(dǎo)出立即可用的常量。導(dǎo)出一些初始數(shù)據(jù)是有用的,否則這些初始數(shù)據(jù)需要往返的橋梁。

- (NSDictionary *)constantsToExport
{  return @{ @"firstDayOfTheWeek": @"Monday" };
}

JavaScript 能夠立即使用這些值:

console.log(CalendarManager.firstDayOfTheWeek);

注意,只有在初始化時(shí)常量才能被導(dǎo)出,所以如果你在運(yùn)行時(shí)改變了 constantsToExport 的值,它不會(huì)影響 JavaScript 環(huán)境。

發(fā)送事件到 JavaScript

Native 模塊可以在不被直接調(diào)用的情況下向 JavaScript 發(fā)送事件信號(hào)。最簡單的方法是使用 eventDispatcher

    #import "RCTBridge.h"
    #import "RCTEventDispatcher.h"
    @implementation CalendarManager
    @synthesize bridge = _bridge;
    - (void)calendarEventReminderReceived:(NSNotification *)notification
    {        NSString *eventName = notification.userInfo[@"name"];
        [self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"
                                               body:@{@"name": eventName}];
    }    @end

JavaScript 代碼可以訂閱這些事件:

var subscription = DeviceEventEmitter.addListener(  'EventReminder',  (reminder) => console.log(reminder.name)
);
...// Don't forget to unsubscribe
subscription.remove();

更多的向 JavaScript 發(fā)送事件的例子,請(qǐng)看 RCTLocationObserver。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)