Skip to content

Commit

Permalink
refactor(ios): rewrite snapshot's retrieve and restore functionality (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwcg authored Nov 27, 2024
1 parent 204c80f commit 17b5c35
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 38 deletions.
7 changes: 0 additions & 7 deletions framework/ios/base/bridge/HippyBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,6 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass);
@property (nonatomic, copy, readonly) NSArray<Class> *moduleClasses;


#pragma mark - Snapshot

- (NSData *)snapShotData;

- (void)setSnapShotData:(NSData *)data;


#pragma mark - App UI State Related

/// NightMode or not, default is NO.
Expand Down
31 changes: 0 additions & 31 deletions framework/ios/base/bridge/HippyBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -604,12 +604,6 @@ - (void)handleBuffer:(NSArray *)buffer {
NSArray<NSNumber *> *methodIDs = [HippyConvert NSNumberArray:requestsArray[HippyBridgeFieldMethodIDs]];
NSArray<NSArray *> *paramsArrays = [HippyConvert NSArrayArray:requestsArray[HippyBridgeFieldParams]];

int64_t callID = -1;

if (requestsArray.count > 3) {
callID = [requestsArray[HippyBridgeFieldCallID] longLongValue];
}

if (HIPPY_DEBUG && (moduleIDs.count != methodIDs.count || moduleIDs.count != paramsArrays.count)) {
HippyLogError(@"Invalid data message - all must be length: %lu", (unsigned long)moduleIDs.count);
return;
Expand Down Expand Up @@ -659,9 +653,6 @@ - (id)callNativeModule:(NSUInteger)moduleID method:(NSUInteger)methodID params:(
// hippy will send 'destroyInstance' event to JS.
// JS may call actions after that.
// so HippyBatchBridge needs to be valid
// if (!_valid) {
// return nil;
// }
BOOL isValid = [self isValid];
NSArray<HippyModuleData *> *moduleDataByID = [_moduleSetup moduleDataByID];
if (moduleID >= [moduleDataByID count]) {
Expand Down Expand Up @@ -960,28 +951,6 @@ - (void)sendEvent:(NSString *)eventName params:(NSDictionary *_Nullable)params {
args:@{@"eventName": eventName, @"extra": params ? : @{}}];
}

- (NSData *)snapShotData {
auto rootNode = _javaScriptExecutor.pScope->GetRootNode().lock();
if (!rootNode) {
return nil;
}
std::string data = hippy::DomManager::GetSnapShot(rootNode);
return [NSData dataWithBytes:reinterpret_cast<const void *>(data.c_str()) length:data.length()];
}

- (void)setSnapShotData:(NSData *)data {
auto domManager = _javaScriptExecutor.pScope->GetDomManager().lock();
if (!domManager) {
return;
}
auto rootNode = _javaScriptExecutor.pScope->GetRootNode().lock();
if (!rootNode) {
return;
}
std::string string(reinterpret_cast<const char *>([data bytes]), [data length]);
domManager->SetSnapShot(rootNode, string);
}


#pragma mark - RootView Related

Expand Down
10 changes: 10 additions & 0 deletions renderer/native/ios/renderer/HippyRootView.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ extern NSString *const HippySecondaryBundleDidLoadNotification DEPRECATED_MSG_AT
/// you don't want any touch to be registered as soon as the UIScrollView starts scrolling.
- (void)cancelTouches;


#pragma mark - Snapshot

/// Retrieves the current snapshot data.
- (nullable NSData *)retrieveCurrentSnapshotData;

/// Restores the snapshot data with the provided NSData object.
/// - Parameter data: NSData object
- (BOOL)restoreSnapshotData:(nullable NSData *)data;

@end

NS_ASSUME_NONNULL_END
54 changes: 54 additions & 0 deletions renderer/native/ios/renderer/HippyRootView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,19 @@
#import "HippyAssert.h"
#import "HippyView.h"
#import "UIView+Hippy.h"
#import "UIView+Render.h"
#import "HippyComponentMap.h"
#import "HippyInvalidating.h"
#import "HippyBridge.h"
#import "Hippybridge+PerformanceAPI.h"
#import "HippyBridge+BundleLoad.h"
#import "HippyUIManager.h"
#import "HippyUIManager+Private.h"
#import "HippyUtils.h"
#import "HippyDeviceBaseInfo.h"
#import "HippyTouchHandler.h"
#import "HippyJSExecutor.h"
#import "dom/dom_manager.h"
#include <objc/runtime.h>

// Sent when the first subviews are added to the root view
Expand Down Expand Up @@ -78,6 +82,16 @@ - (instancetype)init NS_UNAVAILABLE;
/// Unvaliable, use designated initializer.
+ (instancetype)new NS_UNAVAILABLE;


#pragma mark - Snapshot

/// Retrieves the current snapshot data.
- (nullable NSData *)retrieveCurrentSnapshotData;

/// Restores the snapshot data with the provided NSData object.
/// - Parameter data: NSData object
- (BOOL)restoreSnapshotData:(nullable NSData *)data;

@end

#pragma mark - HippyRootView
Expand Down Expand Up @@ -361,6 +375,17 @@ - (void)onHostControllerTransitionedToSize:(CGSize)size {
userInfo:@{HippyHostControllerSizeKeyNewSize : @(size)}];
}


#pragma mark - Snapshot

- (NSData *)retrieveCurrentSnapshotData {
return [self.contentView retrieveCurrentSnapshotData];
}

- (BOOL)restoreSnapshotData:(NSData *)data {
return [self.contentView restoreSnapshotData:data];
}

@end


Expand Down Expand Up @@ -441,5 +466,34 @@ - (void)invalidate {
}
}

#pragma mark - Snapshot

- (NSData *)retrieveCurrentSnapshotData {
auto rootNode = [self.uiManager.viewRegistry rootNodeForTag:self.hippyTag].lock();
if (!rootNode) {
return nil;
}
std::string data = hippy::DomManager::GetSnapShot(rootNode);
return [NSData dataWithBytes:data.c_str() length:data.length()];
}

- (BOOL)restoreSnapshotData:(NSData *)data {
HippyUIManager *uiManager = self.uiManager;
if (!uiManager || !data) {
return NO;
}
auto domManager = [uiManager domManager].lock();
if (!domManager) {
return NO;
}
auto rootNode = [uiManager.viewRegistry rootNodeForTag:self.hippyTag].lock();
if (!rootNode) {
return NO;
}
std::string string(reinterpret_cast<const char *>([data bytes]), [data length]);
bool result = domManager->SetSnapShot(rootNode, string);
HippyLogInfo(@"Snapshot restore result:%d for root:%@", result, self.hippyTag);
return result;
}

@end
192 changes: 192 additions & 0 deletions tests/ios/HippyRootViewTest.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*!
* iOS SDK
*
* Tencent is pleased to support the open source community by making
* Hippy available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <XCTest/XCTest.h>
#import <hippy/HippyView.h>
#import <hippy/HippyRootView.h>
#import <hippy/HippyUIManager.h>
#import <hippy/UIView+Hippy.h>
#import <hippy/UIView+Render.h>
#import "HippyComponentMap.h"
#import "HippyUIManager+Private.h"
#import "HippyJSEnginesMapper.h"
#import "dom/root_node.h"
#import <OCMock/OCMock.h>


@interface UIView (HippyUIManagerUnitTest)

/// Bind UIView with HippyUIManager, for UnitTest
/// - Parameter uiManager: HippyUIManager instance
- (void)setUiManager:(HippyUIManager *)uiManager;

@end


@interface HippyRootContentView : HippyView

/// Init method
- (instancetype)initWithFrame:(CGRect)frame
bridge:(HippyBridge *)bridge
hippyTag:(NSNumber *)hippyTag
sizeFlexiblity:(HippyRootViewSizeFlexibility)sizeFlexibility;

#pragma mark - Snapshot

/// Retrieves the current snapshot data.
- (nullable NSData *)retrieveCurrentSnapshotData;

/// Restores the snapshot data with the provided NSData object.
/// - Parameter data: NSData object
- (BOOL)restoreSnapshotData:(nullable NSData *)data;

@end


@interface HippyRootView (UnitTest)

/// ContentView for HippyRootView
@property (nonatomic, strong) HippyRootContentView *contentView;

@end

/// Root tag for test
static NSNumber *const kHippyTestRootTag = @10;


@interface HippyRootViewTest : XCTestCase

/// Mock rootView
@property (nonatomic, strong) HippyRootView *mockRootView;
/// Mock HippyRootContentView
@property (nonatomic, strong) HippyRootContentView *mockContentView;
/// UIManager instance
@property (nonatomic, strong) HippyUIManager *uiManager;

@end

@implementation HippyRootViewTest

- (void)setUp {
[super setUp];
HippyBridge *bridge = [[HippyBridge alloc] initWithDelegate:nil moduleProvider:nil launchOptions:nil executorKey:nil];
HippyRootView *rootView = [[HippyRootView alloc] initWithBridge:bridge moduleName:@"Test" initialProperties:nil delegate:nil];
self.mockRootView = OCMPartialMock(rootView);

// Create ContentView's mock instance
HippyRootContentView *contentView = [[HippyRootContentView alloc] initWithFrame:CGRectZero
bridge:bridge
hippyTag:kHippyTestRootTag
sizeFlexiblity:HippyRootViewSizeFlexibilityNone];
self.mockContentView = OCMPartialMock(contentView);
OCMStub([self.mockRootView contentView]).andReturn(self.mockContentView);

// Create UIManager's instance
HippyUIManager *uiManager = [[HippyUIManager alloc] initWithBridge:bridge];
((HippyRootContentView *)self.mockContentView).uiManager = uiManager;
self.uiManager = uiManager;
}

- (void)tearDown {
[(id)self.mockContentView stopMocking];
[(id)self.mockRootView stopMocking];
[super tearDown];
}

- (void)testRetrieveCurrentSnapshotData_WhenRootNotNil {
// Set rootTag
OCMStub([self.mockContentView hippyTag]).andReturn(kHippyTestRootTag);
auto rootNode = std::make_shared<hippy::dom::RootNode>(kHippyTestRootTag.unsignedIntValue);
[self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag];
// Retrive snapshot
NSData *result = [self.mockRootView retrieveCurrentSnapshotData];
OCMVerify([self.mockContentView retrieveCurrentSnapshotData]);
XCTAssertNotNil(result);
}

- (void)testRetrieveCurrentSnapshotData_WhenRootIsNil {
// Set test invalid rootTag @0
OCMStub([self.mockContentView hippyTag]).andReturn(@0);
auto rootNode = std::make_shared<hippy::dom::RootNode>(kHippyTestRootTag.unsignedIntValue);
[self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag];
// Retrive snapshot
NSData *result = [self.mockRootView retrieveCurrentSnapshotData];
OCMVerify([self.mockContentView retrieveCurrentSnapshotData]);
XCTAssertNil(result);
}

- (void)testRestoreSnapshotData_WhenDataIsNil_ReturnsNO {
NSData *data = nil;
BOOL result = [self.mockRootView restoreSnapshotData:data];
OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]);
XCTAssertFalse(result);
}

- (void)testRestoreSnapshotData_WhenUIManagerIsNil_ReturnsNO {
self.mockContentView.uiManager = nil;
NSData *data = [NSData dataWithBytes:"mocked data" length:strlen("mocked data")];
BOOL result = [self.mockRootView restoreSnapshotData:data];
OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]);
XCTAssertFalse(result);
}

- (void)testRestoreSnapshotData_WhenRootNodeIsNil_ReturnsNO {
// Set invalid rootTag
OCMStub([self.mockContentView hippyTag]).andReturn(@0);
auto rootNode = std::make_shared<hippy::dom::RootNode>(kHippyTestRootTag.unsignedIntValue);
[self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag];

// Set dom_manager
auto engineResource = [[HippyJSEnginesMapper defaultInstance] createJSEngineResourceForKey:@"testKey"];
auto domManager = engineResource->GetDomManager();
[self.mockContentView.uiManager setDomManager:domManager];

// Restore snapshot
NSData *data = [NSData dataWithBytes:"mocked data" length:strlen("mocked data")];
BOOL result = [self.mockRootView restoreSnapshotData:data];
OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]);
XCTAssertFalse(result);
}

- (void)testRestoreSnapshotData_WhenValidData_ReturnsYES {
// Set rootTag
OCMStub([self.mockContentView hippyTag]).andReturn(kHippyTestRootTag);
auto rootNode = std::make_shared<hippy::dom::RootNode>(kHippyTestRootTag.unsignedIntValue);
[self.mockContentView.uiManager.viewRegistry addRootComponent:self.mockRootView rootNode:rootNode forTag:kHippyTestRootTag];

// Retrive snapshot
NSData *snapshotData = [self.mockRootView retrieveCurrentSnapshotData];
OCMVerify([self.mockContentView retrieveCurrentSnapshotData]);

// Set dom_manager
auto engineResource = [[HippyJSEnginesMapper defaultInstance] createJSEngineResourceForKey:@"testKey"];
auto domManager = engineResource->GetDomManager();
[self.mockContentView.uiManager setDomManager:domManager];

// Restore snapshot
BOOL result = [self.mockRootView restoreSnapshotData:snapshotData];
OCMVerify([self.mockContentView restoreSnapshotData:[OCMArg any]]);
XCTAssertTrue(result);
}


@end

0 comments on commit 17b5c35

Please sign in to comment.