mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-27 19:25:11 +08:00
Updates from Thu Mar 12
- Fixed sticky section headers in ListView | Nick Lockwood - [ReactNative] AppState cleanup, remove Subscribable, add in OSS examples | Eric Vicenti - [react-packager] package.json cleanup (seperate packager into it's own package) | Amjad Masad - [ReactNative] Move PushNotificationIOS to oss | Tadeu Zagallo - [ReactNative] Fix shake gesture after RedBox is dismissed | Alex Kotliarskyi - [catlyst|madman] fix prop type warning | Jiajie Zhu - [ReactNative] Remove Subscribable from TextInput | Eric Vicenti - Unforked ExceptionsManager, AlertManager and AppState | Nick Lockwood - [ReactNative|MAdMan] Notification Subscribable | Eric Vicenti - [ReactNative] OSS AsyncStorage with example | Spencer Ahrens
This commit is contained in:
7
ReactKit/Modules/RCTAppState.h
Normal file
7
ReactKit/Modules/RCTAppState.h
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTAppState : NSObject<RCTBridgeModule>
|
||||
|
||||
@end
|
||||
105
ReactKit/Modules/RCTAppState.m
Normal file
105
ReactKit/Modules/RCTAppState.m
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTAppState.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
|
||||
static NSString *RCTCurrentAppBackgroundState()
|
||||
{
|
||||
static NSDictionary *states;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
states = @{
|
||||
@(UIApplicationStateActive): @"active",
|
||||
@(UIApplicationStateBackground): @"background",
|
||||
@(UIApplicationStateInactive): @"inactive"
|
||||
};
|
||||
});
|
||||
|
||||
return states[@([[UIApplication sharedApplication] applicationState])] ?: @"unknown";
|
||||
}
|
||||
|
||||
@implementation RCTAppState
|
||||
{
|
||||
NSString *_lastKnownState;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
_lastKnownState = RCTCurrentAppBackgroundState();
|
||||
|
||||
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
|
||||
UIApplicationDidEnterBackgroundNotification,
|
||||
UIApplicationDidFinishLaunchingNotification]) {
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleAppStateDidChange)
|
||||
name:name
|
||||
object:nil];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma mark - App Notification Methods
|
||||
|
||||
- (void)handleAppStateDidChange
|
||||
{
|
||||
NSString *newState = RCTCurrentAppBackgroundState();
|
||||
if (![newState isEqualToString:_lastKnownState]) {
|
||||
_lastKnownState = newState;
|
||||
[_bridge.eventDispatcher sendDeviceEventWithName:@"appStateDidChange"
|
||||
body:@{@"app_state": _lastKnownState}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
/**
|
||||
* Get the current background/foreground state of the app
|
||||
*/
|
||||
- (void)getCurrentAppState:(RCTResponseSenderBlock)callback
|
||||
error:(__unused RCTResponseSenderBlock)error
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
callback(@[@{@"app_state": _lastKnownState}]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the application icon badge number on the home screen
|
||||
*/
|
||||
- (void)setApplicationIconBadgeNumber:(NSInteger)number
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
[UIApplication sharedApplication].applicationIconBadgeNumber = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current application icon badge number on the home screen
|
||||
*/
|
||||
- (void)getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
callback(@[
|
||||
@([UIApplication sharedApplication].applicationIconBadgeNumber)
|
||||
]);
|
||||
}
|
||||
|
||||
@end
|
||||
24
ReactKit/Modules/RCTAsyncLocalStorage.h
Normal file
24
ReactKit/Modules/RCTAsyncLocalStorage.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
/**
|
||||
* A simple, asynchronous, persistent, key-value storage system designed as a
|
||||
* backend to the AsyncStorage JS module, which is modeled after LocalStorage.
|
||||
*
|
||||
* Current implementation stores small values in serialized dictionary and
|
||||
* larger values in separate files. Since we use a serial file queue
|
||||
* `RKFileQueue`, reading/writing from multiple threads should be perceived as
|
||||
* being atomic, unless someone bypasses the `RCTAsyncLocalStorage` API.
|
||||
*
|
||||
* Keys and values must always be strings or an error is returned.
|
||||
*/
|
||||
@interface RCTAsyncLocalStorage : NSObject <RCTBridgeModule>
|
||||
|
||||
- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback;
|
||||
- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback;
|
||||
- (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback;
|
||||
- (void)clear:(RCTResponseSenderBlock)callback;
|
||||
- (void)getAllKeys:(RCTResponseSenderBlock)callback;
|
||||
|
||||
@end
|
||||
292
ReactKit/Modules/RCTAsyncLocalStorage.m
Normal file
292
ReactKit/Modules/RCTAsyncLocalStorage.m
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTAsyncLocalStorage.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <CommonCrypto/CommonCryptor.h>
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static NSString *const kStorageDir = @"RCTAsyncLocalStorage_V1";
|
||||
static NSString *const kManifestFilename = @"manifest.json";
|
||||
static const NSUInteger kInlineValueThreshold = 100;
|
||||
|
||||
#pragma mark - Static helper functions
|
||||
|
||||
static id RCTErrorForKey(NSString *key)
|
||||
{
|
||||
if (![key isKindOfClass:[NSString class]]) {
|
||||
return RCTMakeAndLogError(@"Invalid key - must be a string. Key: ", key, @{@"key": key});
|
||||
} else if (key.length < 1) {
|
||||
return RCTMakeAndLogError(@"Invalid key - must be at least one character. Key: ", key, @{@"key": key});
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
static void RCTAppendError(id error, NSMutableArray **errors)
|
||||
{
|
||||
if (error && errors) {
|
||||
if (!*errors) {
|
||||
*errors = [NSMutableArray new];
|
||||
}
|
||||
[*errors addObject:error];
|
||||
}
|
||||
}
|
||||
|
||||
static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut)
|
||||
{
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||
NSError *error;
|
||||
NSStringEncoding encoding;
|
||||
NSString *entryString = [NSString stringWithContentsOfFile:filePath usedEncoding:&encoding error:&error];
|
||||
if (error) {
|
||||
*errorOut = RCTMakeError(@"Failed to read storage file.", error, @{@"key": key});
|
||||
} else if (encoding != NSUTF8StringEncoding) {
|
||||
*errorOut = RCTMakeError(@"Incorrect encoding of storage file: ", @(encoding), @{@"key": key});
|
||||
} else {
|
||||
return entryString;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static dispatch_queue_t RCTFileQueue(void)
|
||||
{
|
||||
static dispatch_queue_t fileQueue = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
// All JS is single threaded, so a serial queue is our only option.
|
||||
fileQueue = dispatch_queue_create("com.facebook.rkFile", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_set_target_queue(fileQueue,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
|
||||
});
|
||||
|
||||
return fileQueue;
|
||||
}
|
||||
|
||||
#pragma mark - RCTAsyncLocalStorage
|
||||
|
||||
@implementation RCTAsyncLocalStorage
|
||||
{
|
||||
BOOL _haveSetup;
|
||||
// The manifest is a dictionary of all keys with small values inlined. Null values indicate values that are stored
|
||||
// in separate files (as opposed to nil values which don't exist). The manifest is read off disk at startup, and
|
||||
// written to disk after all mutations.
|
||||
NSMutableDictionary *_manifest;
|
||||
NSString *_manifestPath;
|
||||
NSString *_storageDirectory;
|
||||
}
|
||||
|
||||
- (NSString *)_filePathForKey:(NSString *)key
|
||||
{
|
||||
NSString *safeFileName = RCTMD5Hash(key);
|
||||
return [_storageDirectory stringByAppendingPathComponent:safeFileName];
|
||||
}
|
||||
|
||||
- (id)_ensureSetup
|
||||
{
|
||||
if (_haveSetup) {
|
||||
return nil;
|
||||
}
|
||||
NSString *documentDirectory =
|
||||
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||
NSURL *homeURL = [NSURL fileURLWithPath:documentDirectory isDirectory:YES];
|
||||
_storageDirectory = [[homeURL URLByAppendingPathComponent:kStorageDir isDirectory:YES] path];
|
||||
NSError *error;
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:_storageDirectory
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:&error];
|
||||
if (error) {
|
||||
return RCTMakeError(@"Failed to create storage directory.", error, nil);
|
||||
}
|
||||
_manifestPath = [_storageDirectory stringByAppendingPathComponent:kManifestFilename];
|
||||
NSDictionary *errorOut;
|
||||
NSString *serialized = RCTReadFile(_manifestPath, nil, &errorOut);
|
||||
_manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [NSMutableDictionary new];
|
||||
if (error) {
|
||||
RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error);
|
||||
_manifest = [NSMutableDictionary new];
|
||||
}
|
||||
_haveSetup = YES;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)_writeManifest:(NSMutableArray **)errors
|
||||
{
|
||||
NSError *error;
|
||||
NSString *serialized = RCTJSONStringify(_manifest, &error);
|
||||
[serialized writeToFile:_manifestPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
id errorOut;
|
||||
if (error) {
|
||||
errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil);
|
||||
RCTAppendError(errorOut, errors);
|
||||
}
|
||||
return errorOut;
|
||||
}
|
||||
|
||||
- (id)_appendItemForKey:(NSString *)key toArray:(NSMutableArray *)result
|
||||
{
|
||||
id errorOut = RCTErrorForKey(key);
|
||||
if (errorOut) {
|
||||
return errorOut;
|
||||
}
|
||||
id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value.
|
||||
if (value == [NSNull null]) {
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
value = RCTReadFile(filePath, key, &errorOut);
|
||||
}
|
||||
[result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
|
||||
return errorOut;
|
||||
}
|
||||
|
||||
- (id)_writeEntry:(NSArray *)entry
|
||||
{
|
||||
if (![entry isKindOfClass:[NSArray class]] || entry.count != 2) {
|
||||
return RCTMakeAndLogError(@"Entries must be arrays of the form [key: string, value: string], got: ", entry, nil);
|
||||
}
|
||||
if (![entry[1] isKindOfClass:[NSString class]]) {
|
||||
return RCTMakeAndLogError(@"Values must be strings, got: ", entry[1], entry[0]);
|
||||
}
|
||||
NSString *key = entry[0];
|
||||
id errorOut = RCTErrorForKey(key);
|
||||
if (errorOut) {
|
||||
return errorOut;
|
||||
}
|
||||
NSString *value = entry[1];
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
NSError *error;
|
||||
if (value.length <= kInlineValueThreshold) {
|
||||
if (_manifest[key] && _manifest[key] != [NSNull null]) {
|
||||
// If the value already existed but wasn't inlined, remove the old file.
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
||||
}
|
||||
_manifest[key] = value;
|
||||
return nil;
|
||||
}
|
||||
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key});
|
||||
} else {
|
||||
_manifest[key] = [NSNull null]; // Mark existence of file with null, any other value is inline data.
|
||||
}
|
||||
return errorOut;
|
||||
}
|
||||
|
||||
#pragma mark - Exported JS Functions
|
||||
|
||||
- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
if (!callback) {
|
||||
RCTLogError(@"Called getItem without a callback.");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[@[errorOut], [NSNull null]]);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *errors;
|
||||
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
|
||||
for (NSString *key in keys) {
|
||||
id keyError = [self _appendItemForKey:key toArray:result];
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
callback(@[errors ?: [NSNull null], result]);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[@[errorOut]]);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *errors;
|
||||
for (NSArray *entry in kvPairs) {
|
||||
id keyError = [self _writeEntry:entry];
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
if (callback) {
|
||||
callback(@[errors ?: [NSNull null]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[@[errorOut]]);
|
||||
return;
|
||||
}
|
||||
NSMutableArray *errors;
|
||||
for (NSString *key in keys) {
|
||||
id keyError = RCTErrorForKey(key);
|
||||
if (!keyError) {
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
||||
[_manifest removeObjectForKey:key];
|
||||
}
|
||||
RCTAppendError(keyError, &errors);
|
||||
}
|
||||
[self _writeManifest:&errors];
|
||||
if (callback) {
|
||||
callback(@[errors ?: [NSNull null]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)clear:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (!errorOut) {
|
||||
NSError *error;
|
||||
for (NSString *key in _manifest) {
|
||||
NSString *filePath = [self _filePathForKey:key];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
|
||||
}
|
||||
[_manifest removeAllObjects];
|
||||
errorOut = [self _writeManifest:nil];
|
||||
}
|
||||
if (callback) {
|
||||
callback(@[errorOut ?: [NSNull null]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)getAllKeys:(RCTResponseSenderBlock)callback
|
||||
{
|
||||
RCT_EXPORT();
|
||||
|
||||
dispatch_async(RCTFileQueue(), ^{
|
||||
id errorOut = [self _ensureSetup];
|
||||
if (errorOut) {
|
||||
callback(@[errorOut, [NSNull null]]);
|
||||
} else {
|
||||
callback(@[[NSNull null], [_manifest allKeys]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -4,6 +4,14 @@
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTExceptionsManager : NSObject <RCTBridgeModule>
|
||||
@protocol RCTExceptionsManagerDelegate <NSObject>
|
||||
|
||||
- (void)unhandledJSExceptionWithMessage:(NSString *)message stack:(NSArray *)stack;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTExceptionsManager : NSObject <RCTBridgeModule>
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@@ -5,12 +5,32 @@
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
@implementation RCTExceptionsManager
|
||||
{
|
||||
__weak id<RCTExceptionsManagerDelegate> _delegate;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_delegate = delegate;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithDelegate:nil];
|
||||
}
|
||||
|
||||
- (void)reportUnhandledExceptionWithMessage:(NSString *)message stack:(NSArray *)stack
|
||||
{
|
||||
RCT_EXPORT(reportUnhandledException);
|
||||
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
||||
if (_delegate) {
|
||||
[_delegate unhandledJSExceptionWithMessage:message stack:stack];
|
||||
} else {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateExceptionMessage:(NSString *)message stack:(NSArray *)stack
|
||||
|
||||
Reference in New Issue
Block a user