【iOS】KVC原理及自定義

目錄

前言

KVC定義及API

KVC的使用

基本類型

集合類型

訪問非對象類型——結構體

集合操作符

層層嵌套

KVC底層原理

設值過程

取值過程

自定義KVC

setter方法

getter方法

KVC異常小技巧

自動轉換類型

設置空值

未定義的key


前言

在平時的開發中我們經常用到KVC賦值取值、字典轉模型,這篇文章我們來探索一下KVC的底層原理。

KVC定義及API

KVC(Key-Value Coding)是利用NSKeyValueCoding 非正式協議實現的一種機制,對象采用這種機制來提供對其屬性的間接訪問。

NSKeyValueCodingFoundation框架下:

  • KVC是通過對NSObject的擴展來實現的 —— 只要繼承了NSObject的類都可以使用KVC

  • NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet等也遵守KVC協議

  • 除少數類型(結構體)以外都可以使用KVC

KVC常用方法:

// 通過 key 設值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// 通過 key 取值
- (nullable id)valueForKey:(NSString *)key;
// 通過 keyPath 設值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
// 通過 keyPath 取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;

NSKeyValueCoding類別的其它方法:

// 默認為YES。 如果返回為YES,如果沒有找到 set<Key> 方法的話, 會按照_key, _isKey, key, isKey的順序搜索成員變量, 返回NO則不會搜索
+ (BOOL)accessInstanceVariablesDirectly;
// 鍵值驗證, 可以通過該方法檢驗鍵值的正確性, 然后做出相應的處理
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
// 如果key不存在, 并且沒有搜索到和key有關的字段, 會調用此方法, 默認拋出異常。兩個方法分別對應 get 和 set 的情況
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// setValue方法傳 nil 時調用的方法
// 注意文檔說明: 當且僅當 NSNumber 和 NSValue 類型時才會調用此方法 
- (void)setNilValueForKey:(NSString *)key;
// 一組 key對應的value, 將其轉成字典返回, 可用于將 Model 轉成字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

KVC的使用

關于KVC的使用,其實筆者在之前已經有過很詳細的分析了(詳情請見博客【iOS】KVC),但是這里由于要分析KVC的源碼,還是把基本的接口和用法再整理一遍

首先定義兩個類方便后續使用

基本類型

對于基本類型KVC的使用,要注意NSInteger這類的屬性賦值時要轉成NSNumberNSString

打印的結果如下:

集合類型

打印結果:

訪問非對象類型——結構體

  • 對于非對象類型的賦值總是把它先轉成NSValue類型再進行存儲

  • 取值時轉成對應類型后再使用

打印結果:

集合操作符

聚合操作符

  • @avg: 返回操作對象指定屬性的平均值

  • @count: 返回操作對象指定屬性的個數

  • @max: 返回操作對象指定屬性的最大值

  • @min: 返回操作對象指定屬性的最小值

  • @sum: 返回操作對象指定屬性值之和

數組操作符

  • @distinctUnionOfObjects: 返回操作對象指定屬性的集合--去重

  • @unionOfObjects: 返回操作對象指定屬性的集合

嵌套操作符

  • @distinctUnionOfArrays: 返回操作對象(嵌套集合)指定屬性的集合--去重,返回的是 NSArray

  • @unionOfArrays: 返回操作對象(集合)指定屬性的集合

  • @distinctUnionOfSets: 返回操作對象(嵌套集合)指定屬性的集合--去重,返回的是 NSSe

層層嵌套

通過forKeyPath對實例變量(student)進行取值賦值通過forKeyPath對實例變量(student)進行取值賦值

打印結果:

KVC底層原理

設值過程

KVC底層其實就是一個按順序查找的過程:

  1. set<Key>:_set<Key>:順序查找對象中是否有對應的方法

  2. 判斷accessInstanceVariablesDirectly結果

    1. 為YES時按照_<key>_is<Key><key>is<Key>的順序查找成員變量,找到了就賦值;找不到就跳轉第3步

    2. 為NO時跳轉第3步

  3. 調用setValue:forUndefinedKey:。默認情況下會引發一個異常,但是繼承于NSObject的子類可以重寫該方法就可以避免崩潰并做出相應措施

取值過程

取值過程是類似的流程:

  1. 按照get<Key><key>is<Key>_<key>順序查找對象中是否有對應的方法

  2. 查找是否有countOf<Key>objectIn<Key>AtIndex: 方法(對應于NSArray類定義的原始方法)以及<key>AtIndexes: 方法(對應于NSArray方法objectsAtIndexes:)

    1. 如果找到其中的第一個(countOf<Key>),再找到其他兩個中的至少一個,則創建一個響應所有 NSArray方法的代理集合對象,并返回該對象(即要么是countOf<Key> + objectIn<Key>AtIndex:,要么是countOf<Key> + <key>AtIndexes:,要么是countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:)

    2. 如果沒有找到,跳轉到第3步

  3. 查找名為countOf<Key>enumeratorOf<Key>memberOf<Key>這三個方法(對應于NSSet類定義的原始方法)

    1. 如果找到這三個方法,則創建一個響應所有NSSet方法的代理集合對象,并返回該對象

    2. 如果沒有找到,跳轉到第4步

  4. 判斷accessInstanceVariablesDirectly,為YES時按照_<key>_is<Key><key>is<Key>的順序查找成員變量,找到了就取值

  5. 判斷取出的屬性值

    1. 屬性值是對象,直接返回

    2. 屬性值不是對象,但是可以轉化為NSNumber類型,則將屬性值轉化為NSNumber 類型返回

    3. 屬性值不是對象,也不能轉化為NSNumber類型,則將屬性值轉化為NSValue類型返回

  6. 調用valueForUndefinedKey:.默認情況下會引發一個異常,但是繼承于NSObject的子類可以重寫該方法就可以避免崩潰并做出相應措施

自定義KVC

我們可以自定義KVC

setter方法

首先在頭文件中加入這個方法,在.m文件中引入<objc/runtime.h>這個庫

然后開始實現流程,大致流程如下:

- (void)cj_setValue:(nullable id)value forKey:(NSString *)key {// 1:非空判斷一下if (key == nil || key.length == 0) return;// 2:找到相關方法 set<Key> _set<Key> setIs<Key>// key 要大寫NSString *Key = key.capitalizedString;// 拼接方法NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];if ([self cj_performSelectorWithMethodName:setKey value:value]) {NSLog(@"*********%@**********",setKey);return;} else if ([self cj_performSelectorWithMethodName:_setKey value:value]) {NSLog(@"*********%@**********",_setKey);return;} else if ([self cj_performSelectorWithMethodName:setIsKey value:value]) {NSLog(@"*********%@**********",setIsKey);return;}NSString *undefinedMethodName = @"setValue:forUndefinedKey:";IMP undefinedIMP = class_getMethodImplementation([self class], NSSelectorFromString(undefinedMethodName));// 3:判斷是否能夠直接賦值實例變量if (![self.class accessInstanceVariablesDirectly]) {if (undefinedIMP) {[self cj_performSelectorWithMethodName:undefinedMethodName value:value key:key];} else {@throw [NSException exceptionWithName:@"TCJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil];}return;}// 4.找相關實例變量進行賦值// 4.1 定義一個收集實例變量的可變數組NSMutableArray *mArray = [self getIvarListName];NSString *_key = [NSString stringWithFormat:@"_%@",key];NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];NSString *isKey = [NSString stringWithFormat:@"is%@",Key];// _<key> _is<Key> <key> is<Key>if ([mArray containsObject:_key]) {// 4.2 獲取相應的 ivarIvar ivar = class_getInstanceVariable([self class], _key.UTF8String);// 4.3 對相應的 ivar 設置值object_setIvar(self , ivar, value);return;} else if ([mArray containsObject:_isKey]) {Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);object_setIvar(self , ivar, value);return;} else if ([mArray containsObject:key]) {Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);object_setIvar(self , ivar, value);return;} else if ([mArray containsObject:isKey]) {Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);object_setIvar(self , ivar, value);return;}
?// 5:如果找不到相關實例if (undefinedIMP) {[self cj_performSelectorWithMethodName:undefinedMethodName value:value key:key];} else {@throw [NSException exceptionWithName:@"TCJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil];}
}

getter方法

第一步是加入庫和聲明方法,和setter方法相同,實現方法的過程如下:

- (nullable id)cj_valueForKey:(NSString *)key {// 1:刷選key 判斷非空if (key == nil ?|| key.length == 0) return nil;
?// 2:找到相關方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex// key 要大寫NSString *Key = key.capitalizedString;// 拼接方法NSString *getKey = [NSString stringWithFormat:@"get%@",Key];NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"if ([self respondsToSelector:NSSelectorFromString(getKey)]) {return [self performSelector:NSSelectorFromString(getKey)];} else if ([self respondsToSelector:NSSelectorFromString(key)]) {return [self performSelector:NSSelectorFromString(key)];} else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]) {if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];for (int i = 0; i<num-1; i++) {num = (int)[self performSelector:NSSelectorFromString(countOfKey)];}for (int j = 0; j<num; j++) {id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];[mArray addObject:objc];}return mArray;}}
#pragma clang diagnostic popNSString *undefinedMethodName = @"valueForUndefinedKey:";IMP undefinedIMP = class_getMethodImplementation([self class], NSSelectorFromString(undefinedMethodName));// 3:判斷是否能夠直接賦值實例變量if (![self.class accessInstanceVariablesDirectly]) {if (undefinedIMP) {
?
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"return [self performSelector:NSSelectorFromString(undefinedMethodName) withObject:key];
#pragma clang diagnostic pop} else {@throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil];}}// 4.找相關實例變量進行賦值// 4.1 定義一個收集實例變量的可變數組NSMutableArray *mArray = [self getIvarListName];// _<key> _is<Key> <key> is<Key>// _name -> _isName -> name -> isNameNSString *_key = [NSString stringWithFormat:@"_%@",key];NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];NSString *isKey = [NSString stringWithFormat:@"is%@",Key];if ([mArray containsObject:_key]) {Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);return object_getIvar(self, ivar);;} else if ([mArray containsObject:_isKey]) {Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);return object_getIvar(self, ivar);;} else if ([mArray containsObject:key]) {Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);return object_getIvar(self, ivar);;} else if ([mArray containsObject:isKey]) {Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);return object_getIvar(self, ivar);;}
?if (undefinedIMP) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"return [self performSelector:NSSelectorFromString(undefinedMethodName) withObject:key];
#pragma clang diagnostic pop} else {@throw [NSException exceptionWithName:@"FXUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key %@.", self, NSStringFromSelector(_cmd), key] userInfo:nil];}
?return nil;
}
過程幾個用到的方法封裝如下://安全調用方法及傳兩個參數
- (BOOL)cj_performSelectorWithMethodName:(NSString *)methodName value:(id)value key:(id)key {if ([self respondsToSelector:NSSelectorFromString(methodName)]) {#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"[self performSelector:NSSelectorFromString(methodName) withObject:value withObject:key];
#pragma clang diagnostic popreturn YES;}return NO;
} 
?
//安全調用方法及傳一個參數
- (BOOL)cj_performSelectorWithMethodName:(NSString *)methodName value:(id)value {if ([self respondsToSelector:NSSelectorFromString(methodName)]) {#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic popreturn YES;}return NO;
}
?
//安全調用方法
- (id)performSelectorWithMethodName:(NSString *)methodName {if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"return [self performSelector:NSSelectorFromString(methodName)];
#pragma clang diagnostic pop}return nil;
}
?
//取成員變量
- (NSMutableArray *)getIvarListName {NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];unsigned int count = 0;Ivar *ivars = class_copyIvarList([self class], &count);for (int i = 0; i<count; i++) {Ivar ivar = ivars[i];const char *ivarNameChar = ivar_getName(ivar);NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];NSLog(@"ivarName == %@",ivarName);[mArray addObject:ivarName];}free(ivars);return mArray;
}

KVC異常小技巧

自動轉換類型

  • int類型賦值會自動轉成__NSCFNumber

  • 用結構體類型賦值會自動轉成NSConcreteValue

設置空值

有時候在設值時設置空值,可以通過重寫setNilValueForKey來監聽,但是setNilValueForKey只對NSNumber類型有效

// Int類型設置nil
[person setValue:nil forKey:@"age"];
// NSString類型設置nil
[person setValue:nil forKey:@"subject"];
?
@implementation TCJPerson
?
- (void)setNilValueForKey:(NSString *)key {NSLog(@"設置 %@ 是空值", key);
}
?
@end
?

未定義的key

未定義的key可以用setValue:forUndefinedKey:valueForUndefinedKey:來監聽

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/93249.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/93249.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/93249.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

完整設計 之 智能合約系統:主題約定、代理協議和智能合約 (臨時命名)----PromptPilot (助手)答問之2

摘要&#xff08;CSDN的AI助手生成的&#xff09;智能合約系統架構設計摘要本設計構建了一個多層次智能合約系統&#xff0c;包含150字以內的核心架構&#xff1a;三級架構體系&#xff1a;元級&#xff08;序分&#xff09;&#xff1a;MetaModel合約定義系統核心原則模型級&a…

Java基礎 8.16

1.final關鍵字基本介紹final中文意思&#xff1a;最后的&#xff0c;最終的final可以修飾類、屬性、方法和局部變量在某些情況下&#xff0c;程序員可能有以下需求&#xff0c;就會使用到final當不希望類被繼承時,可以用final修飾當不希望父類的某個方法被子類覆蓋/重寫(overri…

YOLOv8目標檢測網絡結構理論

目錄 YOLOv8的網絡結構圖&#xff1a; Backbone 卷積塊&#xff08;Conv Block&#xff09; Conv2d層 BatchNorm2d層 SiLU激活函數 瓶頸塊(Bottleneck Block) C2f 模塊結構 Neck SPPF(空間金字塔池化快速) PAN - FPN Head 結構1.卷積層和激活函數: 2.預測層(Predi…

docker部署hadoop集群

Docker部署hadoop集群下載資源構建鏡像啟動容器搭建集群配置ssh免密節點職責安排修改配置文件啟動集群測試上傳下載執行wordcount程序補充配置歷史服務器日志聚集單節點啟動Java客戶端使用HDFSMapReduce下載資源 java華為鏡像下載地址&#xff1a;Index of java-local/jdk (hu…

常用的T-SQL命令

文章目錄1. 數據庫操作2. 表操作3. 數據插入、更新、刪除4. 數據查詢5. 存儲過程6. 事務處理7、如何使用T-SQL在表中設置主鍵和外鍵&#xff1f;1. 設置主鍵&#xff08;PRIMARY KEY&#xff09;方法1&#xff1a;創建表時定義主鍵方法2&#xff1a;通過ALTER TABLE添加主鍵2. …

C++面試題及詳細答案100道( 31-40 )

《前后端面試題》專欄集合了前后端各個知識模塊的面試題&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

給純小白的 Python 操作 Excel 筆記

&#x1f9f0; 1. 先裝工具電腦鍵盤按 Win R&#xff0c;輸入 cmd&#xff0c;回車&#xff0c;把下面一行粘進去回車&#xff0c;等它跑完。 bashpip install openpyxl——————————————————&#x1f6e0;? 2. 打開一個空白的 Excel 打開 Jupyter Notebook…

HTML 常用屬性介紹

目錄 HTML 屬性 HTML 屬性速查表 一、通用屬性&#xff08;所有元素適用&#xff09; 二、鏈接與引用相關屬性 三、表單與輸入控件屬性 四、媒體與多媒體屬性 五、事件屬性&#xff08;常用 JavaScript 事件&#xff09; 六、其他常用屬性 核心通用屬性 id 屬性 cla…

HTML5練習代碼集:學習與實踐核心特性

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;HTML5作為新一代網頁標準&#xff0c;對Web開發提供了更豐富的功能和工具。本練習代碼集專門針對HTML5的核心特性&#xff0c;包括語義化標簽、離線存儲、多媒體支持、圖形繪制等&#xff0c;以及CSS3的3D效果和…

【RH134知識點問答題】第 10 章:控制啟動過程

目錄 1. 請簡要說明 RHEL9 的啟動過程。 2. 系統重啟和關機的命令分別是什么? 3. Systemd target 是什么&#xff1f; 4. 重置丟失的 root 密碼需要哪些步驟&#xff1f; 5. 如何讓系統日志在重啟后持久保留 1. 請簡要說明 RHEL9 的啟動過程。 答&#xff1a;①開機自檢…

Apollo10.0學習之固態雷達與IMU的外參標定

固態雷達&#xff08;如Livox、禾賽等非旋轉式激光雷達&#xff09;與IMU&#xff08;慣性測量單元&#xff09;的外參標定&#xff08;Extrinsic Calibration&#xff09;是自動駕駛、機器人定位&#xff08;如LIO-SAM、FAST-LIO&#xff09;的關鍵步驟。1. 標定原理 外參標定…

HTML5實現古典音樂網站源碼模板1

文章目錄 1.設計來源1.1 網站首頁1.2 古典音樂界面1.3 著名人物界面1.4 古典樂器界面1.5 歷史起源界面 2.效果和源碼2.1 動態效果2.2 源代碼 源碼下載萬套模板&#xff0c;程序開發&#xff0c;在線開發&#xff0c;在線溝通 作者&#xff1a;xcLeigh 文章地址&#xff1a;http…

40 C++ STL模板庫9-容器2-vector

C STL模板庫9-容器2-vector 文章目錄C STL模板庫9-容器2-vector一、基礎概念1. 類型成員&#xff08;Type Members&#xff09;2. 模板參數二、構造函數1. 語法2. 示例三、元素訪問1. 函數說明2. 示例代碼四、容量操作1. 函數說明2. 關鍵點說明3. 關鍵操作解析4. 操作示例五、修…

GPT-5系列文章2——新功能、測試與性能基準全解析

引言 2025年8月&#xff0c;OpenAI正式發布了其新一代旗艦模型GPT-5。與業界此前期待的AGI(人工通用智能)突破不同&#xff0c;GPT-5更像是OpenAI對現有技術的一次深度整合與用戶體驗優化。本文將全面解析GPT-5的新特性、實際測試表現以及官方發布的基準數據&#xff0c;幫助開…

利用cursor+MCP實現瀏覽器自動化釋放雙手

小伙伴們&#xff0c;我們今天利用cursorMCP實現瀏覽器自動化&#xff0c;釋放雙手&#xff0c;工作效率嘎嘎提升&#xff01;前期準備&#xff1a;安裝node.js網址&#xff1a;https://nodejs.org/zh-cn下載下來安裝即可。 下載browser-tools-mcp擴展程序&#xff1a;下載擴展…

指針/邊界索引混淆梳理

在處理數組/鏈表等數據結構時&#xff0c;時常混淆長度和指針序號。處理技巧&#xff1a;使用0-base索引。則區間長度 rightIndex - LeftIndex 1總長度 lastIndex - firstIndex 1鏈表創建一個dummy節點&#xff0c;添加到head前&#xff0c;則可認為從索引0開始。末尾指針判…

LeetCode 刷題【43. 字符串相乘】

43. 字符串相乘 自己做 解1&#xff1a;矩陣計數 class Solution { public:string multiply(string num1, string num2) {int len1 num1.size();int len2 num2.size();if (num1[0] 0 || num2[0] 0) //結果為0的情況return "0";//存儲計算過程的矩陣vector…

NLP數據增強方法及實現-A

目錄 詞替換 主要參考&#xff1a;paddlenlp/data_aug模塊 詞替換數據增強策略也即將句子中的詞隨機替換為其他單詞進行數據增強&#xff0c;這里我們將介紹如何使用paddlenlp.dataaug.WordSubstitute進行詞級別替換的數據增強。 WordSubstitute 參數介紹&#xff1a;aug_ty…

EhViewer安卓ios全版本類下載安裝工具的完整路徑解析

開發一款類似EhViewer的下載安裝工具&#xff08;集下載管理、應用部署等功能于一體&#xff09;&#xff0c;需要經歷從需求錨定到落地發布的系統性流程。以下從需求拆解到技術落地的全維度指南&#xff0c;將幫你理清開發脈絡&#xff0c;避開常見陷阱。安裝 GitHub - huangy…

MySQL 主鍵詳解:作用與使用方法

在 MySQL 數據庫中&#xff0c;主鍵&#xff08;Primary Key&#xff09; 是表結構設計中最重要的約束之一。它不僅是數據唯一性的保障&#xff0c;也是多表關聯、查詢優化的核心工具。本文將從 主鍵的作用 和 主鍵的用法 兩個方面進行講解&#xff0c;并配合代碼示例幫助理解一…