Interaction between React Native and iOS OC

Pre preparation

First of all, it's better to know a little about oc grammar, otherwise many of them can't be understood

  1. Create declaration file nativeModule.h

    #import <Foundation/Foundation.h>
    #import <React/RCTBridgeModule.h>
    
    @interface nativeModule : NSObject <RCTBridgeModule>
    
    @end
  2. Create the file nativeModule.m

    #import <Foundation/Foundation.h>
    #import "nativeModule.h"
    
    @interface nativeModule ()
    
    @end
    
    @implementation nativeModule
    
    @end

This is the structure directory after adding files

About the difference of interface:
The @ interface in. h, which is called by other classes. Its @ property and functions can be "seen" (public) by other classes

The @ interface in. m, called Class Extension in OC, is the supplement of @ interface in. h file. However, the @ interface in the. m file is not open to the outside world, only visible in the. m file (private)

Therefore, we put the methods and variables that are open to the outside world into the. h file, and put the variables that are not open to the outside world into the. M file (. The methods of the m file can be used directly without declaration).

RN to iOS

Method 1 normal transfer value to native

Add method in. m file:

// Omit the above code

@implementation nativeModule

// This code is required to export the module, so as to access the nativeModule module in RN
RCT_EXPORT_MODULE();

// Receive string
RCT_EXPORT_METHOD(addHelloWord:(NSString *)name location:(NSString *)location)
{
  NSLog(@"%@,%@", name, location);
}
@end

RN Code:

import { Button, NativeModules } from 'react-native'
const { nativeModule } = NativeModules

<Button title={'Send 2 parameters to native'} onPress={() => {
    nativeModule.addHelloWord('Your Name', 'position:Zhejiang')
}}/>

The function of clicking this button is to pass the two strings' your name 'and' location: Zhejiang 'to the native end

Method 2 pass the callback function

Add in. m file:

// Only one parameter is accepted -- an array of parameters passed to the JavaScript callback function.
RCT_EXPORT_METHOD(checkIsRoot:(RCTResponseSenderBlock)callback) {
  NSArray *array = @[@"string", @"number"];
  callback(array);
}

Add code to RN:

<Button title={'js Send a callback to native,An array was received in the callback'} onPress={() => {
    nativeModule.checkIsRoot((str: string, num: string) => {
      console.log(str, num)
    })
}}/>

This is a callback function passed to the native end in RN to solve the problem. If the callback calls RN multiple times, an error will be reported

Method 3 get promise callback

Add code to. m file:

@interface nativeModule ()

@property (nonatomic) RCTPromiseResolveBlock normalResolve;
@property (nonatomic) RCTPromiseRejectBlock normalReject;
@property (nonatomic) NSInteger num;

@end


// This is a timer
-(void)startTime: (NSArray*) data{
  NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
    
    NSArray *events =@[@"Promise ",@"test ",@" array"];
    if (events) {
      self.normalResolve(events);
      [timer invalidate];
    } else {
      [timer invalidate];
      NSError *error=[NSError errorWithDomain:@"I am calling back the error message..." code:101 userInfo:nil];
      self.normalReject(@"no_events", @"There were no events", error);
    }
  }];
  
  [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

// Callback parameter to RN, error message of callback
RCT_EXPORT_METHOD(getHBDeviceUniqueID: (RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
  
  // Tasks to perform
  self.normalResolve = resolve;
  self.normalReject = reject;
  
  [self performSelectorOnMainThread:@selector(startTime:) withObject: [NSArray arrayWithObjects: @"1", @"2", nil] waitUntilDone:YES];
}

Add code to RN:

<Button title={'native Pass on one promise to JS'} onPress={() => {
    nativeModule.getHBDeviceUniqueID().then((arr: string[]) => {
      console.log('resolve', arr)
    }).catch((err: string) => {
      console.error(err)
    })
}}/>

nativeModule.getHBDeviceUniqueID He's a promise,Can get the callback of the native end, It's similar to method 2

Method 3 obtaining promise Synchronization mode of

stay .m Add to file:

// This is a timer 2
-(void)startTime2: (NSArray*) data{
  NSLog(@"data%@",data);
  
  NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    
    NSLog(@"%d", (int)self.num);
    
    self.num = self.num + 1;
    
    NSLog(@"%d", (int)self.num);
    
    if (self.num > 4) {
      [timer invalidate];
      NSLog(@"end");
      self.normalResolve(data);
    }
    
  }];
  
  [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

// RCT_REMAP_METHOD and RCT_ EXPORT_ The method is the same, but the method is called synchronously from JS on the JS thread and may return results.
// Synchronization may have performance problems. It is not recommended to use any other than promise
RCT_REMAP_METHOD(findEvents,
                 findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
  self.normalResolve = resolve;
  self.normalReject = reject;
  
  
  self.num = 0;
  
  [self performSelectorOnMainThread:@selector(startTime2:) withObject: [NSArray arrayWithObjects: @"1", @"2", nil] waitUntilDone:YES];
}

Add code at RN end:

<Button title={'native Pass on one promise to JS2'} onPress={() => {
    nativeModule.findEvents().then((arr: string[]) => {
      console.log('resolve', arr)
    }).catch((err: string) => {
      console.error(err)
    })
}}/>

Method 4 and method 3 are basically the same, but there is a difference, namely RCT_REMAP_METHOD use this method to change the code to synchronous state

iOS sends value to RN

Initial data provision

Add code in appDelegate.m file:

NSArray *imageList = @[@"http://foo.com/bar1.png",
                @"http://foo.com/bar2.png"];

NSDictionary *props = @{@"images" : imageList};


RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"learn" initialProperties:props];
// This line of code is original, the difference is initialp roperties:props

Write in RN:

// Rewrite app. Images is the data provided by iOS. Here we pass the data through context
export default class App extends React.Component<{ images: string[] }> {

  render() {
    return <NativeProps.Provider value={this.props.images}>
      <AppContainer/>
    </NativeProps.Provider>
  }
}

// Simple use in hooks

const images = useContext(NativeProps);

<Text>This is from native Initial data from the end{JSON.stringify(images)}</Text>

Add listening event

Add code to. m file:

// Event name available for listening
- (NSArray<NSString *> *)supportedEvents
{
  return @[@"EventReminder"];
}


RCT_EXPORT_METHOD(postNotificationEvent:(NSString *)name)
{
  NSLog(@"calendarEventReminderReceived");
    [self sendEventWithName:@"EventReminder" body:@{@"name": name}];;
}

- (void)calendarEventReminderReceived:(NSNotification *)notification
{
  // This is an example of the official website
  NSLog(@"calendarEventReminderReceived");
  NSString *eventName = notification.userInfo[@"name"];
  [self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
}

RCT_EXPORT_METHOD(Send){
  NSDictionary *dict = @{@"name" : @"veuimyzi"};
  
  NSNotification *notification = [[NSNotification alloc] initWithName:@"EventReminder" object:nil userInfo:dict] ;
  
  [self calendarEventReminderReceived:notification];
}

Add code to RN:

const ManagerEmitter = new NativeEventEmitter(nativeModule)

const [msg, setMsg] = useState([])

// Use in hooks, similar to the component didmount lifecycle
useEffect(() => {
    const subscription = ManagerEmitter.addListener(
      'EventReminder',
      (reminder) => {
        setMsg(prevState => {
          return prevState.concat(reminder.name)
        })
        console.log('This is monitored EventReminder Event response', reminder.name)
      }
    )

    return () => {
      subscription.remove()
    }
}, [])


<Button title={'js Listening events,Give Way native to js Give notice'} onPress={() => {
    nativeModule.postNotificationEvent('test')
}}/>

<Button title={'js Listening events,Give Way native to js Give notice send'} onPress={() => {
    nativeModule.Send()
}}/>

{
    msg.map((item, index) => {
      return <Text key={item + index}>item:{item}</Text>
    })
}

The postNotificationEvent method is the simplest use. When sendEventWithName is called on the native side, data can be passed to RN for listening

Another method, Send and calendareventremindereceived, is an example from the official website, which is to get data from NSNotification, and Send is to pass data to calendareventremindereceived

As for the optimization of monitoring, it is also available on the official website. If you have time, you can see the following code in the. m file:

@implementation nativeModule
{
  bool hasListeners;
  // A local variable
}

-(void)startObserving {
  hasListeners = YES;
}

-(void)stopObserving {
  hasListeners = NO;
}
// Add judgment in sending listening, send only when there is listening, effectively reducing the call of bridge code
if (hasListeners) { 
    [self sendEventWithName:@"EventReminder" body:@{@"name": name}];;
}

summary

Library of the above code: https://github.com/Grewer/lea...

This is basically the interaction between the native end and the RN end. Of course, there are more and more complex operations on the native end, such as processes. If you want to write bridging methods, you will encounter a lot of them. However, if you master the above, it is enough for some three-party SDK calls

Tags: iOS React Javascript JSON

Posted on Tue, 23 Jun 2020 05:44:25 -0400 by scotch33