block本質探尋五之atuto類型局部實例對象

来源:https://www.cnblogs.com/lybSkill/archive/2019/01/15/10268273.html
-Advertisement-
Play Games

說明:閱讀本文章,請參考之前的block文章加以理解; 一、棧區block分析 //代碼 //列印 分析: <1>block代碼內部引用的Person實例對象先於輸出語句銷毀,因為per僅限於大括弧內,但此時block銷毀了沒有?往下看; <2>上述block代碼塊並沒有被指針持有,接下來看看指針持 ...


說明:閱讀本文章,請參考之前的block文章加以理解;

一、棧區block分析

//代碼

//ARC
void test1()
{
    {
        Person *per = [[Person alloc] init];
        per.age = 10;
        ^{
            NSLog(@"age:%d", per.age);
        };
    }
    
    NSLog(@"-------1");
}

 

//列印

2019-01-14 17:24:12.118653+0800 MJ_TEST[6638:285938] Person dealloc
2019-01-14 17:24:12.118934+0800 MJ_TEST[6638:285938] -------1
Program ended with exit code: 0 

分析:

<1>block代碼內部引用的Person實例對象先於輸出語句銷毀,因為per僅限於大括弧內,但此時block銷毀了沒有?往下看;

<2>上述block代碼塊並沒有被指針持有,接下來看看指針持有的情況;

//代碼

typedef void(^MyBlock)(void);

//ARC
void test2()
{
    MyBlock block;
    
    {
        Person *per = [[Person alloc] init];
        per.age = 10;
        block = ^{
            NSLog(@"age:%d", per.age);
        };
    }
    
    NSLog(@"-------1");
}

//列印

2019-01-14 17:34:58.473267+0800 MJ_TEST[6824:293129] -------1
2019-01-14 17:34:58.473705+0800 MJ_TEST[6824:293129] Person dealloc
Program ended with exit code: 0

分析:Person實例對象後於輸出語句銷毀,為什麼有指針持有,順序就變了?

<1>等號左邊:是一個auto類型的局部的block指針變數,存放在棧區;等號右邊:是一個block代碼塊(對象),也是一個局部對象,存放在棧區;

<2>在ARC模式下,如果有指針持有(預設是強指針,修飾符為__strong)一個局部的block對象,系統會自動copy該block對象從棧區到堆區;

補充:其他三種情況——block作為函數返回值、含usingBlock方法(如數據的枚舉方法)、GCD的應用(自己可以驗證,此處不再贅述);

那麼,我們再看看MRC的情況

//列印————test1()和test2()

2019-01-14 17:56:46.641171+0800 MJ_TEST[7171:306788] -------1
Program ended with exit code: 0

分析:為什麼per對象沒有銷毀?——因為需要手動釋放;

//代碼

[per release];

//列印————test1()和test2()

2019-01-14 17:59:39.091313+0800 MJ_TEST[7243:309139] Person dealloc
2019-01-14 17:59:39.091974+0800 MJ_TEST[7243:309139] -------1
2019-01-14 17:59:39.092013+0800 MJ_TEST[7243:309139] Person dealloc
2019-01-14 17:59:39.092086+0800 MJ_TEST[7243:309139] -------1
Program ended with exit code: 0

分析:

<1>此時的block對象的作用域在第一個大括弧範圍內,超出則被釋放;

<2>Person實例對象被捕獲到block對象結構體體中,同時其作用域也僅限於第一個大括弧內,因此超出同樣被釋放;

 

二、堆區block分析

1)類型分析——ARC

//strong類型

執行上述test2()方法,我們知道系統會自動將block對象從棧區copy到堆區;同時,Person實例對象會被捕捉到block對象的結構體中,如下

struct __test2_block_impl_0 {
  struct __block_impl impl;
  struct __test2_block_desc_0* Desc;
  Person *per;
  __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *_per, int flags=0) : per(_per) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

分析:可以看到per是一個指針變數,而該指針變數預設修飾符為__strong;修改代碼

 __strong Person *per = [[Person alloc] init];

clang命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

說明:

<1>該命令行,只針對ARC模式下,MRC模式下,如果有release語句會報錯;

<2>該命令行,是解決ARC模式下,實例對象為__weak類型,轉成C++代碼;

struct __test2_block_impl_0 {
  struct __block_impl impl;
  struct __test2_block_desc_0* Desc;
  Person *__strong per;
  __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__strong _per, int flags=0) : per(_per) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

 分析:因此,一般的指針變數,預設修飾符為__strong;

 

//weak類型

//代碼

//ARC
void test2()
{
    MyBlock block;
    
    {
        Person *per = [[Person alloc] init];
        per.age = 10;
        __weak Person *weakPer = per;
        block = ^{
            NSLog(@"age:%d", weakPer.age);
        };
//        [per release];
    }
    
    NSLog(@"-------1");
}

//C++代碼

struct __test2_block_impl_0 {
  struct __block_impl impl;
  struct __test2_block_desc_0* Desc;
  Person *__weak weakPer;
  __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//列印

2019-01-15 10:21:36.280746+0800 MJ_TEST[997:45906] Person dealloc
2019-01-15 10:21:36.281063+0800 MJ_TEST[997:45906] -------1
Program ended with exit code: 0

分析:

<1>此時的per由weakPer弱指針指向,並被捕捉到block對象結構體中;

<2>結合上述強指針引用Person實例對象的列印結果,weak修飾的指針變數先於輸出語句銷毀,可以肯定系統是沒有對block對象copy到堆區的;

那針對不同類型指針的引用,系統是如果判斷操作的呢?往下看

 

2)調用原理

//C++代碼

typedef void(*MyBlock)(void);



struct __test2_block_impl_0 {
  struct __block_impl impl;
  struct __test2_block_desc_0* Desc;
  Person *__weak weakPer;
  __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __test2_block_func_0(struct __test2_block_impl_0 *__cself) {
  Person *__weak weakPer = __cself->weakPer; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3e374f_mi_2, ((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPer, sel_registerName("age")));
        }

static void __test2_block_copy_0(struct __test2_block_impl_0*dst, struct __test2_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPer, (void*)src->weakPer, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __test2_block_dispose_0(struct __test2_block_impl_0*src) {_Block_object_dispose((void*)src->weakPer, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __test2_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*);
  void (*dispose)(struct __test2_block_impl_0*);
} __test2_block_desc_0_DATA = { 0, sizeof(struct __test2_block_impl_0), __test2_block_copy_0, __test2_block_dispose_0};

void test2()
{
    MyBlock block;

    {
        Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)per, sel_registerName("setAge:"), 10);
        __attribute__((objc_ownership(weak))) Person *weakPer = per;
        block = ((void (*)())&__test2_block_impl_0((void *)__test2_block_func_0, &__test2_block_desc_0_DATA, weakPer, 570425344));

    }

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_3e374f_mi_3);
}

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        test2();
    }
    return 0;
}

分析:

<1>根據前面的文章分析,我們發現block描述結構體(__test2_block_desc_0)中多了兩個函數指針的成員變數

void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*);
  void (*dispose)(struct __test2_block_impl_0*);

其中,copy函數指針指向__test2_block_copy_0函數,dispose指向__test2_block_dispose_0;

<2>__test2_block_copy_0函數主要是通過_Block_object_assign函數來確定對per對象是否強引用,其根據就是per的引用類型——如果是__strong類型,則block對象對per對對象進行強引用(per的生命周期可控);如果是__weak類型,則進行弱引用(per的生命周期不可控);

 

<3>當block對像從堆區銷毀時,會調用__test2_block_dispose_0函數會自動釋放引用的per對象(相當於release)——註:嚴格意義上,此處的釋放指的斷開是block對象對per的引用即retainCount減1,至於per對象所占的記憶體是否被釋放(回收)則在所不問(也許還有其他的指針變數引用),只有retainCount變為0零,其記憶體才會被回收;

補充:當block訪問的外部的auto類型的局部數據為對象時,則會產生上述兩個函數指針;如果是非實例對象(如基礎數據類型),則不會有上述兩個函數指針——原因:實例對象一般是在堆區開闢的記憶體,需要對其進行記憶體管理————註:如果是__block修飾前述變數(包括實例對象),也會產生上述兩個指針函數,具體後面文章會寫到!

//代碼

void test3()
{
    int age = 10;
    ^{
        NSLog(@"%d", age);
    };
}

//clang

static struct __test3_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __test3_block_desc_0_DATA = { 0, sizeof(struct __test3_block_impl_0)};

 

三、結論 

【1】棧區block:不論是ARC還是MRC模式,指向該block對象的指針變數,不會對引用的auto類型的局部的實例對象進行強引用;

【2】堆區block:不論是ARC還是MRC模式,指向該block對象的指針變數,根據引用的auto類型的局部的實例對象的引用類型,通過調用block結構體中的copy函數指針來調用_Block_object_assign函數,來決定對實例對象是否進行強引用——__strong類型強引用,__weak類型弱引用;

【3】堆區block釋放:系統會通過調用block結構體中的dispose函數指針來調用__test2_block_dispose_0函數,自動釋放引用的外部auto類型的局部實例對象;

說明:

<1>block對象本身,即代碼塊(位於等號右邊),非block指針變數(位於等號左邊);

<2>所謂的強引用,類似於retain操作即保留實例對象(所占記憶體不隨作用域限制而被自動回收),保證手動管理記憶體釋放,達到可控的目的;

 

四、拓展——GCD引用分析

1)

//代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *per = [[Person alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*3), dispatch_get_main_queue(), ^{
        NSLog(@"%@", per);
    });
    
    NSLog(@"touchesBegan");
}

//列印

2019-01-15 11:56:19.867668+0800 GCD_Refrence[1558:90109] touchesBegan
2019-01-15 11:56:22.867823+0800 GCD_Refrence[1558:90109] <Person: 0x600003bb8cf0>
2019-01-15 11:56:22.867996+0800 GCD_Refrence[1558:90109] Person dealloc

2)

//代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *per = [[Person alloc] init];
    __weak Person *weakPer = per;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*3), dispatch_get_main_queue(), ^{
        NSLog(@"%@", weakPer);
    });
    
    NSLog(@"touchesBegan");
}

//列印

2019-01-15 11:58:58.381396+0800 GCD_Refrence[1619:93062] touchesBegan
2019-01-15 11:58:58.381583+0800 GCD_Refrence[1619:93062] Person dealloc
2019-01-15 11:59:01.381697+0800 GCD_Refrence[1619:93062] (null)

3)

//代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *per = [[Person alloc] init];
    __weak Person *weakPer = per;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
            NSLog(@"%@", per);
        });
    });
    
    NSLog(@"touchesBegan");
}

//列印

2019-01-15 12:00:44.996108+0800 GCD_Refrence[1653:94592] touchesBegan
2019-01-15 12:00:48.088426+0800 GCD_Refrence[1653:94592] <Person: 0x6000010f4a20>
2019-01-15 12:00:48.088664+0800 GCD_Refrence[1653:94592] Person dealloc

4)

//代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *per = [[Person alloc] init];
    __weak Person *weakPer = per;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakPer);
        });
    });
    
    NSLog(@"touchesBegan");
}

//列印

2019-01-15 12:01:42.122836+0800 GCD_Refrence[1672:95546] touchesBegan
2019-01-15 12:01:42.123038+0800 GCD_Refrence[1672:95546] Person dealloc
2019-01-15 12:01:45.123256+0800 GCD_Refrence[1672:95546] (null)

5)

//代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *per = [[Person alloc] init];
    __weak Person *weakPer = per;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
        NSLog(@"%@", weakPer);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
            NSLog(@"%@", per);
        });
    });
    
    NSLog(@"touchesBegan");
}

//列印

2019-01-15 12:02:50.591355+0800 GCD_Refrence[1693:96581] touchesBegan
2019-01-15 12:02:51.685830+0800 GCD_Refrence[1693:96581] <Person: 0x6000033a4470>
2019-01-15 12:02:53.686541+0800 GCD_Refrence[1693:96581] <Person: 0x6000033a4470>
2019-01-15 12:02:53.686810+0800 GCD_Refrence[1693:96581] Person dealloc

6)

//代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *per = [[Person alloc] init];
    __weak Person *weakPer = per;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
        NSLog(@"%@", per);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakPer);
        });
    });
    
    NSLog(@"touchesBegan");
}

//列印

2019-01-15 12:03:47.349637+0800 GCD_Refrence[1715:97486] touchesBegan
2019-01-15 12:03:48.447971+0800 GCD_Refrence[1715:97486] <Person: 0x600000163900>
2019-01-15 12:03:48.448271+0800 GCD_Refrence[1715:97486] Person dealloc
2019-01-15 12:03:50.448553+0800 GCD_Refrence[1715:97486] (null)

7)

//代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    Person *per = [[Person alloc] init];
    __weak Person *weakPer = per;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*1), dispatch_get_main_queue(), ^{
        NSLog(@"%@", per);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC*2), dispatch_get_main_queue(), ^{
            NSLog(@"%@", per);
        });
    });
    
    NSLog(@"touchesBegan");
}

//列印

2019-01-15 12:04:37.584067+0800 GCD_Refrence[1735:98332] touchesBegan
2019-01-15 12:04:38.679922+0800 GCD_Refrence[1735:98332] <Person: 0x600003bd7570>
2019-01-15 12:04:40.876560+0800 GCD_Refrence[1735:98332] <Person: 0x600003bd7570>
2019-01-15 12:04:40.876803+0800 GCD_Refrence[1735:98332] Person dealloc

分析:

<1>ARC環境下,使用GCD時,系統自動將block對象從棧區copy到堆區;

<2>根據以上列印結果,發現如果是單純的對per進行強引用,則延時3秒後per對象才銷毀;如果是弱引用,則立即銷毀,再次使用時為空(此時已經被釋放);

<3>如果既對per強引用又有弱引用,在嵌套的GCD使用中,始終以強引用為準,弱引用不影響強引用;

 

結論:同一個auto類型的局部的實例對象,既有強引用,也有弱引用,以強引用為準;

 

GitHub


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

-Advertisement-
Play Games
更多相關文章
  • 狂神聲明 : 文章均為自己的學習筆記 , 轉載一定註明出處 ; 編輯不易 , 防君子不防小人~共勉 ! mysql學習【第1篇】:初識MySQL 只會寫代碼的是碼農;學好資料庫,基本能混口飯吃;在此基礎上再學好操作系統和電腦網路,就能當一個不錯的程式員。如果能再把離散數學、數字電路、體繫結構、數據 ...
  • 字元串函數 是最常用的的一種函數,在一個具體應用中通常會綜合幾個甚至幾類函數來實現相應的應用: 1、LOWER(column|str):將字元串參數值轉換為全小寫字母後返回 2、UPPER(column|str):將字元串參數值轉換為全大寫字母後返回 3、CONCAT(column|str1, co ...
  • 一、下載 1.1 官方下載地址:https://dev.mysql.com/downloads/mysql/ ,點擊Download 1.2 點擊 No thanks,just start my download 二、安裝準備 2.1 將下載的ZIP包解壓到安裝目錄 2.2 將解壓文件夾下的bin路 ...
  • 查詢:一:查詢所有數據select * from Info 查所有數據select Code,Name from Info 查特定列二:根據條件查select * from Info where Code='p001' 一個條件查詢select * from Info where Code='p00 ...
  • 1、MySQL的複製原理以及流程 基本原理流程,3個線程以及之間的關聯; 主:binlog線程——記錄下所有改變了資料庫數據的語句,放進master上的binlog中; 從:io線程——在使用start slave 之後,負責從master上拉取 binlog 內容,放進 自己的relay log中 ...
  • 之前也面試別人,現在輪到自己找工作,怎麼說呢,現在輪到自己出去面試,怎麼說呢,其實還是挺緊張的,原以為自己不會因此緊張或者焦慮,實際上,還是有的,在沒找到合適的工作的時候,甚至晚上有點睡不著覺,總覺著有什麼事壓在心頭,睡覺都不安心。既然睡不著,那還是看看資料吧,我有個習慣,睡前看點問題,第二天早上就 ...
  • Android工程項目打包成SDK 在app的gradle下進行設置: (1)將apply plugin: ‘com.android.application’ 改為apply plugin: ‘com.android.library’; (2)註釋或刪掉applicationId "com.libr ...
  • 前言 移動研發火熱不停,越來越多人開始學習android開發。但很多人感覺入門容易成長很難,對未來比較迷茫,不知道自己技能該怎麼提升,到達下一階段需要補充哪些內容。市面上也多是談論知識圖譜,缺少體系和成長節奏感,特此編寫一份android研發進階之路,希望能對大家有所幫助。 這篇文章里,我們只談技術 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...