Block 迴圈引用(上)

来源:https://www.cnblogs.com/EchoHG/archive/2018/03/04/8505502.html
-Advertisement-
Play Games

iOS的記憶體管理機制 Objective-C在iOS中不支持GC(垃圾回收)機制,而是採用的引用計數的方式管理記憶體。 引用計數:在引用計數中,每一個對象負責維護對象所有引用的計數值。當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就將釋放占有的資 ...


 

iOS的記憶體管理機制

Objective-C在iOS中不支持GC(垃圾回收)機制,而是採用的引用計數的方式管理記憶體。

引用計數:在引用計數中,每一個對象負責維護對象所有引用的計數值。當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就將釋放占有的資源。

以開燈為例:

圖中,“需要照明的人數”即對應我們要說的引用計數值。

  1. 第一個人進入辦公室,“需要照明的人數”加1,計數值從0變為1,因此需要開燈;
  2. 之後每當有人進入辦公室,“需要照明的人數”就加1。如計數值從1變成2;
  3. 每當有人下班離開辦公室,“需要照明的人數”加減1如計數值從2變成1;
  4. 最後一個人下班離開辦公室時,“需要照明的人數”減1。計數值從1變成0,因此需要關燈。

在Objective-C中,”對象“相當於辦公室的照明設備,”對象的使用環境“相當於進入辦公室的人。上班進入辦公室的人對辦公室照明設備發出的動作,與Objective-C中的對應關係如下表

對照明設備所做的動作對Objective-C對象所做的動作
開燈 生成對象
需要照明 持有對
不需要照明 釋放對象

 

廢棄對象

 

MRC(Manual Reference Counting)中引起應用計數變化的方法

Objective-C對象方法說明
alloc/new/copy/mutableCopy 創建對象,引用計數加1
retain 引用計數加1
release 引用計數減1
dealloc 當引用計數為0時調用
[NSArray array] 引用計數不增加,由自動釋放池管理
[NSDictionary dictionary] 引用計數不增加,由自動釋放池管理

 

ARC(Automatic Reference Counting)中記憶體管理

Objective-C對象所有權修飾符說明
__strong 對象預設修飾符,對象強引用,在對象超出作用域時失效。其實就相當於retain操作,超出作用域時執行release操作
__weak 弱引用,不持有對象,對象釋放時會將對象置nil。
__unsafe_unretained 弱引用,不持有對象,對象釋放時不會將對象置nil。
__autoreleasing 自動釋放,由自動釋放池管理對象

 

 


 

引用一

1 [self.teacher requestData:^(NSData *data) {
2     self.name = @"case";
3 }];

此種情況是最常見的迴圈引用導致的記憶體泄露了,在這裡,self強引用了teacher, teacher又強引用了一個block,而該block在回調時又調用了self,會導致該block又強引用了self,造成了一個保留環,最終導致self無法釋放。

self -> teacher -> block -> self

解決方法

1 __weak typeof(self) weakSelf = self;
2     [self.teacher requestData:^(NSData *data) {
3         typeof(weakSelf) strongSelf = weakSelf;
4        strongSelf.name = @"case";
5     }];

通過__weak的修飾,先把self弱引用(預設是強引用,實際上self是有個隱藏的__strong修飾的),然後在block回調里用weakSelf,這樣就會打破保留環,從而避免了迴圈引用,如下:

self -> teacher -> block -> weakSelf

註意:一般會在block回調里再強引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;),因為__weak修飾的都是存在棧內,可能隨時會被系統釋放,造成後面調用weakSelf時weakSelf可能已經是nil了,後面用weakSelf調用任何代碼都是無效的。

 


 

 

引用二

代碼

ViewController.m

 1 #import "ViewController.h"
 2 #import "HYBAController.h"
 3 
 4 @interface ViewController ()
 5 
 6 @property (nonatomic, strong) UIButton *button;
 7 @property (nonatomic, strong) HYBAController *vc;
 8 
 9 @end
10 
11 @implementation ViewController
12 
13 - (void)goToNext {
14   __weak __typeof(self) weakSelf = self;
15   HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
16     [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
17   }];
18 //  self.vc = vc;
19   [self.navigationController pushViewController:vc animated:YES];
20 }
21 
22 
23 
24 - (void)viewDidLoad {
25   [super viewDidLoad];
26   
27   UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
28   [button setTitle:@"進入下一個界面" forState:UIControlStateNormal];
29   button.frame = CGRectMake(50, 200, 200, 45);
30   button.backgroundColor = [UIColor redColor];
31   [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
32   [button addTarget:self action:@selector(goToNext) forControlEvents:UIControlEventTouchUpInside];
33   [self.view addSubview:button];
34   self.button = button;
35   
36   self.title = @"ViewController";
37 
38 }
39 
40 @end
View Code

 

HYBAController.h

1 #import <UIKit/UIKit.h>
2 
3 typedef void(^HYBCallbackBlock)();
4 
5 @interface HYBAController : UIViewController
6 
7 - (instancetype)initWithCallback:(HYBCallbackBlock)callback;
8 
9 @end

HYBAController.m

 1 #import "HYBAController.h"
 2 #import "HYBAView.h"
 3 
 4 @interface HYBAController()
 5 
 6 @property (nonatomic, copy) HYBCallbackBlock callbackBlock;
 7 
 8 @property (nonatomic, strong) HYBAView *aView;
 9 @property (nonatomic, strong) id currentModel;
10 
11 @end
12 
13 @implementation HYBAController
14 
15 - (instancetype)initWithCallback:(HYBCallbackBlock)callback {
16   if (self = [super init]) {
17     self.callbackBlock = callback;
18   }
19   
20   return self;
21 }
22 
23 - (void)viewDidLoad {
24   [super viewDidLoad];
25   
26   self.title = @"HYBAController";
27   self.view.backgroundColor = [UIColor whiteColor];
28   
29   __block __weak __typeof(_currentModel) weakModel = _currentModel;
30   self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
31     // 假設要更新model
32     weakModel = model;
33 //      self.currentModel = model;
34   }];
35   // 假設占滿全屏
36   self.aView.frame = self.view.bounds;
37   [self.view addSubview:self.aView];
38   self.aView.backgroundColor = [UIColor whiteColor];
39   
40 }
41 
42 - (void)viewDidAppear:(BOOL)animated {
43   [super viewDidAppear:animated];
44   
45   NSLog(@"進入控制器:%@", [[self class] description]);
46 }
47 
48 - (void)dealloc {
49   NSLog(@"%@-->控制器被dealloc", [[self class] description]);
50 }
51 
52 @end
View Code

 

 

HYBAView.h

1 #import <UIKit/UIKit.h>
2 
3 typedef void(^HYBFeedbackBlock)(id model);
4 
5 @interface HYBAView : UIView
6 
7 - (instancetype)initWithBlock:(HYBFeedbackBlock)block;
8 
9 @end

HYBAView.m

 1 #import "HYBAView.h"
 2 
 3 @interface HYBAView ()
 4 
 5 @property (nonatomic, copy) HYBFeedbackBlock block;
 6 
 7 @end
 8 
 9 @implementation HYBAView
10 
11 - (void)dealloc {
12   NSLog(@"dealloc: %@", [[self class] description]);
13 }
14 
15 - (instancetype)initWithBlock:(HYBFeedbackBlock)block {
16   if (self = [super init]) {
17     self.block = block;
18     
19     UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
20     [button setTitle:@"反饋給controller" forState:UIControlStateNormal];
21     button.frame = CGRectMake(50, 200, 200, 45);
22     button.backgroundColor = [UIColor redColor];
23     [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
24     [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside];
25     [self addSubview:button];
26   }
27   
28   return self;
29 }
30 
31 - (void)feedback {
32   if (self.block) {
33     // 傳模型回去,這裡沒有數據,假設傳nil
34     self.block(nil);
35   }
36 }
37 
38 @end
View Code

以上是正常運行,不存在記憶體泄露的代碼,下麵進行細緻討論情況

場景一:Controller之間block傳值

 1 @interface ViewController ()
 2  
 3 // 引用按鈕只是為了測試
 4 @property (nonatomic, strong) UIButton *button;
 5 // 只是為了測試記憶體問題,引用之。在開發中,有很多時候我們是
 6 // 需要引用另一個控制器的,因此這裡模擬之。
 7 @property (nonatomic, strong) HYBAController *vc;
 8  
 9 @end
10  
11 // 點擊button時
12 - (void)goToNext {
13   HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
14     [self.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
15   }];
16   self.vc = vc;
17   [self.navigationController pushViewController:vc animated:YES];
18 }

 原因:

這裡形成了兩個環:

  • ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController

  • ViewController->強引用了屬性vc->強引用了callback->強引用了ViewController的屬性button 

解決方案:

不聲明vc屬性或者將vc屬性聲明為weak引用的類型,在callback回調處,將self.button改成weakSelf.button,也就是讓callback這個block對viewcontroller改成弱引用。如就是改成如下,記憶體就可以正常釋放了:

1 - (void)goToNext {
2   __weak __typeof(self) weakSelf = self;
3   HYBAController *vc = [[HYBAController alloc] initWithCallback:^{
4     [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
5   }];
6 //  self.vc = vc;
7   [self.navigationController pushViewController:vc animated:YES];
8 }

 筆者嘗試過使用Leaks檢測記憶體泄露,但是全是通過,一個綠色的勾,讓你以為記憶體處理得很好了,實際上記憶體並得不到釋放。

針對這種場景,給大家提點建議:

 在控制器的生命周期viewDidAppear里列印日誌:

1 - (void)viewDidAppear:(BOOL)animated {
2   [super viewDidAppear:animated];
3  
4   NSLog(@"進入控制器:%@", [[self class] description]);
5 }

在控制器的生命周期dealloc里列印日誌

1 - (void)dealloc {
2   NSLog(@"控制器被dealloc: %@", [[self class] description]);
3 }

 

 

場景二: Controller與View之間Block傳值

定義一個view,用於與Controller交互。當點擊view的按鈕時,就會通過block回調給controller,也就反饋到控制器了,並將對應的數據傳給控制器以記錄

 1 typedef void(^HYBFeedbackBlock)(id model);
 2  
 3 @interface HYBAView : UIView
 4  
 5 - (instancetype)initWithBlock:(HYBFeedbackBlock)block;
 6  
 7 @end
 8  
 9 @interface HYBAView ()
10  
11 @property (nonatomic, copy) HYBFeedbackBlock block;
12  
13 @end
14  
15 @implementation HYBAView
16  
17 - (void)dealloc {
18   NSLog(@"dealloc: %@", [[self class] description]);
19 }
20  
21 - (instancetype)initWithBlock:(HYBFeedbackBlock)block {
22   if (self = [super init]) {
23     self.block = block;
24  
25     UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
26     [button setTitle:@"反饋給controller" forState:UIControlStateNormal];
27     button.frame = CGRectMake(50, 200, 200, 45);
28     button.backgroundColor = [UIColor redColor];
29     [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal];
30     [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside];
31     [self addSubview:button];
32   }
33  
34   return self;
35 }
36  
37 - (void)feedback {
38   if (self.block) {
39     // 傳模型回去,這裡沒有數據,假設傳nil
40     self.block(nil);
41   }
42 }
43  
44 @end
View Code

 

HYBAController,增加了兩個屬性,在viewDidLoad時,創建了aView屬性

 1 @interface HYBAController()
 2  
 3 @property (nonatomic, copy) HYBCallbackBlock callbackBlock;
 4  
 5 @property (nonatomic, strong) HYBAView *aView;
 6 @property (nonatomic, strong) id currentModel;
 7  
 8 @end
 9  
10 @implementation HYBAController
11  
12 - (instancetype)initWithCallback:(HYBCallbackBlock)callback {
13   if (self = [super init]) {
14     self.callbackBlock = callback;
15   }
16  
17   return self;
18 }
19  
20 - (void)viewDidLoad {
21   [super viewDidLoad];
22  
23   self.title = @"HYBAController";
24   self.view.backgroundColor = [UIColor whiteColor];
25  
26   self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
27     // 假設要更新model
28     self.currentModel = model;
29   }];
30   // 假設占滿全屏
31   self.aView.frame = self.view.bounds;
32   [self.view addSubview:self.aView];
33   self.aView.backgroundColor = [UIColor whiteColor];
34 }
35  
36 - (void)viewDidAppear:(BOOL)animated {
37   [super viewDidAppear:animated];
38  
39   NSLog(@"進入控制器:%@", [[self class] description]);
40 }
41  
42 - (void)dealloc {
43   NSLog(@"控制器被dealloc: %@", [[self class] description]);
44 }
45  
46 @end
View Code

 

 

原因,形成的環:

  • vc->aView->block->vc(self)

  • vc->aView->block->vc.currentModel

 

解決的辦法可以是:在創建aView時,block內對currentModel的引用改成弱引用:

1 __weak __typeof(self) weakSelf = self;
2 self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
3     // 假設要更新model
4     weakSelf.currentModel = model;
5 }];

 

很多類似這樣的代碼,直接使用成員變數,而不是屬性,然後他們以為這樣就不會引用self,也就是控制器,從而不形成環:

1 self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
2     // 假設要更新model
3     _currentModel = model;
4 }];
View Code

 

這是錯誤的理解,當我們引用了_currentModel時,它是控制器的成員變數,因此也就引用了控制器。要解決此問題,也是要改成弱引用:

1 __block __weak __typeof(_currentModel) weakModel = _currentModel;
2 self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
3   // 假設要更新model
4   weakModel = model;
5 }];
View Code

這裡還要加上__block啊!

 

模擬迴圈引用

假設下麵如此寫代碼,是否出現記憶體得不到釋放問題?(其中,controller屬性都是強引用聲明的)

1 @autoreleasepool {
2   A *aVC = [[A alloc] init];
3   B *bVC = [[B allcok] init];
4   aVC.controller = bVC;
5   bVC.controller = aVC;
6 }

 

分析:

aVC->強引用了bVC->強引用了aVC,因此形成了一個環,導致記憶體得不到釋放。

 


 

 

引用三

註意在UI中,如果一個AViewController中存在,並且存在Block屬性,若AViewController不消失,則整個AViewControlller不會釋放,不會列印dealloc中的東西,所以要想列印需要將AViewController push掉用導航控制器或者模態視圖消失掉!

block中迴圈引用問題

由於block會對block中的對象進行持有操作,就相當於持有了其中的對象,而如果此時block中的對象又持有了該block,則會造成迴圈引用。如下:

typedef void (^ block)(void);

@property (copy, nonatomic) block myBlock;
@property (copy, nonatomic) NSString *blockString;



- (void)viewWillAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    NSLog(@"進入控制器:%@", [[self class] description]);
    
    [self testBlock];
}

- (void)testBlock {
    self.myBlock = ^() {
        //其實註釋中的代碼,同樣會造成迴圈引用
        NSString *localString = self.blockString;
        //        NSString *localString = _blockString;
        //        [self doSomething];
    };
    
}

- (void)dealloc {
    NSLog(@"%@--------->控制器被dealloc", [[self class] description]);
}

 

 

列印結果:

2018-03-04 20:57:57.890402+0800 BlockTest[2547:345760] 進入控制器:NextViewController

 

當有someObj持有self對象,此時的關係圖如下。

 當someObj對象release self對象時,self和myblock相互引用,retainCount都為1,造成迴圈引用

 

註意

註釋掉的代碼同樣會造成迴圈引用,因為不管是通過self.blockString還是_blockString,或是函數調用[self doSomething],因為只要 block中用到了對象的屬性或者函數,block就會持有該對象而不是該對象中的某個屬性或者函數。

解決方法:

1  __weak typeof(self) weakSelf = self;
2     self.myBlock = ^() {
3     NSString *localString = weakSelf.blockString;
4     };

 使用__weak修飾self,使其在block中不被持有,打破迴圈引用。開始狀態如下

當someObj對象釋放self對象時,Self的retainCount為0,走dealloc,釋放myBlock對象,使其retainCount也為0。

 

多個對象發生迴圈引用

以上迴圈引用的情況很容易發現,因為此時Xcode就會報警告。而發生在多個對象間的時候,Xcode就檢測不出來了,這往往就容易被忽略。

 1 //ClassB
 2 @interface ClassB : NSObject
 3 @property (strong, nonatomic) ClassA *objA;
 4 - (void)doSomething;
 5 @end
 6   
 7 //ClassA
 8 @property (strong, nonatomic) ClassB *objB;
 9 @property (copy, nonatomic) block myBlock;
10 
11 - (void)testBlockRetainCycle {
12     ClassB* objB = [[ClassB alloc] init];
13     self.myBlock = ^() {
14         [objB doSomething];
15     };
16     objB.objA = self;
17 }

解決方法:

1 - (void)testBlockRetainCycle {
2     ClassB* objB = [[ClassB alloc] init];
3     __weak typeof(objB) weakObjB = objB;
4     self.myBlock = ^() {
5         [weakObjB doSomething];
6     };
7     objB.objA = self;
8 }

將objA對象weak,使其不在block中被持有

註:以上使用__weak打破迴圈的方法只在ARC下才有效,在MRC下應該使用__block

 或者,在block執行完後,將block置nil,這樣也可以打破迴圈引用

1 - (void)testBlockRetainCycle {
2     ClassB* objB = [[ClassB alloc] init];
3     self.myBlock = ^() {
4         [objB doSomething];
5     };
6     objA.objA = self;
7     self.myBlock();
8     self.myBlock = nil;
9 }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MySQL的複製解決什麼樣的問題,MySQL的二進位日誌的格式對數據的影響,如何實現主從複製,MySQL的複製拓撲圖 ...
  • 什麼是資料庫? 資料庫是一個以某種 有組織的方式存儲的數據集合 。也就是: 保存有組織數據的容器 (一個文件或一組文件) 為什麼我們需要資料庫? 毫無疑問,資料庫是用來存儲數據的。我們對excel肯定不會陌生,excel也是用來存儲數據。那既然有excel這樣非常好用的軟體了,為什麼需要資料庫呢?? ...
  • 1.存儲引擎 innodb與MyIASM存儲引擎的區別: 1.innodb 是mysql5.5版本以後的預設存儲引擎, 而MyISAM是5.5版本以前的預設存儲引擎. 2.innodb 支持事物,而MyISAM不支持事物 3.innodb 支持行級鎖.而MyIASM 它支持的是併發的表級鎖. 4.i ...
  • 本文主要介紹Oracle資料庫體繫結構以及相關DDL、DCL、DML語句的操作。 ...
  • 場景描述: 公司埋點項目,數據從介面服務寫入kafka集群,再從kafka集群消費寫入HDFS文件系統,最後通過Hive進行查詢輸出。這其中存在一個問題就是:埋點介面中的數據欄位是變化,後續會有少量欄位添加進來。這導致Hive表結構也需要跟著變化,否則無法通過Hive查詢到最新添加欄位的數據。 解決 ...
  • 複雜性是不可避免的,而且只會隨時間增長,所以在增加特性時一定要為重構和代碼簡化留出時間。真正遇到問題這前先不要擔心性能。iPhone非常強壯,你可能永遠也不會遇到預想的性能問題。 能過互聯網向一個設備傳送音頻時,可以採用兩種傳輸模型:流式傳輸和下載。 對於流式傳輸,音頻伺服器會按音頻的比特率通過網路 ...
  • 大家可以看到,大眾點評中,為了突出這個購買條,當向上滾動時,該滾動條會顯示在最上面(如圖2),而當用戶滑動回來的時候,又可以恢復回第一張圖的樣子 下麵說一下具體的實現思路: 從這張圖,我們可以看下具體的佈局.實際上在最頂部的位置,有一個購買條1,最開始的時候是隱藏的,而當從上向下滑動到具體位置的時候 ...
  • No1: View的滑動 1)layout()方法的 2)offsetLeftAndRight()與offsetTopAndBottom() 對上面代碼進行修改 3)LayoutParams(改變佈局參數) 同樣對上面代碼進行修改 4)動畫 5)scrollTo與scrollBy scrollTo( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...