Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ios): rewrite snapshot's retrieve and restore functionality #4136

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading