Browse Source

地址列表ui添加,接口字段逻辑对接

Abel 1 year ago
parent
commit
79bef8ca7a
100 changed files with 6878 additions and 52 deletions
  1. 52 0
      Asteria.xcodeproj/project.pbxproj
  2. 11 11
      Asteria/Fuction/Category/vc/ASSearchViewController.m
  3. 10 19
      Asteria/Fuction/Category/vm/KWHisAndHotWordsViewModel.m
  4. 23 0
      Asteria/Fuction/UserCenter/Address/ASAddressListViewController.h
  5. 199 0
      Asteria/Fuction/UserCenter/Address/ASAddressListViewController.m
  6. 36 0
      Asteria/Fuction/UserCenter/Address/ASAddressViewModel.h
  7. 97 0
      Asteria/Fuction/UserCenter/Address/ASAddressViewModel.m
  8. 25 0
      Asteria/Fuction/UserCenter/Address/ASMineAddressCell.h
  9. 162 0
      Asteria/Fuction/UserCenter/Address/ASMineAddressCell.m
  10. 52 0
      Asteria/Fuction/UserCenter/Address/ASMineAddressModel.h
  11. 50 0
      Asteria/Fuction/UserCenter/Address/ASMineAddressModel.m
  12. 20 0
      Asteria/Fuction/UserCenter/Address/edit/ASEditAddressViewController.h
  13. 516 0
      Asteria/Fuction/UserCenter/Address/edit/ASEditAddressViewController.m
  14. 3 6
      Asteria/Fuction/UserCenter/Setting/ASSettingViewController.m
  15. 6 0
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/Contents.json
  16. 23 0
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/Contents.json
  17. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/编辑.png
  18. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/编辑@2x.png
  19. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/编辑@3x.png
  20. 23 0
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/Contents.json
  21. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/组 9776.png
  22. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/组 9776@2x.png
  23. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/组 9776@3x.png
  24. 23 0
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/Contents.json
  25. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/组 8657.png
  26. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/组 8657@2x.png
  27. BIN
      Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/组 8657@3x.png
  28. 15 11
      Asteria/Fuction/UserCenter/VipCenter/ASBirthdayTreatViewController.m
  29. 7 2
      Asteria/Fuction/UserManager/info/ASUserInfoManager.h
  30. 10 2
      Asteria/Fuction/UserManager/info/ASUserInfoManager.m
  31. 6 0
      Asteria/Fuction/UserManager/info/ASUserModel.h
  32. 28 0
      Asteria/Fuction/UserManager/info/ASUserModel.m
  33. 2 0
      Asteria/NetTools/ASNetApis.h
  34. 16 0
      Asteria/Third/InTableScrollView.h
  35. 27 0
      Asteria/Third/InTableScrollView.m
  36. 1 1
      Podfile.lock
  37. 21 0
      Pods/LookinServer/LICENSE
  38. 73 0
      Pods/LookinServer/README.md
  39. 40 0
      Pods/LookinServer/Src/Base/LookinIvarTrace.h
  40. 70 0
      Pods/LookinServer/Src/Base/LookinIvarTrace.m
  41. 41 0
      Pods/LookinServer/Src/Main/Server/Category/CALayer+LookinServer.h
  42. 233 0
      Pods/LookinServer/Src/Main/Server/Category/CALayer+LookinServer.m
  43. 41 0
      Pods/LookinServer/Src/Main/Server/Category/NSObject+LookinServer.h
  44. 99 0
      Pods/LookinServer/Src/Main/Server/Category/NSObject+LookinServer.m
  45. 21 0
      Pods/LookinServer/Src/Main/Server/Category/UIBlurEffect+LookinServer.h
  46. 57 0
      Pods/LookinServer/Src/Main/Server/Category/UIBlurEffect+LookinServer.m
  47. 26 0
      Pods/LookinServer/Src/Main/Server/Category/UIColor+LookinServer.h
  48. 183 0
      Pods/LookinServer/Src/Main/Server/Category/UIColor+LookinServer.m
  49. 22 0
      Pods/LookinServer/Src/Main/Server/Category/UIImage+LookinServer.h
  50. 95 0
      Pods/LookinServer/Src/Main/Server/Category/UIImage+LookinServer.m
  51. 20 0
      Pods/LookinServer/Src/Main/Server/Category/UIImageView+LookinServer.h
  52. 31 0
      Pods/LookinServer/Src/Main/Server/Category/UIImageView+LookinServer.m
  53. 21 0
      Pods/LookinServer/Src/Main/Server/Category/UILabel+LookinServer.h
  54. 29 0
      Pods/LookinServer/Src/Main/Server/Category/UILabel+LookinServer.m
  55. 19 0
      Pods/LookinServer/Src/Main/Server/Category/UITableView+LookinServer.h
  56. 29 0
      Pods/LookinServer/Src/Main/Server/Category/UITableView+LookinServer.m
  57. 21 0
      Pods/LookinServer/Src/Main/Server/Category/UITextField+LookinServer.h
  58. 29 0
      Pods/LookinServer/Src/Main/Server/Category/UITextField+LookinServer.m
  59. 21 0
      Pods/LookinServer/Src/Main/Server/Category/UITextView+LookinServer.h
  60. 29 0
      Pods/LookinServer/Src/Main/Server/Category/UITextView+LookinServer.m
  61. 44 0
      Pods/LookinServer/Src/Main/Server/Category/UIView+LookinServer.h
  62. 215 0
      Pods/LookinServer/Src/Main/Server/Category/UIView+LookinServer.m
  63. 19 0
      Pods/LookinServer/Src/Main/Server/Category/UIViewController+LookinServer.h
  64. 48 0
      Pods/LookinServer/Src/Main/Server/Category/UIViewController+LookinServer.m
  65. 21 0
      Pods/LookinServer/Src/Main/Server/Category/UIVisualEffectView+LookinServer.h
  66. 33 0
      Pods/LookinServer/Src/Main/Server/Category/UIVisualEffectView+LookinServer.m
  67. 29 0
      Pods/LookinServer/Src/Main/Server/Connection/LKS_ConnectionManager.h
  68. 268 0
      Pods/LookinServer/Src/Main/Server/Connection/LKS_ConnectionManager.m
  69. 21 0
      Pods/LookinServer/Src/Main/Server/Connection/LKS_RequestHandler.h
  70. 558 0
      Pods/LookinServer/Src/Main/Server/Connection/LKS_RequestHandler.m
  71. 26 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.h
  72. 51 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.m
  73. 19 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_CustomAttrModificationHandler.h
  74. 155 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_CustomAttrModificationHandler.m
  75. 30 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.h
  76. 148 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.m
  77. 23 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_InbuiltAttrModificationHandler.h
  78. 255 0
      Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_InbuiltAttrModificationHandler.m
  79. 17 0
      Pods/LookinServer/Src/Main/Server/LookinServer.h
  80. 26 0
      Pods/LookinServer/Src/Main/Server/Others/LKSConfigManager.h
  81. 195 0
      Pods/LookinServer/Src/Main/Server/Others/LKSConfigManager.m
  82. 21 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_AttrGroupsMaker.h
  83. 302 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_AttrGroupsMaker.m
  84. 27 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrGroupsMaker.h
  85. 486 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrGroupsMaker.m
  86. 56 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrSetterManager.h
  87. 117 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrSetterManager.m
  88. 22 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_CustomDisplayItemsMaker.h
  89. 144 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_CustomDisplayItemsMaker.m
  90. 21 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_EventHandlerMaker.h
  91. 215 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_EventHandlerMaker.m
  92. 21 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_ExportManager.h
  93. 193 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_ExportManager.m
  94. 26 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_GestureTargetActionsSearcher.h
  95. 52 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_GestureTargetActionsSearcher.m
  96. 29 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_Helper.h
  97. 38 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_Helper.m
  98. 31 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_HierarchyDisplayItemsMaker.h
  99. 162 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_HierarchyDisplayItemsMaker.m
  100. 0 0
      Pods/LookinServer/Src/Main/Server/Others/LKS_MultiplatformAdapter.h

+ 52 - 0
Asteria.xcodeproj/project.pbxproj

@@ -124,6 +124,10 @@
 		8186564A2A5BE9790049D861 /* ASSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 818656492A5BE9790049D861 /* ASSearchViewController.m */; };
 		8186564D2A5BF6010049D861 /* ASCategoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8186564C2A5BF6010049D861 /* ASCategoryViewController.m */; };
 		81932E2B29F7539B007C37AF /* UIColor+AS.m in Sources */ = {isa = PBXBuildFile; fileRef = 81932E2A29F7539B007C37AF /* UIColor+AS.m */; };
+		8193D5C82BEA299F00B9AB11 /* ASAddressListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8193D5C72BEA299F00B9AB11 /* ASAddressListViewController.m */; };
+		8193D5CB2BEA2A0800B9AB11 /* ASMineAddressModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8193D5CA2BEA2A0800B9AB11 /* ASMineAddressModel.m */; };
+		8193D5CE2BEA2B8E00B9AB11 /* ASAddressViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8193D5CD2BEA2B8E00B9AB11 /* ASAddressViewModel.m */; };
+		8193D5D12BEA2F3100B9AB11 /* ASMineAddressCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8193D5D02BEA2F3100B9AB11 /* ASMineAddressCell.m */; };
 		819900222A020A6F006FE68C /* LYTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 819900212A020A6F006FE68C /* LYTools.m */; };
 		81AA11D02B23EA15008EB5C7 /* ASVipCouponsViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 81AA11CF2B23EA15008EB5C7 /* ASVipCouponsViewModel.m */; };
 		81AA11D32B23EC20008EB5C7 /* ASVipCouponModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 81AA11D22B23EC20008EB5C7 /* ASVipCouponModel.m */; };
@@ -178,6 +182,8 @@
 		81EC47722A3402CA00516573 /* ASHomeFlashDealCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EC47712A3402CA00516573 /* ASHomeFlashDealCell.m */; };
 		81EC47752A3423FC00516573 /* ASHomeLookingCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EC47742A3423FC00516573 /* ASHomeLookingCell.m */; };
 		81EC47782A3426CE00516573 /* ASHomeLookingCollCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 81EC47772A3426CE00516573 /* ASHomeLookingCollCell.m */; };
+		81FC419C2BEB1E0200EB0A85 /* ASEditAddressViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81FC419B2BEB1E0200EB0A85 /* ASEditAddressViewController.m */; };
+		81FC41A32BEB4D6E00EB0A85 /* InTableScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 81FC41A22BEB4D6D00EB0A85 /* InTableScrollView.m */; };
 		8C24ECE114420CDEE7B9B22B /* Pods_Asteria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54DCE8001991D89B696E7D44 /* Pods_Asteria.framework */; };
 		9A1247942A1B082300126226 /* Fuction_Tool.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A1247932A1B082300126226 /* Fuction_Tool.m */; };
 		9A1247972A1B0A2800126226 /* AS_ForgotC.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A1247962A1B0A2800126226 /* AS_ForgotC.m */; };
@@ -548,6 +554,14 @@
 		8186564C2A5BF6010049D861 /* ASCategoryViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASCategoryViewController.m; sourceTree = "<group>"; };
 		81932E2929F7539B007C37AF /* UIColor+AS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+AS.h"; sourceTree = "<group>"; };
 		81932E2A29F7539B007C37AF /* UIColor+AS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+AS.m"; sourceTree = "<group>"; };
+		8193D5C62BEA299F00B9AB11 /* ASAddressListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASAddressListViewController.h; sourceTree = "<group>"; };
+		8193D5C72BEA299F00B9AB11 /* ASAddressListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASAddressListViewController.m; sourceTree = "<group>"; };
+		8193D5C92BEA2A0800B9AB11 /* ASMineAddressModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASMineAddressModel.h; sourceTree = "<group>"; };
+		8193D5CA2BEA2A0800B9AB11 /* ASMineAddressModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASMineAddressModel.m; sourceTree = "<group>"; };
+		8193D5CC2BEA2B8E00B9AB11 /* ASAddressViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASAddressViewModel.h; sourceTree = "<group>"; };
+		8193D5CD2BEA2B8E00B9AB11 /* ASAddressViewModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASAddressViewModel.m; sourceTree = "<group>"; };
+		8193D5CF2BEA2F3100B9AB11 /* ASMineAddressCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASMineAddressCell.h; sourceTree = "<group>"; };
+		8193D5D02BEA2F3100B9AB11 /* ASMineAddressCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASMineAddressCell.m; sourceTree = "<group>"; };
 		8199001E2A0206F7006FE68C /* SizeDefine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SizeDefine.h; sourceTree = "<group>"; };
 		819900202A020A6F006FE68C /* LYTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LYTools.h; sourceTree = "<group>"; };
 		819900212A020A6F006FE68C /* LYTools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LYTools.m; sourceTree = "<group>"; };
@@ -657,6 +671,10 @@
 		81EC47742A3423FC00516573 /* ASHomeLookingCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASHomeLookingCell.m; sourceTree = "<group>"; };
 		81EC47762A3426CD00516573 /* ASHomeLookingCollCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASHomeLookingCollCell.h; sourceTree = "<group>"; };
 		81EC47772A3426CE00516573 /* ASHomeLookingCollCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASHomeLookingCollCell.m; sourceTree = "<group>"; };
+		81FC419A2BEB1E0200EB0A85 /* ASEditAddressViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASEditAddressViewController.h; sourceTree = "<group>"; };
+		81FC419B2BEB1E0200EB0A85 /* ASEditAddressViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASEditAddressViewController.m; sourceTree = "<group>"; };
+		81FC41A12BEB4D6D00EB0A85 /* InTableScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InTableScrollView.h; sourceTree = "<group>"; };
+		81FC41A22BEB4D6D00EB0A85 /* InTableScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InTableScrollView.m; sourceTree = "<group>"; };
 		9A1247922A1B082300126226 /* Fuction_Tool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Fuction_Tool.h; sourceTree = "<group>"; };
 		9A1247932A1B082300126226 /* Fuction_Tool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Fuction_Tool.m; sourceTree = "<group>"; };
 		9A1247952A1B0A2800126226 /* AS_ForgotC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AS_ForgotC.h; sourceTree = "<group>"; };
@@ -1160,6 +1178,8 @@
 		81601FE72A2DC76C00E4A8F1 /* Third */ = {
 			isa = PBXGroup;
 			children = (
+				81FC41A12BEB4D6D00EB0A85 /* InTableScrollView.h */,
+				81FC41A22BEB4D6D00EB0A85 /* InTableScrollView.m */,
 				9A3520372B479B8100D097CB /* YJLAttributesLabel */,
 				9A31EDFA2B468A41009F11EE /* RadioButton */,
 				81C326632A36B6D9002EF442 /* TextRollIngView */,
@@ -1405,6 +1425,22 @@
 			path = NotificationServiceExtension;
 			sourceTree = "<group>";
 		};
+		8193D5C52BEA1F9600B9AB11 /* Address */ = {
+			isa = PBXGroup;
+			children = (
+				81FC41992BEB1DDC00EB0A85 /* edit */,
+				8193D5C62BEA299F00B9AB11 /* ASAddressListViewController.h */,
+				8193D5C72BEA299F00B9AB11 /* ASAddressListViewController.m */,
+				8193D5CF2BEA2F3100B9AB11 /* ASMineAddressCell.h */,
+				8193D5D02BEA2F3100B9AB11 /* ASMineAddressCell.m */,
+				8193D5CC2BEA2B8E00B9AB11 /* ASAddressViewModel.h */,
+				8193D5CD2BEA2B8E00B9AB11 /* ASAddressViewModel.m */,
+				8193D5C92BEA2A0800B9AB11 /* ASMineAddressModel.h */,
+				8193D5CA2BEA2A0800B9AB11 /* ASMineAddressModel.m */,
+			);
+			path = Address;
+			sourceTree = "<group>";
+		};
 		8199001F2A020A4F006FE68C /* Tools */ = {
 			isa = PBXGroup;
 			children = (
@@ -1555,6 +1591,7 @@
 		81D484DD2A0F40510075DC43 /* UserCenter */ = {
 			isa = PBXGroup;
 			children = (
+				8193D5C52BEA1F9600B9AB11 /* Address */,
 				81C796312A55159B003083B8 /* Setting */,
 				81C796282A539E65003083B8 /* Message */,
 				81E5EE8D2A498FA20075695F /* VipCenter */,
@@ -1732,6 +1769,15 @@
 			path = m;
 			sourceTree = "<group>";
 		};
+		81FC41992BEB1DDC00EB0A85 /* edit */ = {
+			isa = PBXGroup;
+			children = (
+				81FC419A2BEB1E0200EB0A85 /* ASEditAddressViewController.h */,
+				81FC419B2BEB1E0200EB0A85 /* ASEditAddressViewController.m */,
+			);
+			path = edit;
+			sourceTree = "<group>";
+		};
 		9A1247912A1B07F400126226 /* Tool */ = {
 			isa = PBXGroup;
 			children = (
@@ -2661,7 +2707,9 @@
 				819900222A020A6F006FE68C /* LYTools.m in Sources */,
 				816020072A2DD4FB00E4A8F1 /* Target_Home.m in Sources */,
 				81CE28942AF490C20012AA45 /* ASGiftCardTableView.m in Sources */,
+				81FC41A32BEB4D6E00EB0A85 /* InTableScrollView.m in Sources */,
 				816020132A2EE5A200E4A8F1 /* ASCategaryCollectCell.m in Sources */,
+				8193D5CB2BEA2A0800B9AB11 /* ASMineAddressModel.m in Sources */,
 				81C796422A551FE9003083B8 /* KWTextField.m in Sources */,
 				9AD346012A08D60F005CA070 /* ZFFloatView.m in Sources */,
 				9A3ADC0A2B60BAE200B04BA6 /* GoodsSizePayMentCell.m in Sources */,
@@ -2792,6 +2840,7 @@
 				9AD3460D2A08D60F005CA070 /* ZFLandScapeControlView.m in Sources */,
 				9AD364C62A05E73E00452C7A /* AS_GoodsDetailsC.m in Sources */,
 				8120211E2B15F2B30026B8B5 /* ASVipUrlTempModel.m in Sources */,
+				8193D5CE2BEA2B8E00B9AB11 /* ASAddressViewModel.m in Sources */,
 				9A35203A2B479BAA00D097CB /* YJLAttributesLabel.m in Sources */,
 				816020102A2EE55F00E4A8F1 /* ASCategaryListCell.m in Sources */,
 				9AD3460B2A08D60F005CA070 /* UIView+ZFFrame.m in Sources */,
@@ -2803,11 +2852,13 @@
 				8160200A2A2DD59E00E4A8F1 /* CTMediator+Home.m in Sources */,
 				81717C9C2A3BF1F100648139 /* ASHomeAlertViewController.m in Sources */,
 				9AD345FC2A08D60F005CA070 /* ZFPlayerLogManager.m in Sources */,
+				8193D5C82BEA299F00B9AB11 /* ASAddressListViewController.m in Sources */,
 				9A78E0142B6389FC00CA4E32 /* Cart_MyCartC.m in Sources */,
 				815970BA2B54DC830073041D /* ASUserCenterViewModel.m in Sources */,
 				816D0C9D2AF3988400395B5B /* ASGiftCardAvailabelCell.m in Sources */,
 				8120211B2B15F03B0026B8B5 /* ASVipModel.m in Sources */,
 				81354C0E2A297D6A0082C93A /* HomeFlashDealSubCollectCell.m in Sources */,
+				81FC419C2BEB1E0200EB0A85 /* ASEditAddressViewController.m in Sources */,
 				81354BFD2A28998B0082C93A /* KWMineMoreProductsCell.m in Sources */,
 				9ACBEC252A14707400A8F97A /* AS_SignUpC.m in Sources */,
 				81717D1F2A3C4AE000648139 /* KWMoneyTypeHeadView.m in Sources */,
@@ -2822,6 +2873,7 @@
 				9AD346162A08D60F005CA070 /* UIImageView+ZFCache.m in Sources */,
 				81717D1D2A3C4AE000648139 /* KWSearchSubTypeHeadView.m in Sources */,
 				9AD345A92A08D571005CA070 /* TYCyclePagerTransformLayout.m in Sources */,
+				8193D5D12BEA2F3100B9AB11 /* ASMineAddressCell.m in Sources */,
 				815DA3D92A39575100616EF7 /* ASProductListImageCell.m in Sources */,
 				815DA3DC2A39625200616EF7 /* ASProductListTypeDesCell.m in Sources */,
 				81932E2B29F7539B007C37AF /* UIColor+AS.m in Sources */,

+ 11 - 11
Asteria/Fuction/Category/vc/ASSearchViewController.m

@@ -145,18 +145,18 @@
 // MARK: - events
 /// show lenovo Words
 - (void) showLenovoV:(NSString *)key {
-    self.lenovoV.hidden = false;
-    [self.lenovoV setData:@[@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",] origen:CGPointMake(self.searchTf.frame.origin.x, self.customNavBar.maxY - 8)];
+//    self.lenovoV.hidden = false;
+//    [self.lenovoV setData:@[@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",@"124356",] origen:CGPointMake(self.searchTf.frame.origin.x, self.customNavBar.maxY - 8)];
     
-//    @weakify(self);
-//    [self.hisVm getAboutKeyList:key back:^{
-//        if (weak_self.hisVm.aboutKeys.count > 0 && [self.searchTf.text isEqualToString:key]) {
-//            weak_self.lenovoV.hidden = false;
-//            [weak_self.lenovoV setData:weak_self.hisVm.aboutKeys origen:CGPointMake(self.searchTf.frame.origin.x, self.customNavBar.maxY - 8)];
-//        } else {
-//            weak_self.lenovoV.hidden = true;
-//        }
-//    }];
+    __weak typeof(self) weak_self = self;
+    [self.hisWordV.vm getAboutKeyList:key back:^{
+        if (weak_self.hisWordV.vm.aboutKeys.count > 0 && [self.searchTf.text isEqualToString:key]) {
+            weak_self.lenovoV.hidden = false;
+            [weak_self.lenovoV setData:weak_self.hisWordV.vm.aboutKeys origen:CGPointMake(self.searchTf.frame.origin.x, self.customNavBar.maxY - 8)];
+        } else {
+            weak_self.lenovoV.hidden = true;
+        }
+    }];
     
    
 }

+ 10 - 19
Asteria/Fuction/Category/vm/KWHisAndHotWordsViewModel.m

@@ -27,25 +27,16 @@ static NSString *localSearchList = @"localSearchList";
 
 
 -(void)getAboutKeyList:(NSString *)key back:(btnClickBlock)success {
-//    @weakify(self);
-//    NSDictionary *para = @{@"kwd":key};
-//    [PPNetworkHelper POST:KeyAboutWord parameters:para success:^(id responseObject) {
-//        @strongify(self);
-//        if (RequestSuccess) {
-//            NSLog(@"success:%@",responseObject);
-//            NSArray *arr = [NSString mj_objectArrayWithKeyValuesArray:responseObject[@"data"]];
-//            self.aboutKeys = arr;
-//        } else {
-//            NSLog(@"fail:%@",RequestErrorMsg);
-//            self.aboutKeys = @[];
-//
-//        }
-//        success();
-//    } failure:^(NSError *error) {
-//        NSLog(@"err:%@",error);
-//        self.aboutKeys = @[];
-//        success();
-//    }];
+    
+    __weak typeof(self) weakSelf = self;
+    [ASNetTools.shared getWithPath:getLinkingKey param:@{@"q":key} success:^(id _Nonnull json) {
+        NSLog(@"------url:%@---json:%@------", getHotList, json);
+        
+        success();
+    } faild:^(NSString * _Nonnull code, NSString * _Nonnull msg) {
+        NSLog(@"------url:%@---code:%@---msg:%@---", getHotList, code, msg);
+        success();
+    }];
 }
 
 

+ 23 - 0
Asteria/Fuction/UserCenter/Address/ASAddressListViewController.h

@@ -0,0 +1,23 @@
+//
+//  ASAddressListViewController.h
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import "ASBaseViewController.h"
+#import "ASMineAddressModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ASAddressListViewController : ASBaseViewController
+
+@property (nonatomic, assign) BOOL isSelMode;
+@property (nonatomic, copy) NSString *sel_Id;
+
+/// 选择回调事件
+@property (nonatomic , copy) void(^selectAddressBlock)(ASMineAddressModel *addressM);
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 199 - 0
Asteria/Fuction/UserCenter/Address/ASAddressListViewController.m

@@ -0,0 +1,199 @@
+//
+//  ASAddressListViewController.m
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import "ASAddressListViewController.h"
+#import "ASAddressViewModel.h"
+#import "ASMineAddressCell.h"
+#import "ASEditAddressViewController.h"
+
+@interface ASAddressListViewController () <UITableViewDelegate,UITableViewDataSource>
+
+@property (nonatomic, strong) UIView *bottomV;
+@property (nonatomic, strong) UIButton *addNewBt;
+@property (nonatomic, strong) UITableView *tableV;
+
+@property (nonatomic, strong) NSArray <ASAddressModel *>*addArr;
+
+
+@end
+
+@implementation ASAddressListViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    [self loadSubV];
+    
+    [self setEmptyTip:@"No Addresses"];
+    
+    self.titleStr = @"SHIPPING ADDRESS";
+    __weak typeof(self) weak_self = self;
+    self.tableV.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
+        [weak_self getData];
+    }];
+    
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self.tableV.mj_header beginRefreshing];
+    
+}
+
+- (void)getData {
+    __weak typeof(self) weakSelf = self;
+    [ASUserInfoManager.shared baseInfoNet:^{
+        [weakSelf.tableV.mj_header endRefreshing];
+        weakSelf.addArr = ASUserInfoManager.shared.userInfo.addresses;
+        if (weakSelf.addArr.count == 0) {
+            [weakSelf showEmptyV:weakSelf.tableV];
+        } else {
+            [weakSelf hiddenEmpty];
+        }
+        [weakSelf.tableV reloadData];
+    }];
+}
+
+
+
+- (void)loadSubV {
+    [self.view addSubview:self.tableV];
+    
+    [self.view addSubview:self.bottomV];
+    [self.bottomV addSubview:self.addNewBt];
+    [self.tableV mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.equalTo(self.customNavBar.mas_bottom);
+        make.left.right.equalTo(self.view);
+    }];
+    [self.bottomV mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.equalTo(self.view);
+        make.top.equalTo(self.tableV.mas_bottom);
+        make.height.equalTo(@65);
+        make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
+    }];
+    [self.addNewBt mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.equalTo(self.bottomV).offset(10);
+        make.left.equalTo(self.bottomV).offset(20);
+        make.center.equalTo(self.bottomV);
+        make.height.equalTo(@45);
+    }];
+    
+}
+
+- (UIButton *)addNewBt {
+    if (!_addNewBt) {
+        UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
+        [bt setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
+//        bt.layer.borderWidth = 1;
+//        bt.layer.borderColor = [UIColor blackColor].CGColor;
+//        bt.layer.cornerRadius = 4;
+//        bt.layer.masksToBounds = true;
+        bt.backgroundColor = UIColor.blackColor;
+        [bt setTitle:@"+ NEW ADDRESS" forState:UIControlStateNormal];
+        [bt addTarget:self action:@selector(addNewbtAction) forControlEvents:UIControlEventTouchUpInside];
+        bt.titleLabel.font = [UIFont fontWithName:Rob_Medium size:18];
+        _addNewBt = bt;
+    }
+    return _addNewBt;
+}
+
+-(UIView *)bottomV {
+    if (!_bottomV) {
+        UIView *v = [UIView new];
+        v.frame = CGRectMake(0, 0, KScreenWidth, 70);
+        v.backgroundColor = [UIColor whiteColor];
+        _bottomV = v;
+    }
+    return _bottomV;
+}
+
+- (UITableView *)tableV {
+    IPhoneXHeigh
+    if (!_tableV) {
+        UITableView *tabV = [[UITableView alloc] initWithFrame:CGRectMake(securitytop_Y, 0, KScreenWidth, security_H) style:UITableViewStylePlain];
+        tabV.backgroundColor = [UIColor whiteColor];
+        [tabV registerClass:[ASMineAddressCell class] forCellReuseIdentifier:@"ASMineAddressCell"];
+        tabV.delegate = self;
+        tabV.dataSource = self;
+        tabV.rowHeight = UITableViewAutomaticDimension;
+        tabV.estimatedRowHeight = 100;
+        tabV.separatorStyle = UITableViewCellSeparatorStyleNone;
+
+        _tableV = tabV;
+    }
+    return _tableV;
+}
+
+
+- (void)addNewbtAction {
+    ASEditAddressViewController *vc = [ASEditAddressViewController new];
+    ///当添加地址时,默认地址都改为美国地址
+    ASAddressModel *model = [ASAddressModel defualtData];
+    vc.m =  model;
+    vc.isEdit = false;
+    [self.navigationController pushViewController:vc animated:true];
+}
+
+#pragma mark - UITableViewDelegate,UITableViewDataSource
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    
+    return self.addArr.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    ASMineAddressCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ASMineAddressCell" forIndexPath:indexPath];
+    ASAddressModel *m = self.addArr[indexPath.row];
+    [cell setData:m];
+    if(self.isSelMode){
+        cell.checkBtn.hidden = NO;
+  
+        if([m.Id isEqualToString:self.sel_Id]){
+            cell.checkBtn.selected = YES;
+        }else{
+            cell.checkBtn.selected = NO;
+        }
+        [cell setCheckBack:^{
+            if (self.selectAddressBlock) {
+                self.selectAddressBlock(m);
+            }
+            [self.navigationController popViewControllerAnimated:YES];
+        }];
+        
+    }else{
+        cell.checkBtn.hidden = YES;
+    }
+    @weakify(self)
+    [cell setEditBack:^{
+//        @strongify(self)
+//        // 去编辑地址
+//        KWEditAddressViewController *vc = [KWEditAddressViewController new];
+//        vc.m = m;
+//        vc.isEdit = true;
+//        if(self.addressType == AddressTypeShippingAddress){
+//            vc.editAddressType = EditAddressTypeShippingAddress;
+//            vc.tempListC = self;
+//        }else{
+//            vc.editAddressType = EditAddressTypeSettingAddress;
+//        }
+//        [self.navigationController pushViewController:vc animated:true];
+    }];
+    return  cell;
+}
+
+-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{
+    return 0.1;
+}
+-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
+    return 0.1;
+}
+
+
+@end

+ 36 - 0
Asteria/Fuction/UserCenter/Address/ASAddressViewModel.h

@@ -0,0 +1,36 @@
+//
+//  ASAddressViewModel.h
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import <Foundation/Foundation.h>
+#import "ASMineAddressModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ASAddressViewModel : NSObject
+
+@property (nonatomic, assign) BOOL hadGetProvince;
+
+@property (nonatomic, strong) NSMutableArray<KWCountryAddressModel *> *countryArr;
+@property (nonatomic, strong) NSMutableArray<KWProvinceAddressModel *> *provinceArr;
+
+@property (nonatomic, strong) NSMutableArray<ASMineAddressModel *> *addressArr;
+
+- (void)getMineAddressList:(void(^)(void))complate;
+
+- (void)upAddress:(ASAddressModel *)addressModel complate:(void(^)(BOOL,NSString *))complate;
+
+- (void)getCountryList:(void(^)(void))success;
+- (void)getProvinceList:(NSString *)country_id  success:(void(^)(void))success;
+
+- (void)deleteAnAddress:(ASAddressModel *)addressModel complate:(void(^)(BOOL))complate;
+
+- (NSArray *)provinceNameList;
+- (NSArray *)countryNameList;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 97 - 0
Asteria/Fuction/UserCenter/Address/ASAddressViewModel.m

@@ -0,0 +1,97 @@
+//
+//  ASAddressViewModel.m
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import "ASAddressViewModel.h"
+
+@implementation ASAddressViewModel
+
+- (void)upAddress:(ASAddressModel *)addressModel complate:(void(^)(BOOL,NSString *))complate {
+    complate(true, @"");
+}
+
+- (void)deleteAnAddress:(ASAddressModel *)addressModel complate:(void(^)(BOOL))complate {
+    complate(true);
+}
+
+- (void)getCountryList:(void(^)(void))success  {
+    success();
+}
+
+- (void)getProvinceList:(NSString *)country_id  success:(void(^)(void))success {
+    success();
+}
+
+- (NSArray *)countryNameList {
+    NSMutableArray *arr = [NSMutableArray array];
+    for (KWCountryAddressModel* item in self.countryArr) {
+        [arr addObject:item.name];
+    }
+    return arr;
+}
+
+- (NSArray *)provinceNameList {
+    NSMutableArray *arr = [NSMutableArray array];
+    for (KWProvinceAddressModel* item in self.provinceArr) {
+        [arr addObject:item.name];
+    }
+    return arr;
+}
+
+- (void)getMineAddressList:(void(^)(void))complate {
+    NSDictionary *dic = @{};
+    complate();
+    
+//    @weakify(self);
+    
+//    [PPNetworkHelper POST:MineUserAddress parameters:dic success:^(id responseObject) {
+//        
+//        if (RequestSuccess) {
+//            NSLog(@"success:%@",responseObject);
+//            NSDictionary *temDic = (NSDictionary *)responseObject;
+//            KWMineAddressModel *defaultBillingAddress = [KWMineAddressModel mj_objectWithKeyValues:temDic[@"data"][@"defaultBillingAddress"]];
+//            defaultBillingAddress.title = @"Default Billing Address";
+//            defaultBillingAddress.addressType = 1;
+//                
+//            
+//            KWMineAddressModel *defaultShippingAddress = [KWMineAddressModel mj_objectWithKeyValues:temDic[@"data"][@"defaultShippingAddress"]];
+//            defaultShippingAddress.title = @"Default Shipping Address";
+//            defaultShippingAddress.addressType = 2;
+//             
+//            NSMutableArray *arr = [KWMineAddressModel mj_objectArrayWithKeyValuesArray:temDic[@"data"][@"additionalAddressEntries"]];
+//
+//            if (( defaultShippingAddress && ![defaultShippingAddress.Id isEmpty]) &&
+//                [defaultShippingAddress.Id isEqualToString:defaultBillingAddress.Id] &&
+//                ( defaultBillingAddress && ![defaultBillingAddress.Id isEmpty])) {
+//                defaultBillingAddress.addressType = 3;
+//                defaultBillingAddress.title = @"Default Billing Address\nDefault Shipping Address";
+//                self.defaultBillAderssM = defaultBillingAddress;
+//                [arr insertObject:defaultBillingAddress atIndex:0];
+//            }else{
+//                if ( defaultShippingAddress && ![defaultShippingAddress.Id isEmpty]) {
+//                    [arr insertObject:defaultShippingAddress atIndex:0];
+//                }
+//                if ( defaultBillingAddress && ![defaultBillingAddress.Id isEmpty]) {
+//                    self.defaultBillAderssM = defaultBillingAddress;
+//                    [arr insertObject:defaultBillingAddress atIndex:0];
+//                }
+//            }
+//     
+//            weak_self.addressArr = arr;
+//            
+//        } else {
+//            weak_self.addressArr = [NSMutableArray array];
+//        }
+//        complate();
+//    } failure:^(NSError *error) {
+//        NSLog(@"err:%@",error);
+//        weak_self.addressArr = [NSMutableArray array];
+//        complate();
+//    }];
+    
+}
+
+@end

+ 25 - 0
Asteria/Fuction/UserCenter/Address/ASMineAddressCell.h

@@ -0,0 +1,25 @@
+//
+//  ASMineAddressCell.h
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import <UIKit/UIKit.h>
+#import "ASMineAddressModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ASMineAddressCell : UITableViewCell
+@property (nonatomic, strong) UIButton *checkBtn;
+@property (nonatomic, strong) UIButton *editBt;
+@property (nonatomic, strong) UILabel *titleLB;
+
+- (void)setData:(ASMineAddressModel *)m;
+
+@property (nonatomic, copy) btnClickBlock editBack;
+@property (nonatomic, copy) btnClickBlock checkBack;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 162 - 0
Asteria/Fuction/UserCenter/Address/ASMineAddressCell.m

@@ -0,0 +1,162 @@
+//
+//  ASMineAddressCell.m
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import "ASMineAddressCell.h"
+
+
+@interface ASMineAddressCell ()
+
+@property (nonatomic, strong) UIView *bgV;
+
+@property (nonatomic, strong) UILabel *nameLB;
+@property (nonatomic, strong) UILabel *addressLB;
+@property (nonatomic, strong) UILabel *codeLB;
+@property (nonatomic, strong) UILabel *countryLB;
+@property (nonatomic, strong) UILabel *phoneLB;
+
+@end
+
+@implementation ASMineAddressCell
+
+- (void)setData:(ASAddressModel *)m {
+    if (m.title && ![m.title isEmpty]) {
+        self.titleLB.text = m.title;
+    } else {
+        self.titleLB.text = @"Other Address";
+    }
+    self.nameLB.text = [NSString stringWithFormat:@"%@ %@",m.lastname,m.firstname];
+    self.addressLB.text = [NSString stringWithFormat:@"%@,%@,%@",m.city,m.region,m.street];
+    self.codeLB.text = m.postcode;//[NSString stringWithFormat:@""]
+    self.countryLB.text = m.country;
+    self.phoneLB.text = [NSString stringWithFormat:@"T:%@",m.telephone];
+}
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
+    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+    if (self) {
+        [self configSubV];
+    }
+    return self;
+}
+
+- (void)configSubV {
+    self.backgroundColor = [UIColor whiteColor];
+    self.selectionStyle = UITableViewCellSelectionStyleNone;
+    self.bgV = [UIView new];
+    self.bgV.backgroundColor = _F8F8F8;
+//    self.bgV.layer.cornerRadius = 4;
+//    self.bgV.layer.masksToBounds = true;
+    [self.contentView addSubview:self.bgV];
+    [self.bgV mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.equalTo(self.contentView).offset(0);
+        make.left.equalTo(self.contentView).offset(0);
+        make.centerX.equalTo(self.contentView);
+        make.bottom.equalTo(self.contentView).offset(-10);
+    }];
+    
+    self.titleLB = [self createLb];
+    self.titleLB.font = [UIFont fontWithName:Rob_Bold size:16];
+    self.titleLB.numberOfLines =0;
+    self.titleLB.preferredMaxLayoutWidth = KScreenWidth-40-10-44;
+    [self.bgV addSubview:self.titleLB];
+    [self.titleLB mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.equalTo(self.bgV).offset(20);
+        make.left.equalTo(self.bgV).offset(15);
+        make.width.mas_equalTo(KScreenWidth-40-10-44);
+    }];
+    
+
+    
+    self.editBt = [UIButton buttonWithType:UIButtonTypeCustom];
+    [self.editBt setImage:[UIImage imageNamed:@"avater_edit"] forState:UIControlStateNormal];
+    [self.editBt addTarget:self action:@selector(editBtnAction) forControlEvents:UIControlEventTouchUpInside];
+    [self.bgV addSubview:self.editBt];
+    [self.editBt mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.width.height.equalTo(@44);
+        make.right.equalTo(self.bgV);
+        make.centerY.equalTo(self.bgV);
+    }];
+    
+    
+    self.checkBtn = [UIButton buttonWithType:UIButtonTypeCustom];
+    self.checkBtn.hidden = YES;
+    [self.checkBtn setImage:[UIImage imageNamed:@"cartship_radio_unselect"] forState:UIControlStateNormal];
+    [self.checkBtn setImage:[UIImage imageNamed:@"cartship_radio_select"] forState:UIControlStateSelected];
+    [self.checkBtn addTarget:self action:@selector(handle_checkBtnAction) forControlEvents:UIControlEventTouchUpInside];
+    [self.bgV addSubview:self.checkBtn];
+    [self.checkBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.height.equalTo(@44);
+        make.width.equalTo(@44);
+        make.right.equalTo(self.bgV);
+        make.centerY.equalTo(self.titleLB);
+    }];
+    
+    UIStackView *tStackV = [[UIStackView alloc] init];
+    tStackV.axis = UILayoutConstraintAxisVertical;
+    tStackV.alignment = UIStackViewAlignmentFill;
+    tStackV.distribution = UIStackViewDistributionFill;
+    tStackV.spacing = 10;
+    [self.bgV addSubview:tStackV];
+    [tStackV mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.equalTo(self.titleLB.mas_bottom).offset(15);
+        make.left.equalTo(self.bgV).offset(15);
+        make.right.equalTo(self.editBt.mas_left).offset(-15);
+        make.bottom.equalTo(self.bgV).offset(-20);
+    }];
+    
+    self.nameLB = [self createLb];
+    [tStackV addArrangedSubview:self.nameLB];
+    
+    self.addressLB = [self createLb];
+    [tStackV addArrangedSubview:self.addressLB];
+    self.addressLB.numberOfLines = 0;
+    
+    self.codeLB = [self createLb];
+    [tStackV addArrangedSubview:self.codeLB];
+    
+    self.countryLB = [self createLb];
+    [tStackV addArrangedSubview:self.countryLB];
+    
+    self.phoneLB = [self createLb];
+    [tStackV addArrangedSubview:self.phoneLB];
+}
+
+- (void)editBtnAction {
+    if (self.editBack) {
+        self.editBack();
+    }
+}
+
+-(void)handle_checkBtnAction{
+    if(self.checkBack){
+        self.checkBack();
+    }
+}
+
+- (UILabel *)createLb {
+    UILabel *lb = [[UILabel alloc] init];
+    lb.font = [UIFont fontWithName:Rob_Regular size:14];
+    lb.textColor = UIColor.blackColor;
+    [lb mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.height.greaterThanOrEqualTo(@14);
+    }];
+    return lb;
+}
+
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    // Initialization code
+}
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
+    [super setSelected:selected animated:animated];
+
+    // Configure the view for the selected state
+}
+
+@end

+ 52 - 0
Asteria/Fuction/UserCenter/Address/ASMineAddressModel.h

@@ -0,0 +1,52 @@
+//
+//  ASMineAddressModel.h
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ASMineAddressModel : NSObject
+
+@property (nonatomic, copy) NSString *title;
+/// 0:啥也不是   默认地址 1:bill Adress   2:shiping Address      3:bill Adress 和shiping Address
+@property (nonatomic, assign) NSInteger addressType;
+
+@property (nonatomic, copy) NSString *Id;// [string]    是            展开
+@property (nonatomic, copy) NSString *firstname;// [string]    是            展开
+@property (nonatomic, copy) NSString *lastname;// [string]    是            展开
+@property (nonatomic, copy) NSString *city;// [string]    是            展开
+@property (nonatomic, copy) NSString *country_id;// [string]    是            展开
+@property (nonatomic, copy) NSString *country;
+@property (nonatomic, copy) NSString *region;// 地区 [string]    是            展开
+@property (nonatomic, copy) NSString *region_id;// [string]    是            展开
+@property (nonatomic, copy) NSString *postcode;// [string]    是            展开
+@property (nonatomic, copy) NSString *telephone;// [string]    是            展开
+@property (nonatomic, copy) NSString *street;//
+
++ (ASMineAddressModel *)defualtData;
+
+@end
+
+
+@interface KWCountryAddressModel : NSObject
+
+@property (nonatomic, copy) NSString *country_id;
+@property (nonatomic, copy) NSString *iso2_code;
+@property (nonatomic, copy) NSString *iso3_code;
+@property (nonatomic, copy) NSString *name;
+
+@end
+
+@interface KWProvinceAddressModel : NSObject
+
+@property (nonatomic, copy) NSString *Id;
+@property (nonatomic, copy) NSString *name;
+
+@end
+
+
+NS_ASSUME_NONNULL_END

+ 50 - 0
Asteria/Fuction/UserCenter/Address/ASMineAddressModel.m

@@ -0,0 +1,50 @@
+//
+//  ASMineAddressModel.m
+//  Asteria
+//
+//  Created by iOS on 2024/5/7.
+//
+
+#import "ASMineAddressModel.h"
+
+@implementation ASMineAddressModel
+
++ (NSDictionary *)mj_replacedKeyFromPropertyName {
+    return @{
+        @"Id": @"id",
+    };
+}
+
++ (ASMineAddressModel *)defualtData {
+
+    ASMineAddressModel *m = [ASMineAddressModel new];
+    m.title = @"";
+    m.firstname = @"";
+    m.lastname = @"";
+    m.street = @"";
+    m.postcode = @"";
+    m.city = @"";
+    m.region = @"";
+    m.telephone = @"";
+    m.country = @"Unites States";
+    m.country_id = @"US";
+    
+
+    return m;
+}
+
+@end
+
+@implementation KWCountryAddressModel
+
+@end
+
+@implementation KWProvinceAddressModel
+
++ (NSDictionary *)mj_replacedKeyFromPropertyName {
+    return @{
+        @"Id": @"id",
+    };
+}
+
+@end

+ 20 - 0
Asteria/Fuction/UserCenter/Address/edit/ASEditAddressViewController.h

@@ -0,0 +1,20 @@
+//
+//  ASEditAddressViewController.h
+//  Asteria
+//
+//  Created by iOS on 2024/5/8.
+//
+
+#import "ASBaseViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ASEditAddressViewController : ASBaseViewController
+
+@property (nonatomic, strong) ASAddressModel *m;
+@property (nonatomic, assign) BOOL isEdit;
+@property (nonatomic, assign) BOOL isCartEdit;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 516 - 0
Asteria/Fuction/UserCenter/Address/edit/ASEditAddressViewController.m

@@ -0,0 +1,516 @@
+//
+//  ASEditAddressViewController.m
+//  Asteria
+//
+//  Created by iOS on 2024/5/8.
+//
+
+#import "ASEditAddressViewController.h"
+#import "ASAddressViewModel.h"
+#import "InTableScrollView.h"
+#import "KWTextField.h"
+#import <BRPickerView/BRPickerView.h>
+
+
+@interface ASEditAddressViewController () <UITextFieldDelegate>
+
+@property (nonatomic, strong) ASAddressViewModel *vm;
+
+@property (nonatomic, strong) InTableScrollView *scrollV;
+@property (nonatomic, strong) UIStackView *totalStackV;
+
+@property (nonatomic, strong) NSMutableArray<KWTextField *> *tfArr;
+@property (nonatomic, strong) NSMutableArray<UILabel *> *errLbArr;
+
+@property (nonatomic, strong) NSArray<NSString *> *placeHoldArr;
+@property (nonatomic, strong) NSArray<NSString *> *errDesArr;
+
+@property (nonatomic, strong) UIButton *billTypeBt;
+@property (nonatomic, strong) UIButton *shipTypeBt;
+
+@property (nonatomic, strong) UIButton *saveBt;
+@property (nonatomic, strong) UIButton *deletBt;
+
+@end
+
+@implementation ASEditAddressViewController
+
+
+-(ASAddressViewModel *)vm {
+    if (!_vm) {
+        _vm = [ASAddressViewModel new];
+    }
+    return _vm;
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self.vm getCountryList:^{}];
+    }
+    return self;
+}
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+    [self setUI];
+    if (self.isEdit) {
+        [self setTitle:@"EDIT ADDRESS"];
+        
+        [self.vm getProvinceList:self.m.country_id success:^{}];
+        [self setEditDefualtData];
+        
+   
+    } else {
+        if (!self.m) {
+            self.m = [ASAddressModel defualtData];
+        } else {
+            [self.vm getProvinceList:self.m.country_id success:^{}];
+            [self setEditDefualtData];
+        }
+        [self setTitle: @"NEW ADDRESS"];
+        _deletBt.hidden = true;
+
+    }
+    if(self.isCartEdit){
+        [self.saveBt setTitle:@"SAVE AND APPLY" forState:UIControlStateNormal];
+    }
+
+}
+#pragma mark - **************** EditAddressTypeShippingNewAddress ****************
+- (void)xxx_tengteng_confignavBarBar{
+    self.navigationController.navigationBar.hidden = YES;
+    UIButton *closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
+    [closeBtn setImage:[UIImage imageNamed:@"login_close_black"] forState:UIControlStateNormal];
+    [closeBtn addTarget:self action:@selector(xxx_closeVC) forControlEvents:UIControlEventTouchUpInside];
+    [self.view addSubview:closeBtn];
+    NSInteger top = IPHONEX ? 44 : 20;
+    closeBtn.frame = CGRectMake((KScreenWidth -44)/2, top, 44, 44);
+    
+    IPhoneXHeigh
+    [self.scrollV mas_remakeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.equalTo(self.view);
+        make.top.mas_equalTo(securitytop_Y +10);
+        make.height.mas_equalTo(security_H -10);
+    }];
+
+}
+#pragma mark - **************** handle 触发事件****************
+-(void)xxx_closeVC{
+    [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+
+- (void)setEditDefualtData {
+    NSArray<NSString *> *arr = @[self.m.firstname,
+                                 self.m.lastname,
+                                 self.m.street,
+                                 self.m.postcode,
+                                 self.m.city,
+                                 self.m.country,
+                                 self.m.region.region,
+                                 self.m.telephone
+    ];
+    
+    for (int i= 0 ; i<arr.count; i++) {
+        if (self.tfArr.count > i) {
+            self.tfArr[i].text = arr[i];
+        }
+    }
+    _billTypeBt.selected = false;
+    _shipTypeBt.selected = false;
+    switch (self.m.addressType) {
+        case 1:
+            [_billTypeBt setSelected:true];
+            break;
+        case 2:
+            [_shipTypeBt setSelected:true];
+            break;
+        case 3:
+            [_shipTypeBt setSelected:true];
+            [_billTypeBt setSelected:true];
+        default:
+            break;
+    }
+    
+}
+
+- (void)setData {
+    self.placeHoldArr = @[@"* FIRST NAME",
+                          @"* LAST NAME",
+                          @"* ADDRESS (NO ACCEPTING P0.BOX)",
+                          @"* ZIP/POSTAL CODE",
+                          @"* CITY",
+                          @"* COUNTY",
+                          @"* STATE/PROVINCE",
+                          @"* TELEPHONE"];
+    self.errDesArr = @[@"*This Is Mandatory",
+                      @"*This Is Mandatory",
+                      @"*This Is Mandatory",
+                      @"*This Is Mandatory",
+                      @"*This Is Mandatory",
+                      @"*This Is Mandatory",
+                      @"*This Is Mandatory",
+                      @"*This Is Mandatory"];
+    
+}
+
+- (void)setUI {
+    self.tfArr = [NSMutableArray array];
+    self.errLbArr = [NSMutableArray array];
+    [self setData];
+    [self.view addSubview:self.scrollV];
+    IPhoneXHeigh
+    [self.scrollV mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.equalTo(self.view);
+        make.top.mas_equalTo(securitytop_Y);
+//        make.top.equalTo(self.customNavBar.mas_bottom);
+        make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
+    }];
+    
+    
+    
+    UIStackView *tStackV = [[UIStackView alloc] init];
+    tStackV.axis = UILayoutConstraintAxisVertical;
+    tStackV.alignment = UIStackViewAlignmentFill;
+    tStackV.distribution = UIStackViewDistributionFill;
+    tStackV.spacing = 16;
+    self.totalStackV = tStackV;
+    
+    UIStackView *topStackV = [[UIStackView alloc] init];
+    topStackV.axis = UILayoutConstraintAxisHorizontal;
+    topStackV.alignment = UIStackViewAlignmentFill;
+    topStackV.distribution = UIStackViewDistributionFillEqually;
+    topStackV.spacing = 20;
+    [self.totalStackV addArrangedSubview:topStackV];
+    
+    [self.tfArr removeAllObjects];
+    [self.errLbArr removeAllObjects];
+    for (int i = 0; i<self.placeHoldArr.count; i++) {
+        UIView *v = [UIView new];
+        v.backgroundColor = [UIColor clearColor];
+        UILabel *lb = [[UILabel alloc] init];
+        lb.font = [UIFont fontWithName:Rob_Regular size:12];
+        lb.textColor = [UIColor colorWithHexString:@"#E60013"];
+        lb.textAlignment = NSTextAlignmentCenter;
+        lb.tag = 70000 + i;
+        KWTextField *tf = [[KWTextField alloc] init];
+        tf.tag = 90000 + i;
+        if (i == 5 || i == 6) {
+            tf.delegate = self;
+        }
+        [v addSubview:tf];
+        [v addSubview:lb];
+        UIStackView *stackV = [[UIStackView alloc] init];
+        stackV.axis = UILayoutConstraintAxisVertical;
+        stackV.alignment = UIStackViewAlignmentFill;
+        stackV.distribution = UIStackViewDistributionFill;
+        stackV.spacing = 0;
+        [stackV addArrangedSubview:v];
+        lb.text = self.errDesArr[i];
+        lb.hidden = true;
+        tf.placeholder = self.placeHoldArr[i];
+        [v mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.height.equalTo(@59);
+        }];
+        [tf mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.top.right.equalTo(v);
+            make.height.equalTo(@45);
+        }];
+        [lb mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.bottom.right.equalTo(v);
+            make.height.equalTo(@14);
+        }];
+        if (i == 0 || i == 1) {
+            [topStackV addArrangedSubview:stackV];
+        } else {
+            [self.totalStackV addArrangedSubview:stackV];
+        }
+        [self.tfArr addObject:tf];
+        [self.errLbArr addObject:lb];
+    }
+    
+    [self.scrollV addSubview:self.totalStackV];
+    [self.totalStackV mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.equalTo(self.scrollV).offset(20);
+        make.left.equalTo(self.scrollV).offset(20);
+        make.right.equalTo(self.view).offset(-20);
+        make.bottom.equalTo(self.scrollV).offset(-20);
+        make.width.equalTo([NSNumber numberWithFloat:KScreenWidth-40]);
+    }];
+    
+    
+    NSMutableArray *btArr = [[NSMutableArray alloc]init];
+    if(self.isCartEdit){
+        [btArr addObjectsFromArray:@[self.saveBt]];
+    }else{
+        [btArr addObjectsFromArray:@[self.billTypeBt, self.shipTypeBt, self.saveBt, self.deletBt]];
+    }
+    for (UIButton *bt in btArr) {
+        UIView * v = [UIView new];
+        v.backgroundColor = [UIColor clearColor];
+        [v addSubview:bt];
+        [self.totalStackV addArrangedSubview:v];
+        [v mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.height.equalTo(bt == self.saveBt ? @45 : @30);
+        }];
+        [bt mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.top.left.bottom.equalTo(v);
+            if (bt == self.saveBt || bt == self.deletBt) {
+                make.right.equalTo(v);
+            } else {
+            }
+        }];
+        
+    }
+    
+    
+    
+}
+
+- (InTableScrollView *)scrollV {
+    if (!_scrollV) {
+        InTableScrollView *v = [[InTableScrollView alloc] init];
+        v.showsVerticalScrollIndicator = false;
+        v.showsHorizontalScrollIndicator = false;
+        v.alwaysBounceVertical = true;
+        v.backgroundColor = [UIColor whiteColor];
+        _scrollV = v;
+    }
+    return _scrollV;
+}
+
+- (UIButton *)billTypeBt {
+    if (!_billTypeBt) {
+        UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
+        [bt setImage:[UIImage imageNamed:@"login_selectRido"] forState:UIControlStateNormal];
+        [bt setImage:[UIImage imageNamed:@"login_selectRido_select"] forState:UIControlStateSelected];
+        [bt setTitle:@" SET TO THE DEFAULT BILLING ADDRESS" forState:UIControlStateNormal];
+        bt.titleLabel.font = [UIFont fontWithName:Rob_Regular size:12];
+        bt.selected = false;
+        [bt setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
+        [bt addTarget:self action:@selector(typeButtonAction:) forControlEvents:UIControlEventTouchUpInside];
+        _billTypeBt = bt;
+    }
+    return _billTypeBt;
+}
+
+- (UIButton *)shipTypeBt {
+    if (!_shipTypeBt) {
+        UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
+        [bt setImage:[UIImage imageNamed:@"login_selectRido"] forState:UIControlStateNormal];
+        [bt setImage:[UIImage imageNamed:@"login_selectRido_select"] forState:UIControlStateSelected];
+        [bt setTitle:@" SET TO THE DEFAULT SHIPPING ADDRESS" forState:UIControlStateNormal];
+        bt.titleLabel.font = [UIFont fontWithName:Rob_Regular size:12];
+        bt.selected = false;
+        [bt setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
+        [bt addTarget:self action:@selector(typeButtonAction:) forControlEvents:UIControlEventTouchUpInside];
+        _shipTypeBt = bt;
+    }
+    return _shipTypeBt;
+}
+
+- (void)typeButtonAction:(UIButton *)bt {
+    if ([bt isEqual:self.billTypeBt]) {
+        self.billTypeBt.selected = !self.billTypeBt.isSelected;
+    }
+    if ([bt isEqual:self.shipTypeBt]) {
+        self.shipTypeBt.selected = !self.shipTypeBt.selected;
+        
+    }
+}
+
+
+- (UIButton *)saveBt {
+    if (!_saveBt) {
+        UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
+        [bt setTitle:@"Save" forState:UIControlStateNormal];
+        bt.titleLabel.font = [UIFont fontWithName:Rob_Regular size:16];
+        [bt setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
+        [bt setBackgroundColor:[UIColor colorWithHexString:@"#000000"]];
+        bt.layer.cornerRadius = 4;
+        bt.layer.masksToBounds = true;
+//        bt.enabled = true;
+        [bt addTarget:self action:@selector(bottomBtAction) forControlEvents:UIControlEventTouchUpInside];
+        _saveBt = bt;
+    }
+    return _saveBt;
+}
+
+- (void)bottomBtAction {
+    [self uploadData];
+}
+
+- (UIButton *)deletBt {
+    if (!_deletBt) {
+        UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
+        NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:@"Delete"];
+        [attStr addAttributes:@{
+            NSFontAttributeName:[UIFont fontWithName:Rob_Regular size:14],
+            NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle),
+            NSUnderlineColorAttributeName:[UIColor colorWithHexString:@"#0B0B0B"]
+        } range:[attStr.string rangeOfString:attStr.string]];
+        [attStr setColor:[UIColor colorWithHexString:@"#0B0B0B"]];
+        [bt setAttributedTitle:attStr forState:UIControlStateNormal];
+        [bt addTarget:self action:@selector(deleteBtAction) forControlEvents:UIControlEventTouchUpInside];
+        _deletBt = bt;
+    }
+    return _deletBt;
+}
+
+-(void)deleteBtAction {
+    [MBProgressHUD showHUDAddedTo:self.view animated:true];
+    @weakify(self);
+    [self.vm deleteAnAddress:self.m complate:^(BOOL flag) {
+        [MBProgressHUD hideHUDForView:weak_self.view animated:true];
+        NSString *str = flag ? @"Successed" : @"Faild";
+        [weak_self.view makeToast:[NSString  stringWithFormat:@"Delete %@", str]];
+        if (flag) {
+            
+            [weak_self.navigationController popViewControllerAnimated:true];
+        }
+    }];
+}
+
+
+- (void)uploadData {
+    BOOL hadError = false;
+    for (int i = 0; i<self.tfArr.count; i++) {
+        NSString *text = self.tfArr[i].text;
+        if (text && ![text isEqualToString:@""]) {
+            self.errLbArr[i].hidden = true;
+            switch (i) {
+                case 0:
+                    self.m.firstname = text;
+                    break;
+                case 1:
+                    self.m.lastname = text;
+                    break;
+                case 2:
+                    self.m.street = text;
+                    break;
+                case 3:
+                    self.m.postcode = text;
+                    break;
+                case 4:
+                    self.m.city = text;
+                    break;
+                case 5:
+                    self.m.country = text;
+                    break;
+                case 6:
+                    self.m.region.region = text;
+                    break;
+                case 7:
+                    self.m.telephone = text;
+                    break;
+                    
+                default:
+                    break;
+            }
+        } else {
+            hadError = true;
+            self.errLbArr[i].hidden = false;
+        }
+    }
+    
+    if (self.billTypeBt.isSelected) {
+        self.m.addressType = 1;
+    }
+    if (self.shipTypeBt.isSelected) {
+        self.m.addressType = 2;
+    }
+    if ((self.shipTypeBt.isSelected && self.billTypeBt.isSelected) || self.isCartEdit){
+        self.m.addressType = 3;
+    }
+    if (hadError) {
+        return;
+    }
+    [MBProgressHUD showHUDAddedTo:self.view animated:true];
+    __weak typeof(self) weak_self = self;
+    [self.vm upAddress:self.m complate:^(BOOL success, NSString * _Nonnull addressid) {
+        [MBProgressHUD hideHUDForView:weak_self.view animated:true];
+        if (success) {
+            [weak_self.view makeToast:@"Edit Successed"];
+            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5), dispatch_get_main_queue(), ^{
+                [weak_self.navigationController popViewControllerAnimated:true];
+            });
+        } else {
+            [weak_self.view makeToast:addressid];
+        }
+        
+    }];
+    
+}
+
+- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
+    [self.view endEditing:true];
+    NSInteger tag = textField.tag-90000;
+    if (tag == 5) {//country
+        NSInteger index = 0;
+        for (int i=0;i<self.vm.countryArr.count; i++) {
+            KWCountryAddressModel *item = self.vm.countryArr[i];
+            if (item.country_id == self.m.country_id) {
+                index = i;
+            }
+        }
+        @weakify(self);
+        [BRStringPickerView showPickerWithTitle:@"Select Country" dataSourceArr:self.vm.countryNameList selectIndex:index resultBlock:^(BRResultModel * _Nullable resultModel) {
+            KWCountryAddressModel *item = weak_self.vm.countryArr[resultModel.index];
+            weak_self.tfArr[6].text = @"";
+            textField.text = item.name;
+            weak_self.m.country = item.name;
+            weak_self.m.country_id = item.country_id;
+            [weak_self.vm getProvinceList:item.country_id success:^{}];
+            
+        }];
+    }
+    if (tag == 6) {// province
+        if (self.m.country_id && ![self.m.country_id isEqualToString:@""]) {
+            
+            if (self.vm.hadGetProvince) {
+                if (self.vm.provinceArr.count > 0) {
+                    [self showProvincePick];
+                } else {
+                    return true;
+                }
+            } else {
+                [MBProgressHUD showHUDAddedTo:self.view animated:true];
+                @weakify(self);
+                [self.vm getProvinceList:self.m.country_id success:^{
+                    [MBProgressHUD hideHUDForView:weak_self.view animated:true];
+                    if (weak_self.vm.provinceArr.count > 0) {
+                        [weak_self showProvincePick];
+                    } else {
+                        [weak_self.tfArr[6] becomeFirstResponder];
+                    }
+                }];
+            }
+        } else {
+            [self.view makeToast:@"Please Select Country First"];
+        }
+    }
+    return false;
+}
+
+
+- (void)showProvincePick {
+    NSInteger index = 0;
+    for (int i=0;i<self.vm.provinceArr.count; i++) {
+        KWProvinceAddressModel *item = self.vm.provinceArr[i];
+        if (item.Id == self.m.region_id) {
+            index = i;
+        }
+    }
+    @weakify(self);
+    [BRStringPickerView showPickerWithTitle:@"Select Province" dataSourceArr:self.vm.provinceNameList selectIndex:index resultBlock:^(BRResultModel * _Nullable resultModel) {
+        KWProvinceAddressModel *item = weak_self.vm.provinceArr[resultModel.index];
+        weak_self.m.region.region = item.name;
+        weak_self.m.region_id = item.Id;
+        weak_self.tfArr[6].text = item.name;
+    }];
+}
+
+
+@end

+ 3 - 6
Asteria/Fuction/UserCenter/Setting/ASSettingViewController.m

@@ -9,6 +9,7 @@
 #import "ASSettingListCell.h"
 #import "ASInfomationSetController.h"
 #import "ASHelpListViewController.h"
+#import "ASAddressListViewController.h"
 
 @interface ASSettingViewController () <UITableViewDelegate,UITableViewDataSource>
 
@@ -148,12 +149,8 @@
             break;
         }
         case 1:{
-//            if (![self checkLogin:true]) {
-//                return;
-//            }
-//            KWAddressListViewController *vc = [KWAddressListViewController new];
-//            vc.addressType = AddressTypeSettingAddress;
-//            [self.navigationController pushViewController:vc animated:true];
+            ASAddressListViewController *vc = [ASAddressListViewController new];
+            [self.navigationController pushViewController:vc animated:true];
             break;
         }
         case 3:{

+ 6 - 0
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 23 - 0
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "编辑.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "编辑@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "编辑@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/编辑.png


BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/编辑@2x.png


BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/avater_edit.imageset/编辑@3x.png


+ 23 - 0
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "组 9776.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "组 9776@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "组 9776@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/组 9776.png


BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/组 9776@2x.png


BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_select.imageset/组 9776@3x.png


+ 23 - 0
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "组 8657.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "组 8657@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "组 8657@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/组 8657.png


BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/组 8657@2x.png


BIN
Asteria/Fuction/UserCenter/UserCenter.xcassets/address/cartship_radio_unselect.imageset/组 8657@3x.png


+ 15 - 11
Asteria/Fuction/UserCenter/VipCenter/ASBirthdayTreatViewController.m

@@ -54,9 +54,9 @@
 //        @weakify(self);
 //        [self.vm getVIPCouponData:^(BOOL flag) {
 //            if (flag) {
-//                weak_self.codeDesLB.text = [NSString stringWithFormat: @"USE CODE: %@\nGET DISCOUNT & GIFTPACK", weak_self.vm.code];
-//                NSString *birthStr = [NSString stringWithFormat: @"Our Gift To You?\n$%@ To Spend On Your\nFavorite Hair.", weak_self.vm.amount];
-//                weak_self.contentLB.text = birthStr;
+                self.codeDesLB.text = [NSString stringWithFormat: @"USE CODE: %@\nGET DISCOUNT & GIFTPACK", ASUserInfoManager.shared.code];
+                NSString *birthStr = [NSString stringWithFormat: @"Our Gift To You?\n$%@ To Spend On Your\nFavorite Hair.", ASUserInfoManager.shared.amount];
+                self.contentLB.text = birthStr;
 //            }
 //
 //
@@ -352,14 +352,18 @@
 }
 
 - (void)btnAction {
-//    if (![self.vm.typeId isEqualToString:@""]) {
-//        KWProductListViewController *vc = [[KWProductListViewController alloc] init];
-//        vc.titleName = @"HD Lace";
-//        vc.type = self.vm.typeId;
-//        [self.navigationController pushViewController:vc animated:YES];
-//    } else {
-//        [self.view makeToast:@"Get Birth Products Type Error"];
-//    }
+    if (![ASUserInfoManager.shared.typeId isEqualToString:@""]) {
+        NSDictionary *para = @{
+            @"type":ASUserInfoManager.shared.typeId,  //String
+            @"title":@"HD Lace",//String
+            @"searchKey":@"",//String
+        };
+        UIViewController *vc = [CTMediator.sharedInstance getProductListVc:para];
+        
+        [self.navigationController pushViewController:vc animated:true];
+    } else {
+        [self.view makeToast:@"Get Birth Products Type Error"];
+    }
 }
 
 - (UIView *)dateV {

+ 7 - 2
Asteria/Fuction/UserManager/info/ASUserInfoManager.h

@@ -21,8 +21,11 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, strong) ASVipModel *curVipInfo;
 @property (nonatomic, strong) ASVipModel *nextVipInfo;
 
-@property (nonatomic, strong) NSString *birthDay;
-@property (nonatomic, strong) NSString *birthStatus;
+@property (nonatomic, copy) NSString *birthDay;
+@property (nonatomic, copy) NSString *birthStatus;
+@property (nonatomic, copy) NSString *amount;
+@property (nonatomic, copy) NSString *code;
+@property (nonatomic, copy) NSString *typeId;
 
 /// 以获取总积分
 @property (nonatomic, copy) NSString *pointAmount;
@@ -33,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 - (void)getInfo;
 
+- (void)baseInfoNet:(void(^)(void))comp;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 10 - 2
Asteria/Fuction/UserManager/info/ASUserInfoManager.m

@@ -35,18 +35,22 @@
 }
 
 - (void)getInfo {
-    [self baseInfoNet];
+    [self baseInfoNet:^{
+        
+    }];
     [self vipInfoNet];
     [self birthInfoNet];
 }
 
-- (void)baseInfoNet {
+- (void)baseInfoNet:(void(^)(void))comp {
     [ASNetTools.shared getWithPath:userinfoUrl param:@{} success:^(id _Nonnull json) {
         self.userInfo = [ASUserModel mj_objectWithKeyValues:json];
         [NSNotificationCenter.defaultCenter postNotificationName:UserInfoUpdate object:nil];
         NSLog(@"----userInfo:%@-----id:%@----issub:%u----address:%@----", self.userInfo, self.userInfo.Id, self.userInfo.is_subscribed, self.userInfo.addresses);
+        comp();
     } faild:^(NSString * _Nonnull code, NSString * _Nonnull msg) {
         [Current_normalTool.currentNav.topViewController.view makeToast:msg];
+        comp();
     }];
 }
 
@@ -54,6 +58,10 @@
     [ASNetTools.shared getWithPath:userBirthUrl param:@{} success:^(id _Nonnull json) {
         self.birthDay = [NSString stringWithFormat:@"%@", json[@"birthDay"]];
         self.birthStatus = [NSString stringWithFormat:@"%@", json[@"status"]];
+        self.amount = [NSString stringWithFormat:@"%@", json[@"amount"]];
+        self.code = [NSString stringWithFormat:@"%@", json[@"code"]];
+        self.typeId = [NSString stringWithFormat:@"%@", json[@"typeid"]];
+        
     } faild:^(NSString * _Nonnull code, NSString * _Nonnull msg) {
         [Current_normalTool.currentNav.topViewController.view makeToast:msg];
     }];

+ 6 - 0
Asteria/Fuction/UserManager/info/ASUserModel.h

@@ -22,11 +22,16 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface ASAddressModel : NSObject
 
+/// 0:啥也不是   默认地址 1:bill Adress   2:shiping Address      3:bill Adress 和shiping Address
+@property (nonatomic, assign) NSInteger addressType;
+
+@property (nonatomic, copy) NSString *title;
 @property (nonatomic, copy) NSString *Id;
 @property (nonatomic, copy) NSString *customer_id;
 @property (nonatomic, strong) ASAddressReginModel *region;
 @property (nonatomic, copy) NSString *region_id;
 @property (nonatomic, copy) NSString *country_id;
+@property (nonatomic, copy) NSString *country;
 @property (nonatomic, copy) NSString *street;
 @property (nonatomic, copy) NSString *company;
 @property (nonatomic, copy) NSString *telephone;
@@ -37,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, copy) NSString *default_shipping;
 @property (nonatomic, copy) NSString *default_billing;
 
++ (ASAddressModel *)defualtData;
 
 @end
 

+ 28 - 0
Asteria/Fuction/UserManager/info/ASUserModel.m

@@ -43,9 +43,37 @@
 + (NSDictionary *)mj_replacedKeyFromPropertyName {
     return @{
         @"Id": @"id",
+        @"title": @"extension_attributes.title",
+        @"country": @"extension_attributes.country",
+        @"addressType": @"extension_attributes.address_type",
     };
 }
 
+
++ (ASAddressModel *)defualtData {
+
+    ASAddressModel *m = [ASAddressModel new];
+    m.title = @"";
+    m.firstname = @"";
+    m.lastname = @"";
+    m.street = @"";
+    m.postcode = @"";
+    m.city = @"";
+//    m.region = @"";
+    ASAddressReginModel *re = [ASAddressReginModel new];
+    re.region = @"";
+    re.region_code = @"";
+    re.region_id = @"";
+    m.region = re;
+    m.region_id = @"";
+    m.telephone = @"";
+    m.country = @"Unites States";
+    m.country_id = @"US";
+    
+
+    return m;
+}
+
 @end
 
 @implementation ASAddressReginModel

+ 2 - 0
Asteria/NetTools/ASNetApis.h

@@ -39,6 +39,8 @@
 #define getAllCategoriesUrl BaseRequestrUrl(@"rewrite/categories")
 // 热词
 #define getHotList BaseRequestrUrl(@"hot/index")
+// 联想词
+#define getLinkingKey BaseRequestrUrl(@"searchsuiteautocomplete/index")
 
 
 // MARK: - userinfo

+ 16 - 0
Asteria/Third/InTableScrollView.h

@@ -0,0 +1,16 @@
+//
+//  InTableScrollView.h
+//  westkissMob
+//
+//  Created by iOS on 2024/3/12.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface InTableScrollView : UIScrollView
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 27 - 0
Asteria/Third/InTableScrollView.m

@@ -0,0 +1,27 @@
+//
+//  InTableScrollView.m
+//  westkissMob
+//
+//  Created by iOS on 2024/3/12.
+//
+
+#import "InTableScrollView.h"
+
+@implementation InTableScrollView
+
+- (BOOL)canCancelContentTouches {
+    return true;
+}
+
+- (BOOL)delaysContentTouches {
+    return false;
+}
+
+- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
+    if ([view isKindOfClass:[UITableView class]]) {
+        return false;
+    }
+    return [super touchesShouldCancelInContentView:view];
+}
+
+@end

+ 1 - 1
Podfile.lock

@@ -1283,4 +1283,4 @@ SPEC CHECKSUMS:
 
 PODFILE CHECKSUM: ba7c106e897ece4e287441c9de35990e024aa198
 
-COCOAPODS: 1.15.2
+COCOAPODS: 1.13.0

+ 21 - 0
Pods/LookinServer/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) [2023] [LI KAI]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 73 - 0
Pods/LookinServer/README.md

@@ -0,0 +1,73 @@
+![Preview](https://cdn.lookin.work/public/style/images/independent/homepage/preview_en_1x.jpg "Preview")
+
+# Introduction
+You can inspect and modify views in iOS app via Lookin, just like UI Inspector in Xcode, or another app called Reveal.
+
+Official Website:https://lookin.work/
+
+# Integration Guide
+To use Lookin macOS app, you need to integrate LookinServer (iOS Framework of Lookin) into your iOS project.
+
+> **Warning**
+> 1. Never integrate LookinServer in Release building configuration.
+> 2. Do not use versions earlier than 1.0.6, as it contains a critical bug that could lead to online incidents in your project: https://qxh1ndiez2w.feishu.cn/wiki/Z9SpwT7zWiqvYvkBe7Lc6Disnab
+
+## via CocoaPods:
+### Swift Project
+`pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']`
+### Objective-C Project
+`pod 'LookinServer', :configurations => ['Debug']`
+## via Swift Package Manager:
+`https://github.com/QMUI/LookinServer/`
+
+# Repository
+LookinServer: https://github.com/QMUI/LookinServer
+
+macOS app: https://github.com/hughkli/Lookin/
+
+# Tips
+- How to display custom information in Lookin: https://bytedance.larkoffice.com/docx/TRridRXeUoErMTxs94bcnGchnlb
+- How to display more member variables in Lookin: https://bytedance.larkoffice.com/docx/CKRndHqdeoub11xSqUZcMlFhnWe
+- How to turn on Swift optimization for Lookin: https://bytedance.larkoffice.com/docx/GFRLdzpeKoakeyxvwgCcZ5XdnTb
+- Documentation Collection: https://bytedance.larkoffice.com/docx/Yvv1d57XQoe5l0xZ0ZRc0ILfnWb
+
+# Acknowledgements
+https://qxh1ndiez2w.feishu.cn/docx/YIFjdE4gIolp3hxn1tGckiBxnWf
+
+---
+# 简介
+Lookin 可以查看与修改 iOS App 里的 UI 对象,类似于 Xcode 自带的 UI Inspector 工具,或另一款叫做 Reveal 的软件。
+
+官网:https://lookin.work/
+
+# 安装 LookinServer Framework
+如果这是你的 iOS 项目第一次使用 Lookin,则需要先把 LookinServer 这款 iOS Framework 集成到你的 iOS 项目中。
+
+> **Warning**
+> 
+> 1. 不要在 AppStore 模式下集成 LookinServer。
+> 2. 不要使用早于 1.0.6 的版本,因为它包含一个严重 Bug,可能导致线上事故: https://qxh1ndiez2w.feishu.cn/wiki/Z9SpwT7zWiqvYvkBe7Lc6Disnab
+## 通过 CocoaPods:
+
+### Swift 项目
+`pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']`
+### Objective-C 项目
+`pod 'LookinServer', :configurations => ['Debug']`
+
+## 通过 Swift Package Manager:
+`https://github.com/QMUI/LookinServer/`
+
+# 源代码仓库
+
+iOS 端 LookinServer:https://github.com/QMUI/LookinServer
+
+macOS 端软件:https://github.com/hughkli/Lookin/
+
+# 技巧
+- 如何在 Lookin 中展示自定义信息: https://bytedance.larkoffice.com/docx/TRridRXeUoErMTxs94bcnGchnlb
+- 如何在 Lookin 中展示更多成员变量: https://bytedance.larkoffice.com/docx/CKRndHqdeoub11xSqUZcMlFhnWe
+- 如何为 Lookin 开启 Swift 优化: https://bytedance.larkoffice.com/docx/GFRLdzpeKoakeyxvwgCcZ5XdnTb
+- 文档汇总:https://bytedance.larkoffice.com/docx/Yvv1d57XQoe5l0xZ0ZRc0ILfnWb
+
+# 鸣谢
+https://qxh1ndiez2w.feishu.cn/docx/YIFjdE4gIolp3hxn1tGckiBxnWf

+ 40 - 0
Pods/LookinServer/Src/Base/LookinIvarTrace.h

@@ -0,0 +1,40 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LookinIvarTrace.h
+//  Lookin
+//
+//  Created by Li Kai on 2019/4/30.
+//  https://lookin.work
+//
+
+#import <Foundation/Foundation.h>
+
+extern NSString *const LookinIvarTraceRelationValue_Self;
+
+/// 如果 hostClassName 和 ivarName 均 equal,则认为两个 LookinIvarTrace 对象彼此 equal
+/// 比如 A 是 B 的 superview,且 A 的 "_stageView" 指向 B,则 B 会有一个 LookinIvarTrace:hostType 为 “superview”,hostClassName 为 A 的 class,ivarName 为 “_stageView”
+@interface LookinIvarTrace : NSObject <NSSecureCoding, NSCopying>
+
+/// 该值可能是 "superview"、"superlayer"、“self” 或 nil
+@property(nonatomic, copy) NSString *relation;
+
+@property(nonatomic, copy) NSString *hostClassName;
+
+@property(nonatomic, copy) NSString *ivarName;
+
+#pragma mark - No Coding
+
+#if TARGET_OS_IPHONE
+@property(nonatomic, weak) id hostObject;
+#endif
+
+@end
+
+@interface NSObject (LookinServerTrace)
+
+@property(nonatomic, copy) NSArray<LookinIvarTrace *> *lks_ivarTraces;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 70 - 0
Pods/LookinServer/Src/Base/LookinIvarTrace.m

@@ -0,0 +1,70 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LookinIvarTrace.m
+//  Lookin
+//
+//  Created by Li Kai on 2019/4/30.
+//  https://lookin.work
+//
+
+#import "LookinIvarTrace.h"
+
+NSString *const LookinIvarTraceRelationValue_Self = @"self";
+
+@implementation LookinIvarTrace
+
+#pragma mark - Equal
+
+- (NSUInteger)hash {
+    return self.hostClassName.hash ^ self.ivarName.hash;
+}
+
+- (BOOL)isEqual:(id)object {
+    if (self == object) {
+        return YES;
+    }
+    if (![object isKindOfClass:[LookinIvarTrace class]]) {
+        return NO;
+    }
+    LookinIvarTrace *comparedObj = object;
+    if ([self.hostClassName isEqualToString:comparedObj.hostClassName] && [self.ivarName isEqualToString:comparedObj.ivarName]) {
+        return YES;
+    }
+    return NO;
+}
+
+#pragma mark - <NSCopying>
+    
+- (id)copyWithZone:(NSZone *)zone {
+    LookinIvarTrace *newTrace = [[LookinIvarTrace allocWithZone:zone] init];
+    newTrace.relation = self.relation;
+    newTrace.hostClassName = self.hostClassName;
+    newTrace.ivarName = self.ivarName;
+    return newTrace;
+}
+
+#pragma mark - <NSCoding>
+
+- (void)encodeWithCoder:(NSCoder *)aCoder {
+    [aCoder encodeObject:self.relation forKey:@"relation"];
+    [aCoder encodeObject:self.hostClassName forKey:@"hostClassName"];
+    [aCoder encodeObject:self.ivarName forKey:@"ivarName"];
+}
+
+- (instancetype)initWithCoder:(NSCoder *)aDecoder {
+    if (self = [super init]) {
+        self.relation = [aDecoder decodeObjectForKey:@"relation"];
+        self.hostClassName = [aDecoder decodeObjectForKey:@"hostClassName"];
+        self.ivarName = [aDecoder decodeObjectForKey:@"ivarName"];
+    }
+    return self;
+}
+
++ (BOOL)supportsSecureCoding {
+    return YES;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 41 - 0
Pods/LookinServer/Src/Main/Server/Category/CALayer+LookinServer.h

@@ -0,0 +1,41 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIView+LookinMobile.h
+//  WeRead
+//
+//  Created by Li Kai on 2018/11/30.
+//  Copyright © 2018 tencent. All rights reserved.
+//
+
+#import "LookinDefines.h"
+#import "TargetConditionals.h"
+#import <UIKit/UIKit.h>
+
+@interface CALayer (LookinServer)
+
+/// 如果 myView.layer == myLayer,则 myLayer.lks_hostView 会返回 myView
+@property(nonatomic, readonly, weak) UIView *lks_hostView;
+
+- (UIWindow *)lks_window;
+
+- (CGRect)lks_frameInWindow:(UIWindow *)window;
+
+- (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality;
+/// 当没有 sublayers 时,该方法返回 nil
+- (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality;
+
+/// 获取和该对象有关的对象的 Class 层级树
+- (NSArray<NSArray<NSString *> *> *)lks_relatedClassChainList;
+
+- (NSArray<NSString *> *)lks_selfRelation;
+
+@property(nonatomic, strong) UIColor *lks_backgroundColor;
+@property(nonatomic, strong) UIColor *lks_borderColor;
+@property(nonatomic, strong) UIColor *lks_shadowColor;
+@property(nonatomic, assign) CGFloat lks_shadowOffsetWidth;
+@property(nonatomic, assign) CGFloat lks_shadowOffsetHeight;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 233 - 0
Pods/LookinServer/Src/Main/Server/Category/CALayer+LookinServer.m

@@ -0,0 +1,233 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIView+LookinMobile.m
+//  WeRead
+//
+//  Created by Li Kai on 2018/11/30.
+//  Copyright © 2018 tencent. All rights reserved.
+//
+
+#import "CALayer+LookinServer.h"
+#import "LKS_HierarchyDisplayItemsMaker.h"
+#import "LookinDisplayItem.h"
+#import <objc/runtime.h>
+#import "LKS_ConnectionManager.h"
+#import "LookinIvarTrace.h"
+#import "LookinServerDefines.h"
+#import "UIColor+LookinServer.h"
+#import "LKS_MultiplatformAdapter.h"
+
+@implementation CALayer (LookinServer)
+
+- (UIWindow *)lks_window {
+    CALayer *layer = self;
+    while (layer) {
+        UIView *hostView = layer.lks_hostView;
+        if (hostView.window) {
+            return hostView.window;
+        } else if ([hostView isKindOfClass:[UIWindow class]]) {
+            return (UIWindow *)hostView;
+        }
+        layer = layer.superlayer;
+    }
+    return nil;
+}
+
+- (CGRect)lks_frameInWindow:(UIWindow *)window {
+    UIWindow *selfWindow = [self lks_window];
+    if (!selfWindow) {
+        return CGRectZero;
+    }
+    
+    CGRect rectInSelfWindow = [selfWindow.layer convertRect:self.frame fromLayer:self.superlayer];
+    CGRect rectInWindow = [window convertRect:rectInSelfWindow fromWindow:selfWindow];
+    return rectInWindow;
+}
+
+#pragma mark - Host View
+
+- (UIView *)lks_hostView {
+    if (self.delegate && [self.delegate isKindOfClass:UIView.class]) {
+        UIView *view = (UIView *)self.delegate;
+        if (view.layer == self) {
+            return view;
+        }
+    }
+    return nil;
+}
+
+#pragma mark - Screenshot
+
+- (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality {
+    
+    CGFloat screenScale = [LKS_MultiplatformAdapter mainScreenScale];
+    CGFloat pixelWidth = self.frame.size.width * screenScale;
+    CGFloat pixelHeight = self.frame.size.height * screenScale;
+    if (pixelWidth <= 0 || pixelHeight <= 0) {
+        return nil;
+    }
+    
+    CGFloat renderScale = lowQuality ? 1 : 0;
+    CGFloat maxLength = MAX(pixelWidth, pixelHeight);
+    if (maxLength > LookinNodeImageMaxLengthInPx) {
+        // 确保最终绘制出的图片长和宽都不能超过 LookinNodeImageMaxLengthInPx
+        // 如果算出的 renderScale 大于 1 则取 1,因为似乎用 1 渲染的速度要比一个别的奇怪的带小数点的数字要更快
+        renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1);
+    }
+    
+    CGSize contextSize = self.frame.size;
+    if (contextSize.width <= 0 || contextSize.height <= 0 || contextSize.width > 20000 || contextSize.height > 20000) {
+        NSLog(@"LookinServer - Failed to capture screenshot. Invalid context size: %@ x %@", @(contextSize.width), @(contextSize.height));
+        return nil;
+    }
+    UIGraphicsBeginImageContextWithOptions(contextSize, NO, renderScale);
+    CGContextRef context = UIGraphicsGetCurrentContext();
+    if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) {
+        [self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES];
+    } else {
+        [self renderInContext:context];
+    }
+    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+    UIGraphicsEndImageContext();
+    return image;
+}
+
+- (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality {
+    if (!self.sublayers.count) {
+        return nil;
+    }
+    
+    CGFloat screenScale = [LKS_MultiplatformAdapter mainScreenScale];
+    CGFloat pixelWidth = self.frame.size.width * screenScale;
+    CGFloat pixelHeight = self.frame.size.height * screenScale;
+    if (pixelWidth <= 0 || pixelHeight <= 0) {
+        return nil;
+    }
+    
+    CGFloat renderScale = lowQuality ? 1 : 0;
+    CGFloat maxLength = MAX(pixelWidth, pixelHeight);
+    if (maxLength > LookinNodeImageMaxLengthInPx) {
+        // 确保最终绘制出的图片长和宽都不能超过 LookinNodeImageMaxLengthInPx
+        // 如果算出的 renderScale 大于 1 则取 1,因为似乎用 1 渲染的速度要比一个别的奇怪的带小数点的数字要更快
+        renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1);
+    }
+    
+    if (self.sublayers.count) {
+        NSArray<CALayer *> *sublayers = [self.sublayers copy];
+        NSMutableArray<CALayer *> *visibleSublayers = [NSMutableArray arrayWithCapacity:sublayers.count];
+        [sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
+            if (!sublayer.hidden) {
+                sublayer.hidden = YES;
+                [visibleSublayers addObject:sublayer];
+            }
+        }];
+        
+        CGSize contextSize = self.frame.size;
+        if (contextSize.width <= 0 || contextSize.height <= 0 || contextSize.width > 20000 || contextSize.height > 20000) {
+            NSLog(@"LookinServer - Failed to capture screenshot. Invalid context size: %@ x %@", @(contextSize.width), @(contextSize.height));
+            return nil;
+        }
+        
+        UIGraphicsBeginImageContextWithOptions(contextSize, NO, renderScale);
+        CGContextRef context = UIGraphicsGetCurrentContext();
+        if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) {
+            [self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES];
+        } else {
+            [self renderInContext:context];
+        }
+        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+        UIGraphicsEndImageContext();
+        
+        [visibleSublayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
+            sublayer.hidden = NO;
+        }];
+        
+        return image;
+    }
+    return nil;
+}
+
+- (NSArray<NSArray<NSString *> *> *)lks_relatedClassChainList {
+    NSMutableArray *array = [NSMutableArray arrayWithCapacity:2];
+    if (self.lks_hostView) {
+        [array addObject:[CALayer lks_getClassListOfObject:self.lks_hostView endingClass:@"UIView"]];
+        UIViewController* vc = [self.lks_hostView lks_findHostViewController];
+        if (vc) {
+            [array addObject:[CALayer lks_getClassListOfObject:vc endingClass:@"UIViewController"]];
+        }
+    } else {
+        [array addObject:[CALayer lks_getClassListOfObject:self endingClass:@"CALayer"]];
+    }
+    return array.copy;
+}
+
++ (NSArray<NSString *> *)lks_getClassListOfObject:(id)object endingClass:(NSString *)endingClass {
+    NSArray<NSString *> *completedList = [object lks_classChainList];
+    NSUInteger endingIdx = [completedList indexOfObject:endingClass];
+    if (endingIdx != NSNotFound) {
+        completedList = [completedList subarrayWithRange:NSMakeRange(0, endingIdx + 1)];
+    }
+    return completedList;
+}
+
+- (NSArray<NSString *> *)lks_selfRelation {
+    NSMutableArray *array = [NSMutableArray array];
+    NSMutableArray<LookinIvarTrace *> *ivarTraces = [NSMutableArray array];
+    if (self.lks_hostView) {
+        UIViewController* vc = [self.lks_hostView lks_findHostViewController];
+        if (vc) {
+            [array addObject:[NSString stringWithFormat:@"(%@ *).view", NSStringFromClass(vc.class)]];
+            
+            [ivarTraces addObjectsFromArray:vc.lks_ivarTraces];
+        }
+        [ivarTraces addObjectsFromArray:self.lks_hostView.lks_ivarTraces];
+    } else {
+        [ivarTraces addObjectsFromArray:self.lks_ivarTraces];
+    }
+    if (ivarTraces.count) {
+        [array addObjectsFromArray:[ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) {
+            return [NSString stringWithFormat:@"(%@ *) -> %@", value.hostClassName, value.ivarName];
+        }]];
+    }
+    return array.count ? array.copy : nil;
+}
+
+- (UIColor *)lks_backgroundColor {
+    return [UIColor lks_colorWithCGColor:self.backgroundColor];
+}
+- (void)setLks_backgroundColor:(UIColor *)lks_backgroundColor {
+    self.backgroundColor = lks_backgroundColor.CGColor;
+}
+
+- (UIColor *)lks_borderColor {
+    return [UIColor lks_colorWithCGColor:self.borderColor];
+}
+- (void)setLks_borderColor:(UIColor *)lks_borderColor {
+    self.borderColor = lks_borderColor.CGColor;
+}
+
+- (UIColor *)lks_shadowColor {
+    return [UIColor lks_colorWithCGColor:self.shadowColor];
+}
+- (void)setLks_shadowColor:(UIColor *)lks_shadowColor {
+    self.shadowColor = lks_shadowColor.CGColor;
+}
+
+- (CGFloat)lks_shadowOffsetWidth {
+    return self.shadowOffset.width;
+}
+- (void)setLks_shadowOffsetWidth:(CGFloat)lks_shadowOffsetWidth {
+    self.shadowOffset = CGSizeMake(lks_shadowOffsetWidth, self.shadowOffset.height);
+}
+
+- (CGFloat)lks_shadowOffsetHeight {
+    return self.shadowOffset.height;
+}
+- (void)setLks_shadowOffsetHeight:(CGFloat)lks_shadowOffsetHeight {
+    self.shadowOffset = CGSizeMake(self.shadowOffset.width, lks_shadowOffsetHeight);
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 41 - 0
Pods/LookinServer/Src/Main/Server/Category/NSObject+LookinServer.h

@@ -0,0 +1,41 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  NSObject+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/4/21.
+//  https://lookin.work
+//
+
+#import "LookinDefines.h"
+#import <Foundation/Foundation.h>
+
+@class LookinIvarTrace;
+
+@interface NSObject (LookinServer)
+
+#pragma mark - oid
+
+/// 如果 oid 不存在则会创建新的 oid
+- (unsigned long)lks_registerOid;
+
+/// 0 表示不存在
+@property(nonatomic, assign) unsigned long lks_oid;
+
++ (NSObject *)lks_objectWithOid:(unsigned long)oid;
+
+#pragma mark - trace
+
+@property(nonatomic, copy) NSString *lks_specialTrace;
+
++ (void)lks_clearAllObjectsTraces;
+
+/**
+ 获取当前对象的 Class 层级树,如 @[@"UIView", @"UIResponder", @"NSObject"]。未 demangle,有 Swift Module Name
+ */
+- (NSArray<NSString *> *)lks_classChainList;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 99 - 0
Pods/LookinServer/Src/Main/Server/Category/NSObject+LookinServer.m

@@ -0,0 +1,99 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  NSObject+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/4/21.
+//  https://lookin.work
+//
+
+#import "NSObject+Lookin.h"
+#import "NSObject+LookinServer.h"
+#import "LookinServerDefines.h"
+#import "LKS_ObjectRegistry.h"
+#import <objc/runtime.h>
+
+@implementation NSObject (LookinServer)
+
+#pragma mark - oid
+
+- (unsigned long)lks_registerOid {
+    if (!self.lks_oid) {
+        unsigned long oid = [[LKS_ObjectRegistry sharedInstance] addObject:self];
+        self.lks_oid = oid;
+    }
+    return self.lks_oid;
+}
+
+- (void)setLks_oid:(unsigned long)lks_oid {
+    [self lookin_bindObject:@(lks_oid) forKey:@"lks_oid"];
+}
+
+- (unsigned long)lks_oid {
+    NSNumber *number = [self lookin_getBindObjectForKey:@"lks_oid"];
+    return [number unsignedLongValue];
+}
+
++ (NSObject *)lks_objectWithOid:(unsigned long)oid {
+    return [[LKS_ObjectRegistry sharedInstance] objectWithOid:oid];
+}
+
+#pragma mark - trace
+
+- (void)setLks_ivarTraces:(NSArray<LookinIvarTrace *> *)lks_ivarTraces {
+    [self lookin_bindObject:lks_ivarTraces.copy forKey:@"lks_ivarTraces"];
+    
+    if (lks_ivarTraces) {
+        [[NSObject lks_allObjectsWithTraces] addPointer:(void *)self];
+    }
+}
+
+- (NSArray<LookinIvarTrace *> *)lks_ivarTraces {
+    return [self lookin_getBindObjectForKey:@"lks_ivarTraces"];
+}
+
+- (void)setLks_specialTrace:(NSString *)lks_specialTrace {
+    [self lookin_bindObject:lks_specialTrace forKey:@"lks_specialTrace"];
+    if (lks_specialTrace) {
+        [[NSObject lks_allObjectsWithTraces] addPointer:(void *)self];
+    }
+}
+- (NSString *)lks_specialTrace {
+    return [self lookin_getBindObjectForKey:@"lks_specialTrace"];
+}
+
++ (void)lks_clearAllObjectsTraces {
+    [[[NSObject lks_allObjectsWithTraces] allObjects] enumerateObjectsUsingBlock:^(NSObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        obj.lks_ivarTraces = nil;
+        obj.lks_specialTrace = nil;
+    }];
+    [NSObject lks_allObjectsWithTraces].count = 0;
+}
+
++ (NSPointerArray *)lks_allObjectsWithTraces {
+    static dispatch_once_t onceToken;
+    static NSPointerArray *lks_allObjectsWithTraces = nil;
+    dispatch_once(&onceToken,^{
+        lks_allObjectsWithTraces = [NSPointerArray weakObjectsPointerArray];
+    });
+    return lks_allObjectsWithTraces;
+}
+
+- (NSArray<NSString *> *)lks_classChainList {
+    NSMutableArray<NSString *> *classChainList = [NSMutableArray array];
+    Class currentClass = self.class;
+    
+    while (currentClass) {
+        NSString *currentClassName = NSStringFromClass(currentClass);
+        if (currentClassName) {
+            [classChainList addObject:currentClassName];
+        }
+        currentClass = [currentClass superclass];
+    }
+    return classChainList.copy;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Category/UIBlurEffect+LookinServer.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIBlurEffect+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/10/8.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UIBlurEffect (LookinServer)
+
+/// 该 number 包装的对象是 UIBlurEffectStyle,之所以用 NSNumber 是因为想把 0 和 nil 区分开,毕竟这里是在 hook 系统,稳一点好。
+/// 该方法的实现需要 Hook,因此若定义了 LOOKIN_SERVER_DISABLE_HOOK 宏,则属性会返回 nil
+@property(nonatomic, strong) NSNumber *lks_effectStyleNumber;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 57 - 0
Pods/LookinServer/Src/Main/Server/Category/UIBlurEffect+LookinServer.m

@@ -0,0 +1,57 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIBlurEffect+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/10/8.
+//  https://lookin.work
+//
+
+#import "UIBlurEffect+LookinServer.h"
+#import "NSObject+Lookin.h"
+#import <objc/runtime.h>
+
+@implementation UIBlurEffect (LookinServer)
+
+#ifdef LOOKIN_SERVER_DISABLE_HOOK
+
+- (void)setLks_effectStyleNumber:(NSNumber *)lks_effectStyleNumber {
+}
+
+- (NSNumber *)lks_effectStyleNumber {
+    return nil;
+}
+
+#else
+
++ (void)load {
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Method oriMethod = class_getClassMethod([self class], @selector(effectWithStyle:));
+        Method newMethod = class_getClassMethod([self class], @selector(lks_effectWithStyle:));
+        method_exchangeImplementations(oriMethod, newMethod);
+    });
+}
+
++ (UIBlurEffect *)lks_effectWithStyle:(UIBlurEffectStyle)style {
+    id effect = [self lks_effectWithStyle:style];
+    if ([effect respondsToSelector:@selector(setLks_effectStyleNumber:)]) {
+        [effect setLks_effectStyleNumber:@(style)];
+    }
+    return effect;
+}
+
+- (void)setLks_effectStyleNumber:(NSNumber *)lks_effectStyleNumber {
+    [self lookin_bindObject:lks_effectStyleNumber forKey:@"lks_effectStyleNumber"];
+}
+
+- (NSNumber *)lks_effectStyleNumber {
+    return [self lookin_getBindObjectForKey:@"lks_effectStyleNumber"];
+}
+
+#endif /* LOOKIN_SERVER_DISABLE_HOOK */
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 26 - 0
Pods/LookinServer/Src/Main/Server/Category/UIColor+LookinServer.h

@@ -0,0 +1,26 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIColor+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/5.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UIColor (LookinServer)
+
+- (NSArray<NSNumber *> *)lks_rgbaComponents;
++ (instancetype)lks_colorFromRGBAComponents:(NSArray<NSNumber *> *)components;
+
+- (NSString *)lks_rgbaString;
+- (NSString *)lks_hexString;
+
+/// will check if the argument is a real CGColor
++ (UIColor *)lks_colorWithCGColor:(CGColorRef)cgColor;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 183 - 0
Pods/LookinServer/Src/Main/Server/Category/UIColor+LookinServer.m

@@ -0,0 +1,183 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIColor+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/5.
+//  https://lookin.work
+//
+
+#import "UIColor+LookinServer.h"
+
+@implementation UIColor (LookinServer)
+
+- (NSArray<NSNumber *> *)lks_rgbaComponents {
+    CGFloat r, g, b, a;
+    CGColorRef cgColor = [self CGColor];
+    const CGFloat *components = CGColorGetComponents(cgColor);
+    if (CGColorGetNumberOfComponents(cgColor) == 4) {
+        r = components[0];
+        g = components[1];
+        b = components[2];
+        a = components[3];
+    } else if (CGColorGetNumberOfComponents(cgColor) == 2) {
+        r = components[0];
+        g = components[0];
+        b = components[0];
+        a = components[1];
+    } else if (CGColorGetNumberOfComponents(cgColor) == 1) {
+        r = components[0];
+        g = components[0];
+        b = components[0];
+        a = components[0];
+    } else {
+        r = 0;
+        g = 0;
+        b = 0;
+        a = 0;
+        NSAssert(NO, @"");
+    }
+    NSArray<NSNumber *> *rgba = @[@(r), @(g), @(b), @(a)];
+    return rgba;
+}
+
++ (instancetype)lks_colorFromRGBAComponents:(NSArray<NSNumber *> *)components {
+    if (!components) {
+        return nil;
+    }
+    if (components.count != 4) {
+        NSAssert(NO, @"");
+        return nil;
+    }
+    UIColor *color = [UIColor colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue];
+    return color;
+}
+
+- (NSString *)lks_rgbaString {
+    CGFloat r, g, b, a;
+    CGColorRef cgColor = [self CGColor];
+    const CGFloat *components = CGColorGetComponents(cgColor);
+    if (CGColorGetNumberOfComponents(cgColor) == 4) {
+        r = components[0];
+        g = components[1];
+        b = components[2];
+        a = components[3];
+    } else if (CGColorGetNumberOfComponents(cgColor) == 2) {
+        r = components[0];
+        g = components[0];
+        b = components[0];
+        a = components[1];
+    } else {
+        r = 0;
+        g = 0;
+        b = 0;
+        a = 0;
+        NSAssert(NO, @"");
+    }
+    
+    if (a >= 1) {
+        return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f)", r * 255, g * 255, b * 255];
+    } else {
+        return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %.2f)", r * 255, g * 255, b * 255, a];
+    }
+}
+
+- (NSString *)lks_hexString {
+    CGFloat r, g, b, a;
+    CGColorRef cgColor = [self CGColor];
+    const CGFloat *components = CGColorGetComponents(cgColor);
+    if (CGColorGetNumberOfComponents(cgColor) == 4) {
+        r = components[0];
+        g = components[1];
+        b = components[2];
+        a = components[3];
+    } else if (CGColorGetNumberOfComponents(cgColor) == 2) {
+        r = components[0];
+        g = components[0];
+        b = components[0];
+        a = components[1];
+    } else {
+        r = 0;
+        g = 0;
+        b = 0;
+        a = 0;
+        NSAssert(NO, @"");
+    }
+    
+    NSInteger red = r * 255;
+    NSInteger green = g * 255;
+    NSInteger blue = b * 255;
+    NSInteger alpha = a * 255;
+    
+    return [[NSString stringWithFormat:@"#%@%@%@%@",
+             [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:alpha]],
+             [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:red]],
+             [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:green]],
+             [UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:blue]]] lowercaseString];
+}
+
+// 对于色值只有单位数的,在前面补一个0,例如“F”会补齐为“0F”
++ (NSString *)_alignColorHexStringLength:(NSString *)hexString {
+    return hexString.length < 2 ? [@"0" stringByAppendingString:hexString] : hexString;
+}
+
++ (NSString *)_hexStringWithInteger:(NSInteger)integer {
+    NSString *hexString = @"";
+    NSInteger remainder = 0;
+    for (NSInteger i = 0; i < 9; i++) {
+        remainder = integer % 16;
+        integer = integer / 16;
+        NSString *letter = [self _hexLetterStringWithInteger:remainder];
+        hexString = [letter stringByAppendingString:hexString];
+        if (integer == 0) {
+            break;
+        }
+        
+    }
+    return hexString;
+}
+
++ (NSString *)_hexLetterStringWithInteger:(NSInteger)integer {
+    NSAssert(integer < 16, @"要转换的数必须是16进制里的个位数,也即小于16,但你传给我是%@", @(integer));
+    
+    NSString *letter = nil;
+    switch (integer) {
+        case 10:
+            letter = @"A";
+            break;
+        case 11:
+            letter = @"B";
+            break;
+        case 12:
+            letter = @"C";
+            break;
+        case 13:
+            letter = @"D";
+            break;
+        case 14:
+            letter = @"E";
+            break;
+        case 15:
+            letter = @"F";
+            break;
+        default:
+            letter = [[NSString alloc]initWithFormat:@"%@", @(integer)];
+            break;
+    }
+    return letter;
+}
+
++ (UIColor *)lks_colorWithCGColor:(CGColorRef)cgColor {
+    if (!cgColor) {
+        return nil;
+    }
+    if (CFGetTypeID(cgColor) != CGColorGetTypeID()) {
+        return nil;
+    }
+    return [UIColor colorWithCGColor:cgColor];
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 22 - 0
Pods/LookinServer/Src/Main/Server/Category/UIImage+LookinServer.h

@@ -0,0 +1,22 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIImage+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/5/14.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UIImage (LookinServer)
+
+/// 该方法的实现需要 Hook,因此若定义了 LOOKIN_SERVER_DISABLE_HOOK 宏,则属性会返回 nil
+@property(nonatomic, copy) NSString *lks_imageSourceName;
+
+- (NSData *)lookin_data;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 95 - 0
Pods/LookinServer/Src/Main/Server/Category/UIImage+LookinServer.m

@@ -0,0 +1,95 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIImage+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/5/14.
+//  https://lookin.work
+//
+
+#import <objc/runtime.h>
+#import "UIImage+LookinServer.h"
+#import "LookinServerDefines.h"
+
+@implementation UIImage (LookinServer)
+
+#ifdef LOOKIN_SERVER_DISABLE_HOOK
+
+- (void)setLks_imageSourceName:(NSString *)lks_imageSourceName {
+}
+
+- (NSString *)lks_imageSourceName {
+    return nil;
+}
+
+#else
+
++ (void)load {
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        Method oriMethod = class_getClassMethod([self class], @selector(imageNamed:));
+        Method newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:));
+        method_exchangeImplementations(oriMethod, newMethod);
+        
+        oriMethod = class_getClassMethod([self class], @selector(imageWithContentsOfFile:));
+        newMethod = class_getClassMethod([self class], @selector(lks_imageWithContentsOfFile:));
+        method_exchangeImplementations(oriMethod, newMethod);
+        
+        oriMethod = class_getClassMethod([self class], @selector(imageNamed:inBundle:compatibleWithTraitCollection:));
+        newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:inBundle:compatibleWithTraitCollection:));
+        method_exchangeImplementations(oriMethod, newMethod);
+        
+        if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+            oriMethod = class_getClassMethod([self class], @selector(imageNamed:inBundle:withConfiguration:));
+            newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:inBundle:withConfiguration:));
+            method_exchangeImplementations(oriMethod, newMethod);
+        }
+    });
+}
+
++ (nullable UIImage *)lks_imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle withConfiguration:(nullable UIImageConfiguration *)configuration API_AVAILABLE(ios(13.0),tvos(13.0),watchos(6.0))
+{
+    UIImage *image = [self lks_imageNamed:name inBundle:bundle withConfiguration:configuration];
+    image.lks_imageSourceName = name;
+    return image;
+}
+
++ (nullable UIImage *)lks_imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection API_AVAILABLE(ios(8.0))
+{
+    UIImage *image = [self lks_imageNamed:name inBundle:bundle compatibleWithTraitCollection:traitCollection];
+    image.lks_imageSourceName = name;
+    return image;
+}
+
++ (UIImage *)lks_imageNamed:(NSString *)name {
+    UIImage *image = [self lks_imageNamed:name];
+    image.lks_imageSourceName = name;
+    return image;
+}
+
++ (UIImage *)lks_imageWithContentsOfFile:(NSString *)path {
+    UIImage *image = [self lks_imageWithContentsOfFile:path];
+    
+    NSString *fileName = [[path componentsSeparatedByString:@"/"].lastObject componentsSeparatedByString:@"."].firstObject;
+    image.lks_imageSourceName = fileName;
+    return image;
+}
+
+- (void)setLks_imageSourceName:(NSString *)lks_imageSourceName {
+    [self lookin_bindObject:lks_imageSourceName.copy forKey:@"lks_imageSourceName"];
+}
+
+- (NSString *)lks_imageSourceName {
+    return [self lookin_getBindObjectForKey:@"lks_imageSourceName"];
+}
+
+#endif /* LOOKIN_SERVER_DISABLE_HOOK */
+
+- (NSData *)lookin_data {
+    return UIImagePNGRepresentation(self);
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 20 - 0
Pods/LookinServer/Src/Main/Server/Category/UIImageView+LookinServer.h

@@ -0,0 +1,20 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIImageView+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/9/18.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UIImageView (LookinServer)
+
+- (NSString *)lks_imageSourceName;
+- (NSNumber *)lks_imageViewOidIfHasImage;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 31 - 0
Pods/LookinServer/Src/Main/Server/Category/UIImageView+LookinServer.m

@@ -0,0 +1,31 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIImageView+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/9/18.
+//  https://lookin.work
+//
+
+#import "UIImageView+LookinServer.h"
+#import "UIImage+LookinServer.h"
+#import "NSObject+LookinServer.h"
+
+@implementation UIImageView (LookinServer)
+
+- (NSString *)lks_imageSourceName {
+    return self.image.lks_imageSourceName;
+}
+
+- (NSNumber *)lks_imageViewOidIfHasImage {
+    if (!self.image) {
+        return nil;
+    }
+    unsigned long oid = [self lks_registerOid];
+    return @(oid);
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Category/UILabel+LookinServer.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UILabel+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/26.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UILabel (LookinServer)
+
+@property(nonatomic, assign) CGFloat lks_fontSize;
+
+- (NSString *)lks_fontName;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 29 - 0
Pods/LookinServer/Src/Main/Server/Category/UILabel+LookinServer.m

@@ -0,0 +1,29 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UILabel+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/26.
+//  https://lookin.work
+//
+
+#import "UILabel+LookinServer.h"
+
+@implementation UILabel (LookinServer)
+
+- (CGFloat)lks_fontSize {
+    return self.font.pointSize;
+}
+- (void)setLks_fontSize:(CGFloat)lks_fontSize {
+    UIFont *font = [self.font fontWithSize:lks_fontSize];
+    self.font = font;
+}
+
+- (NSString *)lks_fontName {
+    return self.font.fontName;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 19 - 0
Pods/LookinServer/Src/Main/Server/Category/UITableView+LookinServer.h

@@ -0,0 +1,19 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UITableView+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/9/5.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UITableView (LookinServer)
+
+- (NSArray<NSNumber *> *)lks_numberOfRows;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 29 - 0
Pods/LookinServer/Src/Main/Server/Category/UITableView+LookinServer.m

@@ -0,0 +1,29 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UITableView+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/9/5.
+//  https://lookin.work
+//
+
+#import "UITableView+LookinServer.h"
+#import "LookinServerDefines.h"
+
+@implementation UITableView (LookinServer)
+
+- (NSArray<NSNumber *> *)lks_numberOfRows {
+    NSUInteger sectionsCount = MIN(self.numberOfSections, 10);
+    NSArray<NSNumber *> *rowsCount = [NSArray lookin_arrayWithCount:sectionsCount block:^id(NSUInteger idx) {
+        return @([self numberOfRowsInSection:idx]);
+    }];
+    if (rowsCount.count == 0) {
+        return nil;
+    }
+    return rowsCount;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Category/UITextField+LookinServer.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UITextField+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/26.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UITextField (LookinServer)
+
+@property(nonatomic, assign) CGFloat lks_fontSize;
+
+- (NSString *)lks_fontName;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 29 - 0
Pods/LookinServer/Src/Main/Server/Category/UITextField+LookinServer.m

@@ -0,0 +1,29 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UITextField+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/26.
+//  https://lookin.work
+//
+
+#import "UITextField+LookinServer.h"
+
+@implementation UITextField (LookinServer)
+
+- (CGFloat)lks_fontSize {
+    return self.font.pointSize;
+}
+- (void)setLks_fontSize:(CGFloat)lks_fontSize {
+    UIFont *font = [self.font fontWithSize:lks_fontSize];
+    self.font = font;
+}
+
+- (NSString *)lks_fontName {
+    return self.font.fontName;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Category/UITextView+LookinServer.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UITextView+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/26.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UITextView (LookinServer)
+
+@property(nonatomic, assign) CGFloat lks_fontSize;
+
+- (NSString *)lks_fontName;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 29 - 0
Pods/LookinServer/Src/Main/Server/Category/UITextView+LookinServer.m

@@ -0,0 +1,29 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UITextView+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/26.
+//  https://lookin.work
+//
+
+#import "UITextView+LookinServer.h"
+
+@implementation UITextView (LookinServer)
+
+- (CGFloat)lks_fontSize {
+    return self.font.pointSize;
+}
+- (void)setLks_fontSize:(CGFloat)lks_fontSize {
+    UIFont *font = [self.font fontWithSize:lks_fontSize];
+    self.font = font;
+}
+
+- (NSString *)lks_fontName {
+    return self.font.fontName;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 44 - 0
Pods/LookinServer/Src/Main/Server/Category/UIView+LookinServer.h

@@ -0,0 +1,44 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIView+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/3/19.
+//  https://lookin.work
+//
+
+#import "LookinDefines.h"
+#import <UIKit/UIKit.h>
+
+@interface UIView (LookinServer)
+
+/// 如果 myViewController.view = myView,则可以通过 myView 的 lks_findHostViewController 方法找到 myViewController
+- (UIViewController *)lks_findHostViewController;
+
+/// 是否是 UITabBar 的 childrenView,如果是的话,则截图时需要强制使用 renderInContext: 的方式而非 drawViewHierarchyInRect:afterScreenUpdates: 否则在 iOS 13 上获取到的图像是空的不知道为什么
+@property(nonatomic, assign) BOOL lks_isChildrenViewOfTabBar;
+
+/// point 是相对于 receiver 自身的坐标系
+- (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray<Class> *)preferredClasses;
+
+- (CGFloat)lks_bestWidth;
+- (CGFloat)lks_bestHeight;
+- (CGSize)lks_bestSize;
+
+@property(nonatomic, assign) float lks_horizontalContentHuggingPriority;
+@property(nonatomic, assign) float lks_verticalContentHuggingPriority;
+
+@property(nonatomic, assign) float lks_horizontalContentCompressionResistancePriority;
+@property(nonatomic, assign) float lks_verticalContentCompressionResistancePriority;
+
+/// 遍历全局的 view 并给他们设置 lks_involvedRawConstraints 属性
++ (void)lks_rebuildGlobalInvolvedRawConstraints;
+/// 该属性保存了牵扯到当前 view 的所有 constraints,包括那些没有生效的
+@property(nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *lks_involvedRawConstraints;
+
+- (NSArray<NSDictionary<NSString *, id> *> *)lks_constraints;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 215 - 0
Pods/LookinServer/Src/Main/Server/Category/UIView+LookinServer.m

@@ -0,0 +1,215 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIView+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/3/19.
+//  https://lookin.work
+//
+
+#import "UIView+LookinServer.h"
+#import <objc/runtime.h>
+#import "LookinObject.h"
+#import "LookinAutoLayoutConstraint.h"
+#import "LookinServerDefines.h"
+#import "LKS_MultiplatformAdapter.h"
+
+@implementation UIView (LookinServer)
+
+- (UIViewController *)lks_findHostViewController {
+    UIResponder *responder = [self nextResponder];
+    if (!responder) {
+        return nil;
+    }
+    if (![responder isKindOfClass:[UIViewController class]]) {
+        return nil;
+    }
+    UIViewController *viewController = (UIViewController *)responder;
+    if (viewController.view != self) {
+        return nil;
+    }
+    return viewController;
+}
+
+- (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray<Class> *)preferredClasses {
+    BOOL isPreferredClassForSelf = [preferredClasses lookin_any:^BOOL(Class obj) {
+        return [self isKindOfClass:obj];
+    }];
+    if (isPreferredClassForSelf) {
+        return self;
+    }
+    
+    UIView *targetView = [self.subviews lookin_lastFiltered:^BOOL(__kindof UIView *obj) {
+        if (obj.hidden || obj.alpha <= 0.01) {
+            return NO;
+        }
+        BOOL contains = CGRectContainsPoint(obj.frame, point);
+        return contains;
+    }];
+    
+    if (!targetView) {
+        return self;
+    }
+    
+    CGPoint newPoint = [targetView convertPoint:point fromView:self];
+    targetView = [targetView lks_subviewAtPoint:newPoint preferredClasses:preferredClasses];
+    return targetView;
+}
+
+- (CGSize)lks_bestSize {
+    return [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
+}
+
+- (CGFloat)lks_bestWidth {
+    return self.lks_bestSize.width;
+}
+
+- (CGFloat)lks_bestHeight {
+    return self.lks_bestSize.height;
+}
+
+- (void)setLks_isChildrenViewOfTabBar:(BOOL)lks_isChildrenViewOfTabBar {
+    [self lookin_bindBOOL:lks_isChildrenViewOfTabBar forKey:@"lks_isChildrenViewOfTabBar"];
+}
+- (BOOL)lks_isChildrenViewOfTabBar {
+    return [self lookin_getBindBOOLForKey:@"lks_isChildrenViewOfTabBar"];
+}
+
+- (void)setLks_verticalContentHuggingPriority:(float)lks_verticalContentHuggingPriority {
+    [self setContentHuggingPriority:lks_verticalContentHuggingPriority forAxis:UILayoutConstraintAxisVertical];
+}
+- (float)lks_verticalContentHuggingPriority {
+    return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisVertical];
+}
+
+- (void)setLks_horizontalContentHuggingPriority:(float)lks_horizontalContentHuggingPriority {
+    [self setContentHuggingPriority:lks_horizontalContentHuggingPriority forAxis:UILayoutConstraintAxisHorizontal];
+}
+- (float)lks_horizontalContentHuggingPriority {
+    return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisHorizontal];
+}
+
+- (void)setLks_verticalContentCompressionResistancePriority:(float)lks_verticalContentCompressionResistancePriority {
+    [self setContentCompressionResistancePriority:lks_verticalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisVertical];
+}
+- (float)lks_verticalContentCompressionResistancePriority {
+    return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisVertical];
+}
+
+- (void)setLks_horizontalContentCompressionResistancePriority:(float)lks_horizontalContentCompressionResistancePriority {
+    [self setContentCompressionResistancePriority:lks_horizontalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisHorizontal];
+}
+- (float)lks_horizontalContentCompressionResistancePriority {
+    return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
+}
+
++ (void)lks_rebuildGlobalInvolvedRawConstraints {
+    [[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
+        [self lks_removeInvolvedRawConstraintsForViewsRootedByView:window];
+    }];
+    [[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
+        [self lks_addInvolvedRawConstraintsForViewsRootedByView:window];
+    }];
+}
+
++ (void)lks_addInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView {
+    [rootView.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull constraint, NSUInteger idx, BOOL * _Nonnull stop) {
+        UIView *firstView = constraint.firstItem;
+        if ([firstView isKindOfClass:[UIView class]] && ![firstView.lks_involvedRawConstraints containsObject:constraint]) {
+            if (!firstView.lks_involvedRawConstraints) {
+                firstView.lks_involvedRawConstraints = [NSMutableArray array];
+            }
+            [firstView.lks_involvedRawConstraints addObject:constraint];
+        }
+        
+        UIView *secondView = constraint.secondItem;
+        if ([secondView isKindOfClass:[UIView class]] && ![secondView.lks_involvedRawConstraints containsObject:constraint]) {
+            if (!secondView.lks_involvedRawConstraints) {
+                secondView.lks_involvedRawConstraints = [NSMutableArray array];
+            }
+            [secondView.lks_involvedRawConstraints addObject:constraint];
+        }
+    }];
+    
+    [rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
+        [self lks_addInvolvedRawConstraintsForViewsRootedByView:subview];
+    }];
+}
+
++ (void)lks_removeInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView {
+    [rootView.lks_involvedRawConstraints removeAllObjects];
+    [rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
+        [self lks_removeInvolvedRawConstraintsForViewsRootedByView:subview];
+    }];
+}
+
+- (void)setLks_involvedRawConstraints:(NSMutableArray<NSLayoutConstraint *> *)lks_involvedRawConstraints {
+    [self lookin_bindObject:lks_involvedRawConstraints forKey:@"lks_involvedRawConstraints"];
+}
+
+- (NSMutableArray<NSLayoutConstraint *> *)lks_involvedRawConstraints {
+    return [self lookin_getBindObjectForKey:@"lks_involvedRawConstraints"];
+}
+
+- (NSArray<LookinAutoLayoutConstraint *> *)lks_constraints {
+    /**
+     - lks_involvedRawConstraints 保存了牵扯到了 self 的所有的 constraints(包括未生效的,但不包括 inactive 的,整个产品逻辑都是直接忽略 inactive 的 constraints)
+     - 通过 constraintsAffectingLayoutForAxis 可以拿到会影响 self 布局的所有已生效的 constraints(这里称之为 effectiveConstraints)
+     - 很多情况下,一条 constraint 会出现在 effectiveConstraints 里但不会出现在 lks_involvedRawConstraints 里,比如:
+        · UIWindow 拥有 minX, minY, width, height 四个 effectiveConstraints,但 lks_involvedRawConstraints 为空,因为它的 constraints 属性为空(这一点不知道为啥,但 Xcode Inspector 和 Reveal 确实也不会显示这四个 constraints)
+        · 如果设置了 View1 的 center 和 superview 的 center 保持一致,则 superview 的 width 和 height 也会出现在 effectiveConstraints 里,但不会出现在 lks_involvedRawConstraints 里(这点可以理解,毕竟这种场景下 superview 的 width 和 height 确实会影响到 View1)
+     */
+    NSMutableArray<NSLayoutConstraint *> *effectiveConstraints = [NSMutableArray array];
+    [effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal]];
+    [effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisVertical]];
+    
+    NSArray<LookinAutoLayoutConstraint *> *lookinConstraints = [self.lks_involvedRawConstraints lookin_map:^id(NSUInteger idx, __kindof NSLayoutConstraint *constraint) {
+        BOOL isEffective = [effectiveConstraints containsObject:constraint];
+        if ([constraint isActive]) {
+            // trying to get firstItem or secondItem of an inactive constraint may cause dangling-pointer crash
+            // https://github.com/QMUI/LookinServer/issues/86
+            LookinConstraintItemType firstItemType = [self _lks_constraintItemTypeForItem:constraint.firstItem];
+            LookinConstraintItemType secondItemType = [self _lks_constraintItemTypeForItem:constraint.secondItem];
+            LookinAutoLayoutConstraint *lookinConstraint = [LookinAutoLayoutConstraint instanceFromNSConstraint:constraint isEffective:isEffective firstItemType:firstItemType secondItemType:secondItemType];
+            return lookinConstraint;
+        }
+        return nil;
+    }];
+    return lookinConstraints.count ? lookinConstraints : nil;
+}
+
+- (LookinConstraintItemType)_lks_constraintItemTypeForItem:(id)item {
+    if (!item) {
+        return LookinConstraintItemTypeNil;
+    }
+    if (item == self) {
+        return LookinConstraintItemTypeSelf;
+    }
+    if (item == self.superview) {
+        return LookinConstraintItemTypeSuper;
+    }
+    
+    // 在 runtime 时,这里会遇到的 UILayoutGuide 和 _UILayoutGuide 居然是 UIView 的子类,不知道是看错了还是有什么玄机,所以在判断是否是 UIView 之前要先判断这个
+    if (@available(iOS 9.0, *)) {
+        if ([item isKindOfClass:[UILayoutGuide class]]) {
+            return LookinConstraintItemTypeLayoutGuide;
+        }
+    }
+    
+    NSString *className = NSStringFromClass([item class]);
+    if ([className hasSuffix:@"_UILayoutGuide"]) {
+        return LookinConstraintItemTypeLayoutGuide;
+    }
+    
+    if ([item isKindOfClass:[UIView class]]) {
+        return LookinConstraintItemTypeView;
+    }
+    
+    NSAssert(NO, @"");
+    return LookinConstraintItemTypeUnknown;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 19 - 0
Pods/LookinServer/Src/Main/Server/Category/UIViewController+LookinServer.h

@@ -0,0 +1,19 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIViewController+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/4/22.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UIViewController (LookinServer)
+
++ (UIViewController *)lks_visibleViewController;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 48 - 0
Pods/LookinServer/Src/Main/Server/Category/UIViewController+LookinServer.m

@@ -0,0 +1,48 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIViewController+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/4/22.
+//  https://lookin.work
+//
+
+#import "UIViewController+LookinServer.h"
+#import "UIView+LookinServer.h"
+#import <objc/runtime.h>
+#import "LKS_MultiplatformAdapter.h"
+
+@implementation UIViewController (LookinServer)
+
++ (nullable UIViewController *)lks_visibleViewController {
+    
+    UIViewController *rootViewController = [LKS_MultiplatformAdapter keyWindow].rootViewController;
+    UIViewController *visibleViewController = [rootViewController lks_visibleViewControllerIfExist];
+    return visibleViewController;
+}
+
+- (UIViewController *)lks_visibleViewControllerIfExist {
+    
+    if (self.presentedViewController) {
+        return [self.presentedViewController lks_visibleViewControllerIfExist];
+    }
+    
+    if ([self isKindOfClass:[UINavigationController class]]) {
+        return [((UINavigationController *)self).visibleViewController lks_visibleViewControllerIfExist];
+    }
+    
+    if ([self isKindOfClass:[UITabBarController class]]) {
+        return [((UITabBarController *)self).selectedViewController lks_visibleViewControllerIfExist];
+    }
+    
+    if (self.isViewLoaded && !self.view.hidden && self.view.alpha > 0.01) {
+        return self;
+    } else {
+        return nil;
+    }
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Category/UIVisualEffectView+LookinServer.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIVisualEffectView+LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/10/8.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+@interface UIVisualEffectView (LookinServer)
+
+- (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber;
+
+- (NSNumber *)lks_blurEffectStyleNumber;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 33 - 0
Pods/LookinServer/Src/Main/Server/Category/UIVisualEffectView+LookinServer.m

@@ -0,0 +1,33 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  UIVisualEffectView+LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/10/8.
+//  https://lookin.work
+//
+
+#import "UIVisualEffectView+LookinServer.h"
+#import "UIBlurEffect+LookinServer.h"
+
+@implementation UIVisualEffectView (LookinServer)
+
+- (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber {
+    UIBlurEffectStyle style = [lks_blurEffectStyleNumber integerValue];
+    UIBlurEffect *effect = [UIBlurEffect effectWithStyle:style];
+    self.effect = effect;
+}
+
+- (NSNumber *)lks_blurEffectStyleNumber {
+    UIVisualEffect *effect = self.effect;
+    if (![effect isKindOfClass:[UIBlurEffect class]]) {
+        return nil;
+    }
+    UIBlurEffect *blurEffect = (UIBlurEffect *)effect;
+    return blurEffect.lks_effectStyleNumber;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 29 - 0
Pods/LookinServer/Src/Main/Server/Connection/LKS_ConnectionManager.h

@@ -0,0 +1,29 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  Lookin.h
+//  Lookin
+//
+//  Created by Li Kai on 2018/8/5.
+//  https://lookin.work
+//
+
+#import <UIKit/UIKit.h>
+
+extern NSString *const LKS_ConnectionDidEndNotificationName;
+
+@class LookinConnectionResponseAttachment;
+
+@interface LKS_ConnectionManager : NSObject
+
++ (instancetype)sharedInstance;
+
+@property(nonatomic, assign) BOOL applicationIsActive;
+
+- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag;
+
+- (void)pushData:(NSObject *)data type:(uint32_t)type;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 268 - 0
Pods/LookinServer/Src/Main/Server/Connection/LKS_ConnectionManager.m

@@ -0,0 +1,268 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LookinServer.m
+//  LookinServer
+//
+//  Created by Li Kai on 2018/8/5.
+//  https://lookin.work
+//
+
+#import "LKS_ConnectionManager.h"
+#import "Lookin_PTChannel.h"
+#import "LKS_RequestHandler.h"
+#import "LookinConnectionResponseAttachment.h"
+#import "LKS_ExportManager.h"
+#import "LookinServerDefines.h"
+#import "LKS_TraceManager.h"
+#import "LKS_MultiplatformAdapter.h"
+
+NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName";
+
+@interface LKS_ConnectionManager () <Lookin_PTChannelDelegate>
+
+@property(nonatomic, weak) Lookin_PTChannel *peerChannel_;
+
+@property(nonatomic, strong) LKS_RequestHandler *requestHandler;
+
+@end
+
+@implementation LKS_ConnectionManager
+
++ (instancetype)sharedInstance {
+    static LKS_ConnectionManager *sharedInstance;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedInstance = [[LKS_ConnectionManager alloc] init];
+    });
+    return sharedInstance;
+}
+
++ (void)load {
+    // 触发 init 方法
+    [LKS_ConnectionManager sharedInstance];
+}
+
+- (instancetype)init {
+    if (self = [super init]) {
+        NSLog(@"LookinServer - Will launch. Framework version: %@", LOOKIN_SERVER_READABLE_VERSION);
+        
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil];
+        
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_2D" object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_3D" object:nil];
+        [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
+            [[LKS_ExportManager sharedInstance] exportAndShare];
+        }];
+        [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_RelationSearch" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
+            [[LKS_TraceManager sharedInstance] addSearchTarger:note.object];
+        }];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGetLookinInfo:) name:@"GetLookinInfo" object:nil];
+        
+        self.requestHandler = [LKS_RequestHandler new];
+    }
+    return self;
+}
+
+- (void)_handleWillResignActiveNotification {
+    self.applicationIsActive = NO;
+    
+    if (self.peerChannel_ && ![self.peerChannel_ isConnected]) {
+        [self.peerChannel_ close];
+        self.peerChannel_ = nil;
+    }
+}
+
+- (void)_handleApplicationDidBecomeActive {
+    self.applicationIsActive = YES;
+    [self searchPortToListenIfNoConnection];
+}
+
+- (void)searchPortToListenIfNoConnection {
+    if ([self.peerChannel_ isConnected]) {
+        NSLog(@"LookinServer - Abort to search ports. Already has connected channel.");
+        return;
+    }
+    NSLog(@"LookinServer - Searching port to listen...");
+    [self.peerChannel_ close];
+    self.peerChannel_ = nil;
+    
+    if ([self isiOSAppOnMac]) {
+        [self _tryToListenOnPortFrom:LookinSimulatorIPv4PortNumberStart to:LookinSimulatorIPv4PortNumberEnd current:LookinSimulatorIPv4PortNumberStart];
+    } else {
+        [self _tryToListenOnPortFrom:LookinUSBDeviceIPv4PortNumberStart to:LookinUSBDeviceIPv4PortNumberEnd current:LookinUSBDeviceIPv4PortNumberStart];
+    }
+}
+
+- (BOOL)isiOSAppOnMac {
+#if TARGET_OS_SIMULATOR
+    return YES;
+#else
+    if (@available(iOS 14.0, *)) {
+        // isiOSAppOnMac 这个 API 看似在 iOS 14.0 上可用,但其实在 iOS 14 beta 上是不存在的、有 unrecognized selector 问题,因此这里要用 respondsToSelector 做一下保护
+        NSProcessInfo *info = [NSProcessInfo processInfo];
+        if ([info respondsToSelector:@selector(isiOSAppOnMac)]) {
+            return [info isiOSAppOnMac];
+        } else if ([info respondsToSelector:@selector(isMacCatalystApp)]) {
+            return [info isMacCatalystApp];
+        } else {
+            return NO;
+        }
+    }
+    if (@available(iOS 13.0, tvOS 13.0, *)) {
+        return [NSProcessInfo processInfo].isMacCatalystApp;
+    }
+    return NO;
+#endif
+}
+
+- (void)_tryToListenOnPortFrom:(int)fromPort to:(int)toPort current:(int)currentPort  {
+    Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self];
+    channel.targetPort = currentPort;
+    [channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
+        if (error) {
+            if (error.code == 48) {
+                // 该地址已被占用
+            } else {
+                // 未知失败
+            }
+            
+            if (currentPort < toPort) {
+                // 尝试下一个端口
+                NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@). Will try anothor address ...", currentPort, error);
+                [self _tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)];
+            } else {
+                // 所有端口都尝试完毕,全部失败
+                NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@).", currentPort, error);
+                NSLog(@"LookinServer - Connect failed in the end.");
+            }
+            
+        } else {
+            // 成功
+            NSLog(@"LookinServer - Connected successfully on 127.0.0.1:%d", currentPort);
+            // 此时 peerChannel_ 状态为 listening
+            self.peerChannel_ = channel;
+        }
+    }];
+}
+
+- (void)dealloc {
+    if (self.peerChannel_) {
+        [self.peerChannel_ close];
+    }
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
+    [self _sendData:data frameOfType:requestType tag:tag];
+}
+
+- (void)pushData:(NSObject *)data type:(uint32_t)type {
+    [self _sendData:data frameOfType:type tag:0];
+}
+
+- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag {
+    if (self.peerChannel_) {
+        NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data];
+        dispatch_data_t payload = [archivedData createReferencingDispatchData];
+        
+        [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) {
+            if (error) {
+            }
+        }];
+    }
+}
+
+#pragma mark - Lookin_PTChannelDelegate
+
+- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize {
+    if (channel != self.peerChannel_) {
+        return NO;
+    } else if ([self.requestHandler canHandleRequestType:type]) {
+        return YES;
+    } else {
+        [channel close];
+        return NO;
+    }
+}
+
+- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload {
+    id object = nil;
+    if (payload) {
+        id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfDispatchData:payload.dispatchData]];
+        if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) {
+            LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject;
+            object = attachment.data;
+        } else {
+            object = unarchivedObject;
+        }
+    }
+    [self.requestHandler handleRequestType:type tag:tag object:object];
+}
+
+/// 当 Client 端链接成功时,该方法会被调用,然后 channel 的状态会变成 connected
+- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address {
+    NSLog(@"LookinServer - channel:%@, acceptConnection:%@", channel.debugTag, otherChannel.debugTag);
+
+    Lookin_PTChannel *previousChannel = self.peerChannel_;
+    
+    otherChannel.targetPort = address.port;
+    self.peerChannel_ = otherChannel;
+    
+    [previousChannel cancel];
+}
+
+/// 当连接过 Lookin 客户端,然后 Lookin 客户端又被关闭时,会走到这里
+- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error {
+    if (self.peerChannel_ != channel) {
+        // Client 端第一次连接上时,之前 listen 的 port 会被 Peertalk 内部 cancel(并在 didAcceptConnection 方法里给业务抛一个新建的 connected 状态的 channel),那个被 cancel 的 channel 会走到这里
+        NSLog(@"LookinServer - Ignore channel%@ end.", channel.debugTag);
+        return;
+    }
+    // Client 端关闭时,会走到这里
+    NSLog(@"LookinServer - channel%@ DidEndWithError:%@", channel.debugTag, error);
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:LKS_ConnectionDidEndNotificationName object:self];
+    [self searchPortToListenIfNoConnection];
+}
+
+#pragma mark - Handler
+
+- (void)_handleLocalInspect:(NSNotification *)note {
+    UIAlertController  *alertController = [UIAlertController  alertControllerWithTitle:@"Lookin" message:@"Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality."  preferredStyle:UIAlertControllerStyleAlert];
+    UIAlertAction *okAction  = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
+    [alertController addAction:okAction];
+    UIWindow *keyWindow = [LKS_MultiplatformAdapter keyWindow];
+    UIViewController *rootViewController = [keyWindow rootViewController];
+    [rootViewController presentViewController:alertController animated:YES completion:nil];
+    
+    NSLog(@"LookinServer - Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality.");
+}
+
+- (void)handleGetLookinInfo:(NSNotification *)note {
+    NSDictionary* userInfo = note.userInfo;
+    if (!userInfo) {
+        return;
+    }
+    NSMutableDictionary* infoWrapper = userInfo[@"infos"];
+    if (![infoWrapper isKindOfClass:[NSMutableDictionary class]]) {
+        NSLog(@"LookinServer - GetLookinInfo failed. Params invalid.");
+        return;
+    }
+    infoWrapper[@"lookinServerVersion"] = LOOKIN_SERVER_READABLE_VERSION;
+}
+
+@end
+
+/// 这个类使得用户可以通过 NSClassFromString(@"Lookin") 来判断 LookinServer 是否被编译进了项目里
+
+@interface Lookin : NSObject
+
+@end
+
+@implementation Lookin
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Connection/LKS_RequestHandler.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_RequestHandler.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/1/15.
+//  https://lookin.work
+//
+
+#import <Foundation/Foundation.h>
+
+@interface LKS_RequestHandler : NSObject
+
+- (BOOL)canHandleRequestType:(uint32_t)requestType;
+
+- (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 558 - 0
Pods/LookinServer/Src/Main/Server/Connection/LKS_RequestHandler.m

@@ -0,0 +1,558 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_RequestHandler.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/1/15.
+//  https://lookin.work
+//
+
+#import "LKS_RequestHandler.h"
+#import "NSObject+LookinServer.h"
+#import "UIImage+LookinServer.h"
+#import "LKS_ConnectionManager.h"
+#import "LookinConnectionResponseAttachment.h"
+#import "LookinAttributeModification.h"
+#import "LookinDisplayItemDetail.h"
+#import "LookinHierarchyInfo.h"
+#import "LookinServerDefines.h"
+#import <objc/runtime.h>
+#import "LookinObject.h"
+#import "LookinAppInfo.h"
+#import "LKS_AttrGroupsMaker.h"
+#import "LKS_InbuiltAttrModificationHandler.h"
+#import "LKS_CustomAttrModificationHandler.h"
+#import "LKS_AttrModificationPatchHandler.h"
+#import "LKS_HierarchyDetailsHandler.h"
+#import "LookinStaticAsyncUpdateTask.h"
+
+@interface LKS_RequestHandler ()
+
+@property(nonatomic, strong) NSMutableSet<LKS_HierarchyDetailsHandler *> *activeDetailHandlers;
+
+@end
+
+@implementation LKS_RequestHandler {
+    NSSet *_validRequestTypes;
+}
+
+- (instancetype)init {
+    if (self = [super init]) {
+        _validRequestTypes = [NSSet setWithObjects:@(LookinRequestTypePing),
+                              @(LookinRequestTypeApp),
+                              @(LookinRequestTypeHierarchy),
+                              @(LookinRequestTypeInbuiltAttrModification),
+                              @(LookinRequestTypeCustomAttrModification),
+                              @(LookinRequestTypeAttrModificationPatch),
+                              @(LookinRequestTypeHierarchyDetails),
+                              @(LookinRequestTypeFetchObject),
+                              @(LookinRequestTypeAllAttrGroups),
+                              @(LookinRequestTypeAllSelectorNames),
+                              @(LookinRequestTypeInvokeMethod),
+                              @(LookinRequestTypeFetchImageViewImage),
+                              @(LookinRequestTypeModifyRecognizerEnable),
+                              @(LookinPush_CanceHierarchyDetails),
+                              nil];
+        
+        self.activeDetailHandlers = [NSMutableSet set];
+    }
+    return self;
+}
+
+- (BOOL)canHandleRequestType:(uint32_t)requestType {
+    if ([_validRequestTypes containsObject:@(requestType)]) {
+        return YES;
+    }
+    return NO;
+}
+
+- (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object {
+    if (requestType == LookinRequestTypePing) {
+        LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
+        // 当 app 处于后台时,可能可以执行代码也可能不能执行代码,如果运气好了可以执行代码,则这里直接主动使用 appIsInBackground 标识 app 处于后台,不要让 Lookin 客户端傻傻地等待超时了
+        if (![LKS_ConnectionManager sharedInstance].applicationIsActive) {
+            responseAttachment.appIsInBackground = YES;            
+        }
+        [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
+        
+    } else if (requestType == LookinRequestTypeApp) {
+        // 请求可用设备信息
+        if (![object isKindOfClass:[NSDictionary class]]) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        NSDictionary<NSString *, id> *params = object;
+        BOOL needImages = ((NSNumber *)params[@"needImages"]).boolValue;
+        NSArray<NSNumber *> *localIdentifiers = params[@"local"];
+        
+        LookinAppInfo *appInfo = [LookinAppInfo currentInfoWithScreenshot:needImages icon:needImages localIdentifiers:localIdentifiers];
+        
+        LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
+        responseAttachment.data = appInfo;
+        [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
+        
+    } else if (requestType == LookinRequestTypeHierarchy) {
+        // 从 LookinClient 1.0.4 开始有这个参数,之前是 nil
+        NSString *clientVersion = nil;
+        if ([object isKindOfClass:[NSDictionary class]]) {
+            NSDictionary<NSString *, id> *params = object;
+            NSString *version = params[@"clientVersion"];
+            if ([version isKindOfClass:[NSString class]]) {
+                clientVersion = version;
+            }
+        }
+        
+        LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
+        responseAttachment.data = [LookinHierarchyInfo staticInfoWithLookinVersion:clientVersion];
+        [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
+        
+    } else if (requestType == LookinRequestTypeInbuiltAttrModification) {
+        // 请求修改某个属性
+        [LKS_InbuiltAttrModificationHandler handleModification:object completion:^(LookinDisplayItemDetail *data, NSError *error) {
+            LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
+            if (error) {
+                attachment.error = error;
+            } else {
+                attachment.data = data;
+            }
+            [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
+        }];
+        
+    } else if (requestType == LookinRequestTypeCustomAttrModification) {
+        BOOL succ = [LKS_CustomAttrModificationHandler handleModification:object];
+        if (succ) {
+            [self _submitResponseWithData:nil requestType:requestType tag:tag];
+        } else {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+        }
+        
+    } else if (requestType == LookinRequestTypeAttrModificationPatch) {
+        NSArray<LookinStaticAsyncUpdateTask *> *tasks = object;
+        NSUInteger dataTotalCount = tasks.count;
+        [LKS_InbuiltAttrModificationHandler handlePatchWithTasks:tasks block:^(LookinDisplayItemDetail *data) {
+            LookinConnectionResponseAttachment *attrAttachment = [LookinConnectionResponseAttachment new];
+            attrAttachment.data = data;
+            attrAttachment.dataTotalCount = dataTotalCount;
+            attrAttachment.currentDataCount = 1;
+            [[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag];
+        }];
+        
+    } else if (requestType == LookinRequestTypeHierarchyDetails) {
+        NSArray<LookinStaticAsyncUpdateTasksPackage *> *packages = object;
+        NSUInteger responsesDataTotalCount = [packages lookin_reduceInteger:^NSInteger(NSInteger accumulator, NSUInteger idx, LookinStaticAsyncUpdateTasksPackage *package) {
+            accumulator += package.tasks.count;
+            return accumulator;
+        } initialAccumlator:0];
+        
+        LKS_HierarchyDetailsHandler *handler = [LKS_HierarchyDetailsHandler new];
+        [self.activeDetailHandlers addObject:handler];
+        
+        [handler startWithPackages:packages block:^(NSArray<LookinDisplayItemDetail *> *details) {
+            LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
+            attachment.data = details;
+            attachment.dataTotalCount = responsesDataTotalCount;
+            attachment.currentDataCount = details.count;
+            [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag];
+            
+        } finishedBlock:^{
+            [self.activeDetailHandlers removeObject:handler];
+        }];
+        
+    } else if (requestType == LookinRequestTypeFetchObject) {
+        unsigned long oid = ((NSNumber *)object).unsignedLongValue;
+        NSObject *object = [NSObject lks_objectWithOid:oid];
+        LookinObject *lookinObj = [LookinObject instanceWithObject:object];
+        
+        LookinConnectionResponseAttachment *attach = [LookinConnectionResponseAttachment new];
+        attach.data = lookinObj;
+        [[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag];
+        
+    } else if (requestType == LookinRequestTypeAllAttrGroups) {
+        unsigned long oid = ((NSNumber *)object).unsignedLongValue;
+        CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid];
+        if (![layer isKindOfClass:[CALayer class]]) {
+            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:LookinRequestTypeAllAttrGroups tag:tag];
+            return;
+        }
+        
+        NSArray<LookinAttributesGroup *> *list = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
+        [self _submitResponseWithData:list requestType:LookinRequestTypeAllAttrGroups tag:tag];
+        
+    } else if (requestType == LookinRequestTypeAllSelectorNames) {
+        if (![object isKindOfClass:[NSDictionary class]]) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        NSDictionary *params = object;
+        Class targetClass = NSClassFromString(params[@"className"]);
+        BOOL hasArg = [(NSNumber *)params[@"hasArg"] boolValue];
+        if (!targetClass) {
+            NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"Didn't find the class named \"%@\". Please input another class and try again."), object];
+            [self _submitResponseWithError:LookinErrorMake(errorMsg, @"") requestType:requestType tag:tag];
+            return;
+        }
+        
+        NSArray<NSString *> *selNames = [self _methodNameListForClass:targetClass hasArg:hasArg];
+        [self _submitResponseWithData:selNames requestType:requestType tag:tag];
+        
+    } else if (requestType == LookinRequestTypeInvokeMethod) {
+        if (![object isKindOfClass:[NSDictionary class]]) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        NSDictionary *param = object;
+        unsigned long oid = [param[@"oid"] unsignedLongValue];
+        NSString *text = param[@"text"];
+        if (!text.length) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        NSObject *targerObj = [NSObject lks_objectWithOid:oid];
+        if (!targerObj) {
+            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
+            return;
+        }
+        
+        SEL targetSelector = NSSelectorFromString(text);
+        if (targetSelector && [targerObj respondsToSelector:targetSelector]) {
+            NSString *resultDescription;
+            NSObject *resultObject;
+            NSError *error;
+            [self _handleInvokeWithObject:targerObj selector:targetSelector resultDescription:&resultDescription resultObject:&resultObject error:&error];
+            if (error) {
+                [self _submitResponseWithError:error requestType:requestType tag:tag];
+                return;
+            }
+            NSMutableDictionary *responseData = [NSMutableDictionary dictionaryWithCapacity:2];
+            if (resultDescription) {
+                responseData[@"description"] = resultDescription;
+            }
+            if (resultObject) {
+                responseData[@"object"] = resultObject;
+            }
+            [self _submitResponseWithData:responseData requestType:requestType tag:tag];
+        } else {
+            NSString *errMsg = [NSString stringWithFormat:LKS_Localized(@"%@ doesn't have an instance method called \"%@\"."), NSStringFromClass(targerObj.class), text];
+            [self _submitResponseWithError:LookinErrorMake(errMsg, @"") requestType:requestType tag:tag];
+        }
+        
+    } else if (requestType == LookinPush_CanceHierarchyDetails) {
+        [self.activeDetailHandlers enumerateObjectsUsingBlock:^(LKS_HierarchyDetailsHandler * _Nonnull handler, BOOL * _Nonnull stop) {
+            [handler cancel];
+        }];
+        [self.activeDetailHandlers removeAllObjects];
+        
+    } else if (requestType == LookinRequestTypeFetchImageViewImage) {
+        if (![object isKindOfClass:[NSNumber class]]) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        unsigned long imageViewOid = [(NSNumber *)object unsignedLongValue];
+        UIImageView *imageView = (UIImageView *)[NSObject lks_objectWithOid:imageViewOid];
+        if (!imageView) {
+            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
+            return;
+        }
+        if (![imageView isKindOfClass:[UIImageView class]]) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        UIImage *image = imageView.image;
+        NSData *imageData = [image lookin_data];
+        [self _submitResponseWithData:imageData requestType:requestType tag:tag];
+    
+    } else if (requestType == LookinRequestTypeModifyRecognizerEnable) {
+        if (![object isKindOfClass:[NSDictionary class]]) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        NSDictionary<NSString *, NSNumber *> *params = object;
+        unsigned long recognizerOid = ((NSNumber *)params[@"oid"]).unsignedLongValue;
+        BOOL shouldBeEnabled = ((NSNumber *)params[@"enable"]).boolValue;
+        
+        UIGestureRecognizer *recognizer = (UIGestureRecognizer *)[NSObject lks_objectWithOid:recognizerOid];
+        if (!recognizer) {
+            [self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
+            return;
+        }
+        if (![recognizer isKindOfClass:[UIGestureRecognizer class]]) {
+            [self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
+            return;
+        }
+        recognizer.enabled = shouldBeEnabled;
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            // dispatch 以确保拿到的 enabled 是比较新的
+            [self _submitResponseWithData:@(recognizer.enabled) requestType:requestType tag:tag];
+        });
+    }
+}
+
+- (NSArray<NSString *> *)_methodNameListForClass:(Class)aClass hasArg:(BOOL)hasArg {
+    NSSet<NSString *> *prefixesToVoid = [NSSet setWithObjects:@"_", @"CA_", @"cpl", @"mf_", @"vs_", @"pep_", @"isNS", @"avkit_", @"PG_", @"px_", @"pl_", @"nsli_", @"pu_", @"pxg_", nil];
+    NSMutableArray<NSString *> *array = [NSMutableArray array];
+    
+    Class currentClass = aClass;
+    while (currentClass) {
+        NSString *className = NSStringFromClass(currentClass);
+        BOOL isSystemClass = ([className hasPrefix:@"UI"] || [className hasPrefix:@"CA"] || [className hasPrefix:@"NS"]);
+        
+        unsigned int methodCount = 0;
+        Method *methods = class_copyMethodList(currentClass, &methodCount);
+        for (unsigned int i = 0; i < methodCount; i++) {
+            NSString *selName = NSStringFromSelector(method_getName(methods[i]));
+            
+            if (!hasArg && [selName containsString:@":"]) {
+                continue;
+            }
+            
+            if (isSystemClass) {
+                BOOL invalid = [prefixesToVoid lookin_any:^BOOL(NSString *prefix) {
+                    return [selName hasPrefix:prefix];
+                }];
+                if (invalid) {
+                    continue;
+                }
+            }
+            if (selName.length && ![array containsObject:selName]) {
+                [array addObject:selName];
+            }
+        }
+        if (methods) free(methods);
+        currentClass = [currentClass superclass];
+    }
+
+    return [array lookin_sortedArrayByStringLength];
+}
+
+- (void)_handleInvokeWithObject:(NSObject *)obj selector:(SEL)selector resultDescription:(NSString **)description resultObject:(LookinObject **)resultObject error:(NSError **)error {
+    NSMethodSignature *signature = [obj methodSignatureForSelector:selector];
+    if (signature.numberOfArguments > 2) {
+        *error = LookinErrorMake(LKS_Localized(@"Lookin doesn't support invoking methods with arguments yet."), @"");
+        return;
+    }
+    
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+    [invocation setTarget:obj];
+    [invocation setSelector:selector];
+    [invocation invoke];
+
+    const char *returnType = [signature methodReturnType];
+    
+    
+    if (strcmp(returnType, @encode(void)) == 0) {
+        //void, do nothing
+        *description = LookinStringFlag_VoidReturn;
+        
+    } else if (strcmp(returnType, @encode(char)) == 0) {
+        char charValue;
+        [invocation getReturnValue:&charValue];
+        *description = [NSString stringWithFormat:@"%@", @(charValue)];
+        
+    } else if (strcmp(returnType, @encode(int)) == 0) {
+        int intValue;
+        [invocation getReturnValue:&intValue];
+        if (intValue == INT_MAX) {
+            *description = @"INT_MAX";
+        } else if (intValue == INT_MIN) {
+            *description = @"INT_MIN";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(intValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(short)) == 0) {
+        short shortValue;
+        [invocation getReturnValue:&shortValue];
+        if (shortValue == SHRT_MAX) {
+            *description = @"SHRT_MAX";
+        } else if (shortValue == SHRT_MIN) {
+            *description = @"SHRT_MIN";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(shortValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(long)) == 0) {
+        long longValue;
+        [invocation getReturnValue:&longValue];
+        if (longValue == NSNotFound) {
+            *description = @"NSNotFound";
+        } else if (longValue == LONG_MAX) {
+            *description = @"LONG_MAX";
+        } else if (longValue == LONG_MIN) {
+            *description = @"LONG_MAX";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(longValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(long long)) == 0) {
+        long long longLongValue;
+        [invocation getReturnValue:&longLongValue];
+        if (longLongValue == LLONG_MAX) {
+            *description = @"LLONG_MAX";
+        } else if (longLongValue == LLONG_MIN) {
+            *description = @"LLONG_MIN";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(longLongValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(unsigned char)) == 0) {
+        unsigned char ucharValue;
+        [invocation getReturnValue:&ucharValue];
+        if (ucharValue == UCHAR_MAX) {
+            *description = @"UCHAR_MAX";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(ucharValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(unsigned int)) == 0) {
+        unsigned int uintValue;
+        [invocation getReturnValue:&uintValue];
+        if (uintValue == UINT_MAX) {
+            *description = @"UINT_MAX";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(uintValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(unsigned short)) == 0) {
+        unsigned short ushortValue;
+        [invocation getReturnValue:&ushortValue];
+        if (ushortValue == USHRT_MAX) {
+            *description = @"USHRT_MAX";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(ushortValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(unsigned long)) == 0) {
+        unsigned long ulongValue;
+        [invocation getReturnValue:&ulongValue];
+        if (ulongValue == ULONG_MAX) {
+            *description = @"ULONG_MAX";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(ulongValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
+        unsigned long long ulongLongValue;
+        [invocation getReturnValue:&ulongLongValue];
+        if (ulongLongValue == ULONG_LONG_MAX) {
+            *description = @"ULONG_LONG_MAX";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(ulongLongValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(float)) == 0) {
+        float floatValue;
+        [invocation getReturnValue:&floatValue];
+        if (floatValue == FLT_MAX) {
+            *description = @"FLT_MAX";
+        } else if (floatValue == FLT_MIN) {
+            *description = @"FLT_MIN";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(floatValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(double)) == 0) {
+        double doubleValue;
+        [invocation getReturnValue:&doubleValue];
+        if (doubleValue == DBL_MAX) {
+            *description = @"DBL_MAX";
+        } else if (doubleValue == DBL_MIN) {
+            *description = @"DBL_MIN";
+        } else {
+            *description = [NSString stringWithFormat:@"%@", @(doubleValue)];
+        }
+        
+    } else if (strcmp(returnType, @encode(BOOL)) == 0) {
+        BOOL boolValue;
+        [invocation getReturnValue:&boolValue];
+        *description = boolValue ? @"YES" : @"NO";
+        
+    } else if (strcmp(returnType, @encode(SEL)) == 0) {
+        SEL selValue;
+        [invocation getReturnValue:&selValue];
+        *description = [NSString stringWithFormat:@"SEL(%@)", NSStringFromSelector(selValue)];
+        
+    } else if (strcmp(returnType, @encode(Class)) == 0) {
+        Class classValue;
+        [invocation getReturnValue:&classValue];
+        *description = [NSString stringWithFormat:@"<%@>", NSStringFromClass(classValue)];
+        
+    } else if (strcmp(returnType, @encode(CGPoint)) == 0) {
+        CGPoint targetValue;
+        [invocation getReturnValue:&targetValue];
+        *description = NSStringFromCGPoint(targetValue);
+
+    } else if (strcmp(returnType, @encode(CGVector)) == 0) {
+        CGVector targetValue;
+        [invocation getReturnValue:&targetValue];
+        *description = NSStringFromCGVector(targetValue);
+
+    } else if (strcmp(returnType, @encode(CGSize)) == 0) {
+        CGSize targetValue;
+        [invocation getReturnValue:&targetValue];
+        *description = NSStringFromCGSize(targetValue);
+
+    } else if (strcmp(returnType, @encode(CGRect)) == 0) {
+        CGRect rectValue;
+        [invocation getReturnValue:&rectValue];
+        *description = NSStringFromCGRect(rectValue);
+        
+    } else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) {
+        CGAffineTransform rectValue;
+        [invocation getReturnValue:&rectValue];
+        *description = NSStringFromCGAffineTransform(rectValue);
+        
+    } else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) {
+        UIEdgeInsets targetValue;
+        [invocation getReturnValue:&targetValue];
+        *description = NSStringFromUIEdgeInsets(targetValue);
+        
+    } else if (strcmp(returnType, @encode(UIOffset)) == 0) {
+        UIOffset targetValue;
+        [invocation getReturnValue:&targetValue];
+        *description = NSStringFromUIOffset(targetValue);
+        
+    } else {
+        if (@available(iOS 11.0, tvOS 11.0, *)) {
+            if (strcmp(returnType, @encode(NSDirectionalEdgeInsets)) == 0) {
+                NSDirectionalEdgeInsets targetValue;
+                [invocation getReturnValue:&targetValue];
+                *description = NSStringFromDirectionalEdgeInsets(targetValue);
+                return;
+            }
+        }
+        
+        NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType];
+        if ([argType_string hasPrefix:@"@"] || [argType_string hasPrefix:@"^{"]) {
+            __unsafe_unretained id returnObjValue;
+            [invocation getReturnValue:&returnObjValue];
+            
+            if (returnObjValue) {
+                *description = [NSString stringWithFormat:@"%@", returnObjValue];
+                
+                LookinObject *parsedLookinObj = [LookinObject instanceWithObject:returnObjValue];
+                *resultObject = parsedLookinObj;
+            } else {
+                *description = @"nil";
+            }
+        } else {
+            *description = [NSString stringWithFormat:LKS_Localized(@"%@ was invoked successfully, but Lookin can't parse the return value:%@"), NSStringFromSelector(selector), argType_string];
+        }
+    }
+}
+
+- (void)_submitResponseWithError:(NSError *)error requestType:(uint32_t)requestType tag:(uint32_t)tag {
+    LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
+    attachment.error = error;
+    [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
+}
+
+- (void)_submitResponseWithData:(NSObject *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
+    LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
+    attachment.data = data;
+    [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 26 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.h

@@ -0,0 +1,26 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_AttrModificationPatchHandler.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/12.
+//  https://lookin.work
+//
+
+#import <Foundation/Foundation.h>
+
+@class LookinDisplayItemDetail;
+
+@interface LKS_AttrModificationPatchHandler : NSObject
+
+/**
+ @param oids 数组内 idx 较小的应该为 displayItems 里的 subItem,idx 较大的应该为 superItem
+ @param lowImageQuality 是否采用低图像质量
+ @param block 该 block 会被多次调用,其中 tasksTotalCount 是总的调用次数(即可被用来作为 TotalResponseCount)
+ */
++ (void)handleLayerOids:(NSArray<NSNumber *> *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 51 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_AttrModificationPatchHandler.m

@@ -0,0 +1,51 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_AttrModificationPatchHandler.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/12.
+//  https://lookin.work
+//
+
+#import "LKS_AttrModificationPatchHandler.h"
+#import "LookinDisplayItemDetail.h"
+#import "LookinServerDefines.h"
+
+@implementation LKS_AttrModificationPatchHandler
+
++ (void)handleLayerOids:(NSArray<NSNumber *> *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block {
+    if (!block) {
+        NSAssert(NO, @"");
+        return;
+    }
+    if (![oids isKindOfClass:[NSArray class]]) {
+        block(nil, 1, LookinErr_Inner);
+        return;
+    }
+    
+    [oids enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        unsigned long oid = [obj unsignedLongValue];
+        LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new];
+        detail.displayItemOid = oid;
+        
+        CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid];
+        if (![layer isKindOfClass:[CALayer class]]) {
+            block(nil, idx + 1, LookinErr_ObjNotFound);
+            *stop = YES;
+            return;
+        }
+        
+        if (idx == 0) {
+            detail.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowImageQuality];
+            detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality];
+        } else {
+            detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality];
+        }
+        block(detail, oids.count, nil);
+    }];
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 19 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_CustomAttrModificationHandler.h

@@ -0,0 +1,19 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+//
+//  LKS_CustomAttrModificationHandler.h
+//  LookinServer
+//
+//  Created by likaimacbookhome on 2023/11/4.
+//
+
+#import <Foundation/Foundation.h>
+#import "LookinCustomAttrModification.h"
+
+@interface LKS_CustomAttrModificationHandler : NSObject
+
+/// 返回值表示是否修改成功(有成功调用 setter block 就算成功)
++ (BOOL)handleModification:(LookinCustomAttrModification *)modification;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 155 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_CustomAttrModificationHandler.m

@@ -0,0 +1,155 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+//
+//  LKS_CustomAttrModificationHandler.m
+//  LookinServer
+//
+//  Created by likaimacbookhome on 2023/11/4.
+//
+
+#import "LKS_CustomAttrModificationHandler.h"
+#import "LKS_CustomAttrSetterManager.h"
+#import "UIColor+LookinServer.h"
+
+@implementation LKS_CustomAttrModificationHandler
+
++ (BOOL)handleModification:(LookinCustomAttrModification *)modification {
+    if (!modification || modification.customSetterID.length == 0) {
+        return NO;
+    }
+    switch (modification.attrType) {
+        case LookinAttrTypeNSString: {
+            NSString *newValue = modification.value;
+            if (newValue != nil && ![newValue isKindOfClass:[NSString class]]) {
+                // nil 是合法的
+                return NO;
+            }
+            LKS_StringSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getStringSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue);
+            return YES;
+        }
+            
+        case LookinAttrTypeDouble: {
+            NSNumber *newValue = modification.value;
+            if (![newValue isKindOfClass:[NSNumber class]]) {
+                return NO;
+            }
+            LKS_NumberSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getNumberSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue);
+            return YES;
+        }
+            
+        case LookinAttrTypeBOOL: {
+            NSNumber *newValue = modification.value;
+            if (![newValue isKindOfClass:[NSNumber class]]) {
+                return NO;
+            }
+            LKS_BoolSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getBoolSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue.boolValue);
+            return YES;
+        }
+        
+        case LookinAttrTypeUIColor: {
+            LKS_ColorSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getColorSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            
+            NSArray<NSNumber *> *newValue = modification.value;
+            if (newValue == nil) {
+                // nil 是合法的
+                setter(nil);
+                return YES;
+            }
+            if (![newValue isKindOfClass:[NSArray class]]) {
+                return NO;
+            }
+            UIColor *color = [UIColor lks_colorFromRGBAComponents:newValue];
+            if (!color) {
+                return NO;
+            }
+            setter(color);
+            return YES;
+        }
+            
+        case LookinAttrTypeEnumString: {
+            NSString *newValue = modification.value;
+            if (![newValue isKindOfClass:[NSString class]]) {
+                return NO;
+            }
+            LKS_EnumSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getEnumSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue);
+            return YES;
+        }
+            
+        case LookinAttrTypeCGRect: {
+            NSValue *newValue = modification.value;
+            if (![newValue isKindOfClass:[NSValue class]]) {
+                return NO;
+            }
+            LKS_RectSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getRectSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue.CGRectValue);
+            return YES;
+        }
+            
+        case LookinAttrTypeCGSize: {
+            NSValue *newValue = modification.value;
+            if (![newValue isKindOfClass:[NSValue class]]) {
+                return NO;
+            }
+            LKS_SizeSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getSizeSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue.CGSizeValue);
+            return YES;
+        }
+            
+        case LookinAttrTypeCGPoint: {
+            NSValue *newValue = modification.value;
+            if (![newValue isKindOfClass:[NSValue class]]) {
+                return NO;
+            }
+            LKS_PointSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getPointSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue.CGPointValue);
+            return YES;
+        }
+            
+        case LookinAttrTypeUIEdgeInsets: {
+            NSValue *newValue = modification.value;
+            if (![newValue isKindOfClass:[NSValue class]]) {
+                return NO;
+            }
+            LKS_InsetsSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getInsetsSetterWithID:modification.customSetterID];
+            if (!setter) {
+                return NO;
+            }
+            setter(newValue.UIEdgeInsetsValue);
+            return YES;
+        }
+            
+        default:
+            return NO;
+    }
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 30 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.h

@@ -0,0 +1,30 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_HierarchyDetailsHandler.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/20.
+//  https://lookin.work
+//
+
+#import <Foundation/Foundation.h>
+
+@class LookinDisplayItemDetail, LookinStaticAsyncUpdateTasksPackage;
+
+typedef void (^LKS_HierarchyDetailsHandler_ProgressBlock)(NSArray<LookinDisplayItemDetail *> *details);
+typedef void (^LKS_HierarchyDetailsHandler_FinishBlock)(void);
+
+@interface LKS_HierarchyDetailsHandler : NSObject
+
+/// packages 会按照 idx 从小到大的顺序被执行
+/// 全部任务完成时,finishBlock 会被调用
+/// 如果调用了 cancel,则 finishBlock 不会被执行
+- (void)startWithPackages:(NSArray<LookinStaticAsyncUpdateTasksPackage *> *)packages block:(LKS_HierarchyDetailsHandler_ProgressBlock)progressBlock finishedBlock:(LKS_HierarchyDetailsHandler_FinishBlock)finishBlock;
+
+/// 取消所有任务
+- (void)cancel;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 148 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_HierarchyDetailsHandler.m

@@ -0,0 +1,148 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_HierarchyDetailsHandler.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/20.
+//  https://lookin.work
+//
+
+#import "LKS_HierarchyDetailsHandler.h"
+#import "LookinDisplayItemDetail.h"
+#import "LKS_AttrGroupsMaker.h"
+#import "LookinStaticAsyncUpdateTask.h"
+#import "LKS_ConnectionManager.h"
+#import "LookinServerDefines.h"
+#import "LKS_CustomAttrGroupsMaker.h"
+#import "LKS_HierarchyDisplayItemsMaker.h"
+
+@interface LKS_HierarchyDetailsHandler ()
+
+@property(nonatomic, strong) NSMutableArray<LookinStaticAsyncUpdateTasksPackage *> *taskPackages;
+/// 标识哪些 oid 已经拉取过 attrGroups 了
+@property(nonatomic, strong) NSMutableSet<NSNumber *> *attrGroupsSyncedOids;
+
+@property(nonatomic, copy) LKS_HierarchyDetailsHandler_ProgressBlock progressBlock;
+@property(nonatomic, copy) LKS_HierarchyDetailsHandler_FinishBlock finishBlock;
+
+@end
+
+@implementation LKS_HierarchyDetailsHandler
+
+- (instancetype)init {
+    if (self = [super init]) {
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleConnectionDidEnd:) name:LKS_ConnectionDidEndNotificationName object:nil];
+        
+        self.attrGroupsSyncedOids = [NSMutableSet set];
+    }
+    return self;
+}
+
+- (void)startWithPackages:(NSArray<LookinStaticAsyncUpdateTasksPackage *> *)packages block:(LKS_HierarchyDetailsHandler_ProgressBlock)progressBlock finishedBlock:(LKS_HierarchyDetailsHandler_FinishBlock)finishBlock {
+    if (!progressBlock || !finishBlock) {
+        NSAssert(NO, @"");
+        return;
+    }
+    if (!packages.count) {
+        finishBlock();
+        return;
+    }
+    self.taskPackages = [packages mutableCopy];
+    self.progressBlock = progressBlock;
+    self.finishBlock = finishBlock;
+    
+    [UIView lks_rebuildGlobalInvolvedRawConstraints];
+    
+    [self _dequeueAndHandlePackage];
+}
+
+- (void)cancel {
+    [self.taskPackages removeAllObjects];
+}
+
+- (void)_dequeueAndHandlePackage {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        LookinStaticAsyncUpdateTasksPackage *package = self.taskPackages.firstObject;
+        if (!package) {
+            self.finishBlock();
+            return;
+        }
+        //        NSLog(@"LookinServer - will handle tasks, count: %@", @(tasks.count));
+        NSArray<LookinDisplayItemDetail *> *details = [package.tasks lookin_map:^id(NSUInteger idx, LookinStaticAsyncUpdateTask *task) {
+            LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new];
+            itemDetail.displayItemOid = task.oid;
+            
+            id object = [NSObject lks_objectWithOid:task.oid];
+            if (!object || ![object isKindOfClass:[CALayer class]]) {
+                itemDetail.failureCode = -1;
+                return itemDetail;
+            }
+            
+            CALayer *layer = object;
+
+            if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) {
+                UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO];
+                itemDetail.soloScreenshot = image;
+            } else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) {
+                UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO];
+                itemDetail.groupScreenshot = image;
+            }
+            
+            BOOL shouldMakeAttr = [self queryIfShouldMakeAttrsFromTask:task];
+            if (shouldMakeAttr) {
+                itemDetail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
+                
+                NSString *version = task.clientReadableVersion;
+                if (version.length > 0 && [version lookin_numbericOSVersion] >= 10004) {
+                    LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
+                    [maker execute];
+                    itemDetail.customAttrGroupList = [maker getGroups];
+                    itemDetail.customDisplayTitle = [maker getCustomDisplayTitle];
+                    itemDetail.danceUISource = [maker getDanceUISource];
+                }
+                [self.attrGroupsSyncedOids addObject:@(task.oid)];
+            }
+            
+            if (task.needBasisVisualInfo) {
+                itemDetail.frameValue = [NSValue valueWithCGRect:layer.frame];
+                itemDetail.boundsValue = [NSValue valueWithCGRect:layer.bounds];
+                itemDetail.hiddenValue = [NSNumber numberWithBool:layer.isHidden];
+                itemDetail.alphaValue = @(layer.opacity);
+            }
+            
+            if (task.needSubitems) {
+                itemDetail.subitems = [LKS_HierarchyDisplayItemsMaker subitemsOfLayer:layer];
+            }
+            
+            return itemDetail;
+        }];
+        self.progressBlock(details);
+        
+        [self.taskPackages removeObjectAtIndex:0];
+        [self _dequeueAndHandlePackage];
+    });
+}
+
+- (BOOL)queryIfShouldMakeAttrsFromTask:(LookinStaticAsyncUpdateTask *)task {
+    switch (task.attrRequest) {
+        case LookinDetailUpdateTaskAttrRequest_Automatic: {
+            BOOL alreadyMadeBefore = [self.attrGroupsSyncedOids containsObject:@(task.oid)];
+            return !alreadyMadeBefore;
+        }
+        case LookinDetailUpdateTaskAttrRequest_Need:
+            return YES;
+        case LookinDetailUpdateTaskAttrRequest_NotNeed:
+            return NO;
+    }
+    NSAssert(NO, @"");
+    return YES;
+}
+
+- (void)_handleConnectionDidEnd:(id)obj {
+    [self cancel];
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 23 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_InbuiltAttrModificationHandler.h

@@ -0,0 +1,23 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_InbuiltAttrModificationHandler.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/12.
+//  https://lookin.work
+//
+
+#import <Foundation/Foundation.h>
+
+@class LookinAttributeModification, LookinDisplayItemDetail, LookinStaticAsyncUpdateTask;
+
+@interface LKS_InbuiltAttrModificationHandler : NSObject
+
++ (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion;
+
++ (void)handlePatchWithTasks:(NSArray<LookinStaticAsyncUpdateTask *> *)tasks block:(void (^)(LookinDisplayItemDetail *data))block;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 255 - 0
Pods/LookinServer/Src/Main/Server/Connection/RequestHandler/LKS_InbuiltAttrModificationHandler.m

@@ -0,0 +1,255 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_InbuiltAttrModificationHandler.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/12.
+//  https://lookin.work
+//
+
+#import "LKS_InbuiltAttrModificationHandler.h"
+#import "UIColor+LookinServer.h"
+#import "LookinAttributeModification.h"
+#import "LKS_AttrGroupsMaker.h"
+#import "LookinDisplayItemDetail.h"
+#import "LookinStaticAsyncUpdateTask.h"
+#import "LookinServerDefines.h"
+#import "LKS_CustomAttrGroupsMaker.h"
+
+@implementation LKS_InbuiltAttrModificationHandler
+
++ (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion {
+    if (!completion) {
+        NSAssert(NO, @"");
+        return;
+    }
+    if (!modification || ![modification isKindOfClass:[LookinAttributeModification class]]) {
+        completion(nil, LookinErr_Inner);
+        return;
+    }
+    
+    NSObject *receiver = [NSObject lks_objectWithOid:modification.targetOid];
+    if (!receiver) {
+        completion(nil, LookinErr_ObjNotFound);
+        return;
+    }
+    
+    NSMethodSignature *setterSignature = [receiver methodSignatureForSelector:modification.setterSelector];
+    NSInvocation *setterInvocation = [NSInvocation invocationWithMethodSignature:setterSignature];
+    setterInvocation.target = receiver;
+    setterInvocation.selector = modification.setterSelector;
+    
+    if (setterSignature.numberOfArguments != 3 || ![receiver respondsToSelector:modification.setterSelector]) {
+        completion(nil, LookinErr_Inner);
+        return;
+    }
+    
+    switch (modification.attrType) {
+        case LookinAttrTypeNone:
+        case LookinAttrTypeVoid: {
+            completion(nil, LookinErr_Inner);
+            return;
+        }
+        case LookinAttrTypeChar: {
+            char expectedValue = [(NSNumber *)modification.value charValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeInt:
+        case LookinAttrTypeEnumInt: {
+            int expectedValue = [(NSNumber *)modification.value intValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeShort: {
+            short expectedValue = [(NSNumber *)modification.value shortValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeLong:
+        case LookinAttrTypeEnumLong: {
+            long expectedValue = [(NSNumber *)modification.value longValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeLongLong: {
+            long long expectedValue = [(NSNumber *)modification.value longLongValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeUnsignedChar: {
+            unsigned char expectedValue = [(NSNumber *)modification.value unsignedCharValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeUnsignedInt: {
+            unsigned int expectedValue = [(NSNumber *)modification.value unsignedIntValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeUnsignedShort: {
+            unsigned short expectedValue = [(NSNumber *)modification.value unsignedShortValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeUnsignedLong: {
+            unsigned long expectedValue = [(NSNumber *)modification.value unsignedLongValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeUnsignedLongLong: {
+            unsigned long long expectedValue = [(NSNumber *)modification.value unsignedLongLongValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeFloat: {
+            float expectedValue = [(NSNumber *)modification.value floatValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeDouble: {
+            double expectedValue = [(NSNumber *)modification.value doubleValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeBOOL: {
+            BOOL expectedValue = [(NSNumber *)modification.value boolValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeSel: {
+            SEL expectedValue = NSSelectorFromString(modification.value);
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeClass: {
+            Class expectedValue = NSClassFromString(modification.value);
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeCGPoint: {
+            CGPoint expectedValue = [(NSValue *)modification.value CGPointValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeCGVector: {
+            CGVector expectedValue = [(NSValue *)modification.value CGVectorValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeCGSize: {
+            CGSize expectedValue = [(NSValue *)modification.value CGSizeValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeCGRect: {
+            CGRect expectedValue = [(NSValue *)modification.value CGRectValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeCGAffineTransform: {
+            CGAffineTransform expectedValue = [(NSValue *)modification.value CGAffineTransformValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeUIEdgeInsets: {
+            UIEdgeInsets expectedValue = [(NSValue *)modification.value UIEdgeInsetsValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeUIOffset: {
+            UIOffset expectedValue = [(NSValue *)modification.value UIOffsetValue];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            break;
+        }
+        case LookinAttrTypeCustomObj:
+        case LookinAttrTypeNSString: {
+            NSObject *expectedValue = modification.value;
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            [setterInvocation retainArguments];
+            break;
+        }
+        case LookinAttrTypeUIColor: {
+            NSArray<NSNumber *> *rgba = modification.value;
+            UIColor *expectedValue = [UIColor lks_colorFromRGBAComponents:rgba];
+            [setterInvocation setArgument:&expectedValue atIndex:2];
+            [setterInvocation retainArguments];
+            break;
+        }
+        default: {
+            completion(nil, LookinErr_Inner);
+            return;
+        }
+    }
+    
+    NSError *error = nil;
+    @try {
+        [setterInvocation invoke];
+    } @catch (NSException *exception) {
+        NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"<%@: %p>: an exception was raised when invoking %@. (%@)"), NSStringFromClass(receiver.class), receiver, NSStringFromSelector(modification.setterSelector), exception.reason];
+        error = [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Exception userInfo:@{NSLocalizedDescriptionKey:LKS_Localized(@"The modification may failed."), NSLocalizedRecoverySuggestionErrorKey:errorMsg}];
+    } @finally {
+        
+    }
+    
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        CALayer *layer = nil;
+        if ([receiver isKindOfClass:[CALayer class]]) {
+            layer = (CALayer *)receiver;
+        } else if ([receiver isKindOfClass:[UIView class]]) {
+            layer = ((UIView *)receiver).layer;
+        } else {
+            completion(nil, LookinErr_ObjNotFound);
+            return;
+        }
+        // 比如试图更改 frame 时,这个改动很有可能触发用户业务的 relayout,因此这时 dispatch 一下以确保拿到的 attrGroups 数据是最新的
+        LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new];
+        detail.displayItemOid = modification.targetOid;
+        detail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
+        
+        NSString *version = modification.clientReadableVersion;
+        if (version.length > 0 && [version lookin_numbericOSVersion] >= 10004) {
+            LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
+            [maker execute];
+            detail.customAttrGroupList = [maker getGroups];
+        }
+        
+        detail.frameValue = [NSValue valueWithCGRect:layer.frame];
+        detail.boundsValue = [NSValue valueWithCGRect:layer.bounds];
+        detail.hiddenValue = [NSNumber numberWithBool:layer.isHidden];
+        detail.alphaValue = @(layer.opacity);
+        completion(detail, error);
+    });
+}
+
+
++ (void)handlePatchWithTasks:(NSArray<LookinStaticAsyncUpdateTask *> *)tasks block:(void (^)(LookinDisplayItemDetail *data))block {
+    if (!block) {
+        NSAssert(NO, @"");
+        return;
+    }
+    [tasks enumerateObjectsUsingBlock:^(LookinStaticAsyncUpdateTask * _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
+        LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new];
+        itemDetail.displayItemOid = task.oid;
+        id object = [NSObject lks_objectWithOid:task.oid];
+        if (!object || ![object isKindOfClass:[CALayer class]]) {
+            block(itemDetail);
+            return;
+        }
+        
+        CALayer *layer = object;
+        if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) {
+            UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO];
+            itemDetail.soloScreenshot = image;
+        } else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) {
+            UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO];
+            itemDetail.groupScreenshot = image;
+        }
+        block(itemDetail);
+    }];
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 17 - 0
Pods/LookinServer/Src/Main/Server/LookinServer.h

@@ -0,0 +1,17 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LookinServer.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/7/20.
+//  https://lookin.work
+//
+
+#ifndef LookinServer_h
+#define LookinServer_h
+
+
+#endif /* LookinServer_h */
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 26 - 0
Pods/LookinServer/Src/Main/Server/Others/LKSConfigManager.h

@@ -0,0 +1,26 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+
+//
+//  LKSConfigManager.h
+//  LookinServer
+//
+//  Created by likai.123 on 2023/1/10.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface LKSConfigManager : NSObject
+
++ (NSArray<NSString *> *)collapsedClassList;
+
++ (NSDictionary<NSString *, UIColor *> *)colorAlias;
+
++ (BOOL)shouldCaptureScreenshotOfLayer:(CALayer *)layer;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 195 - 0
Pods/LookinServer/Src/Main/Server/Others/LKSConfigManager.m

@@ -0,0 +1,195 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+
+//
+//  LKSConfigManager.m
+//  LookinServer
+//
+//  Created by likai.123 on 2023/1/10.
+//
+
+#import "LKSConfigManager.h"
+#import "NSArray+Lookin.h"
+#import "CALayer+LookinServer.h"
+
+@implementation LKSConfigManager
+
++ (NSArray<NSString *> *)collapsedClassList {
+    NSArray<NSString *> *result = [self queryCollapsedClassListWithClass:[NSObject class] selector:@"lookin_collapsedClassList"];
+    if (result) {
+        return result;
+    }
+    
+    // Legacy logic. Deprecated.
+    Class configClass = NSClassFromString(@"LookinConfig");
+    if (!configClass) {
+        return nil;
+    }
+    NSArray<NSString *> *legacyCodeResult = [self queryCollapsedClassListWithClass:configClass selector:@"collapsedClasses"];
+    return legacyCodeResult;
+}
+
++ (NSArray<NSString *> *)queryCollapsedClassListWithClass:(Class)class selector:(NSString *)selectorName {
+    SEL selector = NSSelectorFromString(selectorName);
+    if (![class respondsToSelector:selector]) {
+        return nil;
+    }
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[class methodSignatureForSelector:selector]];
+    [invocation setTarget:class];
+    [invocation setSelector:selector];
+    [invocation invoke];
+    void *arrayValue;
+    [invocation getReturnValue:&arrayValue];
+    id classList = (__bridge id)(arrayValue);
+    
+    if ([classList isKindOfClass:[NSArray class]]) {
+        NSArray *validClassList = [((NSArray *)classList) lookin_filter:^BOOL(id obj) {
+            return [obj isKindOfClass:[NSString class]];
+        }];
+        return [validClassList copy];
+    }
+    return nil;
+}
+
++ (NSDictionary<NSString *, UIColor *> *)colorAlias {
+    NSDictionary<NSString *, UIColor *> *result = [self queryColorAliasWithClass:[NSObject class] selector:@"lookin_colorAlias"];
+    if (result) {
+        return result;
+    }
+    
+    // Legacy logic. Deprecated.
+    Class configClass = NSClassFromString(@"LookinConfig");
+    if (!configClass) {
+        return nil;
+    }
+    NSDictionary<NSString *, UIColor *> *legacyCodeResult = [self queryColorAliasWithClass:configClass selector:@"colors"];
+    return legacyCodeResult;
+}
+
++ (NSDictionary<NSString *, UIColor *> *)queryColorAliasWithClass:(Class)class selector:(NSString *)selectorName {
+    SEL selector = NSSelectorFromString(selectorName);
+    if (![class respondsToSelector:selector]) {
+        return nil;
+    }
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[class methodSignatureForSelector:selector]];
+    [invocation setTarget:class];
+    [invocation setSelector:selector];
+    [invocation invoke];
+    void *dictValue;
+    [invocation getReturnValue:&dictValue];
+    id colorAlias = (__bridge id)(dictValue);
+    
+    if ([colorAlias isKindOfClass:[NSDictionary class]]) {
+        NSMutableDictionary *validDictionary = [NSMutableDictionary dictionary];
+        [(NSDictionary *)colorAlias enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
+            if ([key isKindOfClass:[NSString class]]) {
+                if ([obj isKindOfClass:[UIColor class]]) {
+                    [validDictionary setObject:obj forKey:key];
+                    
+                } else if ([obj isKindOfClass:[NSDictionary class]]) {
+                    __block BOOL isValidSubDict = YES;
+                    [((NSDictionary *)obj) enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull subKey, id  _Nonnull subObj, BOOL * _Nonnull stop) {
+                        if (![subKey isKindOfClass:[NSString class]] || ![subObj isKindOfClass:[UIColor class]]) {
+                            isValidSubDict = NO;
+                            *stop = YES;
+                        }
+                    }];
+                    if (isValidSubDict) {
+                        [validDictionary setObject:obj forKey:key];
+                    }
+                }
+            }
+        }];
+        return [validDictionary copy];
+    }
+    return nil;
+}
+
++ (BOOL)shouldCaptureScreenshotOfLayer:(CALayer *)layer {
+    if (!layer) {
+        return YES;
+    }
+    if (![self shouldCaptureImageOfLayer:layer]) {
+        return NO;
+    }
+    UIView *view = layer.lks_hostView;
+    if (!view) {
+        return YES;
+    }
+    if (![self shouldCaptureImageOfView:view]) {
+        return NO;
+    }
+    return YES;
+}
+
++ (BOOL)shouldCaptureImageOfLayer:(CALayer *)layer {
+    if (!layer) {
+        return YES;
+    }
+    SEL selector = NSSelectorFromString(@"lookin_shouldCaptureImageOfLayer:");
+    if ([NSObject respondsToSelector:selector]) {
+        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSObject methodSignatureForSelector:selector]];
+        [invocation setTarget:[NSObject class]];
+        [invocation setSelector:selector];
+        [invocation setArgument:&layer atIndex:2];
+        [invocation invoke];
+        BOOL resultValue = YES;
+        [invocation getReturnValue:&resultValue];
+        if (!resultValue) {
+            return NO;
+        }
+    }
+
+    SEL selector2 = NSSelectorFromString(@"lookin_shouldCaptureImage");
+    if ([layer respondsToSelector:selector2]) {
+        NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:[layer methodSignatureForSelector:selector2]];
+        [invocation2 setTarget:layer];
+        [invocation2 setSelector:selector2];
+        [invocation2 invoke];
+        BOOL resultValue2 = YES;
+        [invocation2 getReturnValue:&resultValue2];
+        if (!resultValue2) {
+            return NO;
+        }
+    }
+
+    return YES;
+}
+
++ (BOOL)shouldCaptureImageOfView:(UIView *)view {
+    if (!view) {
+        return YES;
+    }
+    
+    SEL selector = NSSelectorFromString(@"lookin_shouldCaptureImageOfView:");
+    if ([NSObject respondsToSelector:selector]) {
+        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSObject methodSignatureForSelector:selector]];
+        [invocation setTarget:[NSObject class]];
+        [invocation setSelector:selector];
+        [invocation setArgument:&view atIndex:2];
+        [invocation invoke];
+        BOOL resultValue = YES;
+        [invocation getReturnValue:&resultValue];
+        if (!resultValue) {
+            return NO;
+        }
+    }
+    
+    SEL selector2 = NSSelectorFromString(@"lookin_shouldCaptureImage");
+    if ([view respondsToSelector:selector2]) {
+        NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:[view methodSignatureForSelector:selector2]];
+        [invocation2 setTarget:view];
+        [invocation2 setSelector:selector2];
+        [invocation2 invoke];
+        BOOL resultValue2 = YES;
+        [invocation2 getReturnValue:&resultValue2];
+        if (!resultValue2) {
+            return NO;
+        }
+    }
+
+    return YES;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_AttrGroupsMaker.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_AttrGroupsMaker.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/6.
+//  https://lookin.work
+//
+
+#import "LookinDefines.h"
+
+@class LookinAttributesGroup;
+
+@interface LKS_AttrGroupsMaker : NSObject
+    
++ (NSArray<LookinAttributesGroup *> *)attrGroupsForLayer:(CALayer *)layer;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 302 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_AttrGroupsMaker.m

@@ -0,0 +1,302 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_AttrGroupsMaker.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/6/6.
+//  https://lookin.work
+//
+
+#import "LKS_AttrGroupsMaker.h"
+#import "LookinAttributesGroup.h"
+#import "LookinAttributesSection.h"
+#import "LookinAttribute.h"
+#import "LookinDashboardBlueprint.h"
+#import "LookinIvarTrace.h"
+#import "UIColor+LookinServer.h"
+#import "LookinServerDefines.h"
+
+@implementation LKS_AttrGroupsMaker
+
++ (NSArray<LookinAttributesGroup *> *)attrGroupsForLayer:(CALayer *)layer {
+    if (!layer) {
+        NSAssert(NO, @"");
+        return nil;
+    }
+    NSArray<LookinAttributesGroup *> *groups = [[LookinDashboardBlueprint groupIDs] lookin_map:^id(NSUInteger idx, LookinAttrGroupIdentifier groupID) {
+        LookinAttributesGroup *group = [LookinAttributesGroup new];
+        group.identifier = groupID;
+
+        NSArray<LookinAttrSectionIdentifier> *secIDs = [LookinDashboardBlueprint sectionIDsForGroupID:groupID];
+        group.attrSections = [secIDs lookin_map:^id(NSUInteger idx, LookinAttrSectionIdentifier secID) {
+            LookinAttributesSection *sec = [LookinAttributesSection new];
+            sec.identifier = secID;
+            
+            NSArray<LookinAttrIdentifier> *attrIDs = [LookinDashboardBlueprint attrIDsForSectionID:secID];
+            sec.attributes = [attrIDs lookin_map:^id(NSUInteger idx, LookinAttrIdentifier attrID) {
+                NSInteger minAvailableVersion = [LookinDashboardBlueprint minAvailableOSVersionWithAttrID:attrID];
+                if (minAvailableVersion > 0 && (NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < minAvailableVersion)) {
+                    // iOS 版本过低不支持该属性
+                    return nil;
+                }
+                
+                id targetObj = nil;
+                if ([LookinDashboardBlueprint isUIViewPropertyWithAttrID:attrID]) {
+                    targetObj = layer.lks_hostView;
+                } else {
+                    targetObj = layer;
+                }
+                
+                if (targetObj) {
+                    Class targetClass = NSClassFromString([LookinDashboardBlueprint classNameWithAttrID:attrID]);
+                    if (![targetObj isKindOfClass:targetClass]) {
+                        return nil;
+                    }
+                    
+                    LookinAttribute *attr = [self _attributeWithIdentifer:attrID targetObject:targetObj];
+                    return attr;
+                } else {
+                    return nil;
+                }
+            }];
+            
+            if (sec.attributes.count) {
+                return sec;
+            } else {
+                return nil;
+            }
+        }];
+        
+        if ([groupID isEqualToString:LookinAttrGroup_AutoLayout]) {
+            // 这里特殊处理一下,如果 AutoLayout 里面不包含 Constraints 的话(只有 Hugging 和 Resistance),就丢弃掉这整个 AutoLayout 不显示
+            BOOL hasConstraits = [group.attrSections lookin_any:^BOOL(LookinAttributesSection *obj) {
+                return [obj.identifier isEqualToString:LookinAttrSec_AutoLayout_Constraints];
+            }];
+            if (!hasConstraits) {
+                return nil;
+            }
+        }
+        
+        if (group.attrSections.count) {
+            return group;
+        } else {
+            return nil;
+        }
+    }];
+    
+    return groups;
+}
+
++ (LookinAttribute *)_attributeWithIdentifer:(LookinAttrIdentifier)identifier targetObject:(id)target {
+    if (!target) {
+        NSAssert(NO, @"");
+        return nil;
+    }
+    
+    LookinAttribute *attribute = [LookinAttribute new];
+    attribute.identifier = identifier;
+    
+    SEL getter = [LookinDashboardBlueprint getterWithAttrID:identifier];
+    if (!getter) {
+        NSAssert(NO, @"");
+        return nil;
+    }
+    if (![target respondsToSelector:getter]) {
+        // 比如某些 QMUI 的属性,不引入 QMUI 就会走到这个分支里
+        return nil;
+    }
+    NSMethodSignature *signature = [target methodSignatureForSelector:getter];
+    if (signature.numberOfArguments > 2) {
+        NSAssert(NO, @"getter 不可以有参数");
+        return nil;
+    }
+    if (strcmp([signature methodReturnType], @encode(void)) == 0) {
+        NSAssert(NO, @"getter 返回值不能为 void");
+        return nil;
+    }
+    
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+    invocation.target = target;
+    invocation.selector = getter;
+    [invocation invoke];
+    
+    const char *returnType = [signature methodReturnType];
+    
+    if (strcmp(returnType, @encode(char)) == 0) {
+        char targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeChar;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(int)) == 0) {
+        int targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.value = @(targetValue);
+        if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) {
+            attribute.attrType = LookinAttrTypeEnumInt;
+        } else {
+            attribute.attrType = LookinAttrTypeInt;
+        }
+        
+    } else if (strcmp(returnType, @encode(short)) == 0) {
+        short targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeShort;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(long)) == 0) {
+        long targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.value = @(targetValue);
+        if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) {
+            attribute.attrType = LookinAttrTypeEnumLong;
+        } else {
+            attribute.attrType = LookinAttrTypeLong;
+        }
+        
+    } else if (strcmp(returnType, @encode(long long)) == 0) {
+        long long targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeLongLong;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(unsigned char)) == 0) {
+        unsigned char targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeUnsignedChar;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(unsigned int)) == 0) {
+        unsigned int targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeUnsignedInt;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(unsigned short)) == 0) {
+        unsigned short targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeUnsignedShort;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(unsigned long)) == 0) {
+        unsigned long targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeUnsignedLong;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
+        unsigned long long targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeUnsignedLongLong;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(float)) == 0) {
+        float targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeFloat;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(double)) == 0) {
+        double targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeDouble;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(BOOL)) == 0) {
+        BOOL targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeBOOL;
+        attribute.value = @(targetValue);
+        
+    } else if (strcmp(returnType, @encode(SEL)) == 0) {
+        SEL targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeSel;
+        attribute.value = NSStringFromSelector(targetValue);
+        
+    } else if (strcmp(returnType, @encode(Class)) == 0) {
+        Class targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeClass;
+        attribute.value = NSStringFromClass(targetValue);
+        
+    } else if (strcmp(returnType, @encode(CGPoint)) == 0) {
+        CGPoint targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeCGPoint;
+        attribute.value = [NSValue valueWithCGPoint:targetValue];
+        
+    } else if (strcmp(returnType, @encode(CGVector)) == 0) {
+        CGVector targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeCGVector;
+        attribute.value = [NSValue valueWithCGVector:targetValue];
+        
+    } else if (strcmp(returnType, @encode(CGSize)) == 0) {
+        CGSize targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeCGSize;
+        attribute.value = [NSValue valueWithCGSize:targetValue];
+        
+    } else if (strcmp(returnType, @encode(CGRect)) == 0) {
+        CGRect targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeCGRect;
+        attribute.value = [NSValue valueWithCGRect:targetValue];
+        
+    } else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) {
+        CGAffineTransform targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeCGAffineTransform;
+        attribute.value = [NSValue valueWithCGAffineTransform:targetValue];
+        
+    } else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) {
+        UIEdgeInsets targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeUIEdgeInsets;
+        attribute.value = [NSValue valueWithUIEdgeInsets:targetValue];
+        
+    } else if (strcmp(returnType, @encode(UIOffset)) == 0) {
+        UIOffset targetValue;
+        [invocation getReturnValue:&targetValue];
+        attribute.attrType = LookinAttrTypeUIOffset;
+        attribute.value = [NSValue valueWithUIOffset:targetValue];
+        
+    } else {
+        NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType];
+        if ([argType_string hasPrefix:@"@"]) {
+            __unsafe_unretained id returnObjValue;
+            [invocation getReturnValue:&returnObjValue];
+            
+            if (!returnObjValue && [LookinDashboardBlueprint hideIfNilWithAttrID:identifier]) {
+                // 对于某些属性,若 value 为 nil 则不显示
+                return nil;
+            }
+            
+            attribute.attrType = [LookinDashboardBlueprint objectAttrTypeWithAttrID:identifier];
+            if (attribute.attrType == LookinAttrTypeUIColor) {
+                if (returnObjValue == nil) {
+                    attribute.value = nil;
+                } else if ([returnObjValue isKindOfClass:[UIColor class]] && [returnObjValue respondsToSelector:@selector(lks_rgbaComponents)]) {
+                    attribute.value = [returnObjValue lks_rgbaComponents];
+                } else {
+                    // https://github.com/QMUI/LookinServer/issues/124
+                    return nil;
+                }
+            } else {
+                attribute.value = returnObjValue;
+            }
+            
+        } else {
+            NSAssert(NO, @"不支持解析该类型的返回值");
+            return nil;
+        }
+    }
+    
+    return attribute;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 27 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrGroupsMaker.h

@@ -0,0 +1,27 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+//
+//  LKS_CustomAttrGroupsMaker.h
+//  LookinServer
+//
+//  Created by LikaiMacStudioWork on 2023/10/31.
+//
+
+#import "LookinDefines.h"
+
+@class LookinAttributesGroup;
+
+@interface LKS_CustomAttrGroupsMaker : NSObject
+
+- (instancetype)initWithLayer:(CALayer *)layer;
+
+- (void)execute;
+
+- (NSArray<LookinAttributesGroup *> *)getGroups;
+- (NSString *)getCustomDisplayTitle;
+- (NSString *)getDanceUISource;
+
++ (NSArray<LookinAttributesGroup *> *)makeGroupsFromRawProperties:(NSArray *)rawProperties saveCustomSetter:(BOOL)saveCustomSetter;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 486 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrGroupsMaker.m

@@ -0,0 +1,486 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+//
+//  LKS_CustomAttrGroupsMaker.m
+//  LookinServer
+//
+//  Created by LikaiMacStudioWork on 2023/10/31.
+//
+
+#import "LKS_CustomAttrGroupsMaker.h"
+#import "LKS_AttrGroupsMaker.h"
+#import "LookinAttributesGroup.h"
+#import "LookinAttributesSection.h"
+#import "LookinAttribute.h"
+#import "LookinDashboardBlueprint.h"
+#import "LookinIvarTrace.h"
+#import "UIColor+LookinServer.h"
+#import "LookinServerDefines.h"
+#import "LKS_CustomAttrSetterManager.h"
+
+@interface LKS_CustomAttrGroupsMaker ()
+
+/// key 是 section title
+@property(nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray<LookinAttribute *> *> *sectionAndAttrs;
+
+@property(nonatomic, copy) NSString *resolvedCustomDisplayTitle;
+@property(nonatomic, copy) NSString *resolvedDanceUISource;
+@property(nonatomic, strong) NSMutableArray *resolvedGroups;
+
+@property(nonatomic, weak) CALayer *layer;
+
+@end
+
+@implementation LKS_CustomAttrGroupsMaker
+
+- (instancetype)initWithLayer:(CALayer *)layer {
+    if (self = [super init]) {
+        self.sectionAndAttrs = [NSMutableDictionary dictionary];
+        self.layer = layer;
+    }
+    return self;
+}
+
+- (void)execute {
+    if (!self.layer) {
+        NSAssert(NO, @"");
+        return;
+    }
+    NSMutableArray<NSString *> *selectors = [NSMutableArray array];
+    [selectors addObject:@"lookin_customDebugInfos"];
+    for (int i = 0; i < 5; i++) {
+        [selectors addObject:[NSString stringWithFormat:@"lookin_customDebugInfos_%@", @(i)]];
+    }
+    
+    for (NSString *name in selectors) {
+        [self makeAttrsForViewOrLayer:self.layer selectorName:name];
+        
+        UIView *view = self.layer.lks_hostView;
+        if (view) {
+            [self makeAttrsForViewOrLayer:view selectorName:name];
+        }
+    }
+    
+    if ([self.sectionAndAttrs count] == 0) {
+        return;
+    }
+    NSMutableArray<LookinAttributesGroup *> *groups = [NSMutableArray array];
+    [self.sectionAndAttrs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull groupTitle, NSMutableArray<LookinAttribute *> * _Nonnull attrs, BOOL * _Nonnull stop) {
+        LookinAttributesGroup *group = [LookinAttributesGroup new];
+        group.userCustomTitle = groupTitle;
+        group.identifier = LookinAttrGroup_UserCustom;
+        
+        NSMutableArray<LookinAttributesSection *> *sections = [NSMutableArray array];
+        [attrs enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
+            LookinAttributesSection *sec = [LookinAttributesSection new];
+            sec.identifier = LookinAttrSec_UserCustom;
+            sec.attributes = @[attr];
+            [sections addObject:sec];
+        }];
+        
+        group.attrSections = sections;
+        [groups addObject:group];
+    }];
+    [groups sortedArrayUsingComparator:^NSComparisonResult(LookinAttributesGroup *obj1, LookinAttributesGroup *obj2) {
+        return [obj1.userCustomTitle compare:obj2.userCustomTitle];
+    }];
+    
+    self.resolvedGroups = groups;
+}
+
+- (void)makeAttrsForViewOrLayer:(id)viewOrLayer selectorName:(NSString *)selectorName {
+    if (!viewOrLayer || !selectorName.length) {
+        return;
+    }
+    if (![viewOrLayer isKindOfClass:[UIView class]] && ![viewOrLayer isKindOfClass:[CALayer class]]) {
+        return;
+    }
+    SEL selector = NSSelectorFromString(selectorName);
+    if (![viewOrLayer respondsToSelector:selector]) {
+        return;
+    }
+    NSMethodSignature *signature = [viewOrLayer methodSignatureForSelector:selector];
+    if (signature.numberOfArguments > 2) {
+        NSAssert(NO, @"LookinServer - There should be no explicit parameters.");
+        return;
+    }
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+    [invocation setTarget:viewOrLayer];
+    [invocation setSelector:selector];
+    [invocation invoke];
+    
+    // 小心这里的内存管理
+    NSDictionary<NSString *, id> * __unsafe_unretained tempRawData;
+    [invocation getReturnValue:&tempRawData];
+    if (!tempRawData || ![tempRawData isKindOfClass:[NSDictionary class]]) {
+        return;
+    }
+    
+    NSDictionary<NSString *, id> *rawData = tempRawData;
+    NSArray *rawProperties = rawData[@"properties"];
+    
+    NSString *customTitle = rawData[@"title"];
+    if (customTitle && [customTitle isKindOfClass:[NSString class]] && customTitle.length > 0) {
+        self.resolvedCustomDisplayTitle = customTitle;
+    }
+    
+    NSString *danceSource = rawData[@"lookin_source"];
+    if (danceSource && [danceSource isKindOfClass:[NSString class]] && danceSource.length > 0) {
+        self.resolvedDanceUISource = danceSource;
+    }
+    
+    [self makeAttrsFromRawProperties:rawProperties];
+}
+
+- (void)makeAttrsFromRawProperties:(NSArray *)rawProperties {
+    if (!rawProperties || ![rawProperties isKindOfClass:[NSArray class]]) {
+        return;
+    }
+    
+    for (NSDictionary<NSString *, id> *dict in rawProperties) {
+        NSString *groupTitle;
+        LookinAttribute *attr = [LKS_CustomAttrGroupsMaker attrFromRawDict:dict saveCustomSetter:YES groupTitle:&groupTitle];
+        if (!attr) {
+            continue;
+        }
+        if (!self.sectionAndAttrs[groupTitle]) {
+            self.sectionAndAttrs[groupTitle] = [NSMutableArray array];
+        }
+        [self.sectionAndAttrs[groupTitle] addObject:attr];
+    }
+}
+
++ (LookinAttribute *)attrFromRawDict:(NSDictionary *)dict saveCustomSetter:(BOOL)saveCustomSetter groupTitle:(inout NSString **)inoutGroupTitle {
+    LookinAttribute *attr = [LookinAttribute new];
+    attr.identifier = LookinAttr_UserCustom;
+    
+    NSString *title = dict[@"title"];
+    NSString *type = dict[@"valueType"];
+    NSString *section = dict[@"section"];
+    id value = dict[@"value"];
+    
+    if (!title || ![title isKindOfClass:[NSString class]]) {
+        NSLog(@"LookinServer - Wrong title");
+        return nil;
+    }
+    if (!type || ![type isKindOfClass:[NSString class]]) {
+        NSLog(@"LookinServer - Wrong valueType");
+        return nil;
+    }
+    if (!section || ![section isKindOfClass:[NSString class]] || section.length == 0) {
+        *inoutGroupTitle = @"Custom";
+    } else {
+        *inoutGroupTitle = section;
+    }
+    
+    attr.displayTitle = title;
+    
+    NSString *fixedType = type.lowercaseString;
+    if ([fixedType isEqualToString:@"string"]) {
+        if (value != nil && ![value isKindOfClass:[NSString class]]) {
+            // nil 是合法的
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeNSString;
+        attr.value = value;
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_StringSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveStringSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"number"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSNumber class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeDouble;
+        attr.value = value;
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_NumberSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveNumberSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+        
+    if ([fixedType isEqualToString:@"bool"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSNumber class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeBOOL;
+        attr.value = value;
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_BoolSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveBoolSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"color"]) {
+        if (value != nil && ![value isKindOfClass:[UIColor class]]) {
+            // nil 是合法的
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeUIColor;
+        attr.value = [(UIColor *)value lks_rgbaComponents];
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_ColorSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveColorSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"rect"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSValue class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeCGRect;
+        attr.value = value;
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_RectSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveRectSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"size"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSValue class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeCGSize;
+        attr.value = value;
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_SizeSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveSizeSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"point"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSValue class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeCGPoint;
+        attr.value = value;
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_PointSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] savePointSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"insets"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSValue class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeUIEdgeInsets;
+        attr.value = value;
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_InsetsSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveInsetsSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"shadow"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSDictionary class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        NSDictionary *shadowInfo = value;
+        if (![shadowInfo[@"offset"] isKindOfClass:[NSValue class]]) {
+            NSLog(@"LookinServer - Wrong value. No offset.");
+            return nil;
+        }
+        if (![shadowInfo[@"opacity"] isKindOfClass:[NSNumber class]]) {
+            NSLog(@"LookinServer - Wrong value. No opacity.");
+            return nil;
+        }
+        if (![shadowInfo[@"radius"] isKindOfClass:[NSNumber class]]) {
+            NSLog(@"LookinServer - Wrong value. No radius.");
+            return nil;
+        }
+        NSMutableDictionary *checkedShadowInfo = [@{
+            @"offset": shadowInfo[@"offset"],
+            @"opacity": shadowInfo[@"opacity"],
+            @"radius": shadowInfo[@"radius"]
+        } mutableCopy];
+        if ([shadowInfo[@"color"] isKindOfClass:[UIColor class]]) {
+            checkedShadowInfo[@"color"] = [(UIColor *)shadowInfo[@"color"] lks_rgbaComponents];
+        }
+        
+        attr.attrType = LookinAttrTypeShadow;
+        attr.value = checkedShadowInfo;
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"enum"]) {
+        if (value == nil) {
+            NSLog(@"LookinServer - No value.");
+            return nil;
+        }
+        if (![value isKindOfClass:[NSString class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeEnumString;
+        attr.value = value;
+        
+        NSArray<NSString *> *allEnumCases = dict[@"allEnumCases"];
+        if ([allEnumCases isKindOfClass:[NSArray class]]) {
+            attr.extraValue = allEnumCases;
+        }
+        
+        if (saveCustomSetter && dict[@"retainedSetter"]) {
+            NSString *uniqueID = [[NSUUID new] UUIDString];
+            LKS_EnumSetter setter = dict[@"retainedSetter"];
+            [[LKS_CustomAttrSetterManager sharedInstance] saveEnumSetter:setter uniqueID:uniqueID];
+            attr.customSetterID = uniqueID;
+        }
+        
+        return attr;
+    }
+    
+    if ([fixedType isEqualToString:@"json"]) {
+        if (![value isKindOfClass:[NSString class]]) {
+            NSLog(@"LookinServer - Wrong value type.");
+            return nil;
+        }
+        attr.attrType = LookinAttrTypeJson;
+        attr.value = value;
+        
+        return attr;
+    }
+    
+    NSLog(@"LookinServer - Unsupported value type.");
+    return nil;
+}
+
+- (NSArray<LookinAttributesGroup *> *)getGroups {
+    return self.resolvedGroups;
+}
+
+- (NSString *)getCustomDisplayTitle {
+    return self.resolvedCustomDisplayTitle;
+}
+
+- (NSString *)getDanceUISource {
+    return self.resolvedDanceUISource;
+}
+
++ (NSArray<LookinAttributesGroup *> *)makeGroupsFromRawProperties:(NSArray *)rawProperties saveCustomSetter:(BOOL)saveCustomSetter {
+    if (!rawProperties || ![rawProperties isKindOfClass:[NSArray class]]) {
+        return nil;
+    }
+    // key 是 group title
+    NSMutableDictionary<NSString *, NSMutableArray<LookinAttribute *> *> *groupTitleAndAttrs = [NSMutableDictionary dictionary];
+    
+    for (NSDictionary<NSString *, id> *dict in rawProperties) {
+        NSString *groupTitle;
+        LookinAttribute *attr = [LKS_CustomAttrGroupsMaker attrFromRawDict:dict saveCustomSetter:saveCustomSetter groupTitle:&groupTitle];
+        if (!attr) {
+            continue;
+        }
+        if (!groupTitleAndAttrs[groupTitle]) {
+            groupTitleAndAttrs[groupTitle] = [NSMutableArray array];
+        }
+        [groupTitleAndAttrs[groupTitle] addObject:attr];
+    }
+    
+    if ([groupTitleAndAttrs count] == 0) {
+        return nil;
+    }
+    NSMutableArray<LookinAttributesGroup *> *groups = [NSMutableArray array];
+    [groupTitleAndAttrs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull groupTitle, NSMutableArray<LookinAttribute *> * _Nonnull attrs, BOOL * _Nonnull stop) {
+        LookinAttributesGroup *group = [LookinAttributesGroup new];
+        group.userCustomTitle = groupTitle;
+        group.identifier = LookinAttrGroup_UserCustom;
+        
+        NSMutableArray<LookinAttributesSection *> *sections = [NSMutableArray array];
+        [attrs enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
+            LookinAttributesSection *sec = [LookinAttributesSection new];
+            sec.identifier = LookinAttrSec_UserCustom;
+            sec.attributes = @[attr];
+            [sections addObject:sec];
+        }];
+        
+        group.attrSections = sections;
+        [groups addObject:group];
+    }];
+    [groups sortedArrayUsingComparator:^NSComparisonResult(LookinAttributesGroup *obj1, LookinAttributesGroup *obj2) {
+        return [obj1.userCustomTitle compare:obj2.userCustomTitle];
+    }];
+    return [groups copy];
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 56 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrSetterManager.h

@@ -0,0 +1,56 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+//
+//  LKS_CustomAttrSetterManager.h
+//  LookinServer
+//
+//  Created by likai.123 on 2023/11/4.
+//
+
+#import <UIKit/UIKit.h>
+
+typedef void(^LKS_StringSetter)(NSString *);
+typedef void(^LKS_NumberSetter)(NSNumber *);
+typedef void(^LKS_BoolSetter)(BOOL);
+typedef void(^LKS_ColorSetter)(UIColor *);
+typedef void(^LKS_EnumSetter)(NSString *);
+typedef void(^LKS_RectSetter)(CGRect);
+typedef void(^LKS_SizeSetter)(CGSize);
+typedef void(^LKS_PointSetter)(CGPoint);
+typedef void(^LKS_InsetsSetter)(UIEdgeInsets);
+
+@interface LKS_CustomAttrSetterManager : NSObject
+
++ (instancetype)sharedInstance;
+
+- (void)removeAll;
+
+- (void)saveStringSetter:(LKS_StringSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_StringSetter)getStringSetterWithID:(NSString *)uniqueID;
+
+- (void)saveNumberSetter:(LKS_NumberSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_NumberSetter)getNumberSetterWithID:(NSString *)uniqueID;
+
+- (void)saveBoolSetter:(LKS_BoolSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_BoolSetter)getBoolSetterWithID:(NSString *)uniqueID;
+
+- (void)saveColorSetter:(LKS_ColorSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_ColorSetter)getColorSetterWithID:(NSString *)uniqueID;
+
+- (void)saveEnumSetter:(LKS_EnumSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_EnumSetter)getEnumSetterWithID:(NSString *)uniqueID;
+
+- (void)saveRectSetter:(LKS_RectSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_RectSetter)getRectSetterWithID:(NSString *)uniqueID;
+
+- (void)saveSizeSetter:(LKS_SizeSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_SizeSetter)getSizeSetterWithID:(NSString *)uniqueID;
+
+- (void)savePointSetter:(LKS_PointSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_PointSetter)getPointSetterWithID:(NSString *)uniqueID;
+
+- (void)saveInsetsSetter:(LKS_InsetsSetter)setter uniqueID:(NSString *)uniqueID;
+- (LKS_InsetsSetter)getInsetsSetterWithID:(NSString *)uniqueID;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 117 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_CustomAttrSetterManager.m

@@ -0,0 +1,117 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+//
+//  LKS_CustomAttrSetterManager.m
+//  LookinServer
+//
+//  Created by likai.123 on 2023/11/4.
+//
+
+#import "LKS_CustomAttrSetterManager.h"
+
+@interface LKS_CustomAttrSetterManager ()
+
+@property(nonatomic, strong) NSMutableDictionary *settersMap;
+
+@end
+
+@implementation LKS_CustomAttrSetterManager
+
++ (instancetype)sharedInstance {
+    static dispatch_once_t onceToken;
+    static LKS_CustomAttrSetterManager *instance = nil;
+    dispatch_once(&onceToken,^{
+        instance = [[super allocWithZone:NULL] init];
+    });
+    return instance;
+}
+
++ (id)allocWithZone:(struct _NSZone *)zone {
+    return [self sharedInstance];
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        self.settersMap = [NSMutableDictionary new];
+    }
+    return self;
+}
+
+- (void)removeAll {
+    [self.settersMap removeAllObjects];
+}
+
+- (void)saveStringSetter:(nonnull LKS_StringSetter)setter uniqueID:(nonnull NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (nullable LKS_StringSetter)getStringSetterWithID:(nonnull NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)saveNumberSetter:(LKS_NumberSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (nullable LKS_NumberSetter)getNumberSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)saveBoolSetter:(LKS_BoolSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (LKS_BoolSetter)getBoolSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)saveColorSetter:(LKS_ColorSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (LKS_ColorSetter)getColorSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)saveEnumSetter:(LKS_EnumSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (LKS_EnumSetter)getEnumSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)saveRectSetter:(LKS_RectSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (LKS_RectSetter)getRectSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)saveSizeSetter:(LKS_SizeSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (LKS_SizeSetter)getSizeSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)savePointSetter:(LKS_PointSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (LKS_PointSetter)getPointSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+- (void)saveInsetsSetter:(LKS_InsetsSetter)setter uniqueID:(NSString *)uniqueID {
+    self.settersMap[uniqueID] = setter;
+}
+
+- (LKS_InsetsSetter)getInsetsSetterWithID:(NSString *)uniqueID {
+    return self.settersMap[uniqueID];
+}
+
+@end
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 22 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_CustomDisplayItemsMaker.h

@@ -0,0 +1,22 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+
+//
+//  LKS_CustomDisplayItemsMaker.h
+//  LookinServer
+//
+//  Created by likai.123 on 2023/11/1.
+//
+
+#import <UIKit/UIKit.h>
+
+@class LookinDisplayItem;
+
+@interface LKS_CustomDisplayItemsMaker : NSObject
+
+- (instancetype)initWithLayer:(CALayer *)layer saveAttrSetter:(BOOL)saveAttrSetter;
+
+- (NSArray<LookinDisplayItem *> *)make;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 144 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_CustomDisplayItemsMaker.m

@@ -0,0 +1,144 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_CustomDisplayItemsMaker.m
+//  LookinServer
+//
+//  Created by likai.123 on 2023/11/1.
+//
+
+#import "LKS_CustomDisplayItemsMaker.h"
+#import "CALayer+LookinServer.h"
+#import "LookinDisplayItem.h"
+#import "NSArray+Lookin.h"
+#import "LKS_CustomAttrGroupsMaker.h"
+
+@interface LKS_CustomDisplayItemsMaker ()
+
+@property(nonatomic, weak) CALayer *layer;
+@property(nonatomic, assign) BOOL saveAttrSetter;
+@property(nonatomic, strong) NSMutableArray *allSubitems;
+
+@end
+
+@implementation LKS_CustomDisplayItemsMaker
+
+- (instancetype)initWithLayer:(CALayer *)layer saveAttrSetter:(BOOL)saveAttrSetter {
+    if (self = [super init]) {
+        self.layer = layer;
+        self.saveAttrSetter = saveAttrSetter;
+        self.allSubitems = [NSMutableArray array];
+    }
+    return self;
+}
+
+- (NSArray<LookinDisplayItem *> *)make {
+    if (!self.layer) {
+        NSAssert(NO, @"");
+        return nil;
+    }
+    NSMutableArray<NSString *> *selectors = [NSMutableArray array];
+    [selectors addObject:@"lookin_customDebugInfos"];
+    for (int i = 0; i < 5; i++) {
+        [selectors addObject:[NSString stringWithFormat:@"lookin_customDebugInfos_%@", @(i)]];
+    }
+    
+    for (NSString *name in selectors) {
+        [self makeSubitemsForViewOrLayer:self.layer selectorName:name];
+        
+        UIView *view = self.layer.lks_hostView;
+        if (view) {
+            [self makeSubitemsForViewOrLayer:view selectorName:name];
+        }
+    }
+    
+    if (self.allSubitems.count) {
+        return self.allSubitems;
+    } else {
+        return nil;
+    }
+}
+
+- (void)makeSubitemsForViewOrLayer:(id)viewOrLayer selectorName:(NSString *)selectorName {
+    if (!viewOrLayer || !selectorName.length) {
+        return;
+    }
+    if (![viewOrLayer isKindOfClass:[UIView class]] && ![viewOrLayer isKindOfClass:[CALayer class]]) {
+        return;
+    }
+    SEL selector = NSSelectorFromString(selectorName);
+    if (![viewOrLayer respondsToSelector:selector]) {
+        return;
+    }
+    NSMethodSignature *signature = [viewOrLayer methodSignatureForSelector:selector];
+    if (signature.numberOfArguments > 2) {
+        NSAssert(NO, @"LookinServer - There should be no explicit parameters.");
+        return;
+    }
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
+    [invocation setTarget:viewOrLayer];
+    [invocation setSelector:selector];
+    [invocation invoke];
+    
+    // 小心这里的内存管理
+    NSDictionary<NSString *, id> * __unsafe_unretained tempRawData;
+    [invocation getReturnValue:&tempRawData];
+    NSDictionary<NSString *, id> *rawData = tempRawData;
+    
+    [self makeSubitemsFromRawData:rawData];
+}
+
+- (void)makeSubitemsFromRawData:(NSDictionary<NSString *, id> *)data {
+    if (!data || ![data isKindOfClass:[NSDictionary class]]) {
+        return;
+    }
+    NSArray *rawSubviews = data[@"subviews"];
+    NSArray<LookinDisplayItem *> *newSubitems = [self displayItemsFromRawArray:rawSubviews];
+    if (newSubitems) {
+        [self.allSubitems addObjectsFromArray:newSubitems];
+    }
+}
+
+- (NSArray<LookinDisplayItem *> *)displayItemsFromRawArray:(NSArray<NSDictionary *> *)rawArray {
+    if (!rawArray || ![rawArray isKindOfClass:[NSArray class]]) {
+        return nil;
+    }
+    NSArray *items = [rawArray lookin_map:^id(NSUInteger idx, NSDictionary *rawDict) {
+        if (![rawDict isKindOfClass:[NSDictionary class]]) {
+            return nil;
+        }
+        return [self displayItemFromRawDict:rawDict];
+    }];
+    return items;
+}
+
+- (LookinDisplayItem *)displayItemFromRawDict:(NSDictionary<NSString *, id> *)dict {
+    NSString *title = dict[@"title"];
+    NSString *subtitle = dict[@"subtitle"];
+    NSValue *frameValue = dict[@"frameInWindow"];
+    NSArray *properties = dict[@"properties"];
+    NSArray *subviews = dict[@"subviews"];
+    NSString *danceSource = dict[@"lookin_source"];
+    
+    if (![title isKindOfClass:[NSString class]]) {
+        return nil;
+    }
+    LookinDisplayItem *newItem = [LookinDisplayItem new];
+    if (subviews && [subviews isKindOfClass:[NSArray class]]) {
+        newItem.subitems = [self displayItemsFromRawArray:subviews];
+    }
+    newItem.isHidden = NO;
+    newItem.alpha = 1.0;
+    newItem.customInfo = [LookinCustomDisplayItemInfo new];
+    newItem.customInfo.title = title;
+    newItem.customInfo.subtitle = subtitle;
+    newItem.customInfo.frameInWindow = frameValue;
+    newItem.customInfo.danceuiSource = danceSource;
+    newItem.customAttrGroupList = [LKS_CustomAttrGroupsMaker makeGroupsFromRawProperties:properties saveCustomSetter:self.saveAttrSetter];
+    
+    return newItem;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_EventHandlerMaker.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_EventHandlerMaker.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/8/7.
+//  https://lookin.work
+//
+
+#import "LookinDefines.h"
+
+@class LookinEventHandler;
+
+@interface LKS_EventHandlerMaker : NSObject
+
++ (NSArray<LookinEventHandler *> *)makeForView:(UIView *)view;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 215 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_EventHandlerMaker.m

@@ -0,0 +1,215 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_EventHandlerMaker.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/8/7.
+//  https://lookin.work
+//
+
+#import "LKS_EventHandlerMaker.h"
+#import "LookinTuple.h"
+#import "LookinEventHandler.h"
+#import "LookinObject.h"
+#import "LookinWeakContainer.h"
+#import "LookinIvarTrace.h"
+#import "LookinServerDefines.h"
+#import "LKS_GestureTargetActionsSearcher.h"
+#import "LKS_MultiplatformAdapter.h"
+
+@implementation LKS_EventHandlerMaker
+
++ (NSArray<LookinEventHandler *> *)makeForView:(UIView *)view {
+    if (!view) {
+        return nil;
+    }
+    
+    NSMutableArray<LookinEventHandler *> *allHandlers = nil;
+    
+    if ([view isKindOfClass:[UIControl class]]) {
+        NSArray<LookinEventHandler *> *targetActionHandlers = [self _targetActionHandlersForControl:(UIControl *)view];
+        if (targetActionHandlers.count) {
+            if (!allHandlers) {
+                allHandlers = [NSMutableArray array];
+            }
+            [allHandlers addObjectsFromArray:targetActionHandlers];
+        }
+    }
+    
+    NSArray<LookinEventHandler *> *gestureHandlers = [self _gestureHandlersForView:view];
+    if (gestureHandlers.count) {
+        if (!allHandlers) {
+            allHandlers = [NSMutableArray array];
+        }
+        [allHandlers addObjectsFromArray:gestureHandlers];
+    }
+    
+    return allHandlers.copy;
+}
+
++ (NSArray<LookinEventHandler *> *)_gestureHandlersForView:(UIView *)view {
+    if (view.gestureRecognizers.count == 0) {
+        return nil;
+    }
+    NSArray<LookinEventHandler *> *handlers = [view.gestureRecognizers lookin_map:^id(NSUInteger idx, __kindof UIGestureRecognizer *recognizer) {
+        LookinEventHandler *handler = [LookinEventHandler new];
+        handler.handlerType = LookinEventHandlerTypeGesture;
+        handler.eventName = NSStringFromClass([recognizer class]);
+        
+        NSArray<LookinTwoTuple *> *targetActionInfos = [LKS_GestureTargetActionsSearcher getTargetActionsFromRecognizer:recognizer];
+        handler.targetActions = [targetActionInfos lookin_map:^id(NSUInteger idx, LookinTwoTuple *rawTuple) {
+            NSObject *target = ((LookinWeakContainer *)rawTuple.first).object;
+            if (!target) {
+                // 该 target 已被释放
+                return nil;
+            }
+            LookinStringTwoTuple *newTuple = [LookinStringTwoTuple new];
+            newTuple.first = [LKS_Helper descriptionOfObject:target];
+            newTuple.second = (NSString *)rawTuple.second;
+            return newTuple;
+        }];
+        handler.inheritedRecognizerName = [self _inheritedRecognizerNameForRecognizer:recognizer];
+        handler.gestureRecognizerIsEnabled = recognizer.enabled;
+        if (recognizer.delegate) {
+            handler.gestureRecognizerDelegator = [LKS_Helper descriptionOfObject:recognizer.delegate];
+        }
+        handler.recognizerIvarTraces = [recognizer.lks_ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *trace) {
+            return [NSString stringWithFormat:@"(%@ *) -> %@", trace.hostClassName, trace.ivarName];
+        }];
+        
+        handler.recognizerOid = [recognizer lks_registerOid];
+        return handler;
+    }];
+    return handlers;
+}
+
++ (NSString *)_inheritedRecognizerNameForRecognizer:(UIGestureRecognizer *)recognizer {
+    if (!recognizer) {
+        NSAssert(NO, @"");
+        return nil;
+    }
+    
+    static NSArray<Class> *baseRecognizers;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        // 注意这里 UIScreenEdgePanGestureRecognizer 在 UIPanGestureRecognizer 前面,因为 UIScreenEdgePanGestureRecognizer 继承于 UIPanGestureRecognizer
+#if TARGET_OS_TV
+        baseRecognizers = @[[UILongPressGestureRecognizer class],
+                            [UIPanGestureRecognizer class],
+                            [UISwipeGestureRecognizer class],
+                            [UITapGestureRecognizer class]];
+#elif TARGET_OS_VISION
+        baseRecognizers = @[[UILongPressGestureRecognizer class],
+                            [UIPanGestureRecognizer class],
+                            [UISwipeGestureRecognizer class],
+                            [UIRotationGestureRecognizer class],
+                            [UIPinchGestureRecognizer class],
+                            [UITapGestureRecognizer class]];
+#else
+        baseRecognizers = @[[UILongPressGestureRecognizer class],
+                            [UIScreenEdgePanGestureRecognizer class],
+                            [UIPanGestureRecognizer class],
+                            [UISwipeGestureRecognizer class],
+                            [UIRotationGestureRecognizer class],
+                            [UIPinchGestureRecognizer class],
+                            [UITapGestureRecognizer class]];
+#endif
+
+    });
+    
+    __block NSString *result = @"UIGestureRecognizer";
+    [baseRecognizers enumerateObjectsUsingBlock:^(Class  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        if ([recognizer isMemberOfClass:obj]) {
+            // 自身就是基本款,则直接置为 nil
+            result = nil;
+            *stop = YES;
+            return;
+        }
+        if ([recognizer isKindOfClass:obj]) {
+            result = NSStringFromClass(obj);
+            *stop = YES;
+            return;
+        }
+    }];
+    return result;
+}
+
++ (NSArray<LookinEventHandler *> *)_targetActionHandlersForControl:(UIControl *)control {
+    static dispatch_once_t onceToken;
+    static NSArray<NSNumber *> *allEvents = nil;
+    dispatch_once(&onceToken,^{
+        allEvents = @[@(UIControlEventTouchDown), @(UIControlEventTouchDownRepeat), @(UIControlEventTouchDragInside), @(UIControlEventTouchDragOutside), @(UIControlEventTouchDragEnter), @(UIControlEventTouchDragExit), @(UIControlEventTouchUpInside), @(UIControlEventTouchUpOutside), @(UIControlEventTouchCancel), @(UIControlEventValueChanged), @(UIControlEventEditingDidBegin), @(UIControlEventEditingChanged), @(UIControlEventEditingDidEnd), @(UIControlEventEditingDidEndOnExit)];
+        if (@available(iOS 9.0, *)) {
+            allEvents = [allEvents arrayByAddingObject:@(UIControlEventPrimaryActionTriggered)];
+        }
+    });
+
+    NSSet *allTargets = control.allTargets;
+    
+    if (!allTargets.count) {
+        return nil;
+    }
+    
+    NSMutableArray<LookinEventHandler *> *handlers = [NSMutableArray array];
+    
+    [allEvents enumerateObjectsUsingBlock:^(NSNumber * _Nonnull eventNum, NSUInteger idx, BOOL * _Nonnull stop) {
+        UIControlEvents event = [eventNum unsignedIntegerValue];
+        
+        NSMutableArray<LookinStringTwoTuple *> *targetActions = [NSMutableArray array];
+        
+        [allTargets enumerateObjectsUsingBlock:^(id  _Nonnull target, BOOL * _Nonnull stop) {
+            NSArray<NSString *> *actions = [control actionsForTarget:target forControlEvent:event];
+            [actions enumerateObjectsUsingBlock:^(NSString * _Nonnull action, NSUInteger idx, BOOL * _Nonnull stop) {
+                LookinStringTwoTuple *tuple = [LookinStringTwoTuple new];
+                tuple.first = [LKS_Helper descriptionOfObject:target];
+                tuple.second = action;
+                [targetActions addObject:tuple];
+            }];
+        }];
+        
+        if (targetActions.count) {
+            LookinEventHandler *handler = [LookinEventHandler new];
+            handler.handlerType = LookinEventHandlerTypeTargetAction;
+            handler.eventName = [self _nameFromControlEvent:event];
+            handler.targetActions = targetActions.copy;
+            [handlers addObject:handler];
+        }
+    }];
+    
+    return handlers;
+}
+
++ (NSString *)_nameFromControlEvent:(UIControlEvents)event {
+    static dispatch_once_t onceToken;
+    static NSDictionary<NSNumber *, NSString *> *eventsAndNames = nil;
+    dispatch_once(&onceToken,^{
+        NSMutableDictionary<NSNumber *, NSString *> *eventsAndNames_m = @{
+            @(UIControlEventTouchDown): @"UIControlEventTouchDown",
+            @(UIControlEventTouchDownRepeat): @"UIControlEventTouchDownRepeat",
+            @(UIControlEventTouchDragInside): @"UIControlEventTouchDragInside",
+            @(UIControlEventTouchDragOutside): @"UIControlEventTouchDragOutside",
+            @(UIControlEventTouchDragEnter): @"UIControlEventTouchDragEnter",
+            @(UIControlEventTouchDragExit): @"UIControlEventTouchDragExit",
+            @(UIControlEventTouchUpInside): @"UIControlEventTouchUpInside",
+            @(UIControlEventTouchUpOutside): @"UIControlEventTouchUpOutside",
+            @(UIControlEventTouchCancel): @"UIControlEventTouchCancel",
+            @(UIControlEventValueChanged): @"UIControlEventValueChanged",
+            @(UIControlEventEditingDidBegin): @"UIControlEventEditingDidBegin",
+            @(UIControlEventEditingChanged): @"UIControlEventEditingChanged",
+            @(UIControlEventEditingDidEnd): @"UIControlEventEditingDidEnd",
+            @(UIControlEventEditingDidEndOnExit): @"UIControlEventEditingDidEndOnExit",
+        }.mutableCopy;
+        if (@available(iOS 9.0, *)) {
+            eventsAndNames_m[@(UIControlEventPrimaryActionTriggered)] = @"UIControlEventPrimaryActionTriggered";
+        }
+        eventsAndNames = eventsAndNames_m.copy;
+    });
+    
+    NSString *name = eventsAndNames[@(event)];
+    return name;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 21 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_ExportManager.h

@@ -0,0 +1,21 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_ExportManager.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/5/13.
+//  https://lookin.work
+//
+
+#import <Foundation/Foundation.h>
+
+@interface LKS_ExportManager : NSObject
+
++ (instancetype)sharedInstance;
+
+- (void)exportAndShare;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 193 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_ExportManager.m

@@ -0,0 +1,193 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_ExportManager.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/5/13.
+//  https://lookin.work
+//
+
+#import "LKS_ExportManager.h"
+#import "UIViewController+LookinServer.h"
+#import "LookinHierarchyInfo.h"
+#import "LookinHierarchyFile.h"
+#import "LookinAppInfo.h"
+#import "LookinServerDefines.h"
+#import "LKS_MultiplatformAdapter.h"
+
+@interface LKS_ExportManagerMaskView : UIView
+
+@property(nonatomic, strong) UIView *tipsView;
+@property(nonatomic, strong) UILabel *firstLabel;
+@property(nonatomic, strong) UILabel *secondLabel;
+@property(nonatomic, strong) UILabel *thirdLabel;
+
+@end
+
+@implementation LKS_ExportManagerMaskView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    if (self = [super initWithFrame:frame]) {
+        self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.35];
+        
+        self.tipsView = [UIView new];
+        self.tipsView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.88];
+        self.tipsView.layer.cornerRadius = 6;
+        self.tipsView.layer.masksToBounds = YES;
+        [self addSubview:self.tipsView];
+        
+        self.firstLabel = [UILabel new];
+        self.firstLabel.text = LKS_Localized(@"Creating File…");
+        self.firstLabel.textColor = [UIColor whiteColor];
+        self.firstLabel.font = [UIFont boldSystemFontOfSize:14];
+        self.firstLabel.textAlignment = NSTextAlignmentCenter;
+        self.firstLabel.numberOfLines = 0;
+        [self.tipsView addSubview:self.firstLabel];
+        
+        self.secondLabel = [UILabel new];
+        self.secondLabel.text = LKS_Localized(@"May take 8 or more seconds according to the UI complexity.");
+        self.secondLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1];
+        self.secondLabel.font = [UIFont systemFontOfSize:12];
+        self.secondLabel.textAlignment = NSTextAlignmentLeft;
+        self.secondLabel.numberOfLines = 0;
+        [self.tipsView addSubview:self.secondLabel];
+        
+        self.thirdLabel = [UILabel new];
+        self.thirdLabel.text = LKS_Localized(@"The file can be opend by Lookin.app in macOS.");
+        self.thirdLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1];
+        self.thirdLabel.font = [UIFont systemFontOfSize:12];
+        self.thirdLabel.textAlignment = NSTextAlignmentCenter;
+        self.thirdLabel.numberOfLines = 0;
+        [self.tipsView addSubview:self.thirdLabel];
+    }
+    return self;
+}
+
+- (void)layoutSubviews {
+    [super layoutSubviews];
+    
+    UIEdgeInsets insets = UIEdgeInsetsMake(8, 10, 8, 10);
+    CGFloat maxLabelWidth = self.bounds.size.width * .8 - insets.left - insets.right;
+    
+    CGSize firstSize = [self.firstLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
+    CGSize secondSize = [self.secondLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
+    CGSize thirdSize = [self.thirdLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
+
+    CGFloat tipsWidth = MAX(MAX(firstSize.width, secondSize.width), thirdSize.width) + insets.left + insets.right;
+
+    self.firstLabel.frame = CGRectMake(tipsWidth / 2.0 - firstSize.width / 2.0, insets.top, firstSize.width, firstSize.height);
+    self.secondLabel.frame = CGRectMake(tipsWidth / 2.0 - secondSize.width / 2.0, CGRectGetMaxY(self.firstLabel.frame) + 10, secondSize.width, secondSize.height);
+    self.thirdLabel.frame = CGRectMake(tipsWidth / 2.0 - thirdSize.width / 2.0, CGRectGetMaxY(self.secondLabel.frame) + 5, thirdSize.width, thirdSize.height);
+
+    self.tipsView.frame = ({
+        CGFloat height = CGRectGetMaxY(self.thirdLabel.frame) + insets.bottom;
+        CGRectMake(self.bounds.size.width / 2.0 - tipsWidth / 2.0, self.bounds.size.height / 2.0 - height / 2.0, tipsWidth, height);
+    });
+}
+
+@end
+
+@interface LKS_ExportManager ()
+
+#if TARGET_OS_TV
+#else
+@property(nonatomic, strong) UIDocumentInteractionController *documentController;
+#endif
+
+@property(nonatomic, strong) LKS_ExportManagerMaskView *maskView;
+
+@end
+
+@implementation LKS_ExportManager
+
++ (instancetype)sharedInstance {
+    static dispatch_once_t onceToken;
+    static LKS_ExportManager *instance = nil;
+    dispatch_once(&onceToken,^{
+        instance = [[super allocWithZone:NULL] init];
+    });
+    return instance;
+}
+
++ (id)allocWithZone:(struct _NSZone *)zone{
+    return [self sharedInstance];
+}
+
+#if TARGET_OS_TV
+- (void)exportAndShare {
+    NSAssert(NO, @"not supported");
+}
+#else
+- (void)exportAndShare {
+    
+    UIViewController *visibleVc = [UIViewController lks_visibleViewController];
+    if (!visibleVc) {
+        NSLog(@"LookinServer - Failed to export because we didn't find any visible view controller.");
+        return;
+    }
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_WillExport" object:nil];
+    
+    if (!self.maskView) {
+        self.maskView = [LKS_ExportManagerMaskView new];
+    }
+    [visibleVc.view.window addSubview:self.maskView];
+    self.maskView.frame = visibleVc.view.window.bounds;
+
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        LookinHierarchyInfo *info = [LookinHierarchyInfo exportedInfo];
+        LookinHierarchyFile *file = [LookinHierarchyFile new];
+        file.serverVersion = info.serverVersion;
+        file.hierarchyInfo = info;
+        NSData *data = [NSKeyedArchiver archivedDataWithRootObject:file];
+        if (!data) {
+            return;
+        }
+        
+        NSString *fileName = ({
+            NSString *timeString = ({
+                NSDate *date = [NSDate date];
+                NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+                [formatter setDateFormat:@"MMddHHmm"];
+                [formatter stringFromDate:date];
+            });
+            NSString *iOSVersion = ({
+                NSString *str = info.appInfo.osDescription;
+                NSUInteger dotIdx = [str rangeOfString:@"."].location;
+                if (dotIdx != NSNotFound) {
+                    str = [str substringToIndex:dotIdx];
+                }
+                str;
+            });
+            [NSString stringWithFormat:@"%@_ios%@_%@.lookin", info.appInfo.appName, iOSVersion, timeString];
+        });
+        NSString *path = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), fileName];
+        [data writeToFile:path atomically:YES];
+        
+        [self.maskView removeFromSuperview];
+        
+        if (!self.documentController) {
+            self.documentController = [UIDocumentInteractionController new];
+        }
+        self.documentController.URL = [NSURL fileURLWithPath:path];
+        if ([LKS_MultiplatformAdapter isiPad]) {
+            [self.documentController presentOpenInMenuFromRect:CGRectMake(0, 0, 1, 1) inView:visibleVc.view animated:YES];
+        } else {
+            [self.documentController presentOpenInMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES];
+        }
+        
+        [[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_DidFinishExport" object:nil];
+        
+//        [self.documentController presentOptionsMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES];
+        
+//        CFTimeInterval endTime = CACurrentMediaTime();
+//        CFTimeInterval consumingTime = endTime - startTime;
+//        NSLog(@"LookinServer - 导出 UI 结构耗时:%@", @(consumingTime));
+    });
+}
+#endif
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 26 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_GestureTargetActionsSearcher.h

@@ -0,0 +1,26 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+
+//
+//  LKS_GestureTargetActionsSearcher.h
+//  LookinServer
+//
+//  Created by likai.123 on 2023/9/11.
+//
+
+#import <UIKit/UIKit.h>
+
+@class LookinTwoTuple;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface LKS_GestureTargetActionsSearcher : NSObject
+
+/// 返回一个 UIGestureRecognizer 实例身上绑定的 target & action 信息
+/// tuple.first => LookinWeakContainer(包裹着 target),tuple.second => action(方法名字符串)
++ (NSArray<LookinTwoTuple *> *)getTargetActionsFromRecognizer:(UIGestureRecognizer *)recognizer;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 52 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_GestureTargetActionsSearcher.m

@@ -0,0 +1,52 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER
+
+//
+//  LKS_GestureTargetActionsSearcher.m
+//  LookinServer
+//
+//  Created by likai.123 on 2023/9/11.
+//
+
+#import "LKS_GestureTargetActionsSearcher.h"
+#import <objc/runtime.h>
+#import "NSArray+Lookin.h"
+#import "LookinTuple.h"
+#import "LookinWeakContainer.h"
+
+@implementation LKS_GestureTargetActionsSearcher
+
++ (NSArray<LookinTwoTuple *> *)getTargetActionsFromRecognizer:(UIGestureRecognizer *)recognizer {
+    if (!recognizer) {
+        return @[];
+    }
+    // KVC 要放到 try catch 里面防止 Crash
+    @try {
+        NSArray* targetsList = [recognizer valueForKey:@"_targets"];
+        if (!targetsList || targetsList.count == 0) {
+            return @[];
+        }
+        // 数组元素对象是 UIGestureRecognizerTarget*
+        // 这个元素有两个属性,一个是名为 _target 的属性指向某个实例,另一个是名为 _action 的属性保存一个 SEL
+        NSArray<LookinTwoTuple *>* ret = [targetsList lookin_map:^id(NSUInteger idx, id targetBox) {
+            id targetObj = [targetBox valueForKey:@"_target"];
+            if (!targetObj) {
+                return nil;
+            }
+            SEL action = ((SEL (*)(id, Ivar))object_getIvar)(targetBox, class_getInstanceVariable([targetBox class], "_action"));
+            
+            LookinTwoTuple* tuple = [LookinTwoTuple new];
+            tuple.first = [LookinWeakContainer containerWithObject:targetObj];
+            tuple.second = (action == NULL ? @"NULL" : NSStringFromSelector(action));
+            return tuple;
+        }];
+        return ret;
+    }
+    @catch (NSException * e) {
+        NSLog(@"LookinServer - %@", e);
+        return @[];
+    }
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 29 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_Helper.h

@@ -0,0 +1,29 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_Helper.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/7/20.
+//  https://lookin.work
+//
+
+#import "LookinDefines.h"
+
+
+
+#import <Foundation/Foundation.h>
+
+#define LKS_Localized(stringKey) NSLocalizedStringFromTableInBundle(stringKey, nil, [NSBundle bundleForClass:self.class], nil)
+
+@interface LKS_Helper : NSObject
+
+/// 如果 object 为 nil 则返回字符串 “nil”,否则返回字符串格式类似于 (UIView *)
++ (NSString *)descriptionOfObject:(id)object;
+
+/// 返回当前的bundle
++ (NSBundle *)bundle;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 38 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_Helper.m

@@ -0,0 +1,38 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_Helper.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/7/20.
+//  https://lookin.work
+//
+
+#import "LKS_Helper.h"
+#import "NSObject+LookinServer.h"
+
+@implementation LKS_Helper
+
++ (NSString *)descriptionOfObject:(id)object {
+    if (!object) {
+        return @"nil";
+    }
+    NSString *className = NSStringFromClass([object class]);
+    return [NSString stringWithFormat:@"(%@ *)", className];
+}
+
++ (NSBundle *)bundle {
+    static id bundle = nil;
+    if (bundle != nil) {
+#ifdef SPM_RESOURCE_BUNDLE_IDENTIFITER
+        bundle = [NSBundle bundleWithIdentifier:SPM_RESOURCE_BUNDLE_IDENTIFITER];
+#else
+        bundle = [NSBundle bundleForClass:self.class];
+#endif
+    }
+    return bundle;
+}
+    
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 31 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_HierarchyDisplayItemsMaker.h

@@ -0,0 +1,31 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_HierarchyDisplayItemsMaker.h
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/19.
+//  https://lookin.work
+//
+
+
+
+#import "LookinDefines.h"
+
+@class LookinDisplayItem;
+
+@interface LKS_HierarchyDisplayItemsMaker : NSObject
+
+/// @param hasScreenshots 是否包含 soloScreenshots 和 groupScreenshot 属性
+/// @param hasAttrList 是否包含 attributesGroupList 属性
+/// @param lowQuality screenshots 是否为低质量(当 hasScreenshots 为 NO 时,该属性无意义)
+/// @param readCustomInfo 是否读取 lookin_customDebugInfos,比如低版本的 Lookin 发请求时,就无需读取(因为 Lookin 解析不了、还可能出 Bug)
+/// @param saveCustomSetter 是否要读取并保存用户给 attribute 配置的 custom setter
++ (NSArray<LookinDisplayItem *> *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter;
+
+/// 把 layer 的 sublayers 转换为 displayItem 数组并返回
++ (NSArray<LookinDisplayItem *> *)subitemsOfLayer:(CALayer *)layer;
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 162 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_HierarchyDisplayItemsMaker.m

@@ -0,0 +1,162 @@
+#ifdef SHOULD_COMPILE_LOOKIN_SERVER 
+
+//
+//  LKS_HierarchyDisplayItemsMaker.m
+//  LookinServer
+//
+//  Created by Li Kai on 2019/2/19.
+//  https://lookin.work
+//
+
+#import "LKS_HierarchyDisplayItemsMaker.h"
+#import "LookinDisplayItem.h"
+#import "LKS_TraceManager.h"
+#import "LKS_AttrGroupsMaker.h"
+#import "LKS_EventHandlerMaker.h"
+#import "LookinServerDefines.h"
+#import "UIColor+LookinServer.h"
+#import "LKSConfigManager.h"
+#import "LKS_CustomAttrGroupsMaker.h"
+#import "LKS_CustomDisplayItemsMaker.h"
+#import "LKS_CustomAttrSetterManager.h"
+#import "LKS_MultiplatformAdapter.h"
+
+@implementation LKS_HierarchyDisplayItemsMaker
+
++ (NSArray<LookinDisplayItem *> *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter {
+    
+    [[LKS_TraceManager sharedInstance] reload];
+    
+    NSArray<UIWindow *> *windows = [LKS_MultiplatformAdapter allWindows];
+    NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:windows.count];
+    [windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
+        LookinDisplayItem *item = [self _displayItemWithLayer:window.layer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality readCustomInfo:readCustomInfo saveCustomSetter:saveCustomSetter];
+        item.representedAsKeyWindow = window.isKeyWindow;
+        if (item) {
+            [resultArray addObject:item];
+        }
+    }];
+    
+    return [resultArray copy];
+}
+
++ (LookinDisplayItem *)_displayItemWithLayer:(CALayer *)layer screenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter {
+    if (!layer) {
+        return nil;
+    }
+    
+    LookinDisplayItem *item = [LookinDisplayItem new];
+    CGRect layerFrame = layer.frame;
+    UIView *hostView = layer.lks_hostView;
+    if (hostView && hostView.superview) {
+        layerFrame = [hostView.superview convertRect:layerFrame toView:nil];
+    }
+    if ([self validateFrame:layerFrame]) {
+        item.frame = layer.frame;
+    } else {
+        NSLog(@"LookinServer - The layer frame(%@) seems really weird. Lookin will ignore it to avoid potential render error in Lookin.", NSStringFromCGRect(layer.frame));
+        item.frame = CGRectZero;
+    }
+    item.bounds = layer.bounds;
+    if (hasScreenshots) {
+        item.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowQuality];
+        item.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowQuality];
+        item.screenshotEncodeType = LookinDisplayItemImageEncodeTypeNSData;
+    }
+    
+    if (hasAttrList) {
+        item.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
+        LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
+        [maker execute];
+        item.customAttrGroupList = [maker getGroups];
+        item.customDisplayTitle = [maker getCustomDisplayTitle];
+        item.danceuiSource = [maker getDanceUISource];
+    }
+    
+    item.isHidden = layer.isHidden;
+    item.alpha = layer.opacity;
+    item.layerObject = [LookinObject instanceWithObject:layer];
+    item.shouldCaptureImage = [LKSConfigManager shouldCaptureScreenshotOfLayer:layer];
+    
+    if (layer.lks_hostView) {
+        UIView *view = layer.lks_hostView;
+        item.viewObject = [LookinObject instanceWithObject:view];
+        item.eventHandlers = [LKS_EventHandlerMaker makeForView:view];
+        item.backgroundColor = view.backgroundColor;
+        
+        UIViewController* vc = [view lks_findHostViewController];
+        if (vc) {
+            item.hostViewControllerObject = [LookinObject instanceWithObject:vc];
+        }
+    } else {
+        item.backgroundColor = [UIColor lks_colorWithCGColor:layer.backgroundColor];
+    }
+    
+    if (layer.sublayers.count) {
+        NSArray<CALayer *> *sublayers = [layer.sublayers copy];
+        NSMutableArray<LookinDisplayItem *> *allSubitems = [NSMutableArray arrayWithCapacity:sublayers.count];
+        [sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
+            LookinDisplayItem *sublayer_item = [self _displayItemWithLayer:sublayer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality readCustomInfo:readCustomInfo saveCustomSetter:saveCustomSetter];
+            if (sublayer_item) {
+                [allSubitems addObject:sublayer_item];
+            }
+        }];
+        item.subitems = [allSubitems copy];
+    }
+    if (readCustomInfo) {
+        NSArray<LookinDisplayItem *> *customSubitems = [[[LKS_CustomDisplayItemsMaker alloc] initWithLayer:layer saveAttrSetter:saveCustomSetter] make];
+        if (customSubitems.count > 0) {
+            if (item.subitems) {
+                item.subitems = [item.subitems arrayByAddingObjectsFromArray:customSubitems];
+            } else {
+                item.subitems = customSubitems;
+            }
+        }        
+    }
+    
+    return item;
+}
+
++ (NSArray<LookinDisplayItem *> *)subitemsOfLayer:(CALayer *)layer {
+    if (!layer || layer.sublayers.count == 0) {
+        return @[];
+    }
+    [[LKS_TraceManager sharedInstance] reload];
+    
+    NSMutableArray<LookinDisplayItem *> *resultSubitems = [NSMutableArray array];
+
+    NSArray<CALayer *> *sublayers = [layer.sublayers copy];
+    [sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
+        LookinDisplayItem *sublayer_item = [self _displayItemWithLayer:sublayer screenshots:NO attrList:NO lowImageQuality:NO readCustomInfo:YES saveCustomSetter:YES];
+        if (sublayer_item) {
+            [resultSubitems addObject:sublayer_item];
+        }
+    }];
+
+    NSArray<LookinDisplayItem *> *customSubitems = [[[LKS_CustomDisplayItemsMaker alloc] initWithLayer:layer saveAttrSetter:YES] make];
+    if (customSubitems.count > 0) {
+        [resultSubitems addObjectsFromArray:customSubitems];
+    }
+    
+    return resultSubitems;
+}
+
++ (BOOL)validateFrame:(CGRect)frame {
+    return !CGRectIsNull(frame) && !CGRectIsInfinite(frame) && ![self cgRectIsNaN:frame] && ![self cgRectIsInf:frame] && ![self cgRectIsUnreasonable:frame];
+}
+
++ (BOOL)cgRectIsNaN:(CGRect)rect {
+    return isnan(rect.origin.x) || isnan(rect.origin.y) || isnan(rect.size.width) || isnan(rect.size.height);
+}
+
++ (BOOL)cgRectIsInf:(CGRect)rect {
+    return isinf(rect.origin.x) || isinf(rect.origin.y) || isinf(rect.size.width) || isinf(rect.size.height);
+}
+
++ (BOOL)cgRectIsUnreasonable:(CGRect)rect {
+    return ABS(rect.origin.x) > 100000 || ABS(rect.origin.y) > 100000 || rect.size.width < 0 || rect.size.height < 0 || rect.size.width > 100000 || rect.size.height > 100000;
+}
+
+@end
+
+#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

+ 0 - 0
Pods/LookinServer/Src/Main/Server/Others/LKS_MultiplatformAdapter.h


Some files were not shown because too many files changed in this diff