Runtime 全方位裝逼指南

来源:http://www.cnblogs.com/lurenq/archive/2017/11/13/7820042.html
-Advertisement-
Play Games

Runtime是什麼?見名知意,其概念無非就是“因為 Objective-C 是一門動態語言,所以它需要一個運行時系統……這就是 Runtime 系統”云云。對博主這種菜鳥而言,Runtime 在實際開發中,其實就是一組C語言的函數。胡適說:“多研究些問題,少談些主義”,雲山霧罩的概念聽多了總是容易 ...


Runtime是什麼?見名知意,其概念無非就是“因為 Objective-C 是一門動態語言,所以它需要一個運行時系統……這就是 Runtime 系統”云云。對博主這種菜鳥而言,Runtime 在實際開發中,其實就是一組C語言的函數。胡適說:“多研究些問題,少談些主義”,雲山霧罩的概念聽多了總是容易頭暈,接下來我們直接從代碼入手學習 Runtime。

1、由objc_msgSend說開去

Objective-C 中的方法調用,不是簡單的方法調用,而是發送消息,也就是說,其實 [receiver message] 會被編譯器轉化為: objc_msgSend(receiver, selector),何以證明?新建一個類 MyClass,其.m文件如下:

1 2 3 4 5 6 7 8 9 10 11 #import "MyClass.h" @implementation MyClass -(instancetype)init{     if (self = [super init]) {         [self showUserName];     }     return self; } -(void)showUserName{     NSLog(@"Dave Ping"); }

使用 clang 重寫命令:

1 $ clang -rewrite-objc MyClass.m

然後在同一目錄下會多出一個 MyClass.cpp 文件,雙擊打開,可以看到 init 方法已經被編譯器轉化為下麵這樣:

1 2 3 4 5 6 static instancetype _I_MyClass_init(MyClass * self, SEL _cmd) {     if (self = ((MyClass *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MyClass"))}, sel_registerName("init"))) {         ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"));     }     return self; }

我們要找的就是它:

1 ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"))

objc_msgSend 函數被定義在 objc/message.h 目錄下,其函數原型是醬紫滴:

1 OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )

該函數有兩個參數,一個 id 類型,一個 SEL 類型。

2、SEL

SEL 被定義在 objc/objc.h 目錄下:

1 typedef struct objc_selector *SEL;

其實它就是個映射到方法的C字元串,你可以用 Objective-C 編譯器命令 @selector() 或者 Runtime 系統的 sel_registerName 函數來獲得一個 SEL 類型的方法選擇器。

3、id

與 SEL 一樣,id 也被定義在 objc/objc.h 目錄下:

1 typedef struct objc_object *id;

id 是一個結構體指針類型,它可以指向 Objective-C 中的任何對象。objc_object 結構體定義如下:

1 struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};

我們通常所說的對象,就長這個樣子,這個結構體只有一個成員變數 isa,對象可以通過 isa 指針找到其所屬的類。isa 是一個 Class 類型的成員變數,那麼 Class 又是什麼呢?

4、Class

Class 也是一個結構體指針類型:

1 typedef struct objc_class *Class;

objc_class 結構體是醬紫滴:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct objc_class {     Class isa  OBJC_ISA_AVAILABILITY; #if !__OBJC2__     Class super_class   OBJC2_UNAVAILABLE;     const char *name   OBJC2_UNAVAILABLE;     long version     OBJC2_UNAVAILABLE;     long info     OBJC2_UNAVAILABLE;     long instance_size   OBJC2_UNAVAILABLE;     struct objc_ivar_list *ivars  OBJC2_UNAVAILABLE;     struct objc_method_list **methodLists  OBJC2_UNAVAILABLE;     struct objc_cache *cache    OBJC2_UNAVAILABLE;     struct objc_protocol_list *protocols   OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;

我們通常說的類就長這樣子:

  • ·Class 也有一個 isa 指針,指向其所屬的元類(meta)。

  • ·super_class:指向其超類。

  • ·name:是類名。

  • ·version:是類的版本信息。

  • ·info:是類的詳情。

  • ·instance_size:是該類的實例對象的大小。

  • ·ivars:指向該類的成員變數列表。

  • ·methodLists:指向該類的實例方法列表,它將方法選擇器和方法實現地址聯繫起來。methodLists 是指向 ·objc_method_list 指針的指針,也就是說可以動態修改 *methodLists 的值來添加成員方法,這也是 Category 實現的原理,同樣解釋了 Category 不能添加屬性的原因。

  • ·cache:Runtime 系統會把被調用的方法存到 cache 中(理論上講一個方法如果被調用,那麼它有可能今後還會被調用),下次查找的時候效率更高。

  • ·protocols:指向該類的協議列表。

說到這裡有點亂了,我們來捋一下,當我們調用一個方法時,其運行過程大致如下:

首先,Runtime 系統會把方法調用轉化為消息發送,即 objc_msgSend,並且把方法的調用者,和方法選擇器,當做參數傳遞過去.

此時,方法的調用者會通過 isa 指針來找到其所屬的類,然後在 cache 或者 methodLists 中查找該方法,找得到就跳到對應的方法去執行。

如果在類中沒有找到該方法,則通過 super_class 往上一級超類查找(如果一直找到 NSObject 都沒有找到該方法的話,這種情況,我們放到後面消息轉發的時候再說)。

前面我們說 methodLists 指向該類的實例方法列表,實例方法即-方法,那麼類方法(+方法)存儲在哪兒呢?類方法被存儲在元類中,Class 通過 isa 指針即可找到其所屬的元類。

1396900-1b94ff9a3905ba68.jpg

上圖實線是 super_class 指針,虛線是 isa 指針。根元類的超類是NSObject,而 isa 指向了自己。NSObject 的超類為 nil,也就是它沒有超類。

5、使用objc_msgSend

前面我們使用 clang 重寫命令,看到 Runtime 是如何將方法調用轉化為消息發送的。我們也可以依樣畫葫蘆,來學習使用一下 objc_msgSend。新建一個類 TestClass,添加如下方法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -(void)showAge{     NSLog(@"24"); } -(void)showName:(NSString *)aName{     NSLog(@"name is %@",aName); } -(void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight{     NSLog(@"size is %.2f * %.2f",aWidth, aHeight); } -(float)getHeight{     return 187.5f; } -(NSString *)getInfo{     return @"Hi, my name is Dave Ping, I'm twenty-four years old in the year, I like apple, nice to meet you."; }

我們可以像下麵這樣,使用 objc_msgSend 依次調用這些方法:

1 2 3 4 5 6 7 8 TestClass *objct = [[TestClass alloc] init]; ((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("showAge")); ((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("showName:"), @"Dave Ping"); ((void (*) (id, SEL, float, float)) objc_msgSend) (objct, sel_registerName("showSizeWithWidth:andHeight:"), 110.5f, 200.0f); float f = ((float (*) (id, SEL)) objc_msgSend_fpret) (objct, sel_registerName("getHeight")); NSLog(@"height is %.2f",f); NSString *info = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getInfo")); NSLog(@"%@",info);

也許你已經註意到,objc_msgSend 在使用時都被強制轉換了一下,這是因為 objc_msgSend 函數可以hold住各種不同的返回值以及多個參數,但預設情況下是沒有參數和返回值的。如果我們把調用 showAge 方法改成這樣:

1 objc_msgSend(objct, sel_registerName("showAge"));

Xcode 就會報錯:

1 Too many arguments to function call, expected 0, have 2.

完整的 objc_msgSend 使用代碼在這裡

6、objc_msgSendSuper

編譯器會根據情況在 objc_msgSend,objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper_stret 或 objc_msgSend_fpret 五個方法中選擇一個來調用。如果消息是傳遞給超類,那麼會調用 objc_msgSendSuper 方法,如果消息返回值是數據結構,就會調用 objc_msgSendSuper_stret 方法,如果返回值是浮點數,則調用 objc_msgSend_fpret 方法。

這裡我們重點說一下 objc_msgSendSuper,objc_msgSendSuper 函數原型如下:

1 OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

當我們調用 [super selector] 時,Runtime 會調用 objc_msgSendSuper 方法,objc_msgSendSuper 方法有兩個參數,super 和 op,Runtime 會把 selector 方法選擇器賦值給 op。而 super 是一個 objc_super 結構體指針,objc_super 結構體定義如下:

1 2 3 4 5 6 7 8 9 10 11 12 struct objc_super {     /// Specifies an instance of a class.     __unsafe_unretained id receiver;     /// Specifies the particular superclass of the instance to message. #if !defined(__cplusplus)  &&  !__OBJC2__     /* For compatibility with old objc-runtime.h header */     __unsafe_unretained Class class; #else     __unsafe_unretained Class super_class; #endif     /* super_class is the first class to search */ };

Runtime 會創建一個 objc_spuer 結構體變數,將其地址作為參數(super)傳遞給 objc_msgSendSuper,並且將 self 賦值給 receiver:super—>receiver=self。

舉個慄子,問下麵的代碼輸出什麼:

1 2 3 4 5 6 7 8 9 10 11 12 @implementation Son : Father - (id)init {     self = [super init];     if (self)     {         NSLog(@"%@", NSStringFromClass([self class]));         NSLog(@"%@", NSStringFromClass([super class]));     }     return self; } @end

答案是全部輸出 Son。

使用 clang 重寫命令,發現上述代碼被轉化為:

1 2 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")))); NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));

當調用 [super class] 時,會轉換成 objc_msgSendSuper 函數:

  • 第一步先構造 objc_super 結構體,結構體第一個成員就是 self。第二個成員是 (id)class_getSuperclass(objc_getClass(“Son”)).

  • 第二步是去 Father 這個類里去找 - (Class)class,沒有,然後去 NSObject 類去找,找到了。最後內部是使用 objc_msgSend(objc_super->receiver, @selector(class)) 去調用,此時已經和 [self class] 調用相同了,所以兩個輸出結果都是 Son。

7、對象關聯

對象關聯允許開發者對已經存在的類在 Category 中添加自定義的屬性:

1 OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

·object 是源對象

·value 是被關聯的對象

·key 是關聯的鍵,objc_getAssociatedObject 方法通過不同的 key 即可取出對應的被關聯對象

·policy 是一個枚舉值,表示關聯對象的行為,從命名就能看出各個枚舉值的含義:

1 2 3 4 5 6 7 8 9 10 11 typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {     OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */     OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.                                             *   The association is not made atomically. */     OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.                                             *   The association is not made atomically. */     OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.                                             *   The association is made atomically. */     OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.                                             *   The association is made atomically. */ };

要取出被關聯的對象使用 objc_getAssociatedObject 方法即可,要刪除一個被關聯的對象,使用 objc_setAssociatedObject 方法將對應的 key 設置成 nil 即可:

1 objc_setAssociatedObject(self, associatedKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);

objc_removeAssociatedObjects 方法將會移除源對象中所有的關聯對象.

舉個慄子,假如我們要給 UIButton 添加一個監聽單擊事件的 block 屬性,新建 UIButton 的 Category,其.m文件如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #import "UIButton+ClickBlock.h" #import static const void *associatedKey = "associatedKey"; @implementation UIButton (ClickBlock) //Category中的屬性,只會生成setter和getter方法,不會生成成員變數 -(void)setClick:(clickBlock)click{     objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);     [self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];     if (click) {         [self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];     } } -(clickBlock)click{     return objc_getAssociatedObject(self, associatedKey); } -(void)buttonClick{     if (self.click) {         self.click();     } } @end

然後在代碼中,就可以使用 UIButton 的屬性來監聽單擊事件了:

1 2 3 4 5 6 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = self.view.bounds; [self.view addSubview:button]; button.click = ^{     NSLog(@"buttonClicked"); };

完整的對象關聯代碼點這裡

8、自動歸檔

博主在學習 Runtime 之前,歸檔的時候是醬紫寫的:

1 2 3 4 5 6 7 8 9 10 11 - (void)encodeWithCoder:(NSCoder *)aCoder{     [aCoder encodeObject:self.name forKey:@"name"];     [aCoder encodeObject:self.ID forKey:@"ID"]; } - (id)initWithCoder:(NSCoder *)aDecoder{     if (self = [super init]) {         self.ID = [aDecoder decodeObjectForKey:@"ID"];         self.name = [aDecoder decodeObjectForKey:@"name"];     }     return self; }

那麼問題來了,如果當前 Model 有100個屬性的話,就需要寫100行這種代碼:

1 [aCoder encodeObject:self.name forKey:@"name"];

想想都頭疼,通過 Runtime 我們就可以輕鬆解決這個問題:

1.使用 class_copyIvarList 方法獲取當前 Model 的所有成員變數.

2.使用 ivar_getName 方法獲取成員變數的名稱.

3.通過 KVC 來讀取 Model 的屬性值(encodeWithCoder:),以及給 Model 的屬性賦值(initWithCoder:).

舉個慄子,新建一個 Model 類,其.m文件如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #import "TestModel.h" #import #import @implementation TestModel - (void)encodeWithCoder:(NSCoder *)aCoder{     unsigned int outCount = 0;     Ivar *vars = class_copyIvarList([self class], &outCount);     for (int i = 0; i < outCount; i ++) {         Ivar var = vars[i];         const char *name = ivar_getName(var);         NSString *key = [NSString stringWithUTF8String:name];         // 註意kvc的特性是,如果能找到key這個屬性的setter方法,則調用setter方法         // 如果找不到setter方法,則查找成員變數key或者成員變數_key,並且為其賦值         // 所以這裡不需要再另外處理成員變數名稱的“_”首碼         id value = [self valueForKey:key];         [aCoder encodeObject:value forKey:key];     } } - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{     if (self = [super init]) {         unsigned int outCount = 0;         Ivar *vars = class_copyIvarList([self class], &outCount);         for (int i = 0; i < outCount; i ++) {             Ivar var = vars[i];             const char *name = ivar_getName(var);             NSString *key = [NSString stringWithUTF8String:name];             id value = [aDecoder decodeObjectForKey:key];             [self setValue:value forKey:key];         }     }     return self; } @end

完整的自動歸檔代碼在這裡

9、字典與模型互轉

最開始博主是這樣用字典給 Model 賦值的:

1 2 3 4 5 6 7 -(instancetype)initWithDictionary:(NSDictionary *)dict{     if (self = [super init]) {         self.age = dict[@"age"];         self.name = dict[@"name"];     }     return self; }

可想而知,遇到的問題跟歸檔時候一樣(後來使用MJExtension),這裡我們稍微來學習一下其中原理,字典轉模型的時候:

1.根據字典的 key 生成 setter 方法

2.使用 objc_msgSend 調用 setter 方法為 Model 的屬性賦值(或者 KVC)

模型轉字典的時候:

1.調用 class_copyPropertyList 方法獲取當前 Model 的所有屬性

2.調用 property_getName 獲取屬性名稱

3.根據屬性名稱生成 getter 方法

4.使用 objc_msgSend 調用 getter 方法獲取屬性值(或者 KVC)

代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #import "NSObject+KeyValues.h" #import #import @implementation NSObject (KeyValues) //字典轉模型 +(id)objectWithKeyValues:(NSDictionary *)aDictionary{     id objc = [[self alloc] init];     for (NSString *key in aDictionary.allKeys) {         id value = aDictionary[key];         /*判斷當前屬性是不是Model*/         objc_property_t property = class_getProperty(self, key.UTF8String);         unsigned int outCount = 0;         objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);         objc_property_attribute_t attribute = attributeList[0];         NSString *typeString = [NSString stringWithUTF8String:attribute.value];         if ([typeString isEqualToString:@"@\"TestModel\""]) {             value = [self objectWithKeyValues:value];         }         /**********************/         //生成setter方法,並用objc_msgSend調用         NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];         SEL setter = sel_registerName(methodName.UTF8String);   
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 調用系統圖庫: Intent intent = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(intent, REQUEST_CODE_IMAGE... ...
  • Hello Android 代碼 備註 //創建對象 //設置對話框標題 //設置圖標 //設置內容 //設置點擊返回鍵對話框不消失 //顯示對話框 控制項id.setOnClickListener{ ​ ///事件內容 } 對話框對象AlertDialog 對話框引用android.support. ...
  • Ps:7-10月 完成公司兩個app項目上架。漏掉的總結 開始慢慢補上。 一、寫一個Activity的管理類 1、單例模式,以棧(先進後出)的形式存儲Activity對象 2、給AppManager管理類添加幾個常用方法。 (1)、添加Activity對象 (2)、結束當前Activity對象,即棧 ...
  • 轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/78113868 本文出自 "【趙彥軍的博客】" 一:概述 如果不瞭解插件開發基礎的同學可以先看, "Android Studio 插件開發詳解一:入門練手" "Android Stud ...
  • 更新iOS11後,發現有些地方需要做適配,整理後按照優先順序分為以下三類: 1.單純升級iOS11後造成的變化; 2.Xcode9 打包後造成的變化; 3.iPhoneX的適配 一、單純升級iOS11後造成的變化 1. 升級後,發現某個擁有tableView的界面錯亂,組間距和contentInset ...
  • 比較基礎的一個方法。即繪製文本 使用如下: 效果; 可以看下方法: 這篇只討論第三個方法。 可以看到 4個參數,第二個、第三個參數 是float類型,實際上就是 繪製的文本的繪製參考坐標。註意這個坐標 不是文本的左上角的那個點,float y 代表 基線的Y位置。 驗證一下: 繪製一條藍色的基線,繪 ...
  • 什麼是RAC? 其實RAC就是一個簡化代碼的第三方庫ReactiveCocoa,直接用Cocoapods添加到項目里就可以, podfile 文件添加 pod 'ReactiveCocoa',然後終端 pod install ,頭文件引用: 使用RAC 1.方法 RAC最簡單的使用技巧就是對事件的監 ...
  • 自己離線打包Html5+ Runtime,通常是導入SDK的Hello實例,然後修改。在做消息通知功能時,使用push.createMessage不起作用。 首先參考Android平臺離線打包推送插件配置 需要特別註意的是AndroidManifest的配置,需要將包名替換成自己的包名 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...