diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..7c7925d --- /dev/null +++ b/Podfile @@ -0,0 +1,120 @@ +# Uncomment the next line to define a global platform for your project +source 'https://gitee.com/mirrors/CocoaPods-Specs.git' +platform :ios, '15.0' + +target 'QuickLocation' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for QuickLocation + # Pods for Base + pod 'Moya' + pod 'ObjectMapper' + pod 'RxCocoa' + pod 'RxSwift' + pod 'SwiftyUserDefaults' + pod 'SwiftyJSON' + pod 'URLNavigator' + pod 'RxDataSources' + pod 'RxSwiftExt' + pod 'SwiftDate' + #pod 'RxGesture' + pod 'CocoaLumberjack/Swift' + pod 'KingfisherWebP' + pod 'Masonry' + + #工具 + #pod 'FLEX', :configurations => ['Debug', 'AdHoc'] + pod 'HXPHPicker' + pod 'SwiftKeychainWrapper' + pod 'SGQRCode', '~> 4.1.0' #扫描 + #pod 'PGDatePicker' + pod 'IQKeyboardManagerSwift' + + #UI + pod 'SnapKit' + pod 'MJRefresh' + pod 'MBProgressHUD', :git => 'https://github.com/jdg/MBProgressHUD.git' + pod 'MarqueeLabel' + pod 'Popover' + + #第三方SDk + pod 'GYSDK' #个推一键登录 + + #高德地图 + pod 'AMap3DMap' #3D地图SDK + pod 'AMapSearch' #地图SDK搜索功能 + pod 'AMapLocation' #定位SDK + +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' + end + end + + # These frameworks ship without a valid arm64-simulator slice (even some + # xcframeworks contain device-only arm64). Linking them for simulator on + # Xcode 15+ (arm64-only simulator) fails. Fix: remove EXCLUDED_ARCHS (so + # simulator destinations appear), strip them from general OTHER_LDFLAGS, + # and add them back only via OTHER_LDFLAGS[sdk=iphoneos*]. + device_only_fws = %w[MAMapKit AMapFoundationKit AMapLocationKit AMapSearchKit ZXSDK GeYanSdk GTCommonSDK] + device_only_dirs = %w[AMap3DMap AMapFoundation AMapLocation AMapSearch ZXSDK GYSDK GTCommonSDK] + + Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |path| + content = File.read(path) + + # Remove arm64 exclusion + content.gsub!(/^EXCLUDED_ARCHS\[sdk=iphonesimulator\*\] = arm64.*\n?/, '') + + if content =~ /^OTHER_LDFLAGS = (.+)$/ + ldflags = $1 + stripped_ld = false + + device_only_fws.each do |fw| + if ldflags.gsub!(/ *-(weak_)?framework "#{fw}"/, '') + stripped_ld = true + end + end + + content.sub!(/^OTHER_LDFLAGS = .+$/, "OTHER_LDFLAGS = #{ldflags}") + + # Remove any old conditional lines + content.gsub!(/^OTHER_LDFLAGS\[sdk=iphonesimulator\*\].*\n?/, '') + content.gsub!(/^OTHER_LDFLAGS\[sdk=iphoneos\*\].*\n?/, '') + + # Only add device-only conditional if we actually stripped something + if stripped_ld + device_flags = device_only_fws.map { |fw| %(-framework "#{fw}") }.join(' ') + content += "\nOTHER_LDFLAGS[sdk=iphoneos*] = $(inherited) #{device_flags}\n" + end + end + + # Strip device-only dirs from FRAMEWORK_SEARCH_PATHS and move to iphoneos only + if content =~ /^FRAMEWORK_SEARCH_PATHS = (.+)$/ + fsp = $1 + device_search_paths = [] + + device_only_dirs.each do |dir| + %w[PODS_ROOT PODS_CONFIGURATION_BUILD_DIR PODS_XCFRAMEWORKS_BUILD_DIR].each do |var| + pattern = / *"\$\{#{var}\}\/#{dir}"/ + if fsp.match?(pattern) + fsp.gsub!(pattern, '') + device_search_paths << "\"${#{var}}/#{dir}\"" + end + end + end + + content.sub!(/^FRAMEWORK_SEARCH_PATHS = .+$/, "FRAMEWORK_SEARCH_PATHS = #{fsp}") + content.gsub!(/^FRAMEWORK_SEARCH_PATHS\[sdk=iphoneos\*\].*\n?/, '') + + unless device_search_paths.empty? + content += "\nFRAMEWORK_SEARCH_PATHS[sdk=iphoneos*] = $(inherited) #{device_search_paths.uniq.join(' ')}\n" + end + end + + File.write(path, content) + end +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..0881524 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,243 @@ +PODS: + - Alamofire (5.10.1) + - AMap3DMap (11.1.200): + - AMapFoundation (>= 1.8.7) + - AMapFoundation (1.8.7) + - AMapLocation (2.11.1): + - AMapFoundation (>= 1.8.7) + - AMapSearch (9.7.5): + - AMapFoundation (>= 1.8.0) + - CocoaLumberjack/Core (3.9.1) + - CocoaLumberjack/Swift (3.9.1): + - CocoaLumberjack/Core + - Differentiator (5.0.0) + - GTCommonSDK (3.2.3.0): + - ZXSDK + - GYSDK (3.1.3.0): + - GTCommonSDK (>= 3.0.0.0) + - HXPHPicker (1.1.5): + - HXPHPicker/Core (= 1.1.5) + - HXPHPicker/Editor (= 1.1.5) + - HXPHPicker/Picker (= 1.1.5) + - HXPHPicker/Core (1.1.5) + - HXPHPicker/Editor (1.1.5): + - HXPHPicker/Core + - HXPHPicker/Picker (1.1.5): + - HXPHPicker/Core + - IQKeyboardCore (1.0.8) + - IQKeyboardManagerSwift (8.0.1): + - IQKeyboardManagerSwift/Appearance (= 8.0.1) + - IQKeyboardManagerSwift/Core (= 8.0.1) + - IQKeyboardManagerSwift/IQKeyboardReturnManager (= 8.0.1) + - IQKeyboardManagerSwift/IQKeyboardToolbarManager (= 8.0.1) + - IQKeyboardManagerSwift/IQTextView (= 8.0.1) + - IQKeyboardManagerSwift/Resign (= 8.0.1) + - IQKeyboardManagerSwift/Appearance (8.0.1): + - IQKeyboardManagerSwift/Core + - IQKeyboardManagerSwift/Core (8.0.1): + - IQKeyboardNotification + - IQTextInputViewNotification + - IQKeyboardManagerSwift/IQKeyboardReturnManager (8.0.1): + - IQKeyboardReturnManager + - IQKeyboardManagerSwift/IQKeyboardToolbarManager (8.0.1): + - IQKeyboardManagerSwift/Core + - IQKeyboardToolbarManager + - IQKeyboardManagerSwift/IQTextView (8.0.1): + - IQTextView + - IQKeyboardManagerSwift/Resign (8.0.1): + - IQKeyboardManagerSwift/Core + - IQKeyboardNotification (1.0.6) + - IQKeyboardReturnManager (1.0.6): + - IQKeyboardCore + - IQKeyboardToolbar (1.1.3): + - IQKeyboardToolbar/Core (= 1.1.3) + - IQKeyboardToolbar/Core (1.1.3): + - IQKeyboardCore + - IQKeyboardToolbar/Placeholderable + - IQKeyboardToolbar/Placeholderable (1.1.3) + - IQKeyboardToolbarManager (1.1.5): + - IQKeyboardToolbar + - IQTextInputViewNotification + - IQTextInputViewNotification (1.0.9): + - IQKeyboardCore + - IQTextView (1.0.5): + - IQKeyboardToolbar/Placeholderable + - Kingfisher (8.1.1) + - KingfisherWebP (1.7.3): + - Kingfisher (~> 8.0) + - libwebp (>= 1.1.0) + - libwebp (1.5.0): + - libwebp/demux (= 1.5.0) + - libwebp/mux (= 1.5.0) + - libwebp/sharpyuv (= 1.5.0) + - libwebp/webp (= 1.5.0) + - libwebp/demux (1.5.0): + - libwebp/webp + - libwebp/mux (1.5.0): + - libwebp/demux + - libwebp/sharpyuv (1.5.0) + - libwebp/webp (1.5.0): + - libwebp/sharpyuv + - MarqueeLabel (4.5.3) + - Masonry (1.1.0) + - MBProgressHUD (1.2.0) + - MJRefresh (3.7.9) + - Moya (15.0.0): + - Moya/Core (= 15.0.0) + - Moya/Core (15.0.0): + - Alamofire (~> 5.0) + - ObjectMapper (4.4.2) + - Popover (1.3.0) + - RxCocoa (6.8.0): + - RxRelay (= 6.8.0) + - RxSwift (= 6.8.0) + - RxDataSources (5.0.0): + - Differentiator (~> 5.0) + - RxCocoa (~> 6.0) + - RxSwift (~> 6.0) + - RxRelay (6.8.0): + - RxSwift (= 6.8.0) + - RxSwift (6.8.0) + - RxSwiftExt (6.2.1): + - RxSwiftExt/Core (= 6.2.1) + - RxSwiftExt/RxCocoa (= 6.2.1) + - RxSwiftExt/Core (6.2.1): + - RxSwift (~> 6.0) + - RxSwiftExt/RxCocoa (6.2.1): + - RxCocoa (~> 6.0) + - RxSwiftExt/Core + - SGQRCode (4.1.0) + - SnapKit (5.7.1) + - SwiftDate (7.0.0) + - SwiftKeychainWrapper (4.0.1) + - SwiftyJSON (5.0.2) + - SwiftyUserDefaults (5.3.0) + - URLNavigator (2.5.1) + - ZXSDK (3.3.2) + +DEPENDENCIES: + - AMap3DMap + - AMapLocation + - AMapSearch + - CocoaLumberjack/Swift + - GYSDK + - HXPHPicker + - IQKeyboardManagerSwift + - KingfisherWebP + - MarqueeLabel + - Masonry + - MBProgressHUD (from `https://github.com/jdg/MBProgressHUD.git`) + - MJRefresh + - Moya + - ObjectMapper + - Popover + - RxCocoa + - RxDataSources + - RxSwift + - RxSwiftExt + - SGQRCode (~> 4.1.0) + - SnapKit + - SwiftDate + - SwiftKeychainWrapper + - SwiftyJSON + - SwiftyUserDefaults + - URLNavigator + +SPEC REPOS: + https://gitee.com/mirrors/CocoaPods-Specs.git: + - Alamofire + - AMap3DMap + - AMapFoundation + - AMapLocation + - AMapSearch + - CocoaLumberjack + - Differentiator + - GTCommonSDK + - GYSDK + - HXPHPicker + - IQKeyboardCore + - IQKeyboardManagerSwift + - IQKeyboardNotification + - IQKeyboardReturnManager + - IQKeyboardToolbar + - IQKeyboardToolbarManager + - IQTextInputViewNotification + - IQTextView + - Kingfisher + - KingfisherWebP + - libwebp + - MarqueeLabel + - Masonry + - MJRefresh + - Moya + - ObjectMapper + - Popover + - RxCocoa + - RxDataSources + - RxRelay + - RxSwift + - RxSwiftExt + - SGQRCode + - SnapKit + - SwiftDate + - SwiftKeychainWrapper + - SwiftyJSON + - SwiftyUserDefaults + - URLNavigator + - ZXSDK + +EXTERNAL SOURCES: + MBProgressHUD: + :git: https://github.com/jdg/MBProgressHUD.git + +CHECKOUT OPTIONS: + MBProgressHUD: + :commit: 4a7c5f3e53cdea77c5dcb8578c2ee5acacdf6781 + :git: https://github.com/jdg/MBProgressHUD.git + +SPEC CHECKSUMS: + Alamofire: 840d2a1ad82355b536ec6ba5f97e5bfa54600ca3 + AMap3DMap: 89c17fdbca2f25e4b46d6a45a8e50346980fc799 + AMapFoundation: e99da1cc722528c60b39340b4763a95305f6c55e + AMapLocation: 6e44f50b044dc54c6b3dcb1dee5ffd6de2689e41 + AMapSearch: 29224a399b56b17da1540e4312fc4d9dc37342bb + CocoaLumberjack: e4ba3b414dfca8c1916c6303d37f63b3a95134c6 + Differentiator: e8497ceab83c1b10ca233716d547b9af21b9344d + GTCommonSDK: 238c6735add91e654f8564638854686ee3dcb4ac + GYSDK: db5f4b3aae8df06201be5e786076211ec11791be + HXPHPicker: 451f6af62845d60d5dd2235f4665d81f2688a51b + IQKeyboardCore: 8652977ec919cf5351aa2977fedd1a6546476fbc + IQKeyboardManagerSwift: 835fc9c6e4732398113406d84900ad2e8f141218 + IQKeyboardNotification: eb4910401f5a0e68f97e71c62f8a0c5b7e9d535c + IQKeyboardReturnManager: fcbf51fc68d7536fc1fbcca5231c4e82576b12ac + IQKeyboardToolbar: 9fe900f8e7def414be4025af0ca098df764d4fe3 + IQKeyboardToolbarManager: d449479b5843d4a3fb902d03bf62d6ca031ac20e + IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559 + IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7 + Kingfisher: fc2788e497306f97fdef6e4df49f02106cb57788 + KingfisherWebP: 064869d5f1201396ec121dcc4c9cfbdba6ecb900 + libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 + MarqueeLabel: 0c57d4c6634e04a6d015af79f7c9a175b2309525 + Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 + MBProgressHUD: 1b0fb447e80a0fda94808180750e8b78a07b3cd2 + MJRefresh: ff9e531227924c84ce459338414550a05d2aea78 + Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee + ObjectMapper: e6e4d91ff7f2861df7aecc536c92d8363f4c9677 + Popover: 10e1d9528f81d9504df984b7b3f491292bc1822d + RxCocoa: 2d33c1e1e5d66492052ad46b11024ae287572880 + RxDataSources: aa47cc1ed6c500fa0dfecac5c979b723542d79cf + RxRelay: 335c78b926a2aea8d863a6d25f1ed3b5ad8e8705 + RxSwift: 4e28be97cbcfeee614af26d83415febbf2bf6f45 + RxSwiftExt: 43aaacb6a4d3c69c55e9d1cf4f79920043d13583 + SGQRCode: 6ad664d63f38f2842503bc5087812c5a3136d924 + SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a + SwiftDate: bbc26e26fc8c0c33fbee8c140c5e8a68293a148a + SwiftKeychainWrapper: 807ba1d63c33a7d0613288512399cd1eda1e470c + SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a + SwiftyUserDefaults: 63f80248cf5bfb3458825d9a78f2eb7e1293a040 + URLNavigator: e9c0426ba6e6ac57f34d018bbf3df840797f984d + ZXSDK: 786338c0a18e98e03eda00699c3bfd2700b97117 + +PODFILE CHECKSUM: 8221656bba011c856037590658f6282ed7f4e02c + +COCOAPODS: 1.16.2 diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_navi_sky_day.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_navi_sky_day.data new file mode 100644 index 0000000..1987d45 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_navi_sky_day.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_sky_day.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_sky_day.data new file mode 100644 index 0000000..c0975de Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_sky_day.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_sky_night.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_sky_night.data new file mode 100644 index 0000000..3f1eeaa Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/3d_sky_night.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1015_1.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1015_1.png new file mode 100644 index 0000000..3edab19 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1015_1.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1015_2.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1015_2.png new file mode 100644 index 0000000..0f4044d Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1015_2.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1016_1.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1016_1.png new file mode 100644 index 0000000..3285339 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1016_1.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1016_2.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1016_2.png new file mode 100644 index 0000000..18c1614 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/1016_2.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_bk_grass_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_bk_grass_day.png new file mode 100644 index 0000000..b699004 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_bk_grass_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_bk_grass_night.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_bk_grass_night.png new file mode 100644 index 0000000..a42e8ce Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_bk_grass_night.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_sky_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_sky_day.png new file mode 100644 index 0000000..8af2167 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_sky_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_sky_night.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_sky_night.png new file mode 100644 index 0000000..80322ab Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/cross_sky_night.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/crossing_day_bk.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/crossing_day_bk.data new file mode 100644 index 0000000..fff1b61 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/crossing_day_bk.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/crossing_nigth_bk.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/crossing_nigth_bk.data new file mode 100644 index 0000000..928155e Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/crossing_nigth_bk.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/d_yellow_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/d_yellow_day.png new file mode 100644 index 0000000..39a10c5 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/d_yellow_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/d_yellow_night.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/d_yellow_night.png new file mode 100644 index 0000000..2b5f54c Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/d_yellow_night.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/exit_label_bk_main_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/exit_label_bk_main_day.png new file mode 100644 index 0000000..2cc5acb Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/exit_label_bk_main_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/exit_label_bk_secondary_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/exit_label_bk_secondary_day.png new file mode 100644 index 0000000..55f58a1 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/exit_label_bk_secondary_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/grass_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/grass_day.png new file mode 100644 index 0000000..a57fe33 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/grass_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/grass_night.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/grass_night.png new file mode 100644 index 0000000..0820386 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/grass_night.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/icons_40_25_1736924274.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/icons_40_25_1736924274.data new file mode 100644 index 0000000..7e271f0 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/icons_40_25_1736924274.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/icons_42_25_1617197042.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/icons_42_25_1617197042.data new file mode 100644 index 0000000..9b0a5c3 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/icons_42_25_1617197042.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/road_bottom_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/road_bottom_day.png new file mode 100644 index 0000000..5ef99c4 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/road_bottom_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/road_bottom_night.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/road_bottom_night.png new file mode 100644 index 0000000..0e0bb9e Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/road_bottom_night.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/roadbk_main_day.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/roadbk_main_day.png new file mode 100644 index 0000000..c3732fe Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/roadbk_main_day.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/roadbk_main_night.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/roadbk_main_night.png new file mode 100644 index 0000000..f9f1ae2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/VM3DRes/roadbk_main_night.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/anscii.fnt b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/anscii.fnt new file mode 100644 index 0000000..cccf994 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/anscii.fnt @@ -0,0 +1,191 @@ +info face="Arial" size=32 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0 +common lineHeight=32 base=26 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0 +page id=0 file="anscii_0.png" +chars count=95 +char id=32 x=110 y=22 width=3 height=1 xoffset=-1 yoffset=31 xadvance=8 page=0 chnl=15 +char id=33 x=199 y=63 width=4 height=20 xoffset=2 yoffset=6 xadvance=8 page=0 chnl=15 +char id=34 x=242 y=79 width=10 height=7 xoffset=0 yoffset=6 xadvance=10 page=0 chnl=15 +char id=35 x=0 y=48 width=16 height=20 xoffset=-1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=36 x=94 y=0 width=15 height=23 xoffset=0 yoffset=5 xadvance=15 page=0 chnl=15 +char id=37 x=161 y=0 width=22 height=20 xoffset=1 yoffset=6 xadvance=24 page=0 chnl=15 +char id=38 x=132 y=21 width=17 height=20 xoffset=1 yoffset=6 xadvance=18 page=0 chnl=15 +char id=39 x=0 y=106 width=5 height=7 xoffset=0 yoffset=6 xadvance=5 page=0 chnl=15 +char id=40 x=47 y=0 width=8 height=25 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=15 +char id=41 x=56 y=0 width=8 height=25 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=15 +char id=42 x=230 y=79 width=11 height=8 xoffset=0 yoffset=6 xadvance=11 page=0 chnl=15 +char id=43 x=187 y=84 width=14 height=12 xoffset=1 yoffset=10 xadvance=16 page=0 chnl=15 +char id=44 x=6 y=106 width=4 height=6 xoffset=2 yoffset=24 xadvance=8 page=0 chnl=15 +char id=45 x=53 y=106 width=9 height=2 xoffset=0 yoffset=18 xadvance=9 page=0 chnl=15 +char id=46 x=209 y=79 width=4 height=2 xoffset=2 yoffset=24 xadvance=8 page=0 chnl=15 +char id=47 x=153 y=63 width=10 height=20 xoffset=-1 yoffset=6 xadvance=8 page=0 chnl=15 +char id=48 x=15 y=69 width=14 height=20 xoffset=0 yoffset=6 xadvance=15 page=0 chnl=15 +char id=49 x=164 y=63 width=9 height=20 xoffset=2 yoffset=6 xadvance=15 page=0 chnl=15 +char id=50 x=134 y=42 width=14 height=20 xoffset=0 yoffset=6 xadvance=15 page=0 chnl=15 +char id=51 x=59 y=68 width=13 height=20 xoffset=1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=52 x=179 y=42 width=14 height=20 xoffset=0 yoffset=6 xadvance=15 page=0 chnl=15 +char id=53 x=45 y=69 width=13 height=20 xoffset=1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=54 x=194 y=42 width=14 height=20 xoffset=0 yoffset=6 xadvance=15 page=0 chnl=15 +char id=55 x=115 y=66 width=13 height=20 xoffset=1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=56 x=102 y=45 width=15 height=20 xoffset=0 yoffset=6 xadvance=15 page=0 chnl=15 +char id=57 x=118 y=43 width=15 height=20 xoffset=0 yoffset=6 xadvance=15 page=0 chnl=15 +char id=58 x=251 y=63 width=4 height=15 xoffset=2 yoffset=11 xadvance=8 page=0 chnl=15 +char id=59 x=204 y=63 width=4 height=19 xoffset=2 yoffset=11 xadvance=8 page=0 chnl=15 +char id=60 x=172 y=84 width=14 height=13 xoffset=1 yoffset=10 xadvance=16 page=0 chnl=15 +char id=61 x=215 y=79 width=14 height=8 xoffset=1 yoffset=12 xadvance=16 page=0 chnl=15 +char id=62 x=157 y=84 width=14 height=13 xoffset=1 yoffset=10 xadvance=16 page=0 chnl=15 +char id=63 x=101 y=68 width=13 height=20 xoffset=1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=64 x=0 y=0 width=26 height=26 xoffset=1 yoffset=6 xadvance=27 page=0 chnl=15 +char id=65 x=227 y=0 width=19 height=20 xoffset=-1 yoffset=6 xadvance=18 page=0 chnl=15 +char id=66 x=17 y=48 width=16 height=20 xoffset=1 yoffset=6 xadvance=18 page=0 chnl=15 +char id=67 x=58 y=26 width=18 height=20 xoffset=1 yoffset=6 xadvance=20 page=0 chnl=15 +char id=68 x=168 y=21 width=17 height=20 xoffset=2 yoffset=6 xadvance=20 page=0 chnl=15 +char id=69 x=51 y=47 width=16 height=20 xoffset=1 yoffset=6 xadvance=18 page=0 chnl=15 +char id=70 x=240 y=21 width=15 height=20 xoffset=2 yoffset=6 xadvance=17 page=0 chnl=15 +char id=71 x=0 y=27 width=19 height=20 xoffset=1 yoffset=6 xadvance=21 page=0 chnl=15 +char id=72 x=186 y=21 width=17 height=20 xoffset=1 yoffset=6 xadvance=19 page=0 chnl=15 +char id=73 x=184 y=63 width=4 height=20 xoffset=2 yoffset=6 xadvance=8 page=0 chnl=15 +char id=74 x=129 y=64 width=12 height=20 xoffset=0 yoffset=6 xadvance=13 page=0 chnl=15 +char id=75 x=39 y=26 width=18 height=20 xoffset=1 yoffset=6 xadvance=18 page=0 chnl=15 +char id=76 x=0 y=69 width=14 height=20 xoffset=1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=77 x=184 y=0 width=21 height=20 xoffset=1 yoffset=6 xadvance=23 page=0 chnl=15 +char id=78 x=96 y=24 width=17 height=20 xoffset=1 yoffset=6 xadvance=19 page=0 chnl=15 +char id=79 x=206 y=0 width=20 height=20 xoffset=1 yoffset=6 xadvance=21 page=0 chnl=15 +char id=80 x=85 y=47 width=16 height=20 xoffset=1 yoffset=6 xadvance=17 page=0 chnl=15 +char id=81 x=110 y=0 width=21 height=21 xoffset=0 yoffset=6 xadvance=21 page=0 chnl=15 +char id=82 x=77 y=26 width=18 height=20 xoffset=2 yoffset=6 xadvance=20 page=0 chnl=15 +char id=83 x=68 y=47 width=16 height=20 xoffset=1 yoffset=6 xadvance=18 page=0 chnl=15 +char id=84 x=34 y=48 width=16 height=20 xoffset=0 yoffset=6 xadvance=16 page=0 chnl=15 +char id=85 x=204 y=21 width=17 height=20 xoffset=1 yoffset=6 xadvance=19 page=0 chnl=15 +char id=86 x=222 y=21 width=17 height=20 xoffset=0 yoffset=6 xadvance=17 page=0 chnl=15 +char id=87 x=132 y=0 width=28 height=20 xoffset=0 yoffset=6 xadvance=28 page=0 chnl=15 +char id=88 x=150 y=21 width=17 height=20 xoffset=0 yoffset=6 xadvance=17 page=0 chnl=15 +char id=89 x=20 y=27 width=18 height=20 xoffset=0 yoffset=6 xadvance=18 page=0 chnl=15 +char id=90 x=114 y=22 width=17 height=20 xoffset=0 yoffset=6 xadvance=17 page=0 chnl=15 +char id=91 x=73 y=0 width=7 height=25 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15 +char id=92 x=174 y=63 width=9 height=20 xoffset=-1 yoffset=6 xadvance=8 page=0 chnl=15 +char id=93 x=65 y=0 width=7 height=25 xoffset=0 yoffset=6 xadvance=8 page=0 chnl=15 +char id=94 x=202 y=84 width=12 height=10 xoffset=0 yoffset=6 xadvance=12 page=0 chnl=15 +char id=95 x=35 y=106 width=17 height=2 xoffset=-1 yoffset=29 xadvance=15 page=0 chnl=15 +char id=96 x=28 y=106 width=6 height=4 xoffset=1 yoffset=6 xadvance=9 page=0 chnl=15 +char id=97 x=48 y=90 width=14 height=15 xoffset=0 yoffset=11 xadvance=15 page=0 chnl=15 +char id=98 x=149 y=42 width=14 height=20 xoffset=1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=99 x=106 y=89 width=13 height=15 xoffset=0 yoffset=11 xadvance=14 page=0 chnl=15 +char id=100 x=164 y=42 width=14 height=20 xoffset=0 yoffset=6 xadvance=15 page=0 chnl=15 +char id=101 x=0 y=90 width=15 height=15 xoffset=0 yoffset=11 xadvance=15 page=0 chnl=15 +char id=102 x=142 y=63 width=10 height=20 xoffset=-1 yoffset=6 xadvance=7 page=0 chnl=15 +char id=103 x=209 y=42 width=14 height=20 xoffset=0 yoffset=11 xadvance=15 page=0 chnl=15 +char id=104 x=73 y=68 width=13 height=20 xoffset=1 yoffset=6 xadvance=15 page=0 chnl=15 +char id=105 x=189 y=63 width=4 height=20 xoffset=1 yoffset=6 xadvance=6 page=0 chnl=15 +char id=106 x=81 y=0 width=7 height=25 xoffset=-2 yoffset=6 xadvance=6 page=0 chnl=15 +char id=107 x=87 y=68 width=13 height=20 xoffset=1 yoffset=6 xadvance=14 page=0 chnl=15 +char id=108 x=194 y=63 width=4 height=20 xoffset=1 yoffset=6 xadvance=6 page=0 chnl=15 +char id=109 x=209 y=63 width=20 height=15 xoffset=1 yoffset=11 xadvance=22 page=0 chnl=15 +char id=110 x=78 y=89 width=13 height=15 xoffset=1 yoffset=11 xadvance=15 page=0 chnl=15 +char id=111 x=16 y=90 width=15 height=15 xoffset=0 yoffset=11 xadvance=15 page=0 chnl=15 +char id=112 x=30 y=69 width=14 height=20 xoffset=1 yoffset=11 xadvance=15 page=0 chnl=15 +char id=113 x=224 y=42 width=14 height=20 xoffset=0 yoffset=11 xadvance=15 page=0 chnl=15 +char id=114 x=147 y=84 width=9 height=15 xoffset=1 yoffset=11 xadvance=9 page=0 chnl=15 +char id=115 x=63 y=89 width=14 height=15 xoffset=0 yoffset=11 xadvance=14 page=0 chnl=15 +char id=116 x=247 y=0 width=8 height=20 xoffset=0 yoffset=6 xadvance=8 page=0 chnl=15 +char id=117 x=92 y=89 width=13 height=15 xoffset=1 yoffset=11 xadvance=15 page=0 chnl=15 +char id=118 x=32 y=90 width=15 height=15 xoffset=-1 yoffset=11 xadvance=13 page=0 chnl=15 +char id=119 x=230 y=63 width=20 height=15 xoffset=-1 yoffset=11 xadvance=19 page=0 chnl=15 +char id=120 x=134 y=85 width=12 height=15 xoffset=0 yoffset=11 xadvance=12 page=0 chnl=15 +char id=121 x=239 y=42 width=14 height=20 xoffset=0 yoffset=11 xadvance=14 page=0 chnl=15 +char id=122 x=120 y=87 width=13 height=15 xoffset=0 yoffset=11 xadvance=13 page=0 chnl=15 +char id=123 x=37 y=0 width=9 height=25 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=15 +char id=124 x=89 y=0 width=4 height=25 xoffset=1 yoffset=6 xadvance=6 page=0 chnl=15 +char id=125 x=27 y=0 width=9 height=25 xoffset=0 yoffset=6 xadvance=9 page=0 chnl=15 +char id=126 x=11 y=106 width=16 height=4 xoffset=0 yoffset=14 xadvance=16 page=0 chnl=15 +kernings count=91 +kerning first=32 second=65 amount=-2 +kerning first=32 second=84 amount=-1 +kerning first=32 second=89 amount=-1 +kerning first=121 second=46 amount=-2 +kerning first=121 second=44 amount=-2 +kerning first=119 second=46 amount=-2 +kerning first=119 second=44 amount=-2 +kerning first=118 second=46 amount=-2 +kerning first=118 second=44 amount=-2 +kerning first=114 second=46 amount=-2 +kerning first=49 second=49 amount=-2 +kerning first=65 second=32 amount=-2 +kerning first=65 second=84 amount=-2 +kerning first=65 second=86 amount=-2 +kerning first=65 second=87 amount=-1 +kerning first=65 second=89 amount=-2 +kerning first=65 second=118 amount=-1 +kerning first=65 second=119 amount=-1 +kerning first=65 second=121 amount=-1 +kerning first=114 second=44 amount=-2 +kerning first=70 second=44 amount=-3 +kerning first=70 second=46 amount=-3 +kerning first=70 second=65 amount=-2 +kerning first=76 second=32 amount=-1 +kerning first=76 second=84 amount=-2 +kerning first=76 second=86 amount=-2 +kerning first=76 second=87 amount=-2 +kerning first=76 second=89 amount=-2 +kerning first=76 second=121 amount=-1 +kerning first=102 second=102 amount=-1 +kerning first=80 second=32 amount=-1 +kerning first=80 second=44 amount=-4 +kerning first=80 second=46 amount=-4 +kerning first=80 second=65 amount=-2 +kerning first=82 second=84 amount=-1 +kerning first=82 second=86 amount=-1 +kerning first=82 second=87 amount=-1 +kerning first=82 second=89 amount=-1 +kerning first=84 second=32 amount=-1 +kerning first=84 second=44 amount=-3 +kerning first=84 second=45 amount=-2 +kerning first=84 second=46 amount=-3 +kerning first=84 second=58 amount=-3 +kerning first=89 second=118 amount=-2 +kerning first=84 second=65 amount=-2 +kerning first=84 second=79 amount=-1 +kerning first=84 second=97 amount=-3 +kerning first=84 second=99 amount=-3 +kerning first=84 second=101 amount=-3 +kerning first=84 second=105 amount=-1 +kerning first=84 second=111 amount=-3 +kerning first=84 second=114 amount=-1 +kerning first=84 second=115 amount=-3 +kerning first=84 second=117 amount=-1 +kerning first=84 second=119 amount=-2 +kerning first=84 second=121 amount=-2 +kerning first=86 second=44 amount=-3 +kerning first=86 second=45 amount=-2 +kerning first=86 second=46 amount=-3 +kerning first=86 second=58 amount=-1 +kerning first=89 second=117 amount=-2 +kerning first=86 second=65 amount=-2 +kerning first=86 second=97 amount=-2 +kerning first=86 second=101 amount=-2 +kerning first=86 second=105 amount=-1 +kerning first=86 second=111 amount=-2 +kerning first=86 second=114 amount=-1 +kerning first=86 second=117 amount=-1 +kerning first=86 second=121 amount=-1 +kerning first=87 second=44 amount=-2 +kerning first=87 second=45 amount=-1 +kerning first=87 second=46 amount=-2 +kerning first=87 second=58 amount=-1 +kerning first=89 second=113 amount=-3 +kerning first=87 second=65 amount=-1 +kerning first=87 second=97 amount=-1 +kerning first=87 second=101 amount=-1 +kerning first=89 second=112 amount=-2 +kerning first=87 second=111 amount=-1 +kerning first=87 second=114 amount=-1 +kerning first=87 second=117 amount=-1 +kerning first=89 second=111 amount=-3 +kerning first=89 second=32 amount=-1 +kerning first=89 second=44 amount=-4 +kerning first=89 second=45 amount=-3 +kerning first=89 second=46 amount=-4 +kerning first=89 second=58 amount=-2 +kerning first=89 second=105 amount=-1 +kerning first=89 second=65 amount=-2 +kerning first=89 second=97 amount=-2 +kerning first=89 second=101 amount=-3 diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/anscii_0.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/anscii_0.png new file mode 100644 index 0000000..25305da Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/anscii_0.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_10_25_1757046143.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_10_25_1757046143.data new file mode 100644 index 0000000..46d98d1 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_10_25_1757046143.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_3_25_1757046136.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_3_25_1757046136.data new file mode 100644 index 0000000..f8a16e2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_3_25_1757046136.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_6_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_6_25_1757046135.data new file mode 100644 index 0000000..7a14630 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_6_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_9_25_1757046143.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_9_25_1757046143.data new file mode 100644 index 0000000..d744770 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_group_9_25_1757046143.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_info_1757046189.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_info_1757046189.data new file mode 100644 index 0000000..18d8a61 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/assets_info_1757046189.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/bktile.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/bktile.data new file mode 100644 index 0000000..bb2a17f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/bktile.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/bktile_n.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/bktile_n.data new file mode 100644 index 0000000..5181b70 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/bktile_n.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/config_1_25_1756777905.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/config_1_25_1756777905.data new file mode 100644 index 0000000..380163c Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/config_1_25_1756777905.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/config_2_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/config_2_25_1755682869.data new file mode 100644 index 0000000..1a1b2a9 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/config_2_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash.data new file mode 100644 index 0000000..da1b48f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash_cd.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash_cd.data new file mode 100644 index 0000000..3a59032 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash_cd.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash_tq.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash_tq.data new file mode 100644 index 0000000..c721927 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/dash_tq.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10000_25_1755682872.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10000_25_1755682872.data new file mode 100644 index 0000000..b4ebd1d Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10000_25_1755682872.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10001_25_1755682873.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10001_25_1755682873.data new file mode 100644 index 0000000..2e2e9f9 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10001_25_1755682873.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10002_25_1755682874.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10002_25_1755682874.data new file mode 100644 index 0000000..9b3f6f8 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10002_25_1755682874.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10003_25_1755682872.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10003_25_1755682872.data new file mode 100644 index 0000000..f8c7f8f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10003_25_1755682872.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10004_25_1755682872.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10004_25_1755682872.data new file mode 100644 index 0000000..64743b8 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10004_25_1755682872.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10005_25_1755682873.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10005_25_1755682873.data new file mode 100644 index 0000000..0fc540d Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10005_25_1755682873.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10006_25_1755682872.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10006_25_1755682872.data new file mode 100644 index 0000000..755b771 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10006_25_1755682872.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10007_25_1755682872.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10007_25_1755682872.data new file mode 100644 index 0000000..847f83c Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_10007_25_1755682872.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30000_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30000_25_1755682869.data new file mode 100644 index 0000000..af12cc9 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30000_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30001_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30001_25_1755682869.data new file mode 100644 index 0000000..7e26ab0 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30001_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30002_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30002_25_1755682869.data new file mode 100644 index 0000000..be44037 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_30002_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_41_25_1757046158.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_41_25_1757046158.data new file mode 100644 index 0000000..dbf2df1 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_41_25_1757046158.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_5_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_5_25_1757046135.data new file mode 100644 index 0000000..947b97f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/icons_5_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_1_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_1_25_1755682869.data new file mode 100644 index 0000000..1eae9b2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_1_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_low_1_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_low_1_25_1755682869.data new file mode 100644 index 0000000..71fa13d Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_low_1_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_mid_1_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_mid_1_25_1755682869.data new file mode 100644 index 0000000..71fa13d Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/laneprofile_mid_1_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/lineround.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/lineround.data new file mode 100644 index 0000000..0231b37 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/lineround.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/lottie.zip b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/lottie.zip new file mode 100644 index 0000000..f438607 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/lottie.zip differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapfeatureprofile_1_25_1757046099.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapfeatureprofile_1_25_1757046099.data new file mode 100644 index 0000000..b19775f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapfeatureprofile_1_25_1757046099.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapprofile_1_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapprofile_1_25_1755682869.data new file mode 100644 index 0000000..ccc2f30 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapprofile_1_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapprofile_2_25_1755682869.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapprofile_2_25_1755682869.data new file mode 100644 index 0000000..6b1a269 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/mapprofile_2_25_1755682869.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/search_scenic_icon.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/search_scenic_icon.data new file mode 100644 index 0000000..896934b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/search_scenic_icon.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/sky_hd_powersaving.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/sky_hd_powersaving.data new file mode 100644 index 0000000..49e32a5 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/sky_hd_powersaving.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style-for-custom_X_MainStd_Std_D_s_25_1757046134.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style-for-custom_X_MainStd_Std_D_s_25_1757046134.data new file mode 100644 index 0000000..9343adb Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style-for-custom_X_MainStd_Std_D_s_25_1757046134.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_0_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_0_25_1757046130.data new file mode 100644 index 0000000..0437519 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_0_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_d_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_d_25_1757046141.data new file mode 100644 index 0000000..6d88403 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_d_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_s_25_1757046140.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_s_25_1757046140.data new file mode 100644 index 0000000..4e0d6e6 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_s_25_1757046140.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sh_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sh_25_1757046130.data new file mode 100644 index 0000000..7d3e9e3 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sh_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sh_m_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sh_m_25_1757046130.data new file mode 100644 index 0000000..7d3e9e3 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sh_m_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sl_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sl_25_1757046141.data new file mode 100644 index 0000000..8a31b9a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_D_sl_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_d_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_d_25_1757046141.data new file mode 100644 index 0000000..58eb039 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_d_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_s_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_s_25_1757046141.data new file mode 100644 index 0000000..91bd6df Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_s_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sh_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sh_25_1757046130.data new file mode 100644 index 0000000..e06d401 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sh_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sh_m_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sh_m_25_1757046130.data new file mode 100644 index 0000000..e06d401 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sh_m_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sl_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sl_25_1757046141.data new file mode 100644 index 0000000..35c874a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavH_Drv_N_sl_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_d_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_d_25_1757046142.data new file mode 100644 index 0000000..6a72ce9 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_d_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_s_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_s_25_1757046142.data new file mode 100644 index 0000000..0c12dfe Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_s_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_sl_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_sl_25_1757046142.data new file mode 100644 index 0000000..ceaf120 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_D_sl_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_d_25_1757046143.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_d_25_1757046143.data new file mode 100644 index 0000000..5d6615e Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_d_25_1757046143.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_s_25_1757046143.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_s_25_1757046143.data new file mode 100644 index 0000000..ef9a347 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_s_25_1757046143.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_sl_25_1757046143.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_sl_25_1757046143.data new file mode 100644 index 0000000..049f84a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvNavS_Drv_N_sl_25_1757046143.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_d_25_1757046140.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_d_25_1757046140.data new file mode 100644 index 0000000..50d2948 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_d_25_1757046140.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_s_25_1757046140.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_s_25_1757046140.data new file mode 100644 index 0000000..2addc96 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_s_25_1757046140.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_25_1757046130.data new file mode 100644 index 0000000..12018d2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_l_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_l_25_1757046130.data new file mode 100644 index 0000000..dc8cad9 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_l_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_m_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_m_25_1757046130.data new file mode 100644 index 0000000..7a1ec75 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sh_m_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sl_25_1757046140.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sl_25_1757046140.data new file mode 100644 index 0000000..97e8114 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_D_sl_25_1757046140.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_d_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_d_25_1757046141.data new file mode 100644 index 0000000..3dd36bc Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_d_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_s_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_s_25_1757046141.data new file mode 100644 index 0000000..f47fd4c Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_s_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_25_1757046130.data new file mode 100644 index 0000000..7079671 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_l_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_l_25_1757046130.data new file mode 100644 index 0000000..a292c88 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_l_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_m_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_m_25_1757046130.data new file mode 100644 index 0000000..bdb82bf Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sh_m_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sl_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sl_25_1757046141.data new file mode 100644 index 0000000..57d87f7 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_DrvPlan_Lt_N_sl_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_d_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_d_25_1757046141.data new file mode 100644 index 0000000..d7f8106 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_d_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_s_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_s_25_1757046141.data new file mode 100644 index 0000000..8f8ee90 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_s_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_sl_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_sl_25_1757046142.data new file mode 100644 index 0000000..96fd7f8 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_D_sl_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_d_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_d_25_1757046142.data new file mode 100644 index 0000000..a05c75b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_d_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_s_25_1757046141.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_s_25_1757046141.data new file mode 100644 index 0000000..6db73e9 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_s_25_1757046141.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_sl_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_sl_25_1757046142.data new file mode 100644 index 0000000..53d4e58 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainPub_Pub_N_sl_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainSat_Sat_S_d_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainSat_Sat_S_d_25_1757046142.data new file mode 100644 index 0000000..7cde883 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainSat_Sat_S_d_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainSat_Sat_S_s_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainSat_Sat_S_s_25_1757046142.data new file mode 100644 index 0000000..4bb6836 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainSat_Sat_S_s_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_d_25_1757046134.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_d_25_1757046134.data new file mode 100644 index 0000000..3c6481f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_d_25_1757046134.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_s_25_1757046134.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_s_25_1757046134.data new file mode 100644 index 0000000..61c0975 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_s_25_1757046134.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_25_1757046130.data new file mode 100644 index 0000000..12018d2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_l_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_l_25_1757046130.data new file mode 100644 index 0000000..dc8cad9 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_l_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_m_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_m_25_1757046130.data new file mode 100644 index 0000000..7a1ec75 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sh_m_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sl_25_1757046134.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sl_25_1757046134.data new file mode 100644 index 0000000..479f9fc Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_D_sl_25_1757046134.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_d_25_1757046132.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_d_25_1757046132.data new file mode 100644 index 0000000..e38b595 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_d_25_1757046132.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_s_25_1757046131.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_s_25_1757046131.data new file mode 100644 index 0000000..5ec1ec6 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_s_25_1757046131.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_25_1757046130.data new file mode 100644 index 0000000..7079671 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_l_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_l_25_1757046130.data new file mode 100644 index 0000000..a292c88 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_l_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_m_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_m_25_1757046130.data new file mode 100644 index 0000000..bdb82bf Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sh_m_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sl_25_1757046134.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sl_25_1757046134.data new file mode 100644 index 0000000..e76a9f0 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_MainStd_Std_N_sl_25_1757046134.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_d_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_d_25_1757046142.data new file mode 100644 index 0000000..69c9da7 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_d_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_s_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_s_25_1757046142.data new file mode 100644 index 0000000..cb99550 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_s_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_sl_25_1757046142.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_sl_25_1757046142.data new file mode 100644 index 0000000..1dda6b2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_X_WalkNavi_Lt_D_sl_25_1757046142.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_d_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_d_25_1757046135.data new file mode 100644 index 0000000..eb5f060 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_d_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_s_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_s_25_1757046135.data new file mode 100644 index 0000000..efc7f55 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_s_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_sl_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_sl_25_1757046135.data new file mode 100644 index 0000000..13c85a6 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s28_Uni_S_sl_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_d_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_d_25_1757046135.data new file mode 100644 index 0000000..0482e12 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_d_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_s_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_s_25_1757046135.data new file mode 100644 index 0000000..325f880 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_s_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_sl_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_sl_25_1757046135.data new file mode 100644 index 0000000..c854ea8 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m31_s29_Uni_S_sl_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s30_Uni_S_s_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s30_Uni_S_s_25_1757046135.data new file mode 100644 index 0000000..4698911 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s30_Uni_S_s_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s30_Uni_S_sl_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s30_Uni_S_sl_25_1757046135.data new file mode 100644 index 0000000..df6f632 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s30_Uni_S_sl_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s31_Uni_S_s_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s31_Uni_S_s_25_1757046135.data new file mode 100644 index 0000000..0f37153 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s31_Uni_S_s_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s31_Uni_S_sl_25_1757046136.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s31_Uni_S_sl_25_1757046136.data new file mode 100644 index 0000000..8d1fe3b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m32_s31_Uni_S_sl_25_1757046136.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m52_s53_Uni_S_s_25_1757046137.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m52_s53_Uni_S_s_25_1757046137.data new file mode 100644 index 0000000..3a3429b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m52_s53_Uni_S_s_25_1757046137.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m54_s55_Uni_S_s_25_1757046137.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m54_s55_Uni_S_s_25_1757046137.data new file mode 100644 index 0000000..376c08e Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_m54_s55_Uni_S_s_25_1757046137.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_material_25_1757046135.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_material_25_1757046135.data new file mode 100644 index 0000000..0cac52d Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_material_25_1757046135.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_rule_25_1756215850.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_rule_25_1756215850.data new file mode 100644 index 0000000..9c42319 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/style_rule_25_1756215850.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/styleiconslist.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/styleiconslist.data new file mode 100644 index 0000000..2a698c7 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/AMap3D.bundle/styleiconslist.data @@ -0,0 +1,93 @@ +92 +assets_group_10_25_1757046143.data +assets_group_3_25_1757046136.data +assets_group_6_25_1757046135.data +assets_group_9_25_1757046143.data +assets_info_1757046189.data +config_1_25_1756777905.data +config_2_25_1755682869.data +icons_10000_25_1755682872.data +icons_10001_25_1755682873.data +icons_10002_25_1755682874.data +icons_10003_25_1755682872.data +icons_10004_25_1755682872.data +icons_10005_25_1755682873.data +icons_10006_25_1755682872.data +icons_10007_25_1755682872.data +icons_30000_25_1755682869.data +icons_30001_25_1755682869.data +icons_30002_25_1755682869.data +icons_41_25_1757046158.data +icons_5_25_1757046135.data +laneprofile_1_25_1755682869.data +laneprofile_low_1_25_1755682869.data +laneprofile_mid_1_25_1755682869.data +mapfeatureprofile_1_25_1757046099.data +mapprofile_1_25_1755682869.data +mapprofile_2_25_1755682869.data +style_0_25_1757046130.data +style_X_DrvNavH_Drv_D_d_25_1757046141.data +style_X_DrvNavH_Drv_D_s_25_1757046140.data +style_X_DrvNavH_Drv_D_sh_25_1757046130.data +style_X_DrvNavH_Drv_D_sh_m_25_1757046130.data +style_X_DrvNavH_Drv_D_sl_25_1757046141.data +style_X_DrvNavH_Drv_N_d_25_1757046141.data +style_X_DrvNavH_Drv_N_s_25_1757046141.data +style_X_DrvNavH_Drv_N_sh_25_1757046130.data +style_X_DrvNavH_Drv_N_sh_m_25_1757046130.data +style_X_DrvNavH_Drv_N_sl_25_1757046141.data +style_X_DrvNavS_Drv_D_d_25_1757046142.data +style_X_DrvNavS_Drv_D_s_25_1757046142.data +style_X_DrvNavS_Drv_D_sl_25_1757046142.data +style_X_DrvNavS_Drv_N_d_25_1757046143.data +style_X_DrvNavS_Drv_N_s_25_1757046143.data +style_X_DrvNavS_Drv_N_sl_25_1757046143.data +style_X_DrvPlan_Lt_D_d_25_1757046140.data +style_X_DrvPlan_Lt_D_s_25_1757046140.data +style_X_DrvPlan_Lt_D_sh_25_1757046130.data +style_X_DrvPlan_Lt_D_sh_l_25_1757046130.data +style_X_DrvPlan_Lt_D_sh_m_25_1757046130.data +style_X_DrvPlan_Lt_D_sl_25_1757046140.data +style_X_DrvPlan_Lt_N_d_25_1757046141.data +style_X_DrvPlan_Lt_N_s_25_1757046141.data +style_X_DrvPlan_Lt_N_sh_25_1757046130.data +style_X_DrvPlan_Lt_N_sh_l_25_1757046130.data +style_X_DrvPlan_Lt_N_sh_m_25_1757046130.data +style_X_DrvPlan_Lt_N_sl_25_1757046141.data +style_X_MainPub_Pub_D_d_25_1757046141.data +style_X_MainPub_Pub_D_s_25_1757046141.data +style_X_MainPub_Pub_D_sl_25_1757046142.data +style_X_MainPub_Pub_N_d_25_1757046142.data +style_X_MainPub_Pub_N_s_25_1757046141.data +style_X_MainPub_Pub_N_sl_25_1757046142.data +style_X_MainSat_Sat_S_d_25_1757046142.data +style_X_MainSat_Sat_S_s_25_1757046142.data +style_X_MainStd_Std_D_d_25_1757046134.data +style_X_MainStd_Std_D_s_25_1757046134.data +style_X_MainStd_Std_D_sh_25_1757046130.data +style_X_MainStd_Std_D_sh_l_25_1757046130.data +style_X_MainStd_Std_D_sh_m_25_1757046130.data +style_X_MainStd_Std_D_sl_25_1757046134.data +style_X_MainStd_Std_N_d_25_1757046132.data +style_X_MainStd_Std_N_s_25_1757046131.data +style_X_MainStd_Std_N_sh_25_1757046130.data +style_X_MainStd_Std_N_sh_l_25_1757046130.data +style_X_MainStd_Std_N_sh_m_25_1757046130.data +style_X_MainStd_Std_N_sl_25_1757046134.data +style_X_WalkNavi_Lt_D_d_25_1757046142.data +style_X_WalkNavi_Lt_D_s_25_1757046142.data +style_X_WalkNavi_Lt_D_sl_25_1757046142.data +style_m31_s28_Uni_S_d_25_1757046135.data +style_m31_s28_Uni_S_s_25_1757046135.data +style_m31_s28_Uni_S_sl_25_1757046135.data +style_m31_s29_Uni_S_d_25_1757046135.data +style_m31_s29_Uni_S_s_25_1757046135.data +style_m31_s29_Uni_S_sl_25_1757046135.data +style_m32_s30_Uni_S_s_25_1757046135.data +style_m32_s30_Uni_S_sl_25_1757046135.data +style_m32_s31_Uni_S_s_25_1757046135.data +style_m32_s31_Uni_S_sl_25_1757046136.data +style_m52_s53_Uni_S_s_25_1757046137.data +style_m54_s55_Uni_S_s_25_1757046137.data +style_material_25_1757046135.data +style_rule_25_1756215850.data \ No newline at end of file diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/GNaviConfig.xml b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/GNaviConfig.xml new file mode 100755 index 0000000..fb3eff1 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/GNaviConfig.xml @@ -0,0 +1,9 @@ + + + + ./data/ + ./diff/ + ./res900/ + ./log/ + + diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/bundleVersion.txt b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/bundleVersion.txt new file mode 100644 index 0000000..5336b75 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/bundleVersion.txt @@ -0,0 +1 @@ +11.1.200 \ No newline at end of file diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/customStyle/style-for-custom_0_25_1757046130.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/customStyle/style-for-custom_0_25_1757046130.data new file mode 100644 index 0000000..966970e Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/customStyle/style-for-custom_0_25_1757046130.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/customStyle/style-for-custom_0_25_1757046130_notex.data b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/customStyle/style-for-custom_0_25_1757046130_notex.data new file mode 100644 index 0000000..771126b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/customStyle/style-for-custom_0_25_1757046130_notex.data differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/high-frequency-devices.plist b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/high-frequency-devices.plist new file mode 100644 index 0000000..d00df82 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/high-frequency-devices.plist @@ -0,0 +1,14 @@ + + + + + iPhone14,2 + iPhone14,3 + iPhone15,2 + iPhone15,3 + iPhone16,1 + iPhone16,2 + iPhone17,1 + iPhone17,2 + + diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10000_25_1757046134.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10000_25_1757046134.png new file mode 100644 index 0000000..84046d5 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10000_25_1757046134.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10001_25_1757046135.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10001_25_1757046135.png new file mode 100644 index 0000000..a39d921 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10001_25_1757046135.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10002_25_1757046134.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10002_25_1757046134.png new file mode 100644 index 0000000..3486abf Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10002_25_1757046134.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10003_25_1757046134.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10003_25_1757046134.png new file mode 100644 index 0000000..c322c16 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_10003_25_1757046134.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_30000_25_1757046130.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_30000_25_1757046130.png new file mode 100644 index 0000000..61651ac Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/icons.bundle/icons_30000_25_1757046130.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_inner.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_inner.png new file mode 100644 index 0000000..11a0b1a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_inner.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_outer.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_outer.png new file mode 100644 index 0000000..f2b40cb Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_outer.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_shadow.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_shadow.png new file mode 100644 index 0000000..e531d68 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_3d_shadow.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_inner.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_inner.png new file mode 100644 index 0000000..6c3e709 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_inner.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_outer.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_outer.png new file mode 100644 index 0000000..26ddb2c Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/arrow_line_outer.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/calloutArrowMask.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/calloutArrowMask.png new file mode 100644 index 0000000..a89c653 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/calloutArrowMask.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/calloutArrowMask@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/calloutArrowMask@2x.png new file mode 100644 index 0000000..f3e2cf2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/calloutArrowMask@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin.png new file mode 100644 index 0000000..20d0424 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin@2x.png new file mode 100644 index 0000000..54d0460 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin@3x.png new file mode 100644 index 0000000..cd5c6a3 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift.png new file mode 100644 index 0000000..48f5c1b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift@2x.png new file mode 100644 index 0000000..20d15d7 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift@3x.png new file mode 100644 index 0000000..f2598b7 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/greenPin_lift@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/infowindow.jpg b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/infowindow.jpg new file mode 100644 index 0000000..03ddf73 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/infowindow.jpg differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineDashTexture.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineDashTexture.png new file mode 100644 index 0000000..20c7df8 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineDashTexture.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineDashTextureDot.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineDashTextureDot.png new file mode 100644 index 0000000..1092c26 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineDashTextureDot.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineTexture.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineTexture.png new file mode 100644 index 0000000..a3d7e71 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineTexture.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineTextureThin.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineTextureThin.png new file mode 100644 index 0000000..4755362 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/lineTextureThin.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/marker_blue.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/marker_blue.png new file mode 100644 index 0000000..6368615 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/marker_blue.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/marker_blue@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/marker_blue@2x.png new file mode 100644 index 0000000..ddb06df Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/marker_blue@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_clear@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_clear@2x.png new file mode 100644 index 0000000..aed3659 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_clear@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_down@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_down@2x.png new file mode 100644 index 0000000..b950a2f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_down@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_shouqi@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_shouqi@2x.png new file mode 100755 index 0000000..9ac82af Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_shouqi@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_shouqi_2@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_shouqi_2@2x.png new file mode 100644 index 0000000..b00a79b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_shouqi_2@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_sousuo@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_sousuo@2x.png new file mode 100644 index 0000000..23eed11 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_sousuo@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_zhankai@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_zhankai@2x.png new file mode 100644 index 0000000..0056674 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_zhankai@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_zhankai_2@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_zhankai_2@2x.png new file mode 100644 index 0000000..86ce247 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/offline_zhankai_2@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_fog.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_fog.png new file mode 100644 index 0000000..f1aa1e7 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_fog.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_haze.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_haze.png new file mode 100644 index 0000000..e24725a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_haze.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_rain.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_rain.png new file mode 100644 index 0000000..bcb304b Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_rain.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_snow.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_snow.png new file mode 100644 index 0000000..984abeb Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_snow.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_sun_0.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_sun_0.png new file mode 100644 index 0000000..8ffaeea Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_sun_0.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_sun_1.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_sun_1.png new file mode 100644 index 0000000..b57761e Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/particle_sun_1.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow.png new file mode 100644 index 0000000..4f85ff1 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow@2x.png new file mode 100644 index 0000000..8acfc67 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow@3x.png new file mode 100644 index 0000000..fc16658 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/pin_shadow@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin.png new file mode 100644 index 0000000..ffe3892 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin@2x.png new file mode 100644 index 0000000..4a08cf2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin@3x.png new file mode 100644 index 0000000..2f92d11 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift.png new file mode 100644 index 0000000..bf1f8f4 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift@2x.png new file mode 100644 index 0000000..ef2b2a6 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift@3x.png new file mode 100644 index 0000000..d58f14f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/purplePin_lift@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin.png new file mode 100644 index 0000000..1cf4744 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin@2x.png new file mode 100644 index 0000000..0882272 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin@3x.png new file mode 100644 index 0000000..2a66034 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift.png new file mode 100644 index 0000000..24c4b9a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift@2x.png new file mode 100644 index 0000000..cfee8aa Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift@3x.png new file mode 100644 index 0000000..5d90fa3 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/redPin_lift@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_.png new file mode 100644 index 0000000..5889ed2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_@2x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_@2x.png new file mode 100644 index 0000000..5889ed2 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_@2x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_@3x.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_@3x.png new file mode 100644 index 0000000..dd955cd Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/select_@3x.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/terrainDefault.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/terrainDefault.png new file mode 100644 index 0000000..11ef10f Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/terrainDefault.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_blue.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_blue.png new file mode 100644 index 0000000..c54767e Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_blue.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_darkred.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_darkred.png new file mode 100644 index 0000000..7935f1a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_darkred.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_gray.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_gray.png new file mode 100644 index 0000000..2a36842 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_gray.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_green.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_green.png new file mode 100644 index 0000000..1cad28c Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_green.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_red.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_red.png new file mode 100644 index 0000000..fccb898 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_red.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_yellow.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_yellow.png new file mode 100644 index 0000000..b222b87 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/images/traffic_texture_yellow.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ pl.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ pl.strings new file mode 100644 index 0000000..61678b6 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ pl.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="stopa"; +"O106184_297"="mila"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ar.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ar.strings new file mode 100644 index 0000000..0108a67 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ar.strings @@ -0,0 +1,4 @@ +"O106184_294"="ميل"; +"O106184_295"="قدم"; +"O106184_296"="كم"; +"O106184_297"="م"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_az.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_az.strings new file mode 100644 index 0000000..d67b0db --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_az.strings @@ -0,0 +1,5 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ayaq"; +"O106184_297"="mil"; + diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_cs.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_cs.strings new file mode 100644 index 0000000..65de0ca --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_cs.strings @@ -0,0 +1,5 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="stopa"; +"O106184_297"="míle"; + diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_de.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_de.strings new file mode 100644 index 0000000..b5504dc --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_de.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ft"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_en.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_en.strings new file mode 100644 index 0000000..80540d7 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_en.strings @@ -0,0 +1,299 @@ +"O106184_01"="[MAMapKit] To use background location services on iOS 11 and above, you need to implement the mapViewRequireLocationAuth: delegate method."; +"O106184_02"="[MAMapKit] To use location services on iOS 11 and above, you need to add the NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription fields in Infoplist."; +"O106184_03"="[MAMapKit] To use background location services on iOS 8 and above, you need to implement the mapViewRequireLocationAuth: delegate method"; +"O106184_04"="[MAMapKit] To use location services on iOS 8 and above, it is necessary to add the NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription field in Infoplist."; +"O106184_05"="Request failed"; +"O106184_06"="MVT feature is not supported"; +"O106184_07"="glTF feature is not supported"; +"O106184_08"="This feature is a paid service, please contact the business department to activate it."; +"O106184_09"="URI exception"; +"O106184_10"="Deprecated, calling has no effect. since 9.6.0"; +"O106184_11"="Deprecated, since 7.9.0"; +"O106184_12"="Deprecated, since 7.9.0, please use alpha in MAHeatMapVectorOverlayRender"; +"O106184_13"="Deprecated, since 7.7.0, please use alpha in MAGroundOverlayRenderer"; +"O106184_14"="Enter city name or pinyin"; +"O106184_15"="Download Management"; +"O106184_16"="City List"; +"O106184_17"="Basic Function Package + Municipality"; +"O106184_18"="Hong Kong, Macao and Taiwan"; +"O106184_19"="Taiwan Province"; +"O106184_20"="Offline map"; +"O106184_21"="Download is not supported"; +"O106184_22"="Cancel"; +"O106184_23"="Delete"; +"O106184_24"="Cancel download"; +"O106184_25"="Pause download"; +"O106184_26"="Update"; +"O106184_27"="Continue Download"; +"O106184_28"="Waiting"; +"O106184_29"="Downloading"; +"O106184_30"="Pause"; +"O106184_31"="Downloaded"; +"O106184_32"="Updated"; +"O106184_33"="No relevant city found"; +"O106184_34"="[MAMapKit] Invalid resource bundle, please check if AMap.bundle is correctly imported"; +"O106184_35"="[MAMapKit]: warning: Please check if the imported AMap.bundle is correct"; +"O106184_36"="Custom styleData cache exists, using cached data"; +"O106184_37"="[MAMapKit] Failed to fetch custom style online"; +"O106184_38"="[MAMapKit] Failed to fetch custom style online, returned data is empty. Please check if styleid and key are correct"; +"O106184_39"="Online styleData pull successful"; +"O106184_40"="[MAMapKit] Failed to decompress style zip"; +"O106184_41"="[MAMapKit] Style file error"; +"O106184_42"="Custom styleExtraData cache exists, using cache"; +"O106184_43"="Online styleExtraData pull succeeded"; +"O106184_44"="[MAMapKit] Failed to decompress custom resource file"; +"O106184_45"="[MAMapKit] The delegate to be added must implement MAMapViewDelegate"; +"O106184_46"="[MAMapKit] The setWorldVectorMapLanguage method is deprecated, please use setStyle:"; +"O106184_47"="Current Location"; +"O106184_48"="Map retrieval service failed"; +"O106184_49"="Warning"; +"O106184_50"="You are currently using a test version of the map, which cannot be used for production. If you need to go live, please contact us to provide the official production package"; +"O106184_51"="[MAMapKit] key authentication failed"; +"O106184_52"="[MAMapKit] The custom map style file currently in use does not match the current version. Please visit the official website (lbs.amap.com) to update to the new version of the style file."; +"O106184_53"="GS (2023)4047 GS (2023)551 | GS (2023)2175"; +"O106184_54"="[MAMapKit] reloadMap execution failed because the map is in background or renderringDisabled = YES"; +"O106184_55"="Deprecated, since 7.9.0, please use the renderringDisabled property"; +"O106184_56"="Deprecated, please use the takeSnapshotInRect:withCompletionBlock: method since 6.0.0"; +"O106184_57"="Deprecated, use the coordinate conversion interface in AMapFoundation"; +"O106184_58"="Loading cloud control cache"; +"O106184_59"="Obtain Cloud Control"; +"O106184_60"="Failed to initialize the engine"; +"O106184_61"="[MAMapKit] Successfully decompressed the resource res file"; +"O106184_62"="[MAMapKit] Failed to decompress resource res file"; +"O106184_63"="Failed to load resources"; +"O106184_64"="North"; +"O106184_65"="[MAMapKit] Successfully extracted the resource style file"; +"O106184_66"="[MAMapKit] Failed to decompress resource style file!"; +"O106184_67"="[MAMapKit] Please set the correct style value."; +"O106184_68"="Title"; +"O106184_69"="Content"; +"O106184_70"="Key verification failed:["; +"O106184_71"="Municipality"; +"O106184_72"="Hong Kong"; +"O106184_73"="Macau"; +"O106184_74"="Hong Kong and Macau"; +"O106184_75"="Extracting"; +"O106184_76"="Beijing"; +"O106184_77"="Total Size"; +"O106184_78"="Paused"; +"O106184_79"="Download failed"; +"O106184_80"="Downloaded - Update Available"; +"O106184_81"="Download exception occurred"; +"O106184_82"="Check for updates"; +"O106184_83"="Beijing"; +"O106184_84"="No such city"; +"O106184_85"="Too few track points or too close distance, track correction failed"; +"O106184_86"="Positioning timeout"; +"O106184_87"="Correction successful"; +"O106184_88"="IO Operation Exception - IOException"; +"O106184_89"="Socket Connection Exception - SocketException"; +"O106184_90"="Socket connection timeout - SocketTimeoutException"; +"O106184_91"="Invalid parameter - IllegalArgumentException"; +"O106184_92"="Null Pointer Exception - NullPointException"; +"O106184_93"="URL exception - MalformedURLException"; +"O106184_94"="Unknown Host - UnKnowHostException"; +"O106184_95"="Server connection failed - UnknownServiceException"; +"O106184_96"="Protocol parsing error - ProtocolException"; +"O106184_97"="http connection failed - ConnectionException"; +"O106184_98"="Unknown error"; +"O106184_99"="Key authentication failed"; +"O106184_100"="[MAMapKit] The custom map style file currently in use does not match the current version. Please visit the official website (lbs.amap.com) to update to the new version of the style file."; +"O106184_101"="Write Exception"; +"O106184_102"="Invalid coordinate value"; +"O106184_103"="Amap is not installed or the version is outdated on the mobile device"; +"O106184_104"="Invalid parameter"; +"O106184_105"="Overseas authentication failed"; +"O106184_106"="GLTF feature is not supported"; +"O106184_107"="The topographic map feature is a paid capability. Please contact our sales team to activate this function."; +"O106184_108"="Library mismatch"; +"O106184_109"="lib loading failed"; +"O106184_110"="User signature not verified"; +"O106184_111"="User key is incorrect or expired"; +"O106184_112"="Requested service does not exist"; +"O106184_113"="Daily access limit exceeded"; +"O106184_114"="User access is too frequent"; +"O106184_115"="Invalid user IP"; +"O106184_116"="Invalid user domain"; +"O106184_117"="User MD5 security code verification failed"; +"O106184_118"="The requested key does not match the bound platform"; +"O106184_119"="IP access limit exceeded"; +"O106184_120"="Service does not support HTTPS requests"; +"O106184_121"="Insufficient permissions, service request denied"; +"O106184_122"="The developer deleted the key, and the key cannot be used normally after deletion"; +"O106184_123"="Service request response error"; +"O106184_124"="Engine returned abnormal data"; +"O106184_125"="Server request connection timeout"; +"O106184_126"="Timeout reading service result"; +"O106184_127"="Invalid request parameters"; +"O106184_128"="Missing required parameter"; +"O106184_129"="Illegal request protocol"; +"O106184_130"="Other unknown error"; +"O106184_131"="HTTP or socket connection failed - ConnectionException"; +"O106184_132"="No corresponding error"; +"O106184_133"="Prompt"; +"O106184_134"="The current application lacks necessary permissions. + +Please tap \"Settings\"-\"Permissions\"- enable required permissions"; +"O106184_135"="Settings"; +"O106184_136"="Add custom building"; +"O106184_137"="Change building color"; +"O106184_138"="Change building height"; +"O106184_139"="Destroy Building Layer"; +"O106184_140"="Click on the heatmap area to get the heat value"; +"O106184_141"="Search"; +"O106184_142"="Please enter a keyword"; +"O106184_143"="Map"; +"O106184_144"="Set Route"; +"O106184_145"="Start"; +"O106184_146"="Trajectory Correction"; +"O106184_147"="Clear completed routes"; +"O106184_148"="AutoNavi Coordinates"; +"O106184_149"="GPS Coordinates"; +"O106184_150"="Baidu Coordinates"; +"O106184_151"="Total distance:"; +"O106184_152"="Wait:"; +"O106184_153"="Start trajectory correction"; +"O106184_154"="Stop track correction"; +"O106184_155"="Lujiazui"; +"O106184_156"="Zhongguancun"; +"O106184_157"="Enable personalized map"; +"O106184_158"="Standard Map"; +"O106184_159"="Satellite Map"; +"O106184_160"="Night mode"; +"O106184_161"="Navigation Mode"; +"O106184_162"="Bus Mode"; +"O106184_163"="Navigation Night Mode"; +"O106184_164"="Please enter the bus route name"; +"O106184_165"="International map authentication failed, the current key does not have permission to use the international map, the international map will not be displayed!"; +"O106184_166"="Search results"; +"O106184_167"="Previous page"; +"O106184_168"="Next page"; +"O106184_169"="Set Polygon properties"; +"O106184_170"="ID Search"; +"O106184_171"="Nearby Search"; +"O106184_172"="Route Name Query"; +"O106184_173"="Local Search"; +"O106184_174"="Convert to AutoNavi coordinate system"; +"O106184_175"="Picture Mode"; +"O106184_176"="Add HD junction view"; +"O106184_177"="Add intersection enlarged signboard"; +"O106184_178"="Add intersection enlarged view jj"; +"O106184_179"="Remove"; +"O106184_180"="Drag marker1"; +"O106184_181"="Set arrival time"; +"O106184_182"="Confirm"; +"O106184_183"="Province"; +"O106184_184"="City"; +"O106184_185"="Polygon Search"; +"O106184_186"="Search"; +"O106184_187"="Tap or long press the map"; +"O106184_188"="Move camera"; +"O106184_189"="Map touch event"; +"O106184_190"="Swipe gesture"; +"O106184_191"="Zoom gesture"; +"O106184_192"="Tilt gesture"; +"O106184_193"="Rotate gesture"; +"O106184_194"="Add hole to circle"; +"O106184_195"="Polygon add hole"; +"O106184_196"="Start"; +"O106184_197"="Set Display Area"; +"O106184_198"="Map latitude and longitude coordinates (Click on the map to get the coordinates)"; +"O106184_199"="Latitude and longitude to screen pixel coordinates"; +"O106184_200"="Convert screen pixel coordinates to latitude and longitude"; +"O106184_201"="Positioning"; +"O106184_202"="District/County"; +"O106184_203"="Follow"; +"O106184_204"="Rotate"; +"O106184_205"="Rotate Position"; +"O106184_206"="Follow without moving the center point"; +"O106184_207"="Display"; +"O106184_208"="The rotation position does not move to the center point"; +"O106184_209"="logo position"; +"O106184_210"="Bottom left"; +"O106184_211"="Bottom center"; +"O106184_212"="Rotation does not move to the center point"; +"O106184_213"="Growth Animation"; +"O106184_214"="Move Animation"; +"O106184_215"="Breathing Animation"; +"O106184_216"="Minimum level default 3"; +"O106184_217"="Maximum level default 19 indoor 20"; +"O106184_218"="Settings"; +"O106184_219"="Reset"; +"O106184_220"="Current zoom level: "; +"O106184_221"="Custom maps"; +"O106184_222"="Polyhedron Example"; +"O106184_223"="All cities"; +"O106184_224"="Download Management"; +"O106184_225"="Beijing"; +"O106184_226"="Total Size"; +"O106184_227"="marker's z-index"; +"O106184_228"="z-index of polyline"; +"O106184_229"="Bottom right"; +"O106184_230"="Sunny"; +"O106184_231"="Rainy day"; +"O106184_232"="Snowy day"; +"O106184_233"="haze"; +"O106184_234"="Custom"; +"O106184_235"="Future Time"; +"O106184_236"="Route"; +"O106184_237"="Please enter search keywords"; +"O106184_238"="Please enter a keyword"; +"O106184_239"="Please enter the city"; +"O106184_240"="Start search"; +"O106184_241"="Next page"; +"O106184_242"="Set Polyline properties"; +"O106184_243"="Intercity Bus"; +"O106184_244"="Add"; +"O106184_245"="Remove"; +"O106184_246"="Gas station"; +"O106184_247"="Auto Repair Shop"; +"O106184_248"="Toilet"; +"O106184_249"="Map screenshot"; +"O106184_250"="Continuous Screenshot"; +"O106184_251"="Capture oversized image"; +"O106184_252"="First query interval"; +"O106184_253"="Query interval"; +"O106184_254"="Allowable deviation"; +"O106184_255"="Location Sharing"; +"O106184_256"="Route Planning Sharing"; +"O106184_257"="POI Sharing"; +"O106184_258"="Navigation Sharing"; +"O106184_259"="Tsinghua University"; +"O106184_260"="Address..."; +"O106184_261"="First floor"; +"O106184_262"="Second floor"; +"O106184_263"="Third floor"; +"O106184_264"="Open TileOverlay"; +"O106184_265"="Departure Time"; +"O106184_266"="Arrival Time"; +"O106184_267"="Total mileage"; +"O106184_268"="z-index of polygon"; +"O106184_269"="Get scale"; +"O106184_270"="Scale"; +"O106184_271"="Zoom display and location"; +"O106184_272"="Display"; +"O106184_273"="Set arrival time"; +"O106184_274"="Compass"; +"O106184_275"="My Location Layer"; +"O106184_276"="Real-time"; +"O106184_277"="Release Time"; +"O106184_278"="Forecast"; +"O106184_279"="Release Time"; +"O106184_280"="This method is no longer valid. Please go to the official website (lbs.amap.com) to update the new version of the style file and use setCustomMapStyleOptions"; +"O106184_281"="When constructing CameraPosition, the location (target) cannot be null"; +"O106184_282"="The custom map style file currently in use does not match the current version. Please update to the new version of the style file on the official website (lbs.amap.com)."; +"O106184_283"="middle right"; +"O106184_284"="Authentication failed. The current key does not have permission to use custom textures. Custom texture-related content will not be displayed!"; +"O106184_285"="Terrain map authentication failed, the current key does not have permission to use the terrain map, the terrain map will not be displayed!"; +"O106184_286"="Context is null, please use MapsInitializer.initialize(Context paramContext) to set the Context before calling the map"; +"O106184_287"="No relevant city found"; +"O106184_288"="Network anomaly"; +"O106184_289"="No network connection"; +"O106184_290"="country"; +"O106184_291"="No. GS(2024)2108 | No. GS(2025)3454"; +"O106184_292"="No. GS (2023)4047"; +"O106184_293"="No. GS(2021)6352"; +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ft"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_es.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_es.strings new file mode 100644 index 0000000..b5504dc --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_es.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ft"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_fr.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_fr.strings new file mode 100644 index 0000000..53fc800 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_fr.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="pi"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_he.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_he.strings new file mode 100644 index 0000000..7b59ac8 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_he.strings @@ -0,0 +1,4 @@ +"O106184_294" = "מייל"; +"O106184_295" = "רגל"; +"O106184_296" = "ק\"מ"; +"O106184_297" = "מטר"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_id.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_id.strings new file mode 100644 index 0000000..ebace70 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_id.strings @@ -0,0 +1,299 @@ +"O106184_01"="[MAMapKit] Untuk menggunakan layanan lokasi latar belakang di iOS 11 dan di atasnya, Anda perlu menerapkan metode delegasi mapViewRequireLocationAuth:"; +"O106184_02"=" [MAMapKit] Untuk menggunakan Layanan Lokasi di iOS 11 dan lebih baru, Anda perlu menambahkan bidang NSLocationAlwaysAndWhenInUseUsageDescription dan NSLocationWhenInUseUsageDescription di Info.plist."; +"O106184_03"="[MAMapKit] Untuk menggunakan layanan lokasi latar belakang di iOS 8 dan di atasnya, Anda perlu mengimplementasikan metode delegasi mapViewRequireLocationAuth:"; +"O106184_04"="[MAMapKit] Untuk menggunakan layanan lokasi pada iOS 8 dan lebih baru, perlu menambahkan bidang NSLocationAlwaysUsageDescription atau NSLocationWhenInUseUsageDescription di Info.plist."; +"O106184_05"="Permintaan gagal"; +"O106184_06"="Fitur MVT tidak didukung"; +"O106184_07"="Fitur glTF tidak didukung"; +"O106184_08"="Fitur ini adalah layanan berbayar, silakan hubungi departemen bisnis untuk mengaktifkannya."; +"O106184_09"="Pengecualian URI"; +"O106184_10"="Tidak digunakan lagi, pemanggilan tidak memiliki efek. since 9.6.0"; +"O106184_11"="Tidak digunakan lagi, sejak 7.9.0"; +"O106184_12"="Sudah usang, sejak 7.9.0, silakan gunakan alpha di MAHeatMapVectorOverlayRender"; +"O106184_13"="Tidak digunakan lagi, sejak 7.7.0, silakan gunakan alpha di MAGroundOverlayRenderer"; +"O106184_14"="Masukkan nama kota atau pinyin"; +"O106184_15"="Manajemen unduhan"; +"O106184_16"="Daftar kota"; +"O106184_17"="Paket Fungsi Dasar + Kotamadya"; +"O106184_18"="Hong Kong, Makau dan Taiwan"; +"O106184_19"="Provinsi Taiwan"; +"O106184_20"="Peta luring"; +"O106184_21"="Unduhan tidak didukung"; +"O106184_22"="Batal"; +"O106184_23"="Hapus"; +"O106184_24"="Batalkan unduhan"; +"O106184_25"="Download dijeda"; +"O106184_26"="Perbarui"; +"O106184_27"="Lanjutkan unduhan"; +"O106184_28"="Menunggu"; +"O106184_29"="Sedang mengunduh"; +"O106184_30"="Jeda"; +"O106184_31"="Telah diunduh"; +"O106184_32"="Ada pembaruan"; +"O106184_33"="Kota yang relevan tidak ditemukan"; +"O106184_34"="[MAMapKit] Bundel sumber tidak valid, harap periksa apakah AMap.bundle diimpor dengan benar"; +"O106184_35"="[MAMapKit]: peringatan: Harap periksa apakah AMap.bundle yang diimpor sudah benar"; +"O106184_36"="Cache styleData kustom ada, menggunakan cache"; +"O106184_37"="[MAMapKit] Gagal mengambil gaya kustom secara online"; +"O106184_38"="[MAMapKit] Gagal mengambil gaya kustom secara online, data yang dikembalikan kosong. Harap periksa apakah styleid dan key benar"; +"O106184_39"="Pengambilan styleData online berhasil"; +"O106184_40"="[MAMapKit] Gagal mengekstrak zip gaya"; +"O106184_41"="[MAMapKit] Kesalahan File Gaya"; +"O106184_42"="Ada cache styleExtraData kustom, menggunakan cache"; +"O106184_43"="Data styleExtraData online berhasil diambil"; +"O106184_44"="[MAMapKit] Dekompresi file sumber daya kustom gagal"; +"O106184_45"="[MAMapKit] Delegasi yang akan ditambahkan harus mengimplementasikan MAMapViewDelegate"; +"O106184_46"="[MAMapKit] Metode setWorldVectorMapLanguage sudah tidak digunakan, harap gunakan setStyle:"; +"O106184_47"="Lokasi saat ini"; +"O106184_48"="Layanan pengambilan peta gagal"; +"O106184_49"="Peringatan"; +"O106184_50"="Anda saat ini menggunakan versi uji coba peta, yang tidak dapat digunakan untuk produksi. Jika Anda perlu meluncurkan, silakan hubungi kami untuk menyediakan paket produksi resmi"; +"O106184_51"="[MAMapKit] Autentikasi kunci gagal"; +"O106184_52"="[MAMapKit] File gaya peta kustom yang saat ini digunakan tidak cocok dengan versi saat ini. Silakan perbarui ke versi baru file gaya di situs web resmi (lbs.amap.com)"; +"O106184_53"="GS (2023)4047 GS (2023)551 | GS (2023)2175"; +"O106184_54"="[MAMapKit] reloadMap gagal dijalankan karena peta berada di latar belakang atau renderringDisabled = YES"; +"O106184_55"="Tidak digunakan lagi, sejak 7.9.0, harap gunakan properti renderringDisabled"; +"O106184_56"="Tidak digunakan lagi, silakan gunakan metode takeSnapshotInRect:withCompletionBlock: sejak 6.0.0"; +"O106184_57"="Tidak digunakan lagi, gunakan antarmuka konversi koordinat di AMapFoundation"; +"O106184_58"="Memuat cache kontrol awan"; +"O106184_59"="Dapatkan Kontrol Awan"; +"O106184_60"="Gagal menginisialisasi mesin"; +"O106184_61"="[MAMapKit] Berhasil mendekompresi file res sumber daya"; +"O106184_62"="[MAMapKit] Gagal mengekstrak file sumber daya res"; +"O106184_63"="Gagal memuat sumber daya"; +"O106184_64"="Utara"; +"O106184_65"="[MAMapKit] Berhasil mengekstrak file gaya sumber daya"; +"O106184_66"="[MAMapKit] Gagal mendekompresi file sumber daya gaya!"; +"O106184_67"="[MAMapKit] Harap atur nilai style yang benar."; +"O106184_68"="Judul"; +"O106184_69"="konten"; +"O106184_70"="Verifikasi kunci gagal:["; +"O106184_71"="Kotamadya"; +"O106184_72"="Hong Kong"; +"O106184_73"="Makau"; +"O106184_74"="Hong Kong dan Makau"; +"O106184_75"="Sedang mengekstrak"; +"O106184_76"="Beijing"; +"O106184_77"="Ukuran total"; +"O106184_78"="Dalam jeda"; +"O106184_79"="Unduhan gagal"; +"O106184_80"="Diunduh - Ada Pembaruan"; +"O106184_81"="Unduhan terjadi kesalahan"; +"O106184_82"="Periksa pembaruan"; +"O106184_83"="Beijing"; +"O106184_84"="Tidak ada kota tersebut"; +"O106184_85"="Titik jalur terlalu sedikit atau jaraknya terlalu dekat, koreksi jalur gagal"; +"O106184_86"="Waktu habis untuk penentuan posisi"; +"O106184_87"="Koreksi berhasil"; +"O106184_88"="IO Operasi Pengecualian - IOException"; +"O106184_89"="Koneksi Socket Tidak Normal - SocketException"; +"O106184_90"="Waktu koneksi socket habis - SocketTimeoutException"; +"O106184_91"="Parameter tidak valid - IllegalArgumentException"; +"O106184_92"="Pengecualian Pointer Null - NullPointException"; +"O106184_93"="Pengecualian URL - MalformedURLException"; +"O106184_94"="Host Tidak Dikenal - UnKnowHostException"; +"O106184_95"="Gagal terhubung ke server - UnknownServiceException"; +"O106184_96"="Kesalahan parsing protokol - ProtocolException"; +"O106184_97"="http koneksi gagal - ConnectionException"; +"O106184_98"="Kesalahan tidak diketahui"; +"O106184_99"="Autentikasi kunci gagal"; +"O106184_100"="[MAMapKit] File gaya peta kustom yang saat ini digunakan tidak cocok dengan versi saat ini. Silakan perbarui ke versi baru file gaya di situs web resmi (lbs.amap.com)"; +"O106184_101"="Pengecualian Penulisan"; +"O106184_102"="Nilai koordinat tidak valid"; +"O106184_103"="Amap tidak terpasang atau versinya sudah lama di perangkat seluler"; +"O106184_104"="Parameter tidak valid"; +"O106184_105"="Otentikasi luar negeri gagal"; +"O106184_106"="Fitur GLTF tidak didukung"; +"O106184_107"="Fitur peta topografi adalah kemampuan berbayar. Silakan hubungi tim penjualan untuk mengaktifkan fungsi ini."; +"O106184_108"="Ketidakcocokan perpustakaan"; +"O106184_109"="pemuatan lib gagal"; +"O106184_110"="Tanda tangan pengguna tidak diverifikasi"; +"O106184_111"="Kunci pengguna tidak benar atau kedaluwarsa"; +"O106184_112"="Layanan yang diminta tidak ada"; +"O106184_113"="Akses harian telah melebihi batas"; +"O106184_114"="Akses pengguna terlalu sering"; +"O106184_115"="IP pengguna tidak valid"; +"O106184_116"="Domain pengguna tidak valid"; +"O106184_117"="Kode keamanan MD5 pengguna tidak lolos verifikasi"; +"O106184_118"="Kunci yang diminta tidak sesuai dengan platform yang terikat"; +"O106184_119"="Batas akses IP terlampaui"; +"O106184_120"="Layanan tidak mendukung permintaan HTTPS"; +"O106184_121"="Izin tidak cukup, permintaan layanan ditolak"; +"O106184_122"="Pengembang menghapus kunci, dan kunci tidak dapat digunakan secara normal setelah dihapus"; +"O106184_123"="Kesalahan respons permintaan layanan"; +"O106184_124"="Mesin mengembalikan data yang tidak normal"; +"O106184_125"="Waktu tunggu koneksi permintaan server habis"; +"O106184_126"="Waktu habis membaca hasil layanan"; +"O106184_127"="Parameter permintaan tidak valid"; +"O106184_128"="Parameter yang diperlukan tidak ada"; +"O106184_129"="Protokol permintaan ilegal"; +"O106184_130"="Kesalahan tidak dikenal lainnya"; +"O106184_131"="Koneksi HTTP atau socket gagal - ConnectionException"; +"O106184_132"="Tidak ada kesalahan yang sesuai"; +"O106184_133"="Petunjuk"; +"O106184_134"="Aplikasi saat ini tidak memiliki izin yang diperlukan. + +Silakan ketuk \"Pengaturan\"-\"Izin\"- aktifkan izin yang dibutuhkan"; +"O106184_135"="Pengaturan"; +"O106184_136"="Tambahkan bangunan kustom"; +"O106184_137"="Ubah warna bangunan"; +"O106184_138"="Ubah tinggi bangunan"; +"O106184_139"="Hancurkan Lapisan Bangunan"; +"O106184_140"="Klik pada area heatmap untuk mendapatkan nilai panas"; +"O106184_141"="Cari"; +"O106184_142"="Silakan masukkan kata kunci"; +"O106184_143"="Peta"; +"O106184_144"="Atur rute"; +"O106184_145"="Mulai"; +"O106184_146"="Koreksi Lintasan"; +"O106184_147"="Hapus rute yang telah selesai"; +"O106184_148"="Koordinat AutoNavi"; +"O106184_149"="Koordinat GPS"; +"O106184_150"="Koordinat Baidu"; +"O106184_151"="Jarak total:"; +"O106184_152"="Tunggu:"; +"O106184_153"="Mulai koreksi lintasan"; +"O106184_154"="Hentikan koreksi jalur"; +"O106184_155"="Lujiazui"; +"O106184_156"="Zhongguancun"; +"O106184_157"="Aktifkan peta yang dipersonalisasi"; +"O106184_158"="Peta standar"; +"O106184_159"="Peta satelit"; +"O106184_160"="Mode malam"; +"O106184_161"="Mode Navigasi"; +"O106184_162"="Mode Bus"; +"O106184_163"="Mode Malam Navigasi"; +"O106184_164"="Silakan masukkan nama rute bus"; +"O106184_165"="Otorisasi peta internasional gagal, kunci saat ini tidak memiliki izin untuk menggunakan peta internasional, peta internasional tidak akan ditampilkan!"; +"O106184_166"="Hasil pencarian"; +"O106184_167"="Halaman sebelumnya"; +"O106184_168"="Halaman berikutnya"; +"O106184_169"="Atur properti Polygon"; +"O106184_170"="Pencarian ID"; +"O106184_171"="Pencarian Sekitar"; +"O106184_172"="Pencarian Nama Rute"; +"O106184_173"="Pencarian Lokal"; +"O106184_174"="Konversi ke sistem koordinat AutoNavi"; +"O106184_175"="Mode Gambar"; +"O106184_176"="Tambahkan tampilan persimpangan HD"; +"O106184_177"="Tambahkan papan tanda persimpangan yang diperbesar"; +"O106184_178"="Tambahkan tampilan perbesar persimpangan jj"; +"O106184_179"="Hapus"; +"O106184_180"="Geser marker1"; +"O106184_181"="Atur waktu kedatangan"; +"O106184_182"="Konfirmasi"; +"O106184_183"="Provinsi"; +"O106184_184"="Kota"; +"O106184_185"="Pencarian Poligon"; +"O106184_186"="Cari"; +"O106184_187"="Ketuk atau tekan lama peta"; +"O106184_188"="Pindahkan kamera"; +"O106184_189"="Peristiwa sentuh peta"; +"O106184_190"="Gerakan geser"; +"O106184_191"="Gerakan zoom"; +"O106184_192"="Gerakan miring"; +"O106184_193"="Gerakan putar"; +"O106184_194"="Tambahkan lubang ke lingkaran"; +"O106184_195"="Polygon tambahkan lubang"; +"O106184_196"="Berangkat"; +"O106184_197"="Atur Area Tampilan"; +"O106184_198"="Koordinat garis lintang dan bujur peta (Klik pada peta untuk mendapatkan koordinat)"; +"O106184_199"="Koordinat lintang dan bujur ke koordinat piksel layar"; +"O106184_200"="Konversi koordinat piksel layar ke garis lintang dan bujur"; +"O106184_201"="Lokasi"; +"O106184_202"="Distrik/Kabupaten"; +"O106184_203"="Ikuti"; +"O106184_204"="Putar"; +"O106184_205"="Putar Posisi"; +"O106184_206"="Ikuti tanpa memindahkan titik pusat"; +"O106184_207"="Tampilkan"; +"O106184_208"="Posisi rotasi tidak berpindah ke titik pusat"; +"O106184_209"="Lokasi logo"; +"O106184_210"="Kiri bawah"; +"O106184_211"="Tengah bawah"; +"O106184_212"="Rotasi tanpa berpindah ke titik pusat"; +"O106184_213"="Animasi Pertumbuhan"; +"O106184_214"="Animasi Pindah"; +"O106184_215"="Animasi Pernapasan"; +"O106184_216"="Level minimum default 3"; +"O106184_217"="Level maksimum default 19 dalam ruangan 20"; +"O106184_218"="Pengaturan"; +"O106184_219"="Reset"; +"O106184_220"="Tingkat zoom saat ini: "; +"O106184_221"="Peta Khusus"; +"O106184_222"="Contoh Polihedron"; +"O106184_223"="Semua kota"; +"O106184_224"="Manajemen unduhan"; +"O106184_225"="Beijing"; +"O106184_226"="Ukuran total"; +"O106184_227"="z-index penanda"; +"O106184_228"="z-index polyline"; +"O106184_229"="Bawah kanan"; +"O106184_230"="Cerah"; +"O106184_231"="Hari hujan"; +"O106184_232"="Hari bersalju"; +"O106184_233"="Kabut asap"; +"O106184_234"="Kustomisasi"; +"O106184_235"="Waktu masa depan"; +"O106184_236"="Rute"; +"O106184_237"="Silakan masukkan kata kunci pencarian"; +"O106184_238"="Silakan masukkan kata kunci"; +"O106184_239"="Silakan masukkan kota"; +"O106184_240"="Mulai pencarian"; +"O106184_241"="Halaman berikutnya"; +"O106184_242"="Atur properti Polyline"; +"O106184_243"="Bus antarkota"; +"O106184_244"="Tambahkan"; +"O106184_245"="Hapus"; +"O106184_246"="Stasiun pengisian bahan bakar"; +"O106184_247"="Bengkel Mobil"; +"O106184_248"="Toilet"; +"O106184_249"="Tangkapan layar peta"; +"O106184_250"="Tangkapan Layar Berkelanjutan"; +"O106184_251"="Tangkap gambar berukuran besar"; +"O106184_252"="Interval kueri pertama"; +"O106184_253"="Interval kueri"; +"O106184_254"="Deviasi yang diizinkan"; +"O106184_255"="Bagikan lokasi"; +"O106184_256"="Berbagi Perencanaan Rute"; +"O106184_257"="Berbagi POI"; +"O106184_258"="Berbagi Navigasi"; +"O106184_259"="Universitas Tsinghua"; +"O106184_260"="Alamat..."; +"O106184_261"="Lantai satu"; +"O106184_262"="Lantai dua"; +"O106184_263"="Lantai tiga"; +"O106184_264"="Buka TileOverlay"; +"O106184_265"="Waktu keberangkatan"; +"O106184_266"="Waktu kedatangan"; +"O106184_267"="Total jarak tempuh"; +"O106184_268"="zindex dari polygon"; +"O106184_269"="Dapatkan skala"; +"O106184_270"="Skala"; +"O106184_271"="Tampilan zoom dan lokasi"; +"O106184_272"="Tampilkan"; +"O106184_273"="Atur waktu kedatangan"; +"O106184_274"="Kompas"; +"O106184_275"="Lapisan Lokasi Saya"; +"O106184_276"="Real-time"; +"O106184_277"="Waktu Rilis"; +"O106184_278"="Prakiraan"; +"O106184_279"="Waktu Rilis"; +"O106184_280"="Metode ini tidak berlaku lagi. Silakan kunjungi situs web resmi (lbs.amap.com) untuk memperbarui file gaya versi baru dan gunakan setCustomMapStyleOptions"; +"O106184_281"="Saat membangun CameraPosition, lokasi (target) tidak boleh null"; +"O106184_282"="File gaya peta kustom yang saat ini digunakan tidak cocok dengan versi saat ini. Silakan perbarui ke versi baru file gaya di situs web resmi (lbs.amap.com)."; +"O106184_283"="kanan tengah"; +"O106184_284"="Autentikasi gagal. Kunci saat ini tidak memiliki izin untuk menggunakan tekstur khusus. Konten terkait tekstur khusus tidak akan ditampilkan!"; +"O106184_285"="Autentikasi peta medan gagal, kunci saat ini tidak memiliki izin untuk menggunakan peta medan, peta medan tidak akan ditampilkan!"; +"O106184_286"="Context adalah null, silakan gunakan MapsInitializer.initialize(Context paramContext) untuk mengatur Context sebelum memanggil peta"; +"O106184_287"="Kota yang relevan tidak ditemukan"; +"O106184_288"="Pengecualian jaringan"; +"O106184_289"="Tidak ada koneksi jaringan"; +"O106184_290"="negara"; +"O106184_291"="No. GS(2024)2108 | No. GS(2025)3454"; +"O106184_292"="No. GS (2023)4047"; +"O106184_293"="No. GS(2021)6352"; +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="kaki"; +"O106184_297"="mil"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_it.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_it.strings new file mode 100644 index 0000000..816875f --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_it.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="piedi"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ja.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ja.strings new file mode 100644 index 0000000..0739083 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ja.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="フィート"; +"O106184_297"="マイル"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ko.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ko.strings new file mode 100644 index 0000000..198311d --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ko.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="피트"; +"O106184_297"="마일"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ms.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ms.strings new file mode 100644 index 0000000..0e53685 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ms.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="kaki"; +"O106184_297"="batu"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_pt.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_pt.strings new file mode 100644 index 0000000..041d204 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_pt.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="pés"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ru.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ru.strings new file mode 100644 index 0000000..992e32f --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_ru.strings @@ -0,0 +1,4 @@ +"O106184_294"="м"; +"O106184_295"="км"; +"O106184_296"="фут"; +"O106184_297"="мили"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_th.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_th.strings new file mode 100644 index 0000000..da734c0 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_th.strings @@ -0,0 +1,4 @@ +"O106184_294"="เมตร"; +"O106184_295"="กม."; +"O106184_296"="ฟุต"; +"O106184_297"="ไมล์"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_tr.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_tr.strings new file mode 100644 index 0000000..2765f89 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_tr.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ft"; +"O106184_297"="mil"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_uk.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_uk.strings new file mode 100644 index 0000000..f7e0e80 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_uk.strings @@ -0,0 +1,4 @@ +"O106184_294"="м"; +"O106184_295"="км"; +"O106184_296"="фут"; +"O106184_297"="миля"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_vi.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_vi.strings new file mode 100644 index 0000000..d3fbc2d --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_vi.strings @@ -0,0 +1,4 @@ +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ft"; +"O106184_297"="dặm"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_zh-Hans.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_zh-Hans.strings new file mode 100644 index 0000000..56df08c --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_zh-Hans.strings @@ -0,0 +1,299 @@ +"O106184_01"="[MAMapKit] 要在iOS 11及以上版本使用后台定位服务, 需要实现mapViewRequireLocationAuth: 代理方法"; +"O106184_02"="[MAMapKit] 要在iOS 11及以上版本使用定位服务, 需要在Info.plist中添加NSLocationAlwaysAndWhenInUseUsageDescription和NSLocationWhenInUseUsageDescription字段。"; +"O106184_03"="[MAMapKit] 要在iOS 8及以上版本使用后台定位服务, 需要实现mapViewRequireLocationAuth: 代理方法"; +"O106184_04"="[MAMapKit] 在iOS 8及以上版本使用定位服务, 需要在Info.plist中添加NSLocationAlwaysUsageDescription或者NSLocationWhenInUseUsageDescription字段。"; +"O106184_05"="请求失败"; +"O106184_06"="MVT功能不支持"; +"O106184_07"="glTF功能不支持"; +"O106184_08"="该功能为计费能力,请联系商务进行功能开通"; +"O106184_09"="uri异常"; +"O106184_10"="已废弃, 调用不起任何作用。since 9.6.0"; +"O106184_11"="已废弃,since 7.9.0"; +"O106184_12"="已废弃,since 7.9.0,请使用MAHeatMapVectorOverlayRender中的alpha"; +"O106184_13"="已废弃,since 7.7.0,请使用MAGroundOverlayRenderer中的alpha"; +"O106184_14"="输入城市名称或者拼音"; +"O106184_15"="下载管理"; +"O106184_16"="城市列表"; +"O106184_17"="基本功能包+直辖市"; +"O106184_18"="港澳台地图"; +"O106184_19"="台湾省"; +"O106184_20"="离线地图"; +"O106184_21"="暂不支持下载"; +"O106184_22"="取消"; +"O106184_23"="删除"; +"O106184_24"="取消下载"; +"O106184_25"="暂停下载"; +"O106184_26"="更新"; +"O106184_27"="继续下载"; +"O106184_28"="等待中"; +"O106184_29"="下载中"; +"O106184_30"="暂停"; +"O106184_31"="已下载"; +"O106184_32"="有更新"; +"O106184_33"="没有找到相关城市"; +"O106184_34"="[MAMapKit] 无效的资源bundle,请检查AMap.bundle是否正确导入"; +"O106184_35"="[MAMapKit]: warning: 请检查导入的AMap.bundle是否正确"; +"O106184_36"="存在自定义styleData缓存,使用缓存"; +"O106184_37"="[MAMapKit]在线获取自定义样式失败"; +"O106184_38"="[MAMapKit]在线获取自定义样式失败, 返回数据为空。请检查styleid和key是否正确"; +"O106184_39"="线上styleData拉取成功"; +"O106184_40"="[MAMapKit] 解压样式zip失败"; +"O106184_41"="[MAMapKit] 样式文件错误"; +"O106184_42"="存在自定义styleExtraData缓存,使用缓存"; +"O106184_43"="线上styleExtraData拉取成功"; +"O106184_44"="[MAMapKit] 自定义资源文件解压失败"; +"O106184_45"="[MAMapKit] 要添加的delegate需实现MAMapViewDelegate"; +"O106184_46"="[MAMapKit] setWorldVectorMapLanguage方法已废弃,请使用setStyle:"; +"O106184_47"="当前位置"; +"O106184_48"="取图服务失败"; +"O106184_49"="警告"; +"O106184_50"="您当前使用的是测试版本地图,不能用于上线。如需上线请联系我们提供正式上线包"; +"O106184_51"="[MAMapKit] key鉴权失败"; +"O106184_52"="[MAMapKit]当前使用的自定义地图样式文件和目前版本不匹配,请到官网(lbs.amap.com)更新新版样式文件"; +"O106184_53"="GS (2023)4047号GS (2023)551号 | GS (2023)2175号"; +"O106184_54"="[MAMapKit] reloadMap执行失败,因为地图处于后台或 renderringDisabled = YES"; +"O106184_55"="已废弃,since 7.9.0,请使用renderringDisabled属性"; +"O106184_56"="已废弃,请使用takeSnapshotInRect:withCompletionBlock:方法 since 6.0.0"; +"O106184_57"="已废弃,使用AMapFoundation中关于坐标转换的接口"; +"O106184_58"="加载云控缓存"; +"O106184_59"="获取云控"; +"O106184_60"="初始化引擎失败"; +"O106184_61"="[MAMapKit] 解压资源res文件成功"; +"O106184_62"="[MAMapKit] 解压资源res文件失败"; +"O106184_63"="加载资源失败"; +"O106184_64"="北"; +"O106184_65"="[MAMapKit] 解压资源style文件成功"; +"O106184_66"="[MAMapKit] 解压资源style文件失败!"; +"O106184_67"="[MAMapKit] 请设置正确的style值."; +"O106184_68"="标题"; +"O106184_69"="内容"; +"O106184_70"="Key验证失败:["; +"O106184_71"="直辖市"; +"O106184_72"="香港"; +"O106184_73"="澳门"; +"O106184_74"="港澳"; +"O106184_75"="解压中"; +"O106184_76"="北京"; +"O106184_77"="总大小"; +"O106184_78"="暂停中"; +"O106184_79"="下载失败"; +"O106184_80"="已下载-有更新"; +"O106184_81"="下载出现异常"; +"O106184_82"="检查更新"; +"O106184_83"="北京市"; +"O106184_84"="没有该城市"; +"O106184_85"="轨迹点太少或距离太近,轨迹纠偏失败"; +"O106184_86"="定位超时"; +"O106184_87"="纠偏成功"; +"O106184_88"="IO 操作异常 - IOException"; +"O106184_89"="socket 连接异常 - SocketException"; +"O106184_90"="socket 连接超时 - SocketTimeoutException"; +"O106184_91"="无效的参数 - IllegalArgumentException"; +"O106184_92"="空指针异常 - NullPointException"; +"O106184_93"="url异常 - MalformedURLException"; +"O106184_94"="未知主机 - UnKnowHostException"; +"O106184_95"="服务器连接失败 - UnknownServiceException"; +"O106184_96"="协议解析错误 - ProtocolException"; +"O106184_97"="http连接失败 - ConnectionException"; +"O106184_98"="未知的错误"; +"O106184_99"="key鉴权失败"; +"O106184_100"="[MAMapKit]当前使用的自定义地图样式文件和目前版本不匹配,请到官网(lbs.amap.com)更新新版样式文件"; +"O106184_101"="不可写入异常"; +"O106184_102"="非法坐标值"; +"O106184_103"="移动设备上未安装高德地图或高德地图版本较旧"; +"O106184_104"="非法参数"; +"O106184_105"="海外鉴权失败"; +"O106184_106"="GLTF功能不支持"; +"O106184_107"="地形图功能为计费能力,请联系商务进行功能开通。"; +"O106184_108"="lib不匹配"; +"O106184_109"="lib加载失败"; +"O106184_110"="用户签名未通过"; +"O106184_111"="用户key不正确或过期"; +"O106184_112"="请求服务不存在"; +"O106184_113"="访问已超出日访问量"; +"O106184_114"="用户访问过于频繁"; +"O106184_115"="用户IP无效"; +"O106184_116"="用户域名无效"; +"O106184_117"="用户MD5安全码未通过"; +"O106184_118"="请求key与绑定平台不符"; +"O106184_119"="IP访问超限"; +"O106184_120"="服务不支持https请求"; +"O106184_121"="权限不足,服务请求被拒绝"; +"O106184_122"="开发者删除了key,key被删除后无法正常使用"; +"O106184_123"="请求服务响应错误"; +"O106184_124"="引擎返回数据异常"; +"O106184_125"="服务端请求链接超时"; +"O106184_126"="读取服务结果超时"; +"O106184_127"="请求参数非法"; +"O106184_128"="缺少必填参数"; +"O106184_129"="请求协议非法"; +"O106184_130"="其他未知错误"; +"O106184_131"="http或socket连接失败 - ConnectionException"; +"O106184_132"="没有对应的错误"; +"O106184_133"="提示"; +"O106184_134"="当前应用缺少必要权限。 + +请点击\"设置\"-\"权限\"-打开所需权限"; +"O106184_135"="设置"; +"O106184_136"="添加自定义建筑"; +"O106184_137"="改变建筑颜色"; +"O106184_138"="改变建筑高度"; +"O106184_139"="销毁建筑图层"; +"O106184_140"="单击热力区域可以获取热力值"; +"O106184_141"="搜索"; +"O106184_142"="请输入关键字"; +"O106184_143"="地图"; +"O106184_144"="设置路线"; +"O106184_145"="开始"; +"O106184_146"="轨迹纠偏"; +"O106184_147"="清除已完成线路"; +"O106184_148"="高德坐标"; +"O106184_149"="GPS坐标"; +"O106184_150"="百度坐标"; +"O106184_151"="总距离:"; +"O106184_152"="等 待:"; +"O106184_153"="开始轨迹纠偏"; +"O106184_154"="停止轨迹纠偏"; +"O106184_155"="陆家嘴"; +"O106184_156"="中关村"; +"O106184_157"="开启个性化地图"; +"O106184_158"="标准地图"; +"O106184_159"="卫星地图"; +"O106184_160"="夜间模式"; +"O106184_161"="导航模式"; +"O106184_162"="公交模式"; +"O106184_163"="导航夜间模式"; +"O106184_164"="请输入公交线路名"; +"O106184_165"="国际图鉴权失败,当前key没有国际图的使用权限,国际图,将不会呈现!"; +"O106184_166"="搜索结果"; +"O106184_167"="前一页"; +"O106184_168"="下一页"; +"O106184_169"="设置Polygon属性"; +"O106184_170"="ID检索"; +"O106184_171"="周边检索"; +"O106184_172"="线路名称查询"; +"O106184_173"="本地检索"; +"O106184_174"="转换为高德坐标系"; +"O106184_175"="图片模式"; +"O106184_176"="添加路口放大图HD"; +"O106184_177"="添加路口放大图路牌"; +"O106184_178"="添加路口放大图jj"; +"O106184_179"="移除"; +"O106184_180"="拖动marker1"; +"O106184_181"="设置到达时间"; +"O106184_182"="确定"; +"O106184_183"="省"; +"O106184_184"="市"; +"O106184_185"="多边形检索"; +"O106184_186"="搜索"; +"O106184_187"="点击或者长按地图"; +"O106184_188"="移动camera"; +"O106184_189"="地图触摸事件"; +"O106184_190"="滑动手势"; +"O106184_191"="缩放手势"; +"O106184_192"="倾斜手势"; +"O106184_193"="旋转手势"; +"O106184_194"="Circle添加洞"; +"O106184_195"="Polygon添加洞"; +"O106184_196"="出发"; +"O106184_197"="设置显示区域"; +"O106184_198"="地图经纬度坐标(单击地图获取经纬度坐标)"; +"O106184_199"="经纬度转屏幕像素坐标"; +"O106184_200"="屏幕像素坐标转经纬度"; +"O106184_201"="定位"; +"O106184_202"="区/县"; +"O106184_203"="追随"; +"O106184_204"="旋转"; +"O106184_205"="旋转位置"; +"O106184_206"="跟随不移动中心点"; +"O106184_207"="展示"; +"O106184_208"="旋转位置不移动到中心点"; +"O106184_209"="logo位置"; +"O106184_210"="左下"; +"O106184_211"="底部居中"; +"O106184_212"="旋转不移动到中心点"; +"O106184_213"="生长动画"; +"O106184_214"="移动动画"; +"O106184_215"="呼吸动画"; +"O106184_216"="最小级别 默认 3"; +"O106184_217"="最大级别 默认 19 室内 20"; +"O106184_218"="设置"; +"O106184_219"="重置"; +"O106184_220"="当前缩放级别为:"; +"O106184_221"="自定义地图"; +"O106184_222"="多面体示例"; +"O106184_223"="所有城市"; +"O106184_224"="下载管理"; +"O106184_225"="北京"; +"O106184_226"="总大小"; +"O106184_227"="marker的zindex"; +"O106184_228"="polyline的zindex"; +"O106184_229"="右下"; +"O106184_230"="晴天"; +"O106184_231"="雨天"; +"O106184_232"="雪天"; +"O106184_233"="雾霾"; +"O106184_234"="自定义"; +"O106184_235"="未来用时"; +"O106184_236"="路线"; +"O106184_237"="请输入搜索关键字"; +"O106184_238"="请输入关键字"; +"O106184_239"="请输入城市"; +"O106184_240"="开始搜索"; +"O106184_241"="下一页"; +"O106184_242"="设置Polyline属性"; +"O106184_243"="跨城公交"; +"O106184_244"="添加"; +"O106184_245"="移除"; +"O106184_246"="加油站"; +"O106184_247"="汽修店"; +"O106184_248"="厕所"; +"O106184_249"="地图截屏"; +"O106184_250"="持续截图"; +"O106184_251"="截超大图"; +"O106184_252"="首个查询间隔"; +"O106184_253"="查询时间间隔"; +"O106184_254"="允许偏差"; +"O106184_255"="位置分享"; +"O106184_256"="路径规划分享"; +"O106184_257"="POI分享"; +"O106184_258"="导航分享"; +"O106184_259"="清华大学"; +"O106184_260"="地址..."; +"O106184_261"="一楼"; +"O106184_262"="二楼"; +"O106184_263"="三楼"; +"O106184_264"="打开TileOverlay"; +"O106184_265"="出发时间"; +"O106184_266"="到达时间"; +"O106184_267"="全部里程"; +"O106184_268"="polygon的zindex"; +"O106184_269"="获取比例尺"; +"O106184_270"="比例尺"; +"O106184_271"="zoom显示及位置"; +"O106184_272"="显示"; +"O106184_273"="设定到达时间"; +"O106184_274"="指南针"; +"O106184_275"="我的位置图层"; +"O106184_276"="实时"; +"O106184_277"="发布时间"; +"O106184_278"="预报"; +"O106184_279"="发布时间"; +"O106184_280"="该方法已无效,请到官网(lbs.amap.com)更新新版样式文件并使用setCustomMapStyleOptions"; +"O106184_281"="构建CameraPosition时,位置(target)不能为null"; +"O106184_282"="当前使用的自定义地图样式文件和目前版本不匹配,请到官网(lbs.amap.com)更新新版样式文件"; +"O106184_283"="右中"; +"O106184_284"="鉴权失败,当前key没有自定义纹理的使用权限,自定义纹理相关内容,将不会呈现!"; +"O106184_285"="地形图鉴权失败,当前key没有地形图的使用权限,地形图,将不会呈现!"; +"O106184_286"="Context 为 null 请在地图调用之前 使用 MapsInitializer.initialize(Context paramContext) 来设置Context"; +"O106184_287"="未找到相关城市"; +"O106184_288"="网络异常"; +"O106184_289"="无网络连接"; +"O106184_290"="国"; +"O106184_291"="GS(2024)2108号 | GS(2025)3454号"; +"O106184_292"="GS (2023)4047号"; +"O106184_293"="GS(2021)6352号"; +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ft"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_zh-Hant-HK.strings b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_zh-Hant-HK.strings new file mode 100644 index 0000000..c686bbe --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/localization.bundle/Localizable_zh-Hant-HK.strings @@ -0,0 +1,298 @@ +"O106184_01"="[MAMapKit] 要在iOS 11及以上版本使用背景定位服務, 需要實現mapViewRequireLocationAuth: 代理方法"; +"O106184_02"="[MAMapKit] 要在 iOS 11 及以上版本使用定位服務,需要在 Info.plist 中添加 NSLocationAlwaysAndWhenInUseUsageDescription 和 NSLocationWhenInUseUsageDescription 字段。"; +"O106184_03"="[MAMapKit] 要在iOS 8及以上版本使用後台定位服務, 需要實作mapViewRequireLocationAuth: 代理方法"; +"O106184_04"="[MAMapKit] 在iOS 8及以上版本使用定位服務,需要在Info.plist中加入NSLocationAlwaysUsageDescription或者NSLocationWhenInUseUsageDescription欄位。"; +"O106184_05"="請求失敗"; +"O106184_06"="MVT功能不支援"; +"O106184_07"="glTF功能不支援"; +"O106184_08"="此功能為計費能力,請聯繫商務進行功能開通"; +"O106184_09"="URI異常"; +"O106184_10"="已棄用,調用不起任何作用。since 9.6.0"; +"O106184_11"="已廢棄,since 7.9.0"; +"O106184_12"="已廢棄,since 7.9.0,請使用MAHeatMapVectorOverlayRender中的alpha"; +"O106184_13"="已廢棄,since 7.7.0,請使用 MAGroundOverlayRenderer 中的 alpha"; +"O106184_14"="輸入城市名稱或者拼音"; +"O106184_15"="下載管理"; +"O106184_16"="城市列表"; +"O106184_17"="基本功能包+直轄市"; +"O106184_18"="港澳台地圖"; +"O106184_19"="台灣省"; +"O106184_20"="離線地圖"; +"O106184_21"="暫不支持下载"; +"O106184_22"="取消"; +"O106184_23"="刪除"; +"O106184_24"="取消下載"; +"O106184_25"="暫停下載"; +"O106184_26"="更新"; +"O106184_27"="繼續下載"; +"O106184_28"="等待中"; +"O106184_29"="正在下載"; +"O106184_30"="暫停"; +"O106184_31"="已下載"; +"O106184_32"="有更新"; +"O106184_33"="沒有找到相關城市"; +"O106184_34"="[MAMapKit] 无效的资源bundle,请检查AMap.bundle是否正确导入"; +"O106184_35"="[MAMapKit]: warning: 請檢查導入的AMap.bundle是否正確"; +"O106184_36"="存在自訂styleData緩存,使用緩存"; +"O106184_37"="[MAMapKit]線上取得自訂樣式失敗"; +"O106184_38"="[MAMapKit]線上獲取自訂樣式失敗,返回數據為空。請檢查styleid和key是否正確"; +"O106184_39"="線上styleData拉取成功"; +"O106184_40"="[MAMapKit] 解壓樣式zip失敗"; +"O106184_41"="[MAMapKit] 樣式檔案錯誤"; +"O106184_42"="存在自訂styleExtraData緩存,使用緩存"; +"O106184_43"="線上styleExtraData拉取成功"; +"O106184_44"="[MAMapKit] 自訂資源檔案解壓失敗"; +"O106184_45"="[MAMapKit] 要添加的delegate需實現MAMapViewDelegate"; +"O106184_46"="[MAMapKit] setWorldVectorMapLanguage方法已棄用,請使用setStyle:"; +"O106184_47"="目前位置"; +"O106184_48"="取圖服務失敗"; +"O106184_49"="警告"; +"O106184_50"="您目前使用的是測試版本地圖,不能用作上線。如需上線請聯絡我們提供正式上線包"; +"O106184_51"="[MAMapKit] key驗證失敗"; +"O106184_52"="[MAMapKit]當前使用的自訂地圖樣式檔案和目前版本不相符,請到官網(lbs.amap.com)更新新版樣式檔案"; +"O106184_53"="GS (2023)4047號GS (2023)551號 | GS (2023)2175號"; +"O106184_54"="[MAMapKit] reloadMap執行失敗,因為地圖處於後台或 renderringDisabled = YES"; +"O106184_55"="已棄用,since 7.9.0,請使用renderringDisabled屬性"; +"O106184_56"="已廢棄,請使用takeSnapshotInRect:withCompletionBlock:方法 since 6.0.0"; +"O106184_57"="已廢棄,使用AMapFoundation中關於坐標轉換的接口"; +"O106184_58"="載入雲控緩存"; +"O106184_59"="取得雲控"; +"O106184_60"="初始化引擎失敗"; +"O106184_61"="[MAMapKit] 解壓資源res檔案成功"; +"O106184_62"="[MAMapKit] 解壓資源res檔案失敗"; +"O106184_63"="載入資源失敗"; +"O106184_64"="北"; +"O106184_65"="[MAMapKit] 解壓資源style檔案成功"; +"O106184_66"="[MAMapKit] 解壓資源style檔案失敗!"; +"O106184_67"="[MAMapKit] 解壓資源style檔案失敗!"; +"O106184_68"="標題"; +"O106184_69"="內容"; +"O106184_70"="內容"; +"O106184_71"="直轄市"; +"O106184_72"="香港"; +"O106184_73"="澳門"; +"O106184_74"="港澳"; +"O106184_75"="解壓中"; +"O106184_76"="北京"; +"O106184_77"="總大小"; +"O106184_78"="暫停中"; +"O106184_79"="下載失敗"; +"O106184_80"="已下載-有更新"; +"O106184_81"="下載出現異常"; +"O106184_82"="檢查更新"; +"O106184_83"="北京市"; +"O106184_84"="沒有該城市"; +"O106184_85"="軌跡點太少或距離太近,軌跡糾偏失敗"; +"O106184_86"="定位超時"; +"O106184_87"="糾偏成功"; +"O106184_88"="IO 操作異常 - IOException"; +"O106184_89"="socket 連接異常 - SocketException"; +"O106184_90"="socket 連線超時 - SocketTimeoutException"; +"O106184_91"="无效的参数 - IllegalArgumentException"; +"O106184_92"="空指標異常 - NullPointException"; +"O106184_93"="URL異常 - MalformedURLException"; +"O106184_94"="未知主機 - UnKnowHostException"; +"O106184_95"="伺服器連接失敗 - UnknownServiceException"; +"O106184_96"="協議解析錯誤 - ProtocolException"; +"O106184_97"="http連接失敗 - ConnectionException"; +"O106184_98"="未知的錯誤"; +"O106184_99"="key鑑權失敗"; +"O106184_100"="[MAMapKit]當前使用的自訂地圖樣式檔案和目前版本不相符,請到官網(lbs.amap.com)更新新版樣式檔案"; +"O106184_101"="不可寫入異常"; +"O106184_102"="非法座標值"; +"O106184_103"="移動裝置上未安裝高德地圖或高德地圖版本較舊"; +"O106184_104"="非法參數"; +"O106184_105"="海外鑑權失敗"; +"O106184_106"="GLTF功能不支持"; +"O106184_107"="地形圖功能為計費能力,請聯繫商務進行功能開通。"; +"O106184_108"="lib不匹配"; +"O106184_109"="lib載入失敗"; +"O106184_110"="用戶簽名未通過"; +"O106184_111"="用戶key不正確或過期"; +"O106184_112"="請求服務不存在"; +"O106184_113"="訪問已超出日訪問量"; +"O106184_114"="用戶訪問過於頻繁"; +"O106184_115"="用戶IP無效"; +"O106184_116"="用戶域名無效"; +"O106184_117"="用戶MD5安全碼未通過"; +"O106184_118"="請求key與綁定平台不符"; +"O106184_119"="IP訪問超限"; +"O106184_120"="服務不支持https請求"; +"O106184_121"="權限不足,服務請求被拒絕"; +"O106184_122"="開發者刪除了key,key被刪除後無法正常使用"; +"O106184_123"="請求服務回應錯誤"; +"O106184_124"="引擎返回數據異常"; +"O106184_125"="伺服器請求連結逾時"; +"O106184_126"="讀取服務結果超時"; +"O106184_127"="請求參數非法"; +"O106184_128"="缺少必填參數"; +"O106184_129"="請求協議非法"; +"O106184_130"="其他未知錯誤"; +"O106184_131"="http或socket連接失敗 - ConnectionException"; +"O106184_132"="沒有對應的錯誤"; +"O106184_133"="提示"; +"O106184_134"="應用程式目前缺少必要的權限。 +請點擊\"設定\"-\"權限\"-啟用所需的權限"; +"O106184_135"="設定"; +"O106184_136"="添加自訂建築"; +"O106184_137"="更改建築顏色"; +"O106184_138"="改變建築高度"; +"O106184_139"="銷毀建築圖層"; +"O106184_140"="點擊熱力區域可以獲取熱力值"; +"O106184_141"="搜尋"; +"O106184_142"="請輸入關鍵字"; +"O106184_143"="地圖"; +"O106184_144"="設定路線"; +"O106184_145"="開始"; +"O106184_146"="軌跡修正"; +"O106184_147"="清除已完成路線"; +"O106184_148"="高德座標"; +"O106184_149"="GPS座標"; +"O106184_150"="百度坐标"; +"O106184_151"="總距離:"; +"O106184_152"="等 待:"; +"O106184_153"="開始軌跡糾偏"; +"O106184_154"="停止軌跡糾偏"; +"O106184_155"="陸家嘴"; +"O106184_156"="中關村"; +"O106184_157"="開啟個人化地圖"; +"O106184_158"="標準地圖"; +"O106184_159"="衛星圖"; +"O106184_160"="夜間模式"; +"O106184_161"="導航模式"; +"O106184_162"="巴士模式"; +"O106184_163"="導航夜間模式"; +"O106184_164"="請輸入巴士路線名稱"; +"O106184_165"="國際圖鑑權失敗,當前key沒有國際圖的使用權限,國際圖,將不會呈現!"; +"O106184_166"="搜尋結果"; +"O106184_167"="前一頁"; +"O106184_168"="下一頁"; +"O106184_169"="設定Polygon屬性"; +"O106184_170"="ID搜尋"; +"O106184_171"="周邊搜尋"; +"O106184_172"="路線名稱查詢"; +"O106184_173"="本地搜尋"; +"O106184_174"="轉換為高德座標系"; +"O106184_175"="圖片模式"; +"O106184_176"="添加路口放大圖HD"; +"O106184_177"="添加路口放大圖路牌"; +"O106184_178"="添加路口放大圖jj"; +"O106184_179"="移除"; +"O106184_180"="拖動marker1"; +"O106184_181"="設定到達時間"; +"O106184_182"="確定"; +"O106184_183"="省"; +"O106184_184"="市"; +"O106184_185"="多邊形檢索"; +"O106184_186"="搜尋"; +"O106184_187"="點擊或者長按地圖"; +"O106184_188"="移動camera"; +"O106184_189"="地圖觸摸事件"; +"O106184_190"="滑動手势"; +"O106184_191"="縮放動作"; +"O106184_192"="傾斜手勢"; +"O106184_193"="旋轉手势"; +"O106184_194"="圓形添加洞"; +"O106184_195"="Polygon添加洞"; +"O106184_196"="出發"; +"O106184_197"="設定顯示區域"; +"O106184_198"="地圖經緯度座標(點擊地圖取得經緯度座標)"; +"O106184_199"="經緯度轉屏幕像素座標"; +"O106184_200"="屏幕像素座標轉經緯度"; +"O106184_201"="定位"; +"O106184_202"="區/縣"; +"O106184_203"="追隨"; +"O106184_204"="旋轉"; +"O106184_205"="旋轉位置"; +"O106184_206"="跟隨不移動中心點"; +"O106184_207"="展示"; +"O106184_208"="旋轉位置不移動到中心點"; +"O106184_209"="logo位置"; +"O106184_210"="左下"; +"O106184_211"="底部居中"; +"O106184_212"="旋轉不移動到中心點"; +"O106184_213"="生長動畫"; +"O106184_214"="移動動畫"; +"O106184_215"="呼吸動畫"; +"O106184_216"="最小級別 默認 3"; +"O106184_217"="最大級別 默認 19 室內 20"; +"O106184_218"="設定"; +"O106184_219"="重置"; +"O106184_220"="當前縮放級別為:"; +"O106184_221"="自訂地圖"; +"O106184_222"="多面體示例"; +"O106184_223"="所有城市"; +"O106184_224"="下載管理"; +"O106184_225"="北京"; +"O106184_226"="總大小"; +"O106184_227"="標記的zindex"; +"O106184_228"="polyline的zindex"; +"O106184_229"="右下"; +"O106184_230"="晴天"; +"O106184_231"="雨天"; +"O106184_232"="雪天"; +"O106184_233"="霧霾"; +"O106184_234"="自訂"; +"O106184_235"="未來用時"; +"O106184_236"="路線"; +"O106184_237"="請輸入搜索關鍵字"; +"O106184_238"="請輸入關鍵字"; +"O106184_239"="請輸入城市"; +"O106184_240"="開始搜索"; +"O106184_241"="下一頁"; +"O106184_242"="設定Polyline屬性"; +"O106184_243"="跨城巴士"; +"O106184_244"="新增"; +"O106184_245"="移除"; +"O106184_246"="油站"; +"O106184_247"="汽車維修店"; +"O106184_248"="洗手間"; +"O106184_249"="地圖截屏"; +"O106184_250"="持續截圖"; +"O106184_251"="截超大圖"; +"O106184_252"="首次查詢間隔"; +"O106184_253"="查詢時間間隔"; +"O106184_254"="允許偏差"; +"O106184_255"="位置分享"; +"O106184_256"="路線規劃分享"; +"O106184_257"="POI分享"; +"O106184_258"="導航分享"; +"O106184_259"="香港中文大學"; +"O106184_260"="地址..."; +"O106184_261"="一樓"; +"O106184_262"="二樓"; +"O106184_263"="三樓"; +"O106184_264"="開啟TileOverlay"; +"O106184_265"="出發時間"; +"O106184_266"="到達時間"; +"O106184_267"="全部里程"; +"O106184_268"="polygon的zindex"; +"O106184_269"="取得比例尺"; +"O106184_270"="比例尺"; +"O106184_271"="zoom顯示及位置"; +"O106184_272"="顯示"; +"O106184_273"="設定到達時間"; +"O106184_274"="指南針"; +"O106184_275"="我的位置圖層"; +"O106184_276"="實時"; +"O106184_277"="發佈時間"; +"O106184_278"="預報"; +"O106184_279"="發佈時間"; +"O106184_280"="此方法已失效,請前往官網(lbs.amap.com)更新新版樣式檔案並使用setCustomMapStyleOptions"; +"O106184_281"="構建CameraPosition時,位置(target)不能為null"; +"O106184_282"="目前使用嘅自訂地圖樣式檔案同現時版本唔匹配,請到官網(lbs.amap.com)更新新版樣式檔案"; +"O106184_283"="右中"; +"O106184_284"="驗證失敗,當前 key 沒有自訂材質的使用權限,自訂材質相關內容將不會呈現!"; +"O106184_285"="地形圖鑑權失敗,當前key沒有地形圖的使用權限,地形圖,將不會呈現!"; +"O106184_286"="Context 為 null 請在地圖調用之前 使用 MapsInitializer.initialize(Context paramContext) 來設定Context"; +"O106184_287"="未找到相關城市"; +"O106184_288"="網絡異常"; +"O106184_289"="無網絡連接"; +"O106184_290"="國"; +"O106184_291"="GS(2024)2108號 | GS(2025)3454號"; +"O106184_292"="GS (2023)4047號"; +"O106184_293"="GS(2021)6352號"; +"O106184_294"="m"; +"O106184_295"="km"; +"O106184_296"="ft"; +"O106184_297"="mi"; diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/offline/offlinePackage.plist b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/offline/offlinePackage.plist new file mode 100644 index 0000000..d6701a8 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/offline/offlinePackage.plist differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/res.ck b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/res.ck new file mode 100644 index 0000000..1f81e6c --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/res.ck @@ -0,0 +1 @@ +abbbd15ca7eccc7c038af317f3aa4eec \ No newline at end of file diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/res.zip b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/res.zip new file mode 100644 index 0000000..7774841 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/res.zip differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/back.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/back.png new file mode 100644 index 0000000..3593e2a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/back.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/bottom.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/bottom.png new file mode 100644 index 0000000..7275992 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/bottom.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/front.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/front.png new file mode 100644 index 0000000..3497949 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/front.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/left.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/left.png new file mode 100644 index 0000000..3728f1a Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/left.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/right.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/right.png new file mode 100644 index 0000000..cdc69ed Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/right.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/top.png b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/top.png new file mode 100644 index 0000000..e9488ae Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/AMap.bundle/skybox-1/top.png differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnimatedAnnotation.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnimatedAnnotation.h new file mode 100644 index 0000000..c39b9f6 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnimatedAnnotation.h @@ -0,0 +1,78 @@ +// +// MAAnimatedAnnotation.h +// MAMapKit +// +// Created by shaobin on 16/12/8. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import "MAPointAnnotation.h" +#import "MAAnnotationMoveAnimation.h" + +///支持动画效果的点标注 +///Point annotation with animation support +@interface MAAnimatedAnnotation : MAPointAnnotation + +///移动方向. 正北为0度,顺时针方向。即正东90,正南180,正西270。since 4.5.0 +///Movement direction. 0 degrees is true north, clockwise. That is, 90 degrees is east, 180 degrees is south, and 270 degrees is west. since 4.5.0 +@property (nonatomic, assign) CLLocationDirection movingDirection; + +/** + @brief 添加移动动画, 第一个添加的动画以当前coordinate为起始点,沿传入的coordinates点移动,否则以上一个动画终点为起始点. since 4.5.0 + Add a movement animation, the first added animation starts from the current coordinate and moves along the passed coordinates, otherwise it starts from the endpoint of the previous animation. since 4.5.0 + @param coordinates c数组,由调用者负责coordinates指向内存的管理 + C array, the caller is responsible for the management of the memory pointed to by coordinates + @param count coordinates数组大小 + coordinates array size + @param duration 动画时长,0或<0为无动画 + animation duration, 0 or <0 means no animation + @param name 名字,如不指定可传nil + name, can pass nil if not specified + @param completeCallback 动画完成回调,isFinished: 动画是否执行完成 + animation completion callback, isFinished: whether the animation is completed + */ +- (MAAnnotationMoveAnimation *)addMoveAnimationWithKeyCoordinates:(CLLocationCoordinate2D *)coordinates + count:(NSUInteger)count + withDuration:(CGFloat)duration + withName:(NSString *)name + completeCallback:(void(^)(BOOL isFinished))completeCallback; + +/** + @brief 添加移动动画, 第一个添加的动画以当前coordinate为起始点,沿传入的coordinates点移动,否则以上一个动画终点为起始点. since 5.4.0 + Add a movement animation, the first added animation starts from the current coordinate and moves along the passed coordinates, otherwise it starts from the endpoint of the previous animation. since 5.4.0 + @param coordinates c数组,由调用者负责coordinates指向内存的管理 + C array, the caller is responsible for the management of the memory pointed to by coordinates + @param count coordinates数组大小 + coordinates array size + @param duration 动画时长,0或<0为无动画 + animation duration, 0 or <0 means no animation + @param name 名字,如不指定可传nil + name, can pass nil if not specified + @param completeCallback 动画完成回调,isFinished: 动画是否执行完成 + animation completion callback, isFinished: whether the animation is completed + @param stepCallback 动画每一帧回调 + Animation frame callback + */ +- (MAAnnotationMoveAnimation *)addMoveAnimationWithKeyCoordinates:(CLLocationCoordinate2D *)coordinates + count:(NSUInteger)count + withDuration:(CGFloat)duration + withName:(NSString *)name + completeCallback:(void(^)(BOOL isFinished))completeCallback + stepCallback:(void(^)(MAAnnotationMoveAnimation* currentAni))stepCallback; + +/** + * @brief 获取所有未完成的移动动画, 返回数组内为MAAnnotationMoveAnimation对象. since 4.5.0 + * Retrieve all unfinished move animations, returning an array of MAAnnotationMoveAnimation objects. since 4.5.0 + * @return 返回所有移动动画,数组内元素类型为 MAAnnotationMoveAnimation + * Return all move animations, the elements in the array are of type MAAnnotationMoveAnimation + */ +- (NSArray *)allMoveAnimations; + +/** + * @brief 设置需要开始动画,自定义其他类型动画时需要调用. since 6.0.0 + * Settings require starting animation, which needs to be called when customizing other types of animations. since 6.0.0 + */ +- (void)setNeedsStart; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotation.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotation.h new file mode 100755 index 0000000..e9a121f --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotation.h @@ -0,0 +1,89 @@ +// +// MAAnnotation.h +// MAMapKit +// +// Created by yin cai on 11-12-13. +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import +#import +#import "MAGeometry.h" + +///该类为标注点的protocol,提供了标注类的基本信息函数 +///This class is the protocol for annotation points, providing basic information functions for the annotation class +@protocol MAAnnotation + +///标注view中心坐标 +///the center coordinates of the annotation view +@property (nonatomic, readonly) CLLocationCoordinate2D coordinate; + +@optional + +///annotation标题 +///the annotation title +@property (nonatomic, copy) NSString *title; + +///annotation副标题 +///the annotation subtitle +@property (nonatomic, copy) NSString *subtitle; + +/** + * @brief 设置标注的坐标,在拖拽时会被调用. + * Set the coordinates of the annotation, which will be called during dragging + * @param newCoordinate 新的坐标值 + * New coordinate values + */ +- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate; + +///annotation海拔高度,单位米,默认0 +///the annotation altitude, in meters, default is 0 +@property (nonatomic, assign) double altitude; +@end + +/** + * 支持动画需要实现的协议. since 4.5.0 + */ +/** + * Protocol that needs to be implemented to support animation. since 4.5.0 + */ +@protocol MAAnimatableAnnotation + +@required +/** + * @brief 动画帧更新回调接口,实现者可在内部做更新处理,如更新coordinate. (since 4.5.0) + * Animation frame update callback interface, implementers can perform update processing internally, such as updating coordinates. (since 4.5.0) + * @param timeDelta 时间步长,单位秒 + * time step, unit seconds + */ +- (void)step:(CGFloat)timeDelta; + +/** + * @brief 动画是否已完成. 通过此方法判断是否需要将动画annotation移出渲染执行过程。(since 4.5.0) + * Whether the animation has been completed. This method is used to determine whether the animation annotation needs to be removed from the rendering execution process.(since 4.5.0) + * @return YES动画已完成,NO没有完成 + * YES animation is completed, NO not completed + */ +- (BOOL)isAnimationFinished; + +/** + * @brief 动画是否可以开始. 通过此方法判断是否需要将动画annotation加入渲染过程,已经start且尚未finish的动画标注才会调用step方法。(since 6.0.0) + * Whether the animation can start. This method determines whether the animation annotation needs to be added to the rendering process. Only animations that have started but not yet finished will call the step method.(since 6.0.0) + * @return YES 可以开始,NO 尚未开始。 + * YES can start, NO has not started yet. + */ +- (BOOL)shouldAnimationStart; + +@optional +/** + * @brief 动画更新时调用此接口,获取annotationView的旋转角度,不实现默认为0. (since 4.5.0) + * This interface is called when the animation is updated to get the rotation angle of the annotationView. If not implemented, it defaults to 0. (since 4.5.0) + * @return 当前annotation的旋转角度,正北为0度,顺时针方向。即正东90,正南180,正西270。 + * The rotation angle of the current annotation, with true north as 0 degrees, in a clockwise direction. That is, 90 degrees for due east, 180 degrees for due south, and 270 degrees for due west. + */ +- (CLLocationDirection)rotateDegree; + + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotationMoveAnimation.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotationMoveAnimation.h new file mode 100644 index 0000000..ac4a207 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotationMoveAnimation.h @@ -0,0 +1,81 @@ +// +// MAAnnotationMoveAnimation.h +// MAMapKit +// +// Created by shaobin on 16/11/21. +// Copyright © 2016 Amap. All rights reserved. +// + + +#import "MAConfig.h" +#import +#import "MAAnnotation.h" + +///annotation移动动画. since 4.5.0 +///annotation animation since 4.5.0 +@interface MAAnnotationMoveAnimation : NSObject + +/** + * @brief 获取动画名字 + * get animation name + * @return 添加动画时传入的名字 + * name passed when adding animation + */ +- (NSString *)name; + +/** + * @brief 获取经纬度坐标点数组 + * get latitude and longitude coordinate point array + * @return 返回经纬度坐标点数组 + * return latitude and longitude coordinate point array + */ +- (CLLocationCoordinate2D *)coordinates; + +/** + * @brief 获取coordinates数组内坐标点个数 + * get number of coordinate points in the coordinates array + * @return coordinates数组内坐标点个数 + * Number of coordinate points in the coordinates array + */ +- (NSUInteger)count; + +/** + * @brief 获取动画时长 + * Get animation duration + * @return 动画时长 + * Animation duration + */ +- (CGFloat)duration; + +/** + * @brief 获取动画已执行的时长 + * Get elapsed animation duration + * @return 动画已执行的时长 + * Elapsed animation duration + */ +- (CGFloat)elapsedTime; + +/** + * @brief 取消 + * Cancel + */ +- (void)cancel; + +/** + * @brief 是否已取消 + * Is canceled + * @return YES已取消,NO未取消 + * YES canceled, NO not canceled + */ +- (BOOL)isCancelled; + +/** + * @brief 获取当前动画已走过点的总个数 + * Get the total number of points the current animation has passed + * @return 个数 + * count + */ +- (NSInteger)passedPointCount; + + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotationView.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotationView.h new file mode 100644 index 0000000..77947cc --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAAnnotationView.h @@ -0,0 +1,140 @@ +// +// MAAnnotationView.h +// MAMapKitDemo +// +// Created by songjian on 13-1-7. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MACustomCalloutView.h" + +///MAAnnotationView拖动状态 +///MAAnnotationView drag state +typedef NS_ENUM(NSInteger, MAAnnotationViewDragState) +{ + MAAnnotationViewDragStateNone = 0, ///< 静止状态 stationary state + MAAnnotationViewDragStateStarting, ///< 开始拖动 start dragging + MAAnnotationViewDragStateDragging, ///< 拖动中 dragging + MAAnnotationViewDragStateCanceling, ///< 取消拖动 cancel dragging + MAAnnotationViewDragStateEnding ///< 拖动结束 end dragging +}; + +@protocol MAAnnotation; + +///标注view +///annotation view +@interface MAAnnotationView : UIView + +///复用标识 +///reuse identifier +@property (nonatomic, readonly, copy) NSString *reuseIdentifier; + +///z值,大值在上,默认为0。类似CALayer的zPosition。zIndex属性只有在viewForAnnotation或者didAddAnnotationViews回调中设置有效。 +///z-value, larger values on top, default is 0. Similar to CALayer's zPosition. The zIndex property only takes effect when set in the viewForAnnotation or didAddAnnotationViews callbacks. +@property (nonatomic, assign) NSInteger zIndex; + +///关联的annotation +///associated annotation +@property (nonatomic, strong) id annotation; + +///显示的image +///Displayed image +@property (nonatomic, strong) UIImage *image; + +///image所对应的UIImageView since 5.0.0 +///UIImageView corresponding to the image. since 5.0.0 +@property (nonatomic, strong, readonly) UIImageView *imageView; + +///自定制弹出框view, 用于替换默认弹出框. 注意:此弹出框不会触发-(void)mapView: didAnnotationViewCalloutTapped: since 5.0.0 +///Custom pop-up view, used to replace the default pop-up. Note: This pop-up will not trigger -(void)mapView: didAnnotationViewCalloutTapped: since 5.0.0 +@property (nonatomic, strong) MACustomCalloutView *customCalloutView; + +///annotationView的中心默认位于annotation的坐标位置,可以设置centerOffset改变view的位置,正的偏移使view朝右下方移动,负的朝左上方,单位是屏幕坐标 +///The center of the annotationView is by default located at the coordinate position of the annotation. You can set the centerOffset to change the position of the view. A positive offset moves the view towards the lower right, while a negative one moves it towards the upper left, with units in screen coordinates. +@property (nonatomic) CGPoint centerOffset; + +///弹出框默认位于view正中上方,可以设置calloutOffset改变view的位置,正的偏移使view朝右下方移动,负的朝左上方,单位是屏幕坐标 +///The pop-up box is by default located above the center of the view, and the position of the view can be changed by setting calloutOffset. A positive offset moves the view to the lower right, and a negative offset moves it to the upper left, in screen coordinates. +@property (nonatomic) CGPoint calloutOffset; + +///默认为YES,当为NO时view忽略触摸事件 +///Default is YES, when NO the view ignores touch events +@property (nonatomic, getter=isEnabled) BOOL enabled; + +///是否高亮 +///Whether to highlight +@property (nonatomic, getter=isHighlighted) BOOL highlighted; + +///设置是否处于选中状态, 外部如果要选中请使用mapView的selectAnnotation方法 +///Set whether it is in the selected state. To select externally, use the selectAnnotation method of mapView +@property (nonatomic, getter=isSelected) BOOL selected; + +///是否允许弹出callout +///Whether to allow callout to pop up +@property (nonatomic) BOOL canShowCallout; + +///显示在默认弹出框左侧的view +///The view displayed on the left side of the default popup +@property (nonatomic, strong) UIView *leftCalloutAccessoryView; + +///显示在默认弹出框右侧的view +///the view displayed on the right side of the default popup +@property (nonatomic, strong) UIView *rightCalloutAccessoryView; + +///是否支持拖动 +///whether dragging is supported +@property (nonatomic, getter=isDraggable) BOOL draggable; + +///当前view的拖动状态 +///the dragging state of the current view +@property (nonatomic) MAAnnotationViewDragState dragState; + +///弹出默认弹出框时,是否允许地图调整到合适位置来显示弹出框,默认为YES +///whether the map is allowed to adjust to a suitable position to display the popup when the default popup is displayed, default is YES +@property (nonatomic) BOOL canAdjustPositon; + +///是否开启碰撞检测,默认为NO。开启后标注会在地图缩放和密集显示时进行碰撞检测,避免重叠显示 +///Whether to enable collision detection, default is NO. When enabled, annotations will perform collision detection during map zooming and dense display to avoid overlapping display +@property (nonatomic) BOOL isOpenCollisionDetection; + +/** + * @brief 设置是否处于选中状态, 外部如果要选中请使用mapView的selectAnnotation方法 + * Set whether it is in the selected state, if you want to select it externally, use the selectAnnotation method of mapView + * @param selected 是否选中 + * whether it is selected + * @param animated 是否使用动画效果 + * whether to use animation effects + */ +- (void)setSelected:(BOOL)selected animated:(BOOL)animated; + +/** + * @brief 初始化并返回一个annotation view + * Initialize and return an annotation view + * @param annotation 关联的annotation对象 + * associated annotation object + * @param reuseIdentifier 如果要重用view,传入一个字符串,否则设为nil,建议重用view + * if you want to reuse the view, pass a string, otherwise set it to nil, it is recommended to reuse the view + * @return 初始化成功则返回annotation view,否则返回nil + * Returns the annotation view if initialization is successful, otherwise returns nil + */ +- (id)initWithAnnotation:(id )annotation reuseIdentifier:(NSString *)reuseIdentifier; + +/** + * @brief 当从reuse队列里取出时被调用, 子类重新必须调用super + * Called when taken out of the reuse queue, the subclass must call super + */ +- (void)prepareForReuse; + +/** + * @brief 设置view的拖动状态 + * set the drag state of the view + * @param newDragState 新的拖动状态 + * the new drag state + * @param animated 是否使用动画动画 + * whether to use animation + */ +- (void)setDragState:(MAAnnotationViewDragState)newDragState animated:(BOOL)animated; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAArc.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAArc.h new file mode 100644 index 0000000..2983f9b --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAArc.h @@ -0,0 +1,49 @@ +// +// MAArc.h +// MAMapKit +// +// Created by liubo on 2018/4/10. +// Copyright © 2018年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_ARC + +#import "MAShape.h" +#import "MAOverlay.h" + +///该类用于定义一个圆弧, 通常MAArc是MAArcRenderer的model +///This class is used to define an arc, usually MAArc serves as the model for MAArcRenderer +@interface MAArc : MAShape + +///起点经纬度坐标,无效坐标按照{0,0}处理 +///Starting point latitude and longitude coordinates, invalid coordinates are treated as {0, 0} +@property (nonatomic, assign) CLLocationCoordinate2D startCoordinate; + +///途径点经纬度坐标,无效坐标按照{0,0}处理 +///Waypoint latitude and longitude coordinates, invalid coordinates are treated as {0, 0} +@property (nonatomic, assign) CLLocationCoordinate2D passedCoordinate; + +///终点经纬度坐标,无效坐标按照{0,0}处理 +///Destination latitude and longitude coordinates, invalid coordinates are treated as {0, 0} +@property (nonatomic, assign) CLLocationCoordinate2D endCoordinate; + +/** + * @brief 根据起点、途经点和终点生成圆弧 + * Generate an arc based on the starting point, waypoints, and endpoint + * @param startCoordinate 起点的经纬度坐标,无效坐标按照{0,0}处理 + * Starting point latitude and longitude coordinates, invalid coordinates are treated as {0, 0} + * @param passedCoordinate 途径点的经纬度坐标,无效坐标按照{0,0}处理 + * Waypoint latitude and longitude coordinates, invalid coordinates are treated as {0, 0} + * @param endCoordinate 终点的经纬度坐标,无效坐标按照{0,0}处理 + * Destination latitude and longitude coordinates, invalid coordinates are treated as {0, 0} + * @return 新生成的圆弧 + * the newly generated arc + */ ++ (instancetype)arcWithStartCoordinate:(CLLocationCoordinate2D)startCoordinate + passedCoordinate:(CLLocationCoordinate2D)passedCoordinate + endCoordinate:(CLLocationCoordinate2D)endCoordinate; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAArcRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAArcRenderer.h new file mode 100644 index 0000000..76e30e6 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAArcRenderer.h @@ -0,0 +1,35 @@ +// +// MAArcRenderer.h +// MAMapKit +// +// Created by liubo on 2018/4/10. +// Copyright © 2018年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_ARC + +#import "MAArc.h" +#import "MAOverlayPathRenderer.h" + +///此类用于绘制MAArc,可以通过MAOverlayPathRenderer修改其stroke attributes +///This class is used to draw MAArc, and its stroke attributes can be modified via MAOverlayPathRenderer +@interface MAArcRenderer : MAOverlayPathRenderer + +///关联的MAArc model +///Associated MAArc model +@property (nonatomic, readonly) MAArc *arc; + +/** + * @brief 根据指定的MAArc生成一个圆弧Renderer + * Generate an arc Renderer based on the specified MAArc + * @param arc 指定MAArc + * Specify MAArc + * @return 新生成的圆弧Renderer + * The newly generated arc Renderer + */ +- (instancetype)initWithArc:(MAArc *)arc; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MABaseEngineOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MABaseEngineOverlay.h new file mode 100644 index 0000000..ea9957f --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MABaseEngineOverlay.h @@ -0,0 +1,18 @@ +// +// MABaseEngineOverlay.h +// MAMapKit +// +// Created by linshiqing on 2024/1/23. +// Copyright © 2024 Amap. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MABaseEngineOverlay : NSObject +/// 移除Overlay Remove Overlay +- (void)removeOverlay; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MABaseOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MABaseOverlay.h new file mode 100644 index 0000000..2a2e1ae --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MABaseOverlay.h @@ -0,0 +1,31 @@ +// +// MABaseOverlay.h +// MAMapKit +// +// Created by cuishaobin on 2020/6/17. +// Copyright © 2020 Amap. All rights reserved. +// + +#import +#import "MAOverlay.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MABaseOverlay : NSObject { + double _altitude; ///<海拔 Elevation +} + +///返回区域中心坐标 +///Return to regional center coordinates +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +///区域外接矩形 +///Regional bounding rectangle +@property (nonatomic, assign) MAMapRect boundingMapRect; + +///海拔,单位米,默认0 +///Elevation, in meters, default 0 +@property (nonatomic, assign) double altitude; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MACircle.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACircle.h new file mode 100755 index 0000000..3961e0a --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACircle.h @@ -0,0 +1,64 @@ +// +// MACircle.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. + +#import "MAConfig.h" +#import "MAShape.h" +#import "MAOverlay.h" +#import "MAGeometry.h" + +///该类用于定义一个圆, 通常MACircle是MACircleView的model +///This class is used to define a circle, usually MACircle is the model of MACircleView +@interface MACircle : MAShape + +///设置中空区域,用来创建中间带空洞的复杂图形。注意:传入的overlay只支持MAPolgon类型和MACircle类型,不支持与此circle边相交或在circle外部,不支持hollowShapes彼此间相交,和空洞顺序有关,不支持嵌套. since 5.5.0 +///Set the hollow area to create complex shapes with holes in the middle. Note: The overlay passed in only supports MAPolygon type and MACircle type,Intersection with the edge of this circle or being outside the circle is not supported, intersection between hollowShapes is not supported, it is related to the order of voids, nesting is not supported. since 5.5.0 +@property (nonatomic, strong) NSArray> *hollowShapes; + +///中心点经纬度坐标,无效坐标按照{0,0}处理 +///Center point latitude and longitude coordinates, invalid coordinates are processed as {0, 0} +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +///半径,单位:米 负数按照0处理 +///Radius, unit: meters, negative numbers are treated as 0 +@property (nonatomic, assign) CLLocationDistance radius; + +/** + * @brief 根据中心点和半径生成圆 + * Generate a circle based on the center point and radius + * @param coord 中心点的经纬度坐标,无效坐标按照{0,0}处理 + * Latitude and longitude coordinates of the center point, invalid coordinates are treated as {0, 0} + * @param radius 半径,单位:米, 负数按照0处理 + * Radius, unit: meters, negative values are treated as 0 + * @return 新生成的圆 + * Newly generated circle + */ ++ (instancetype)circleWithCenterCoordinate:(CLLocationCoordinate2D)coord + radius:(CLLocationDistance)radius; + +/** + * @brief 根据map rect生成圆 + * Generate a circle based on map rect + * @param mapRect mapRect 圆的最小外界矩形 + * mapRect is the minimum bounding rectangle of the circle + * @return 新生成的圆 + * the newly generated circle + */ ++ (instancetype)circleWithMapRect:(MAMapRect)mapRect; + +/** + * @brief 设置圆的中心点和半径. since 5.0.0 + * set the center point and radius of the circle. since 5.0.0 + * @param coord 中心点的经纬度坐标,无效坐标按照{0,0}处理 + * Latitude and longitude coordinates of the center point, invalid coordinates are treated as {0, 0} + * @param radius 半径,单位:米 负数按照0处理 + * Radius, unit: meters, negative values are treated as 0 + * @return 是否设置成功 + * whether the setting is successful + */ +- (BOOL)setCircleWithCenterCoordinate:(CLLocationCoordinate2D)coord radius:(CLLocationDistance)radius; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MACircleRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACircleRenderer.h new file mode 100755 index 0000000..c6f5955 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACircleRenderer.h @@ -0,0 +1,31 @@ +// +// MACircleRenderer.h +// MAMapKit +// +// Created by yin cai on 11-12-30. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import "MACircle.h" +#import "MAOverlayPathRenderer.h" + +///该类是MACircle的显示圆Renderer,可以通过MAOverlayPathRenderer修改其fill和stroke attributes +///This class is the renderer for MACircle's display circle. Its fill and stroke attributes can be modified via MAOverlayPathRenderer +@interface MACircleRenderer : MAOverlayPathRenderer + +///关联的MAcirlce model +///Associated MAcircle model +@property (nonatomic, readonly) MACircle *circle; + +/** + * @brief 根据指定圆生成对应的Renderer + * Generate corresponding Renderer based on specified circle + * @param circle 指定的MACircle model + * Specified MACircle model + * @return 生成的Renderer + * Generated Renderer + */ +- (instancetype)initWithCircle:(MACircle *)circle; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAConfig.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAConfig.h new file mode 100644 index 0000000..08487f7 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAConfig.h @@ -0,0 +1,146 @@ +/* + * @Author: hunk.lc + * @Date: 2022-08-17 15:09:58 + * @Description: + */ + +#pragma once + +#pragma mark - iOS 平台特有的宏 + +#ifndef MA_INCLUDE_OFFLINE +#define MA_INCLUDE_OFFLINE 1 +#endif + +#ifndef MA_INCLUDE_TRACE_CORRECT +#define MA_INCLUDE_TRACE_CORRECT 1 +#endif + +#ifndef MA_INCLUDE_INDOOR +#define MA_INCLUDE_INDOOR 1 +#endif + +#ifndef MA_INCLUDE_CACHE +#define MA_INCLUDE_CACHE 1 +#endif + +#ifndef MA_INCLUDE_CUSTOM_MAP_STYLE +#define MA_INCLUDE_CUSTOM_MAP_STYLE 1 +#endif + +#ifndef MA_INCLUDE_WORLD_EN_MAP +#define MA_INCLUDE_WORLD_EN_MAP 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_TILE +#define MA_INCLUDE_OVERLAY_TILE 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_HEATMAP +#define MA_INCLUDE_OVERLAY_HEATMAP 1 +#endif + +#ifndef MA_INCLUDE_QUARDTREE +#define MA_INCLUDE_QUARDTREE 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_ARC +#define MA_INCLUDE_OVERLAY_ARC 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_CUSTOMBUILDING +#define MA_INCLUDE_OVERLAY_CUSTOMBUILDING 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_GROUND +#define MA_INCLUDE_OVERLAY_GROUND 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_GEODESIC +#define MA_INCLUDE_OVERLAY_GEODESIC 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_MAMultiPolyline +#define MA_INCLUDE_OVERLAY_MAMultiPolyline 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_MAMultiPoint +#define MA_INCLUDE_OVERLAY_MAMultiPoint 1 +#endif + +#ifndef MA_INCLUDE_OVERLAY_ParticleSystem +#define MA_INCLUDE_OVERLAY_ParticleSystem 1 +#endif + +#ifndef MA_INCLUDE_OVERSEA +#define MA_INCLUDE_OVERSEA 0 +#endif + +#ifndef MA_ENABLE_ThirdPartyLog +#define MA_ENABLE_ThirdPartyLog 0 +#endif + +//标识是否支持异步等待引擎线程池结束 +#ifndef MA_INCLUDE_END_THEADPOOL_ASYN +#define MA_INCLUDE_END_THEADPOOL_ASYN 1 +#endif + +//国际图 +#ifndef AMC_INCLUDE_Global +#define AMC_INCLUDE_Global 1 +#endif + +#pragma mark - iOS和android 双平台都用到的宏 + +#ifndef AMC_INCLUDE_GNaviSearch +#define AMC_INCLUDE_GNaviSearch 1 +#endif + +#ifndef AMC_INCLUDE_MAMapVectorOverlay +#define AMC_INCLUDE_MAMapVectorOverlay 1 +#endif + +//自定义楼块 +#ifndef AMC_INCLUDE_CustomBuilding +#define AMC_INCLUDE_CustomBuilding 1 +#endif + +//粒子系统 +#ifndef AMC_INCLUDE_ParticleSystem +#define AMC_INCLUDE_ParticleSystem 1 +#endif + +// 3d模型 +#ifndef MA_INCLUDE_3DModel +#define MA_INCLUDE_3DModel 1 +#endif + +// gltf +#ifndef FEATURE_GLTF +#define FEATURE_GLTF 1 +#endif + +// mvt +#ifndef FEATURE_MVT +#define FEATURE_MVT 1 +#endif + +// 3dtiles +#ifndef FEATURE_3DTiles +#define FEATURE_3DTiles 1 +#endif + +// location +#ifndef FEATURE_LOCATION +#define FEATURE_LOCATION 1 +#endif + +// engine routeoverlay +#ifndef FEATURE_ROUTE_OVERLAY +#define FEATURE_ROUTE_OVERLAY 1 +#endif + +// arrowOverlay +#ifndef FEATURE_ARROWOVERLAY +#define FEATURE_ARROWOVERLAY 1 +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomBuildingOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomBuildingOverlay.h new file mode 100644 index 0000000..083f681 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomBuildingOverlay.h @@ -0,0 +1,101 @@ +// +// MACustomBuildingOverlay.h +// MAMapKit +// +// Created by liubo on 2018/5/23. +// Copyright © 2018年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_CUSTOMBUILDING + +#import "MAShape.h" +#import "MAOverlay.h" +#import "MAMultiPoint.h" + +#pragma mark - MACustomBuildingOverlayOption + +///该类用于定义一个楼块显示选项. since 6.3.0 +///This class is used to define a building block display option. since 6.3.0 +@interface MACustomBuildingOverlayOption : MAMultiPoint + +///楼块的高度. 修改该属性会使option范围内的所有楼块为同一个高度. (范围 (-1 U [1, 1000]). 默认-1,显示为默认高度.) +///The height of the building block. Modifying this property will set all building blocks within the option scope to the same height. (Range (-1 U [1, 1000]). Default -1, displayed as the default height. +@property (nonatomic, assign) CGFloat height; + +///楼块的高度缩放比例. 修改该属性会使option范围内的所有楼块高度放大或者缩小heightScale倍. (默认1. 如果指定了height则此值将被忽略.) +///The height scaling ratio of the building blocks. Modifying this attribute will scale the height of all building blocks within the option range by a factor of heightScale. (Default is 1. If height is specified, this value will be ignored.) +@property (nonatomic, assign) CGFloat heightScale; + +///楼块的顶面颜色. (默认[UIColor lightGrayColor], 不支持透明度) +///The top color of the building block. (Default [UIColor lightGrayColor], transparency not supported) +@property (nonatomic, strong) UIColor *topColor; + +///楼块的侧面颜色. (默认[UIColor darkGrayColor], 不支持透明度) +///The side color of the building block. (Default [UIColor darkGrayColor], transparency not supported) +@property (nonatomic, strong) UIColor *sideColor; + +///option选项是否可见. (默认YES) +///Whether the option is visible. (Default: YES) +@property (nonatomic, assign) BOOL visibile; + +/** + * @brief 根据经纬度坐标数据生成楼块显示选项option + * Generate building block display options based on latitude and longitude coordinate data + * @param coords 经纬度坐标点数据,coords对应的内存会拷贝,调用者负责该内存的释放 + * Latitude and longitude coordinate point data, the memory corresponding to coords will be copied, the caller is responsible for releasing this memory + * @param count 经纬度坐标点数组个数 + * Number of latitude and longitude coordinate points array + * @return 新生成的楼块显示选项option + * Newly generated building block display option + */ ++ (instancetype)optionWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count; + +/** + * @brief 重新设置option范围. + * Reset the option range + * @param coords 指定的经纬度坐标点数组, C数组,内部会做copy,调用者负责内存管理 + * Specified latitude and longitude coordinate points array, C array, internal copy will be made, caller is responsible for memory management + * @param count 坐标点的个数 + * Number of coordinate points + * @return 是否设置成功 + * Whether the setup was successful + */ +- (BOOL)setOptionWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count; + +@end + + +#pragma mark - MACustomBuildingOverlay + +///该类用于定义一个自定义楼块MACustomBuildingOverlay, 通常MACustomBuildingOverlay是MACustomBuildingOverlayRenderer的model.(注意: 自定义楼块仅支持在zoomLevel>=15级时显示) since 6.3.0 +///This class is used to define a custom building block MACustomBuildingOverlay,Generally, MACustomBuildingOverlay is the model of MACustomBuildingOverlayRenderer. (Note: Custom buildings are only displayed when zoomLevel>=15) since 6.3.0 +@interface MACustomBuildingOverlay : MAShape + +///默认的楼块显示option, 将显示所有的楼块. (如果不需要显示所有的楼块,可以设置defaultOption.visibile = NO) +///The default building block display option will show all building blocks. (If you do not need to display all building blocks, you can set defaultOption.visible = NO) +@property (nonatomic, readonly) MACustomBuildingOverlayOption *defaultOption; + +///当前自定义的楼块显示options. (options按添加顺序, 后添加的在最上层显示) +///Current customized building block display options. (Options are displayed in the order they are added, with the last added on top) +@property (nonatomic, readonly) NSArray *customOptions; + +/** + * @brief 增加自定义楼块显示的option + * Add custom building display options + * @param option 要增加的自定义楼块显示option + * Custom building display options to be added + */ +- (void)addCustomOption:(MACustomBuildingOverlayOption *)option; + +/** + * @brief 移除自定义楼块显示的option + * Remove the option for displaying custom building blocks + * @param option 要移除的自定义楼块显示option + * The custom building block display option to be removed + */ +- (void)removeCustomOption:(MACustomBuildingOverlayOption *)option; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomBuildingOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomBuildingOverlayRenderer.h new file mode 100644 index 0000000..081a6ae --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomBuildingOverlayRenderer.h @@ -0,0 +1,34 @@ +// +// MACustomBuildingOverlayRenderer.h +// MAMapKit +// +// Created by liubo on 2018/5/23. +// Copyright © 2018年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_CUSTOMBUILDING + +#import "MAOverlayRenderer.h" +#import "MACustomBuildingOverlay.h" + +///该类是MACustomBuildingOverlay的显示Renderer. since 6.3.0 +///This class is the renderer for MACustomBuildingOverlay display. since 6.3.0 +@interface MACustomBuildingOverlayRenderer : MAOverlayRenderer + +///关联的MACustomBuildingOverlay model +///Associated with the MACustomBuildingOverlay model +@property (nonatomic, readonly) MACustomBuildingOverlay *customBuildingOverlay; + +/** + * @brief 根据指定MACustomBuildingOverlay生成对应的Renderer + * Generate the corresponding Renderer based on the specified MACustomBuildingOverlay + * @param customBuildingOverlay 指定的MACustomBuildingOverlay model + * The specified MACustomBuildingOverlay model + * @return 生成的Renderer + * The generated Renderer + */ +- (instancetype)initWithCustomBuildingOverlay:(MACustomBuildingOverlay *)customBuildingOverlay; + +@end +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomCalloutView.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomCalloutView.h new file mode 100644 index 0000000..bfd2176 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MACustomCalloutView.h @@ -0,0 +1,35 @@ +// +// MACustomCalloutView.h +// MAMapKit +// +// Created by shaobin on 17/1/6. +// Copyright © 2017年 Amap. All rights reserved. +// + +#import +#import "MAConfig.h" + +///自定义annotationView的弹出框. 注意:不会触发-(void)mapView: didAnnotationViewCalloutTapped: since 5.0.0 +///Customize the popup of annotationView. Note: It will not trigger -(void)mapView: didAnnotationViewCalloutTapped: since 5.0.0 +@interface MACustomCalloutView : UIView + +///init时传入的customView since 5.0.0 +///CustomView passed during init since 5.0.0 +@property (nonatomic, strong, readonly) UIView *customView; + +///用户自定义数据,内部不做处理 since 5.0.0 +///User-defined data, no internal processing since 5.0.0 +@property (nonatomic, strong) id userData; + +/** + * @brief 初始化并返回一个MACustomCalloutView since 5.0.0 + * Initialize and return a MACustomCalloutView since 5.0.0 + * @param customView 自定义View,不能为nil + * Custom View, cannot be nil + * @return 初始化成功则返回MACustomCalloutView,否则返回nil + * Returns MACustomCalloutView if initialization is successful, otherwise returns nil + */ +- (id)initWithCustomView:(UIView *)customView; + + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGeodesicPolyline.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGeodesicPolyline.h new file mode 100644 index 0000000..64ce213 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGeodesicPolyline.h @@ -0,0 +1,68 @@ +// +// MAGeodesicPolyline.h +// MapKit_static +// +// Created by songjian on 13-10-23. +// Copyright © 2016 Amap. All rights reserved. + +#import "MAConfig.h" + +#if MA_INCLUDE_OVERLAY_GEODESIC + +#import "MAPolyline.h" + +///大地曲线 +///Earth's curvature +@interface MAGeodesicPolyline : MAPolyline + +/** + * @brief 根据MAMapPoints生成大地曲线 + * Generate geodesic curves based on MAMapPoints + * @param points MAMapPoint点 + * MAMapPoint points + * @param count 点的个数 + * Number of points + * @return 生成的大地曲线 + * Generated geodesic curves + */ ++ (instancetype)polylineWithPoints:(MAMapPoint *)points count:(NSUInteger)count; + +/** + * @brief 根据经纬度生成大地曲线 + * Generate geodesic curves based on latitude and longitude + * @param coords 经纬度 + * Latitude and longitude + * @param count 点的个数 + * Number of points + * @return 生成的大地曲线 + * Generated geodesic curves + */ ++ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count; + +/** + * @brief 重新设置坐标点. since 5.0.0 + * Reset coordinate points. since 5.0.0 + * @param points 指定的直角坐标点数组,C数组,内部会做copy,调用者负责内存管理。 + * Specified rectangular coordinate points array, C array, internal copy will be made, caller is responsible for memory management + * @param count 坐标点的个数 + * Number of coordinate points + * @return 是否设置成功 + * Whether the setup is successful + */ +- (BOOL)setPolylineWithPoints:(MAMapPoint *)points count:(NSInteger)count; + +/** + * @brief 重新设置坐标点. since 5.0.0 + * Reset coordinate points. since 5.0.0 + * @param coords 指定的经纬度坐标点数组,C数组,内部会做copy,调用者负责内存管理 + * Specified array of latitude and longitude coordinate points, C array, internal copy will be performed, caller is responsible for memory management + * @param count 坐标点的个数 + * Number of coordinate points + * @return 是否设置成功 + * Whether the setup is successful + */ +- (BOOL)setPolylineWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSInteger)count; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGeometry.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGeometry.h new file mode 100644 index 0000000..da9b260 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGeometry.h @@ -0,0 +1,634 @@ +// +// MAGeometry.h +// MAMapKit +// +// Created by AutoNavi. +// Copyright (c) 2013年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import +#import + + +#ifdef __cplusplus +extern "C" { +#endif + + ///东北、西南两个点定义的四边形经纬度范围 + ///Latitude and longitude range of the quadrilateral defined by the northeast and southwest points + typedef struct MACoordinateBounds{ + CLLocationCoordinate2D northEast; ///< 东北角经纬度 Latitude and longitude of the northeast corner + CLLocationCoordinate2D southWest; ///< 西南角经纬度 Latitude and longitude of the southwest corner + } MACoordinateBounds; + + ///经度、纬度定义的经纬度跨度范围 + ///Latitude and longitude span range defined by longitude and latitude + typedef struct MACoordinateSpan{ + CLLocationDegrees latitudeDelta; ///< 纬度跨度 Latitude span + CLLocationDegrees longitudeDelta; ///< 经度跨度 Longitude span + } MACoordinateSpan; + + ///中心点、跨度范围定义的四边形经纬度范围 + ///Quadrilateral latitude and longitude range defined by center point and span + typedef struct MACoordinateRegion{ + CLLocationCoordinate2D center; ///< 中心点经纬度 Center point latitude and longitude + MACoordinateSpan span; ///< 跨度范围 Span range + } MACoordinateRegion; + + ///平面投影坐标结构定义 + ///Plane projection coordinate structure definition + typedef struct MAMapPoint{ + double x; /// +#import "MAShape.h" +#import "MAOverlay.h" + +///该类用于确定覆盖在地图上的图片,及其覆盖区域, 通常MAGroundOverlay是MAGroundOverlayRenderer的model +///This class is used to determine the images overlaid on the map and their coverage areas. Typically, MAGroundOverlay is the model of MAGroundOverlayRenderer. +@interface MAGroundOverlay : MAShape + +///绘制在地图上的覆盖图片 +///Overlay image drawn on the map +@property (nonatomic, readonly) UIImage *icon; + +///透明度. 最终透明度 = 纹理透明度 * alpha. 有效范围为[0.f, 1.f], 默认为1.f +///Transparency. Final transparency = texture transparency * alpha. Valid range is [0.f, 1.f], default is 1.f +@property (nonatomic, assign) CGFloat alpha __attribute((deprecated("Deprecated, since 7.7.0, please use alpha in MAGroundOverlayRenderer"))); + +///覆盖图片在地图尺寸等同于其像素的zoom值 +///The zoom level at which the overlay image's size on the map equals its pixel dimensions. +@property (nonatomic, readonly) CGFloat zoomLevel; + +///图片在地图中的覆盖范围 +///Image coverage area on the map +@property (nonatomic, readonly) MACoordinateBounds bounds; + +/** + * @brief 根据bounds值和icon生成GroundOverlay + * Generate GroundOverlay based on bounds and icon + * @param bounds 图片的在地图的覆盖范围 + * Image coverage area on the map + * @param icon 覆盖图片 + * Overlay image + * @return 以bounds和icon 新生成GroundOverlay + * Create a new GroundOverlay with bounds and icon + */ ++ (instancetype)groundOverlayWithBounds:(MACoordinateBounds)bounds + icon:(UIImage *)icon; + +/** + * @brief 根据coordinate,icon,zoomLevel生成GroundOverlay + * Generate GroundOverlay based on coordinate, icon, and zoomLevel + * @param coordinate 图片的在地图上的中心点 + * The center point of the image on the map + * @param zoomLevel 图片在地图尺寸等同于像素的zoom值 + * The zoom value where the image size on the map equals its pixel dimensions + * @param icon 覆盖图片 + * Overlay image + * @return 以coordinate,icon,zoomLevel 新生成GroundOverlay + * Create a new GroundOverlay with coordinate, icon, zoomLevel + */ ++ (instancetype)groundOverlayWithCoordinate:(CLLocationCoordinate2D)coordinate + zoomLevel:(CGFloat)zoomLevel + icon:(UIImage *)icon; + +/** + * @brief 更新GroundOverlay. since 5.0.0 + * Update GroundOverlay. since 5.0.0 + * @param bounds 图片的在地图的覆盖范围 + * Image coverage on the map + * @param icon 覆盖图片 + * Overlay image + * @return 返回是否成功 + * Return success status + */ +- (BOOL)setGroundOverlayWithBounds:(MACoordinateBounds)bounds icon:(UIImage *)icon; + +/** + * @brief 更新GroundOverlay, 内部会自动计算覆盖物大小,以满足zoomLevel下显示大小为icon大小. since 5.0.0 + * Update GroundOverlay, the system will automatically calculate the overlay size to ensure the display size matches the icon size at the zoomLevel. since 5.0.0 + * @param coordinate 图片在地图上的中心点 + * The center point of the image on the map + * @param zoomLevel 图片在地图尺寸等同于像素的zoom值 + * The zoom value where the image size on the map equals its pixel dimensions + * @param icon 覆盖图片 + * Overlay image + * @return 返回是否成功 + * Return success status + */ +- (BOOL)setGroundOverlayWithCoordinate:(CLLocationCoordinate2D)coordinate + zoomLevel:(CGFloat)zoomLevel + icon:(UIImage *)icon; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGroundOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGroundOverlayRenderer.h new file mode 100644 index 0000000..de01d8f --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAGroundOverlayRenderer.h @@ -0,0 +1,36 @@ +// +// MAGroundOverlayRenderer.h +// MapKit_static +// +// Created by Li Fei on 11/13/13. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OVERLAY_GROUND + +#import "MAOverlayRenderer.h" +#import "MAGroundOverlay.h" + +///此类是将MAGroundOverlay中的覆盖图片显示在地图上的renderer +///This class is the renderer that displays the overlay image from MAGroundOverlay on the map +@interface MAGroundOverlayRenderer : MAOverlayRenderer + +///具有覆盖图片,以及图片覆盖的区域 +///It has the overlay image and the area covered by the image +@property (nonatomic ,readonly) MAGroundOverlay *groundOverlay; + +/** + * @brief 根据指定的GroundOverlay生成将图片显示在地图上Renderer + * Generate a Renderer to display the image on the map based on the specified GroundOverlay + * @param groundOverlay 制定了覆盖图片,以及图片的覆盖区域的groundOverlay + * Defined the overlay image and the groundOverlay area of the image + * @return 以GroundOverlay新生成Renderer + * It generates a new Renderer with GroundOverlay + */ +- (instancetype)initWithGroundOverlay:(MAGroundOverlay *)groundOverlay; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapTileOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapTileOverlay.h new file mode 100644 index 0000000..afd2915 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapTileOverlay.h @@ -0,0 +1,82 @@ +// +// MAHeatMapTileOverlay.h +// test2D +// +// Created by xiaoming han on 15/4/21. +// Copyright (c) 2015年 Amap. All rights reserved. +// + + + +#import "MAConfig.h" + +#if MA_INCLUDE_OVERLAY_HEATMAP + +#import "MATileOverlay.h" + +///热力图节点 +///Heatmap node +@interface MAHeatMapNode : NSObject + +///经纬度 +///Latitude and longitude +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +///强度 +///Intensity +@property (nonatomic, assign) float intensity; + +@end + +///热力图渐变属性 +///Heatmap gradient properties +@interface MAHeatMapGradient : NSObject + +///颜色变化数组。 default [blue,green,red] +///Color gradient array. default [blue,green,red] +@property (nonatomic, readonly) NSArray *colors; + +///颜色变化起点,需为递增数组,区间为(0, 1)。default[@(0.2),@(0.5),@(0,9)] +///Starting point of color gradient, must be an increasing array, range (0, 1). default[@(0.2),@(0.5),@(0,9)] +@property (nonatomic, readonly) NSArray *startPoints; + +/** + * @brief 重新设置gradient的时候,需要执行 MATileOverlayRenderer 的 reloadData 方法重刷新渲染缓存。 + * When resetting the gradient, you need to execute the reloadData method of MATileOverlayRenderer to refresh the rendering cache. + * @param colors 颜色 + * Color + * @param startPoints startPoints + * @return instance + */ +- (instancetype)initWithColor:(NSArray *)colors andWithStartPoints:(NSArray *)startPoints; + +@end + +///热力图tileOverlay +///Heatmap tileOverlay +@interface MAHeatMapTileOverlay : MATileOverlay + +///MAHeatMapNode array +@property (nonatomic, strong) NSArray *data; + +///热力图半径,默认为12,范围:10-200 screen point +///Heatmap radius, default is 12, range: 10-200 screen points +@property (nonatomic, assign) NSInteger radius; + +///透明度,默认为0.6,范围:0-1 +///Opacity, default is 0.6, range: 0-1 +@property (nonatomic, assign) CGFloat opacity; + +///热力图梯度 +///Heatmap gradient +@property (nonatomic, strong) MAHeatMapGradient *gradient; + +///是否开启高清热力图,默认关闭 +///Whether to enable high-definition heatmap, default is off +@property (nonatomic, assign) BOOL allowRetinaAdapting; + +@end + +#endif + + diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorGridOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorGridOverlay.h new file mode 100644 index 0000000..03a0b33 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorGridOverlay.h @@ -0,0 +1,83 @@ +// +// MAHeatMapVectorGridOverlay.h +// MAMapKit +// +// Created by ldj on 2019/7/25. +// Copyright © 2019 Amap. All rights reserved. +// 热力图网格覆盖物(通过顶点直接绘制) + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_HEATMAP + +#import "MAShape.h" +#import "MAOverlay.h" +#import "MAHeatMapVectorOverlay.h" + +///单个点对象 +///Single point object +@interface MAHeatMapVectorGridNode : NSObject +///经纬度 +///Latitude and longitude +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; +@end + +///单个网格 +///Single grid +@interface MAHeatMapVectorGrid : NSObject +/// 网格顶点 +/// Grid vertices +@property (nonatomic, copy) NSArray *inputNodes; + +/// 网格颜色 +/// Grid color +@property (nonatomic, strong) UIColor *color; +@end + +/// 该类用于定义热力图属性. +/// This class is used to define heatmap attributes. +@interface MAHeatMapVectorGridOverlayOptions : NSObject + +/// 热力图类型 (默认为蜂窝类型MAHeatMapTypeHoneycomb) +/// Heatmap type (default is honeycomb type MAHeatMapTypeHoneycomb) +@property (nonatomic, assign) MAHeatMapType type; + +/// option选项是否可见. (默认YES) +/// whether the option is visible (default YES +@property (nonatomic, assign) BOOL visible; + +/// 网格数据 +/// grid data +@property (nonatomic, copy) NSArray *inputGrids; + +/// 最小显示级别 default 3 +/// minimum display level, default 3 +@property (nonatomic, assign) CGFloat minZoom; + +/// 最大显示级别 default 20 +/// maximum display level, default 20 +@property (nonatomic, assign) CGFloat maxZoom; + +@end + +///矢量热力图,支持类型详见MAHeatMapType +///Vector heatmap, supported types can be found in MAHeatMapType +@interface MAHeatMapVectorGridOverlay : MAShape + + +///热力图的配置属性 +///Configuration properties of the heatmap +@property (nonatomic, strong) MAHeatMapVectorGridOverlayOptions *option; + +/** + * @brief 根据配置属性option生成MAHeatMapVectorGridOverlay + * Generate MAHeatMapVectorGridOverlay based on configuration property option + * @param option 热力图配置属性option + * Heatmap configuration property option + * @return 新生成的热力图MAHeatMapVectorGridOverlay + * Newly generated heatmap MAHeatMapVectorGridOverlay + */ ++ (instancetype)heatMapOverlayWithOption:(MAHeatMapVectorGridOverlayOptions *)option; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorGridOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorGridOverlayRenderer.h new file mode 100644 index 0000000..3c879dd --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorGridOverlayRenderer.h @@ -0,0 +1,33 @@ +// +// MAHeatMapVectorGridOverlayRenderer.h +// MAMapKit +// +// Created by ldj on 2019/7/26. +// Copyright © 2019 Amap. All rights reserved. +// +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_HEATMAP + +#import "MAOverlayRenderer.h" +#import "MAHeatMapVectorGridOverlay.h" + +///矢量热力图绘制类 +///Vector heatmap rendering class +@interface MAHeatMapVectorGridOverlayRenderer : MAOverlayRenderer + +///关联的MAHeatMapVectorOverlay +///Associated MAHeatMapVectorOverlay +@property (nonatomic, readonly) MAHeatMapVectorGridOverlay *heatOverlay; + +/** + * @brief 根据指定的MAHeatMapVectorOverlay生成一个Renderer + * Generate a Renderer based on the specified MAHeatMapVectorOverlay + * @param heatOverlay 指定MAHeatMapVectorOverlay + * Specify MAHeatMapVectorOverlay + * @return 新生成的MAHeatMapVectorOverlayRender + * Newly generated MAHeatMapVectorOverlayRender + */ +- (instancetype)initWithHeatOverlay:(MAHeatMapVectorGridOverlay *)heatOverlay; +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorOverlay.h new file mode 100644 index 0000000..785bab4 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorOverlay.h @@ -0,0 +1,146 @@ +// +// MAHeatMapVectorOverlay.h +// MAMapKit +// +// Created by ldj on 2019/7/25. +// Copyright © 2019 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_HEATMAP + +#import "MAShape.h" +#import "MAOverlay.h" + +///热力图类型 +///Heatmap Type +typedef NS_ENUM(NSInteger, MAHeatMapType) +{ + MAHeatMapTypeSquare = 1, ///< 网格热力图 Grid Heatmap + MAHeatMapTypeHoneycomb = 2 ///< 蜂窝热力图 Hexagonal Heatmap +}; + +///单个点对象 +///Single Point Object +@interface MAHeatMapVectorNode : NSObject + +///经纬度 +///Latitude and Longitude +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +///权重 +///Weight +@property (nonatomic, assign) float weight; + +@end + +///热力图展示节点(用以描述一个蜂窝或一个网格) +///Heatmap Display Node (used to describe a hexagon or a grid) +@interface MAHeatMapVectorItem : NSObject + +///中心点坐标 +///Center Point Coordinates +@property (nonatomic, readonly) MAMapPoint center; + +///当前热力值,求和后的权重 +///Current heat value, summed weight +@property (nonatomic, readonly) float intensity; + +///落在此节点区域内的所有热力点的索引数组 +///Index array of all heat points within this node area +@property (nonatomic, readonly) NSArray *nodeIndices; + +@end + +///该类用于定义热力图属性. +///This class is used to define heatmap properties +@interface MAHeatMapVectorOverlayOptions : NSObject + +///热力图类型 (默认为蜂窝类型MAHeatMapTypeHoneycomb) +///Heatmap type (default is honeycomb type MAHeatMapTypeHoneycomb) +@property (nonatomic, assign) MAHeatMapType type; + +///option选项是否可见. (默认YES) +///Whether the option is visible. (Default: YES) +@property (nonatomic, assign) BOOL visible; + +///MAHeatMapVectorNode array +@property (nonatomic, strong) NSArray *inputNodes; + +/** + @verbatim + 节点的宽 单位:米 负数按照0处理 default 2000 + —— —— —— —— + 丨 丨 丨 丨 + 丨 丨 丨 丨 + —— —— —— —— + + 每个方框的宽就是 size(六边形同理) + 两个方框之间的间隔就是 gap (六边形同理) + @endverbatim + */ +/** + @verbatim + Node width in meters (negative values treated as 0, default 2000) + —— —— —— —— + 丨 丨 丨 丨 + 丨 丨 丨 丨 + —— —— —— —— + + The width of each box is size (same for hexagons) + The gap between two boxes is gap (same for hexagons) + @endverbatim + */ +@property (nonatomic, assign) CLLocationDistance size; + +///节点之间的间隔 单位:米 负数按照0处理。注意:改变gap可能会改变热力节点的计算,内部会用size+gap来计算热力,最终用size来画方框。 +///The spacing between nodes, unit: meters, negative values are treated as 0. Note: Changing the gap may alter the calculation of heat nodes; internally, size + gap is used to calculate the heat, and finally, size is used to draw the box. +@property (nonatomic, assign) CGFloat gap; + +///颜色变化数组。 注意:colors和startPoints两数组长度必须一致且不能为0, +///Color gradient array. Note: The lengths of the colors and startPoints arrays must be equal and cannot be 0. +@property (nonatomic, strong) NSArray *colors; + +///颜色变化起点,需为递增数组,区间为[0, 1]。 注意:colors和startPoints两数组长度必须一致且不能为0。例如:startPoints @[@(0), @(0.3),@(0.7)] 表示区间 [0,0.3)使用第一个颜色,区间[0.3,0.7)使用第二个颜色,区间[0.7,1]使用第三个颜色。注意:startPoints首位需设置成0,如果首位不是0,内部也会把首位当成0来处理。 +///The starting point of color change must be an increasing array within the range [0, 1]. Note: the lengths of the colors and startPoints arrays must be the same and cannot be zero.For example: startPoints @[@(0), @(0.3),@(0.7)] means the interval [0,0.3) uses the first color, the interval [0.3,0.7) uses the second color, and the interval [0.7,1] uses the third color.Note: The first element of startPoints must be set to 0. If it is not 0, the system will treat the first element as 0 internally. +@property (nonatomic, strong) NSArray *startPoints; + +///透明度,取值范围[0,1] ,default为1不透明 +///Transparency, value range [0,1], default is 1 for opaque +@property (nonatomic, assign) CGFloat opacity __attribute((deprecated("Deprecated, since 7.9.0, please use alpha in MAHeatMapVectorOverlayRender")));; + +///权重的最大值,default为0,表示不填,不填则取数组inputNodes中权重的最大值 +///Maximum weight, default is 0, indicating not filled, if not filled, the maximum weight in the array inputNodes is taken +@property (nonatomic, assign) int maxIntensity; + +///最小显示级别 default 3 +///Minimum display level, default 3 +@property (nonatomic, assign) CGFloat minZoom; + +///最大显示级别 default 20 +///Maximum display level, default 20 +@property (nonatomic, assign) CGFloat maxZoom; + +@end + +///矢量热力图,支持类型详见MAHeatMapType +///Vector heatmap, supported types refer to MAHeatMapType +@interface MAHeatMapVectorOverlay : MAShape + +///热力图的配置属性 +///Configuration properties of heatmap +@property (nonatomic, strong) MAHeatMapVectorOverlayOptions *option; + +/** + * @brief 根据配置属性option生成MAHeatMapVectorOverlay + * Generate MAHeatMapVectorOverlay based on configuration attribute option + * @param option 热力图配置属性option + * Heatmap configuration attribute option + * @return 新生成的热力图MAHeatMapVectorOverlay + * Newly generated heatmap MAHeatMapVectorOverlay + */ ++ (instancetype)heatMapOverlayWithOption:(MAHeatMapVectorOverlayOptions *)option; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorOverlayRender.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorOverlayRender.h new file mode 100644 index 0000000..a3dfbe2 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAHeatMapVectorOverlayRender.h @@ -0,0 +1,43 @@ +// +// MAHeatMapVectorOverlayRender.h +// MAMapKit +// +// Created by ldj on 2019/7/26. +// Copyright © 2019 Amap. All rights reserved. +// +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_HEATMAP + +#import "MAOverlayRenderer.h" +#import "MAHeatMapVectorOverlay.h" + +///矢量热力图绘制类 +///Vector Heatmap Drawing Class +@interface MAHeatMapVectorOverlayRender : MAOverlayRenderer + +///关联的MAHeatMapVectorOverlay +///Associated MAHeatMapVectorOverlay +@property (nonatomic, readonly) MAHeatMapVectorOverlay *heatOverlay; + +/** + * @brief 根据指定的MAHeatMapVectorOverlay生成一个Renderer + * Generate a Renderer based on the specified MAHeatMapVectorOverlay + * @param heatOverlay 指定MAHeatMapVectorOverlay + * Specify MAHeatMapVectorOverlay + * @return 新生成的MAHeatMapVectorOverlayRender + * Newly generated MAHeatMapVectorOverlayRender + */ +- (instancetype)initWithHeatOverlay:(MAHeatMapVectorOverlay *)heatOverlay; + + +/** + * @brief 根据经纬度获取对应的热力节点信息MAHeatMapVectorItem + * Obtain corresponding heatmap node information MAHeatMapVectorItem based on latitude and longitude + * @param coordinate 经纬度 + * Latitude and longitude + */ +- (MAHeatMapVectorItem *)getHeatMapItem:(CLLocationCoordinate2D )coordinate; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAIndoorInfo.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAIndoorInfo.h new file mode 100644 index 0000000..f8948c2 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAIndoorInfo.h @@ -0,0 +1,64 @@ +// +// MAIndoorInfo.h +// MAMapKit +// +// Created by 翁乐 on 5/6/16. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_INDOOR + +#import + +///室内楼层信息 +///Indoor floor information +@interface MAIndoorFloorInfo : NSObject +///楼层名 +///Floor name +@property (nonatomic, readonly) NSString *floorName; +///楼层index +///Floor index +@property (nonatomic, readonly) int floorIndex; +///楼层别名 +///Floor alias +@property (nonatomic, readonly) NSString *floorNona; +///是否属于停车场 +///Whether it belongs to a parking lot +@property (nonatomic, readonly) BOOL isPark; +@end + +///室内图信息 +///Indoor map information +@interface MAIndoorInfo : NSObject +///室内地图中文名 +///Indoor map Chinese name +@property (nonatomic, readonly) NSString *cnName; +///室内地图英文名 +///Indoor map English name +@property (nonatomic, readonly) NSString *enName; +///室内地图poiID +///Indoor map poiID +@property (nonatomic, readonly) NSString *poiID; +///建筑类型 +///Building type +@property (nonatomic, readonly) NSString *buildingType; +///当前楼层index,和floorInfo内部的index相关 +///Current floor index, related to the index inside floorInfo +@property (nonatomic, readonly) int activeFloorIndex; +///当前激活的楼层,只和floorInfo相关,与floorInfo内部元素的index无关 +///Currently active floor, only related to floorInfo, not related to the index of elements inside floorInfo +@property (nonatomic, readonly) int activeFloorInfoIndex; +///由 MAIndoorFloorInfo 组成,可直接通过 activeFloorInfoIndex 取出当前楼层 +///Composed of MAIndoorFloorInfo, the current floor can be directly retrieved through activeFloorInfoIndex +@property (nonatomic, readonly) NSArray *floorInfo; +///楼层数量 +///Number of floors +@property (nonatomic, readonly) int numberOfFloor; +///停车场楼层数量 +///Number of parking floors +@property (nonatomic, readonly) int numberOfParkFloor; +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MALineDrawType.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MALineDrawType.h new file mode 100644 index 0000000..0f4fb55 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MALineDrawType.h @@ -0,0 +1,40 @@ +// +// MALineDrawType.h +// MapKit_static +// +// Created by yi chen on 14-7-30. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#ifndef MapKit_static_MALineDrawType_h +#define MapKit_static_MALineDrawType_h + +enum MALineJoinType +{ + kMALineJoinBevel, ///< 斜面连接点 Bevel join + kMALineJoinMiter, ///< 斜接连接点 Miter join + kMALineJoinRound ///< 圆角连接点 Round join +}; +typedef enum MALineJoinType MALineJoinType; + +enum MALineCapType +{ + kMALineCapButt, ///< 普通头 Butt cap + kMALineCapSquare, ///< 扩展头 Square cap + kMALineCapArrow, ///< 箭头 Arrow + kMALineCapRound ///< 圆形头 Round cap +}; +typedef enum MALineCapType MALineCapType; + +///虚线类型 +enum MALineDashType +{ + kMALineDashTypeNone = 0, ///<不画虚线 No dash + kMALineDashTypeSquare, ///<方块样式 Square style + kMALineDashTypeDot, ///<圆点样式 Dot style +}; +typedef enum MALineDashType MALineDashType; + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMVTTileOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMVTTileOverlay.h new file mode 100644 index 0000000..def7757 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMVTTileOverlay.h @@ -0,0 +1,29 @@ +// +// MAMVTTileOverlay.h +// MapKit_static +// +// Created by Li Fei on 11/22/13. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_TILE +#if FEATURE_MVT +#import "MATileOverlay.h" +#import "MABaseOverlay.h" + +@interface MAMVTTileOverlayOptions : NSObject +@property (nonatomic, copy) NSString *url; // URL +@property (nonatomic, copy) NSString *key; // key +@property (nonatomic, copy) NSString *Id; // id +@property (nonatomic, assign) BOOL visible; // 是否可见 默认YES Visible, default YES +@end + +/// MVT瓦片数据 MVT tile data +@interface MAMVTTileOverlay : MATileOverlay +/// MVT配置选项 MVT configuration options +@property (nonatomic, strong, readonly) MAMVTTileOverlayOptions *option; ++ (instancetype)mvtTileOverlayWithOption:(MAMVTTileOverlayOptions *)option; +@end +#endif +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMVTTileOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMVTTileOverlayRenderer.h new file mode 100644 index 0000000..acb47ae --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMVTTileOverlayRenderer.h @@ -0,0 +1,22 @@ +// +// MAMVTTileOverlayRenderer.h +// MapKit_static +// +// Created by Li Fei on 11/25/13. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OVERLAY_TILE +#if FEATURE_MVT +#import "MATileOverlayRenderer.h" +#import "MAMVTTileOverlay.h" + +/// 此类是将MAMVTOverlayRenderer中的覆盖tiles显示在地图上的Renderer +/// This class is a Renderer that displays the overlay tiles from MAMVTOverlayRenderer on the map +@interface MAMVTTileOverlayRenderer : MATileOverlayRenderer +@end + +#endif +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapAccessibilityIdentifier.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapAccessibilityIdentifier.h new file mode 100644 index 0000000..43b5e09 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapAccessibilityIdentifier.h @@ -0,0 +1,27 @@ +// +// MAMapAccessibilityIdentifier.h +// MAMapKit +// +// Created by hanxiaoming on 17/1/9. +// Copyright © 2017年 Amap. All rights reserved. +// + +#ifndef MAMapAccessibilityIdentifier_h +#define MAMapAccessibilityIdentifier_h + +#define kMAAcceIdForMapView @"mamapview" +#define kMAAcceIdForRender @"marender" +#define kMAAcceIdForLogoView @"malogo" + +#define kMAAcceIdForAnnotationContainer @"maannotationcontainer" +#define kMAAcceIdForOverlayContainer @"maoverlaycontainer" +#define kMAAcceIdForAnnotationView @"maannotationview" +#define kMAAcceIdForCalloutView @"macalloutview" + +#define kMAAcceIdForUserLocationView @"mauserlocationview" + +#define kMAAcceIdForScaleView @"mascaleview" +#define kMAAcceIdForCompassView @"macompassview" +#define kMAAcceIdForIndoorView @"maindoorview" + +#endif /* MAMapAccessibilityIdentifier_h */ diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapCustomStyleOptions.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapCustomStyleOptions.h new file mode 100644 index 0000000..cc54f7d --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapCustomStyleOptions.h @@ -0,0 +1,34 @@ +// +// MAMapCustomStyleOptions.h +// MAMapKit +// +// Created by ldj on 2018/11/27. +// Copyright © 2018 Amap. All rights reserved. +// + +#import + +@interface MAMapCustomStyleOptions : NSObject + +///自定义样式二进制 +///Custom style binary +@property (nonatomic, strong) NSData *styleData; + +///海外自定义样式文件路径 +///Overseas custom style file path +@property (nonatomic, strong) NSString *styleDataOverseaPath; + +///设置地图自定义样式对应的styleID,从官网获取 +///Set the styleID corresponding to the map custom style, obtained from the official website +@property (nonatomic, strong) NSString *styleId; + +///设置自定义纹理文件二进制 +///Set custom texture file binary +@property (nonatomic, strong) NSData *styleTextureData; + +///样式额外的配置,比如路况,背景颜色等 since 6.7.0 +///Additional style configurations, such as traffic conditions, background color, etc. since 6.7.0 +@property (nonatomic, strong) NSData *styleExtraData; + +@end + diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapKit.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapKit.h new file mode 100644 index 0000000..86119f2 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapKit.h @@ -0,0 +1,84 @@ +// +// MAMapKit.h +// MAMapKit +// +// Created by 翁乐 on 12/2/15. +// Copyright © 2015 Amap. All rights reserved. +// + +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import + +#import +#import + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import + +#import +#import +#import +#import +#import +#import +#import + +#if __has_include() +#import +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapSnapshot.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapSnapshot.h new file mode 100644 index 0000000..c18e6b6 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapSnapshot.h @@ -0,0 +1,64 @@ +// +// MAMapSnapshot.h +// MAMapKit +// +// Created by ZhaoRui on 2022/3/30. +// Copyright © 2022 Amap. All rights reserved. +// + +#import "MAMapKit/MAMapKit.h" + +@interface MAMapSnapshotModel : NSObject + +@property (nonatomic, assign) CGSize size; +@property (nonatomic, assign) CGPoint position; +@property (nonatomic, assign) MAMapPoint tlPoint; +@property (nonatomic, assign) MAMapPoint trPoint; +@property (nonatomic, strong) UIImage* image; + +@end + +@interface MAMapSnapshot : NSObject + +@property (nonatomic, readonly) CGSize minSize; +@property (nonatomic, readonly) CGSize maxSize; + +- (instancetype)init:(MAMapView*)mapview; + +/** + * @brief 异步在指定区域内截图(默认会包含该区域内的annotationView), 地图载入完整时回调 + * Asynchronously capture a screenshot within the specified area (by default, it will include the annotationView within that area), callback when the map is fully loaded + * @param size 指定的区域 + * The specified area + * @param tl 左上 + * Top left + * @param tr 右上 + * Top right + * @param block 回调block(resultImages:返回的图片集合,state:0载入不完整,1完整) + * Callback block(resultImages: returned image collection, state: 0 incomplete loading, 1 complete) + */ +typedef void(^CaptureResultBlock)(NSArray *resultImages, NSInteger state); +- (BOOL)captureBigPicture:(CGSize)pixelSize + topLeft:(CLLocationCoordinate2D)tl + topRight:(CLLocationCoordinate2D)tr + complete:(CaptureResultBlock)block; +/** + * @brief 异步在指定区域内截图(默认会包含该区域内的annotationView), 地图载入完整时回调 + * Asynchronously capture a screenshot within the specified area (by default, it will include the annotationView within that area), callback when the map is fully loaded + * @param size 指定的区域 + * The specified area + * @param tl 左上 + * Top left + * @param tr 右上 + * Top right + * @param block 回调block(resultImage:返回的图片,state:0载入不完整,1完整) + * Callback block(resultImage: returned image, state: 0 incomplete loading, 1 complete) + */ +typedef void(^Observer)(UIImage *resultImage, NSInteger state); +- (BOOL)capture:(CGSize)size + topLeft:(CLLocationCoordinate2D)tl + topRight:(CLLocationCoordinate2D)tr + complete:(Observer)block; + +@end + diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapStatus.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapStatus.h new file mode 100644 index 0000000..a9663d3 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapStatus.h @@ -0,0 +1,83 @@ +// +// MAMapStatus.h +// MapKit_static +// +// Created by yi chen on 1/27/15. +// Copyright © 2016 Amap. All rights reserved. +// + + + +#import "MAConfig.h" +#import +#import + +///地图状态对象 +///Map status object +@interface MAMapStatus : NSObject + +///地图的中心点,改变该值时,地图的比例尺级别不会发生变化 +///The center point of the map, when this value is changed, the scale level of the map will not change +@property (nonatomic) CLLocationCoordinate2D centerCoordinate; + +///缩放级别 +///Zoom level +@property (nonatomic) CGFloat zoomLevel; + +///设置地图旋转角度(逆时针为正向), 单位度, [0,360) +///Set the rotation angle of the map (counterclockwise is positive), in degrees, [0,360) +@property (nonatomic) CGFloat rotationDegree; + +///设置地图相机角度(范围为[0.f, 45.f]) +///Set the map camera angle (range [0.f, 45.f]) +@property (nonatomic) CGFloat cameraDegree; + +///地图的视图锚点。坐标系归一化,(0, 0)为MAMapView左上角,(1, 1)为右下角。默认为(0.5, 0.5),即当前地图的视图中心 +///The view anchor point of the map. The coordinate system is normalized, with (0, 0) at the top-left corner of MAMapView and (1, 1) at the bottom-right corner. Default is (0.5, 0.5), which is the center of the current map view. +@property (nonatomic) CGPoint screenAnchor; + +/** + * @brief 根据指定参数生成对应的status + * Generate the corresponding status based on the specified parameters + * @param coordinate 地图的中心点,改变该值时,地图的比例尺级别不会发生变化 + * The center point of the map, changing this value will not affect the map's scale level + * @param zoomLevel 缩放级别 + * Zoom level + * @param rotationDegree 设置地图旋转角度(逆时针为正向) + * Set the map rotation angle (counterclockwise is positive) + * @param cameraDegree 设置地图相机角度(范围为[0.f, 45.f]) + * Set the map camera angle (range [0.f, 45.f]) + * @param screenAnchor 地图的视图锚点。坐标系归一化,(0, 0)为MAMapView左上角,(1, 1)为右下角。默认为(0.5, 0.5),即当前地图的视图中心 + * The view anchor point of the map. The coordinate system is normalized, with (0, 0) being the top-left corner of MAMapView and (1, 1) being the bottom-right corner. Default is (0.5, 0.5), which is the center of the current map view. + * @return 生成的Status + * Generated Status + */ ++ (instancetype)statusWithCenterCoordinate:(CLLocationCoordinate2D)coordinate + zoomLevel:(CGFloat)zoomLevel + rotationDegree:(CGFloat)rotationDegree + cameraDegree:(CGFloat)cameraDegree + screenAnchor:(CGPoint)screenAnchor; + +/** + * @brief 根据指定参数初始化对应的status + * Initialize the corresponding status according to the specified parameters + * @param coordinate 地图的中心点,改变该值时,地图的比例尺级别不会发生变化 + * The center point of the map, changing this value will not affect the map's scale level + * @param zoomLevel 缩放级别 + * Zoom level + * @param rotationDegree 设置地图旋转角度(逆时针为正向) + * Set the map rotation angle (counterclockwise is positive) + * @param cameraDegree 设置地图相机角度(范围为[0.f, 45.f]) + * Set the map camera angle (range [0.f, 45.f]) + * @param screenAnchor 地图的视图锚点。坐标系归一化,(0, 0)为MAMapView左上角,(1, 1)为右下角。默认为(0.5, 0.5),即当前地图的视图中心 + * The view anchor point of the map. The coordinate system is normalized, with (0, 0) being the top-left corner of MAMapView and (1, 1) being the bottom-right corner. Default is (0.5, 0.5), which is the center of the current map view. + * @return 生成的Status + * Generated Status + */ +- (id)initWithCenterCoordinate:(CLLocationCoordinate2D)coordinate + zoomLevel:(CGFloat)zoomLevel + rotationDegree:(CGFloat)rotationDegree + cameraDegree:(CGFloat)cameraDegree + screenAnchor:(CGPoint)screenAnchor; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapVersion.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapVersion.h new file mode 100644 index 0000000..4e7e1e7 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapVersion.h @@ -0,0 +1,27 @@ +// +// MAMapVersion.h +// MAMapKit +// +// Created by yi chen on 2/24/16. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import + +#ifndef MAMapVersion_h +#define MAMapVersion_h + +#define MAMapVersionNumber 11010200 +#define MAMapMinRequiredFoundationVersion 10807 + +// 依赖库版本检测 +#if AMapFoundationVersionNumber < MAMapMinRequiredFoundationVersion +#error "The AMapFoundationKit version is less than minimum required, please update! Any questions please to visit http://lbs.amap.com" +#endif + +FOUNDATION_EXTERN NSString * const MAMapKitVersion; +FOUNDATION_EXTERN NSString * const MAMapKitName; + + +#endif /* MAMapVersion_h */ diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapView+Resource.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapView+Resource.h new file mode 100644 index 0000000..6c05e48 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapView+Resource.h @@ -0,0 +1,24 @@ +// +// MAMapView+Resource.h +// MAMapKit +// +// Created by caowei on 2025/4/8. +// Copyright © 2025 Amap. All rights reserved. +// + +#import "MAMapView.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MAMapView (Resource) + +/// 设置地图资源路径 +/// @note 在初始化地图前使用 +/// - Parameter path: Amap.bundle的路径 +/// - Returns: 返回值 0:成功 1:版本号校验失败 2 路径不存在 +/// @since 10.5.0 ++ (NSInteger)setBundlePath:(NSString *)path; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapView.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapView.h new file mode 100644 index 0000000..e0d3ebf --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMapView.h @@ -0,0 +1,1600 @@ +// +// MAMapView.h +// MAMapKit +// +// Created by 翁乐 on 3/17/16. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import +#import "MAOverlay.h" +#import "MAOverlayRenderer.h" +#import "MAAnnotationView.h" +#import "MACircle.h" +#import "MAUserLocation.h" +#import "MAMapStatus.h" +#import "MAIndoorInfo.h" +#import "MAUserLocationRepresentation.h" +#import "MAMapCustomStyleOptions.h" +#import +#import "MABaseEngineOverlay.h" +#import "MAPoiFilter.h" + +///地图类型 +///Map Types +typedef NS_ENUM(NSInteger, MAMapType) +{ + MAMapTypeStandard = 0, ///< 普通地图 Standard Map + MAMapTypeSatellite, ///< 卫星地图 Satellite Map + MAMapTypeStandardNight, ///< 夜间视图 Night View + MAMapTypeNavi, ///< 导航视图 Navigation View + MAMapTypeBus, ///< 公交视图 Transit View + MAMapTypeNaviNight ///< 导航夜间视图 Navigation Night View +}; + +///用户跟踪模式 +///User Tracking Mode +typedef NS_ENUM(NSInteger, MAUserTrackingMode) +{ + MAUserTrackingModeNone = 0, ///< 不追踪用户的location更新 Do Not Track User's Location Updates + MAUserTrackingModeFollow = 1, ///< 追踪用户的location更新 Track User's Location Updates + MAUserTrackingModeFollowWithHeading = 2 ///< 追踪用户的location与heading更新 Track user's location and heading updates +}; + +///交通拥堵状态 +///Traffic congestion status +typedef NS_ENUM(NSInteger, MATrafficStatus) +{ + MATrafficStatusSmooth = 1, ///< 1 通畅 Smooth + MATrafficStatusSlow, ///< 2 缓行 Slow-moving + MATrafficStatusJam, ///< 3 阻塞 Blocked + MATrafficStatusSeriousJam, ///< 4 严重阻塞 Severely blocked + MATrafficStatusExtremelySmooth, ///< 5 极度通畅 Extremely smooth +}; + +///绘制overlay的层级 +///Draw overlay layers +typedef NS_ENUM(NSInteger, MAOverlayLevel) { + MAOverlayLevelAboveRoads = 0, ///< 在地图底图标注和兴趣点图标之下绘制overlay Draw overlay below map base annotations and POI icons + MAOverlayLevelAboveLabels ///< 在地图底图标注和兴趣点图标之上绘制overlay Draw overlay on top of map base annotations and POI icons +}; + +///比例尺单位 +///Enumeration for scale control units +///@since 11.1.200 +typedef NS_ENUM(NSInteger, MAScaleControlsUnit){ + MAScaleControlsUnitMETRIC, ///< 公制 METRIC + MAScaleControlsUnitIMPERIAL ///< 英制 IMPERIAL +}; + +#pragma mark - 动画相关的key Animation-related key +///中心点(MAMapPoint)key, 封装成[NSValue valueWithMAMapPoint:] +///Center point (MAMapPoint) key, encapsulated as [NSValue valueWithMAMapPoint:] +extern NSString * const kMAMapLayerCenterMapPointKey; + +///缩放级别key, 范围[minZoomLevel, maxZoomLevel], 封装成NSNumber +///Zoom level key, range [minZoomLevel, maxZoomLevel], encapsulated as NSNumber +extern NSString * const kMAMapLayerZoomLevelKey; + +///旋转角度key, 范围[0, 360), 封装成NSNumber +///Rotation angle key, range [0, 360), encapsulated as NSNumber +extern NSString * const kMAMapLayerRotationDegreeKey; + +///摄像机俯视角度, 范围[0, 45], 封装成NSNumber +///Camera tilt angle, range [0, 45], encapsulated as NSNumber +extern NSString * const kMAMapLayerCameraDegreeKey; + + +@protocol MAMapViewDelegate; + +@interface MAMapView : UIView + +///标记是否开启metal,默认NO. 注意:因机型或系统限制(要求机型最低5S,系统最低iOS10),开启metal可能失败。 +///Flag indicating whether to enable metal, default is NO. Note: Due to device or system limitations (minimum device requirement: 5S, minimum iOS version: iOS10), enabling metal may fail. +@property (nonatomic, assign, class) BOOL metalEnabled; + +///是否打开地形图,默认NO. 注意:需在地图创建前设置 (since 8.2.0) +///Whether to enable the terrain map, default is NO. Note: It needs to be set before the map is created.(since 8.2.0) +@property (nonatomic, assign, class) BOOL terrainEnabled; + +///是否有地形图授权,默认YES. 如果没有授权会回调MAMapViewDelegate的 - (void)mapView:(MAMapView *)mapView didFailLoadTerrainWithError:(NSError *)error 方法 (since 9.3.1) +///Whether there is terrain map authorization, default is YES. If there is no authorization, the - (void)mapView:(MAMapView *)mapView didFailLoadTerrainWithError:(NSError *)error method of MAMapViewDelegate will be called back. (since 9.3.1) +@property (nonatomic, assign, readonly) BOOL terrainAuth; + +///地图view的delegate +///Delegate of the map view +@property (nonatomic, weak) id delegate; + +///地图类型。注意:自定义样式优先级高于mapType,如开启了自定义样式,要关闭自定义样式后mapType才生效 +///Map type. Note: Custom styles take precedence over mapType. If a custom style is enabled, mapType will only take effect after the custom style is turned off. +@property (nonatomic) MAMapType mapType; + +///当前地图的中心点,改变该值时,地图的比例尺级别不会发生变化 +///The center point of the current map; when this value is changed, the scale level of the map will not change. +@property (nonatomic) CLLocationCoordinate2D centerCoordinate; + +///当前地图的经纬度范围,设定的该范围可能会被调整为适合地图窗口显示的范围 +///The latitude and longitude range of the current map, the set range may be adjusted to fit the display window of the map +@property (nonatomic) MACoordinateRegion region; + +///可见区域, 设定的该范围可能会被调整为适合地图窗口显示的范围 +///Visible area, the set range may be adjusted to fit the map window display +@property (nonatomic) MAMapRect visibleMapRect; + +///设置可见地图区域的矩形边界,如限制地图只显示北京市范围 +///Set the rectangular boundaries of the visible map area, such as limiting the map to display only the Beijing area +@property (nonatomic, assign) MACoordinateRegion limitRegion; + +///设置可见地图区域的矩形边界,如限制地图只显示北京市范围 +///Set the rectangular boundaries of the visible map area, such as limiting the map to display only the Beijing area +@property (nonatomic, assign) MAMapRect limitMapRect; + +///缩放级别(默认3-19,有室内地图时为3-20) +///Zoom level (default 3-19, 3-20 when indoor maps are available) +@property (nonatomic) CGFloat zoomLevel; + +///最小缩放级别 +///Minimum zoom level +@property (nonatomic) CGFloat minZoomLevel; + +///最大缩放级别(有室内地图时最大为20,否则为19) +///Maximum zoom level (20 when indoor maps are available, otherwise 19) +@property (nonatomic) CGFloat maxZoomLevel; + +///设置地图旋转角度(逆时针为正向) +///Set map rotation angle (counterclockwise is positive) +@property (nonatomic) CGFloat rotationDegree; + +///设置地图相机角度(范围为[0.f, 60.f],但高于40度的角度需要在16级以上才能生效) +///Set the map camera angle (range is [0.f, 60.f], but angles above 40 degrees require level 16 or higher to take effect) +@property (nonatomic) CGFloat cameraDegree; + +///是否以screenAnchor点作为锚点进行缩放,默认为YES。如果为NO,则以手势中心点作为锚点 +///Whether to use the screenAnchor point as the anchor for scaling, default is YES. If NO, the gesture center point is used as the anchor. +@property (nonatomic, assign) BOOL zoomingInPivotsAroundAnchorPoint; + +///是否支持缩放, 默认YES +///Whether to support zoom, default YES +@property (nonatomic, getter = isZoomEnabled) BOOL zoomEnabled; + +///是否支持平移, 默认YES +///Whether to support pan, default YES +@property (nonatomic, getter = isScrollEnabled) BOOL scrollEnabled; + +///是否支持旋转, 默认YES +///Whether rotation is supported, default YES +@property (nonatomic, getter = isRotateEnabled) BOOL rotateEnabled; + +///是否支持camera旋转, 默认YES +///Whether camera rotation is supported, default YES +@property (nonatomic, getter = isRotateCameraEnabled) BOOL rotateCameraEnabled; + +///是否显示楼块,默认为YES +///Whether to show building blocks, default YES +@property (nonatomic, getter = isShowsBuildings) BOOL showsBuildings; + +///是否显示底图标注, 默认为YES +///Whether to show basemap labels, default YES +@property (nonatomic, assign, getter = isShowsLabels) BOOL showsLabels; + +///是否显示交通路况图层, 默认为NO +///Whether to display the traffic layer, default is NO +@property (nonatomic, getter = isShowTraffic) BOOL showTraffic; + +///设置实时交通颜色,key为 MATrafficStatus +///set real-time traffic color, key is MATrafficStatus +@property (nonatomic, copy) NSDictionary *trafficStatus __attribute((deprecated("Deprecated since 7.8.0"))); + +///是否支持单击地图获取POI信息(默认为YES), 对应的回调是 -(void)mapView:(MAMapView *) didTouchPois:(NSArray *) +///Is it possible to click on the map to get POI information (default is YES), the corresponding callback is -(void)mapView:(MAMapView *) didTouchPois:(NSArray *) +@property (nonatomic, assign) BOOL touchPOIEnabled; + +///是否显示指南针, 默认YES +///whether to display the compass, default is YES +@property (nonatomic, assign) BOOL showsCompass; + +///指南针原点位置 +///compass origin position +@property (nonatomic, assign) CGPoint compassOrigin; + +///指南针的宽高 +///Compass width and height +@property (nonatomic, readonly) CGSize compassSize; + +///设置比例尺单位 +///Set the unit used by the scale control +///@since 11.1.200 +@property (nonatomic, assign) MAScaleControlsUnit scaleControlsUnit; + +///是否显示比例尺, 默认YES +///Whether to display the scale bar, default is YES +@property (nonatomic, assign) BOOL showsScale; + +///比例尺原点位置 +///Scale bar origin position +@property (nonatomic, assign) CGPoint scaleOrigin; + +///比例尺的最大宽高 +///Maximum width and height of the scale bar +@property (nonatomic, readonly) CGSize scaleSize; + +///logo位置, 必须在mapView.bounds之内,否则会被忽略 +///Logo position, must be within mapView.bounds, otherwise it will be ignored +@property (nonatomic, assign) CGPoint logoCenter; + +///logo的宽高 +///The width and height of the logo +@property (nonatomic, readonly) CGSize logoSize; + +///在当前缩放级别下, 基于地图中心点, 1 screen point 对应的距离(单位是米) +///At the current zoom level, based on the map center point, the distance corresponding to 1 screen point (in meters) +@property (nonatomic, readonly) double metersPerPointForCurrentZoom; + +///标识当前地图中心位置是否在中国范围外。此属性不是精确判断,不能用于边界区域 +///Indicates whether the current map center location is outside of China. This property is not an accurate judgment and should not be used for border areas. +@property (nonatomic, readonly) BOOL isAbroad; + +///最大帧数,有效的帧数为:60、30、20、10等能被60整除的数。默认为60 +///Maximum frame rate, valid frame rates are numbers divisible by 60 such as 60, 30, 20, 10. Default is 60 +@property (nonatomic, assign) NSUInteger maxRenderFrame; + +///是否允许降帧,默认为YES +///Whether to allow frame rate reduction, default is YES +@property (nonatomic, assign) BOOL isAllowDecreaseFrame; + +///停止/开启 OpenGLES绘制, 默认NO. 对应回调是 - (void)mapView:(MAMapView *) didChangeOpenGLESDisabled:(BOOL) +///Stop/Start OpenGLES rendering, default NO. The corresponding callback is - (void)mapView:(MAMapView *) didChangeOpenGLESDisabled:(BOOL) +@property (nonatomic, assign) BOOL openGLESDisabled __attribute((deprecated("Deprecated, since 7.9.0, please use the renderringDisabled property"))); + +///停止/开启 地图绘制, 默认NO. +///Stop/Start map drawing, default NO +@property (nonatomic, assign) BOOL renderringDisabled; + +///是否强制关闭render,默认NO.地图元素完全不创建 +//disable map drawing, default NO +@property (nonatomic, assign) BOOL drawingDisabled; + +///地图的视图锚点。坐标系归一化,(0, 0)为MAMapView左上角,(1, 1)为右下角。默认为(0.5, 0.5),即当前地图的视图中心 (since 5.0.0) +///The anchor point of the map view. The coordinate system is normalized, (0, 0) is the top-left corner of MAMapView, (1, 1) is the bottom-right corner. Default is (0.5, 0.5), which is the center of the current map view.(since 5.0.0) +@property (nonatomic, assign) CGPoint screenAnchor; + +/** + * @brief 瓦片加载回调 + * @param success 加载是否成功 + * @param durationMs 耗时(毫秒) + * @param tileIdentifier 瓦片唯一标识 + */ + +typedef void (^MATileLoadCallback)(BOOL success, int64_t durationMs, NSString *tileIdentifier); + +@property (nonatomic, copy) MATileLoadCallback tileLoadCallback; + +///地图渲染的runloop mode,默认为NSRunLoopCommonModes。如果是和UIScrollView一起使用且不希望地图在scrollView拖动时渲染,请设置此值为NSDefaultRunLoopMode。(since 5.1.0) +///The runloop mode for map rendering, default is NSRunLoopCommonModes. If used with UIScrollView and you don't want the map to render during scrollView dragging, set this value to NSDefaultRunLoopMode.(since 5.1.0) +@property (nonatomic, copy) NSRunLoopMode runLoopMode; + +///设置语言。中文:@0: 英文:@1. 英文使用注意事项:1、不能和自定义地图样式同时使用;2、英文状态只在MAMapTypeStandard生效 +///Set language. Chinese:@0: English:@1. Notes for English usage: 1. Cannot be used with custom map styles simultaneously; 2. English mode only takes effect in MAMapTypeStandard +#if MA_INCLUDE_WORLD_EN_MAP +@property (nonatomic, strong) NSNumber *mapLanguage; +#endif + +///语言类型 +///language type +@property (nonatomic, assign, class) AMapRegionLanguageType regionLanguageType; + +/// 是否显示国际图 +/// Whether to display the international map +@property (nonatomic, assign, class) BOOL loadWorldVectorMap; + +/** + * @brief 导航模式下初始化地图,当需要将地图传给导航SDK时,请使用此方法创建地图,并设置isNavi为YES + * Initialize the map in navigation mode. When you need to pass the map to the navigation SDK, use this method to create the map and set isNavi to YES + * @param frame 地图 frame + * map frame + * @param isNavi 是否是导航模式 + * whether it is in navigation mode + */ +- (instancetype)initWithFrame:(CGRect)frame isNavi:(BOOL)isNavi; + +/** + * @brief 强制指定scaleFactor方式来初始化地图,原initWithFrame:方法自动根据系统scale初始化 + * @param frame 如系统initWithFrame:参数 + * @param scaleFactor 强制指定屏幕scaleFactor.0.0是走系统默认值,非0值生效。支持范围是[0-3] + * since 10.5.2.03 + */ +- (instancetype)initWithFrame:(CGRect)frame scaleFactor:(CGFloat)scaleFactor; + + +/** + * @brief 设定当前地图的经纬度范围,该范围可能会被调整为适合地图窗口显示的范围 + * Set the latitude and longitude range of the current map, which may be adjusted to fit the map window + * @param region 要设定的经纬度范围 + * The latitude and longitude range to be set + * @param animated 是否动画设置 + * Whether to set animation + */ +- (void)setRegion:(MACoordinateRegion)region animated:(BOOL)animated; + +/** + * @brief 根据当前地图视图frame的大小调整region范围 + * Adjust the region range according to the size of the current map view frame + * @param region 要调整的经纬度范围 + * The latitude and longitude range to be adjusted + * @return 调整后的经纬度范围 + * The adjusted latitude and longitude range + */ +- (MACoordinateRegion)regionThatFits:(MACoordinateRegion)region; + +/** + * @brief 设置可见区域 + * Set the visible region + * @param mapRect 要设定的可见区域 + * The visible region to be set + * @param animated 是否动画设置 + * Whether to set animation + */ +- (void)setVisibleMapRect:(MAMapRect)mapRect animated:(BOOL)animated; + +/** + * @brief 重新计算可见地图矩形区域,使之匹配mapview长宽比 + * Recalculate the visible map rectangle area to match the mapview aspect ratio + * @param mapRect 要调整的地图矩形区域 + * the map rectangle area to be adjusted + * @return 调整后的地图矩形区域 + * the adjusted map rectangle area + */ +- (MAMapRect)mapRectThatFits:(MAMapRect)mapRect; + +/** + * @brief 根据边缘插入来调整地图矩形区域,使之匹配mapview加insets后的长宽比 + * Adjust the map rectangular area according to the edge insertion to match the aspect ratio of the mapview with insets + * @param mapRect 要调整的地图矩形区域 + * the map rectangular area to be adjusted + * @param insets 边缘插入 + * edge insertion + * @return 调整后的地图矩形区域 + * the adjusted map rectangle area + */ +- (MAMapRect)mapRectThatFits:(MAMapRect)mapRect edgePadding:(UIEdgeInsets)insets; + +/** + * @brief 设置可见地图矩形区域 + * Set visible map rectangle area + * @param insets 边缘插入 + * edge insertion + * @param mapRect 要显示的地图矩形区域 + * The map rectangle area to display + * @param animated 是否动画效果 + * Whether to enable animation + */ +- (void)setVisibleMapRect:(MAMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated; + +/** + * @brief 设置可见地图矩形区域 + * Set visible map rectangle area + * @param insets 边缘插入 + * edge insertion + * @param mapRect 要显示的地图矩形区域 + * The map rectangle area to display + * @param animated 是否动画效果 + * Whether to enable animation + * @param duration 动画时长,单位秒 + * Animation duration in seconds + */ +- (void)setVisibleMapRect:(MAMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated duration:(CFTimeInterval)duration; + +/** + * @brief 设置当前地图的中心点,改变该值时,地图的比例尺级别不会发生变化 + * Set the center point of the current map. When this value is changed, the scale level of the map will not change + * @param coordinate 要设置的中心点 + * The center point to set + * @param animated 是否动画设置 + * Whether to animate the setting + */ +- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated; + +/** + * @brief 设置缩放级别(默认3-19,有室内地图时为3-20) + * Set the zoom level (default 3-19, 3-20 when indoor maps are available) + * @param zoomLevel 要设置的缩放级别 + * The zoom level to be set + * @param animated 是否动画设置 + * Whether to animate the setting + */ +- (void)setZoomLevel:(CGFloat)zoomLevel animated:(BOOL)animated; + +/** + * @brief 根据指定的枢纽点来缩放地图 + * Zoom the map based on the specified pivot point + * @param zoomLevel 缩放级别 + * Zoom level + * @param pivot 枢纽点(基于地图view的坐标系) + * Pivot point (based on the map view coordinate system) + * @param animated 是否动画 + * Whether to animate + */ +- (void)setZoomLevel:(CGFloat)zoomLevel atPivot:(CGPoint)pivot animated:(BOOL)animated; + +/** + * @brief 设置地图旋转角度(逆时针为正向) + * Set the map rotation angle (counterclockwise is positive) + * @param rotationDegree 旋转角度, 如当前角度是0,720表示逆时针旋转2周,-720表示正时针旋转2周 + * Rotation angle, if the current angle is 0, 720 means rotating counterclockwise for 2 rounds, -720 means rotating clockwise for 2 rounds + * @param animated 动画 + * Animation + * @param duration 动画时间 + * Animation duration + */ +- (void)setRotationDegree:(CGFloat)rotationDegree animated:(BOOL)animated duration:(CFTimeInterval)duration; + +/** + * @brief 设置地图相机角度(范围为[0.f, 60.f],但高于40度的角度需要在16级以上才能生效) + * Set map camera angle (range [0.f, 60.f], but angles above 40 degrees require level 16 or higher to take effect) + * @param cameraDegree 要设置的相机角度 + * Camera angle to set + * @param animated 是否动画 + * Whether to animate + * @param duration 动画时间 + * Animation duration + */ +- (void)setCameraDegree:(CGFloat)cameraDegree animated:(BOOL)animated duration:(CFTimeInterval)duration; + +/** + * @brief 获取地图状态 + * Get map status + * @return 地图状态 + * Map status + */ +- (MAMapStatus *)getMapStatus; + + +/** + * @brief 设置地图状态 + * Set map status + * @param status 要设置的地图状态 + * Map status to be set + * @param animated 是否动画 + * Whether to animate + */ +- (void)setMapStatus:(MAMapStatus *)status animated:(BOOL)animated; + +/** + * @brief 设置地图状态 + * Set map status + * @param status 要设置的地图状态 + * Map status to be set + * @param animated 是否动画 + * Whether to animate + * @param duration 动画时间,默认动画时间为0.35s + * Animation time, default animation time is 0.35s + */ +- (void)setMapStatus:(MAMapStatus *)status + animated:(BOOL)animated + duration:(CFTimeInterval)duration; + +/** + * @brief 设置指南针的图片 + * Set compass image + * @param image 新的指南针图片 + * New compass image + */ +- (void)setCompassImage:(UIImage *)image; + +/** + * @brief 在指定区域内截图(默认会包含该区域内的annotationView),注意不要在地图回调方法内直接调用 + * Take screenshot in specified area (annotationView within the area will be included by default). Note: Do not call directly within map callback methods + * @param rect 指定的区域 + * Specified area + * @return 截图image + * Screenshot image + */ +- (UIImage *)takeSnapshotInRect:(CGRect)rect __attribute((deprecated("Deprecated, please use the takeSnapshotInRect:withCompletionBlock: method since 6.0.0"))); + +/** + * @brief 异步在指定区域内截图(默认会包含该区域内的annotationView), 地图载入完整时回调 + * Asynchronously capture a screenshot within the specified area (by default includes the annotationView within that area), callback when the map is fully loaded + * @param rect 指定的区域 + * Specified area + * @param block 回调block(resultImage:返回的图片,state:0载入不完整,1完整) + * Callback block(resultImage: returned image, state: 0 incomplete loading, 1 complete) + */ +- (void)takeSnapshotInRect:(CGRect)rect withCompletionBlock:(void (^)(UIImage *resultImage, NSInteger state))block; + +/** + * @brief 异步在指定区域内截图(默认会包含该区域内的annotationView), 地图载入完整时回调 (since 7.8.0) + * Asynchronously capture a screenshot within the specified area (by default includes the annotationView within the area), callback when the map is fully loaded (since 7.8.0) + * @param rect 指定的区域 + * Specified area + * @param timeout 超时时间 + * timeout + * @param block 回调block(resultImage:返回的图片,state:0载入不完整,1完整) + * Callback block(resultImage: returned image, state: 0 incomplete loading, 1 complete) + */ +- (void)takeSnapshotInRect:(CGRect)rect timeoutInterval:(NSTimeInterval)timeout completionBlock:(void (^)(UIImage *resultImage, NSInteger state))block; + +/** + * @brief 在指定的缩放级别下, 基于地图中心点, 1 screen point 对应的距离(单位是米). + * At a specified zoom level, based on the map center point, the distance corresponding to 1 screen point (in meters) + * @param zoomLevel 指定的缩放级别, 在[minZoomLevel, maxZoomLevel]范围内. + * the specified zoom level is within the [minZoomLevel, maxZoomLevel] range. + * @return 对应的距离(单位是米) + * Corresponding distance (unit is meters) + */ +- (double)metersPerPointForZoomLevel:(CGFloat)zoomLevel; + +/** + * @brief 将经纬度转换为指定view坐标系的坐标 + * Convert latitude and longitude to coordinates in the specified view coordinate system + * @param coordinate 经纬度 + * Latitude and longitude + * @param view 指定的view + * Specified view + * @return 基于指定view坐标系的坐标 + * Coordinates based on the specified view coordinate system + */ +- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(UIView *)view; + +/** + * @brief 将指定view坐标系的坐标转换为经纬度 + * Convert coordinates in the specified view coordinate system to latitude and longitude + * @param point 指定view坐标系的坐标 + * Coordinates in the specified view coordinate system + * @param view 指定的view + * Specified view + * @return 经纬度 + * Latitude and longitude + */ +- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view; + +/** + * @brief 将经纬度region转换为指定view坐标系的rect + * Convert latitude and longitude region to rect in the specified view coordinate system + * @param region 经纬度region + * Latitude and longitude region + * @param view 指定的view + * Specified view + * @return 指定view坐标系的rect + * Specify the rect of the view coordinate system + */ +- (CGRect)convertRegion:(MACoordinateRegion)region toRectToView:(UIView *)view; + +/** + * @brief 将指定view坐标系的rect转换为经纬度region + * Convert the rect of the specified view coordinate system to a latitude and longitude region + * @param rect 指定view坐标系的rect + * Specify the rect of the view coordinate system + * @param view 指定的view + * Specified view + * @return 经纬度region + * Latitude and longitude region + */ +- (MACoordinateRegion)convertRect:(CGRect)rect toRegionFromView:(UIView *)view; + +/** + * @brief 重新加载地图 + * Reload the map + * + * 将离线地图解压到 Documents/3dvmap/ 目录下后,调用此函数使离线数据生效, + * After extracting the offline map to the Documents/3dvmap/ directory, call this function to activate the offline data. + * 对应的回调分别是 offlineDataWillReload:(MAMapView *)mapView, offlineDataDidReload:(MAMapView *)mapView. + * The corresponding callbacks are offlineDataWillReload:(MAMapView *)mapView, offlineDataDidReload:(MAMapView *)mapView. + */ +- (void)reloadMap; + +/** + * @brief 清除所有磁盘上缓存的地图数据(不包括离线地图) + * Clear all cached map data on disk (excluding offline maps) + */ +- (void)clearDisk; + +/** + * @brief 重新加载内部纹理,在纹理被错误释放时可以执行此方法。(since 5.4.0) + * Reload internal textures, this method can be executed when textures are incorrectly released.(since 5.4.0) + */ +- (void)reloadInternalTexture; + +/** + * @brief 获取地图审图号。如果启用了“自定义样式”功能(customMapStyleEnabled 为 YES),则返回nil。(since 5.4.0) + * Obtain the map approval number. If the 'Custom Style' feature is enabled (customMapStyleEnabled is YES), return nil.(since 5.4.0) + * @return 地图审图号 + * Map approval number + */ +- (NSString *)mapContentApprovalNumber; + +/** + * @brief 获取卫星图片审图号。(since 5.4.0) + * Obtain satellite image approval number (since 5.4.0) + * @return 卫星图片审图号 + * Satellite image approval number + */ +- (NSString *)satelliteImageApprovalNumber; + +/** + * @brief 获取地形图审图号。(since 8.2.0) + * Obtain topographic map approval number(since 8.2.0) + * @return 地形图审图号 + * Topographic map approval number + */ +- (NSString *)terrainApprovalNumber; + +/** + * @brief 添加CAKeyframeAnimation动画。(since 6.0.0) + * Add CAKeyframeAnimation(since 6.0.0) + * @param mapCenterAnimation 地图中心点动画 + * Map center point animation + * @param zoomAnimation 放大缩小动画 + * Zoom in/out animation + * @param rotateAnimation 旋转动画 + * Rotation animation + * @param cameraDegreeAnimation 仰角动画 + * Elevation animation + */ +- (void)addAnimationWith:(CAKeyframeAnimation *)mapCenterAnimation + zoomAnimation:(CAKeyframeAnimation *)zoomAnimation + rotateAnimation:(CAKeyframeAnimation *)rotateAnimation + cameraDegreeAnimation:(CAKeyframeAnimation *)cameraDegreeAnimation; + +/** + * @brief 强制刷新。(since 6.0.0) + * Force refresh + */ +- (void)forceRefresh; + +/** + * @brief 设置在建道路图层是否显示。默认NO(since 7.7.0) + * Set whether the road under construction layer is displayed. Default NO(since 7.7.0) + * @param enabled 是否显示 + * Whether to display + */ +- (void)setConstructingRoadEnable:(BOOL)enabled; + +- (void)setMapOptRecordState:(BOOL)state; +#pragma mark - Privacy 隐私合规 Privacy compliance +/** + * @brief 更新App是否显示隐私弹窗的状态,隐私弹窗是否包含高德SDK隐私协议内容的状态. 注意:必须在MAMapView实例化之前调用 since 8.1.0 + * Update the status of whether the app displays a privacy popup and whether the popup includes the content of the Amap SDK privacy agreement. Note: This must be called before the MAMapView is instantiated. since 8.1.0 + * @param showStatus 隐私弹窗状态 + * Privacy pop-up status + * @param containStatus 包含高德SDK隐私协议状态 + * includes the status of the AMap SDK privacy agreement + */ ++ (void)updatePrivacyShow:(AMapPrivacyShowStatus)showStatus privacyInfo:(AMapPrivacyInfoStatus)containStatus; +/** + * @brief 更新用户授权高德SDK隐私协议状态. 注意:必须在MAMapView实例化之前调用 since 8.1.0 + * Updates the user's authorization status for the AMap SDK privacy agreement. Note: Must be called before the instantiation of MAMapView. since 8.1.0 + * @param agreeStatus 用户授权高德SDK隐私协议状态 + * User authorization status of the AMap SDK privacy agreement +*/ ++ (void)updatePrivacyAgree:(AMapPrivacyAgreeStatus)agreeStatus; + +@end + +@interface MAMapView (Annotation) + +///所有添加的标注, 注意从5.3.0开始返回数组内不再包含定位蓝点userLocation +///All added annotations, note that from version 5.3.0 the returned array no longer includes the location blue dot userLocation +@property (nonatomic, readonly) NSArray *annotations; + +///处于选中状态的标注数据数据(其count == 0 或 1) +///Annotation data in selected state (where count == 0 or 1) +@property (nonatomic, copy) NSArray *selectedAnnotations; + +///annotation 可见区域 +///annotation visible area +@property (nonatomic, readonly) CGRect annotationVisibleRect; + +/** + * @brief 向地图窗口添加标注,需要实现MAMapViewDelegate的-mapView:viewForAnnotation:函数来生成标注对应的View + * To add annotations to the map window, you need to implement the -mapView:viewForAnnotation: function of MAMapViewDelegate to generate the corresponding view for the annotation + * @param annotation 要添加的标注 + * Annotation to be added + */ +- (void)addAnnotation:(id )annotation; + +/** + * @brief 向地图窗口添加一组标注,需要实现MAMapViewDelegate的-mapView:viewForAnnotation:函数来生成标注对应的View + * To add a set of annotations to the map window, you need to implement the -mapView:viewForAnnotation: function of MAMapViewDelegate to generate the corresponding view for the annotation + * @param annotations 要添加的标注数组 + * Array of annotations to add + */ +- (void)addAnnotations:(NSArray *)annotations; + +/** + * @brief 移除标注 + * Remove annotation + * @param annotation 要移除的标注 + * Annotation to remove + */ +- (void)removeAnnotation:(id )annotation; + +/** + * @brief 移除一组标注 + * Remove a group of annotations + * @param annotations 要移除的标注数组 + * Array of annotations to remove + */ +- (void)removeAnnotations:(NSArray *)annotations; + +/** + * @brief 获取指定投影矩形范围内的标注 + * Get annotations within the specified projected rectangle + * @param mapRect 投影矩形范围 + * Projected rectangle bounds + * @return 标注集合 + * Annotation collection + */ +- (NSSet *)annotationsInMapRect:(MAMapRect)mapRect; + +/** + * @brief 根据标注数据获取标注view + * Get annotation view from annotation data + * @param annotation 标注数据 + * Annotation data + * @return 对应的标注view + * Corresponding annotation view + */ +- (MAAnnotationView *)viewForAnnotation:(id )annotation; + +/** + * @brief 从复用内存池中获取制定复用标识的annotation view + * Retrieve annotation view with specified reuse identifier from reuse memory pool + * @param identifier 复用标识 + * Reuse identifier + * @return annotation view + * annotation view + */ +- (MAAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier; + +/** + * @brief 选中标注数据对应的view。注意:如果annotation对应的annotationView因不在屏幕范围内而被移入复用池,为了完成选中操作,会将对应的annotationView添加到地图上,并将地图中心点移至annotation.coordinate的位置。 + * Select the view corresponding to the annotation data. Note: If the annotationView corresponding to the annotation is moved into the reuse pool because it is not within the screen range,To complete the selection operation, the corresponding annotationView will be added to the map, and the center point of the map will be moved to the position of annotation.coordinate. + * @param annotation 标注数据 + * Annotation data + * @param animated 是否有动画效果 + * Whether there is an animation effect + */ +- (void)selectAnnotation:(id )annotation animated:(BOOL)animated; + +/** + * @brief 取消选中标注数据对应的view + * Deselect the view corresponding to the annotation data + * @param annotation 标注数据 + * Annotation data + * @param animated 是否有动画效果 + * Whether there is an animation effect + */ +- (void)deselectAnnotation:(id )annotation animated:(BOOL)animated; + +/** + * @brief 设置地图使其可以显示数组中所有的annotation, 如果数组中只有一个则直接设置地图中心为annotation的位置。 + * Configure the map to display all annotations in the array. If there is only one annotation in the array, directly set the map center to the location of the annotation. + * @param annotations 需要显示的annotation + * Annotation to be displayed + * @param animated 是否执行动画 + * Whether to execute the animation + */ +- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated; + +/** + * @brief 设置地图使其可以显示数组中所有的annotation, 如果数组中只有一个则直接设置地图中心为annotation的位置。 + * Configure the map to display all annotations in the array. If there is only one annotation in the array, directly set the map center to the location of the annotation. + * @param annotations 需要显示的annotation + * Annotation to be displayed + * @param insets insets 嵌入边界 + * insets embedded boundary + * @param animated 是否执行动画 + * Whether to execute the animation + */ +- (void)showAnnotations:(NSArray *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated; + +@end + + +@interface MAMapView (UserLocation) + +///是否显示用户位置 +///Whether to display user location +@property (nonatomic) BOOL showsUserLocation; + +///当前的位置数据 +///Current location data +@property (nonatomic, readonly) MAUserLocation *userLocation; + +///是否自定义用户位置精度圈(userLocationAccuracyCircle)对应的 view, 默认为 NO.\n 如果为YES: 会调用 - (MAOverlayRenderer *)mapView (MAMapView *)mapView rendererForOverlay:(id )overlay 若返回nil, 则不加载.\n 如果为NO : 会使用默认的样式. +///Whether to customize the view corresponding to the user location accuracy circle (userLocationAccuracyCircle), default is NO.\n If YES: it will call - (MAOverlayRenderer *)mapView (MAMapView *)mapView rendererForOverlay:(id )overlay, if it returns nil, it will not be loaded.\n If NO: the default style will be used. +@property (nonatomic) BOOL customizeUserLocationAccuracyCircleRepresentation; + +///用户位置精度圈 对应的overlay +///Overlay corresponding to user location accuracy circle +@property (nonatomic, readonly) MACircle *userLocationAccuracyCircle; + +///定位用户位置的模式, 注意:在follow模式下,设置地图中心点、设置可见区域、滑动手势、选择annotation操作会取消follow模式,并触发 - (void)mapView:(MAMapView *)mapView didChangeUserTrackingMode:(MAUserTrackingMode)mode animated:(BOOL)animated; +///User location tracking mode, Note: In follow mode, setting the map center point, setting the visible region, swipe gestures, or selecting an annotation will cancel follow mode and trigger - (void)mapView:(MAMapView *)mapView didChangeUserTrackingMode:(MAUserTrackingMode)mode animated:(BOOL)animated; +@property (nonatomic) MAUserTrackingMode userTrackingMode; + +///当前位置在地图中是否可见 +///Whether the current location is visible on the map +@property (nonatomic, readonly, getter=isUserLocationVisible) BOOL userLocationVisible; + +///设定定位的最小更新距离。默认为kCLDistanceFilterNone,会提示任何移动 +///Set the minimum update distance for positioning. The default is kCLDistanceFilterNone, which will prompt any movement +@property (nonatomic) CLLocationDistance distanceFilter; + +///设定定位精度。默认为kCLLocationAccuracyBest +///Set the positioning accuracy. The default is kCLLocationAccuracyBest +@property (nonatomic) CLLocationAccuracy desiredAccuracy; + +///设定最小更新角度。默认为1度,设定为kCLHeadingFilterNone会提示任何角度改变 +///Set the minimum update angle. The default is 1 degree, setting it to kCLHeadingFilterNone will prompt any angle change. +@property (nonatomic) CLLocationDegrees headingFilter; + +///指定定位是否会被系统自动暂停 +///Whether the specified positioning will be automatically paused by the system +@property (nonatomic) BOOL pausesLocationUpdatesAutomatically; + +///是否允许后台定位。默认为NO。只在iOS 9.0之后起作用。\n 设置为YES的时候必须保证 Background Modes 中的 Location updates处于选中状态,否则会抛出异常。\n 注意:定位必须在停止的状态下设置(showsUserLocation = NO),否则无效 +///Whether to allow background location. Default is NO. Only works after iOS 9.0.\n When set to YES, you must ensure that Location updates in Background Modes is selected, otherwise an exception will be thrown.\n Note: The location must be set in a stopped state (showsUserLocation = NO), otherwise it will be invalid. +@property (nonatomic) BOOL allowsBackgroundLocationUpdates; + +/** + * @brief 设置定位用户位置的模式 + * Set the mode for locating the user's position + * @param mode 要设置的模式 + * the mode to be set + * @param animated 是否动画设置 + * whether to animate the setting + */ +- (void)setUserTrackingMode:(MAUserTrackingMode)mode animated:(BOOL)animated; + +/** + * @brief 设定UserLocationView样式。如果用户自定义了userlocation的annotationView,或者该annotationView还未添加到地图上,此方法将不起作用 + * Set the style of UserLocationView. If the user has customized the annotationView for userlocation, or if the annotationView has not been added to the map yet, this method will not work. + * @param representation 样式信息对象 + * the style information object + */ +- (void)updateUserLocationRepresentation:(MAUserLocationRepresentation *)representation; + +@end + +@interface MAMapView (Overlay) + +///所有添加的Overlay +///All added Overlays +@property (nonatomic, readonly) NSArray *overlays; + +/** + * @brief 取位于level下的overlays + * Get overlays under level + * @param level 层级 + * Level + */ +- (NSArray *)overlaysInLevel:(MAOverlayLevel)level; + +/** + * @brief 向地图窗口添加Overlay。 + * Add Overlay to the map window + * 需要实现MAMapViewDelegate的-mapView:rendererForOverlay:函数来生成标注对应的Renderer。 + * Need to implement the -mapView:rendererForOverlay: function of MAMapViewDelegate to generate the corresponding Renderer for the annotation. + * 默认添加层级:MAGroundOverlay默认层级为MAOverlayLevelAboveRoads,其余overlay类型默认层级为MAOverlayLevelAboveLabels + * Default level for adding: MAGroundOverlay's default level is MAOverlayLevelAboveRoads, while other overlay types default to MAOverlayLevelAboveLabels. + * @param overlay 要添加的overlay + * Overlay to be added + */ +- (void)addOverlay:(id )overlay; + +/** + * @brief 向地图窗口添加一组Overlay,需要实现MAMapViewDelegate的-mapView:rendererForOverlay:函数来生成标注对应的Renderer + * To add a set of Overlays to the map window, you need to implement the -mapView:rendererForOverlay: function of MAMapViewDelegate to generate the corresponding Renderer for the annotation + * 默认添加层级:MAOverlayLevelAboveLabels + * Default level for adding: MAOverlayLevelAboveLabels + * @param overlays 要添加的overlay数组 + * Array of overlays to be added + */ +- (void)addOverlays:(NSArray *)overlays; + +/** + * @brief 向地图窗口添加Overlay,需要实现MAMapViewDelegate的-mapView:rendererForOverlay:函数来生成标注对应的Renderer + * Add Overlay to the map window,need to implement the -mapView:rendererForOverlay: function of MAMapViewDelegate to generate the corresponding Renderer for the annotation. + * @param overlay 要添加的overlay + * Overlay to be added + * @param level 添加的overlay所在层级 + * The level of the added overlay + */ +- (void)addOverlay:(id )overlay level:(MAOverlayLevel)level; + +/** + * @brief 向地图窗口添加一组Overlay,需要实现MAMapViewDelegate的-mapView:rendererForOverlay:函数来生成标注对应的Renderer + * To add a set of Overlays to the map window,need to implement the -mapView:rendererForOverlay: function of MAMapViewDelegate to generate the corresponding Renderer for the annotation. + * @param overlays 要添加的overlay数组 + * Array of overlays to be added + * @param level 添加的overlay所在层级 + * The level of the added overlay + */ +- (void)addOverlays:(NSArray *)overlays level:(MAOverlayLevel)level; + +/** + * @brief 移除Overlay + * Remove Overlay + * @param overlay 要移除的overlay + * The overlay to be removed + */ +- (void)removeOverlay:(id )overlay; + +/** + * @brief 移除一组Overlay + * Remove a group of Overlays + * @param overlays 要移除的overlay数组 + * The array of overlays to be removed + */ +- (void)removeOverlays:(NSArray *)overlays; + +/** + * @brief 在指定层级的指定的索引处添加一个Overlay + * Add an overlay at the specified index of the specified level + * @param overlay 要添加的overlay + * Overlay to be added + * @param index 指定的索引 + * Specified index + * @param level Specified level + + * 注:各个层级的索引分开计数; + * Note: The indexes of each level are counted separately + * 若index大于level层级的最大索引,则添加至level层级的最大索引之后。 + * If the index is greater than the maximum index of the level, it will be added after the maximum index of the level + */ +- (void)insertOverlay:(id )overlay atIndex:(NSUInteger)index level:(MAOverlayLevel)level; + +/** + * @brief 在指定的Overlay之上插入一个overlay + * Insert an overlay above the specified overlay + * @param overlay 带添加的Overlay + * Overlay with addition + * @param sibling 用于指定相对位置的Overlay + * Overlay for specifying relative position + */ +- (void)insertOverlay:(id )overlay aboveOverlay:(id )sibling; + +/** + * @brief 在指定的Overlay之下插入一个overlay + * Insert an overlay below the specified overlay + * @param overlay 带添加的Overlay + * Overlay with addition + * @param sibling 用于指定相对位置的Overlay + * Overlay for specifying relative position + */ +- (void)insertOverlay:(id )overlay belowOverlay:(id )sibling; + +/** + * @brief 在指定的索引处添加一个Overlay + * Add an Overlay at the specified index + * @param overlay 要添加的overlay + * Overlay to be added + * @param index 指定的索引 + * the specified index + */ +- (void)insertOverlay:(id )overlay atIndex:(NSUInteger)index; + +/** + * @brief 在MAOverlayLevelAboveLabels上交换指定索引处的Overlay + * swap the Overlay at the specified index on MAOverlayLevelAboveLabels + * @param index1 索引1 + * index 1 + * @param index2 索引2 + * index 2 + */ +- (void)exchangeOverlayAtIndex:(NSUInteger)index1 withOverlayAtIndex:(NSUInteger)index2; + +/** + * @brief 交换指定索引处的Overlay + * Swap Overlay at specified index + * @param index1 索引1 + * index 1 + * @param index2 索引2 + * index 2 + * @param level 所处层级 + * Current hierarchy level + */ +- (void)exchangeOverlayAtIndex:(NSUInteger)index1 withOverlayAtIndex:(NSUInteger)index2 atLevel:(MAOverlayLevel)level; + +/** + * @brief 交换两个overlay + * Swap two overlays + * @param overlay1 overlay1 + * @param overlay2 overlay2 + */ +- (void)exchangeOverlay:(id )overlay1 withOverlay:(id )overlay2; + +/** + * @brief 查找指定overlay对应的Renderer,如果该View尚未创建,返回nil + * Find the Renderer corresponding to the specified overlay, returns nil if the View has not been created + * @param overlay 指定的overlay + * Specify the overlay + * @return 指定overlay对应的Renderer + * Specify the Renderer corresponding to the overlay + */ +- (MAOverlayRenderer *)rendererForOverlay:(id )overlay; + +/** + * @brief 设置地图使其可以显示数组中所有的overlay, 如果数组中只有一个则直接设置地图中心为overlay的位置。 + * Set the map to display all overlays in the array, if there is only one in the array, directly set the map center to the location of the overlay. + * @param overlays 需要显示的overlays + * Overlays to be displayed + * @param animated 是否执行动画 + * whether to execute animation + */ +- (void)showOverlays:(NSArray *)overlays animated:(BOOL)animated; + +/** + * @brief 设置地图使其可以显示数组中所有的overlay, 如果数组中只有一个则直接设置地图中心为overlay的位置。 + * Set the map to display all overlays in the array, if there is only one in the array, directly set the map center to the location of the overlay. + * @param overlays 需要显示的overlays + * Overlays to be displayed + * @param insets insets 嵌入边界 + * insets embedded boundaries + * @param animated 是否执行动画 + * whether to execute animation + */ +- (void)showOverlays:(NSArray *)overlays edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated; + +/** + * @brief 获取点击选中的polylineRenderer, 注意:开启polylineRenderer的点击选中功能,需设置userInteractionEnabled=YES。since 7.1.0 + * obtain the selected polylineRenderer by clicking, note: to enable the click selection function of polylineRenderer, userInteractionEnabled=YES needs to be set. since 7.1.0 + * @param tappedCoord 点击点的坐标 + * Coordinates of the clicked point + * @param traverseAll 如果有polyline重合情况,是否返回多个。NO: 只返回最上面的 YES:返回所有 + * If there is an overlap of polylines, whether to return multiple. NO: Only return the topmost one YES: Return all + * @return 返回选中的polylineRenderer数组,最上面的在第一个 + * Return the selected polylineRenderer array, with the topmost one first + * */ +- (NSArray*)getHittedPolylinesWith:(CLLocationCoordinate2D)tappedCoord traverseAll:(BOOL)traverseAll; + +/** + * @brief 多语言设置经纬度 + * Multilingual setting of latitude and longitude + * @param location 位置 + * Location + */ +- (void)setCurrentLocation:(CLLocationCoordinate2D)location; +@end + +#if MA_INCLUDE_INDOOR +@interface MAMapView (Indoor) + +///是否显示室内地图, 默认NO +///Whether to display indoor maps, default NO +@property (nonatomic, getter = isShowsIndoorMap) BOOL showsIndoorMap; + +///是否显示室内地图默认控件, 默认YES +///Whether to display the default indoor map controls, default YES +@property (nonatomic, getter = isShowsIndoorMapControl) BOOL showsIndoorMapControl; + +///默认室内地图控件的最大宽高 +///The maximum width and height of the default indoor map controls +@property (nonatomic, readonly) CGSize indoorMapControlSize; + +/** + * @brief 设置默认室内地图控件位置 + * Set the position of the default indoor map controls + * @param origin 左上角点位置 + * The position of the top-left corner point + */ +- (void)setIndoorMapControlOrigin:(CGPoint)origin; + +/** + * @brief 设置当前室内地图楼层数 + * Set the current indoor map floor count + * @param floorIndex 要设置的楼层数 + * Number of floors to set + */ +- (void)setCurrentIndoorMapFloorIndex:(NSInteger)floorIndex; + +/** + * @brief 清空室内地图缓存 + * Clear indoor map cache + */ +- (void)clearIndoorMapCache; + +@end +#endif + +///自定义样式 +///Custom style +@interface MAMapView (CustomMapStyle) + +///是否开启自定义样式, 默认NO. since 5.0.0 +///Whether to enable custom style, default NO. since 5.0.0 +@property (nonatomic, assign) BOOL customMapStyleEnabled; + +/** + * @brief 自定义地图样式设置,可以支持分级样式配置,如控制不同级别显示不同的颜色(自7.0.0开始样式有更新,旧的样式文件不能继续使用,必须到官网重新导出新样式文件。 自6.6.0开始使用新版样式,旧版样式无法在新版接口setCustomMapStyleOptions:(MAMapCustomStyleOptions *)styleOptions中使用,请到官网(lbs.amap.com)更新新版样式文件.) + * Custom map style settings support hierarchical style configurations, such as controlling different levels to display different colors (since version 7.0.0, the style has been updated, and old style files can no longer be used. You must re-export the new style file from the official website.From version 6.6.0, the new style is in use. The old style cannot be used in the new interface setCustomMapStyleOptions:(MAMapCustomStyleOptions *)styleOptions. Please update the new style file on the official website (lbs.amap.com).) + * @param styleOptions 自定义样式options. since 6.6.0 + * Custom style options. since 6.6.0 + */ +- (void)setCustomMapStyleOptions:(MAMapCustomStyleOptions *)styleOptions; + +@end + +/// 建筑物操作 @since 9.6.0 +/// Building operations since 9.6.0 +@interface MAMapView (Buildings) + +/** + * @brief 隐藏建筑物 + * Hide buildings + * @param polygon 围栏的经纬度信息 + * Fence's latitude and longitude information + * @param polygonSize 围栏的size(需 >= 3 否则无法构成围栏) + * Fence size (must be >=3 otherwise cannot form a fence) + * @return 隐藏成功返回当前的operationId(>= 0) 失败返回-1 + * Returns current operationId (>=0) if successful, -1 if failed + */ +- (NSInteger)hideBuildingsWithPolygon:(CLLocationCoordinate2D *)polygon polygonSize:(NSUInteger)polygonSize; + +/** + * @brief 显示建筑物 + * Display buildings + * @param operationId 操作Id(隐藏建筑物接口的返回值) + * Operation ID (return value of the hide buildings interface) + */ +- (void)showHiddenBuildingsWithOperationId:(NSInteger)operationId; +@end + +@interface MAMapView (EngineOverlay) + +/** + * @brief 向地图窗口添加Overlay。 + * Add Overlay to the map window + * @param overlay 要添加的engine overlay + * The engine overlay to be added + */ +- (void)addEngineOverlay:(MABaseEngineOverlay *)overlay; +@end + +@interface MAMapView (PoiFilter) +/** + * @brief 添加poi避让框 + * Add POI avoidance frame + * @param poiFilter + */ +- (void)addPoiFilter:(MAPoiFilter *)poiFilter; +/** + * @brief 移除poi避让框 + * Remove POI avoidance frame + * @param keyName 名称 + * Name + */ +- (void)removePoiFilter:(NSString *)keyName; +/** + * @brief 清除poi避让框 + * Clear POI avoidance frame + */ +- (void)clearPoiFilter; +@end + +#pragma mark - MAMapViewDelegate +@protocol MAMapViewDelegate + +@optional + +/** + * @brief 地图区域改变过程中会调用此接口 since 4.6.0 + * This interface will be called during the map region change process. since 4.6.0 + * @param mapView 地图View + * Map View + */ +- (void)mapViewRegionChanged:(MAMapView *)mapView; + +/** + * @brief 地图区域即将改变时会调用此接口 + * This interface will be called when the map region is about to change + * @param mapView 地图View + * Map View + * @param animated 是否动画 + * Whether to animate + */ +- (void)mapView:(MAMapView *)mapView regionWillChangeAnimated:(BOOL)animated; + +/** + * @brief 地图区域改变完成后会调用此接口 + * This interface will be called after the map region change is completed + * @param mapView 地图View + * Map View + * @param animated 是否动画 + * Whether to animate + */ +- (void)mapView:(MAMapView *)mapView regionDidChangeAnimated:(BOOL)animated; + +/** + * @brief 地图区域即将改变时会调用此接口,如实现此接口则不会触发回掉mapView:regionWillChangeAnimated: + * This interface will be called when the map region is about to change, and if this interface is implemented, the callback mapView:regionWillChangeAnimated: will not be triggered + * @param mapView 地图View + * Map View + * @param animated 是否动画 + * Whether to animate + * @param wasUserAction 标识是否是用户动作 + * Indicates whether it is a user action + */ +- (void)mapView:(MAMapView *)mapView regionWillChangeAnimated:(BOOL)animated wasUserAction:(BOOL)wasUserAction; + +/** + * @brief 地图区域改变完成后会调用此接口,如实现此接口则不会触发回掉mapView:regionDidChangeAnimated: + * This interface will be called after the map region change is completed,and if this interface is implemented, the callback mapView:regionDidChangeAnimated: will not be triggered + * @param mapView 地图View + * Map View + * @param animated 是否动画 + * Whether to animate + * @param wasUserAction 标识是否是用户动作 + * Indicates whether it is a user action + */ +- (void)mapView:(MAMapView *)mapView regionDidChangeAnimated:(BOOL)animated wasUserAction:(BOOL)wasUserAction; + +/** + * @brief 地图将要发生移动时调用此接口 + * This interface will be called when the map is about to move + * @param mapView 地图view + * Map view + * @param wasUserAction 标识是否是用户动作 + * Identifies whether it is a user action + */ +- (void)mapView:(MAMapView *)mapView mapWillMoveByUser:(BOOL)wasUserAction; + +/** + * @brief 地图移动结束后调用此接口 + * This interface will be called after the map movement ends + * @param mapView 地图view + * Map view + * @param wasUserAction 标识是否是用户动作 + * Identifies whether it is a user action + */ +- (void)mapView:(MAMapView *)mapView mapDidMoveByUser:(BOOL)wasUserAction; + +/** + * @brief 地图将要发生缩放时调用此接口 + * This interface is called when the map is about to zoom + * @param mapView 地图view + * Map view + * @param wasUserAction 标识是否是用户动作 + * Identifies whether it is a user action + */ +- (void)mapView:(MAMapView *)mapView mapWillZoomByUser:(BOOL)wasUserAction; + +/** + * @brief 地图缩放结束后调用此接口 + * This interface is called after the map zoom ends + * @param mapView 地图view + * Map view + * @param wasUserAction 标识是否是用户动作 + * Identifies whether it is a user action + */ +- (void)mapView:(MAMapView *)mapView mapDidZoomByUser:(BOOL)wasUserAction; + +/** + * @brief 地图开始加载 + * The map starts loading + * @param mapView 地图View + * Map view + */ +- (void)mapViewWillStartLoadingMap:(MAMapView *)mapView; + +/** + * @brief 地图加载成功 + * The map is loaded successfully + * @param mapView 地图View + * Map view + */ +- (void)mapViewDidFinishLoadingMap:(MAMapView *)mapView; + +/** + * @brief 地图加载失败 + * The map fails to load + * @param mapView 地图View + * Map view + * @param error 错误信息 + * Error message + */ +- (void)mapViewDidFailLoadingMap:(MAMapView *)mapView withError:(NSError *)error; + +/** + * @brief 地形图加载失败 + * Terrain map failed to load + * @param mapView 地图View + * Map view + * @param error 错误信息 + * Error message + */ +- (void)mapView:(MAMapView *)mapView didFailLoadTerrainWithError:(NSError *)error; + +/** + * @brief 根据anntation生成对应的View。 + * Generate corresponding View based on annotation. + + 注意: + Note: + 1、5.1.0后由于定位蓝点增加了平滑移动功能,如果在开启定位的情况先添加annotation,需要在此回调方法中判断annotation是否为MAUserLocation,从而返回正确的View。 + if ([annotation isKindOfClass:[MAUserLocation class]]) { + return nil; + } + After version 5.1.0, the positioning blue dot has added a smooth movement function. If you add an annotation while the positioning is turned on,it is necessary to determine whether the annotation is MAUserLocation in this callback method in order to return the correct View. + if ([annotation isKindOfClass:[MAUserLocation class]]) { + return nil; + } + + 2、请不要在此回调中对annotation进行select和deselect操作,此时annotationView还未添加到mapview。 + Please do not perform select or deselect operations on the annotation in this callback, as the annotationView has not been added to the mapView yet. + + * @param mapView 地图View + * Map view + * @param annotation 指定的标注 + * Specified annotation + * @return 生成的标注View + * Generated annotation View + */ +- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id )annotation; + +/** + * @brief 当mapView新添加annotation views时,调用此接口 + * This interface is called when new annotation views are added to the mapView + * @param mapView 地图View + * Map view + * @param views 新添加的annotation views + * the newly added annotation views + */ +- (void)mapView:(MAMapView *)mapView didAddAnnotationViews:(NSArray *)views; + +/** + * @brief 当选中一个annotation view时,调用此接口. 注意如果已经是选中状态,再次点击不会触发此回调。取消选中需调用-(void)deselectAnnotation:animated: + * This interface is called when an annotation view is selected. Note that if it is already in the selected state, clicking again will not trigger this callback. To deselect, call -(void)deselectAnnotation:animated: + * @param mapView 地图View + * Map view + * @param view 选中的annotation view + * On the selected annotation view + */ +- (void)mapView:(MAMapView *)mapView didSelectAnnotationView:(MAAnnotationView *)view; + +/** + * @brief 当取消选中一个annotation view时,调用此接口 + * This interface is called when an annotation view is deselected + * @param mapView 地图View + * Map view + * @param view 取消选中的annotation view + * Deselect the annotation view + */ +- (void)mapView:(MAMapView *)mapView didDeselectAnnotationView:(MAAnnotationView *)view; + +/** + * @brief 在地图View将要启动定位时,会调用此函数 + * This function is called when the map view is about to start positioning + * @param mapView 地图View + * Map view + */ +- (void)mapViewWillStartLocatingUser:(MAMapView *)mapView; + +/** + * @brief 在地图View停止定位后,会调用此函数 + * This function will be called after the MapView stops locating + * @param mapView 地图View + * Map view + */ +- (void)mapViewDidStopLocatingUser:(MAMapView *)mapView; + +/** + * @brief 位置或者设备方向更新后,会调用此函数 + * This function will be called after the location or device orientation is updated + * @param mapView 地图View + * Map view + * @param userLocation 用户定位信息(包括位置与设备方向等数据) + * User location information (including location and device orientation data) + * @param updatingLocation 标示是否是location数据更新, YES:location数据更新 NO:heading数据更新 + * Indicates whether it is a location data update, YES: location data update NO: heading data update + */ +- (void)mapView:(MAMapView *)mapView didUpdateUserLocation:(MAUserLocation *)userLocation updatingLocation:(BOOL)updatingLocation; + +/** + * @brief 当plist配置NSLocationAlwaysUsageDescription或者NSLocationAlwaysAndWhenInUseUsageDescription,并且[CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined,会调用代理的此方法。 + 此方法实现调用后台权限API即可( 该回调必须实现 [locationManager requestAlwaysAuthorization] ); since 6.8.0 + * When the plist is configured with NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription, and [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined, this method of the delegate will be called. This method just needs to call the background permission API (this callback must implement [locationManager requestAlwaysAuthorization]); since 6.8.0 + * @param locationManager 地图的CLLocationManager。 + * Map's CLLocationManager + */ +- (void)mapViewRequireLocationAuth:(CLLocationManager *)locationManager; + +/** + * @brief 定位失败后,会调用此函数 + * This function will be called after positioning failure + * @param mapView 地图View + * Map view + * @param error 错误号,参考CLError.h中定义的错误号 + * Error code, refer to the error codes defined in CLError.h + */ +- (void)mapView:(MAMapView *)mapView didFailToLocateUserWithError:(NSError *)error; + +/** + * @brief 拖动annotation view时view的状态变化 + * State changes of the annotation view during dragging + * @param mapView 地图View + * Map view + * @param view annotation view + * @param newState 新状态 + * New state + * @param oldState 旧状态 + * Old state + */ +- (void)mapView:(MAMapView *)mapView annotationView:(MAAnnotationView *)view didChangeDragState:(MAAnnotationViewDragState)newState + fromOldState:(MAAnnotationViewDragState)oldState; + +/** + * @brief 根据overlay生成对应的Renderer + * Generate the corresponding Renderer based on the overlay + * @param mapView 地图View + * Map view + * @param overlay 指定的overlay + * the specified overlay + * @return 生成的覆盖物Renderer + * the generated overlay Renderer + */ +- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id )overlay; + +/** + * @brief 当mapView新添加overlay renderers时,调用此接口 + * Call this interface when mapView adds new overlay renderers + * @param mapView 地图View + * Map view + * @param overlayRenderers 新添加的overlay renderers + * Newly added overlay renderers + */ +- (void)mapView:(MAMapView *)mapView didAddOverlayRenderers:(NSArray *)overlayRenderers; + +/** + * @brief 标注view的accessory view(必须继承自UIControl)被点击时,触发该回调 + * When the accessory view of the annotation view (must inherit from UIControl) is clicked, this callback is triggered + * @param mapView 地图View + * Map view + * @param view callout所属的标注view + * the annotation view to which the callout belongs + * @param control 对应的control + * Corresponding control + */ +- (void)mapView:(MAMapView *)mapView annotationView:(MAAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control; + +/** + * @brief 标注view的calloutview整体点击时,触发该回调。只有使用默认calloutview时才生效。 + * Triggers this callback when the calloutView of the annotationView is clicked. Only takes effect when using the default calloutView + * @param mapView 地图的view + * Map view + * @param view calloutView所属的annotationView + * The annotationView to which the calloutView belongs + */ +- (void)mapView:(MAMapView *)mapView didAnnotationViewCalloutTapped:(MAAnnotationView *)view; + +/** + * @brief 标注view被点击时,触发该回调。(since 5.7.0) + * When the annotation view is clicked, this callback is triggered (since 5.7.0) + * @param mapView 地图的view + * Map view + * @param view annotationView + */ +- (void)mapView:(MAMapView *)mapView didAnnotationViewTapped:(MAAnnotationView *)view; + +/** + * @brief 当userTrackingMode改变时,调用此接口 + * When the userTrackingMode changes, this interface is called + * @param mapView 地图View + * Map view + * @param mode 改变后的mode + * The changed mode + * @param animated 动画 + * Animation + */ +- (void)mapView:(MAMapView *)mapView didChangeUserTrackingMode:(MAUserTrackingMode)mode animated:(BOOL)animated; + +/** + * @brief 当openGLESDisabled变量改变时,调用此接口 + * When the openGLESDisabled variable changes, call this interface + * @param mapView 地图View + * Map view + * @param openGLESDisabled 改变后的openGLESDisabled + * the changed openGLESDisabled + */ +- (void)mapView:(MAMapView *)mapView didChangeOpenGLESDisabled:(BOOL)openGLESDisabled __attribute((deprecated("Deprecated, since 7.9.0"))); + +/** + * @brief 当touchPOIEnabled == YES时,单击地图使用该回调获取POI信息 + * When touchPOIEnabled == YES, click on the map to use this callback to get POI information + * @param mapView 地图View + * Map view + * @param pois 获取到的poi数组(由MATouchPoi组成) + * the obtained poi array (composed of MATouchPoi) + */ +- (void)mapView:(MAMapView *)mapView didTouchPois:(NSArray *)pois; + +/** + * @brief 单击地图回调,返回经纬度 + * Click on the map callback, return latitude and longitude + * @param mapView 地图View + * Map view + * @param coordinate 经纬度 + * latitude and longitude + */ +- (void)mapView:(MAMapView *)mapView didSingleTappedAtCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * @brief 长按地图,返回经纬度 + * Long press on the map, return latitude and longitude + * @param mapView 地图View + * Map view + * @param coordinate 经纬度 + * latitude and longitude + */ +- (void)mapView:(MAMapView *)mapView didLongPressedAtCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + * @brief 地图初始化完成(在此之后,可以进行坐标计算) + * Map initialization completed (after this, coordinate calculations can be performed) + * @param mapView 地图View + * Map view + */ +- (void)mapInitComplete:(MAMapView *)mapView; + +#if MA_INCLUDE_INDOOR +/** + * @brief 室内地图出现,返回室内地图信息 + * Indoor map appears, returns indoor map information + * + * @param mapView 地图View + * Map view + * @param indoorInfo 室内地图信息 + * Indoor map information + */ +- (void)mapView:(MAMapView *)mapView didIndoorMapShowed:(MAIndoorInfo *)indoorInfo; + +/** + * @brief 室内地图楼层发生变化,返回变化的楼层 + * Indoor map floor changes, returns the changed floor + * + * @param mapView 地图View + * Map view + * @param indoorInfo 变化的楼层 + * Changing floors + */ +- (void)mapView:(MAMapView *)mapView didIndoorMapFloorIndexChanged:(MAIndoorInfo *)indoorInfo; + +/** + * @brief 室内地图消失后,返回室内地图信息 + * After the indoor map disappears, return to the indoor map information + * @param mapView 地图View + * Map view + * @param indoorInfo 室内地图信息 + * Indoor map information + */ +- (void)mapView:(MAMapView *)mapView didIndoorMapHidden:(MAIndoorInfo *)indoorInfo; +#endif //end of MA_INCLUDE_INDOOR + +/** + * @brief 离线地图数据将要被加载, 调用reloadMap会触发该回调,离线数据生效前的回调. + * Offline map data is about to be loaded, calling reloadMap will trigger this callback, the callback before the offline data takes effect. + * @param mapView 地图View + * Map view + */ +- (void)offlineDataWillReload:(MAMapView *)mapView; + +/** + * @brief 离线地图数据加载完成, 调用reloadMap会触发该回调,离线数据生效后的回调. + * Offline map data loading completed. Calling reloadMap will trigger this callback, which is the callback after the offline data takes effect. + * @param mapView 地图View, + * Map view + */ +- (void)offlineDataDidReload:(MAMapView *)mapView; + +/** + * @brief 自定义地图样式在线数据加载鉴权失败回调 + * @param errorCode 错误码 + * @param msg 错误信息 + */ +- (void)customMapStyleAuthFailedWithErrorCode:(int)errorCode message:(NSString *)msg; + +@end + diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiColoredPolylineRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiColoredPolylineRenderer.h new file mode 100644 index 0000000..fac5347 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiColoredPolylineRenderer.h @@ -0,0 +1,43 @@ +// +// MAMultiColoredPolylineRenderer.h +// MapKit_static +// +// Created by yi chen on 12/11/15. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_MAMultiPolyline + +#import "MAPolylineRenderer.h" +#import "MAMultiPolyline.h" + +///此类用于绘制 MAMultiPolyline 对应的多彩线,支持分段颜色绘制 +///This class is used to draw the colorful line corresponding to MAMultiPolyline, supporting segmented color rendering +@interface MAMultiColoredPolylineRenderer : MAPolylineRenderer + +///关联的MAMultiPolyline model +///Associated MAMultiPolyline model +@property (nonatomic, readonly) MAMultiPolyline *multiPolyline; + +///分段绘制的颜色,需要分段颜色绘制时,必须设置(内容必须为UIColor)。根据multiPolyline.drawStyleIndexes属性指示的索引进行渲染。 +///The color for segmented drawing must be set (the content must be UIColor) when segmented color drawing is required. It is rendered according to the index indicated by the multiPolyline.drawStyleIndexes property. +@property (nonatomic, strong) NSArray *strokeColors; + +///颜色是否渐变, 默认为NO。如果设置为YES,则为多彩渐变线。 +///Whether the color is gradient, default is NO. If set to YES, it becomes a colorful gradient line. +@property (nonatomic, getter=isGradient) BOOL gradient; + +/** + * @brief 根据指定的MAPolyline生成一个多段线Renderer + * Generate a polyline Renderer based on the specified MAPolyline + * @param multiPolyline 指定MAMultiPolyline + * Specify MAMultiPolyline + * @return 新生成的多段线Renderer + * Newly generated polyline Renderer +*/ +- (instancetype)initWithMultiPolyline:(MAMultiPolyline *)multiPolyline; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPoint.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPoint.h new file mode 100755 index 0000000..a515524 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPoint.h @@ -0,0 +1,40 @@ +// +// MAMultiPoint.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAShape.h" +#import "MAGeometry.h" + +///该类是个由多个点组成的虚基类, 不能直接实例化对象, 要使用其子类MAPolyline,MAPolygon来实例化 +///This class is an abstract base class composed of multiple points and cannot be directly instantiated. Use its subclasses MAPolyline and MAPolygon for instantiation +@interface MAMultiPoint : MAShape + +///坐标点数组 +///coordinate point array +@property (nonatomic, readonly) MAMapPoint *points; + +///坐标点的个数 +///number of coordinate points +@property (nonatomic, readonly) NSUInteger pointCount; + +///是否跨越180度经度线,默认NO since 6.4.0 +///Whether to cross the 180th meridian, default NO since 6.4.0 +@property (nonatomic, assign, readonly) BOOL cross180Longitude; + +/** + * @brief 将内部的坐标点数据转化为经纬度坐标并拷贝到coords内存中 + * Convert the internal coordinate point data into latitude and longitude coordinates and copy them to the coords memory + * @param coords 调用者提供的内存空间, 该空间长度必须大于等于要拷贝的坐标点的个数(range.length) + * The memory space provided by the caller must be greater than or equal to the number of coordinate points to be copied.(range.length) + * @param range 要拷贝的数据范围 + * Data range to be copied + */ +- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPointOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPointOverlay.h new file mode 100644 index 0000000..32ae29a --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPointOverlay.h @@ -0,0 +1,52 @@ +// +// MAMultiPointOverlay.h +// MAMapKit +// +// Created by hanxiaoming on 2017/4/11. +// Copyright © 2017年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_MAMultiPoint + +#import "MAShape.h" +#import "MAOverlay.h" + +///海量点overlay单个点对象(since 5.1.0)) +///Massive point overlay single point object since 5.1.0 +@interface MAMultiPointItem : NSObject + +///经纬度 +/// Latitude and longitude +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +///唯一标识,默认为nil。 +///Unique identifier, default is nil +@property (nonatomic, copy) NSString *customID; + +///标题 +///Title +@property (nonatomic, copy) NSString *title; + +///副标题 +///Subtitle +@property (nonatomic, copy) NSString *subtitle; + +@end + + +///海量点overlay(since 5.1.0) +///Massive point overlay since 5.1.0 +@interface MAMultiPointOverlay : MAShape + +///点对象集合(注意:MAMultiPointItem属性不支持动态更新) +///Point object collection (Note: MAMultiPointItem properties do not support dynamic updates) +@property (nonatomic, readonly) NSArray *items; + +///初始化方法 +///Initialization method +- (instancetype)initWithMultiPointItems:(NSArray *)items; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPointOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPointOverlayRenderer.h new file mode 100644 index 0000000..40670e3 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPointOverlayRenderer.h @@ -0,0 +1,65 @@ +// +// MAMultiPointOverlayRenderer.h +// MAMapKit +// +// Created by hanxiaoming on 2017/4/11. +// Copyright © 2017年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_MAMultiPoint + +#import "MAMultiPointOverlay.h" +#import "MAOverlayRenderer.h" + +@class MAMultiPointOverlayRenderer; + +///MAMultiPointOverlayRenderer代理(since 5.1.0) +///MAMultiPointOverlayRenderer delegate since 5.1.0 +@protocol MAMultiPointOverlayRendererDelegate +@optional + +/** + @brief 点击海量点图层回调 + Callback for clicking on a massive point layer + + @param renderer 海量点图层渲染器 + Massive point layer renderer + @param item 被点击的单个点对象 + The single point object that was clicked + */ +- (void)multiPointOverlayRenderer:(MAMultiPointOverlayRenderer *)renderer didItemTapped:(MAMultiPointItem *)item; + +@end + +///海量点渲染renderer(since 5.1.0)。 注意:为了保证渲染效率,纹理不受alpha参数影响,如果需要设置透明度,请更换icon。 +///Massive point rendering renderer (since 5.1.0). Note: To ensure rendering efficiency, textures are not affected by the alpha parameter. If you need to set transparency, please change the icon. +@interface MAMultiPointOverlayRenderer : MAOverlayRenderer + +///MAMultiPointOverlayRendererDelegate代理对象 +///MAMultiPointOverlayRendererDelegate delegate object +@property (nonatomic, weak) id delegate; + +///标注纹理图片 +///Label texture image +@property (nonatomic, strong) UIImage *icon; + +///纹理渲染大小,默认为icon图片大小 +///texture rendering size, default is the icon image size +@property (nonatomic, assign) CGSize pointSize; + +///经纬度对应图片中的位置,默认为(0.5,0.5),范围[0-1] 负值自动取其绝对值 左上角为 (0,0) 右下角为 (1,1) +///latitude and longitude correspond to the position in the image, default is (0.5,0.5), range [0-1] negative values automatically take their absolute value, top-left corner is (0,0) bottom-right corner is (1,1) +@property (nonatomic, assign) CGPoint anchor; + +///对应的overlay +///Corresponding overlay +@property (nonatomic, readonly) MAMultiPointOverlay *multiPointOverlay; + +///初始化方法 +///Initialization method +- (instancetype)initWithMultiPointOverlay:(MAMultiPointOverlay *)multiPointOverlay; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPolyline.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPolyline.h new file mode 100644 index 0000000..2566cc9 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiPolyline.h @@ -0,0 +1,110 @@ +// +// MAMultiPolyline.h +// MapKit_static +// +// Created by yi chen on 12/11/15. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_MAMultiPolyline + +#import "MAPolyline.h" + +///多彩线model类。此类用于定义一个由多个点相连的多段线,绘制时支持分段采用不同颜色(纹理)绘制,点与点之间尾部相连但第一点与最后一个点不相连, 通常MAMultiPolyline是MAMultiColoredPolylineRenderer(分段颜色绘制)或MAMultiTexturePolylineRenderer(分段纹理绘制)的model +///The MultiColorLine model class. This class is used to define a polyline connected by multiple points, supporting the use of different colors (textures) for each segment during drawing. The points are connected end to end, but the first and last points are not connected, usually MAMultiPolyline is a model of MAMultiColoredPolylineRenderer (segmented color rendering) or MAMultiTexturePolylineRenderer (segmented texture rendering) +@interface MAMultiPolyline : MAPolyline + +/** + 绘制索引数组(纹理、颜色索引数组), 成员为NSNumber, 且为非负数。 + 例子:[1,3,6] 表示 0-1使用第一种颜色\纹理,1-3使用第二种,3-6使用第三种,6-最后使用第四种。 + 在渐变模式下(MAMultiColoredPolylineRenderer.gradient = YES),0-1使用第一种颜色,3使用第二种,6-最后使用第四种,1-3,3-6使用渐变色进行填充。 + + 注意:polyline在渲染时会进行抽稀以提高渲染效率,但是如果是设置为drawIndex的点,则不会被抽稀。 + 在每一个点都是索引点的极端情况下,则抽稀过程不会生效,点数量很多时会极大的影响渲染效率。所以请尽量少的设置索引点的数量。 + */ +/** + Draw index array (texture, color index array), members are NSNumber and non-negative. + Example: [1,3,6] means using the first color\\texture for 0-1, the second for 1-3, the third for 3-6, and the fourth from 6 to the end. + In gradient mode (MAMultiColoredPolylineRenderer.gradient = YES), use the first color for 0-1, the second for 3, and the fourth from 6 to the end. Use gradient colors for filling between 1-3 and 3-6. + + Note: The polyline will be simplified during rendering to improve efficiency, but points set as drawIndex will not be simplified. + In the extreme case where every point is an index point, the thinning process will not take effect, and a large number of points will significantly affect rendering efficiency. Therefore, please try to minimize the number of index points. + */ +@property (nonatomic, strong) NSArray *drawStyleIndexes; + +/** + * @brief 多彩线,根据MAMapPoint数据生成多彩线 + * Colorful line, generated based on MAMapPoint data; + * + * 分段纹理绘制:其对应的MAMultiTexturePolylineRenderer必须设置strokeTextureImages属性; 否则使用默认的灰色纹理绘制。 + * Segmented texture rendering: its corresponding MAMultiTexturePolylineRenderer must set the strokeTextureImages property; otherwise, the default gray texture will be used. + * 分段颜色绘制:其对应的MAMultiColoredPolylineRenderer必须设置strokeColors属性 + * Segmented color rendering: Its corresponding MAMultiColoredPolylineRenderer must set the strokeColors property + * + * @param points 指定的直角坐标点数组,注意:如果有连续重复点,需要去重处理,只保留一个,否则会导致绘制有问题。 + * Specified array of rectangular coordinate points, note: if there are consecutive duplicate points, deduplication is required, only one should be retained, otherwise it will cause issues in drawing. + * @param count 坐标点的个数 + * Number of coordinate points + * @param drawStyleIndexes 纹理索引数组(颜色索引数组) + * Texture index array (color index array) + * @return 生成的折线对象 + * Generated polyline object + */ ++ (instancetype)polylineWithPoints:(MAMapPoint *)points count:(NSUInteger)count drawStyleIndexes:(NSArray *) drawStyleIndexes; + +/** + * @brief 多彩线,根据经纬度坐标数据生成多彩线 + * Multicolored line, generated based on latitude and longitude coordinate data + * + * 分段纹理绘制:其对应的MAMultiTexturePolylineRenderer必须设置strokeTextureImages属性; 否则使用默认的灰色纹理绘制。 + * Segmented texture rendering: its corresponding MAMultiTexturePolylineRenderer must set the strokeTextureImages property; otherwise, the default gray texture will be used. + * 分段颜色绘制:其对应的MAMultiColoredPolylineRenderer必须设置strokeColors属性。 + * Segmented color rendering: Its corresponding MAMultiColoredPolylineRenderer must set the strokeColors property. + * + * @param coords 指定的经纬度坐标点数组,注意:如果有连续重复点,需要去重处理,只保留一个,否则会导致绘制有问题。 + * The array of specified latitude and longitude coordinate points, note: if there are consecutive duplicate points, they need to be deduplicated, only one should be retained, otherwise it will cause drawing issues. + * @param count 坐标点的个数 + * Number of coordinate points + * @param drawStyleIndexes 纹理索引数组(颜色索引数组), 成员为NSNumber, 且为非负数。 + * Texture index array (color index array), members are NSNumber, and are non-negative. + * @return 生成的折线对象 + * Generated polyline object + */ ++ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count drawStyleIndexes:(NSArray *) drawStyleIndexes; + +/** + * @brief 重新设置坐标点. since 5.0.0 + * Reset the coordinate points . since 5.0.0 + * @param points 指定的直角坐标点数组,C数组,内部会做copy,调用者负责内存管理。注意:如果有连续重复点,需要去重处理,只保留一个,否则会导致绘制有问题。 + * Specified rectangular coordinate point array, C array, internal copy will be made, the caller is responsible for memory management, note: if there are consecutive duplicate points, they need to be deduplicated, only one should be retained, otherwise it will cause drawing issues. + * @param count 坐标点的个数 + * Number of coordinate points + * @param drawStyleIndexes 纹理索引数组(颜色索引数组), 成员为NSNumber, 且为非负数。 + * Texture index array (color index array), members are NSNumber, and are non-negative. + * @return 是否设置成功 + * Whether the setup was successful + */ +- (BOOL)setPolylineWithPoints:(MAMapPoint *)points + count:(NSUInteger)count + drawStyleIndexes:(NSArray *)drawStyleIndexes; + +/** + * @brief 重新设置坐标点. since 5.0.0 + * Reset the coordinate points. since 5.0.0 + * @param coords 指定的经纬度坐标点数组,C数组,内部会做copy,调用者负责内存管理。注意:如果有连续重复点,需要去重处理,只保留一个,否则会导致绘制有问题。 + * Specified latitude and longitude coordinate point array, C array, internal copy will be made, the caller is responsible for memory management. Note: if there are consecutive duplicate points, they need to be deduplicated, only one should be retained, otherwise it will cause drawing issues. + * @param count 坐标点的个数 + * Number of coordinate points + * @param drawStyleIndexes 纹理索引数组(颜色索引数组), 成员为NSNumber, 且为非负数。 + * Texture index array (color index array), members are NSNumber, and are non-negative. + * @return 是否设置成功 + * Whether the setup was successful + */ +- (BOOL)setPolylineWithCoordinates:(CLLocationCoordinate2D *)coords + count:(NSUInteger)count + drawStyleIndexes:(NSArray *)drawStyleIndexes; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiTexturePolylineRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiTexturePolylineRenderer.h new file mode 100644 index 0000000..6f0ff00 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAMultiTexturePolylineRenderer.h @@ -0,0 +1,39 @@ +// +// MAMultiTexturePolylineRenderer.h +// MapKit_static +// +// Created by yi chen on 12/11/15. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_MAMultiPolyline + +#import "MAPolylineRenderer.h" +#import "MAMultiPolyline.h" + +///此类用于绘制MAMultiPolyline对应的多彩线,支持分段纹理绘制 +///This class is used to draw the multi-colored lines corresponding to MAMultiPolyline, supporting segmented texture drawing +@interface MAMultiTexturePolylineRenderer : MAPolylineRenderer + +///关联的MAMultiPolyline model +///The associated MAMultiPolyline model +@property (nonatomic, readonly) MAMultiPolyline *multiPolyline; + +///分段纹理图片数组, 支持非PowerOfTwo图片 +///An array of segmented texture images, supporting non-PowerOfTwo images +@property (nonatomic, strong) NSArray *strokeTextureImages; + +/** + * @brief 根据指定的MAMultiPolyline生成一个多段线Renderer + * Generate a polyline Renderer based on the specified MAMultiPolyline + * @param multiPolyline 指定MAMultiPolyline + * Specify MAMultiPolyline + * @return 新生成的多段线Renderer + * Newly generated polyline Renderer + */ +- (instancetype)initWithMultiPolyline:(MAMultiPolyline *)multiPolyline; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineCity.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineCity.h new file mode 100644 index 0000000..227ddd8 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineCity.h @@ -0,0 +1,24 @@ +// +// MAOfflineCity.h +// +// Copyright (c) 2013年 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OFFLINE + +#import +#import "MAOfflineItem.h" + +///离线地图,城市信息 +///Offline Maps, City Information +@interface MAOfflineCity : MAOfflineItem + +///城市编码 +///City Codes +@property (nonatomic, copy, readonly) NSString *cityCode; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItem.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItem.h new file mode 100644 index 0000000..9ead983 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItem.h @@ -0,0 +1,59 @@ +// +// MAOfflineItem.h +// MapKit_static +// +// Created by songjian on 14-4-23. +// Copyright © 2016 Amap. All rights reserved. +// + + + +#import "MAConfig.h" + +#if MA_INCLUDE_OFFLINE + +#import + +///离线地图item状态 +///Offline map item status +typedef NS_ENUM(NSInteger, MAOfflineItemStatus) +{ + MAOfflineItemStatusNone = 0, ///<不存在 Not exist + MAOfflineItemStatusCached, ///<缓存状态 Cache status + MAOfflineItemStatusInstalled, ///<已安装 Installed + MAOfflineItemStatusExpired ///<已过期 Expired +}; + +@interface MAOfflineItem : NSObject + +///名字 +///Name +@property (nonatomic, copy, readonly) NSString *name; + +///简拼 +///Abbreviation +@property (nonatomic, copy, readonly) NSString *jianpin; + +///拼音 +///Pinyin +@property (nonatomic, copy, readonly) NSString *pinyin; + +///区域编码 +///Region code +@property (nonatomic, copy, readonly) NSString *adcode; + +///离线数据大小 +///Offline data size +@property (nonatomic, assign, readonly) long long size; + +///状态 +///Status +@property (nonatomic, assign, readonly) MAOfflineItemStatus itemStatus; + +///已下载大小(当itemStatus == MAOfflineItemStatusCached 时有效) +///Downloaded size (valid when itemStatus == MAOfflineItemStatusCached) +@property (nonatomic, assign, readonly) long long downloadedSize; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemCommonCity.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemCommonCity.h new file mode 100644 index 0000000..c0729c3 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemCommonCity.h @@ -0,0 +1,25 @@ +// +// MAOfflineItemCommonCity.h +// MapKit_static +// +// Created by songjian on 14-4-23. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OFFLINE + +#import "MAOfflineCity.h" + +///普通城市 +///Ordinary city +@interface MAOfflineItemCommonCity : MAOfflineCity + +///所属省份 +///Province +@property (nonatomic, weak) MAOfflineItem *province; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemMunicipality.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemMunicipality.h new file mode 100644 index 0000000..9352cbb --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemMunicipality.h @@ -0,0 +1,21 @@ +// +// MAOfflineItemMunicipality.h +// MapKit_static +// +// Created by songjian on 14-4-23. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OFFLINE + +#import "MAOfflineCity.h" + +///直辖市 +///Municipality +@interface MAOfflineItemMunicipality : MAOfflineCity + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemNationWide.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemNationWide.h new file mode 100644 index 0000000..bc9acf8 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineItemNationWide.h @@ -0,0 +1,20 @@ +// +// MAOfflineItemNationWide.h +// MapKit_static +// +// Created by songjian on 14-4-23. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OFFLINE + +#import "MAOfflineCity.h" + +///全国概要 National Summary +@interface MAOfflineItemNationWide : MAOfflineCity + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineMap.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineMap.h new file mode 100644 index 0000000..6cb0acd --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineMap.h @@ -0,0 +1,188 @@ +// +// MAOfflineMap.h +// +// Copyright (c) 2013年 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OFFLINE + +#import +#import "MAOfflineProvince.h" +#import "MAOfflineItemNationWide.h" +#import "MAOfflineItemMunicipality.h" + +///离线地图下载状态 +///Offline map download status +typedef NS_ENUM(NSInteger, MAOfflineMapDownloadStatus) +{ + MAOfflineMapDownloadStatusWaiting = 0, //!< 已插入队列,等待中 Inserted into the queue, waiting + MAOfflineMapDownloadStatusStart, //!< 开始下载 Download started + MAOfflineMapDownloadStatusProgress, //!< 下载过程中 Downloading + MAOfflineMapDownloadStatusCompleted, //!< 下载成功 Download successful + MAOfflineMapDownloadStatusCancelled, //!< 取消 Cancel + MAOfflineMapDownloadStatusUnzip, //!< 解压缩 Decompressing + MAOfflineMapDownloadStatusFinished, //!< 全部顺利完成 All completed successfully + MAOfflineMapDownloadStatusError //!< 发生错误 Error occurred +}; + +///离线下载错误domain +///Offline download error domain +extern NSString * const MAOfflineMapErrorDomain; + +///离线地图下载错误类型 +///Offline map download error type +typedef NS_ENUM(NSInteger, MAOfflineMapError) +{ + MAOfflineMapErrorUnknown = -1, //!< 未知的错误 Unknown error + MAOfflineMapErrorCannotWriteToTmp = -2, //!< 写入临时目录失败 Failed to write to temporary directory + MAOfflineMapErrorCannotOpenZipFile = -3, //!< 打开归档文件失败 Failed to open archive file + MAOfflineMapErrorCannotExpand = -4 //!< 解归档文件失败 Failed to extract archive file +}; + +/** + * 当downloadStatus == MAOfflineMapDownloadStatusProgress 时, info参数是个NSDictionary, + * 如下两个key用来获取已下载和总和的数据大小(单位byte), 对应的是NSNumber(long long) 类型. + * 当downloadStatus == MAOfflineMapDownloadStatusError 时, info参数是NSError + */ +/** + * When downloadStatus == MAOfflineMapDownloadStatusProgress, the info parameter is an NSDictionary, + * The following two keys are used to obtain the size of the downloaded and total data (in bytes), corresponding to the NSNumber(long long) type. + * When downloadStatus == MAOfflineMapDownloadStatusError, the info parameter is NSError, + */ + +///下载过程info的key,表示已下载数据大小 +///The key in the download process info, indicating the size of the downloaded data +extern NSString * const MAOfflineMapDownloadReceivedSizeKey; + +///下载过程info的key,表示总的数据大小 +///The key in the download process info, indicating the total data size +extern NSString * const MAOfflineMapDownloadExpectedSizeKey; + +/** + * @brief 离线地图下载过程回调block + * The callback block for the offline map download process + * @param downloadItem 下载的item + * Downloaded item + * @param downloadStatus 下载状态 + * Download status + * @param info 下载过程中的附加信息 + * Additional information during download + */ +typedef void(^MAOfflineMapDownloadBlock)(MAOfflineItem * downloadItem, MAOfflineMapDownloadStatus downloadStatus, id info); + +/** + * @brief 离线地图检查更新回调block + * Offline map check update callback block + * @param hasNewestVersion 是否有新版本的布尔值 + * Boolean value indicating whether there is a new version + */ +/** + * @brief + * @param hasNewestVersion + */ +typedef void(^MAOfflineMapNewestVersionBlock)(BOOL hasNewestVersion); + +///离线地图管理类 +///Offline map management class +@interface MAOfflineMap : NSObject + +/** + * @brief 获取MAOfflineMap 单例 + * Get MAOfflineMap singleton + * @return MAOfflineMap + */ ++ (MAOfflineMap *)sharedOfflineMap; + +///省份数组(每个元素均是MAOfflineProvince类型) +///Province array (each element is of type MAOfflineProvince) +@property (nonatomic, readonly) NSArray *provinces; + +///直辖市数组(每个元素均是MAOfflineItemMunicipality类型) +///Municipality array (each element is of type MAOfflineItemMunicipality) +@property (nonatomic, readonly) NSArray *municipalities; + +///全国概要图 +///National overview map +@property (nonatomic, readonly) MAOfflineItemNationWide *nationWide; + +///城市数组, 包括普通城市与直辖市 +///City array, including ordinary cities and municipalities +@property (nonatomic, readonly) NSArray *cities; + +///离线数据的版本号(由年月日组成, 如@"20130715") +///Version number of offline data (composed of year, month, and day, e.g., @"20130715") +@property (nonatomic, readonly) NSString *version; + +/** + * @brief 初始化离线地图数据,如果第一次运行且offlinePackage.plist文件不存在,则需要首先执行此方法。否则MAOfflineMap中的省、市、版本号等数据都为空。 + * Initialize the offline map data. If it is the first run and the offlinePackage.plist file does not exist, this method needs to be executed first. Otherwise, the data such as provinces, cities, and version numbers in MAOfflineMap will be empty. + * @param block 初始化完成回调 + * Initialization completion callback + */ +- (void)setupWithCompletionBlock:(void(^)(BOOL setupSuccess))block; + +/** + * @brief 启动下载 + * Start download + * @param item 数据 + * Data + * @param shouldContinueWhenAppEntersBackground 进入后台是否允许继续下载 + * Whether to allow continued download in the background + * @param downloadBlock 下载过程block + * Download process block + */ +- (void)downloadItem:(MAOfflineItem *)item shouldContinueWhenAppEntersBackground:(BOOL)shouldContinueWhenAppEntersBackground downloadBlock:(MAOfflineMapDownloadBlock)downloadBlock; + +/** + * @brief 监测是否正在下载 + * Monitor whether downloading is in progress + * @param item 条目 + * item + * @return 是否在下载 + * whether downloading is in progress + */ +- (BOOL)isDownloadingForItem:(MAOfflineItem *)item; + +/** + * @brief 暂停下载 + * pause download + * @param item 条目 + * item + * @return 是否在执行了cancel,如果该item并未在下载中,则返回NO + * whether cancel has been executed, if the item is not being downloaded, return NO + */ +- (BOOL)pauseItem:(MAOfflineItem *)item; + +/** + * @brief 删除item对应离线地图数据 + * delete the offline map data corresponding to the item + * @param item 条目 + * item + */ +- (void)deleteItem:(MAOfflineItem *)item; + +/** + * @brief 取消全部下载 + * Cancel all downloads + */ +- (void)cancelAll; + +/** + * @brief 清除所有在磁盘上的离线地图数据, 之后调用[mapView reloadMap]会使其立即生效 + * Clear all offline map data on disk, calling [mapView reloadMap] will take effect immediately + */ +- (void)clearDisk; + +/** + * @brief 监测新版本。注意:如果有新版本,会重建所有的数据,包括provinces、municipalities、nationWide、cities,外部使用应当在newestVersionBlock中更新所持有的对象。 + * Monitor new versions. Note: If there is a new version, all data will be rebuilt, including provinces, municipalities, nationWide, cities. External usage should update the held objects in the newestVersionBlock. + * @param newestVersionBlock 回调block + * callback block + */ +- (void)checkNewestVersion:(MAOfflineMapNewestVersionBlock)newestVersionBlock; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineMapViewController.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineMapViewController.h new file mode 100644 index 0000000..a1a16c9 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineMapViewController.h @@ -0,0 +1,29 @@ +// +// MAOfflineMapViewController.h +// MAMapKit +// +// Created by hanxiaoming on 2017/12/14. +// Copyright © 2017年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OFFLINE + +#import +#import "MAOfflineMap.h" + +///离线地图ViewController(since 5.7.0) +///Offline Map ViewController(since 5.7.0) +@interface MAOfflineMapViewController : UIViewController + +/// MAOfflineMapViewController单例,请使用单例以保证离线地图状态正确同步。 +/// MAOfflineMapViewController singleton, please use the singleton to ensure correct synchronization of offline map status. ++ (instancetype)sharedInstance; + +///MAOfflineMap实例 +///MAOfflineMap instance +@property (nonatomic, readonly) MAOfflineMap *offlineMap; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineProvince.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineProvince.h new file mode 100644 index 0000000..58575ae --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOfflineProvince.h @@ -0,0 +1,26 @@ +// +// MAOfflineProvince.h +// MapKit_static +// +// Created by songjian on 14-4-24. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" + +#if MA_INCLUDE_OFFLINE + +#import "MAOfflineItem.h" +#import "MAOfflineItemCommonCity.h" + +///离线地图,省地图信息 +///Offline map, provincial map information +@interface MAOfflineProvince : MAOfflineItem + +///包含的城市数组(都是MAOfflineItemCommonCity类型) +///included city array (all of MAOfflineItemCommonCity type) +@property (nonatomic, strong, readonly) NSArray *cities; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlay.h new file mode 100755 index 0000000..80bc0fb --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlay.h @@ -0,0 +1,26 @@ +// +// MAOverlay.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import "MAAnnotation.h" +#import "MAGeometry.h" + +///该类是地图覆盖物的基类,所有地图的覆盖物需要继承自此类 +///This class is the base class for map overlays, all map overlays need to inherit from this class +@protocol MAOverlay +@required + +///返回区域中心坐标 +///Return the center coordinates of the area +- (CLLocationCoordinate2D)coordinate; + +///区域外接矩形 +///The bounding rectangle of the area +- (MAMapRect)boundingMapRect; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlayPathRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlayPathRenderer.h new file mode 100755 index 0000000..b27f714 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlayPathRenderer.h @@ -0,0 +1,49 @@ +// +// MAOverlayPathRenderer.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAOverlayRenderer.h" +#import "MAPathShowRange.h" + +///该类设置overlay绘制的属性,可以使用该类的子类MACircleRenderer, MAPolylineRenderer, MAPolygonRenderer或者继承该类 +///This class sets the properties for overlay drawing, and you can use its subclasses MACircleRenderer, MAPolylineRenderer, MAPolygonRenderer or inherit from this class +@interface MAOverlayPathRenderer : MAOverlayRenderer + +///填充颜色,默认是kMAOverlayRendererDefaultFillColor +///Fill color, default is kMAOverlayRendererDefaultFillColor +@property (nonatomic, retain) UIColor *fillColor; + +///笔触颜色,默认是kMAOverlayRendererDefaultStrokeColor +///stroke color, default is kMAOverlayRendererDefaultStrokeColor +@property (nonatomic, retain) UIColor *strokeColor; + +///笔触宽度, 单位屏幕点坐标,默认是0 +///stroke width, in screen points, default is 0 +@property (nonatomic, assign) CGFloat lineWidth; + +///LineJoin,默认是kMALineJoinBevel +///LineJoin, default is kMALineJoinBevel +@property (nonatomic, assign) MALineJoinType lineJoinType; + +///LineCap,默认是kMALineCapButt +///LineCap, default is kMALineCapButt +@property (nonatomic, assign) MALineCapType lineCapType; + +///MiterLimit,默认是2.f +///MiterLimit, default is 2.f +@property (nonatomic, assign) CGFloat miterLimit; + +///虚线类型, since 5.5.0 +///line dash pattern, since 5.5.0 +@property (nonatomic, assign) MALineDashType lineDashType; + +///是否抽稀,默认为YES,since 10.0.8000 +///Whether to thin, default is YES, since 10.0.8000 +@property (nonatomic, assign) BOOL reducePoint; +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlayRenderer.h new file mode 100755 index 0000000..e5dbc16 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAOverlayRenderer.h @@ -0,0 +1,165 @@ +// +// MAOverlayRenderer.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAOverlay.h" +#import "MALineDrawType.h" + +#define kMAOverlayRendererDefaultStrokeColor [UIColor colorWithRed:0.3 green:0.63 blue:0.89 alpha:0.8] +#define kMAOverlayRendererDefaultFillColor [UIColor colorWithRed:0.77 green:0.88 blue:0.94 alpha:0.8] + +@protocol MAOverlayRenderDelegate,MTLRenderCommandEncoder; + +///该类是地图覆盖物Renderer的基类, 提供绘制overlay的接口但并无实际的实现(render相关方法只能在重写后的glRender方法中使用) +///This class is the base class of the map overlay Renderer, providing an interface for drawing overlays but without actual implementation (render-related methods can only be used in the overridden glRender method) +@interface MAOverlayRenderer : NSObject { + @protected + GLuint _strokeTextureID; + CGSize _strokeTextureSize; + BOOL _needsUpdate; + BOOL _needsLoadStrokeTexture; +} + +///由地图添加时,不要手动设置。如果不是使用mapview进行添加,则需要手动设置。(since 5.1.0) +///When added by the map, do not set manually. If not added using mapview, manual setting is required.(since 5.1.0) +@property (nonatomic, weak) id rendererDelegate; + +///关联的overlay对象 +///Associated overlay object +@property (nonatomic, readonly, retain) id overlay; + +///用于生成笔触纹理id的图片(支持非PowerOfTwo图片; 如果您需要减轻绘制产生的锯齿,您可以参考AMap.bundle中的traffic_texture_blue.png的方式,在image两边增加部分透明像素.)。(since 5.3.0) +///Images used to generate brush stroke texture IDs (supports non-PowerOfTwo images; if you need to reduce aliasing caused by drawing, you can refer to the method in AMap.bundle's traffic_texture_blue.png, adding partially transparent pixels on both sides of the image.). (since 5.3.0) +@property (nonatomic, strong) UIImage *strokeImage; + +///笔触纹理id, 修改纹理id参考, 如果strokeImage未指定、尚未加载或加载失败返回0. 注意:仅使用gles环境 +///Stroke texture ID, modify texture ID reference, returns 0 if strokeImage is not specified, not yet loaded, or fails to load. Note: Only use gles environment. +@property (nonatomic, readonly) GLuint strokeTextureID __attribute((deprecated("Deprecated, since 7.9.0"))); + +///透明度[0,1],默认为1. 使用MAOverlayRenderer类提供的渲染接口会自动应用此属性。(since 5.1.0) +///Opacity [0, 1], default is 1. This property will be automatically applied when using the rendering interface provided by the MAOverlayRenderer class. (since 5.1.0) +@property (nonatomic, assign) CGFloat alpha; + +///overlay渲染的scale。(since 5.1.0) +///Scale of overlay rendering.(since 5.1.0) +@property (nonatomic, readonly) CGFloat contentScale; + +/** + * @brief 初始化并返回一个Overlay Renderer + * initialize and return an Overlay Renderer + * @param overlay 关联的overlay对象 + * associated overlay object + * @return 初始化成功则返回overlay view,否则返回nil + * return the overlay view if initialization is successful, otherwise return nil + */ +- (instancetype)initWithOverlay:(id)overlay; + +/** + * @brief 获取当前地图view矩阵,数组长度为16,无需外界释放. 需要添加至地图后,才能获取有效矩阵数据,否则返回NULL + * Get the current map view matrix with an array length of 16, no external release required. Valid matrix data can only be obtained after adding to the map, otherwise returns NULL. + * @return 矩阵数组 + * matrix array + */ +- (float *)getViewMatrix; + +/** + * @brief 获取当前地图projection矩阵,数组长度为16,无需外界释放. 需要添加至地图后,才能获取有效矩阵数据,否则返回NULL + * Get the current map projection matrix with an array length of 16, no need for external release. Valid matrix data can only be obtained after adding it to the map, otherwise returns NULL. + * @return 矩阵数组 + * matrix array + */ +- (float *)getProjectionMatrix; + +/** + * @brief 获取当前地图中心点偏移,用以把地图坐标转换为gl坐标。需要添加到地图获取才有效。(since 5.1.0) + * Get the current map center offset to convert map coordinates to gl coordinates. It only takes effect when added to map acquisition (since 5.1.0) + * @return 偏移 + * offset + */ +- (MAMapPoint)getOffsetPoint; + +/** + * @brief 获取Metal渲染MTLRenderCommandEncoder对象。注意:打开地图MetalEnable时有效,否则为nil(since 7.9.0) + * Obtain the Metal rendering MTLRenderCommandEncoder object. Note: It is valid when MetalEnable is turned on for the map, otherwise it is nil.(since 7.9.0) + * @return 偏移 + * offset + */ +- (id)getCommandEncoder; + +/** + * @brief 获取当前地图缩放级别,需要添加到地图获取才有效。(since 5.1.0) + * Get the current map zoom level, which is only valid when added to the map(since 5.1.0) + * @return 缩放级别 + * Zoom level + */ +- (CGFloat)getMapZoomLevel; + +/** + * @brief 将MAMapPoint转换为opengles可以直接使用的坐标 + * Convert MAMapPoint to coordinates that can be directly used by OpenGLES + * @param mapPoint MAMapPoint坐标 + * MAMapPoint coordinates + * @return 直接支持的坐标 + * Directly supported coordinates + */ +- (CGPoint)glPointForMapPoint:(MAMapPoint)mapPoint; + +/** + * @brief 批量将MAMapPoint转换为opengles可以直接使用的坐标 + * Batch convert MAMapPoint to coordinates that can be directly used by opengles + * @param mapPoints MAMapPoint坐标数据指针 + * MAMapPoint coordinate data pointer + * @param count 个数 + * count + * @return 直接支持的坐标数据指针(需要调用者手动释放) + * Directly supported coordinate data pointer (requires manual release by the caller) + */ +- (CGPoint *)glPointsForMapPoints:(MAMapPoint *)mapPoints count:(NSUInteger)count; + +/** + * @brief 将屏幕尺寸转换为OpenGLES尺寸 + * Convert screen size to OpenGLES size + * @param windowWidth 屏幕尺寸 + * Screen size + * @return OpenGLES尺寸 + * OpenGLES size + */ +- (CGFloat)glWidthForWindowWidth:(CGFloat)windowWidth; + +/** + * @brief 绘制函数(子类需要重载来实现) + * Drawing function (subclasses need to override to implement) + */ +- (void)glRender; + +/** + * @brief 加载纹理图片. 注意:仅使用gles环境(since 5.1.0) + * Load texture image. Note: Only use gles environment (since 5.1.0) + * @param textureImage 纹理图片(需满足:长宽相等,且宽度值为2的次幂) + * Texture image (must meet: width and height are equal, and width value is a power of 2) + * @return openGL纹理ID, 若纹理加载失败返回0 + * openGL texture ID, if texture loading fails, return 0 + */ +- (GLuint)loadTexture:(UIImage *)textureImage __attribute((deprecated("Deprecated, since 7.9.0"))); + +/** + * @brief 删除纹理. 注意:仅使用gles环境(since 5.1.0) + * Delete texture. Note: Only use in GLES environment(since 5.1.0) + * @param textureId 纹理ID + * Texture ID + */ +- (void)deleteTexture:(GLuint)textureId __attribute((deprecated("Deprecated, since 7.9.0"))); + +/** + * @brief 当关联overlay对象有更新时,调用此接口刷新. since 5.0.0 + * Call this interface to refresh when the associated overlay object is updated. since 5.0.0 + */ +- (void)setNeedsUpdate; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlay.h new file mode 100644 index 0000000..9e31e42 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlay.h @@ -0,0 +1,46 @@ +// +// MAParticleOverlay.h +// MAMapKit +// +// Created by liubo on 2018/9/19. +// Copyright © 2018年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_ParticleSystem + +#import "MAShape.h" +#import "MAOverlay.h" +#import "MAParticleOverlayOptions.h" + +#pragma mark - MAParticleOverlay + +///该类用于定义一个粒子MAParticleOverlay, 通常MAParticleOverlay是MAParticleOverlayRenderer的model. since 6.5.0 +///This class is used to define a particle MAParticleOverlay, usually MAParticleOverlay is the model of MAParticleOverlayRenderer. since 6.5.0 +@interface MAParticleOverlay : MAShape + +/** + * @brief 根据粒子覆盖物选项option生成MAParticleOverlay + * Generate MAParticleOverlay based on the particle overlay option + * @param option 粒子覆盖物选项option + * Particle overlay option + * @return 新生成的粒子覆盖物MAParticleOverlay + * Newly generated MAParticleOverlay + */ ++ (instancetype)particleOverlayWithOption:(MAParticleOverlayOptions *)option; + +///当前粒子覆盖物的option,如果需要修改option的配置,需要修改后重新调用setOverlayOption:方法。 +///The option of the current particle overlay, if you need to modify the configuration of the option, you need to call the setOverlayOption: method again after modification +@property (nonatomic, strong, readonly) MAParticleOverlayOptions *overlayOption; + +/** + * @brief 更新粒子覆盖物选项option + * Update the particle overlay option + * @param overlayOption 要更新的粒子覆盖物选项 + * The particle overlay option to be updated + */ +- (void)updateOverlayOption:(MAParticleOverlayOptions *)overlayOption; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlayOptions.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlayOptions.h new file mode 100644 index 0000000..763afbf --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlayOptions.h @@ -0,0 +1,389 @@ +// +// MAParticleOverlayOptions.h +// MAMapKit +// +// Created by liubo on 2018/9/18. +// Copyright © 2018年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_ParticleSystem + +#import "MAShape.h" +#import "MAOverlay.h" + +#pragma mark - MAParticleOverlayType + +///天气类型 +///Weather types +typedef NS_ENUM(NSInteger, MAParticleOverlayType) +{ + MAParticleOverlayTypeSunny = 1, ///<晴天 Sunny + MAParticleOverlayTypeRain, ///<雨天 Rainy + MAParticleOverlayTypeSnowy, ///<雪天 Snowy + MAParticleOverlayTypeHaze, ///<雾霾 Haze +}; + +#pragma mark - MAParticleVelocityGenerate + +///粒子的速度生成类. since 6.5.0 +///Particle velocity generation class. since 6.5.0 +@protocol MAParticleVelocityGenerate +@required + +///X轴方向上的速度变化率 +///Velocity variation rate in X-axis direction +- (CGFloat)getX; + +///Y轴方向上的速度变化率 +///Velocity variation rate in Y-axis direction +- (CGFloat)getY; + +///Z轴方向上的速度变化率 +///Velocity variation rate in Z-axis direction +- (CGFloat)getZ; +@end + +#pragma mark - MAParticleRandomVelocityGenerate + +///粒子的随机速度生成类. since 6.5.0 +///Random speed generation class for particles since 6.5.0 +@interface MAParticleRandomVelocityGenerate : NSObject + +/** + * @brief 根据速度范围值生成粒子的速度变化类 + * Speed variation class for particles based on speed range values + * @param x1 起始的速度x值 + * Initial speed x value + * @param y1 起始的速度y值 + * Initial speed y value + * @param z1 起始的速度z值 + * Initial speed z value + * @param x2 结束的速度x值 + * Final speed x value + * @param y2 结束的速度y值 + * Final speed y value + * @param z2 结束的速度z值 + * Final speed z value + * @return 生成粒子的颜色变化类 + * Color variation class for particle generation + */ +- (instancetype)initWithBoundaryValueX1:(float)x1 Y1:(float)y1 Z1:(float)z1 X2:(float)x2 Y2:(float)y2 Z2:(float)z2; + +@end + +#pragma mark - MAParticleColorGenerate + +///粒子的颜色生成类. since 6.5.0 +///Particle color generation class. since 6.5.0 +@protocol MAParticleColorGenerate +@required +///生成的颜色值,需为包含四个float值的数组。 +///The generated color value should be an array containing four float values. +- (float *)getColor; +@end + +#pragma mark - MAParticleRandomColorGenerate + +///粒子的随机颜色生成类. since 6.5.0 +///Random color generation class for particles. since 6.5.0 +@interface MAParticleRandomColorGenerate : NSObject + +/** + * @brief 根据颜色范围值生成粒子的颜色变化类 + * Particle color variation class based on color range values + * @param r1 起始的颜色r值 + * Starting color r value + * @param g1 起始的颜色g值 + * Starting color g value + * @param b1 起始的颜色b值 + * Starting color b value + * @param a1 起始的颜色a值 + * Starting color a value + * @param r2 结束的颜色r值 + * Ending color r value + * @param g2 结束的颜色g值 + * Ending color g value + * @param b2 结束的颜色b值 + * Ending color b value + * @param a2 结束的颜色a值 + * Ending color a value + * @return 生成粒子的颜色变化类 + * Particle color variation class + */ +- (instancetype)initWithBoundaryColorR1:(float)r1 G1:(float)g1 B1:(float)b1 A1:(float)a1 R2:(float)r2 G2:(float)g2 B2:(float)b2 A2:(float)a2; + +@end + +#pragma mark - MAParticleRotationGenerate + +///粒子的角度生成类. since 6.5.0 +///Particle angle generation class. since 6.5.0 +@protocol MAParticleRotationGenerate +@required +///生成的角度值 +///Generated angle value +- (float)getRotate; +@end + +#pragma mark - MAParticleConstantRotationGenerate + +///粒子的固定角度生成类. since 6.5.0 +///Particle fixed angle generation class. since 6.5.0 +@interface MAParticleConstantRotationGenerate : NSObject + +/** + * @brief 根据角度生成粒子的角度变化类 + * Angle variation class for generating particles based on angle + * @param rotate 固定的角度 + * Fixed angle + * @return 生成粒子的角度变化类 + * Angle variation class for generating particles + */ +- (instancetype)initWithRotate:(float)rotate; + +@end + +#pragma mark - MAParticleSizeGenerate + +///粒子的大小生成类. since 6.5.0 +///Particle size generation class. since 6.5.0 +@protocol MAParticleSizeGenerate +@required + +///X轴上变化比例 +///Variation ratio on the X-axis +- (float)getSizeX:(float)timeFrame; + +///Y轴上变化比例 +///Variation ratio on the Y-axis +- (float)getSizeY:(float)timeFrame; + +///Z轴上变化比例 +///Variation ratio on the Z-axis +- (float)getSizeZ:(float)timeFrame; +@end + +#pragma mark - MAParticleCurveSizeGenerate + +///粒子的大小变化类. since 6.5.0 +///Particle size variation class. since 6.5.0 +@interface MAParticleCurveSizeGenerate : NSObject + +/** + * @brief 根据三个轴上的变化比例生成粒子的大小变化类 + * Generate particle size variation class based on the scaling ratio on three axes + * @param x X轴上变化比例 + * Variation ratio on the X-axis + * @param y Y轴上变化比例 + * Variation ratio on the Y-axis + * @param z Z轴上变化比例 + * Variation ratio on the Z-axis + * @return 生成粒子的大小变化类 + * Generate particle size variation class + */ +- (instancetype)initWithCurveX:(float)x Y:(float)y Z:(float)z; + +@end + +#pragma mark - MAParticleEmissionModuleOC + +///粒子的发射率类,每隔多少时间发射粒子数量,越多会越密集. since 6.5.0 +///Particle emission rate class, the number of particles emitted per unit time, the more the denser. since 6.5.0 +@interface MAParticleEmissionModuleOC : NSObject + +/** + * @brief 根据发射数量和发射间隔生成粒子的发射率类。关系为:"发射数量为rate粒子->等待rateTime间隔->发射数量为rate粒子->等待rateTime间隔"循环 + * Generate the emission rate class of particles based on the number of emissions and the emission interval. The relationship is: "emit rate particles -> wait rateTime interval -> emit rate particles -> wait rateTime interval + * @param rate 发射数量(不能为0) + * Emission count (cannot be 0) + * @param rateTime 发射间隔 + * Emission interval + * @return 生成粒子的发射率类 + * Emission rate class of generated particles + */ +- (instancetype)initWithEmissionRate:(int)rate rateTime:(int)rateTime; + +@end + +#pragma mark - MAParticleShapeModule + +///粒子的发射区域模型协议. since 6.5.0 +///Emission area model protocol for particles. since 6.5.0 +@protocol MAParticleShapeModule +@required + +///新生成的发射点坐标,需为包含三个float值的数组。 +///Newly generated emission point coordinates, must be an array containing three float values +- (float *)getPoint; + +///坐标是否按比例生成 +///Are coordinates generated proportionally +- (BOOL)isRatioEnable; +@end + +#pragma mark - MAParticleSinglePointShapeModule + +///粒子的发射单个点区域模型. since 6.5.0 +///The emission single-point area model of particles. since 6.5.0 +@interface MAParticleSinglePointShapeModule : NSObject + +/** + * @brief 生成粒子的发射矩形区域模型,以比例的形式设置发射区域 + * The emission rectangular area model of particles, setting the emission area in proportion + * @param x x坐标比例 + * X-coordinate proportion + * @param y y坐标比例 + * Y-coordinate proportion + * @param z z坐标比例 + * Z-coordinate proportion + * @param isUseRatio 是否按比例 + * Is it proportional + * @return 新生成的粒子发射单个点区域模型 + * Newly generated particle emission single-point area model + */ +- (instancetype)initWithShapeX:(float)x Y:(float)y Z:(float)z useRatio:(BOOL)isUseRatio; + +@end + +#pragma mark - MAParticleRectShapeModule + +///粒子的发射矩形区域模型. since 6.5.0 +///Particle emission rectangular area model. since 6.5.0 +@interface MAParticleRectShapeModule : NSObject + +/** + * @brief 生成粒子的发射矩形区域模型,以比例的形式设置发射区域。 + * Particle emission rectangular area model, setting the emission area in the form of proportions + * @param left 左边距比例 + * Left margin ratio + * @param top 上边距比例 + * Top margin ratio + * @param right 右边距比例 + * Right margin ratio + * @param bottom 下边距比例 + * Bottom margin ratio + * @param isUseRatio 是否按比例 + * Is it proportional + * @return 新生成的粒子发射矩形区域模型 + * Newly generated particle emission rectangular area model + */ +- (instancetype)initWithLeft:(float)left top:(float)top right:(float)right bottom:(float)bottom useRatio:(BOOL)isUseRatio; + +@end + +#pragma mark - MAParticleOverLifeModuleOC + +///粒子生命周期过程中状态变化,包含速度、旋转和颜色的变化. since 6.5.0 +///State changes during the particle life cycle, including changes in velocity, rotation, and color. since 6.5.0 +@interface MAParticleOverLifeModuleOC : NSObject + +/** + * @brief 设置粒子生命周期过程中速度的变化 + * Set the change in speed during the particle's life cycle + * @param velocity 遵循MAParticleVelocityGenerate协议的速度生成类 + * Speed generation class that follows the MAParticleVelocityGenerate protocol + */ +- (void)setVelocityOverLife:(id)velocity; + +/** + * @brief 设置粒子生命周期过程中角度的变化 + * Set the change in angle during the particle's life cycle + * @param rotation 遵循MAParticleRotationGenerate协议的角度生成类 + * Angle generation class conforming to the MAParticleRotationGenerate protocol + */ +- (void)setRotationOverLife:(id)rotation; + +/** + * @brief 设置粒子生命周期过程中大小的变化 + * Set the size variation during the particle's lifecycle + * @param size 遵循MAParticleSizeGenerate协议的大小生成类 + * Size generation class conforming to the MAParticleSizeGenerate protocol + */ +- (void)setSizeOverLife:(id)size; + +/** + * @brief 设置粒子生命周期过程中颜色的变化 + * Set color changes during the particle lifecycle + * @param color 遵循MAParticleColorGenerate协议的颜色生成类 + * A color generation class conforming to the MAParticleColorGenerate protocol + */ +- (void)setColorOverLife:(id)color; + +@end + +#pragma mark - MAParticleOverlayOptions + +///该类用于定义一个粒子覆盖物显示选项. since 6.5.0 +///This class is used to define display options for particle overlays. since 6.5.0 +@interface MAParticleOverlayOptions : NSObject + +///option选项是否可见. (默认YES) +///Whether the option is visible. (Default YES) +@property (nonatomic, assign) BOOL visibile; + +///粒子系统存活时间. (默认5000,单位毫秒)) +///Particle system lifetime. (Default 5000, unit milliseconds) +@property (nonatomic, assign) NSTimeInterval duration; + +///粒子系统是否循环. (默认YES) +///Whether the particle system loops. (Default YES) +@property (nonatomic, assign) BOOL loop; + +///粒子系统的粒子最大数量. (默认100) +///Maximum number of particles in the particle system. (Default 100) +@property (nonatomic, assign) NSInteger maxParticles; + +///粒子系统的粒子图标. (默认nil) +///Particle icon of the particle system. (Default nil) +@property (nonatomic, strong) UIImage *icon; + +///每个粒子的初始大小. (默认(32.f*[[UIScreen mainScreen] nativeScale], 32.f*[[UIScreen mainScreen] nativeScale]),单位:OpenGLESPixels数量,计算方式为: OpenGLESPixels = ScreenPoint数量 * [[UIScreen mainScreen] nativeScale]) +///Initial size of each particle.(Default(32.f*[[UIScreen mainScreen] nativeScale], 32.f*[[UIScreen mainScreen] nativeScale]),Unit: OpenGLESPixels quantity, calculated as: OpenGLESPixels = ScreenPoint quantity * [[UIScreen mainScreen] nativeScale]) +@property (nonatomic, assign) CGSize startParticleSize; + +///每个粒子的存活时间. (默认5000,单位毫秒) +///Lifetime of each particle. (Default 5000, unit: milliseconds) +@property (nonatomic, assign) NSTimeInterval particleLifeTime; + +///每个粒子的初始颜色. (默认nil) +///Initial color of each particle. (Default nil) +@property (nonatomic, strong) id particleStartColor; + +///每个粒子的初始速度. (默认nil) +///Initial velocity of each particle. (Default nil) +@property (nonatomic, strong) id particleStartSpeed; + +///粒子的发射率,参考 MAParticleEmissionModuleOC 类. (默认nil) +///The emission rate of particles, refer to the MAParticleEmissionModuleOC class. (default nil) +@property (nonatomic, strong) MAParticleEmissionModuleOC *particleEmissionModule; + +///粒子的发射区域模型. (默认nil) +///The emission area model of particles. (default nil) +@property (nonatomic, strong) id particleShapeModule; + +///粒子生命周期过程,参考 MAParticleOverLifeModuleOC 类. (默认nil) +///The life cycle process of particles, refer to the MAParticleOverLifeModuleOC class. (default nil) +@property (nonatomic, strong) MAParticleOverLifeModuleOC *particleOverLifeModule; + +@end + +#pragma mark - MAParticleOverlayOptionsFactory + +///该类用于根据指定的天气类型,生成SDK内置的天气粒子覆盖物显示选项option. since 6.5.0 +///This class is used to generate the built-in weather particle overlay display option based on the specified weather type. since 6.5.0 +@interface MAParticleOverlayOptionsFactory : NSObject + +/** + * @brief 根据指定的天气类型生成粒子覆盖物显示选项option + * Generate particle overlay display options based on specified weather types + * @param particleType 天气类型 + * weather type + * @return 新生成的粒子覆盖物显示选项option + * newly generated particle overlay display options + */ ++ (NSArray *)particleOverlayOptionsWithType:(MAParticleOverlayType)particleType; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlayRenderer.h new file mode 100644 index 0000000..9d6eda2 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAParticleOverlayRenderer.h @@ -0,0 +1,36 @@ +// +// MAParticleOverlayRenderer.h +// MAMapKit +// +// Created by liubo on 2018/9/19. +// Copyright © 2018年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_ParticleSystem + +#import "MAOverlayRenderer.h" +#import "MAParticleOverlayOptions.h" +#import "MAParticleOverlay.h" + +///该类是MAParticleOverlay的显示Renderer. since 6.5.0 +///This class is the display Renderer of MAParticleOverlay. since 6.5.0 +@interface MAParticleOverlayRenderer : MAOverlayRenderer + +///关联的MAParticleOverlay model +///Associated MAParticleOverlay model +@property (nonatomic, readonly) MAParticleOverlay *particleOverlay; + +/** + * @brief 根据指定MAParticleOverlay生成对应的Renderer + * Generate the corresponding Renderer based on the specified MAParticleOverlay + * @param particleOverlay 指定的MAParticleOverlay model + * the specified MAParticleOverlay model + * @return 生成的Renderer + * the generated Renderer + */ +- (instancetype)initWithParticleOverlay:(MAParticleOverlay *)particleOverlay; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPathShowRange.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPathShowRange.h new file mode 100644 index 0000000..3ed8b3a --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPathShowRange.h @@ -0,0 +1,24 @@ +// +// MAPathShowRange.h +// MAMapKit +// +// Created by shaobin on 2019/12/31. +// Copyright © 2019 Amap. All rights reserved. +// + +#ifndef MAPathShowRange_h +#define MAPathShowRange_h + +struct MAPathShowRange { + float begin; ///<起点位置,整数部分表示起点索引,小数部分表示在线段上的位置 Start position, the integer part represents the start index, the decimal part represents the position on the segment + float end; ///<终点位置,整数部分表示起点索引,小数部分表示在线段上的位置 End position, the integer part represents the start index, the decimal part represents the position on the segment +}; + +typedef struct MAPathShowRange MAPathShowRange; + +static inline MAPathShowRange MAPathShowRangeMake(float begin, float end) { + return (MAPathShowRange){begin, end}; +} + + +#endif /* MAPathShowRange_h */ diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPinAnnotationView.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPinAnnotationView.h new file mode 100644 index 0000000..ccf1fc2 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPinAnnotationView.h @@ -0,0 +1,32 @@ +// +// MAPinAnnotationView.h +// MAMapKitDemo +// +// Created by songjian on 13-1-7. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import "MAMapView.h" +#import "MAAnnotationView.h" + +///MAPinAnnotationColor +typedef NS_ENUM(NSInteger, MAPinAnnotationColor){ + MAPinAnnotationColorRed = 0, ///< 红色大头针 Red pin + MAPinAnnotationColorGreen, ///< 绿色大头针 Green pin + MAPinAnnotationColorPurple ///< 紫色大头针 Purple pin +}; + +///提供类似大头针效果的annotation view +///Annotation view providing a pin-like effect +@interface MAPinAnnotationView : MAAnnotationView + +///大头针的颜色 +///The color of the pin +@property (nonatomic) MAPinAnnotationColor pinColor; + +///添加到地图时是否使用下落动画效果 +///Whether to use a drop animation effect when adding to the map +@property (nonatomic) BOOL animatesDrop; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPoiFilter.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPoiFilter.h new file mode 100644 index 0000000..1c48a2d --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPoiFilter.h @@ -0,0 +1,30 @@ +// +// MAPoiFilter.h +// MAMapKit +// +// Created by linshiqing on 2024/6/18. +// Copyright © 2024 Amap. All rights reserved. +// + +#import +@class MAMapView; + +NS_ASSUME_NONNULL_BEGIN +typedef NS_OPTIONS(NSUInteger, MAPoiFilterType) { + MAPoiFilterTypePoi = 0x00000001, //!< 避让POI Avoid POI + MAPoiFilterTypeRoadName = 0x00000002, //!< 避让底图路名 Avoid basemap road names + MAPoiFilterTypeRoadShield = 0x00000004, //!< 避让路牌 Avoid road signs + MAPoiFilterTypeLabel3rd = 0x00000008, //!< 避让第三方label Avoid third-party labels + MAPoiFilterTypeAll = 0xFFFFFFFF //!< 避让所有 Avoid all +}; + +@interface MAPoiFilter : NSObject +@property (nonatomic, assign) MAPoiFilterType filterType; //!< 避让类型 Avoid type +// 请将CLLocationCoordinate2D类型使用[NSValue valueWithMACoordinate:]包装下 +//Please wrap the CLLocationCoordinate2D type with [NSValue valueWithMACoordinate:] +@property (nonatomic, copy) NSArray *position; //!< 四边形避让框坐标 Quadrilateral avoidance box coordinates +@property (nonatomic, copy) NSString *keyName; //!< 避让框名称 Avoidance box name ++ (instancetype)poiFilter:(MAMapView *)mapView filterType:(MAPoiFilterType)filterType keyName:(NSString *)keyName center:(CLLocationCoordinate2D)center width:(CGFloat)width height:(CGFloat)height; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPointAnnotation.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPointAnnotation.h new file mode 100644 index 0000000..c2402a9 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPointAnnotation.h @@ -0,0 +1,29 @@ +// +// MAPointAnnotation.h +// MAMapKitDemo +// +// Created by songjian on 13-1-7. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import "MAShape.h" +#import + +///点标注数据 +///Point annotation data +@interface MAPointAnnotation : MAShape + +///经纬度 +///Latitude and longitude +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +///是否固定在屏幕一点, 注意,拖动或者手动改变经纬度,都会导致设置失效 +///Whether it is fixed at a point on the screen, note that dragging or manually changing the latitude and longitude will cause the setting to become invalid +@property (nonatomic, assign, getter = isLockedToScreen) BOOL lockedToScreen; + +///固定屏幕点的坐标 +///Fixed screen point coordinates +@property (nonatomic, assign) CGPoint lockedScreenPoint; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolygon.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolygon.h new file mode 100755 index 0000000..c106f13 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolygon.h @@ -0,0 +1,69 @@ +// +// MAPolygon.h +// MAMapKit +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAMultiPoint.h" +#import "MAOverlay.h" + +///此类用于定义一个由多个点组成的闭合多边形, 点与点之间按顺序尾部相连, 第一个点与最后一个点相连, 通常MAPolygon是MAPolygonView的model +///This class is used to define a closed polygon composed of multiple points, where the points are connected sequentially from tail to head, and the first point is connected to the last point. Typically, MAPolygon is the model of MAPolygonView. +@interface MAPolygon : MAMultiPoint + +///设置中空区域,用来创建中间带空洞的复杂图形。注意:传入的overlay只支持MAPolgon类型和MACircle类型,不支持与polygon边相交或在polygon外部,不支持hollowShapes彼此间相交,和空洞顺序有关,不支持嵌套. since 5.5.0 +///Set the hollow area to create complex shapes with holes in the middle. Note: The incoming overlay only supports MAPolgon type and MACircle type. Intersection with polygon edges or being outside the polygon is not supported, intersection between hollowShapes is not supported, it is related to the order of holes, nesting is not supported. since 5.5.0 +@property (nonatomic, strong) NSArray> *hollowShapes; + +/** + * @brief 根据经纬度坐标数据生成闭合多边形 + * Generate a closed polygon based on latitude and longitude coordinate data + * @param coords 经纬度坐标点数据,coords对应的内存会拷贝,调用者负责该内存的释放 + * The memory corresponding to the latitude and longitude coordinate points, coords, will be copied, and the caller is responsible for releasing this memory + * @param count 经纬度坐标点数组个数 + * The number of latitude and longitude coordinate point arrays + * @return 新生成的多边形 + * Newly generated polygon + */ ++ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count; + +/** + * @brief 根据map point数据生成多边形 + * Generate polygon based on map point data + * @param points map point数据,points对应的内存会拷贝,调用者负责该内存的释放 + * map point data, the memory corresponding to points will be copied, the caller is responsible for releasing this memory + * @param count 点的个数 + * number of points + * @return 新生成的多边形 + * Newly generated polygon + */ ++ (instancetype)polygonWithPoints:(MAMapPoint *)points count:(NSUInteger)count; + +/** + * @brief 重新设置多边形顶点. since 5.0.0 + * Reset polygon vertices. since 5.0.0 + * @param points 指定的直角坐标点数组, C数组,内部会做copy,调用者负责内存管理 + * specified rectangular coordinate point array, C array, internal copy will be made, caller is responsible for memory management + * @param count 坐标点的个数 + * number of coordinate points + * @return 是否设置成功 + * whether the setup was successful + */ +- (BOOL)setPolygonWithPoints:(MAMapPoint *)points count:(NSInteger)count; + +/** + * @brief 重新设置多边形顶点. since 5.0.0 + * Reset polygon vertices. since 5.0.0 + * @param coords 指定的经纬度坐标点数组, C数组,内部会做copy,调用者负责内存管理 + * specified latitude and longitude coordinate point array, C array, internal copy will be made, caller is responsible for memory management + * @param count 坐标点的个数 + * number of coordinate points + * @return 是否设置成功 + * whether the setup was successful + */ +- (BOOL)setPolygonWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSInteger)count; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolygonRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolygonRenderer.h new file mode 100755 index 0000000..f37668b --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolygonRenderer.h @@ -0,0 +1,32 @@ +// +// MAPolygonRenderer.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAPolygon.h" +#import "MAOverlayPathRenderer.h" + +///此类用于绘制MAPolygon,可以通过MAOverlayPathRenderer修改其fill和stroke attributes +///This class is used to draw MAPolygon, and its fill and stroke attributes can be modified through MAOverlayPathRenderer +@interface MAPolygonRenderer : MAOverlayPathRenderer + +///关联的MAPolygon model +///associated MAPolygon model +@property (nonatomic, readonly) MAPolygon *polygon; + +/** + * @brief 根据指定的多边形生成一个多边形Renderer + * Generate a polygon Renderer based on the specified polygon + * @param polygon polygon 指定的多边形数据对象 + * polygon specifies the polygon data object + * @return 新生成的多边形Renderer + * newly generated polygon Renderer + */ +- (instancetype)initWithPolygon:(MAPolygon *)polygon; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolyline.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolyline.h new file mode 100755 index 0000000..e55fa9a --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolyline.h @@ -0,0 +1,65 @@ +// +// MAPolyline.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import "MAMultiPoint.h" +#import "MAOverlay.h" + +///此类用于定义一个由多个点相连的多段线,点与点之间尾部相连但第一点与最后一个点不相连, 通常MAPolyline是MAPolylineView的model +///This class is used to define a polyline connected by multiple points, where the points are connected end-to-end but the first point is not connected to the last point. Typically, MAPolyline is the model of MAPolylineView. +@interface MAPolyline : MAMultiPoint + +/** + * @brief 根据map point数据生成多段线 + * Generate polylines based on map point data + * @param points map point数据,points对应的内存会拷贝,调用者负责该内存的释放 + * map point data, the memory corresponding to points will be copied, the caller is responsible for releasing this memory + * @param count map point个数 + * number of map points + * @return 生成的多段线 + * generated polylines + */ ++ (instancetype)polylineWithPoints:(MAMapPoint *)points count:(NSUInteger)count; + +/** + * @brief 根据经纬度坐标数据生成多段线 + * Generate a polyline based on latitude and longitude coordinate data + * @param coords 经纬度坐标数据,coords对应的内存会拷贝,调用者负责该内存的释放 + * Latitude and longitude coordinate data, the memory corresponding to coords will be copied, the caller is responsible for releasing this memory + * @param count 经纬度坐标个数 + * Number of latitude and longitude coordinates + * @return 生成的多段线 + * generated polylines + */ ++ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count; + +/** + * @brief 重新设置折线坐标点. since 5.0.0 + * Reset polyline coordinate points. since 5.0.0 + * @param points 指定的直角坐标点数组, C数组,内部会做copy,调用者负责内存管理 + * Specified rectangular coordinate point array, C array, internal copy will be made, caller is responsible for memory management. + * @param count 坐标点的个数 + * Number of coordinate points + * @return 是否设置成功 + * Whether the setting is successful + */ +- (BOOL)setPolylineWithPoints:(MAMapPoint *)points count:(NSInteger)count; + +/** + * @brief 重新设置折线坐标点. since 5.0.0 + * Reset polyline coordinate points. since 5.0.0 + * @param coords 指定的经纬度坐标点数组, C数组,内部会做copy,调用者负责内存管理 + * Specified latitude-longitude coordinate point array, C array, internal copy will be made, caller is responsible for memory management. + * @param count 坐标点的个数 + * Number of coordinate points + * @return 是否设置成功 + * Whether the setting is successful + */ +- (BOOL)setPolylineWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSInteger)count; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolylineRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolylineRenderer.h new file mode 100755 index 0000000..e9fda6b --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAPolylineRenderer.h @@ -0,0 +1,57 @@ +// +// MAPolylineRenderer.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAPolyline.h" +#import "MAOverlayPathRenderer.h" +#import "MAPathShowRange.h" + +///此类用于绘制MAPolyline,可以通过MAOverlayPathRenderer修改其fill和stroke attributes +///This class is used to draw MAPolyline, and its fill and stroke attributes can be modified through MAOverlayPathRenderer. +@interface MAPolylineRenderer : MAOverlayPathRenderer + +///关联的MAPolyline model +///associated MAPolyline model +@property (nonatomic, readonly) MAPolyline *polyline; + +///设置是否显示3d箭头线, 默认为NO。如果设置为YES,则为3d箭头线。since 6.7.0 +///Set whether to display the 3D arrow line, default is NO. If set to YES, it will be a 3D arrow line. since 6.7.0 +@property (nonatomic, assign) BOOL is3DArrowLine; + +///设置为立体3d箭头的侧边颜色(当is3DArrowLine为YES时有效)顶部颜色使用strokeColor。since 6.7.0 +///Set the side color of the 3D arrow (effective when is3DArrowLine is YES) The top color uses strokeColor. since 6.7.0 +@property (nonatomic, strong) UIColor *sideColor; + +///是否开启点击选中功能,默认NO. since 7.1.0 +///Whether to enable the click selection function, default is NO. since 7.1.0 +@property (nonatomic, assign) BOOL userInteractionEnabled; + +///用于调整点击选中热区大小,默认为0. 负值增大热区,正值减小热区. since 7.1.0 +///Used to adjust the size of the click selection hotspot, default is 0. Negative values increase the hotspot, positive values decrease the hotspot. since 7.1.0 +@property (nonatomic, assign) CGFloat hitTestInset; + +///是否启用显示范围,YES启用,不启用时展示全路径 since 7.5.0 +///Whether to enable the display range, YES to enable, when not enabled, the full path is displayed. since 7.5.0 +@property (nonatomic, assign) BOOL showRangeEnabled; + +///显示范围 since 7.5.0 +///Display range since 7.5.0 +@property (nonatomic, assign) MAPathShowRange showRange; + +/** + * @brief 根据指定的MAPolyline生成一个多段线Renderer + * Generate a polyline Renderer based on the specified MAPolyline + * @param polyline 指定MAPolyline + * Specify MAPolyline + * @return 新生成的多段线Renderer + * Newly generated polyline Renderer + */ +- (instancetype)initWithPolyline:(MAPolyline *)polyline; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MARouteOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MARouteOverlay.h new file mode 100644 index 0000000..3121455 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MARouteOverlay.h @@ -0,0 +1,54 @@ +// +// MARouteOverlay.h +// MAMapKit +// +// Created by linshiqing on 2024/1/18. +// Copyright © 2024 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if FEATURE_ROUTE_OVERLAY +#import "MABaseEngineOverlay.h" +#import "MARouteOverlayModel.h" +NS_ASSUME_NONNULL_BEGIN + + +@interface MARouteOverlay : MABaseEngineOverlay +@property (nonatomic, assign, readonly) NSUInteger mapScene; +@property (nonatomic, copy, readonly) NSArray *params; +@property (nonatomic, assign, readonly) BOOL select; +@property (nonatomic, strong, readonly) MAMapRouteOverlayData *data; +@property (nonatomic, copy, readonly) NSArray *passedColors; + +- (instancetype)initWithMapSecne:(NSUInteger)mapScene params:(NSArray *)params select:(BOOL)select data:(MAMapRouteOverlayData *)data passedColors:(NSArray *)passedColors; + +- (void)setCar2DWithIndex:(uint32_t)index position:(float)postion; + +- (void)setCar3DWithIndex:(uint32_t)index position:(float)postion; + +- (void)addRouteName; + +- (void)removeRouteName; + +- (void)setLineWidthScale:(float)scale; + +- (void)setLine2DWithLineWidth:(int32_t)lineWidth borderWidth:(int32_t)borderWidth; + + - (void)setShowArrow:(BOOL)bShow; + +- (void)setArrow3DTexture:(UIImage *)image; + +- (void)setRouteItemParam:(MARouteOverlayParam *)routeParam; + +- (void)setHighlightType:(MAMapRouteHighLightType)type; + +- (void)setHighlightParam:(MARouteOverlayHighLightParam *)highlightParam; + +- (void)setSelectStatus:(BOOL)status; + +- (void)setShowNaviRouteNameCountMap:(NSDictionary*)countMap; + +- (void)setArrowFlow:(BOOL)bFlow; +@end +NS_ASSUME_NONNULL_END +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MARouteOverlayModel.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MARouteOverlayModel.h new file mode 100644 index 0000000..29c6730 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MARouteOverlayModel.h @@ -0,0 +1,161 @@ +// +// MARouteOverlayModel.h +// MAMapKit +// +// Created by linshiqing on 2024/1/18. +// Copyright © 2024 Amap. All rights reserved. +// + +#import +#import +#if FEATURE_ROUTE_OVERLAY +NS_ASSUME_NONNULL_BEGIN +/** + * @brief 路线纹理枚举 Route texture enumeration + */ +typedef NS_ENUM(NSInteger, MAMapRouteTexture) { + MAMapRouteTextureNonavi = 0, //!< 非导航道路,步行代表不绘制路段 non-navigation roads, walking represents not drawing the road section + MAMapRouteTextureNavi = 1, //!< 导航道路,骑步行代表高亮路段 navigation roads, walking and cycling represent highlighted road sections + MAMapRouteTextureDefault = 2, //!< 实时交通默认状态,步行代表置灰路段 real-time traffic default state, walking represents grayed-out road sections + MAMapRouteTextureOpen = 3, //!< 实时交通畅通状态 real-time traffic smooth state + MAMapRouteTextureAmble = 4, //!< 实时交通缓行状态 Real-time traffic slow status + MAMapRouteTextureJam = 5, //!< 实时交通拥堵状态 Real-time traffic congestion status + MAMapRouteTextureCongested = 6, //!< 实时交通极其拥堵状态 Real-time traffic extremely congested status + MAMapRouteTextureArrow = 7, //!< 路线上鱼骨箭头 Fishbone arrows on the route + MAMapRouteTextureCustom1 = 8, //!< 自定义路线纹理1, 与status值对应 Custom route texture 1, corresponding to status value + MAMapRouteTextureCustom2 = 9, //!< 自定义路线纹理2, 与status值对应 Custom route texture 2, corresponding to status value + MAMapRouteTextureCustom3 = 10, //!< 自定义路线纹理3, 与status值对应 Custom route texture 3, corresponding to status value + MAMapRouteTextureCustom4 = 11, //!< 自定义路线纹理4, 与status值对应 Custom route texture 4, corresponding to status value + MAMapRouteTextureCustom5 = 12, //!< 自定义路线纹理5, 与status值对应 Custom route texture 5, corresponding to status value + MAMapRouteTextureCustom6 = 13, //!< 自定义路线纹理6, 与status值对应 Custom route texture 6, corresponding to status value + MAMapRouteTextureRapider = 16, //!< 自定义路线纹理16,极其畅通 Custom route texture 16, extremely smooth + MAMapRouteTextureRestrain = 30, //!< 自定义路线纹理30, 导航抑制状态 Custom route texture 30, navigation suppression status + MAMapRouteTextureCustomMax = 31, //!< 自定义路线纹理最大值, 与status值对应 Maximum custom route texture value, corresponding to the status value + MAMapRouteTextureCharge = 32, //!< 收费道路 Toll road + MAMapRouteTextureFree = 33, //!< 免费道路 Free road + MAMapRouteTextureLimit = 34, //!< 限行道路 Restricted road + MAMapRouteTextureSlower = 35, //!< 通勤场景下更拥堵道路 More congested road in commuting scenarios + MAMapRouteTextureFaster = 36, //!< 通勤场景下更畅通道路 Smoother roads in commuting scenarios + MAMapRouteTextureWrong = 37, //!< 报错道路 Error roads + MAMapRouteTextureFerry = 38, //!< 轮渡线 Ferry lines + MAMapRouteTextureNumber, //!< 纹理个数 Number of textures +}; + +@interface MAPolylineCapTextureInfo : NSObject +@property (nonatomic, assign) float x1; //!< 纹理左上角X Top-left X of texture +@property (nonatomic, assign) float y1; //!< 纹理左上角Y Top-left Y of texture +@property (nonatomic, assign) float x2; //!< 纹理右下角X Bottom-right X of texture +@property (nonatomic, assign) float y2; //!< 纹理右上角Y Top-right Y of texture +@end + +@interface MAPolylineTextureInfo : MAPolylineCapTextureInfo +@property (nonatomic, assign) float textureLen; //!< 纹理长度,仅在绘制虚线线型时设置 Texture length, only set when drawing dashed line types +@end + +typedef NS_ENUM(NSInteger, MAMapRouteLineWidthType) { + MAMapRouteLineWidthTypePixel = 0, + MAMapRouteLineWidthTypeMeter = 1, +}; + +@interface MARouteOverlayParam : NSObject +@property (nonatomic, assign) BOOL lineExtract; //!< 是否抽稀 Whether to thin out +@property (nonatomic, assign) BOOL useColor; //!< 是否使用颜色 Whether to use color +@property (nonatomic, assign) BOOL usePoint; //!< 是否使用Point点 Whether to use Point +@property (nonatomic, assign) BOOL useCap; //!< 是否使用线帽 Whether to use line caps +@property (nonatomic, assign) BOOL canBeCovered; //!< 能否被覆盖 Whether it can be covered +@property (nonatomic, assign) BOOL showArrow; //!< 是否显示箭头 上层控制箭头是否显示 Whether to display arrows Upper layer controls whether arrows are displayed +@property (nonatomic, assign) BOOL needColorGradient; //!< 是否需要渐变 Whether gradient is needed +@property (nonatomic, assign) BOOL clickable; //!< 是否可点击 Is it clickable +@property (nonatomic, assign) int32_t lineWidth; //!< 线宽 Line width +@property (nonatomic, assign) int32_t borderLineWidth; //!< 边线宽 Border width +@property (nonatomic, strong) UIImage *fillMarkerImage; //!< 里线纹理id Inner line texture ID +@property (nonatomic, strong) UIImage *simple3DFillMarkerImage; //!< 简易三维下里线纹理 Inner line texture in simple 3D +@property (nonatomic, strong) UIImage *borderMarkerImage; //!< 边线纹理id Border texture ID +@property (nonatomic, assign) uint32_t fillColor; //!< 填充颜色 Fill color +@property (nonatomic, assign) uint32_t borderColor; //!< 边颜色 Border color +@property (nonatomic, assign) uint32_t selectFillColor; //!< 选中的填充颜色 Selected fill color +@property (nonatomic, assign) uint32_t unSelectFillColor; //!< 未选中的填充颜色 Unselected fill color +@property (nonatomic, assign) uint32_t selectBorderColor; //!< 选中的边线颜色 Selected border color +@property (nonatomic, assign) uint32_t unSelectBorderColor;//!< 未选中的边线颜色 Unselected border color +@property (nonatomic, assign) uint32_t pointDistance; //!< 两点间距离 Distance between two points +@property (nonatomic, assign) uint32_t priority; //!< 设置item的优先级(只有usePoint为true有效) Set the priority of the item (only valid when usePoint is true) +@property (nonatomic, assign) MAMapRouteTexture routeTexture; //!< 路线纹理枚举 具体参考MapRouteTexture Route texture enumeration, refer to MapRouteTexture for details +@property (nonatomic, strong) MAPolylineTextureInfo *lineTextureInfo; //!< 纹理坐标参数 Texture coordinate parameters +@property (nonatomic, strong) MAPolylineTextureInfo *lineSimple3DTextureInfo;//!< 简易三维下纹理坐标参数 Simplified 3D texture coordinate parameters +@property (nonatomic, strong) MAPolylineCapTextureInfo *lineCapTextureInfo; //!< 线帽纹理参数 Line cap texture parameters +@property (nonatomic, assign) NSString *lineBorderQuery; //!< 边线纹理资源URL地址 Edge texture resource URL +@property (nonatomic, assign) NSString *lineFillQuery; //!< 中心线纹理资源URL地址 Centerline texture resource URL +@property (nonatomic, assign) MAMapRouteLineWidthType lineWidthType; //!< 线宽类型 Line width type +@end + + +typedef NS_ENUM(NSInteger, MAMapRouteHighLightType) { + MAMapRouteHighLightTypeNone = 0, //!< 无高亮效果 No highlight effect + MAMapRouteHighLightTypeSegment //!< 有一段路高亮,其他路段非高亮显示 One section of the road is highlighted, while other sections are not highlighted +}; + +@interface MARouteOverlayHighLightParam : NSObject +@property (nonatomic, assign) uint32_t fillColorHightLight; //!< 高亮路段的填充颜色 The fill color of the highlighted section +@property (nonatomic, assign) uint32_t borderColorHightLight; //!< 高亮路段的边缘颜色 The edge color of the highlighted section +@property (nonatomic, assign) uint32_t fillColorNormal; //!< 非高亮路段的填充颜色 The fill color of the non-highlighted section +@property (nonatomic, assign) uint32_t borderColorNormal; //!< 非高亮路段的边缘颜色 The edge color of the non-highlighted section +@property (nonatomic, assign) uint32_t arrowColorNormal; //!< 非高亮路段的鱼骨线颜色,高亮路段使用纹理原来的颜色 The color of the fishbone line in the non-highlighted section, the highlighted section uses the original color of the texture +@end + + +/** + * @brief 路线交通状态 route traffic status + */ +@interface MAMapRouteOverlayTrafficState : NSObject +@property (nonatomic, assign) uint32_t state; //!< 路线状态 4B route status 4B +@property (nonatomic, assign) uint32_t point2DIndex; //!< 二维起始坐标点索引 4B 2D starting coordinate point index 4B +@property (nonatomic, assign) uint32_t point3DIndex; //!< 三维起始坐标点索引 4B 3D starting coordinate point index 4B +@property (nonatomic, assign) uint32_t point3DCount; //!< 三维坐标点个数 4B number of 3D coordinate points 4B +@end + +/** + * @brief 路线颜色 Route color + */ +@interface MAMapRouteOverlayColorIndex : NSObject +@property (nonatomic, assign) uint32_t nColor; //!< 路线颜色(ARGB) Route color(ARGB) +@property (nonatomic, assign) uint32_t point2DIndex; //!< 二维起始坐标点索引 (如无二维坐标) 2D starting coordinate point index (if no 2D coordinates) +@property (nonatomic, assign) uint32_t point3DIndex; //!< 三维起始坐标点索引 4B 3D starting coordinate point index 4B +@property (nonatomic, assign) uint32_t point3DCount; //!< 三维坐标点个数 4B number of 3D coordinate points 4B +@end + +/** + * @brief 路线道路名称 Route road name + */ +@interface MAMapRouteOverlayRoadName : NSObject +@property (nonatomic, copy) NSString *name; //!< 道路名称字符串, UTF8编码 Road name string, UTF8 encoding +@property (nonatomic, assign) uint32_t point2DIndex; //!< 道路对应的二维形点起始索引 4B Starting index of 2D shape points for road (4B) +@property (nonatomic, assign) uint32_t point2DSize; //!< 道路对应的二维形点个数 4B Number of 2D shape points for road (4B) +@property (nonatomic, assign) uint32_t point3DIndex; //!< 道路对应的三维形点起始索引 4B Starting index of 3D shape points for the road 4B +@property (nonatomic, assign) uint32_t point3DSize; //!< 道路对应的三维形点个数 4B Number of 3D shape points for the road 4B +@property (nonatomic, assign) uint32_t roadLength; //!< 道路长度 4B Road length 4B +@property (nonatomic, assign) uint32_t roadClass; //!< 道路等级 4B Road level 4B +@end + +@interface MAMapPoint2F : NSObject +@property (nonatomic, assign) CGFloat x; +@property (nonatomic, assign) CGFloat y; +@end + +@interface MAMapPoint3F : MAMapPoint2F +@property (nonatomic, assign) CGFloat z; +@end + +@interface MAMapRouteOverlayData : NSObject +@property (nonatomic, assign) uint32_t checkFlag; +@property (nonatomic, assign) uint32_t routeType; +@property (nonatomic, copy) NSArray *point2DArray; +@property (nonatomic, copy) NSArray *trafficStateArray; +@property (nonatomic, copy) NSArray *roadNameArray; +@property (nonatomic, copy) NSArray *point2DFlagArray; +@property (nonatomic, copy) NSArray *point3DArray; +@property (nonatomic, copy) NSArray *point3DFlagArray; +@property (nonatomic, copy) NSArray *colorIndexArray; +@end + +NS_ASSUME_NONNULL_END +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAShape.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAShape.h new file mode 100755 index 0000000..66dd7fb --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAShape.h @@ -0,0 +1,28 @@ +// +// MAShape.h +// MAMapKit +// +// +// Copyright (c) 2011年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAAnnotation.h" +#import "MABaseOverlay.h" + +///该类为一个抽象类,定义了基于MAAnnotation的MAShape类的基本属性和行为,不能直接使用,必须子类化之后才能使用 +///This is an abstract class that defines the basic properties and behaviors of the MAShape class based on MAAnnotation. It cannot be used directly and must be subclassed before use. +@interface MAShape : MABaseOverlay { + + NSString *_title; ///<标题 Title + NSString *_subtitle; ///<副标题 Subtitle +} + +///标题 Title +@property (nonatomic, copy) NSString *title; + +///副标题 Subtitle +@property (nonatomic, copy) NSString *subtitle; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MATerrainOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATerrainOverlay.h new file mode 100644 index 0000000..2ba0294 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATerrainOverlay.h @@ -0,0 +1,33 @@ +// +// MATopographyOverlay.h +// MAMapKit +// +// Created by JZ on 2021/3/17. +// Copyright © 2021 Amap. All rights reserved. +// + +#import "MAMapKit.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MATerrainOverlay : MATileOverlay + +///terrainURLTemplate获取地形数据,默认使用高德地形数据 +///terrainURLTemplate obtains terrain data, defaulting to AutoNavi terrain data +@property (readonly) NSString *terrainURLTemplate; + +///terrainTextureURLTemplate获取地形纹理数据,默认使用高德卫星数据 +///terrainTextureURLTemplate obtains terrain texture data, defaulting to AutoNavi satellite imagery +@property (readonly) NSString *terrainTextureURLTemplate; + +@property (strong, nonatomic) UIImage *terrainDefalutImage; + +/** + * @brief 初始化地形overlay + * initialize terrain overlay + */ +- (instancetype)initDefaultTerrainOverlay; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MATerrainOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATerrainOverlayRenderer.h new file mode 100644 index 0000000..ca9acda --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATerrainOverlayRenderer.h @@ -0,0 +1,18 @@ +// +// MATopographyOverlayRenderer.h +// MAMapKit +// +// Created by JZ on 2021/3/17. +// Copyright © 2021 Amap. All rights reserved. +// + +#import "MAMapKit.h" +#import "MATileOverlayRenderer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MATerrainOverlayRenderer : MATileOverlayRenderer + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MATileOverlay.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATileOverlay.h new file mode 100644 index 0000000..6f386ec --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATileOverlay.h @@ -0,0 +1,97 @@ +// +// MATileOverlay.h +// MapKit_static +// +// Created by Li Fei on 11/22/13. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_TILE + +#import "MAOverlay.h" +#import "MABaseOverlay.h" + +///该类是覆盖在球面墨卡托投影上的图片tiles的数据源 +///This class is the data source for image tiles covering the spherical Mercator projection +@interface MATileOverlay : MABaseOverlay + +///瓦片大小,默认是256x256, 最小支持64*64 +///Tile size, default is 256x256, minimum support is 64*64 +@property (nonatomic, assign) CGSize tileSize; + +///tileOverlay的可见最小Zoom值 +///The minimum visible Zoom value for tileOverlay +@property NSInteger minimumZ __attribute((deprecated("Deprecated, calling has no effect. since 9.6.0"))); + +///tileOverlay的可见最大Zoom值 +///The maximum visible Zoom value of tileOverlay +@property NSInteger maximumZ __attribute((deprecated("Deprecated, calling has no effect. since 9.6.0"))); + +///同initWithURLTemplate:中的URLTemplate +///Same as the URLTemplate in initWithURLTemplate: +@property (readonly) NSString *URLTemplate; + +///暂未开放 +///not yet open +@property (nonatomic) BOOL canReplaceMapContent; + +///是否停止不在显示区域内的瓦片下载,默认NO. since 5.3.0 +///whether to stop downloading tiles outside the display area, default is NO. since 5.3.0 +@property (nonatomic, assign) BOOL disableOffScreenTileLoading; + +/** + * @brief 根据指定的URLTemplate生成tileOverlay + * Generate tileOverlay based on the specified URLTemplate + * @param URLTemplate URLTemplate是一个包含"{x}","{y}","{z}","{scale}"的字符串,"{x}","{y}","{z}","{scale}"会被tile path的值所替换,并生成用来加载tile图片数据的URL 。例如 http://server/path?x={x}&y={y}&z={z}&scale={scale} + * URLTemplate is a string containing "{x}","{y}","{z}","{scale}", where "{x}","{y}","{z}","{scale}" will be replaced by the tile path value to generate the URL for loading tile image data. For example, http://server/path?x={x}&y={y}&z={z}&scale={scale} + * @return 以指定的URLTemplate字符串生成tileOverlay + * Generate tileOverlay using the specified URLTemplate string + */ +- (id)initWithURLTemplate:(NSString *)URLTemplate; + +@end + +///MATileOverlayPath +struct MATileOverlayPath{ + NSInteger x; ///< x坐标 x-coordinate + NSInteger y; ///< y坐标 y-coordinate + NSInteger z; ///< 缩放级别 zoom level + CGFloat contentScaleFactor; ///< 屏幕的scale factor screen's scale factor + NSInteger index; ///< 对应的下载url corresponding download URL + NSInteger requestId; ///<资源下载唯一标识,用于记录 Unique identifier for resource download, used for recording +}; +typedef struct MATileOverlayPath MATileOverlayPath; + +///子类可覆盖CustomLoading中的方法来自定义加载MATileOverlay的行为。 +///Subclasses can override methods in CustomLoading to customize the behavior of loading MATileOverlay +@interface MATileOverlay (CustomLoading) + +/** + * @brief 以tile path生成URL。用于加载tile,此方法默认填充URLTemplate + * Generate URL from tile path. Used to load tiles, this method defaults to filling URLTemplate + * @param path tile path + * @return 以tile path生成tileOverlay + * Generate tileOverlay from tile path + */ +- (NSURL *)URLForTilePath:(MATileOverlayPath)path; + +/** + * @brief 加载被请求的tile,并以tile数据或加载tile失败error访问回调block;默认实现为首先用URLForTilePath去获取URL,然后用异步NSURLConnection加载tile + * Load the requested tile and access the callback block with tile data or a tile loading failure error; the default implementation first uses URLForTilePath to obtain the URL, then loads the tile asynchronously with NSURLConnection. + * @param path tile path + * @param result 用来传入tile数据或加载tile失败的error访问的回调block + * A callback block used to pass in tile data or access errors when tile loading fails + */ +- (void)loadTileAtPath:(MATileOverlayPath)path result:(void (^)(NSData *tileData, NSError *error))result; + +/** + * @brief 取消请求瓦片,当地图显示区域发生变化时,会取消显示区域外的瓦片的下载, 当disableOffScreenTileLoading=YES时会被调用。since 5.3.0 + * Cancel tile requests, when the map display area changes, it will cancel the download of tiles outside the display area, and it will be called when disableOffScreenTileLoading=YES. since 5.3.0 + * @param path tile path + */ +- (void)cancelLoadOfTileAtPath:(MATileOverlayPath)path; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MATileOverlayRenderer.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATileOverlayRenderer.h new file mode 100644 index 0000000..76fa91a --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATileOverlayRenderer.h @@ -0,0 +1,41 @@ +// +// MATileOverlayRenderer.h +// MapKit_static +// +// Created by Li Fei on 11/25/13. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#if MA_INCLUDE_OVERLAY_TILE + +#import "MAOverlayRenderer.h" +#import "MATileOverlay.h" + +///此类是将MAOverlayRenderer中的覆盖tiles显示在地图上的Renderer +///This type of Renderer displays overlay tiles from MAOverlayRenderer on the map +@interface MATileOverlayRenderer : MAOverlayRenderer + +///覆盖在球面墨卡托投影上的图片tiles的数据源 +///Data source for image tiles covering the spherical Mercator projection +@property (nonatomic ,readonly) MATileOverlay *tileOverlay; + +/** + * @brief 根据指定的tileOverlay生成将tiles显示在地图上的Renderer + * Generates a Renderer to display tiles on the map based on the specified tileOverlay + * @param tileOverlay 制定了覆盖图片 + * Established overlay images + * @return 以tileOverlay新生成Renderer + * Newly generated Renderer with tileOverlay + */ +- (instancetype)initWithTileOverlay:(MATileOverlay *)tileOverlay; + +/** + * @brief 清除所有tile的缓存,并刷新overlay + * Clear the cache of all tiles and refresh the overlay + */ +- (void)reloadData; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MATouchPoi.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATouchPoi.h new file mode 100644 index 0000000..e0c5f20 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATouchPoi.h @@ -0,0 +1,29 @@ +// +// MATouchPoi.h +// MapKit_static +// +// Created by songjian on 13-7-17. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import + +///MATouchPoi 定义 +///MATouchPoi definition +@interface MATouchPoi : NSObject + +///名称 +///name +@property (nonatomic, copy, readonly) NSString *name; + +///经纬度坐标 +///latitude and longitude coordinates +@property (nonatomic, assign, readonly) CLLocationCoordinate2D coordinate; + +///poi的ID +///POI ID +@property (nonatomic, copy, readonly) NSString *uid; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MATraceLocation.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATraceLocation.h new file mode 100644 index 0000000..5b499e1 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATraceLocation.h @@ -0,0 +1,50 @@ +// +// MATraceLocation.h +// MAMapKit +// +// Created by shaobin on 16/9/1. +// Copyright © 2016年 Amap. All rights reserved. +// + + + +#import "MAConfig.h" + +#if MA_INCLUDE_TRACE_CORRECT + +#import +#import + +///返回轨迹点定义 +///Return track point definition +@interface MATracePoint : NSObject + +///纬度坐标 +///Latitude coordinate +@property (nonatomic, assign) CLLocationDegrees latitude; +///经度坐标 +///Longitude coordinate +@property (nonatomic, assign) CLLocationDegrees longitude; + +@end + +///传入轨迹点定义 +///Incoming track point definition +@interface MATraceLocation : NSObject + +///经纬度坐标 +///Latitude and longitude coordinates +@property (nonatomic, assign) CLLocationCoordinate2D loc; +///角度, 标识移动方向,单位度 +///Angle, indicating the direction of movement, unit degree +@property (nonatomic, assign) double angle; +///速度,单位km/h +///Speed, unit km/h +@property (nonatomic, assign) double speed; +///时间,单位毫秒 +///Time, unit millisecond +@property (nonatomic, assign) double time; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MATraceManager.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATraceManager.h new file mode 100644 index 0000000..0a56358 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MATraceManager.h @@ -0,0 +1,129 @@ +// +// MATraceManager.h +// MAMapKit +// +// Created by shaobin on 16/9/1. +// Copyright © 2016年 Amap. All rights reserved. +// + + + +#import "MAConfig.h" + +#if MA_INCLUDE_TRACE_CORRECT + +#import +#import +#import "MATraceLocation.h" + +@class MATraceManager; + +///处理中回调, index: 批次编号,0 based +///Processing callback, index: batch number, 0 based +typedef void(^MAProcessingCallback)(int index, NSArray *points); + +///成功回调,distance:距离,单位米 +///Success callback, distance: distance, unit meters +typedef void(^MAFinishCallback)(NSArray *points, double distance); + +///失败回调 +///Failure callback +typedef void(^MAFailedCallback)(int errorCode, NSString *errorDesc); + +///定位回调, locations: 原始定位点; tracePoints: 纠偏后的点,如果纠偏失败返回nil; distance:距离; error: 纠偏失败时的错误信息 +///Location callback, locations: raw location points; tracePoints: corrected points, returns nil if correction fails; distance: distance; error: error message when correction fails +typedef void(^MATraceLocationCallback)(NSArray *locations, NSArray *tracePoints, double distance, NSError *error); + +/** + * @brief 轨迹定位的代理协议,since v6.2.0 + * Proxy protocol for trajectory positioning since v6.2.0 +*/ +@protocol MATraceDelegate + +@required + +/** + * @brief 轨迹定位纠偏的回调方法,since v6.2.0 + * Callback method for trajectory positioning correction v6.2.0 + * @param manager 轨迹定位管理对象 + * Trajectory positioning management object + * @param locations 已经完成纠偏的原始定位数据 + * Original positioning data that has been corrected + * @param tracePoints 已经完成纠偏处理后的轨迹点 + * Trajectory points after correction processing + * @param distance 距离,单位米 + * Distance, in meters + * @param error 如果成功的话为nil,否则为失败原因 + * If successful, it is nil, otherwise it is the reason for failure + */ +- (void)traceManager:(MATraceManager *)manager + didTrace:(NSArray *)locations + correct:(NSArray *)tracePoints + distance:(double)distance + withError:(NSError *)error; + +@optional +/** + * @brief 当plist配置NSLocationAlwaysUsageDescription或者NSLocationAlwaysAndWhenInUseUsageDescription,并且[CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined,会调用代理的此方法。 + 此方法实现调用后台权限API即可( 该回调必须实现 [locationManager requestAlwaysAuthorization] ); since 6.8.1 + * When the plist configures NSLocationAlwaysUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription, and [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined, this method of the delegate will be called. This method can be implemented by calling the background permission API (this callback must implement [locationManager requestAlwaysAuthorization]); since 6.8.1 + * @param locationManager 地图的CLLocationManager。 + * Map's CLLocationManager + */ +- (void)mapViewRequireLocationAuth:(CLLocationManager *)locationManager; + +@end + +///轨迹纠偏管理类 +///Trajectory correction management class +@interface MATraceManager : NSObject + +/** + * @brief 单例方法 + * Singleton method + */ ++ (instancetype)sharedInstance; + +/** + * @brief 获取纠偏后的经纬度点集 + * Obtain the corrected latitude and longitude point set + * @param locations 待纠偏处理的点集, 顺序即为传入的顺序 + * Point set to be corrected, the order is the input order + * @param type loctions经纬度坐标的类型, 如果已经是高德坐标系,传 -1 + * The type of loctions latitude and longitude coordinates. If it is already in the AutoNavi coordinate system, pass -1 + * @param processingCallback 如果一次传入点过多,内部会分批处理。每处理完一批就调用此回调 + * If too many points are passed in at once, they will be processed in batches internally. This callback is called after each batch is processed + * @param finishCallback 全部处理完毕调用此回调 + * This callback is called when all processing is complete + * @param failedCallback 失败调用此回调 + * This callback is called on failure + * @return 返回一个NSOperation对象,可调用cancel取消 + * Returns an NSOperation object, which can be canceled by calling cancel + */ +- (NSOperation *)queryProcessedTraceWith:(NSArray*)locations + type:(AMapCoordinateType)type + processingCallback:(MAProcessingCallback)processingCallback + finishCallback:(MAFinishCallback)finishCallback + failedCallback:(MAFailedCallback)failedCallback; + +/** + * @brief 轨迹定位的代理回调对象,配合start和stop方法使用,since v6.2.0 + * The delegate callback object for trajectory positioning, used in conjunction with the start and stop methods. since v6.2.0 + */ +@property (nonatomic, weak) id delegate; + +/** + * @brief 开始轨迹定位, 内部使用系统CLLocationManager,distanceFilter,desiredAccuracy均为系统默认值,since v6.2.0 + * Start trajectory tracking, internally using the system CLLocationManager, distanceFilter, and desiredAccuracy are set to system default values. since v6.2.0 + */ +- (void)start; + +/** + * @brief 停止轨迹定位,since v6.2.0 + * Stop trajectory tracking. since v6.2.0 + */ +- (void)stop; + +@end + +#endif diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAUserLocation.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAUserLocation.h new file mode 100755 index 0000000..34f7756 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAUserLocation.h @@ -0,0 +1,32 @@ +// +// MAUserLocation.h +// MAMapKit +// +// Created by yin cai on 12-1-4. +// Copyright © 2016 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import "MAAnimatedAnnotation.h" + +@class CLLocation; +@class CLHeading; + +///定位信息类 +///Location Information Category +@interface MAUserLocation : MAAnimatedAnnotation + +///位置更新状态,如果正在更新位置信息,则该值为YES +///Location update status, if the location information is being updated, the value is YES +@property (readonly, nonatomic, getter = isUpdating) BOOL updating; + +///位置信息,如果MAMapView的showsUserLocation为NO,或者尚未定位成功,则该值为nil +///Location information, if the showsUserLocation of MAMapView is NO, or the location has not been successfully determined, the value is nil +@property (readonly, nonatomic, strong) CLLocation *location; + +///heading信息 +///Heading information +@property (readonly, nonatomic, strong) CLHeading *heading; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Headers/MAUserLocationRepresentation.h b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAUserLocationRepresentation.h new file mode 100644 index 0000000..86bdfd1 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Headers/MAUserLocationRepresentation.h @@ -0,0 +1,48 @@ +// +// MAUserLocationRepresentation.h +// MAMapKit +// +// Created by shaobin on 16/12/27. +// Copyright © 2016年 Amap. All rights reserved. +// + +#import "MAConfig.h" +#import +#import + +#define kAccuracyCircleDefaultColor [UIColor colorWithRed:136/255.0 green:166/255.0 blue:227/255.0 alpha:.3] + +///用户位置显示样式控制 +///User location display style control +@interface MAUserLocationRepresentation : NSObject + +///精度圈是否显示,默认YES +///Whether to show the accuracy circle, default is YES +@property (nonatomic, assign) BOOL showsAccuracyRing; +///是否显示方向指示(MAUserTrackingModeFollowWithHeading模式开启)。默认为YES +///Whether to show the direction indicator (MAUserTrackingModeFollowWithHeading mode enabled). Default is YES +@property (nonatomic, assign) BOOL showsHeadingIndicator; +///精度圈 填充颜色, 默认 kAccuracyCircleDefaultColor +///Accuracy circle fill color, default is kAccuracyCircleDefaultColor +@property (nonatomic, strong) UIColor *fillColor; +///精度圈 边线颜色, 默认 kAccuracyCircleDefaultColor +///Accuracy circle border color; default is kAccuracyCircleDefaultColor +@property (nonatomic, strong) UIColor *strokeColor; +///精度圈 边线宽度,默认0 +///Accuracy circle border width, default is 0 +@property (nonatomic, assign) CGFloat lineWidth; + +///定位点背景色,不设置默认白色 +///Positioning point background color, default white if not set +@property (nonatomic, strong) UIColor *locationDotBgColor; +///定位点蓝色圆点颜色,不设置默认蓝色 +///Positioning point blue dot color, default blue if not set +@property (nonatomic, strong) UIColor *locationDotFillColor; +///内部蓝色圆点是否使用律动效果, 默认YES +///Whether to use pulsating effect for inner blue dot, default YES +@property (nonatomic, assign) BOOL enablePulseAnnimation; +///定位图标, 与蓝色原点互斥 +///Location icon, mutually exclusive with the blue dot +@property (nonatomic, strong) UIImage* image; + +@end diff --git a/Pods/AMap3DMap/MAMapKit.framework/Info.plist b/Pods/AMap3DMap/MAMapKit.framework/Info.plist new file mode 100644 index 0000000..89686a5 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/Info.plist differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/MAMapKit b/Pods/AMap3DMap/MAMapKit.framework/MAMapKit new file mode 100644 index 0000000..e5dd075 Binary files /dev/null and b/Pods/AMap3DMap/MAMapKit.framework/MAMapKit differ diff --git a/Pods/AMap3DMap/MAMapKit.framework/Modules/module.modulemap b/Pods/AMap3DMap/MAMapKit.framework/Modules/module.modulemap new file mode 100644 index 0000000..1c5fcb2 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module MAMapKit { + umbrella header "MAMapKit.h" + export * + + module * { export * } +} diff --git a/Pods/AMap3DMap/MAMapKit.framework/version.txt b/Pods/AMap3DMap/MAMapKit.framework/version.txt new file mode 100644 index 0000000..1fedb32 --- /dev/null +++ b/Pods/AMap3DMap/MAMapKit.framework/version.txt @@ -0,0 +1 @@ +11.1.200+3dmap.9fafdbc8.1601 diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/AMapDemangleNodes.def b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapDemangleNodes.def new file mode 100644 index 0000000..3964df2 --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapDemangleNodes.def @@ -0,0 +1,260 @@ +//===--- AMapDemangleNodes.def - Demangling Tree Metaprogramming ----*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file defines macros useful for macro-metaprogramming with nodes in +// the demangling tree. +// +//===----------------------------------------------------------------------===// + +/// NODE(ID) +/// The node's enumerator value is Node::Kind::ID. + +/// CONTEXT_NODE(ID) +/// Nodes that can serve as contexts for other entities. +#ifndef CONTEXT_NODE +#define CONTEXT_NODE(ID) NODE(ID) +#endif + +CONTEXT_NODE(Allocator) +CONTEXT_NODE(AnonymousContext) +NODE(AnyProtocolConformanceList) +NODE(ArgumentTuple) +NODE(AssociatedType) +NODE(AssociatedTypeRef) +NODE(AssociatedTypeMetadataAccessor) +NODE(DefaultAssociatedTypeMetadataAccessor) +NODE(AssociatedTypeWitnessTableAccessor) +NODE(BaseWitnessTableAccessor) +NODE(AutoClosureType) +NODE(BoundGenericClass) +NODE(BoundGenericEnum) +NODE(BoundGenericStructure) +NODE(BoundGenericProtocol) +NODE(BoundGenericOtherNominalType) +NODE(BoundGenericTypeAlias) +NODE(BoundGenericFunction) +NODE(BuiltinTypeName) +NODE(CFunctionPointer) +CONTEXT_NODE(Class) +NODE(ClassMetadataBaseOffset) +NODE(ConcreteProtocolConformance) +CONTEXT_NODE(Constructor) +NODE(CoroutineContinuationPrototype) +CONTEXT_NODE(Deallocator) +NODE(DeclContext) +CONTEXT_NODE(DefaultArgumentInitializer) +NODE(DependentAssociatedConformance) +NODE(DependentAssociatedTypeRef) +NODE(DependentGenericConformanceRequirement) +NODE(DependentGenericParamCount) +NODE(DependentGenericParamType) +NODE(DependentGenericSameTypeRequirement) +NODE(DependentGenericLayoutRequirement) +NODE(DependentGenericSignature) +NODE(DependentGenericType) +NODE(DependentMemberType) +NODE(DependentPseudogenericSignature) +NODE(DependentProtocolConformanceRoot) +NODE(DependentProtocolConformanceInherited) +NODE(DependentProtocolConformanceAssociated) +CONTEXT_NODE(Destructor) +CONTEXT_NODE(DidSet) +NODE(Directness) +NODE(DynamicAttribute) +NODE(DirectMethodReferenceAttribute) +NODE(DynamicSelf) +NODE(DynamicallyReplaceableFunctionImpl) +NODE(DynamicallyReplaceableFunctionKey) +NODE(DynamicallyReplaceableFunctionVar) +CONTEXT_NODE(Enum) +NODE(EnumCase) +NODE(ErrorType) +NODE(EscapingAutoClosureType) +NODE(NoEscapeFunctionType) +NODE(ExistentialMetatype) +CONTEXT_NODE(ExplicitClosure) +CONTEXT_NODE(Extension) +NODE(FieldOffset) +NODE(FullTypeMetadata) +CONTEXT_NODE(Function) +NODE(FunctionSignatureSpecialization) +NODE(FunctionSignatureSpecializationParam) +NODE(FunctionSignatureSpecializationParamKind) +NODE(FunctionSignatureSpecializationParamPayload) +NODE(FunctionType) +NODE(GenericPartialSpecialization) +NODE(GenericPartialSpecializationNotReAbstracted) +NODE(GenericProtocolWitnessTable) +NODE(GenericProtocolWitnessTableInstantiationFunction) +NODE(ResilientProtocolWitnessTable) +NODE(GenericSpecialization) +NODE(GenericSpecializationNotReAbstracted) +NODE(GenericSpecializationParam) +NODE(InlinedGenericFunction) +NODE(GenericTypeMetadataPattern) +CONTEXT_NODE(Getter) +NODE(Global) +CONTEXT_NODE(GlobalGetter) +NODE(Identifier) +NODE(Index) +CONTEXT_NODE(IVarInitializer) +CONTEXT_NODE(IVarDestroyer) +NODE(ImplEscaping) +NODE(ImplConvention) +NODE(ImplFunctionAttribute) +NODE(ImplFunctionType) +CONTEXT_NODE(ImplicitClosure) +NODE(ImplParameter) +NODE(ImplResult) +NODE(ImplErrorResult) +NODE(InOut) +NODE(InfixOperator) +CONTEXT_NODE(Initializer) +NODE(KeyPathGetterThunkHelper) +NODE(KeyPathSetterThunkHelper) +NODE(KeyPathEqualsThunkHelper) +NODE(KeyPathHashThunkHelper) +NODE(LazyProtocolWitnessTableAccessor) +NODE(LazyProtocolWitnessTableCacheVariable) +NODE(LocalDeclName) +CONTEXT_NODE(MaterializeForSet) +NODE(MergedFunction) +NODE(Metatype) +NODE(MetatypeRepresentation) +NODE(Metaclass) +NODE(MethodLookupFunction) +NODE(ObjCMetadataUpdateFunction) +CONTEXT_NODE(ModifyAccessor) +CONTEXT_NODE(Module) +CONTEXT_NODE(NativeOwningAddressor) +CONTEXT_NODE(NativeOwningMutableAddressor) +CONTEXT_NODE(NativePinningAddressor) +CONTEXT_NODE(NativePinningMutableAddressor) +NODE(NominalTypeDescriptor) +NODE(NonObjCAttribute) +NODE(Number) +NODE(ObjCAttribute) +NODE(ObjCBlock) +CONTEXT_NODE(OtherNominalType) +CONTEXT_NODE(OwningAddressor) +CONTEXT_NODE(OwningMutableAddressor) +NODE(PartialApplyForwarder) +NODE(PartialApplyObjCForwarder) +NODE(PostfixOperator) +NODE(PrefixOperator) +NODE(PrivateDeclName) +NODE(PropertyDescriptor) +CONTEXT_NODE(Protocol) +CONTEXT_NODE(ProtocolSymbolicReference) +NODE(ProtocolConformance) +NODE(ProtocolConformanceRefInTypeModule) +NODE(ProtocolConformanceRefInProtocolModule) +NODE(ProtocolConformanceRefInOtherModule) +NODE(ProtocolDescriptor) +NODE(ProtocolConformanceDescriptor) +NODE(ProtocolList) +NODE(ProtocolListWithClass) +NODE(ProtocolListWithAnyObject) +NODE(ProtocolSelfConformanceDescriptor) +NODE(ProtocolSelfConformanceWitness) +NODE(ProtocolSelfConformanceWitnessTable) +NODE(ProtocolWitness) +NODE(ProtocolWitnessTable) +NODE(ProtocolWitnessTableAccessor) +NODE(ProtocolWitnessTablePattern) +NODE(ReabstractionThunk) +NODE(ReabstractionThunkHelper) +CONTEXT_NODE(ReadAccessor) +NODE(RelatedEntityDeclName) +NODE(RetroactiveConformance) +NODE(ReturnType) +NODE(Shared) +NODE(Owned) +NODE(SILBoxType) +NODE(SILBoxTypeWithLayout) +NODE(SILBoxLayout) +NODE(SILBoxMutableField) +NODE(SILBoxImmutableField) +CONTEXT_NODE(Setter) +NODE(SpecializationPassID) +NODE(IsSerialized) +CONTEXT_NODE(Static) +CONTEXT_NODE(Structure) +CONTEXT_NODE(Subscript) +NODE(Suffix) +NODE(ThinFunctionType) +NODE(Tuple) +NODE(TupleElement) +NODE(TupleElementName) +NODE(Type) +CONTEXT_NODE(TypeSymbolicReference) +CONTEXT_NODE(TypeAlias) +NODE(TypeList) +NODE(TypeMangling) +NODE(TypeMetadata) +NODE(TypeMetadataAccessFunction) +NODE(TypeMetadataCompletionFunction) +NODE(TypeMetadataInstantiationCache) +NODE(TypeMetadataInstantiationFunction) +NODE(TypeMetadataSingletonInitializationCache) +NODE(TypeMetadataLazyCache) +NODE(UncurriedFunctionType) +#define REF_STORAGE(Name, ...) NODE(Name) +#include "AMapReferenceStorage.def" +CONTEXT_NODE(UnsafeAddressor) +CONTEXT_NODE(UnsafeMutableAddressor) +NODE(ValueWitness) +NODE(ValueWitnessTable) +CONTEXT_NODE(Variable) +NODE(VTableThunk) +NODE(VTableAttribute) // note: old mangling only +CONTEXT_NODE(WillSet) +NODE(ReflectionMetadataBuiltinDescriptor) +NODE(ReflectionMetadataFieldDescriptor) +NODE(ReflectionMetadataAssocTypeDescriptor) +NODE(ReflectionMetadataSuperclassDescriptor) +NODE(GenericTypeParamDecl) +NODE(CurryThunk) +NODE(DispatchThunk) +NODE(MethodDescriptor) +NODE(ProtocolRequirementsBaseDescriptor) +NODE(AssociatedConformanceDescriptor) +NODE(DefaultAssociatedConformanceAccessor) +NODE(BaseConformanceDescriptor) +NODE(AssociatedTypeDescriptor) +NODE(ThrowsAnnotation) +NODE(EmptyList) +NODE(FirstElementMarker) +NODE(VariadicMarker) +NODE(OutlinedBridgedMethod) +NODE(OutlinedCopy) +NODE(OutlinedConsume) +NODE(OutlinedRetain) +NODE(OutlinedRelease) +NODE(OutlinedInitializeWithTake) +NODE(OutlinedInitializeWithCopy) +NODE(OutlinedAssignWithTake) +NODE(OutlinedAssignWithCopy) +NODE(OutlinedDestroy) +NODE(OutlinedVariable) +NODE(AssocTypePath) +NODE(LabelList) +NODE(ModuleDescriptor) +NODE(ExtensionDescriptor) +NODE(AnonymousDescriptor) +NODE(AssociatedTypeGenericParamRef) +NODE(SugaredOptional) +NODE(SugaredArray) +NODE(SugaredDictionary) +NODE(SugaredParen) +#undef CONTEXT_NODE +#undef NODE diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/AMapFoundationKit b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapFoundationKit new file mode 100644 index 0000000..e9765f5 Binary files /dev/null and b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapFoundationKit differ diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/AMapReferenceStorage.def b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapReferenceStorage.def new file mode 100644 index 0000000..f74ded7 --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapReferenceStorage.def @@ -0,0 +1,197 @@ +//===--- AMapReferenceStorage.def - Non-default reference storage ---*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file defines non-default reference storage kind macros used for +// macro-metaprogramming. +// +//===----------------------------------------------------------------------===// + +/// There are two fundamental reference storage types: checked and unchecked. +/// Checked storage types have runtime enforced correctness. +/// Unchecked storage types have no runtime enforced correctness. +/// +/// Checked reference storage types are also subcategorized by loadability. +/// * Always loadable: The compiler may move the reference or use registers. +/// * Never loadable: The runtime (etc) tracks the address of the reference. +/// * Sometimes loadable: If the reference is a native object, then it is +/// always loadable. Otherwise fall back to never loadable semantics, a.k.a. +/// "address only". +/// +/// Unchecked reference storage types are always loadable. +/// +/// The primary macros therefore are: +/// * ALWAYS_LOADABLE_CHECKED_REF_STORAGE +/// * SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +/// * NEVER_LOADABLE_CHECKED_REF_STORAGE +/// * UNCHECKED_REF_STORAGE +/// +/// Helper macros include: +/// * CHECKED_REF_STORAGE -- Any checked reference storage type. Specifically +/// "always", "sometimes", and "never" -- but not "unchecked". +/// * LOADABLE_REF_STORAGE -- Any loadable reference storage type. Specifically +/// "always", "sometimes", and "unchecked" -- but not "never". +/// * ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE -- self describing. +/// * NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE -- self describing. +/// +/// SUBSYSTEMS NOTES +/// +/// In general, reference storage types are barely visible in the user facing +/// type system and therefore AST clients above SIL can get away with +/// just REF_STORAGE, or CHECKED_REF_STORAGE with UNCHECKED_REF_STORAGE. +/// +/// When it comes to SIL aware AST clients, loadability matters. The best way +/// to understand how the helper macros are used is to look at SILNodes.def. +/// What follows is a short -- possibly not up to date -- summary: +/// +/// UNCHECKED_REF_STORAGE +/// Name##RetainValueInst +/// Name##ReleaseValueInst +/// LOADABLE_REF_STORAGE +/// Ref*ToNameInst +/// Name*ToRefInst +/// NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +/// Load##Name##Inst +/// Store##Name##Inst +/// ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +/// Copy##Name##ValueInst +/// StrongRetain##Name##Inst +/// Name##RetainInst +/// Name##ReleaseInst +/// +/// After helper macro expansion: +/// +/// UNCHECKED_REF_STORAGE +/// Ref*ToNameInst +/// Name*ToRefInst +/// Name##RetainValueInst +/// Name##ReleaseValueInst +/// ALWAYS_LOADABLE_CHECKED_REF_STORAGE +/// Ref*ToNameInst +/// Name*ToRefInst +/// Copy##Name##ValueInst +/// StrongRetain##Name##Inst +/// Name##RetainInst +/// Name##ReleaseInst +/// SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +/// Ref*ToNameInst +/// Name*ToRefInst +/// Load##Name##Inst +/// Store##Name##Inst +/// Copy##Name##ValueInst +/// StrongRetain##Name##Inst +/// Name##RetainInst +/// Name##ReleaseInst +/// NEVER_LOADABLE_CHECKED_REF_STORAGE +/// Load##Name##Inst +/// Store##Name##Inst +/// +/// Finally, a note about IRGen: TypeInfos need to be created per reference +/// storage type, and SOMETIMES_LOADABLE_CHECKED_REF_STORAGE needs *two* +/// TypeInfos to be created. One for the loadable scenario, and one for the +/// address-only scenario. + + +#ifndef REF_STORAGE +#define REF_STORAGE(Name, name, NAME) +#endif + +#ifdef ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +#if defined(ALWAYS_LOADABLE_CHECKED_REF_STORAGE) || \ +defined(SOMETIMES_LOADABLE_CHECKED_REF_STORAGE) +#error Overlapping meta-programming macros +#endif +#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) +#define SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) +#endif + +#ifdef NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +#if defined(NEVER_LOADABLE_CHECKED_REF_STORAGE) || \ +defined(SOMETIMES_LOADABLE_CHECKED_REF_STORAGE) +#error Overlapping meta-programming macros +#endif +#define NEVER_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) +#define SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) +#endif + +#ifdef LOADABLE_REF_STORAGE +#if defined(ALWAYS_LOADABLE_CHECKED_REF_STORAGE) || \ +defined(SOMETIMES_LOADABLE_CHECKED_REF_STORAGE) || \ +defined(UNCHECKED_REF_STORAGE) +#error Overlapping meta-programming macros +#endif +#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +LOADABLE_REF_STORAGE(Name, name, NAME) +#define SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +LOADABLE_REF_STORAGE(Name, name, NAME) +#define UNCHECKED_REF_STORAGE(Name, name, NAME) \ +LOADABLE_REF_STORAGE(Name, name, NAME) +#endif + +#ifdef CHECKED_REF_STORAGE +#if defined(SOMETIMES_LOADABLE_CHECKED_REF_STORAGE) || \ +defined(ALWAYS_LOADABLE_CHECKED_REF_STORAGE) || \ +defined(NEVER_LOADABLE_CHECKED_REF_STORAGE) +#error Overlapping meta-programming macros +#endif +#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +CHECKED_REF_STORAGE(Name, name, NAME) +#define SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +CHECKED_REF_STORAGE(Name, name, NAME) +#define NEVER_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +CHECKED_REF_STORAGE(Name, name, NAME) +#endif + +#ifndef NEVER_LOADABLE_CHECKED_REF_STORAGE +#define NEVER_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +REF_STORAGE(Name, name, NAME) +#endif + +#ifndef SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +#define SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +REF_STORAGE(Name, name, NAME) +#endif + +#ifndef ALWAYS_LOADABLE_CHECKED_REF_STORAGE +#define ALWAYS_LOADABLE_CHECKED_REF_STORAGE(Name, name, NAME) \ +REF_STORAGE(Name, name, NAME) +#endif + +#ifndef UNCHECKED_REF_STORAGE +#define UNCHECKED_REF_STORAGE(Name, name, NAME) \ +REF_STORAGE(Name, name, NAME) +#endif + +#ifndef REF_STORAGE_RANGE +#define REF_STORAGE_RANGE(First, Last) +#endif + +// NOTE: You will need to update ReferenceOwnership in ModuleFormat.h. +//NEVER_LOADABLE_CHECKED_REF_STORAGE(Weak, weak, WEAK) +//SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Unowned, unowned, UNOWNED) +//UNCHECKED_REF_STORAGE(Unmanaged, unmanaged, UNMANAGED) +REF_STORAGE_RANGE(Weak, Unmanaged) + +#undef REF_STORAGE +#undef NEVER_LOADABLE_CHECKED_REF_STORAGE +#undef ALWAYS_LOADABLE_CHECKED_REF_STORAGE +#undef SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +#undef UNCHECKED_REF_STORAGE +#undef REF_STORAGE_RANGE + +#undef ALWAYS_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +#undef NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE +#undef LOADABLE_REF_STORAGE +#undef CHECKED_REF_STORAGE diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/AMapStandardTypesMangling.def b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapStandardTypesMangling.def new file mode 100644 index 0000000..f44b5fe --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapStandardTypesMangling.def @@ -0,0 +1,68 @@ +//===--- AMapStandardTypesMangling - Mangling Metaprogramming ---*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// STANDARD_TYPE(KIND, MANGLING, TYPENAME) +/// The 1-character MANGLING for a known TYPENAME of KIND. + +STANDARD_TYPE(Structure, A, AutoreleasingUnsafeMutablePointer) +STANDARD_TYPE(Structure, a, Array) +STANDARD_TYPE(Structure, b, Bool) +STANDARD_TYPE(Structure, c, UnicodeScalar) +STANDARD_TYPE(Structure, D, Dictionary) +STANDARD_TYPE(Structure, d, Double) +STANDARD_TYPE(Structure, f, Float) +STANDARD_TYPE(Structure, h, Set) +STANDARD_TYPE(Structure, I, DefaultIndices) +STANDARD_TYPE(Structure, i, Int) +STANDARD_TYPE(Structure, J, Character) +STANDARD_TYPE(Structure, N, ClosedRange) +STANDARD_TYPE(Structure, n, Range) +STANDARD_TYPE(Structure, O, ObjectIdentifier) +STANDARD_TYPE(Structure, P, UnsafePointer) +STANDARD_TYPE(Structure, p, UnsafeMutablePointer) +STANDARD_TYPE(Structure, R, UnsafeBufferPointer) +STANDARD_TYPE(Structure, r, UnsafeMutableBufferPointer) +STANDARD_TYPE(Structure, S, String) +STANDARD_TYPE(Structure, s, Substring) +STANDARD_TYPE(Structure, u, UInt) +STANDARD_TYPE(Structure, V, UnsafeRawPointer) +STANDARD_TYPE(Structure, v, UnsafeMutableRawPointer) +STANDARD_TYPE(Structure, W, UnsafeRawBufferPointer) +STANDARD_TYPE(Structure, w, UnsafeMutableRawBufferPointer) + +STANDARD_TYPE(Enum, q, Optional) + +STANDARD_TYPE(Protocol, B, BinaryFloatingPoint) +STANDARD_TYPE(Protocol, E, Encodable) +STANDARD_TYPE(Protocol, e, Decodable) +STANDARD_TYPE(Protocol, F, FloatingPoint) +STANDARD_TYPE(Protocol, G, RandomNumberGenerator) +STANDARD_TYPE(Protocol, H, Hashable) +STANDARD_TYPE(Protocol, j, Numeric) +STANDARD_TYPE(Protocol, K, BidirectionalCollection) +STANDARD_TYPE(Protocol, k, RandomAccessCollection) +STANDARD_TYPE(Protocol, L, Comparable) +STANDARD_TYPE(Protocol, l, Collection) +STANDARD_TYPE(Protocol, M, MutableCollection) +STANDARD_TYPE(Protocol, m, RangeReplaceableCollection) +STANDARD_TYPE(Protocol, Q, Equatable) +STANDARD_TYPE(Protocol, T, Sequence) +STANDARD_TYPE(Protocol, t, IteratorProtocol) +STANDARD_TYPE(Protocol, U, UnsignedInteger) +STANDARD_TYPE(Protocol, X, RangeExpression) +STANDARD_TYPE(Protocol, x, Strideable) +STANDARD_TYPE(Protocol, Y, RawRepresentable) +STANDARD_TYPE(Protocol, y, StringProtocol) +STANDARD_TYPE(Protocol, Z, SignedInteger) +STANDARD_TYPE(Protocol, z, BinaryInteger) + +#undef STANDARD_TYPE diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/AMapValueWitnessMangling.def b/Pods/AMapFoundation/AMapFoundationKit.framework/AMapValueWitnessMangling.def new file mode 100644 index 0000000..e69de29 diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationConst.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationConst.h new file mode 100644 index 0000000..7b1b62f --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationConst.h @@ -0,0 +1,74 @@ +// +// AMapFoundationConst.h +// AMapFoundationKit +// +// Created by JL on 2019/7/22. +// Copyright © 2019 Amap.com. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NSInteger AMapFoundationNSErrorCode; + +//ErrorDomain:文件不存在 错误码:-555555 +extern NSErrorDomain const AMapFoundationNSErrorFileDonotExist; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorFileDonotExistCode; + +//ErrorDomain:文件路径不合法 错误码:-555556 +extern NSErrorDomain const AMapFoundationNSErrorFilePathInvaild; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorFilePathInvaildCode; + +//ErrorDomain:指定类型的日志文件不存在 错误码:-555557 +extern NSErrorDomain const AMapFoundationNSErrorTypeLogDonotExist; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorTypeLogDonotExistCode; + +//ErrorDomain:待上传的数据为空(可能是组装/压缩时出错) 错误码:-555558 +extern NSErrorDomain const AMapFoundationNSErrorUploadDataIsEmpty; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorUploadDataIsEmptyCode; + +//ErrorDomain:参数错误 错误码:-444444 +extern NSErrorDomain const AMapFoundationNSErrorParametersInvalid; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorParametersInvalidCode; + + +extern NSErrorDomain const AMapFoundationNSErrorCloudConfigDisable; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorCloudConfigDisableCode; + +extern NSErrorDomain const AMapFoundationNSErrorNetworkUnusable; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorNetworkUnusableCode; + +extern NSErrorDomain const AMapFoundationNSErrorCurrentworkIsRunning; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorCurrentworkIsRunningCode; + +extern NSErrorDomain const AMapFoundationNSErrorCurrentUploadSizeHaveExcess; +extern AMapFoundationNSErrorCode const AMapFoundationNSErrorCurrentUploadSizeHaveExcessCode; + + +extern NSErrorDomain const AMapFoundationErrorPrivacyShowUnknow; +extern AMapFoundationNSErrorCode const AMapFoundationErrorPrivacyShowUnknowCode; + +extern NSErrorDomain const AMapFoundationErrorPrivacyShowNoShow; +extern AMapFoundationNSErrorCode const AMapFoundationErrorPrivacyShowNoShowCode; + +extern NSErrorDomain const AMapFoundationErrorPrivacyInfoUnknow; +extern AMapFoundationNSErrorCode const AMapFoundationErrorPrivacyInfoUnknowCode; + +extern NSErrorDomain const AMapFoundationErrorPrivacyInfoNotContain; +extern AMapFoundationNSErrorCode const AMapFoundationErrorPrivacyInfoNotContainCode; + +extern NSErrorDomain const AMapFoundationErrorPrivacyAgreeUnknow; +extern AMapFoundationNSErrorCode const AMapFoundationErrorPrivacyAgreeUnknowCode; + +extern NSErrorDomain const AMapFoundationErrorPrivacyAgreeNotAgreee; +extern AMapFoundationNSErrorCode const AMapFoundationErrorPrivacyAgreeNotAgreeeCode; + +extern NSErrorDomain const AMapFoundationErrorBikeLicenseDontAuth; +extern AMapFoundationNSErrorCode const AMapFoundationErrorBikeLicenseDontAuthCode; + + +extern NSErrorDomain const AMapFoundationErrorInvaildUserKey; +extern AMapFoundationNSErrorCode const AMapFoundationErrorInvaildUserKeyCode; + +NS_ASSUME_NONNULL_END diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationKit.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationKit.h new file mode 100644 index 0000000..431b7bd --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationKit.h @@ -0,0 +1,20 @@ +// +// AMapFoundationKit.h +// AMapFoundationKit +// +// Created by xiaoming han on 15/10/28. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import +#import +#import +#import +#import +#import + +#import + +#if __has_include() +#import +#endif diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationVersion.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationVersion.h new file mode 100644 index 0000000..b6798bd --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapFoundationVersion.h @@ -0,0 +1,19 @@ +// +// AMapFoundationVersion.h +// AMapFoundation +// +// Created by xiaoming han on 15/10/26. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import + +#ifndef AMapFoundationVersion_h +#define AMapFoundationVersion_h + +#define AMapFoundationVersionNumber 10807 + +FOUNDATION_EXTERN NSString * const AMapFoundationVersion; +FOUNDATION_EXTERN NSString * const AMapFoundationName; + +#endif /* AMapFoundationVersion_h */ diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapServices.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapServices.h new file mode 100644 index 0000000..6f8a8cb --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapServices.h @@ -0,0 +1,160 @@ +// +// AMapSearchServices.h +// AMapSearchKit +// +// Created by xiaoming han on 15/6/18. +// Copyright (c) 2015年 xiaoming han. All rights reserved. +// + +#import + + +//语言类型 language type +typedef NS_ENUM(int, AMapRegionLanguageType) +{ + /// 简体中文 (Simplified Chinese) + AMapRegionLanguageTypeZhHans = 0, + + /// 香港繁体中文 (Traditional Chinese - Hong Kong) + AMapRegionLanguageTypeZhHantHk, + + /// 英文 (English) + AMapRegionLanguageTypeEn, + + /// 西班牙语 (Spanish) + AMapRegionLanguageTypeEs, + + /// 葡萄牙语 (Portuguese) + AMapRegionLanguageTypePt, + + /// 法语 (French) + AMapRegionLanguageTypeFr, + + /// 德语 (German) + AMapRegionLanguageTypeDe, + + /// 泰语 (Thai) + AMapRegionLanguageTypeTh, + + /// 日语 (Japanese) + AMapRegionLanguageTypeJa, + + /// 韩语 (Korean) + AMapRegionLanguageTypeKo, + + /// 阿拉伯语 (Arabic) + AMapRegionLanguageTypeAr, + + /// 土耳其语 (Turkish) + AMapRegionLanguageTypeTr, + + /// 希伯来语 (Hebrew) + AMapRegionLanguageTypeHe, + + /// 意大利语 (Italian) + AMapRegionLanguageTypeIt, + + /// 俄语 (Russian) + AMapRegionLanguageTypeRu, + + /// 马来语 (Malay) + AMapRegionLanguageTypeMs, + + /// 印尼语 (Indonesian) + AMapRegionLanguageTypeId, + + /// 越南语 (Vietnamese) + AMapRegionLanguageTypeVi, + + /// 波兰语 (Polish) + AMapRegionLanguageTypePl, + + /// 捷克语 (Czech) + AMapRegionLanguageTypeCs, + + /// 乌克兰语 (Ukrainian) + AMapRegionLanguageTypeUk, + + /// 阿塞拜疆语 (Azerbaijani) + AMapRegionLanguageTypeAz, + + /// 最大值标识 (Max Value Marker) + AMapRegionLanguageTypeMax +}; + +/** + * 是否为海外用户...海外用户,SDK内部会屏蔽一些操作 默认为NO. + * @warning AMapServices初始化之前,设置才能生效 + */ +extern BOOL _amapLocationOverseas; + +// 显示隐私弹窗状态 -1: unknow , 0 : 未显示 , 1 : 已显示 +typedef NS_ENUM(NSInteger, AMapPrivacyShowStatus) +{ + AMapPrivacyShowStatusUnknow = -1, + AMapPrivacyShowStatusNotShow = 0, + AMapPrivacyShowStatusDidShow = 1, +}; + +// 集成SDK隐私信息状态 -1: unknow , 0 : 未集成 , 1 : 已集成 +typedef NS_ENUM(NSInteger, AMapPrivacyInfoStatus) +{ + AMapPrivacyInfoStatusUnknow = -1, + AMapPrivacyInfoStatusNotContain = 0, + AMapPrivacyInfoStatusDidContain = 1, +}; + +// 用户同意隐私状态 -1: unknow , 0 : 未同意 , 1 : 已同意 +typedef NS_ENUM(NSInteger, AMapPrivacyAgreeStatus) +{ + AMapPrivacyAgreeStatusUnknow = -1, + AMapPrivacyAgreeStatusNotAgree = 0, + AMapPrivacyAgreeStatusDidAgree = 1, +}; + +///高德SDK服务类 +@interface AMapServices : NSObject + +/** + * @brief 获取单例 + */ ++ (AMapServices *)sharedServices; + + +///APIkey。设置key,需要在高德官网控制台绑定对应的bundleid。 +@property (nonatomic, copy) NSString *apiKey; + +///是否开启HTTPS,从1.3.3版本开始默认为YES。 +@property (nonatomic, assign) BOOL enableHTTPS; + +///域名是否是海外环境, YES 代表海外, 默认是NO。 +///需要在初始化SDK前设置。 +- (void)setIsOverseas:(BOOL)isOverseas; + +///语言类型 +///language type +@property (nonatomic, assign) AMapRegionLanguageType regionLanguageType; + +///是否启用崩溃日志上传。默认为YES, 只有在真机上设置有效。\n开启崩溃日志上传有助于我们更好的了解SDK的状况,可以帮助我们持续优化和改进SDK。需要注意的是,SDK内部是通过设置NSUncaughtExceptionHandler来捕获异常的,如果您的APP中使用了其他收集崩溃日志的SDK,或者自己有设置NSUncaughtExceptionHandler的话,请保证 AMapServices 的初始化是在其他设置NSUncaughtExceptionHandler操作之后进行的,我们的handler会再处理完异常后调用前一次设置的handler,保证之前设置的handler会被执行。 +@property (nonatomic, assign) BOOL crashReportEnabled __attribute__((deprecated("从v1.5.7开始废弃,调用无任何作用"))); + +///设备标识,取自idfv。用于排查问题时提供。 +@property (nonatomic, readonly) NSString *identifier; + +///用户是否同意数据用于安全保障。默认为YES。since 1.8.7 +///Whether the user agrees to use data for security assurance. Default: YES. Since 1.8.7 +@property (nonatomic, assign) BOOL securityAgree; + +///用户是否同意数据用于统计分析。默认为YES。since 1.8.7 +///Whether the user agrees to use data for statistical analysis. Default: YES. Since 1.8.7 +@property (nonatomic, assign) BOOL analysisAgree; + +///当前位置经度。since 1.8.7 +///Current location longitude. Since 1.8.7 +@property (nonatomic, assign) double longitude; + +///当前位置纬度。since 1.8.7 +///Current location latitude. Since 1.8.7 +@property (nonatomic, assign) double latitude; + +@end diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearch.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearch.h new file mode 100644 index 0000000..9379f4f --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearch.h @@ -0,0 +1,41 @@ +// +// AMapURLSearch.h +// AMapFoundation +// +// Created by xiaoming han on 15/10/28. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import +#import "AMapURLSearchConfig.h" + +///调起高德地图URL进行搜索,若是调起失败,可使用`+ (void)getLatestAMapApp;`方法获取最新版高德地图app. +@interface AMapURLSearch : NSObject + +/** + * @brief 打开高德地图AppStore页面 + */ ++ (void)getLatestAMapApp; + +/** + * @brief 调起高德地图app驾车导航. + * @param config 配置参数. + * @return 是否成功.若为YES则成功调起,若为NO则无法调起. + */ ++ (BOOL)openAMapNavigation:(AMapNaviConfig *)config; + +/** + * @brief 调起高德地图app进行路径规划. + * @param config 配置参数. + * @return 是否成功. + */ ++ (BOOL)openAMapRouteSearch:(AMapRouteConfig *)config; + +/** + * @brief 调起高德地图app进行POI搜索. + * @param config 配置参数. + * @return 是否成功. + */ ++ (BOOL)openAMapPOISearch:(AMapPOIConfig *)config; + +@end diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearchConfig.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearchConfig.h new file mode 100644 index 0000000..666b492 --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearchConfig.h @@ -0,0 +1,79 @@ +// +// MAMapURLSearchConfig.h +// MAMapKitNew +// +// Created by xiaoming han on 15/5/25. +// Copyright (c) 2015年 xiaoming han. All rights reserved. +// + +#import +#import +#import "AMapURLSearchType.h" + +///导航配置信息 +@interface AMapNaviConfig : NSObject + +///应用返回的Scheme +@property (nonatomic, copy) NSString *appScheme; + +///应用名称 +@property (nonatomic, copy) NSString *appName; + +///终点 +@property (nonatomic, assign) CLLocationCoordinate2D destination; + +///导航策略 +@property (nonatomic, assign) AMapDrivingStrategy strategy; + +@end + +#pragma mark - + +///路径搜索配置信息 +@interface AMapRouteConfig : NSObject + +///应用返回的Scheme +@property (nonatomic, copy) NSString *appScheme; + +///应用名称 +@property (nonatomic, copy) NSString *appName; + +///起点坐标 +@property (nonatomic, assign) CLLocationCoordinate2D startCoordinate; + +///终点坐标 +@property (nonatomic, assign) CLLocationCoordinate2D destinationCoordinate; + +///驾车策略 +@property (nonatomic, assign) AMapDrivingStrategy drivingStrategy; + +///公交策略 +@property (nonatomic, assign) AMapTransitStrategy transitStrategy; + +///路径规划类型 +@property (nonatomic, assign) AMapRouteSearchType routeType; + +@end + +#pragma mark - + +///POI搜索配置信息 +@interface AMapPOIConfig : NSObject + +///应用返回的Scheme +@property (nonatomic, copy) NSString *appScheme; + +///应用名称 +@property (nonatomic, copy) NSString *appName; + +///搜索关键字 +@property (nonatomic, copy) NSString *keywords; + +///左上角坐标 +@property (nonatomic, assign) CLLocationCoordinate2D leftTopCoordinate; + +///右下角坐标 +@property (nonatomic, assign) CLLocationCoordinate2D rightBottomCoordinate; + +@end + diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearchType.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearchType.h new file mode 100644 index 0000000..424d905 --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapURLSearchType.h @@ -0,0 +1,44 @@ +// +// MAMapURLSearchType.h +// MAMapKitNew +// +// Created by xiaoming han on 15/5/25. +// Copyright (c) 2015年 xiaoming han. All rights reserved. +// + +///驾车策略 +typedef NS_ENUM(NSInteger, AMapDrivingStrategy) +{ + AMapDrivingStrategyFastest = 0, ///<速度最快 + AMapDrivingStrategyMinFare = 1, ///<避免收费 + AMapDrivingStrategyShortest = 2, ///<距离最短 + + AMapDrivingStrategyNoHighways = 3, ///<不走高速 + AMapDrivingStrategyAvoidCongestion = 4, ///<躲避拥堵 + + AMapDrivingStrategyAvoidHighwaysAndFare = 5, ///<不走高速且避免收费 + AMapDrivingStrategyAvoidHighwaysAndCongestion = 6, ///<不走高速且躲避拥堵 + AMapDrivingStrategyAvoidFareAndCongestion = 7, ///<躲避收费和拥堵 + AMapDrivingStrategyAvoidHighwaysAndFareAndCongestion = 8 ///<不走高速躲避收费和拥堵 +}; + +///公交策略 +typedef NS_ENUM(NSInteger, AMapTransitStrategy) +{ + AMapTransitStrategyFastest = 0,///<最快捷 + AMapTransitStrategyMinFare = 1,///<最经济 + AMapTransitStrategyMinTransfer = 2,///<最少换乘 + AMapTransitStrategyMinWalk = 3,///<最少步行 + AMapTransitStrategyMostComfortable = 4,///<最舒适 + AMapTransitStrategyAvoidSubway = 5,///<不乘地铁 +}; + +///路径规划类型 +typedef NS_ENUM(NSInteger, AMapRouteSearchType) +{ + AMapRouteSearchTypeDriving = 0, ///<驾车 + AMapRouteSearchTypeTransit = 1, ///<公交 + AMapRouteSearchTypeWalking = 2, ///<步行 +}; + + diff --git a/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapUtility.h b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapUtility.h new file mode 100644 index 0000000..6cb2692 --- /dev/null +++ b/Pods/AMapFoundation/AMapFoundationKit.framework/Headers/AMapUtility.h @@ -0,0 +1,50 @@ +// +// AMapUtility.h +// AMapFoundation +// +// Created by xiaoming han on 15/10/27. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import +#import + +//工具方法 + +/** + * @brief 如果字符串为nil则返回空字符串 + */ +FOUNDATION_STATIC_INLINE NSString * AMapEmptyStringIfNil(NSString *s) +{ + return s ? s : @""; +} + +///坐标类型枚举 +typedef NS_ENUM(NSInteger, AMapCoordinateType) +{ + AMapCoordinateTypeAMap = -1, /// +#import "AMapGeoFenceRegionObj.h" + +// 以下类涉及的坐标需要使用高德坐标系坐标(GCJ02) + +@protocol AMapGeoFenceManagerDelegate; + +///地理围栏监听状态类型 +typedef NS_OPTIONS(NSUInteger, AMapGeoFenceActiveAction) +{ + AMapGeoFenceActiveActionNone = 0, ///< 不进行监听 + AMapGeoFenceActiveActionInside = 1 << 0, ///< 在范围内 + AMapGeoFenceActiveActionOutside = 1 << 1, ///< 在范围外 + AMapGeoFenceActiveActionStayed = 1 << 2, ///< 停留(在范围内超过10分钟) +}; + +///地理围栏任务状态类型 +typedef NS_OPTIONS(NSUInteger, AMapGeoFenceRegionActiveStatus) +{ + AMapGeoFenceRegionActiveUNMonitor = 0, ///< 未注册 + AMapGeoFenceRegionActiveMonitoring = 1 << 0, ///< 正在监控 + AMapGeoFenceRegionActivePaused = 1 << 1, ///< 暂停监控 +}; + +///地理围栏管理类(since 2.3.0) +@interface AMapGeoFenceManager : NSObject + + +///实现了 AMapGeoFenceManagerDelegate 协议的类指针。 +@property (nonatomic, weak) id delegate; + + +///需要进行通知的行为,默认为AMapGeoFenceActiveActionInside。 +@property (nonatomic, assign) AMapGeoFenceActiveAction activeAction; + + +///指定定位是否会被系统自动暂停。默认为NO。 +@property (nonatomic, assign) BOOL pausesLocationUpdatesAutomatically; + + +///是否允许后台定位。默认为NO。只在iOS 9.0及之后起作用。设置为YES的时候必须保证 Background Modes 中的 Location updates 处于选中状态,否则会抛出异常。 +@property (nonatomic, assign) BOOL allowsBackgroundLocationUpdates; + +///检测是否存在虚拟定位风险,默认为NO,即不检测。 \n如果设置为YES,检测到风险后,会通过amapGeoFenceManager:didGeoFencesStatusChangedForRegion:customID:error: 的error给出风险提示,error的格式为error.domain==AMapGeoFenceErrorDomain; error.code==AMapGeoFenceErroFailureLocating; +@property (nonatomic, assign) BOOL detectRiskOfFakeLocation; + + +/** + * @brief 添加一个圆形围栏 + * @param center 围栏的中心点经纬度坐标 + * @param radius 围栏的半径,单位:米,要求大于0 + * @param customID 用户自定义ID,可选,SDK原值返回 + */ +- (void)addCircleRegionForMonitoringWithCenter:(CLLocationCoordinate2D)center radius:(CLLocationDistance)radius customID:(NSString *)customID; + + +/** + * @brief 根据经纬度坐标数据添加一个闭合的多边形围栏,点与点之间按顺序尾部相连, 第一个点与最后一个点相连 + * @param coordinates 经纬度坐标点数据,coordinates对应的内存会拷贝,调用者负责该内存的释放 + * @param count 经纬度坐标点的个数,不可小于3个 + * @param customID 用户自定义ID,可选,SDK原值返回 + */ +- (void)addPolygonRegionForMonitoringWithCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSInteger)count customID:(NSString *)customID; + + +/** + * @brief 根据要查询的关键字,类型,城市等信息,添加一个或者多个POI地理围栏 + * @param keyword 要查询的关键字,多个关键字用“|”分割,必填,keyword和type两者至少必选其一 + * @param type 要查询的POI类型,多个类型用“|”分割,必填,keyword和type两者至少必选其一,具体分类编码和规则详见: http://lbs.amap.com/api/webservice/guide/api/search/#text + * @param city 要查询的城市 + * @param size 要查询的数据的条数,(0,25],传入<=0的值为10,传入大于25的值为25,默认10 + * @param customID 用户自定义ID,可选,SDK原值返回 + */ +- (void)addKeywordPOIRegionForMonitoringWithKeyword:(NSString *)keyword POIType:(NSString *)type city:(NSString *)city size:(NSInteger)size customID:(NSString *)customID; + + +/** + * @brief 根据要查询的点的经纬度,搜索半径等信息,添加一个或者多个POI围栏 + * @param locationPoint 点的经纬度坐标,必填 + * @param aroundRadius 查询半径,单位:米,(0,50000],超出范围取3000,默认3000 + * @param keyword 要查询的关键字,多个关键字用“|”分割,可选 + * @param type 要查询的POI类型,多个类型用“|”分割,可选 + * @param size 要查询的数据的条数,(0,25],传入<=0的值为10,传入大于25的值为25,默认10 + * @param customID 用户自定义ID,可选,SDK原值返回 + */ +- (void)addAroundPOIRegionForMonitoringWithLocationPoint:(CLLocationCoordinate2D)locationPoint aroundRadius:(NSInteger)aroundRadius keyword:(NSString *)keyword POIType:(NSString *)type size:(NSInteger)size customID:(NSString *)customID; + + +/** + * @brief 根据要查询的行政区域关键字,添加一个或者多个行政区域围栏 + * @param districtName 行政区域关键字,必填,只支持单个关键词语:行政区名称、citycode、adcode,规则详见: http://lbs.amap.com/api/webservice/guide/api/district/#district + * @param customID 用户自定义ID,可选,SDK原值返回 + */ +- (void)addDistrictRegionForMonitoringWithDistrictName:(NSString *)districtName customID:(NSString *)customID; + +/** + * @brief 获取指定围栏的运行状态 + * @param region 要获取运行状态的围栏 + * @return 返回指定围栏的运行状态 + */ +- (AMapGeoFenceRegionActiveStatus)statusWithGeoFenceRegion:(AMapGeoFenceRegion *)region; + +/** + * @brief 根据customID获得所有已经注册的围栏,如果customID传nil,则返回全部已注册围栏 + * @param customID 用户执行添加围栏函数时传入的customID + * @return 获得的围栏构成的数组,如果没有结果,返回nil + */ +- (NSArray *)geoFenceRegionsWithCustomID:(NSString *)customID; + +/** + * @brief 根据customID获得所有正在监控的围栏,如果customID传nil,则返回全部正在监控的围栏 + * @param customID 用户执行添加围栏函数时传入的customID + * @return 获得的围栏构成的数组,如果没有结果,返回nil + */ +- (NSArray *)monitoringGeoFenceRegionsWithCustomID:(NSString *)customID; + +/** + * @brief 根据customID获得所有已经暂停的围栏,如果customID传nil,则返回全部已经暂停的围栏 + * @param customID 用户执行添加围栏函数时传入的customID + * @return 获得的围栏构成的数组,如果没有结果,返回nil + */ +- (NSArray *)pausedGeoFenceRegionsWithCustomID:(NSString *)customID; + + +/** + * @brief 暂停指定customID的围栏 + * @param customID 用户执行添加围栏函数时传入的customID + * @return 返回被暂停围栏的数组,如果没有围栏被暂停,返回nil + */ +- (NSArray *)pauseGeoFenceRegionsWithCustomID:(NSString *)customID; + + +/** + * @brief 暂停指定围栏 + * @param region 要暂停监控的围栏 + * @return 返回指定围栏是否被暂停,如果指定围栏没有注册,则返回NO + */ +- (BOOL)pauseTheGeoFenceRegion:(AMapGeoFenceRegion *)region; + +/** + * @brief 根据customID开始监控已经暂停的围栏 + * @param customID 用户执行添加围栏函数时传入的customID + * @return 返回开始监控的围栏构成的数组 + */ +- (NSArray *)startGeoFenceRegionsWithCustomID:(NSString *)customID; + +/** + * @brief 开始监控指定围栏 + * @param region 要开始监控的围栏 + * @return 返回指定围栏是否开始监控,如果指定围栏没有注册,则返回NO + */ +- (BOOL)startTheGeoFenceRegion:(AMapGeoFenceRegion *)region; + +/** + * @brief 移除指定围栏 + * @param region 要停止监控的围栏 + */ +- (void)removeTheGeoFenceRegion:(AMapGeoFenceRegion *)region; + +/** + * @brief 移除指定customID的围栏 + * @param customID 用户执行添加围栏函数时传入的customID + */ +- (void)removeGeoFenceRegionsWithCustomID:(NSString *)customID; + +/** + * @brief 移除所有围栏 + */ +- (void)removeAllGeoFenceRegions; + +@end + +///地理围栏代理协议(since 2.3.0),该协议定义了获取地理围栏相关回调方法,包括添加、状态改变等。 +@protocol AMapGeoFenceManagerDelegate + +@required + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + +/** + * @brief iOS14及以上版本使用地理围栏功能,需要在plist中配置NSLocationTemporaryUsageDescriptionDictionary字典描述,且添加自定义Key描述地理围栏的使用场景,此描述会在申请临时精确定位权限的弹窗中展示。该回调触发条件:拥有定位权限,但是没有获得精确定位权限的情况下,会触发该回调。此方法实现调用申请临时精确定位权限API即可: + * [locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:@"PurposeKey" completion:^(NSError *error){ + * if(completion){ + * completion(error); + * } + * }]; (必须调用,不然无法正常获取临时精确定位权限) + * @param manager 地理围栏管理类。 + * @param locationManager 需要申请临时精确定位权限的locationManager。 + * @param completion 临时精确定位权限API回调结果。直接返回系统error即可 + * @since 2.6.7 + */ +- (void)amapLocationManager:(AMapGeoFenceManager *)manager doRequireTemporaryFullAccuracyAuth:(CLLocationManager*)locationManager completion:(void(^)(NSError *error))completion; + +#endif + +@optional + +/** + * @brief 当plist配置NSLocationAlwaysUsageDescription或者NSLocationAlwaysAndWhenInUseUsageDescription,并且[CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined,会调用代理的此方法。 + 此方法实现申请后台权限API即可:[locationManager requestAlwaysAuthorization](必须调用,不然无法正常获取定位权限) + * @param manager 地理围栏管理类。 + * @param locationManager 需要申请后台定位权限的locationManager。 + * @since 2.6.2 + */ +- (void)amapGeoFenceManager:(AMapGeoFenceManager *)manager doRequireLocationAuth:(CLLocationManager*)locationManager; + +/** + * @brief 添加地理围栏完成后的回调,成功与失败都会调用 + * @param manager 地理围栏管理类 + * @param regions 成功添加的一个或多个地理围栏构成的数组 + * @param customID 用户执行添加围栏函数时传入的customID + * @param error 添加失败的错误信息 + */ +- (void)amapGeoFenceManager:(AMapGeoFenceManager *)manager didAddRegionForMonitoringFinished:(NSArray *)regions customID:(NSString *)customID error:(NSError *)error; + + +/** + * @brief 地理围栏状态改变时回调,当围栏状态的值发生改变,定位失败都会调用 + * @param manager 地理围栏管理类 + * @param region 状态改变的地理围栏 + * @param customID 用户执行添加围栏函数时传入的customID + * @param error 错误信息,如定位相关的错误 + */ +- (void)amapGeoFenceManager:(AMapGeoFenceManager *)manager didGeoFencesStatusChangedForRegion:(AMapGeoFenceRegion *)region customID:(NSString *)customID error:(NSError *)error; + +@end diff --git a/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapGeoFenceRegionObj.h b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapGeoFenceRegionObj.h new file mode 100644 index 0000000..833fa9c --- /dev/null +++ b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapGeoFenceRegionObj.h @@ -0,0 +1,120 @@ +// +// AMapGeoFenceRegionObj.h +// AMapLocationKit +// +// Created by hanxiaoming on 16/12/5. +// Copyright © 2016年 Amap. All rights reserved. +// + +#import "AMapLocationCommonObj.h" + + +///AMapGeoFence Region State +typedef NS_ENUM(NSInteger, AMapGeoFenceRegionStatus) +{ + AMapGeoFenceRegionStatusUnknown = 0, ///< 未知 + AMapGeoFenceRegionStatusInside = 1, ///< 在范围内 + AMapGeoFenceRegionStatusOutside = 2, ///< 在范围外 + AMapGeoFenceRegionStatusStayed = 3, ///< 停留(在范围内超过10分钟) +}; + +typedef NS_ENUM(NSInteger, AMapGeoFenceRegionType) +{ + AMapGeoFenceRegionTypeCircle = 0, /// 圆形地理围栏 + AMapGeoFenceRegionTypePolygon = 1, /// 多边形地理围栏 + AMapGeoFenceRegionTypePOI = 2, /// 兴趣点(POI)地理围栏 + AMapGeoFenceRegionTypeDistrict = 3, /// 行政区划地理围栏 +}; + +#pragma mark - AMapGeoFenceRegion + + +///地理围栏基类,不可直接使用。(since 2.3.0) +@interface AMapGeoFenceRegion : NSObject + + +///AMapGeoFenceRegion的唯一标识符 +@property (nonatomic, copy, readonly) NSString *identifier; + + +///用户自定义ID,可为nil。 +@property (nonatomic, copy, readonly) NSString *customID; + + +///坐标点和围栏的关系,比如用户的位置和围栏的关系 +@property (nonatomic, assign) AMapGeoFenceRegionStatus fenceStatus; + +///用户自定义ID,可为nil。 +@property (nonatomic, assign) AMapGeoFenceRegionType regionType; + +///缓存最近获取的定位信息,可能会存在延时,可为nil,会在获取定位时更新 +@property (nonatomic, copy) CLLocation *currentLocation; + +@end + + +#pragma mark - AMapLocationCircleRegion + + +///圆形地理围栏(since 2.3.0) +@interface AMapGeoFenceCircleRegion : AMapGeoFenceRegion + + +///中心点的经纬度坐标 +@property (nonatomic, readonly) CLLocationCoordinate2D center; + + +///半径,单位:米 +@property (nonatomic, readonly) CLLocationDistance radius; + +@end + + +#pragma mark -AMapGeoFencePolygonRegion + + +///多边形地理围栏(since 2.3.0) +@interface AMapGeoFencePolygonRegion : AMapGeoFenceRegion + + +///经纬度坐标点数据 +@property (nonatomic, readonly) CLLocationCoordinate2D *coordinates; + + +///经纬度坐标点的个数 +@property (nonatomic, readonly) NSInteger count; + + +@end + + +#pragma mark -AMapGeoFencePOIRegion + + +///兴趣点(POI)地理围栏(since 2.3.0) +@interface AMapGeoFencePOIRegion : AMapGeoFenceCircleRegion + + +///POI信息 +@property (nonatomic, strong, readonly) AMapLocationPOIItem *POIItem; + + +@end + + +#pragma mark -AMapGeoFenceDistrictRegion + + +///行政区划地理围栏(since 2.3.0) +@interface AMapGeoFenceDistrictRegion : AMapGeoFenceRegion + + +///行政区域信息 +@property (nonatomic, strong, readonly) AMapLocationDistrictItem *districtItem; + + +///行政区域轮廓坐标点,每个行政区可能有多个模块,每个模块的坐标点数组由AMapLocationPoint构成 +@property (nonatomic, copy, readonly) NSArray *> *polylinePoints; + + +@end diff --git a/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationCommonObj.h b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationCommonObj.h new file mode 100644 index 0000000..850b2a2 --- /dev/null +++ b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationCommonObj.h @@ -0,0 +1,195 @@ +// +// AMapLocationCommonObj.h +// AMapLocationKit +// +// Created by AutoNavi on 15/10/22. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import +#import +#import + + +///AMapLocation errorDomain +extern NSString * const AMapLocationErrorDomain; + +///AMapLocation errorCode +typedef NS_ENUM(NSInteger, AMapLocationErrorCode) +{ + AMapLocationErrorUnknown = 1, ///<未知错误 + AMapLocationErrorLocateFailed = 2, ///<定位错误 + AMapLocationErrorReGeocodeFailed = 3, ///<逆地理错误 + AMapLocationErrorTimeOut = 4, ///<超时 + AMapLocationErrorCanceled = 5, ///<取消 + AMapLocationErrorCannotFindHost = 6, ///<找不到主机 + AMapLocationErrorBadURL = 7, /// + +///格式化地址 +@property (nonatomic, copy) NSString *formattedAddress; + +///国家 +@property (nonatomic, copy) NSString *country; + +///省/直辖市 +@property (nonatomic, copy) NSString *province; + +///市 +@property (nonatomic, copy) NSString *city; + +///区 +@property (nonatomic, copy) NSString *district; + +///乡镇 +@property (nonatomic, copy) NSString *township __attribute__((deprecated("该字段从v2.2.0版本起不再返回数据,建议您使用AMapSearchKit的逆地理功能获取."))); + +///社区 +@property (nonatomic, copy) NSString *neighborhood __attribute__((deprecated("该字段从v2.2.0版本起不再返回数据,建议您使用AMapSearchKit的逆地理功能获取."))); + +///建筑 +@property (nonatomic, copy) NSString *building __attribute__((deprecated("该字段从v2.2.0版本起不再返回数据,建议您使用AMapSearchKit的逆地理功能获取."))); + +///城市编码 +@property (nonatomic, copy) NSString *citycode; + +///区域编码 +@property (nonatomic, copy) NSString *adcode; + +///街道名称 +@property (nonatomic, copy) NSString *street; + +///门牌号 +@property (nonatomic, copy) NSString *number; + +///兴趣点名称 +@property (nonatomic, copy) NSString *POIName; + +///所属兴趣点名称 +@property (nonatomic, copy) NSString *AOIName; + +@end + +#pragma mark - AMapLocationPoint + +///经纬度坐标点对象 +@interface AMapLocationPoint : NSObject + +///纬度 +@property (nonatomic, assign) CGFloat latitude; + +///经度 +@property (nonatomic, assign) CGFloat longitude; + +/** + * @brief AMapNaviPoint类对象的初始化函数 + * @param lat 纬度 + * @param lon 经度 + * @return AMapNaviPoint类对象id + */ ++ (AMapLocationPoint *)locationWithLatitude:(CGFloat)lat longitude:(CGFloat)lon; + +@end + +///POI信息 +@interface AMapLocationPOIItem : NSObject + +///id +@property (nonatomic, copy) NSString *pId; + +///名称 +@property (nonatomic, copy) NSString *name; + +///类型 +@property (nonatomic, copy) NSString *type; + +///类型编码 +@property (nonatomic, copy) NSString *typeCode; + +///地址信息 +@property (nonatomic, copy) NSString *address; + +///经纬度 +@property (nonatomic, strong) AMapLocationPoint *location; + +///电话号码 +@property (nonatomic, copy) NSString *tel; + +///省份 +@property (nonatomic, copy) NSString *province; + +///城市 +@property (nonatomic, copy) NSString *city; + +///区 +@property (nonatomic, copy) NSString *district; + +@end + +///行政区域信息 +@interface AMapLocationDistrictItem : NSObject + +///城市编码 +@property (nonatomic, copy) NSString *cityCode; + +///区域编码 +@property (nonatomic, copy) NSString *districtCode; + +///区名 +@property (nonatomic, copy) NSString *district; + +///行政区域轮廓坐标点,每个行政区可能有多个模块,每个模块的坐标点数组由AMapLocationPoint构成 +@property (nonatomic, copy) NSArray *> *polylinePoints; + +@end + +///AMapLocation CoordinateType +typedef NS_ENUM(NSUInteger, AMapLocationCoordinateType) +{ + AMapLocationCoordinateTypeBaidu = 0, /// + +#import +#import +#import + +#import +#import +#import diff --git a/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationManager.h b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationManager.h new file mode 100644 index 0000000..aacee85 --- /dev/null +++ b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationManager.h @@ -0,0 +1,283 @@ +// +// AMapLocationManager.h +// AMapLocationKit +// +// Created by AutoNavi on 15/10/22. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import +#import +#import "AMapLocationCommonObj.h" +#import "AMapLocationRegionObj.h" +#import + +/** + * @brief AMapLocatingCompletionBlock 单次定位返回Block + * @param location 定位信息 + * @param regeocode 逆地理信息 + * @param error 错误信息,参考 AMapLocationErrorCode + */ +typedef void (^AMapLocatingCompletionBlock)(CLLocation *location, AMapLocationReGeocode *regeocode, NSError *error); + +@protocol AMapLocationManagerDelegate; + +#pragma mark - AMapLocationManager + +///AMapLocationManager类。初始化之前请设置 AMapServices 中的apikey(例如:[AMapServices sharedServices].apiKey = @"您的key"),否则将无法正常使用服务. +@interface AMapLocationManager : NSObject + +///实现了 AMapLocationManagerDelegate 协议的类指针。 +@property (nonatomic, weak) id delegate; + +///设定定位的最小更新距离。单位米,默认为 kCLDistanceFilterNone,表示只要检测到设备位置发生变化就会更新位置信息。 +@property(nonatomic, assign) CLLocationDistance distanceFilter; + +///设定期望的定位精度。单位米,默认为 kCLLocationAccuracyBest。定位服务会尽可能去获取满足desiredAccuracy的定位结果,但不保证一定会得到满足期望的结果。 +///注意:设置为kCLLocationAccuracyBest或kCLLocationAccuracyBestForNavigation时,单次定位会在达到locationTimeout设定的时间后,将时间内获取到的最高精度的定位结果返回。 +///⚠️ 当iOS14及以上版本,模糊定位权限下可能拿不到设置精度的经纬度 +@property(nonatomic, assign) CLLocationAccuracy desiredAccuracy; + +///指定定位是否会被系统自动暂停。默认为NO。 +@property(nonatomic, assign) BOOL pausesLocationUpdatesAutomatically; + +///是否允许后台定位。默认为NO。只在iOS 9.0及之后起作用。设置为YES的时候必须保证 Background Modes 中的 Location updates 处于选中状态,否则会抛出异常。由于iOS系统限制,需要在定位未开始之前或定位停止之后,修改该属性的值才会有效果。 +@property(nonatomic, assign) BOOL allowsBackgroundLocationUpdates; + +///指定单次定位超时时间,默认为10s。最小值是2s。注意单次定位请求前设置。注意: 单次定位超时时间从确定了定位权限(非kCLAuthorizationStatusNotDetermined状态)后开始计算。 +@property(nonatomic, assign) NSInteger locationTimeout; + +///指定单次定位逆地理超时时间,默认为5s。最小值是2s。注意单次定位请求前设置。 +@property(nonatomic, assign) NSInteger reGeocodeTimeout; + +///连续定位是否返回逆地理信息,默认NO。 +@property (nonatomic, assign) BOOL locatingWithReGeocode; + +///逆地址语言类型,默认是AMapRegionLanguageTypeZhHans +@property (nonatomic, assign) AMapRegionLanguageType reGeocodeLanguage; + +///获取被监控的region集合。 +@property (nonatomic, readonly, copy) NSSet *monitoredRegions; + +///检测是否存在虚拟定位风险,默认为NO,不检测。 \n注意:设置为YES时,单次定位通过 AMapLocatingCompletionBlock 的error给出虚拟定位风险提示;连续定位通过 amapLocationManager:didFailWithError: 方法的error给出虚拟定位风险提示。error格式为 error.domain==AMapLocationErrorDomain; error.code==AMapLocationErrorRiskOfFakeLocation; \n附带的error的详细信息参考 error.localizedDescription 中的描述以及 error.userInfo 中的信息(error.userInfo.AMapLocationRiskyLocateResult 表示有虚拟风险的定位结果; error.userInfo.AMapLocationAccessoryInfo 表示外接辅助设备信息)。 +@property (nonatomic, assign) BOOL detectRiskOfFakeLocation; + + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + +/** + * @brief 设置定位数据回调精度模式,默认为AMapLocationAccuracyFullAndReduce。\n + 注意:如果定位时未获得定位权限,则首先会调用申请定位权限API,实际定位精度权限取决于用户的权限设置。\n + * ————————————————————————————————————————————————————————————————\n + * | 设置选项 | doRequireTemporaryFullAccuracyAuth | 异常/定位数据回调 |\n + * | AMapLocationFullAndReduceAccuracy | 会触发申请临时精确定位回调 | 如果未获得精确定位权限,则依然开启定位,回调模糊定位 |\n + * | AMapLocationFullAccuracy | 会触发申请临时精确定位回调 | 如果未获得精确定位权限,则不开启定位,回调error |\n + * | AMapLocationReduceAccuracy | 不会触发申请临时精确定位回调 | 根据当前定位精度权限,回调定位数据 |\n + * ————————————————————————————————————————————————————————————————\n + * 当设置AMapLocationFullAndReduceAccuracy时,定位数据回调可通过currentAuthorization判断当前是否是精确定位 + * @since 2.6.7 + */ +@property (nonatomic, assign) AMapLocationAccuracyMode locationAccuracyMode API_AVAILABLE(ios(14.0)); + +/** + * @brief 获取当前定位精度权限。 + * @since 2.6.7 + */ +@property (nonatomic, readonly) CLAccuracyAuthorization currentAuthorization API_AVAILABLE(ios(14.0)); + +#endif + +/** + * @brief 设备是否支持方向识别 + * @return YES:设备支持方向识别 ; NO:设备不支持支持方向识别 + */ ++ (BOOL)headingAvailable; + +/** + * @brief 开始获取设备朝向,如果设备支持方向识别,则会通过代理回调方法 + */ +- (void)startUpdatingHeading; + +/** + * @brief 停止获取设备朝向 + */ +- (void)stopUpdatingHeading; + +/** + * @brief 停止设备朝向校准显示 + */ +- (void)dismissHeadingCalibrationDisplay; + +/** + * @brief 单次定位。如果当前正在连续定位,调用此方法将会失败,返回NO。\n该方法将会根据设定的 desiredAccuracy 去获取定位信息。如果获取的定位信息精确度低于 desiredAccuracy ,将会持续的等待定位信息,直到超时后通过completionBlock返回精度最高的定位信息。\n可以通过 stopUpdatingLocation 方法去取消正在进行的单次定位请求。 + * @param withReGeocode 是否带有逆地理信息(获取逆地理信息需要联网) + * @param completionBlock 单次定位完成后的Block + * @return 是否成功添加单次定位Request + */ +- (BOOL)requestLocationWithReGeocode:(BOOL)withReGeocode completionBlock:(AMapLocatingCompletionBlock)completionBlock; + +/** + * @brief 开始连续定位。调用此方法会cancel掉所有的单次定位请求。 + */ +- (void)startUpdatingLocation; + +/** + * @brief 停止连续定位。调用此方法会cancel掉所有的单次定位请求,可以用来取消单次定位。 + */ +- (void)stopUpdatingLocation; + +/** + * @brief 开始监控指定的region。如果已经存在相同identifier的region,则之前的region将会被移除。对 AMapLocationCircleRegion 类实例,将会优先监控radius小的region。 + * @param region 要被监控的范围 + */ +- (void)startMonitoringForRegion:(AMapLocationRegion *)region __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +/** + * @brief 停止监控指定的region + * @param region 要停止监控的范围 + */ +- (void)stopMonitoringForRegion:(AMapLocationRegion *)region __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +/** + * @brief 查询一个region的当前状态。查询结果通过amapLocationManager:didDetermineState:forRegion:回调返回 + * @param region 要查询的region + */ +- (void)requestStateForRegion:(AMapLocationRegion *)region __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +#pragma mark - Privacy 隐私合规 +/** + * @brief 更新App是否显示隐私弹窗的状态,隐私弹窗是否包含高德SDK隐私协议内容的状态,注意:必须在AMapLocationManager实例化之前调用. since 2.8.0 + * @param showStatus 隐私弹窗状态 + * @param containStatus 包含高德SDK隐私协议状态 + */ ++ (void)updatePrivacyShow:(AMapPrivacyShowStatus)showStatus privacyInfo:(AMapPrivacyInfoStatus)containStatus; +/** +* @brief 更新用户授权高德SDK隐私协议状态,注意:必须在AMapLocationManager实例化之前调用. since 2.8.0 +* @param agreeStatus 用户授权高德SDK隐私协议状态 +*/ ++ (void)updatePrivacyAgree:(AMapPrivacyAgreeStatus)agreeStatus; + +@end + +#pragma mark - AMapLocationManagerDelegate + +///AMapLocationManagerDelegate 协议定义了发生错误时的错误回调方法,连续定位的回调方法等。 +@protocol AMapLocationManagerDelegate + +@optional + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + +/** + * @brief 触发前提条件:1、plist配置NSLocationTemporaryUsageDescriptionDictionary。2、locationAccuracyMode设置为AMapLocationFullAndReduceAccuracy/AMapLocationFullAccuracy。3、当前用户已授权过定位权限,且为模糊定位权限。触发时机:满足前提条件时,再次发起定位请求会调用代理的此方法。此方法实现调用申请临时精确定位权限API即可: + * [manager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:@"PurposeKey" completion:^(NSError *error){ + * if(completion){ + * completion(error); + * } + * }]; (必须调用,不然无法正常获取临时精确定位权限) + * @param manager 定位 AMapLocationManager 类。 + * @param locationManager 需要申请临时精确定位权限的locationManager。 + * @param completion 临时精确定位权限API回调结果,error: 直接返回系统error即可。 + * @since 2.6.7 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager doRequireTemporaryFullAccuracyAuth:(CLLocationManager*)locationManager completion:(void(^)(NSError *error))completion; + +#endif + +/** + * @brief 当plist配置NSLocationAlwaysUsageDescription或者NSLocationAlwaysAndWhenInUseUsageDescription,并且[CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined,会调用代理的此方法。 + 此方法实现调用申请后台权限API即可:[locationManager requestAlwaysAuthorization] (必须调用,不然无法正常获取定位权限) + * @param manager 定位 AMapLocationManager 类。 + * @param locationManager 需要申请后台定位权限的locationManager。 + * @since 2.6.2 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager doRequireLocationAuth:(CLLocationManager*)locationManager; + +/** + * @brief 当定位发生错误时,会调用代理的此方法。 + * @param manager 定位 AMapLocationManager 类。 + * @param error 返回的错误,参考 CLError 。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didFailWithError:(NSError *)error; + +/** + * @brief 连续定位回调函数.注意:本方法已被废弃,如果实现了amapLocationManager:didUpdateLocation:reGeocode:方法,则本方法将不会回调。 + * @param manager 定位 AMapLocationManager 类。 + * @param location 定位结果。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location; + +/** + * @brief 连续定位回调函数.注意:如果实现了本方法,则定位信息不会通过amapLocationManager:didUpdateLocation:方法回调。 + * @param manager 定位 AMapLocationManager 类。 + * @param location 定位结果。 + * @param reGeocode 逆地理信息。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location reGeocode:(AMapLocationReGeocode *)reGeocode; + +/** + * @brief 定位权限状态改变时回调函数。注意:iOS13及之前版本回调 + * @param manager 定位 AMapLocationManager 类。 + * @param status 定位权限状态。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status; + +/** + * @brief 定位权限状态改变时回调函数。注意:iOS14及之后版本回调 + * @param manager 定位 AMapLocationManager 类。 + * @param locationManager 定位CLLocationManager类,可通过locationManager.authorizationStatus获取定位权限,通过locationManager.accuracyAuthorization获取定位精度权限 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager locationManagerDidChangeAuthorization:(CLLocationManager*)locationManager; + +/** + * @brief 是否显示设备朝向校准 + * @param manager 定位 AMapLocationManager 类。 + * @return 是否显示设备朝向校准 + */ +- (BOOL)amapLocationManagerShouldDisplayHeadingCalibration:(AMapLocationManager *)manager; + +/** + * @brief 设备方向改变时回调函数 + * @param manager 定位 AMapLocationManager 类。 + * @param newHeading 设备朝向。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading; + +/** + * @brief 开始监控region回调函数 + * @param manager 定位 AMapLocationManager 类。 + * @param region 开始监控的region。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didStartMonitoringForRegion:(AMapLocationRegion *)region __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +/** + * @brief 进入region回调函数 + * @param manager 定位 AMapLocationManager 类。 + * @param region 进入的region。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didEnterRegion:(AMapLocationRegion *)region __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +/** + * @brief 离开region回调函数 + * @param manager 定位 AMapLocationManager 类。 + * @param region 离开的region。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didExitRegion:(AMapLocationRegion *)region __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +/** + * @brief 查询region状态回调函数 + * @param manager 定位 AMapLocationManager 类。 + * @param state 查询的region的状态。 + * @param region 查询的region。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager didDetermineState:(AMapLocationRegionState)state forRegion:(AMapLocationRegion *)region __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +/** + * @brief 监控region失败回调函数 + * @param manager 定位 AMapLocationManager 类。 + * @param region 失败的region。 + * @param error 错误信息,参考 AMapLocationErrorCode 。 + */ +- (void)amapLocationManager:(AMapLocationManager *)manager monitoringDidFailForRegion:(AMapLocationRegion *)region withError:(NSError *)error __attribute__((deprecated("请使用AMapGeoFenceManager"))); + +@end diff --git a/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationRegionObj.h b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationRegionObj.h new file mode 100644 index 0000000..ed74aad --- /dev/null +++ b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationRegionObj.h @@ -0,0 +1,90 @@ +// +// AMapLocationRegionObj.h +// AMapLocationKit +// +// Created by AutoNavi on 15/11/27. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import "AMapLocationCommonObj.h" + +// 以下类涉及的坐标需要使用高德坐标系坐标(GCJ02) + +#pragma mark - AMapLocationRegion + + +///AMapLocationRegion类,该类提供范围类的基本信息,并无具体实现,不要直接使用。 +@interface AMapLocationRegion : NSObject + +///AMapLocationRegion的identifier +@property (nonatomic, copy, readonly) NSString *identifier; + +///当进入region范围时是否通知,默认YES +@property (nonatomic, assign) BOOL notifyOnEntry; + +///当离开region范围时是否通知,默认YES +@property (nonatomic, assign) BOOL notifyOnExit; + +/** + * @brief 初始化方法 + * @param identifier 唯一标识符,必填,不可为nil + */ +- (instancetype)initWithIdentifier:(NSString *)identifier; + +/** + * @brief 坐标点是否在范围内 + * @param coordinate 要判断的坐标点 + * @return 是否在范围内 + */ +- (BOOL)containsCoordinate:(CLLocationCoordinate2D)coordinate; + +@end + +#pragma mark - AMapLocationCircleRegion + + +///AMapLocationCircleRegion类,定义一个圆形范围。 +@interface AMapLocationCircleRegion : AMapLocationRegion + +///中心点的经纬度坐标 +@property (nonatomic, readonly) CLLocationCoordinate2D center; + +///半径,单位:米 +@property (nonatomic, readonly) CLLocationDistance radius; + +/** + * @brief 根据中心点和半径生成圆形范围 + * @param center 中心点的经纬度坐标 + * @param radius 半径,单位:米 + * @param identifier 唯一标识符,必填,不可为nil + * @return AMapLocationCircleRegion类实例 + */ +- (instancetype)initWithCenter:(CLLocationCoordinate2D)center radius:(CLLocationDistance)radius identifier:(NSString *)identifier; + + +@end + +#pragma mark - AMapLocationPolygonRegion + + +///AMapLocationCircleRegion类,定义一个闭合多边形范围,点与点之间按顺序尾部相连, 第一个点与最后一个点相连。 +@interface AMapLocationPolygonRegion : AMapLocationRegion + +///经纬度坐标点数据 +@property (nonatomic, readonly) CLLocationCoordinate2D *coordinates; + +///经纬度坐标点的个数 +@property (nonatomic, readonly) NSInteger count; + +/** + * @brief 根据经纬度坐标数据生成闭合多边形范围 + * @param coordinates 经纬度坐标点数据,coordinates对应的内存会拷贝,调用者负责该内存的释放 + * @param count 经纬度坐标点的个数,不可小于3个 + * @param identifier 唯一标识符,必填,不可为nil + * @return AMapLocationCircleRegion类实例 + */ +- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSInteger)count identifier:(NSString *)identifier; + + + +@end diff --git a/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationVersion.h b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationVersion.h new file mode 100644 index 0000000..45b1b65 --- /dev/null +++ b/Pods/AMapLocation/AMapLocationKit.framework/Headers/AMapLocationVersion.h @@ -0,0 +1,26 @@ +// +// AMapLoctionVersion.h +// AMapLocationKit +// +// Created by AutoNavi on 16/1/22. +// Copyright © 2016年 Amap. All rights reserved. +// + +#import +#import + +#ifndef AMapLoctionVersion_h +#define AMapLoctionVersion_h + +#define AMapLocationVersionNumber 201101 +#define AMapLocationFoundationVersionMinRequired 10806 + +// 依赖库版本检测 +#if AMapFoundationVersionNumber < AMapLocationFoundationVersionMinRequired +#error "The AMapFoundationKit version is less than minimum required, please update! Any questions please to visit http://lbs.amap.com" +#endif + +FOUNDATION_EXTERN NSString * const AMapLocationVersion; +FOUNDATION_EXTERN NSString * const AMapLocationName; + +#endif /* AMapLoctionVersion_h */ diff --git a/Pods/AMapLocation/AMapLocationKit.framework/Info.plist b/Pods/AMapLocation/AMapLocationKit.framework/Info.plist new file mode 100644 index 0000000..f176d88 Binary files /dev/null and b/Pods/AMapLocation/AMapLocationKit.framework/Info.plist differ diff --git a/Pods/AMapLocation/AMapLocationKit.framework/Modules/module.modulemap b/Pods/AMapLocation/AMapLocationKit.framework/Modules/module.modulemap new file mode 100644 index 0000000..643824a --- /dev/null +++ b/Pods/AMapLocation/AMapLocationKit.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module AMapLocationKit { + umbrella header "AMapLocationKit.h" + export * + + module * { export * } +} diff --git a/Pods/AMapLocation/AMapLocationKit.framework/version.txt b/Pods/AMapLocation/AMapLocationKit.framework/version.txt new file mode 100644 index 0000000..4a44cb7 --- /dev/null +++ b/Pods/AMapLocation/AMapLocationKit.framework/version.txt @@ -0,0 +1 @@ +2.11.1+loc.5da92c7 diff --git a/Pods/AMapSearch/AMapSearchKit.framework/AMapSearchKit b/Pods/AMapSearch/AMapSearchKit.framework/AMapSearchKit new file mode 100644 index 0000000..887c89e Binary files /dev/null and b/Pods/AMapSearch/AMapSearchKit.framework/AMapSearchKit differ diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapCommonObj.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapCommonObj.h new file mode 100644 index 0000000..110a989 --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapCommonObj.h @@ -0,0 +1,931 @@ +// +// AMapCommonObj.h +// AMapSearchKit +// +// Created by xiaoming han on 15/7/22. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +/* 该文件定义了搜索结果的基础数据类型。*/ + +#import +#import + +#pragma mark - AMapSearchObject + +///搜索SDK基础类, 通用数据结构和response支持copy和coding(since 4.4.1)。 +@interface AMapSearchObject : NSObject + +/** + * @brief 返回格式化的描述信息。通用数据结构和response类型有效。 + */ +- (NSString *)formattedDescription; + +@end + +#pragma mark - 通用数据结构 + +///经纬度, description中格式为 <经度,纬度> +@interface AMapGeoPoint : AMapSearchObject +///纬度(垂直方向) +@property (nonatomic, assign) CGFloat latitude; +///经度(水平方向) +@property (nonatomic, assign) CGFloat longitude; + +/** + * @brief 实例化一个AMapGeoPoint对象 + * @param lat 纬度 + * @param lon 经度 + */ ++ (AMapGeoPoint *)locationWithLatitude:(CGFloat)lat longitude:(CGFloat)lon; +@end + +///多边形, 当传入两个点的时候,当做矩形处理:左下-右上两个顶点;其他情况视为多边形,几个点即为几边型。 +@interface AMapGeoPolygon : AMapSearchObject +///坐标集, AMapGeoPoint 数组 +@property (nonatomic, strong) NSArray *points; + +/** + * @brief 实例化一个多边形对象 + * @param points 坐标集, AMapGeoPoint 数组 + */ ++ (AMapGeoPolygon *)polygonWithPoints:(NSArray *)points; +@end + +@class AMapDistrict; +///城市 +@interface AMapCity : AMapSearchObject +///城市名称 +@property (nonatomic, copy) NSString *city; +///城市编码 +@property (nonatomic, copy) NSString *citycode; +///城市区域编码 +@property (nonatomic, copy) NSString *adcode; +///此区域的建议结果数目, AMapSuggestion 中使用 +@property (nonatomic, assign) NSInteger num; +///途径区域 AMapDistrict 数组,AMepStep中使用,只有name和adcode。 +@property (nonatomic, strong) NSArray *districts; +@end + +///建议信息 +@interface AMapSuggestion : AMapSearchObject +///NSString 数组 +@property (nonatomic, strong) NSArray *keywords; +///AMapCity 数组 +@property (nonatomic, strong) NSArray *cities; +@end + +#pragma mark - 输入提示 + +///输入提示 +@interface AMapTip : AMapSearchObject +///poi的id +@property (nonatomic, copy) NSString *uid; +///名称 +@property (nonatomic, copy) NSString *name; +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///所属区域 +@property (nonatomic, copy) NSString *district; +///地址 +@property (nonatomic, copy) NSString *address; +///位置 +@property (nonatomic, copy) AMapGeoPoint *location; +///类型码, since 4.5.0. 对应描述可下载参考官网文档 http://a.amap.com/lbs/static/zip/AMap_API_Table.zip。 +@property (nonatomic, copy) NSString *typecode; +@end + +#pragma mark - POI + +///POI图片信息 +@interface AMapImage : AMapSearchObject +///标题 +@property (nonatomic, copy) NSString *title; +///url +@property (nonatomic, copy) NSString *url; +@end + +///POI扩展信息 +@interface AMapPOIExtension : AMapSearchObject +///评分 +@property (nonatomic, assign) CGFloat rating; +///人均消费 +@property (nonatomic, assign) CGFloat cost; +///营业时间 +@property (nonatomic, copy) NSString *openTime; +@end + +///POI室内地图信息 +@interface AMapIndoorData : AMapSearchObject +/// 是否有室内地图标志 1为有 0为没有 @since 9.4.0 +@property (nonatomic, assign) NSInteger indoorMap; +///楼层索引,一般会用数字表示,例如8。indoorMap为0时不返回 +@property (nonatomic, assign) NSInteger floor; +///所在楼层,一般会带有字母,例如F8。indoorMap为0时不返回 +@property (nonatomic, copy) NSString *floorName; +///如果当前POI为建筑物类POI,则cpid为自身POI ID;如果当前POI为商铺类POI,则cpid为其所在建筑物的POI ID。indoorMap为0时不返回 +@property (nonatomic, copy) NSString *pid; +@end + +///POI商圈信息 @since 9.4.0 +@interface AMapBusinessData : AMapSearchObject +///POI所属商圈 +@property (nonatomic, copy) NSString *businessArea; +///POI今日营业时间,如 08:30-17:30 08:30-09:00 12:00-13:30 09:00-13:00 +@property (nonatomic, copy) NSString *opentimeToday; +///POI营业时间描述,如 周一至周五:08:30-17:30(延时服务时间:08:30-09:00;12:00-13:30);周六延时服务时间:09:00-13:00(法定节假日除外) +@property (nonatomic, copy) NSString *opentimeWeek; +///POI的联系电话 +@property (nonatomic, copy) NSString *tel; +///POI特色内容,目前仅在美食POI下返回 +@property (nonatomic, copy) NSString *tag; +///POI评分,目前仅在餐饮、酒店、景点、影院类POI下返回 +@property (nonatomic, copy) NSString *rating; +///POI人均消费,目前仅在餐饮、酒店、景点、影院类POI下返回 +@property (nonatomic, copy) NSString *cost; +///停车场类型(地下、地面、路边),目前仅在停车场类POI下返回 +@property (nonatomic, copy) NSString *parkingType; +///POI的别名,无别名时不返回 +@property (nonatomic, copy) NSString *alias; +@end + +///子POI +@interface AMapSubPOI : AMapSearchObject +///POI全局唯一ID +@property (nonatomic, copy) NSString *uid; +///名称 +@property (nonatomic, copy) NSString *name; +///名称简写 +@property (nonatomic, copy) NSString *sname; +///经纬度 +@property (nonatomic, copy) AMapGeoPoint *location; +///地址 +@property (nonatomic, copy) NSString *address; +///距中心点距离 +@property (nonatomic, assign) NSInteger distance; +///子POI类型 +@property (nonatomic, copy) NSString *subtype; +///子POI分类编码 @since 9.4.0 +@property (nonatomic, copy) NSString *typeCode; +@end + +///沿途POI +@interface AMapRoutePOI : AMapSearchObject +///POI全局唯一ID +@property (nonatomic, copy) NSString *uid; +///名称 +@property (nonatomic, copy) NSString *name; +///经纬度 +@property (nonatomic, copy) AMapGeoPoint *location; +///用户起点经过途经点再到终点的距离,单位是米 +@property (nonatomic, assign) NSInteger distance; +///用户起点经过途经点再到终点的时间,单位为秒 +@property (nonatomic, assign) NSInteger duration; +@end + +///POI +@interface AMapPOI : AMapSearchObject +///POI全局唯一ID +@property (nonatomic, copy) NSString *uid; +///名称 +@property (nonatomic, copy) NSString *name; +///兴趣点类型 +@property (nonatomic, copy) NSString *type; +///类型编码 +@property (nonatomic, copy) NSString *typecode; +///经纬度 +@property (nonatomic, copy) AMapGeoPoint *location; +///地址 +@property (nonatomic, copy) NSString *address; +///电话 +@property (nonatomic, copy) NSString *tel; +///距中心点的距离,单位米。在周边搜索时有效 +@property (nonatomic, assign) CGFloat distance; +///停车场类型,地上、地下、路边 +@property (nonatomic, copy) NSString *parkingType; +///商铺id +@property (nonatomic, copy) NSString *shopID; + +///邮编 +@property (nonatomic, copy) NSString *postcode; +///网址 +@property (nonatomic, copy) NSString *website; +///电子邮件 +@property (nonatomic, copy) NSString *email; +///省 +@property (nonatomic, copy) NSString *province; +///省编码 +@property (nonatomic, copy) NSString *pcode; +///城市名称 +@property (nonatomic, copy) NSString *city; +///城市编码 +@property (nonatomic, copy) NSString *citycode; +///区域名称 +@property (nonatomic, copy) NSString *district; +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///POI对应的导航引导点坐标 @since 9.4.0 +@property (nonatomic, copy) NSString *naviPOIId; +///地理格ID +@property (nonatomic, copy) NSString *gridcode; +///入口经纬度 +@property (nonatomic, copy) AMapGeoPoint *enterLocation; +///出口经纬度 +@property (nonatomic, copy) AMapGeoPoint *exitLocation; +///方向 +@property (nonatomic, copy) NSString *direction; +///是否有室内地图 +@property (nonatomic, assign) BOOL hasIndoorMap; +///所在商圈 +@property (nonatomic, copy) NSString *businessArea; +///室内信息 +@property (nonatomic, strong) AMapIndoorData *indoorData; +///子POI列表 +@property (nonatomic, strong) NSArray *subPOIs; +///图片列表 +@property (nonatomic, strong) NSArray *images; +///所在商圈 @since 9.4.0 +@property (nonatomic, strong) AMapBusinessData *businessData; +///扩展信息只有在ID查询时有效 +@property (nonatomic, strong) AMapPOIExtension *extensionInfo; +@end + +#pragma mark - 逆地理编码 && 地理编码 + +///兴趣区域 +@interface AMapAOI : AMapSearchObject +///AOI全局唯一ID +@property (nonatomic, copy) NSString *uid; +///名称 +@property (nonatomic, copy) NSString *name; +///所在区域编码 +@property (nonatomic, copy) NSString *adcode; +///中心点经纬度 +@property (nonatomic, copy) AMapGeoPoint *location; +///面积,单位平方米 +@property (nonatomic, assign) CGFloat area; +///输入经纬度是否在 aoi 面之中:0,代表在 aoi 内 其余整数代表距离 AOI 的距离 @since V9.7.4 +@property (nonatomic, assign) CGFloat distance; +///所属 aoi 类型 @since V9.7.4 +@property (nonatomic, copy) NSString *type; +@end + +///道路 +@interface AMapRoad : AMapSearchObject +///道路ID +@property (nonatomic, copy) NSString *uid; +///道路名称 +@property (nonatomic, copy) NSString *name; +///距离(单位:米) +@property (nonatomic, assign) NSInteger distance; +///方向 +@property (nonatomic, copy) NSString *direction; +///坐标点 +@property (nonatomic, copy) AMapGeoPoint *location; +@end + +///道路交叉口 +@interface AMapRoadInter : AMapSearchObject +///距离(单位:米) +@property (nonatomic, assign) NSInteger distance; +///方向 +@property (nonatomic, copy) NSString *direction; +///经纬度 +@property (nonatomic, copy) AMapGeoPoint *location; +///第一条道路ID +@property (nonatomic, copy) NSString *firstId; +///第一条道路名称 +@property (nonatomic, copy) NSString *firstName; +///第二条道路ID +@property (nonatomic, copy) NSString *secondId; +///第二条道路名称 +@property (nonatomic, copy) NSString *secondName; +@end + +///门牌信息 +@interface AMapStreetNumber : AMapSearchObject +///街道名称 +@property (nonatomic, copy) NSString *street; +///门牌号 +@property (nonatomic, copy) NSString *number; +///坐标点 +@property (nonatomic, copy) AMapGeoPoint *location; +///距离(单位:米) +@property (nonatomic, assign) NSInteger distance; +///方向 +@property (nonatomic, copy) NSString *direction; +@end + +///商圈 +@interface AMapBusinessArea : AMapSearchObject +///名称 +@property (nonatomic, strong) NSString *name; +///中心坐标 +@property (nonatomic, copy) AMapGeoPoint *location; +@end + +///地址组成要素 +@interface AMapAddressComponent : AMapSearchObject +///国家(since 5.7.0) +@property (nonatomic, copy) NSString *country; +///国家简码(since 7.4.0)仅海外生效 +@property (nonatomic, copy) NSString *countryCode; +///省/直辖市 +@property (nonatomic, copy) NSString *province; +///市 +@property (nonatomic, copy) NSString *city; +///城市编码 +@property (nonatomic, copy) NSString *citycode; +///区 +@property (nonatomic, copy) NSString *district; +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///乡镇街道 +@property (nonatomic, copy) NSString *township; +///乡镇街道编码 +@property (nonatomic, copy) NSString *towncode; +///社区 +@property (nonatomic, copy) NSString *neighborhood; +///建筑 +@property (nonatomic, copy) NSString *building; +///门牌信息 +@property (nonatomic, strong) AMapStreetNumber *streetNumber; +///商圈列表 AMapBusinessArea 数组 +@property (nonatomic, strong) NSArray *businessAreas; +@end + +///逆地理编码 +@interface AMapReGeocode : AMapSearchObject +///格式化地址 +@property (nonatomic, copy) NSString *formattedAddress; +///地址组成要素 +@property (nonatomic, strong) AMapAddressComponent *addressComponent; + +///道路信息 AMapRoad 数组 +@property (nonatomic, strong) NSArray *roads; +///道路路口信息 AMapRoadInter 数组 +@property (nonatomic, strong) NSArray *roadinters; +///兴趣点信息 AMapPOI 数组 +@property (nonatomic, strong) NSArray *pois; +///兴趣区域信息 AMapAOI 数组 +@property (nonatomic, strong) NSArray *aois; +@end + +///地理编码 +@interface AMapGeocode : AMapSearchObject +///格式化地址 +@property (nonatomic, copy) NSString *formattedAddress; +///所在省/直辖市 +@property (nonatomic, copy) NSString *province; +///城市名 +@property (nonatomic, copy) NSString *city; +///城市编码 +@property (nonatomic, copy) NSString *citycode; +///区域名称 +@property (nonatomic, copy) NSString *district; +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///乡镇街道 +@property (nonatomic, copy) NSString *township; +///社区 +@property (nonatomic, copy) NSString *neighborhood; +///楼 +@property (nonatomic, copy) NSString *building; +///坐标点 +@property (nonatomic, copy) AMapGeoPoint *location; +///匹配的等级 +@property (nonatomic, copy) NSString *level; +///国家(since 7.4.0)仅海外生效 +@property (nonatomic, copy) NSString *country; +///国家简码(since 7.4.0)仅海外生效 +@property (nonatomic, copy) NSString *postcode; +@end + +#pragma mark - 公交查询 +@class AMapBusLine; + +///公交站 +@interface AMapBusStop : AMapSearchObject +///公交站点ID +@property (nonatomic, copy) NSString *uid; +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///公交站名 +@property (nonatomic, copy) NSString *name; +///城市编码 +@property (nonatomic, copy) NSString *citycode; +///经纬度坐标 +@property (nonatomic, copy) AMapGeoPoint *location; +///途径此站的公交路线 AMapBusLine 数组 +@property (nonatomic, strong) NSArray *buslines; +///查询公交线路时的第几站 +@property (nonatomic, copy) NSString *sequence; +@end + +///公交线路 +@interface AMapBusLine : AMapSearchObject +///公交线路ID +@property (nonatomic, copy) NSString *uid; +///公交类型 +@property (nonatomic, copy) NSString *type; +///公交线路名称 +@property (nonatomic, copy) NSString *name; +///坐标集合 +@property (nonatomic, copy) NSString *polyline; +///城市编码 +@property (nonatomic, copy) NSString *citycode; +///首发站 +@property (nonatomic, copy) NSString *startStop; +///终点站 +@property (nonatomic, copy) NSString *endStop; +///当查询公交站点时,返回的 AMapBusLine 中含有该字段 +@property (nonatomic, copy) AMapGeoPoint *location; + +///首班车时间 +@property (nonatomic, copy) NSString *startTime; +///末班车时间 +@property (nonatomic, copy) NSString *endTime; +///所属公交公司 +@property (nonatomic, copy) NSString *company; +///距离。在公交线路查询时,该值为此线路的全程距离,单位为千米; 在公交路径规划时,该值为乘坐此路公交车的行驶距离,单位为米 +@property (nonatomic, assign) CGFloat distance; +///起步价 +@property (nonatomic, assign) CGFloat basicPrice; +///全程票价 +@property (nonatomic, assign) CGFloat totalPrice; +///矩形区域左下、右上顶点坐标 +@property (nonatomic, copy) AMapGeoPolygon *bounds; +///本线路公交站 AMapBusStop 数组 +@property (nonatomic, strong) NSArray *busStops; + +///起程站 +@property (nonatomic, strong) AMapBusStop *departureStop; +///下车站 +@property (nonatomic, strong) AMapBusStop *arrivalStop; +///途径公交站 AMapBusStop 数组 +@property (nonatomic, strong) NSArray *viaBusStops; +///预计行驶时间(单位:秒) +@property (nonatomic, assign) NSInteger duration; +///此段途径公交站数 +@property (nonatomic, assign) NSInteger viaNum; + +@end + +#pragma mark - 行政区划 +///行政区划 +@interface AMapDistrict : AMapSearchObject +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///城市编码 +@property (nonatomic, copy) NSString *citycode; +///行政区名称 +@property (nonatomic, copy) NSString *name; +///级别 +@property (nonatomic, copy) NSString *level; +///城市中心点 +@property (nonatomic, copy) AMapGeoPoint *center; +///下级行政区域数组 +@property (nonatomic, strong) NSArray *districts; +///行政区边界坐标点, NSString 数组 +@property (nonatomic, strong) NSArray *polylines; +@end + +#pragma mark - 路径规划 +///公交方案详细导航动作指令 +@interface AMapTransitNavi : AMapSearchObject +///导航主要动作指令 +@property (nonatomic, copy) NSString *action; +///导航辅助动作指令 +@property (nonatomic, copy) NSString *assistantAction; +///算路结果中存在的道路类型: +/* + *0,普通道路 1,人行横道 3,地下通道 4,过街天桥 + *5,地铁通道 6,公园 7,广场 8,扶梯 9,直梯 + *10,索道 11,空中通道 12,建筑物穿越通道 + *13,行人通道 14,游船路线 15,观光车路线 16,滑道 + *18,扩路 19,道路附属连接线 20,阶梯 21,斜坡 + *22,桥 23,隧道 30,轮渡 + */ +@property (nonatomic, copy) NSString *walkType; +@end + +///实时路况信息 +@interface AMapTMC : AMapSearchObject +///长度(单位:米) +@property (nonatomic, assign) NSInteger distance; +///路况状态描述:0 未知,1 畅通,2 缓行,3 拥堵,4 严重拥堵 +@property (nonatomic, copy) NSString *status; +///此路段坐标点串 +@property (nonatomic, copy) NSString *polyline; +@end + +///路段基本信息 +@interface AMapStep : AMapSearchObject +///行走指示 +@property (nonatomic, copy) NSString *instruction; +///方向 +@property (nonatomic, copy) NSString *orientation; +///道路名称 +@property (nonatomic, copy) NSString *road; +///此路段长度(单位:米) +@property (nonatomic, assign) NSInteger distance; +///此路段预计耗时(单位:秒) +@property (nonatomic, assign) NSInteger duration; +///此路段坐标点串 +@property (nonatomic, copy) NSString *polyline; +///导航主要动作 +@property (nonatomic, copy) NSString *action; +///导航辅助动作 +@property (nonatomic, copy) NSString *assistantAction; +///道路类型 +@property (nonatomic, assign) NSInteger walkType; +///此段收费(单位:元) +@property (nonatomic, assign) CGFloat tolls; +///收费路段长度(单位:米) +@property (nonatomic, assign) NSInteger tollDistance; +///主要收费路段 +@property (nonatomic, copy) NSString *tollRoad; +///此段交通信号灯个数 since 9.2.0 ( 只在算路2.0接口有效 ) +@property (nonatomic, assign) NSInteger totalTrafficLights; + +///途径城市 AMapCity 数组,只有驾车路径规划时有效 +@property (nonatomic, strong) NSArray *cities; +///路况信息数组,只有驾车路径规划时有效 +@property (nonatomic, strong) NSArray *tmcs; +@end + +///步行、骑行、驾车方案 +@interface AMapPath : AMapSearchObject +///起点和终点的距离 +@property (nonatomic, assign) NSInteger distance; +///预计耗时(单位:秒) +@property (nonatomic, assign) NSInteger duration; +///导航策略 +@property (nonatomic, copy) NSString *strategy; +///导航路段 AMapStep 数组 +@property (nonatomic, strong) NSArray *steps; +///此方案费用(单位:元) +@property (nonatomic, assign) CGFloat tolls; +///此方案收费路段长度(单位:米) +@property (nonatomic, assign) NSInteger tollDistance; +///此方案交通信号灯个数 +@property (nonatomic, assign) NSInteger totalTrafficLights; + +/** + 限行信息,仅在驾车和货车路径规划时有效。(since 6.0.0) + 驾车路径规划时: + 0 代表限行已规避或未限行; 1 代表限行无法规避。 + 货车路径规划时: + 0,未知(未输入完整/正确车牌号信息时候显示) + 1,已规避限行 + 2,起点限行 + 3,途径点在限行区域内(设置途径点才出现此报错) + 4,途径限行区域 + 5,终点限行 + */ +@property (nonatomic, assign) NSInteger restriction; +///规划路径完整坐标点串集合(since 7.4.0) +@property (nonatomic, copy) NSString *polyline; + +@end + +@interface AMapFutureTimeInfoElement : AMapSearchObject + +///总时长(分钟) +@property (nonatomic, assign) NSInteger duration; +///对应的路径规划方案中的路线 +@property (nonatomic, assign) NSInteger pathindex; +/** + 0:代表限行已规避或未限行,即该路线没有限行路段 + 1:代表限行无法规避,即该线路有限行路段 + */ +@property (nonatomic, assign) NSInteger restriction; +///路况信息数组,只会返回AMapTMC中的status、polyline +@property (nonatomic, strong) NSArray *tmcs; + +@end + +@interface AMapFutureTimeInfo : AMapSearchObject + +///出发时间 +@property (nonatomic, copy) NSString *startTime; +///路线列表 AMapFutureTimeInfoElement 数组 +@property (nonatomic, strong) NSArray *elements; +@end + +///步行换乘信息 +@interface AMapWalking : AMapSearchObject +///起点坐标 +@property (nonatomic, copy) AMapGeoPoint *origin; +///终点坐标 +@property (nonatomic, copy) AMapGeoPoint *destination; +///起点和终点的步行距离 +@property (nonatomic, assign) NSInteger distance; +///步行预计时间 +@property (nonatomic, assign) NSInteger duration; +///步行路段 AMapStep 数组 +@property (nonatomic, strong) NSArray *steps; +@end + +///出租车信息 +@interface AMapTaxi : AMapSearchObject +///起点坐标 +@property (nonatomic, copy) AMapGeoPoint *origin; +///终点坐标 +@property (nonatomic, copy) AMapGeoPoint *destination; +///距离,单位米 +@property (nonatomic, assign) NSInteger distance; +///耗时,单位秒 +@property (nonatomic, assign) NSInteger duration; +///起点名称 +@property (nonatomic, copy) NSString *sname; +///终点名称 +@property (nonatomic, copy) NSString *tname; +///打车预计花费金额 @singce 9.4.0 +@property (nonatomic, copy) NSString *price; +///线路点集合,通过show_fields控制返回与否 @singce 9.4.0 +@property (nonatomic, copy) NSString *polyline; +@end + +///火车站 +@interface AMapRailwayStation : AMapSearchObject +///火车站ID +@property (nonatomic, copy) NSString *uid; +///名称 +@property (nonatomic, copy) NSString *name; +///经纬度坐标 +@property (nonatomic, copy) AMapGeoPoint *location; +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///发车、到站时间,途径站时则为进站时间 +@property (nonatomic, copy) NSString *time; +///途径站点的停靠时间,单位为分钟 +@property (nonatomic, assign) NSInteger wait; +///是否是始发站,为出发站时有效 +@property (nonatomic, assign) BOOL isStart; +///是否是终点站,为到达站时有效 +@property (nonatomic, assign) BOOL isEnd; +@end + +///火车仓位及价格信息 +@interface AMapRailwaySpace : AMapSearchObject +///类型,硬卧、硬座等 +@property (nonatomic, copy) NSString *code; +///票价,单位元 +@property (nonatomic, assign) CGFloat cost; +@end + +///火车信息 +@interface AMapRailway : AMapSearchObject +///火车线路ID +@property (nonatomic, copy) NSString *uid; +///名称 +@property (nonatomic, copy) NSString *name; +///车次 +@property (nonatomic, copy) NSString *trip; +///类型 +@property (nonatomic, copy) NSString *type; +///该换乘段行车总距离,单位为米 +@property (nonatomic, assign) NSInteger distance; +///该线路车段耗时,单位为秒 +@property (nonatomic, assign) NSInteger time; +///出发站 +@property (nonatomic, strong) AMapRailwayStation *departureStation; +///到达站 +@property (nonatomic, strong) AMapRailwayStation *arrivalStation; +///仓位及价格信息 +@property (nonatomic, strong) NSArray *spaces; + +///途径站点信息 +@property (nonatomic, strong) NSArray *viaStops; +///备选路线信息, 目前只有id和name +@property (nonatomic, strong) NSArray *alters; +@end + + +///公交换乘路段,如果walking和buslines同时有值,则是先walking后buslines +@interface AMapSegment : AMapSearchObject +///此路段步行导航信息 +@property (nonatomic, strong) AMapWalking *walking; +///此路段可供选择的不同公交线路 AMapBusLine 数组 +@property (nonatomic, strong) NSArray *buslines; +///出租车信息,跨城时有效 +@property (nonatomic, strong) AMapTaxi *taxi; +///火车信息,跨城时有效 +@property (nonatomic, strong) AMapRailway *railway; +///入口名称 +@property (nonatomic, copy) NSString *enterName; +///入口经纬度 +@property (nonatomic, copy) AMapGeoPoint *enterLocation; +///出口名称 +@property (nonatomic, copy) NSString *exitName; +///出口经纬度 +@property (nonatomic, copy) AMapGeoPoint *exitLocation; +@end + +///公交方案 +@interface AMapTransit : AMapSearchObject +///此公交方案价格(单位:元) +@property (nonatomic, assign) CGFloat cost; +///此换乘方案预期时间(单位:秒) +@property (nonatomic, assign) NSInteger duration; +///是否是夜班车 +@property (nonatomic, assign) BOOL nightflag; +///此方案总步行距离(单位:米) +@property (nonatomic, assign) NSInteger walkingDistance; +///换乘路段 AMapSegment 数组 +@property (nonatomic, strong) NSArray *segments; +///当前方案的总距离 +@property (nonatomic, assign) NSInteger distance; +@end + +///路径规划信息 +@interface AMapRoute : AMapSearchObject +///起点坐标 +@property (nonatomic, copy) AMapGeoPoint *origin; +///终点坐标 +@property (nonatomic, copy) AMapGeoPoint *destination; +///出租车费用(单位:元) +@property (nonatomic, assign) CGFloat taxiCost; +///步行、骑行、驾车方案列表 AMapPath 数组 +@property (nonatomic, strong) NSArray *paths; +///公交换乘方案列表 AMapTransit 数组 +@property (nonatomic, strong) NSArray *transits; +///详细导航动作指令 since 9.4.0 +@property (nonatomic, strong) AMapTransitNavi *transitNavi; +///分路段坐标点串,两点间用“,”分隔 since 9.4.0 +@property (nonatomic, copy) NSString *polyline; +/// 起点和终点总距离 +@property (nonatomic, assign) CGFloat distance; +@end + +///距离测量结果 +@interface AMapDistanceResult : AMapSearchObject +///起点坐标,起点坐标序列号(从1开始) +@property (nonatomic, assign) NSInteger originID; +///终点坐标,终点坐标序列号(从1开始) +@property (nonatomic, assign) NSInteger destID; +///路径距离,单位:米 +@property (nonatomic, assign) NSInteger distance; +///预计行驶时间,单位:秒 +@property (nonatomic, assign) NSInteger duration; +///错误信息,建议用此字段判断请求是否成功 +@property (nonatomic, copy) NSString *info; +///在驾车模式下有效。默认为0;1:指定地点之间没有可以行车的道路;2:起点/终点 距离所有道路均距离过远(例如在海洋/矿业);3;起点/终点不在中国境内; +@property (nonatomic, assign) NSInteger code; +@end + +#pragma mark - 天气查询 + +///实况天气,仅支持中国部分地区数据(台湾省目前没有数据)返回 +@interface AMapLocalWeatherLive : AMapSearchObject +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///省份名 +@property (nonatomic, copy) NSString *province; +///城市名 +@property (nonatomic, copy) NSString *city; +///天气现象 +@property (nonatomic, copy) NSString *weather; +///实时温度 +@property (nonatomic, copy) NSString *temperature; +///风向 +@property (nonatomic, copy) NSString *windDirection; +///风力,单位:级 +@property (nonatomic, copy) NSString *windPower; +///空气湿度 +@property (nonatomic, copy) NSString *humidity; +///数据发布时间 +@property (nonatomic, copy) NSString *reportTime; +@end + +///某一天的天气预报信息 +@interface AMapLocalDayWeatherForecast : AMapSearchObject +///日期 +@property (nonatomic, copy) NSString *date; +///星期 +@property (nonatomic, copy) NSString *week; +///白天天气现象 +@property (nonatomic, copy) NSString *dayWeather; +///晚上天气现象 +@property (nonatomic, copy) NSString *nightWeather; +///白天温度 +@property (nonatomic, copy) NSString *dayTemp; +///晚上温度 +@property (nonatomic, copy) NSString *nightTemp; +///白天风向 +@property (nonatomic, copy) NSString *dayWind; +///晚上风向 +@property (nonatomic, copy) NSString *nightWind; +///白天风力 +@property (nonatomic, copy) NSString *dayPower; +///晚上风力 +@property (nonatomic, copy) NSString *nightPower; +@end + +///天气预报类,支持当前时间在内的3天的天气进行预报 +@interface AMapLocalWeatherForecast : AMapSearchObject +///区域编码 +@property (nonatomic, copy) NSString *adcode; +///省份名 +@property (nonatomic, copy) NSString *province; +///城市名 +@property (nonatomic, copy) NSString *city; +///数据发布时间 +@property (nonatomic, copy) NSString *reportTime; +///天气预报AMapLocalDayWeatherForecast数组 +@property (nonatomic, strong) NSArray *casts; +@end + +#pragma mark - 附近搜索 +///附近搜索返回的用户信息 +@interface AMapNearbyUserInfo : AMapSearchObject +///用户ID +@property (nonatomic, copy) NSString *userID; +///最后更新位置 +@property (nonatomic, copy) AMapGeoPoint *location; +///与搜索点的距离,由搜索时searchType决定 +@property (nonatomic, assign) CGFloat distance; +///最后更新的时间戳,单位秒 +@property (nonatomic, assign) NSTimeInterval updatetime; +@end + +#pragma mark - 交通态势 + +///道路路况评价 since 5.1.0 +@interface AMapTrafficEvaluation : AMapSearchObject +///综述 +@property (nonatomic, copy) NSString *evaluationDescription; +///0:未知;1:畅通;2:缓行;3:拥堵 +@property (nonatomic, assign) NSInteger status; +///畅通所占百分比 +@property (nonatomic, copy) NSString *expedite; +///缓行所占百分比 +@property (nonatomic, copy) NSString *congested; +///拥堵所占百分比 +@property (nonatomic, copy) NSString *blocked; +///未知路段所占百分比 +@property (nonatomic, copy) NSString *unknown; +@end + +///道路路况返回的道路信息 since 5.1.0 +@interface AMapTrafficRoad : AMapSearchObject +///道路名称 +@property (nonatomic, copy) NSString *name; +///0:未知;1:畅通;2:缓行;3:拥堵 +@property (nonatomic, assign) NSInteger status; +///方向描述 +@property (nonatomic, copy) NSString *direction; +///车行角度,判断道路正反向使用。 以正东方向为0度,逆时针方向为正,取值范围:[0,360] +@property (nonatomic, assign) float angle; +///速度 单位:千米/小时 +@property (nonatomic, assign) float speed; +///道路坐标集,经度和纬度使用","分隔,坐标之间使用";"分隔。例如:x1,y1;x2,y2 +@property (nonatomic, copy) NSString *polyline; +@end + +///道路路况信息 since 5.1.0 +@interface AMapTrafficInfo : AMapSearchObject +///路况综述 +@property (nonatomic, copy) NSString *statusDescription; +///路况评价 +@property (nonatomic, strong) AMapTrafficEvaluation *evaluation; +///道路信息 +@property (nonatomic, strong) NSArray* roads; + +@end + +#pragma mark - 企业地图基础数据类型 + +///POI点的图片信息 +@interface AMapCloudImage : AMapSearchObject +///图片的id标识 +@property (nonatomic, copy) NSString *uid; +///图片压缩后的url串 +@property (nonatomic, copy) NSString *preurl; +///图片原始的url +@property (nonatomic, copy) NSString *url; +@end + +///POI信息 +@interface AMapCloudPOI : AMapSearchObject +///唯一标识 +@property (nonatomic, assign) NSInteger uid; +///名称 +@property (nonatomic, copy) NSString *name; +///坐标位置 +@property (nonatomic, copy) AMapGeoPoint *location; +///地址 +@property (nonatomic, copy) NSString *address; +///用户自定义字段 +@property (nonatomic, strong) NSDictionary *customFields; +///创建时间 +@property (nonatomic, copy) NSString *createTime; +///更新时间 +@property (nonatomic, copy) NSString *updateTime; +///离当前位置的距离(只在企业地图周边搜索时有效) +@property (nonatomic, assign) NSInteger distance; +///图片信息 +@property (nonatomic, strong) NSArray *images __attribute((deprecated("已废弃 since 7.4.0"))); + +@end diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapNearbySearchManager.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapNearbySearchManager.h new file mode 100644 index 0000000..1d725f0 --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapNearbySearchManager.h @@ -0,0 +1,85 @@ +// +// AMapNearbySearchManager.h +// AMapSearchKit +// +// Created by xiaoming han on 15/8/31. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +#import +#import "AMapNearbyUploadInfo.h" + +@class AMapNearbySearchManager; + +///附近搜索代理 +@protocol AMapNearbySearchManagerDelegate +@optional + +/** + * @brief 开启自动上传,需实现该回调。 + */ +- (AMapNearbyUploadInfo *)nearbyInfoForUploading:(AMapNearbySearchManager *)manager; + +/** + * @brief 用户信息上传完毕回调。 + * @param error 错误,为空时表示成功。 + */ +- (void)onNearbyInfoUploadedWithError:(NSError *)error; + +/** + * @brief 用户信息清除完毕回调。 + * @param error 错误,为空时表示成功。 + */ +- (void)onUserInfoClearedWithError:(NSError *)error; + +@end + +///附近搜索管理类,同时只能有一个实例开启,否则可能会出现错误。 +@interface AMapNearbySearchManager : NSObject + +///上传最小间隔,默认15s,最小7s。自动上传的过程中设置无效。 +@property (nonatomic, assign) NSTimeInterval uploadTimeInterval; + +///代理对象。 +@property (nonatomic, weak) id delegate; + +///是否正在自动上传状态中。 +@property (nonatomic, readonly) BOOL isAutoUploading; + +/** + * @brief manager单例. + * 初始化之前请设置key,否则将无法正常使用该服务. + * @return nearbySearch实例。 + */ ++ (instancetype)sharedInstance; + +/** + * @brief 请使用单例。 + */ +- (instancetype)init __attribute__((unavailable)); + +/** + * @brief 启动自动上传。 + */ +- (void)startAutoUploadNearbyInfo; + +/** + * @brief 关闭自动上传。 + */ +- (void)stopAutoUploadNearbyInfo; + +/** + * @brief 执行单次上传,执行间隔不低于uploadTimeInterval最小值,否则执行失败。 + * @param info 需要上传的信息。 + * @return 成功执行返回YES,否则返回NO。 + */ +- (BOOL)uploadNearbyInfo:(AMapNearbyUploadInfo *)info __attribute((deprecated("已废弃 since 7.4.0,该功能不再支持"))); + +/** + * @brief 清除服务器上某一用户的信息。 + * @param userID 指定的用户ID + * @return 成功执行返回YES,否则返回NO。 + */ +- (BOOL)clearUserInfoWithID:(NSString *)userID __attribute((deprecated("已废弃 since 7.4.0,该功能不再支持"))); + +@end diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapNearbyUploadInfo.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapNearbyUploadInfo.h new file mode 100644 index 0000000..d8d316b --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapNearbyUploadInfo.h @@ -0,0 +1,32 @@ +// +// AMapNearbyUploadInfo.h +// AMapSearchKit +// +// Created by xiaoming han on 15/9/6. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +#import +#import + +///上传经纬度类型 +typedef NS_ENUM(NSInteger, AMapSearchCoordinateType) +{ + AMapSearchCoordinateTypeGPS = 1, ///< 标准GPS坐标 + AMapSearchCoordinateTypeAMap = 2, ///< 高德坐标 +}; + + +///附近搜索上传信息 +@interface AMapNearbyUploadInfo : NSObject + +///用户唯一标识,不能为空,否则上传会失败. 长度不超过32字符,只能包含英文、数字、下划线、短横杠 +@property (nonatomic, copy) NSString *userID; + +///坐标类型,默认是 AMapSearchCoordinateTypeAMap +@property (nonatomic, assign) AMapSearchCoordinateType coordinateType; + +///用户位置经纬度。 +@property (nonatomic, assign) CLLocationCoordinate2D coordinate; + +@end diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchAPI.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchAPI.h new file mode 100644 index 0000000..dab35e3 --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchAPI.h @@ -0,0 +1,418 @@ +// +// AMapSearchAPI.h +// AMapSearchKit +// +// Created by xiaoming han on 15/7/22. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +#import +#import "AMapSearchObjV1.h" +#import "AMapSearchObj.h" +#import "AMapCommonObj.h" +#import + +@protocol AMapSearchDelegate; + +///搜索结果语言 +#define AMapSearchLanguageZhCN @"zh-Hans" ///< 中文 +#define AMapSearchLanguageEn @"en" ///< 英文 + +///搜索类 +@interface AMapSearchAPI : NSObject + +///实现了 AMapSearchDelegate 协议的类指针。 +@property (nonatomic, weak) id delegate; + +///查询超时时间,单位秒,默认为20秒。 +@property (nonatomic, assign) NSInteger timeout; + +///查询结果返回语言, 默认为中文。 +@property (nonatomic, copy) NSString *language; + +///自定义参数 +@property (nonatomic, copy) NSDictionary *customParams; + +/** + * @brief AMapSearch的初始化函数。 + * 初始化之前请正确设置key,否则将无法正常使用搜索服务. + * @return AMapSearch类对象实例 + */ +- (instancetype)init; + +/** + * @brief 取消所有未回调的请求,触发错误回调。 + */ +- (void)cancelAllRequests; + +#pragma mark - Privacy 隐私合规 +/** + * @brief 更新App是否显示隐私弹窗的状态,隐私弹窗是否包含高德SDK隐私协议内容的状态. since 8.1.0 + * @param showStatus 隐私弹窗状态 + * @param containStatus 包含高德SDK隐私协议状态 + */ ++ (void)updatePrivacyShow:(AMapPrivacyShowStatus)showStatus privacyInfo:(AMapPrivacyInfoStatus)containStatus; +/** +* @brief 更新用户授权高德SDK隐私协议状态. since 8.1.0 +* @param agreeStatus 用户授权高德SDK隐私协议状态 +*/ ++ (void)updatePrivacyAgree:(AMapPrivacyAgreeStatus)agreeStatus; + + +#pragma mark - 搜索服务接口 + +/** + * @brief POI ID查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIIDSearchRequest 类。 + */ +- (void)AMapPOIIDSearch:(AMapPOIIDSearchRequest *)request; + +/** + * @brief POI 关键字查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIKeywordsSearchRequest 类。 + */ +- (void)AMapPOIKeywordsSearch:(AMapPOIKeywordsSearchRequest *)request; + +/** + * @brief POI 周边查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIAroundSearchRequest 类。 + */ +- (void)AMapPOIAroundSearch:(AMapPOIAroundSearchRequest *)request; + +/** + * @brief POI 多边形查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIPolygonSearchRequest 类。 + */ +- (void)AMapPOIPolygonSearch:(AMapPOIPolygonSearchRequest *)request; + +#pragma mark - 搜索服务接口V1(API版本V3) + +/** + * @brief POI ID查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIIDSearchRequestV1 类。 + */ +- (void)AMapPOIIDSearchV1:(AMapPOIIDSearchRequestV1 *)request; + +/** + * @brief POI 关键字查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIKeywordsSearchRequestV1 类。 + */ +- (void)AMapPOIKeywordsSearchV1:(AMapPOIKeywordsSearchRequestV1 *)request; + +/** + * @brief POI 周边查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIAroundSearchRequestV1 类。 + */ +- (void)AMapPOIAroundSearchV1:(AMapPOIAroundSearchRequestV1 *)request; + +/** + * @brief POI 多边形查询接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIPolygonSearchRequestV1 类。 + */ +- (void)AMapPOIPolygonSearchV1:(AMapPOIPolygonSearchRequestV1 *)request; + +/** + * @brief 沿途查询接口 (v4.3.0) + * @param request 查询选项。具体属性字段请参考 AMapRoutePOISearchRequest 类。 + */ +- (void)AMapRoutePOISearch:(AMapRoutePOISearchRequest *)request; + +/** + * @brief 地址编码查询接口 + * @param request 查询选项。具体属性字段请参考 AMapGeocodeSearchRequest 类。 + */ +- (void)AMapGeocodeSearch:(AMapGeocodeSearchRequest *)request; + +/** + * @brief 逆地址编码查询接口 + * @param request 查询选项。具体属性字段请参考 AMapReGeocodeSearchRequest 类。 + */ +- (void)AMapReGoecodeSearch:(AMapReGeocodeSearchRequest *)request; + +/** + * @brief 输入提示查询接口 + * @param request 查询选项。具体属性字段请参考 AMapInputTipsSearchRequest 类。 + */ +- (void)AMapInputTipsSearch:(AMapInputTipsSearchRequest *)request; + +/** + * @brief 公交站点查询接口 + * @param request 查询选项。具体属性字段请参考 AMapBusStopSearchRequest 类。 + */ +- (void)AMapBusStopSearch:(AMapBusStopSearchRequest *)request; + +/** + * @brief 公交线路关键字查询 + * @param request 查询选项。具体属性字段请参考 AMapBusLineIDSearchRequest 类。 + */ +- (void)AMapBusLineIDSearch:(AMapBusLineIDSearchRequest *)request; + +/** + * @brief 公交线路关键字查询 + * @param request 查询选项。具体属性字段请参考 AMapBusLineNameSearchRequest 类。 + */ +- (void)AMapBusLineNameSearch:(AMapBusLineNameSearchRequest *)request; + +/** + * @brief 行政区域查询接口 + * @param request 查询选项。具体属性字段请参考 AMapDistrictSearchRequest 类。 + */ +- (void)AMapDistrictSearch:(AMapDistrictSearchRequest *)request; + +/** + * @brief 驾车路径规划1.0查询接口 + * @param request 查询选项。具体属性字段请参考 AMapDrivingRouteSearchRequest 类。 + */ +- (void)AMapDrivingRouteSearch:(AMapDrivingRouteSearchRequest *)request; + +/** + * @brief 驾车路径规划V2.0查询接口 + * @param request 查询选项。具体属性字段请参考 AMapDrivingCalRouteSearchRequest 类。 + */ +- (void)AMapDrivingV2RouteSearch:(AMapDrivingCalRouteSearchRequest *)request; + +/** + * @brief 步行路径规划查询接口 + * @param request 查询选项。具体属性字段请参考 AMapWalkingRouteSearchRequest 类。 + */ +- (void)AMapWalkingRouteSearch:(AMapWalkingRouteSearchRequest *)request; + +/** + * @brief 公交路径规划查询接口 + * @param request 查询选项。具体属性字段请参考 AMapTransitRouteSearchRequest 类。 + */ +- (void)AMapTransitRouteSearch:(AMapTransitRouteSearchRequest *)request; + +/** + * @brief 骑行路径规划查询接口 (since 4.3.0) + * @param request 查询选项。具体属性字段请参考 AMapRidingRouteSearchRequest 类。 + */ +- (void)AMapRidingRouteSearch:(AMapRidingRouteSearchRequest *)request; + +/** + * @brief 货车路径规划查询接口 (since 6.1.0) + * @param request 查询选项。具体属性字段请参考 AMapTruckRouteSearchRequest 类。 + */ +- (void)AMapTruckRouteSearch:(AMapTruckRouteSearchRequest *)request; + +/** + * @brief 未来路线规划查询接口 (since 6.9.0) + * @param request 查询选项。具体属性字段请参考 AMapTruckRouteSearchRequest 类。 + */ +- (void)AMapFutureRouteSearch:(AMapFutureRouteSearchRequest *)request; + +/** + * @brief 天气查询接口 + * @param request 查询选项。具体属性字段请参考 AMapWeatherSearchRequest 类。 + */ +- (void)AMapWeatherSearch:(AMapWeatherSearchRequest *)request; + +/** + * @brief 距离查询(since 6.1.0) + * @param request 查询选项。具体属性字段请参考 AMapDistanceSearchRequest 类。 + */ +- (void)AMapDistanceSearch:(AMapDistanceSearchRequest *)request; + +- (void)AMapChargeStationSearch:(AMapChargeStationSearchRequest *)request; +#pragma mark - 附近搜索相关 + +/** + * @brief 附近搜索查询接口 + * @param request 查询选项。具体属性字段请参考 AMapNearbySearchRequest 类。 + */ +- (void)AMapNearbySearch:(AMapNearbySearchRequest *)request __attribute__((deprecated("已废弃, from 7.4.0,该功能不再支持"))); + +#pragma mark - 企业地图搜索相关 + +/** + * @brief 企业地图周边查询接口 + * @param request 查询选项。具体属性字段请参考 AMapCloudPOIAroundSearchRequest 类。 + */ +- (void)AMapCloudPOIAroundSearch:(AMapCloudPOIAroundSearchRequest *)request; + +/** + * @brief 企业地图polygon区域查询接口 + * @param request 查询选项。具体属性字段请参考 AMapCloudPOIPolygonSearchRequest 类。 + */ +- (void)AMapCloudPOIPolygonSearch:(AMapCloudPOIPolygonSearchRequest *)request; + +/** + * @brief 企业地图ID查询接口 + * @param request 查询选项。具体属性字段请参考 AMapCloudPOIIDSearchRequest 类。 + */ +- (void)AMapCloudPOIIDSearch:(AMapCloudPOIIDSearchRequest *)request; + +/** + * @brief 企业地图本地查询接口 + * @param request 查询选项。具体属性字段请参考 AMapCloudPOILocalSearchRequest 类。 + */ +- (void)AMapCloudPOILocalSearch:(AMapCloudPOILocalSearchRequest *)request; + +#pragma mark - 短串分享相关 + +/** + * @brief 位置短串分享接口 + * @param request 查询选项。具体属性字段请参考 AMapLocationShareSearchRequest 类。 + */ +- (void)AMapLocationShareSearch:(AMapLocationShareSearchRequest *)request; + +/** + * @brief 兴趣点短串分享接口 + * @param request 查询选项。具体属性字段请参考 AMapPOIShareSearchRequest 类。 + */ +- (void)AMapPOIShareSearch:(AMapPOIShareSearchRequest *)request; + +/** + * @brief 路线规划短串分享接口 + * @param request 查询选项。具体属性字段请参考 AMapRouteShareSearchRequest 类。 + */ +- (void)AMapRouteShareSearch:(AMapRouteShareSearchRequest *)request; + +/** + * @brief 导航短串分享接口 + * @param request 查询选项。具体属性字段请参考 AMapNavigationShareSearchRequest 类。 + */ +- (void)AMapNavigationShareSearch:(AMapNavigationShareSearchRequest *)request; + +@end + +#pragma mark - AMapSearchDelegate + +///AMapSearchDelegate协议, 定义了搜索结果的回调方法,发生错误时的错误回调方法。 +@protocol AMapSearchDelegate +@optional + +/** + * @brief 当请求发生错误时,会调用代理的此方法. + * @param request 发生错误的请求. + * @param error 返回的错误. + */ +- (void)AMapSearchRequest:(id)request didFailWithError:(NSError *)error; + +/** + * @brief POI查询回调函数 + * @param request 发起的请求,具体字段参考 AMapPOISearchBaseRequest 及其子类。 + * @param response 响应结果,具体字段参考 AMapPOISearchResponse 。 + */ +- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response; + +/** + * @brief POI查询回调函数 + * @param request 发起的请求,具体字段参考 AMapPOISearchBaseRequest 及其子类。 + * @param response 响应结果,具体字段参考 AMapPOISearchResponse 。 + */ +- (void)onPOISearchDoneV1:(AMapPOISearchBaseRequestV1 *)request response:(AMapPOISearchResponseV1 *)response; + +/** + * @brief 沿途查询回调函数 (since v4.3.0) + * @param request 发起的请求,具体字段参考 AMapRoutePOISearchRequest 及其子类。 + * @param response 响应结果,具体字段参考 AMapRoutePOISearchResponse 。 + */ +- (void)onRoutePOISearchDone:(AMapRoutePOISearchRequest *)request response:(AMapRoutePOISearchResponse *)response; + +/** + * @brief 地理编码查询回调函数 + * @param request 发起的请求,具体字段参考 AMapGeocodeSearchRequest 。 + * @param response 响应结果,具体字段参考 AMapGeocodeSearchResponse 。 + */ +- (void)onGeocodeSearchDone:(AMapGeocodeSearchRequest *)request response:(AMapGeocodeSearchResponse *)response; + +/** + * @brief 逆地理编码查询回调函数 + * @param request 发起的请求,具体字段参考 AMapReGeocodeSearchRequest 。 + * @param response 响应结果,具体字段参考 AMapReGeocodeSearchResponse 。 + */ +- (void)onReGeocodeSearchDone:(AMapReGeocodeSearchRequest *)request response:(AMapReGeocodeSearchResponse *)response; + +/** + * @brief 输入提示查询回调函数 + * @param request 发起的请求,具体字段参考 AMapInputTipsSearchRequest 。 + * @param response 响应结果,具体字段参考 AMapInputTipsSearchResponse 。 + */ +- (void)onInputTipsSearchDone:(AMapInputTipsSearchRequest *)request response:(AMapInputTipsSearchResponse *)response; + +/** + * @brief 公交站查询回调函数 + * @param request 发起的请求,具体字段参考 AMapBusStopSearchRequest 。 + * @param response 响应结果,具体字段参考 AMapBusStopSearchResponse 。 + */ +- (void)onBusStopSearchDone:(AMapBusStopSearchRequest *)request response:(AMapBusStopSearchResponse *)response; + +/** + * @brief 公交线路关键字查询回调 + * @param request 发起的请求,具体字段参考 AMapBusLineSearchRequest 。 + * @param response 响应结果,具体字段参考 AMapBusLineSearchResponse 。 + */ +- (void)onBusLineSearchDone:(AMapBusLineBaseSearchRequest *)request response:(AMapBusLineSearchResponse *)response; + +/** + * @brief 行政区域查询回调函数 + * @param request 发起的请求,具体字段参考 AMapDistrictSearchRequest 。 + * @param response 响应结果,具体字段参考 AMapDistrictSearchResponse 。 + */ +- (void)onDistrictSearchDone:(AMapDistrictSearchRequest *)request response:(AMapDistrictSearchResponse *)response; + +/** + * @brief 路径规划查询回调 + * @param request 发起的请求,具体字段参考 AMapRouteSearchBaseRequest 及其子类。 + * @param response 响应结果,具体字段参考 AMapRouteSearchResponse 。 + */ +- (void)onRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapRouteSearchResponse *)response; + +/** + * @brief 未来路径规划查询回调 since 6.9.0 + * @param request 发起的请求,具体字段参考 AMapRouteSearchBaseRequest 及其子类。 + * @param response 响应结果,具体字段参考 AMapRouteSearchResponse 。 + */ +- (void)onFutureRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapFutureRouteSearchResponse *)response; + +/** + * @brief 距离查询回调 + * @param request 发起的请求,具体字段参考 AMapDistanceSearchRequest 及其子类。 + * @param response 响应结果,具体字段参考 AMapDistanceSearchResponse 。 + */ +- (void)onDistanceSearchDone:(AMapDistanceSearchRequest *)request response:(AMapDistanceSearchResponse *)response; + +/** + * @brief 天气查询回调 + * @param request 发起的请求,具体字段参考 AMapWeatherSearchRequest 。 + * @param response 响应结果,具体字段参考 AMapWeatherSearchResponse 。 + */ +- (void)onWeatherSearchDone:(AMapWeatherSearchRequest *)request response:(AMapWeatherSearchResponse *)response; + +#pragma mark - 附近搜索回调 + +/** + * @brief 附近搜索回调 + * @param request 发起的请求,具体字段参考 AMapNearbySearchRequest 。 + * @param response 响应结果,具体字段参考 AMapNearbySearchResponse 。 + */ +- (void)onNearbySearchDone:(AMapNearbySearchRequest *)request response:(AMapNearbySearchResponse *)response; + +#pragma mark - 企业地图搜索回调 + +/** + * @brief 企业地图查询回调函数 + * @param request 发起的请求,具体字段参考 AMapCloudSearchBaseRequest 。 + * @param response 响应结果,具体字段参考 AMapCloudPOISearchResponse 。 + */ +- (void)onCloudSearchDone:(AMapCloudSearchBaseRequest *)request response:(AMapCloudPOISearchResponse *)response; + +#pragma mark - 短串分享搜索回调 + +/** + * @brief 短串分享搜索回调 + * @param request 发起的请求 + * @param response 相应结果,具体字段参考 AMapShareSearchResponse。 + */ +- (void)onShareSearchDone:(AMapShareSearchBaseRequest *)request response:(AMapShareSearchResponse *)response; + +#pragma mark - 车机充电站搜索回调 + +/** + * @brief 车机充电站搜索回调 + * @param request 发起的请求 + * @param response 相应结果,具体字段参考 AMapShareSearchResponse。 + */ +- (void)onChargeStationSearchDone:(AMapChargeStationSearchRequest *)request response:(AMapChargeStationSearchResponse *)response; +@end diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchError.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchError.h new file mode 100644 index 0000000..2b4e758 --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchError.h @@ -0,0 +1,80 @@ +// +// AMapSearchError.h +// AMapSearchKit +// +// Created by xiaoming han on 15/7/29. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +#ifndef AMapSearchKit_AMapSearchError_h +#define AMapSearchKit_AMapSearchError_h + +///AMapSearch errorDomain +extern NSString * const AMapSearchErrorDomain; + +///注意:增加errorCode时,需增加code对应详细说明信息errorInfoWithCode +///AMapSearch errorCode +typedef NS_ENUM(NSInteger, AMapSearchErrorCode) +{ + AMapSearchErrorOK = 1000,///< 没有错误 + AMapSearchErrorInvalidSignature = 1001,///< 无效签名 + AMapSearchErrorInvalidUserKey = 1002,///< key非法或过期 + AMapSearchErrorServiceNotAvailable = 1003,///< 没有权限使用相应的接口 + AMapSearchErrorDailyQueryOverLimit = 1004,///< 访问已超出日访问量 + AMapSearchErrorTooFrequently = 1005,///< 用户访问过于频繁 + AMapSearchErrorInvalidUserIP = 1006,///< 用户IP无效 + AMapSearchErrorInvalidUserDomain = 1007,///< 用户域名无效 + AMapSearchErrorInvalidUserSCode = 1008,///< 安全码验证错误,bundleID与key不对应 + AMapSearchErrorUserKeyNotMatch = 1009,///< 请求key与绑定平台不符 + AMapSearchErrorIPQueryOverLimit = 1010,///< IP请求超限 + AMapSearchErrorNotSupportHttps = 1011,///< 不支持HTTPS请求 + AMapSearchErrorInsufficientPrivileges = 1012,///< 权限不足,服务请求被拒绝 + AMapSearchErrorUserKeyRecycled = 1013,///< 开发者key被删除,无法正常使用 + + AMapSearchErrorInvalidResponse = 1100,///< 请求服务响应错误 + AMapSearchErrorInvalidEngineData = 1101,///< 引擎返回数据异常 + AMapSearchErrorConnectTimeout = 1102,///< 服务端请求链接超时 + AMapSearchErrorReturnTimeout = 1103,///< 读取服务结果超时 + AMapSearchErrorInvalidParams = 1200,///< 请求参数非法 + AMapSearchErrorMissingRequiredParams = 1201,///< 缺少必填参数 + AMapSearchErrorIllegalRequest = 1202,///< 请求协议非法 + AMapSearchErrorServiceUnknown = 1203,///< 其他服务端未知错误 + + AMapSearchErrorClientUnknown = 1800,///< 客户端未知错误,服务返回结果为空或其他错误 + AMapSearchErrorInvalidProtocol = 1801,///< 协议解析错误,通常是返回结果无法解析 + AMapSearchErrorTimeOut = 1802,///< 连接超时 + AMapSearchErrorBadURL = 1803,///< URL异常 + AMapSearchErrorCannotFindHost = 1804,///< 找不到主机 + AMapSearchErrorCannotConnectToHost = 1805,///< 服务器连接失败 + AMapSearchErrorNotConnectedToInternet = 1806,///< 连接异常,通常为没有网络的情况 + AMapSearchErrorCancelled = 1807,///< 连接取消 + + AMapSearchErrorOverPassPointCount = 1809,///< 途经点个数超限 + AMapSearchErrorOverPassAreaMaxCount = 1810,///< 避让区域个数超限 + AMapSearchErrorOverPassAreaMaxArea = 1811,///< 避让区域大小超限 + AMapSearchErrorOverPassAreaPointCount = 1812,///< 避让区域点个数超限 + AMapSearchErrorOverPassKeyWordLenth = 1813,///< 搜索关键字长度超限 + + AMapSearchErrorTableIDNotExist = 2000,///< table id 格式不正确 + AMapSearchErrorIDNotExist = 2001,///< id 不存在 + AMapSearchErrorServiceMaintenance = 2002,///< 服务器维护中 + AMapSearchErrorEngineTableIDNotExist = 2003,///< key对应的table id 不存在 + AMapSearchErrorInvalidNearbyUserID = 2100,///< 找不到对应userID的信息 + AMapSearchErrorNearbyKeyNotBind = 2101,///< key未开通“附近”功能 + + AMapSearchErrorOutOfService = 3000,///< 规划点(包括起点、终点、途经点)不在中国范围内 + AMapSearchErrorNoRoadsNearby = 3001,///< 规划点(包括起点、终点、途经点)附近搜不到道路 + AMapSearchErrorRouteFailed = 3002,///< 路线计算失败,通常是由于道路连通关系导致 + AMapSearchErrorOverDirectionRange = 3003,///< 起点终点距离过长 + + AMapSearchErrorShareLicenseExpired = 4000,///< 短串分享认证失败 + AMapSearchErrorShareFailed = 4001,///< 短串请求失败 +}; + +@interface AMapSearchError : NSObject + +///错误码所对应详细信息 ++ (NSString *)errorInfoWithCode:(AMapSearchErrorCode)errorCode; + +@end +#endif diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchKit.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchKit.h new file mode 100644 index 0000000..ed46e5c --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchKit.h @@ -0,0 +1,16 @@ +// +// AMapSearchKit.h +// AMapSearchKit +// +// Created by xiaoming han on 15/7/22. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +#import +#import +#import +#import +#import +#import + +#import diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchObj.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchObj.h new file mode 100644 index 0000000..93b43b4 --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchObj.h @@ -0,0 +1,874 @@ +// +// AMapSearchObj.h +// AMapSearchKit +// +// Created by xiaoming han on 15/7/22. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +/* 该文件定义了搜索请求和返回对象。*/ + +#import +#import "AMapCommonObj.h" + +///沿途搜索类型 +typedef NS_ENUM(NSInteger, AMapRoutePOISearchType) +{ + AMapRoutePOISearchTypeGasStation = 0, ///< 加油站 + AMapRoutePOISearchTypeMaintenanceStation = 1, ///< 维修站 + AMapRoutePOISearchTypeATM = 2, ///< ATM + AMapRoutePOISearchTypeToilet = 3, ///< 厕所 + AMapRoutePOISearchTypeGasAirStation = 4, ///< 加气站 + AMapRoutePOISearchTypeParkStation = 5, ///< 服务区 + AMapRoutePOISearchTypeChargingPile = 6, ///< 充电桩 + AMapRoutePOISearchTypeFood = 7, ///< 美食 + AMapRoutePOISearchTypeHotel = 8, ///< 酒店 +}; + +///天气查询类型 +typedef NS_ENUM(NSInteger, AMapWeatherType) +{ + AMapWeatherTypeLive = 1, ///< 实时 + AMapWeatherTypeForecast ///< 预报 +}; + +///企业地图搜索结果排序 +typedef NS_ENUM(NSInteger, AMapCloudSortType) +{ + AMapCloudSortTypeDESC = 0, ///< 降序 + AMapCloudSortTypeASC = 1 ///< 升序 +}; + +///附近搜索距离类型 +typedef NS_ENUM(NSInteger, AMapNearbySearchType) +{ + AMapNearbySearchTypeLiner = 0, ///< 直线距离 + AMapNearbySearchTypeDriving = 1, ///< 驾车行驶距离 +}; + +///货车类型 +typedef NS_ENUM(NSInteger, AMapTruckSizeType) +{ + AMapTruckSizeTypeMini = 1, ///< 微型车 + AMapTruckSizeTypeLight = 2, ///< 轻型车 + AMapTruckSizeTypeMedium = 3, ///< 中型车 + AMapTruckSizeTypeHeavy = 4, ///< 重型车 +}; + +///规避道路类型 +typedef NS_ENUM(NSInteger, AMapDrivingRouteExcludeType) +{ + AMapDrivingRouteExcludeTypeNone = 0, ///< 不规避 + AMapDrivingRouteExcludeTypeToll = 1, ///< 收费道路 + AMapDrivingRouteExcludeTypeMotorway = 2, ///< 高速路 + AMapDrivingRouteExcludeTypeFerry = 3, ///< 渡船 +}; + +///规避道路类型 @since 9.2.0 +typedef NS_OPTIONS(NSUInteger, AMapDrivingRouteShowFieldType) +{ + AMapDrivingRouteShowFieldTypeNone = 1 << 0, ///< 不返回扩展信息 + AMapDrivingRouteShowFieldTypeCost = 1 << 1, ///< 返回方案所需时间及费用成本 + AMapDrivingRouteShowFieldTypeTmcs = 1 << 2, ///< 返回分段路况详情 + AMapDrivingRouteShowFieldTypeNavi = 1 << 3, ///< 返回详细导航动作指令 + AMapDrivingRouteShowFieldTypeCities = 1 << 4, ///< 返回分段途径城市信息 + AMapDrivingRouteShowFieldTypePolyline = 1 << 5, ///< 返回分段路坐标点串,两点间用“,”分隔 + AMapDrivingRouteShowFieldTypeNewEnergy = 1 << 6,///< 返回分段路坐标点串,两点间用“,”分隔 + AMapDrivingRouteShowFieldTypeAll = ~0UL, ///< 返回所有扩展信息 +}; + +///步行路线规划返回结果控制 @since 9.4.0 +typedef NS_OPTIONS(NSUInteger, AMapWalkingRouteShowFieldType) +{ + AMapWalkingRouteShowFieldTypeNone = 0 << 0, ///< 不返回扩展信息 + AMapWalkingRouteShowFieldTypeCost = 1 << 0, ///< 返回方案所需时间及费用成本 + AMapWalkingRouteShowFieldTypeNavi = 1 << 1, ///< 返回详细导航动作指令 + AMapWalkingRouteShowFieldTypePolyline = 1 << 2, ///< 返回分路段坐标点串,两点间用“,”分隔 + AMapWalkingRouteShowFieldTypeAll = ~0UL, ///< 返回所有扩展信息 +}; + +///公交车路线规划返回结果控制 @since 9.4.0 +typedef NS_OPTIONS(NSUInteger, AMapTransitRouteShowFieldsType) +{ + AMapTransitRouteShowFieldsTypeNone = 1 << 0, ///< 不返回扩展信息 + AMapTransitRouteShowFieldsTypeCost = 1 << 1, ///< 返回方案所需时间及费用成本 + AMapTransitRouteShowFieldsTypeNavi = 1 << 2, ///< 返回详细导航动作指令 + AMapTransitRouteShowFieldsTypePolyline = 1 << 3, ///< 返回分路段坐标点串,两点间用“,”分隔 + AMapTransitRouteShowFieldsTypeAll = ~0UL, ///< 返回所有扩展信息 +}; + +///骑行路线规划返回结果控制 @since 9.4.0 +typedef NS_OPTIONS(NSUInteger, AMapRidingRouteShowFieldsType) +{ + AMapRidingRouteShowFieldsTypeNone = 1 << 0, ///< 不返回扩展信息 + AMapRidingRouteShowFieldsTypeCost = 1 << 1, ///< 返回方案所需时间及费用成本 + AMapRidingRouteShowFieldsTypeNavi = 1 << 2, ///< 返回详细导航动作指令 + AMapRidingRouteShowFieldsTypePolyline = 1 << 3, ///< 返回分路段坐标点串,两点间用“,”分隔 + AMapRidingRouteShowFieldsTypeAll = ~0UL, ///< 返回所有扩展信息 +}; + +/// 搜索POI返回结果控制 @since 9.4.0 +typedef NS_OPTIONS(NSUInteger, AMapPOISearchShowFieldsType) +{ + AMapPOISearchShowFieldsTypeNone = 1 << 0, ///< 不返回扩展信息 + AMapPOISearchShowFieldsTypeChildren = 1 << 1, ///< 返回子POI信息 + AMapPOISearchShowFieldsTypeBusiness = 1 << 2, ///< 返回商业信息 + AMapPOISearchShowFieldsTypeIndoor = 1 << 3, ///< 返回室内相关信息 + AMapPOISearchShowFieldsTypeNavi = 1 << 4, ///< 返回导航位置相关信息 + AMapPOISearchShowFieldsTypePhotos = 1 << 5, ///< 返回poi图片相关信息 + AMapPOISearchShowFieldsTypeAll = ~0UL, ///< 返回所有扩展信息 +}; + +///距离测量类型 @since 7.7.0 +typedef NS_ENUM(NSInteger, AMapDistanceSearchType) +{ + AMapDistanceSearchTypeStraight = 0, ///< 直线距离 + AMapDistanceSearchTypeDrive = 1, ///< 驾车导航距离 + AMapDistanceSearchTypeWalk = 3, ///< 步行导航距离 +}; + +#pragma mark - AMapPOISearchBaseRequest + +///POI搜索请求基类 +@interface AMapPOISearchBaseRequest : AMapSearchObject +///类型,多个类型用“|”分割 可选值:文本分类、分类代码 +@property (nonatomic, copy) NSString *types; +///排序规则, 0-距离排序;1-综合排序, 默认0 +@property (nonatomic, assign) NSInteger sortrule; +///每页记录数, 范围1-25, [default = 10] +@property (nonatomic, assign) NSInteger offset; +///当前页数, 范围1-100, [default = 1] +@property (nonatomic, assign) NSInteger page; +///设置需要返回的扩展信息,默认为AMapPOISearchShowFieldsTypeNone,只返回基础信息字段 +@property (nonatomic, assign) AMapPOISearchShowFieldsType showFieldsType; +@end + +///POI ID搜索请求 +@interface AMapPOIIDSearchRequest : AMapPOISearchBaseRequest +///poi唯一标识,最多可以传入10个id,多个id之间用“|”分隔。必填 +@property (nonatomic, copy) NSString *uid; +@end + +///POI关键字搜索 +@interface AMapPOIKeywordsSearchRequest : AMapPOISearchBaseRequest +///查询关键字,多个关键字用“|”分割 +@property (nonatomic, copy) NSString *keywords; +///查询城市,可选值:cityname(中文或中文全拼)、citycode、adcode.(注:台湾省的城市一律设置为【台湾】,不具体到市。) +@property (nonatomic, copy) NSString *city; +///强制城市限制功能 默认NO,例如:在上海搜索天安门,如果citylimit为true,将不返回北京的天安门相关的POI +@property (nonatomic, assign) BOOL cityLimit; +///设置后,如果sortrule==0,则返回结果会按照距离此点的远近来排序,since 5.2.1 +@property (nonatomic, strong) AMapGeoPoint *location; + +@end + +///POI周边搜索 +@interface AMapPOIAroundSearchRequest : AMapPOISearchBaseRequest +///查询关键字,多个关键字用“|”分割。可选 +@property (nonatomic, copy) NSString *keywords; +///中心点坐标 +@property (nonatomic, copy) AMapGeoPoint *location; +///查询半径,范围:0-50000,单位:米 [default = 3000] +@property (nonatomic, assign) NSInteger radius; +///查询城市,可选值:cityname(中文或中文全拼)、citycode、adcode。注:当用户指定的经纬度和city出现冲突,若范围内有用户指定city的数据,则返回相关数据,否则返回为空。(since 5.7.0) +@property (nonatomic, copy) NSString *city; +@end + +///POI多边形搜索 +@interface AMapPOIPolygonSearchRequest : AMapPOISearchBaseRequest +///查询关键字,多个关键字用“|”分割。可选 +@property (nonatomic, copy) NSString *keywords; +///多边形区域,多个坐标对集合,坐标对用"|"分割。多边形为矩形时,可传入左上右下两顶点坐标对;其他情况下首尾坐标对需相同。必填 +@property (nonatomic, copy) AMapGeoPolygon *polygon; +@end + +///POI搜索返回 +@interface AMapPOISearchResponse : AMapSearchObject +///返回的POI数目 +@property (nonatomic, assign) NSInteger count; +///关键字建议列表和城市建议列表 +@property (nonatomic, strong) AMapSuggestion *suggestion; +///POI结果,AMapPOI 数组 +@property (nonatomic, strong) NSArray *pois; +@end + +#pragma mark - AMapPOIRouteSearchRequest +///沿途搜索, 注意起点和终点不能相距太远(大概70公里),否则可能搜索结果为空 +@interface AMapRoutePOISearchRequest : AMapSearchObject +///中心点坐标 +@property (nonatomic, copy) AMapGeoPoint *origin; +///目标点坐标 +@property (nonatomic, copy) AMapGeoPoint *destination; +///搜索类型 +@property (nonatomic, assign) AMapRoutePOISearchType searchType; +///驾车导航策略,同驾车路径规划请求的策略(5 多策略除外) +@property (nonatomic, assign) NSInteger strategy; +///道路周围搜索范围,单位米,[0-500],默认250。 +@property (nonatomic, assign) NSInteger range; +///用户自己规划的路线,在origin、destination未填入时为必填.格式为:"经度,纬度;经度,纬度;...". 目前限制个数最多为100个点 +@property (nonatomic, strong) NSString *polylineStr; +///用户自己规划的路线,在origin、destination未填入且polylineStr未填入时为必填. 目前限制个数最多为100个点 +@property (nonatomic, strong) NSArray *polyline; + +@end + +///沿途搜索返回 +@interface AMapRoutePOISearchResponse : AMapSearchObject +///返回的POI数目 +@property (nonatomic, assign) NSInteger count; +///POI结果,AMapRoutePOI 数组 +@property (nonatomic, strong) NSArray *pois; +@end + +#pragma mark - AMapInputTipsSearchRequest + +///搜索提示请求 +@interface AMapInputTipsSearchRequest : AMapSearchObject +///查询关键字 +@property (nonatomic, copy) NSString *keywords; +///查询城市,可选值:cityname(中文或中文全拼)、citycode、adcode. +@property (nonatomic, copy) NSString *city; +///类型,多个类型用“|”分割 可选值:文本分类、分类代码 +@property (nonatomic, copy) NSString *types; +///强制城市限制功能,例如:在上海搜索天安门,如果citylimit为true,将不返回北京的天安门相关的POI +@property (nonatomic, assign) BOOL cityLimit; +///格式形如:@"116.481488,39.990464",(经度,纬度),不可以包含空格。如果设置,在此location附近优先返回搜索关键词信息, since 5.0.0 +@property (nonatomic, copy) NSString *location; +@end + +///搜索提示返回 +@interface AMapInputTipsSearchResponse : AMapSearchObject +///返回数目 +@property (nonatomic, assign) NSInteger count; +///提示列表 AMapTip 数组, AMapTip 有多种属性,可根据该对象的返回信息,配合其他搜索服务使用,完善您应用的功能。如:\n 1)uid为空,location为空,该提示语为品牌词,可根据该品牌词进行POI关键词搜索。\n 2)uid不为空,location为空,为公交线路,根据uid进行公交线路查询。\n 3)uid不为空,location也不为空,是一个真实存在的POI,可直接显示在地图上。 +@property (nonatomic, strong) NSArray *tips; +@end + +#pragma mark - AMapGeocodeSearchRequest + +///地理编码请求 +@interface AMapGeocodeSearchRequest : AMapSearchObject +///地址 +@property (nonatomic, copy) NSString *address; +///查询城市,可选值:cityname(中文或中文全拼)、citycode、adcode. +@property (nonatomic, copy) NSString *city; +///指定查询国家,支持多个国家,用“|”分隔,可选值:国家代码ISO 3166 或 global,仅海外生效(since 7.4.0) +@property (nonatomic, copy) NSString *country; +@end + +///地理编码返回 +@interface AMapGeocodeSearchResponse : AMapSearchObject +///返回数目 +@property (nonatomic, assign) NSInteger count; +///地理编码结果 AMapGeocode 数组 +@property (nonatomic, strong) NSArray *geocodes; +@end + + +#pragma mark - AMapReGeocodeSearchRequest + +///逆地理编码请求 +@interface AMapReGeocodeSearchRequest : AMapSearchObject +///是否返回扩展信息,默认NO。 +@property (nonatomic, assign) BOOL requireExtension; +///中心点坐标。 +@property (nonatomic, copy) AMapGeoPoint *location; +///查询半径,单位米,范围0~3000,默认1000。 +@property (nonatomic, assign) NSInteger radius; +///指定返回结果poi数组中的POI类型,在requireExtension=YES时生效。输入为typecode, 支持传入多个typecode, 多值时用“|”分割 +@property (nonatomic, copy) NSString *poitype; +///distance 按距离返回,score 按权重返回,仅海外生效(since 7.4.0) +@property (nonatomic, copy) NSString *mode; + +@end + +///逆地理编码返回 +@interface AMapReGeocodeSearchResponse : AMapSearchObject +///逆地理编码结果 +@property (nonatomic, strong) AMapReGeocode *regeocode; +@end + +#pragma mark - AMapBusStopSearchRequest + +///公交站点请求 +@interface AMapBusStopSearchRequest : AMapSearchObject +///查询关键字 +@property (nonatomic, copy) NSString *keywords; +///城市 可选值:cityname(中文或中文全拼)、citycode、adcode +@property (nonatomic, copy) NSString *city; +///每页记录数,默认为20,取值为:1-50 +@property (nonatomic, assign) NSInteger offset; +///当前页数,默认值为1,取值为:1-100 +@property (nonatomic, assign) NSInteger page; +@end + +///公交站点返回 +@interface AMapBusStopSearchResponse : AMapSearchObject +///公交站数目 +@property (nonatomic, assign) NSInteger count; +///关键字建议列表和城市建议列表 +@property (nonatomic, strong) AMapSuggestion *suggestion; +///公交站点数组,数组中存放AMapBusStop对象 +@property (nonatomic, strong) NSArray *busstops; +@end + +#pragma mark - AMapBusLineSearchRequest + +///公交线路查询请求基类,不可直接调用 +@interface AMapBusLineBaseSearchRequest : AMapSearchObject +///城市 可选值:cityname(中文或中文全拼)、citycode、adcode +@property (nonatomic, copy) NSString *city; +///是否返回扩展信息,默认为NO +@property (nonatomic, assign) BOOL requireExtension; +///每页记录数,默认为20,取值为1-50 +@property (nonatomic, assign) NSInteger offset; +///当前页数,默认为1,取值为1-100 +@property (nonatomic, assign) NSInteger page; +@end + +///公交站线路根据名字请求 +@interface AMapBusLineNameSearchRequest : AMapBusLineBaseSearchRequest +///查询关键字 +@property (nonatomic, copy) NSString *keywords; +@end + +///公交站线路根据ID请求 +@interface AMapBusLineIDSearchRequest : AMapBusLineBaseSearchRequest +///唯一标识 +@property (nonatomic, copy) NSString *uid; +@end + +///公交站线路返回 +@interface AMapBusLineSearchResponse : AMapSearchObject +///返回公交站数目 +@property (nonatomic, assign) NSInteger count; +///关键字建议列表和城市建议列表 +@property (nonatomic, strong) AMapSuggestion *suggestion; +///公交线路数组,数组中存放 AMapBusLine 对象 +@property (nonatomic, strong) NSArray *buslines; +@end + +#pragma mark - AMapDistrictSearchRequest +///行政区划查询请求 +@interface AMapDistrictSearchRequest : AMapSearchObject +///查询关键字,只支持单关键字搜索,全国范围 +@property (nonatomic, copy) NSString *keywords; +///是否返回边界坐标,默认NO +@property (nonatomic, assign) BOOL requireExtension; +///是否显示商圈信息,默认NO。注:已废弃,行政区划搜索无商圈信息。 +@property (nonatomic, assign) BOOL showBusinessArea __attribute__((deprecated("已废弃, from 5.3.0")));; +///每页记录数, 范围1-50, [default = 20] +@property (nonatomic, assign) NSInteger offset; +///当前页数, 范围1-100, [default = 1] +@property (nonatomic, assign) NSInteger page; +///子区域层级,默认1。规则:设置显示下级行政区级数(行政区级别包括:国家、省/直辖市、市、区/县、乡镇/街道多级数据)可选值:0、1、2、3等数字,0-不返回下级行政区;1-返回下一级行政区;2-返回下两级行政区;3-返回下三级行政区 +@property (nonatomic, assign) NSInteger subdistrict; + +@end + +///行政区划响应 +@interface AMapDistrictSearchResponse : AMapSearchObject +///返回数目 +@property (nonatomic, assign) NSInteger count; +///行政区域 AMapDistrict 数组 +@property (nonatomic, strong) NSArray *districts; +@end + +#pragma mark - AMapRouteSearchBaseRequest + +///路径规划基础类,不可直接调用 +@interface AMapRouteSearchBaseRequest : AMapSearchObject +///出发点 +@property (nonatomic, copy) AMapGeoPoint *origin; +///目的地 +@property (nonatomic, copy) AMapGeoPoint *destination; +@end + +#pragma mark - AMapDrivingCalRouteSearchRequest + +///驾车路径规划2.0 +@interface AMapDrivingCalRouteSearchRequest : AMapRouteSearchBaseRequest +/** + 驾车导航策略,默认策略为32。 + 32:默认,高德推荐,同高德地图APP默认 + 33:躲避拥堵 + 34:高速优先 + 35:不走高速 + 36:少收费 + 37:大路优先 + 38:速度最快 + 39:躲避拥堵+高速优先 + 40:躲避拥堵+不走高速 + 41:躲避拥堵+少收费 + 42:少收费+不走高速 + 43:躲避拥堵+少收费+不走高速 + 44:躲避拥堵+大路优先 + 45:躲避拥堵+速度最快 + */ +@property (nonatomic, assign) NSInteger strategy; +///途经点 AMapGeoPoint 数组,目前最多支持16个途经点 +@property (nonatomic, copy) NSArray *waypoints; +///避让区域 AMapGeoPolygon 数组,目前最多支持32个避让区域,每个区域16个点 +@property (nonatomic, copy) NSArray *avoidpolygons; +///避让道路名 +@property (nonatomic, copy) NSString *avoidroad; +///出发点 POI ID +@property (nonatomic, copy) NSString *originId; +///目的地 POI ID +@property (nonatomic, copy) NSString *destinationId; +///出发点POI类型编码,此值可以辅助更精准的起点算路,0:普通道路、1:高架上、2:高架下、3:主路、4:辅路、5:隧道、7:环岛、9:停车场内部 +@property (nonatomic, copy) NSString *origintype; +///目的地POI类型编码 +@property (nonatomic, copy) NSString *destinationtype; +///车牌信息,如京AHA322,支持6位传统车牌和7位新能源车牌,用于判断是否限行 +@property (nonatomic, copy) NSString *plate; +///使用轮渡,0使用1不使用,默认为0使用 +@property (nonatomic, assign) NSInteger ferry; +/** + 驾车路径规划车辆类型,默认策略为0。 + 0:普通汽车(默认值); + 1:纯电动车; + 2:插电混动车 + */ +@property (nonatomic, assign) NSInteger cartype; +///设置需要返回的扩展信息,默认为AMapDrivingRouteShowFieldTypeNone,只返回基础信息字段 +@property (nonatomic, assign) AMapDrivingRouteShowFieldType showFieldType; + +@end + +#pragma mark - AMapDrivingRouteSearchRequest + +///驾车路径规划 +@interface AMapDrivingRouteSearchRequest : AMapRouteSearchBaseRequest + +/** + 驾车导航策略,默认策略为0。 + 0,速度优先(时间);1,费用优先(不走收费路段的最快道路);2,距离优先;3,不走快速路;4,躲避拥堵; + 5,多策略(同时使用速度优先、费用优先、距离优先三个策略计算路径),其中必须说明,就算使用三个策略算路,会根据路况不固定的返回一至三条路径规划信息; + 6,不走高速;7,不走高速且避免收费;8,躲避收费和拥堵;9,不走高速且躲避收费和拥堵; + 10,多备选,时间最短,距离最短,躲避拥堵(考虑路况); + 11,多备选,时间最短,距离最短; + 12,多备选,躲避拥堵(考虑路况); + 13,多备选,不走高速; + 14,多备选,费用优先; + 15,多备选,躲避拥堵,不走高速(考虑路况); + 16,多备选,费用有限,不走高速; + 17,多备选,躲避拥堵,费用优先(考虑路况); + 18,多备选,躲避拥堵,不走高速,费用优先(考虑路况); + 19,多备选,高速优先; + 20,多备选,高速优先,躲避拥堵(考虑路况) + */ +@property (nonatomic, assign) NSInteger strategy; +///途经点 AMapGeoPoint 数组,目前最多支持6个途经点 +@property (nonatomic, copy) NSArray *waypoints; +///避让区域 AMapGeoPolygon 数组,目前最多支持100个避让区域,每个区域16个点 +@property (nonatomic, copy) NSArray *avoidpolygons; +///避让道路名 +@property (nonatomic, copy) NSString *avoidroad; +///出发点 POI ID +@property (nonatomic, copy) NSString *originId; +///目的地 POI ID +@property (nonatomic, copy) NSString *destinationId; +///出发点POI类型编码 +@property (nonatomic, copy) NSString *origintype; +///目的地POI类型编码 +@property (nonatomic, copy) NSString *destinationtype; +///是否返回扩展信息,默认为 NO +@property (nonatomic, assign) BOOL requireExtension; +///车牌省份,用汉字填入车牌省份缩写。用于判断是否限行 +@property (nonatomic, copy) NSString *plateProvince; +///车牌详情,填入除省份及标点之外的字母和数字(需大写)。用于判断是否限行。 +@property (nonatomic, copy) NSString *plateNumber; +///使用轮渡,0使用1不使用,默认为0使用 +@property (nonatomic, assign) NSInteger ferry; +/** + 驾车路径规划车辆类型,默认策略为0。 + 0:普通汽车(默认值); + 1:纯电动车; + 2:插电混动车 + */ +@property (nonatomic, assign) NSInteger cartype; +///规避道路类型,默认为AMapDrivingRouteExcludeTypeNone,仅海外生效 +@property (nonatomic, assign) AMapDrivingRouteExcludeType exclude; + +@end + +#pragma mark - AMapWalkingRouteSearchRequest + +///步行路径规划 +@interface AMapWalkingRouteSearchRequest : AMapRouteSearchBaseRequest +///是否提供备选步行方案([default = 0])0-只提供一条步行方案; 1-提供备选步行方案(有可能无备选方案) +@property (nonatomic, assign) NSInteger multipath __attribute__((deprecated("已废弃, from 5.0.0"))); + +///是否需要室内算路. 0 : 不需要 (默认值); 1 : 需要 +@property (nonatomic, assign) NSInteger isindoor; +///返回路线条数, 0: 默认返回一条路线方案; 1 : 多备选路线中第一条路线; 2 : 多备选路线中前两条路线; 3 : 多备选路线中三条路线 +@property (nonatomic, assign) NSInteger alternativeRoute; +///设置需要返回的扩展信息,默认为AMapPOISearchShowFieldsTypeNone,只返回基础信息字段 +@property (nonatomic, assign) AMapWalkingRouteShowFieldType showFieldsType; +@end + +#pragma mark - AMapTransitRouteSearchRequest + +///公交路径规划 +@interface AMapTransitRouteSearchRequest : AMapRouteSearchBaseRequest +///公交换乘策略([default = 0]) +/* +0:推荐模式,综合权重,同高德APP默认 +1:最经济模式,票价最低 +2:最少换乘模式,换乘次数少 +3:最少步行模式,尽可能减少步行距离 +4:最舒适模式,尽可能乘坐空调车 +5:不乘地铁模式,不乘坐地铁路线 +6:地铁图模式,起终点都是地铁站(地铁图模式下originpoi及destinationpoi为必填项) +7:地铁优先模式,步行距离不超过4KM +8:时间短模式,方案花费总时间最少 + */ +@property (nonatomic, assign) NSInteger strategy; +///起点所在城市, 必填. 仅支持citycode +@property (nonatomic, copy) NSString *city; +///目的地所在城市, 必填. 仅支持citycode,与city相同时代表同城,不同时代表跨城 +@property (nonatomic, copy) NSString *destinationCity; +///是否包含夜班车,默认为 NO +@property (nonatomic, assign) BOOL nightflag; +///起点POI ID +@property (nonatomic, copy) NSString *originPOI; +///目的地POI ID +@property (nonatomic, copy) NSString *destinationPOI; +///起点所在行政区域编码 +@property (nonatomic, copy) NSString *adcode; +///终点所在行政区域编码 +@property (nonatomic, copy) NSString *destinationAdcode; +///返回方案条数 可传入1-10的阿拉伯数字,代表返回的不同条数。默认值:5 +@property (nonatomic, assign) NSInteger alternativeRoute; +/// 是否返回所有地铁出入口,默认为NO +@property (nonatomic, assign) BOOL multiExport; +/// 最大换乘次数 0:直达 1:最多换乘1次 2:最多换乘2次 3:最多换乘3次 4:最多换乘4次。默认值:4 +@property (nonatomic, assign) NSInteger maxTrans; +///请求日期 例如:2013-10-28 +@property (nonatomic, copy) NSString *date; +///请求时间 例如:9-54 +@property (nonatomic, copy) NSString *time; +///返回结果控制 +@property (nonatomic, assign) AMapTransitRouteShowFieldsType showFieldsType; +@end + +#pragma mark - AMapRidingRouteSearchRequest + +///骑行路径规划 +@interface AMapRidingRouteSearchRequest : AMapRouteSearchBaseRequest +///返回路线条数, 0: 默认返回一条路线方案; 1 : 多备选路线中第一条路线; 2 : 多备选路线中前两条路线; 3 : 多备选路线中三条路线 +@property (nonatomic, assign) NSInteger alternativeRoute; +///返回结果控制 +@property (nonatomic, assign) AMapRidingRouteShowFieldsType showFieldsType; +@end + +///路径规划返回 +@interface AMapRouteSearchResponse : AMapSearchObject +///路径规划信息数目 +@property (nonatomic, assign) NSInteger count; +///路径规划信息 +@property (nonatomic, strong) AMapRoute *route; +@end + +///骑行路径规划返回 +@interface AMapRidingRouteSearchResponse : AMapRouteSearchResponse +@end + +#pragma mark - AMapTruckRouteSearchRequest + +///货车路径规划(since 6.1.0) +@interface AMapTruckRouteSearchRequest : AMapRouteSearchBaseRequest + +/** + 驾车导航策略,默认为策略1。 + 1,返回的结果考虑路况,尽量躲避拥堵而规划路径,与高德地图的“躲避拥堵”策略一致; + 2,返回的结果不走高速,与高德地图“不走高速”策略一致; + 3,返回的结果尽可能规划收费较低甚至免费的路径,与高德地图“避免收费”策略一致; + 4,返回的结果考虑路况,尽量躲避拥堵而规划路径,并且不走高速,与高德地图的“躲避拥堵&不走高速”策略一致; + 5,返回的结果尽量不走高速,并且尽量规划收费较低甚至免费的路径结果,与高德地图的“避免收费&不走高速”策略一致; + 6,返回路径规划结果会尽量的躲避拥堵,并且规划收费较低甚至免费的路径结果,与高德地图的“躲避拥堵&避免收费”策略一致; + 7,返回的结果尽量躲避拥堵,规划收费较低甚至免费的路径结果,并且尽量不走高速路,与高德地图的“避免拥堵&避免收费&不走高速”策略一致; + 8,返回的结果会优先选择高速路,与高德地图的“高速优先”策略一致; + 9,返回的结果会优先考虑高速路,并且会考虑路况躲避拥堵,与高德地图的“躲避拥堵&高速优先”策略一致。 + */ +@property (nonatomic, assign) NSInteger strategy; +///途经点 AMapGeoPoint 数组,最多支持16个途经点 +@property (nonatomic, copy) NSArray *waypoints; +///出发点 POI ID +@property (nonatomic, copy) NSString *originId; +///目的地 POI ID +@property (nonatomic, copy) NSString *destinationId; +///出发点POI类型编码 +@property (nonatomic, copy) NSString *origintype; +///目的地POI类型编码 +@property (nonatomic, copy) NSString *destinationtype; +///车牌省份,用汉字填入车牌省份缩写。用于判断是否限行 +@property (nonatomic, copy) NSString *plateProvince; +///车牌详情,填入除省份及标点之外的字母和数字(需大写)。用于判断是否限行。 +@property (nonatomic, copy) NSString *plateNumber; +///货车大小,默认为 轻型车(AMapTruckSizeTypeLight) +@property (nonatomic, assign) AMapTruckSizeType size; +///车辆高度,单位米,取值[0 – 25.5]米,默认 1.6 米 +@property (nonatomic, assign) CGFloat height; +///车辆宽度,单位米,取值[0 – 25.5]米,默认 2.5 米 +@property (nonatomic, assign) CGFloat width; +///车辆总重,单位吨,取值[0 – 6553.5]吨,默认 0.9 吨 +@property (nonatomic, assign) CGFloat load; +///货车核定载重,单位吨,取值[0 – 6553.5]吨,默认 10 吨 +@property (nonatomic, assign) CGFloat weight; +///车辆轴数,单位个,取值[0 –255]个,默认 2个轴 +@property (nonatomic, assign) NSInteger axis; +///是否返回扩展信息,默认为 NO (since 7.6.0) +@property (nonatomic, assign) BOOL requireExtension; +@end + +#pragma mark - AMapDistanceSearchRequest + +///距离查询请求(since 6.1.0) +@interface AMapDistanceSearchRequest : AMapSearchObject +///起点坐标数组,最多支持100个点。 +@property (nonatomic, strong) NSArray *origins; +///终点坐标 +@property (nonatomic, strong) AMapGeoPoint *destination; +///路径计算的类型,当type为导航距离时,会考虑路况,故在不同时间请求返回结果可能不同; +@property (nonatomic, assign) AMapDistanceSearchType type; +///驾车距离测量策略,参考驾车路径规划。仅当type为AMapDistanceSearchTypeDrive时有效,默认4 +@property (nonatomic, assign) NSInteger strategy; +///是否返回扩展信息,默认为 NO (since 7.6.0) +@property (nonatomic, assign) BOOL requireExtension; +@end + +///距离查询结果(since 6.1.0) +@interface AMapDistanceSearchResponse : AMapSearchObject +///距离查询结果 AMapDistanceResult 数组。 +@property (nonatomic, strong) NSArray *results; + +@end + +#pragma mark - AMapWeatherSearchRequest + +///天气查询请求 +@interface AMapWeatherSearchRequest : AMapSearchObject +///城市名称,支持cityname及adcode +@property (nonatomic, copy) NSString *city; +///气象类型,Live为实时天气,Forecast为后三天预报天气,默认为Live +@property (nonatomic, assign) AMapWeatherType type; +@end + +///天气查询返回 +@interface AMapWeatherSearchResponse : AMapSearchObject +///实时天气数据信息 AMapLocalWeatherLive 数组,仅在请求实时天气时有返回。 +@property (nonatomic, strong) NSArray *lives; +///预报天气数据信息 AMapLocalWeatherForecast 数组,仅在请求预报天气时有返回 +@property (nonatomic, strong) NSArray *forecasts; + +@end + +#pragma mark - AMapNearbySearchRequest +///附近搜索请求 +@interface AMapNearbySearchRequest : AMapSearchObject +///中心点坐标 +@property (nonatomic, copy) AMapGeoPoint *center; +///查询半径,范围:[0, 10000],单位:米 [default = 1000] +@property (nonatomic, assign) NSInteger radius; +///搜索距离类型,默认为直线距离 +@property (nonatomic, assign) AMapNearbySearchType searchType; +///检索时间范围,超过24小时的数据无法返回,范围[5, 24*60*60] 单位:秒 [default = 1800] +@property (nonatomic, assign) NSInteger timeRange; +///返回条数,范围[1, 100], 默认30 +@property (nonatomic, assign) NSInteger limit; +@end + +///附近搜索返回 +@interface AMapNearbySearchResponse : AMapSearchObject +///结果总条数 +@property (nonatomic, assign) NSInteger count; +///周边用户信息 AMapNearbyUserInfo 数组 +@property (nonatomic, strong) NSArray *infos; +@end + +#pragma mark - AMapCloudSearchBaseRequest + +///企业地图搜索请求基类 +@interface AMapCloudSearchBaseRequest : AMapSearchObject +///要查询的表格ID, 必选 +@property (nonatomic, copy) NSString *tableID; +///筛选条件数组, 可选, 说明:\n 1.支持建立索引的字段根据多个条件筛选,多个条件用双&符号连接;\n 2.判断符合支持:>= 大于等于,<= 小于等于,>大于,<小于,= 精确匹配(text索引不可用);\n 3.示例规则:key1=value1&&key2=value2&&lastloctime>=1469817532,示例:"name=王师傅|张师傅&&lastloctime>=1469817532 +@property (nonatomic, strong) NSArray *filter; +///排序字段名, 可选.\n 说明:\n 1.支持按建立了排序筛选索引的整数或小数字段进行排序:sortFields = "字段名";\n 2.系统预设的字段(忽略sortType):_distance:坐标与中心点距离升序排序,仅在周边检索时有效(若其它请求使用会异常返回);_weight:权重降序排序,当存在keywords时有效;\n; +@property (nonatomic, copy) NSString *sortFields; +///可选, 排序方式(默认升序) +@property (nonatomic, assign) AMapCloudSortType sortType; +///可选, 每页记录数(每页最大记录数100, 默认20) +@property (nonatomic, assign) NSInteger offset; +///可选, 当前页数(>=1, 默认1) +@property (nonatomic, assign) NSInteger page; +@end + +#pragma mark - AMapCloudPlaceAroundSearchRequest + +///企业地图周边搜请求 +@interface AMapCloudPOIAroundSearchRequest : AMapCloudSearchBaseRequest +///必填,中心点坐标。 +@property (nonatomic, copy) AMapGeoPoint *center; +///可选,查询半径(默认值为3000),单位:米 +@property (nonatomic, assign) NSInteger radius; +///可选,搜索关键词。\n 说明:1. 只支持建立过文本索引的字段查询/n 2.支持关键字模糊检索,即对建立【文本索引字段】对应列内容进行模糊检索;如keywords=工商银行,检索返回已建立文本索引列值中包含“工商”或者“银行”或者“工商银行”关键字的POI结果集。/n 3.支持关键字多值模糊检索;如keywords=招商银行&&华夏银行&&工商银行,检索返回已建立索引列值中包含“招商银行”或者“华夏银行”或者“工商银行”的POI结果集,不会返回检索词切分后,如仅包含“招商”或者“银行”的POI集 +@property (nonatomic, copy) NSString *keywords; +@end + +///企业地图polygon区域查询请求 +@interface AMapCloudPOIPolygonSearchRequest : AMapCloudSearchBaseRequest +///必填,多边形。 +@property (nonatomic, copy) AMapGeoPolygon *polygon; +///可选,搜索关键词。\n 说明:1. 只支持建立过文本索引的字段查询/n 2.支持关键字模糊检索,即对建立【文本索引字段】对应列内容进行模糊检索;如keywords=工商银行,检索返回已建立文本索引列值中包含“工商”或者“银行”或者“工商银行”关键字的POI结果集。/n 3.支持关键字多值模糊检索;如keywords=招商银行&&华夏银行&&工商银行,检索返回已建立索引列值中包含“招商银行”或者“华夏银行”或者“工商银行”的POI结果集,不会返回检索词切分后,如仅包含“招商”或者“银行”的POI集 +@property (nonatomic, copy) NSString *keywords; +@end + +///企业地图ID查询请求 +@interface AMapCloudPOIIDSearchRequest : AMapCloudSearchBaseRequest +///必填,POI的ID +@property (nonatomic, assign) NSInteger uid; +@end + +///企业地图本地查询请求 +@interface AMapCloudPOILocalSearchRequest : AMapCloudSearchBaseRequest +///可选,搜索关键词。\n 说明:1. 只支持建立过文本索引的字段查询/n 2.支持关键字模糊检索,即对建立【文本索引字段】对应列内容进行模糊检索;如keywords=工商银行,检索返回已建立文本索引列值中包含“工商”或者“银行”或者“工商银行”关键字的POI结果集。/n 3.支持关键字多值模糊检索;如keywords=招商银行&&华夏银行&&工商银行,检索返回已建立索引列值中包含“招商银行”或者“华夏银行”或者“工商银行”的POI结果集,不会返回检索词切分后,如仅包含“招商”或者“银行”的POI集 +@property (nonatomic, copy) NSString *keywords; +///必填,城市名称\n 说明:\n 1. 支持全国/省/市/区县行政区划范围的检索;\n 2. city = "全国",即对用户全表搜索;\n 3. 当city值设置非法或不正确时,按照 city = "全国"返回。 +@property (nonatomic, copy) NSString *city; +@end + +///企业地图搜索返回 +@interface AMapCloudPOISearchResponse : AMapSearchObject +///返回结果总数目 +@property (nonatomic, assign) NSInteger count; +///返回的结果, AMapCloudPOI 数组 +@property (nonatomic, strong) NSArray *POIs; + +@end + +#pragma mark - AMapShareSearchBaseRequest + +///短串分享搜索请求基类, 请使用具体的子类。 +@interface AMapShareSearchBaseRequest : AMapSearchObject +@end + +///位置短串分享请求 +@interface AMapLocationShareSearchRequest : AMapShareSearchBaseRequest +///必填, 位置坐标 +@property (nonatomic, copy) AMapGeoPoint *location; +///位置名称,请不要包含【,%&@#】等特殊符号 +@property (nonatomic, copy) NSString *name; +@end + +///兴趣点短串分享请求 +@interface AMapPOIShareSearchRequest : AMapShareSearchBaseRequest +///POI的ID,如果有ID则指定POI,否则按name查询。 +@property (nonatomic, copy) NSString *uid; +///坐标 +@property (nonatomic, copy) AMapGeoPoint *location; +///名称,请不要包含【,%&@#】等特殊符号。 +@property (nonatomic, copy) NSString *name; +///地址,请不要包含【,%&@#】等特殊符号。 +@property (nonatomic, copy) NSString *address; +@end + +///路径规划短串分享请求 +@interface AMapRouteShareSearchRequest : AMapShareSearchBaseRequest +///默认为0\n 驾车:0-速度最快(时间);\n 1-避免收费(不走收费路段的最快道路);\n 2-距离优先;\n 3-不走高速;\n 4-结合实时交通(躲避拥堵);\n 5-不走高速且避免收费;\n 6-不走高速且躲避拥堵;\n 7-躲避收费和拥堵;\n 8-不走高速且躲避收费和拥堵\n\n 公交:0-最快捷;\n 1-最经济;\n 2-最少换乘;\n 3-最少步行;\n 4-最舒适;\n 5-不乘地铁;\n\n 步行,无策略,均一样 +@property (nonatomic, assign) NSInteger strategy; +///Route的type,默认为0,超出范围为0.\n 0为驾车,\n 1为公交,\n 2为步行 +@property (nonatomic, assign) NSInteger type; +///起点坐标 +@property (nonatomic, copy) AMapGeoPoint *startCoordinate; +///终点坐标 +@property (nonatomic, copy) AMapGeoPoint *destinationCoordinate; +///起点名称,默认为“已选择的位置”,请不要包含【,%&@#】等特殊符号 +@property (nonatomic, copy) NSString *startName; +///终点名称,默认为“已选择的位置”,请不要包含【,%&@#】等特殊符号 +@property (nonatomic, copy) NSString *destinationName; +@end + +///导航短串分享请求 +@interface AMapNavigationShareSearchRequest : AMapShareSearchBaseRequest + +///默认为0,超出范围为0\n 驾车:0-速度最快(时间);\n 1-避免收费(不走收费路段的最快道路);\n 2-距离优先;\n 3-不走高速;\n 4-结合实时交通(躲避拥堵);\n 5-不走高速且避免收费;\n 6-不走高速且躲避拥堵;\n 7-躲避收费和拥堵;\n 8-不走高速且躲避收费和拥堵 +@property (nonatomic, assign) NSInteger strategy; +///起点坐标,若跳转到高德地图,默认更换为定位坐标 +@property (nonatomic, copy) AMapGeoPoint *startCoordinate; +///终点坐标 +@property (nonatomic, copy) AMapGeoPoint *destinationCoordinate; +@end + +///导航短串分享响应 +@interface AMapShareSearchResponse : AMapSearchObject +///转换后的短串 +@property (nonatomic, copy) NSString *shareURL; +@end + +///未来路线规划(since 6.9.0) +@interface AMapFutureRouteSearchRequest : AMapRouteSearchBaseRequest +///出发时间 单位为秒 +@property (nonatomic, copy) NSString *beginTime; +///时间间隔 单位为秒 +@property (nonatomic, assign) NSInteger interval; +///时间点个数,最多48个 +@property (nonatomic, assign) NSInteger timeCount; +/** + 未来路线规划策略,默认策略为0。 + 1,返回的结果考虑路况,尽量躲避拥堵而规划路径,与高德地图的“躲避拥堵”策略一致 + 2,返回的结果不走高速,与高德地图“不走高速”策略一致 + 3,返回的结果尽可能规划收费较低甚至免费的路径,与高德地图“避免收费”策略一致 + 4,返回的结果考虑路况,尽量躲避拥堵而规划路径,并且不走高速,与高德地图的“躲避拥堵&不走高速”策略一致 + 5,返回的结果尽量不走高速,并且尽量规划收费较低甚至免费的路径结果,与高德地图的“避免收费&不走高速”策略一致 + 6,返回路径规划结果会尽量的躲避拥堵,并且规划收费较低甚至免费的路径结果,与高德地图的“躲避拥堵&避免收费”策略一致 + 7,返回的结果尽量躲避拥堵,规划收费较低甚至免费的路径结果,并且尽量不走高速路,与高德地图的“避免拥堵&避免收费&不走高速”策略一致 + 8,返回的结果会优先选择高速路,与高德地图的“高速优先”策略一致 + 9,返回的结果会优先考虑高速路,并且会考虑路况躲避拥堵,与高德地图的“躲避拥堵&高速优先”策略一致 + 10,不考虑路况,返回速度最优、耗时最短的路线,但是此路线不一定距离最短 + 11,避让拥堵&速度优先&避免收费 + */ +@property (nonatomic, assign) NSInteger strategy; +///出发点 POI ID +@property (nonatomic, copy) NSString *originId; +///目的地 POI ID +@property (nonatomic, copy) NSString *destinationId; +///出发点POI类型编码 +@property (nonatomic, copy) NSString *origintype; +///目的地POI类型编码 +@property (nonatomic, copy) NSString *destinationtype; +///终点的父POI ID +@property (nonatomic, copy) NSString *parentId; + +/////是否返回扩展信息,默认为 NO +//@property (nonatomic, assign) BOOL requireExtension; +///车牌省份,用汉字填入车牌省份缩写。用于判断是否限行 +@property (nonatomic, copy) NSString *plateProvince; +///车牌详情,填入除省份及标点之外的字母和数字(需大写)。用于判断是否限行。 +@property (nonatomic, copy) NSString *plateNumber; +/** + 驾车路径规划车辆类型,默认策略为0。 + 0:普通汽车(默认值); + 1:纯电动车; + 2:插电混动车 + */ +@property (nonatomic, assign) NSInteger cartype; +@end + +///未来路线规划(since 6.9.0) +@interface AMapFutureRouteSearchResponse : AMapSearchObject +///路径规划方案,只会返回AMapPath中的distance、totalTrafficLights、steps +@property (nonatomic, strong) NSArray *paths; +///不同时间的规划以及信息列表 +@property (nonatomic, strong) NSArray *timeInfos; +@end + +/// 充电站搜索(since 9.7.0) +@interface AMapChargeStationSearchRequest : AMapSearchObject +@end + +/// 充电站搜索(since 9.7.0) +@interface AMapChargeStationSearchResponse : AMapSearchObject +@end diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchObjV1.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchObjV1.h new file mode 100644 index 0000000..c4beac3 --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchObjV1.h @@ -0,0 +1,84 @@ +// +// AMapSearchObjV1.h +// AMapSearchKit +// +// Created by xiaoming han on 15/7/22. +// Copyright (c) 2015年 Amap. All rights reserved. +// + +/* 该文件定义了搜索请求和返回对象。*/ + +#import +#import "AMapCommonObj.h" +#import "AMapSearchObj.h" +#pragma mark - AMapPOISearchBaseRequestV1 + +///POI搜索请求基类 +@interface AMapPOISearchBaseRequestV1 : AMapSearchObject +///类型,多个类型用“|”分割 可选值:文本分类、分类代码 +@property (nonatomic, copy) NSString *types; +///排序规则, 0-距离排序;1-综合排序, 默认0 +@property (nonatomic, assign) NSInteger sortrule; +///每页记录数, 范围1-25, [default = 20] +@property (nonatomic, assign) NSInteger offset; +///当前页数, 范围1-100, [default = 1] +@property (nonatomic, assign) NSInteger page; +///建筑物POI编号,传入建筑物POI之后,则只在该建筑物之内进行搜索(since 4.5.0) +@property (nonatomic, copy) NSString *building; +///是否返回扩展信息,默认为 NO。 +@property (nonatomic, assign) BOOL requireExtension; +///是否返回子POI,默认为 NO。 +@property (nonatomic, assign) BOOL requireSubPOIs; +@end + +///POI ID搜索请求 +@interface AMapPOIIDSearchRequestV1 : AMapPOISearchBaseRequestV1 +///POI全局唯一ID +@property (nonatomic, copy) NSString *uid; +@end + +///POI关键字搜索 +@interface AMapPOIKeywordsSearchRequestV1 : AMapPOISearchBaseRequestV1 +///查询关键字,多个关键字用“|”分割 +@property (nonatomic, copy) NSString *keywords; +///查询城市,可选值:cityname(中文或中文全拼)、citycode、adcode.(注:台湾省的城市一律设置为【台湾】,不具体到市。) +@property (nonatomic, copy) NSString *city; +///强制城市限制功能 默认NO,例如:在上海搜索天安门,如果citylimit为true,将不返回北京的天安门相关的POI +@property (nonatomic, assign) BOOL cityLimit; +///设置后,如果sortrule==0,则返回结果会按照距离此点的远近来排序,since 5.2.1 +@property (nonatomic, strong) AMapGeoPoint *location; + +@end + +///POI周边搜索 +@interface AMapPOIAroundSearchRequestV1 : AMapPOISearchBaseRequestV1 +///查询关键字,多个关键字用“|”分割 +@property (nonatomic, copy) NSString *keywords; +///中心点坐标 +@property (nonatomic, copy) AMapGeoPoint *location; +///查询半径,范围:0-50000,单位:米 [default = 1500] +@property (nonatomic, assign) NSInteger radius; +///查询城市,可选值:cityname(中文或中文全拼)、citycode、adcode。注:当用户指定的经纬度和city出现冲突,若范围内有用户指定city的数据,则返回相关数据,否则返回为空。(since 5.7.0) +@property (nonatomic, copy) NSString *city; +///是否对结果进行人工干预,如火车站,原因为poi较为特殊,结果存在人工干预,干预结果优先,所以距离优先的排序未生效,默认为YES (since 7.4.0) +@property (nonatomic, assign) BOOL special; + +@end + +///POI多边形搜索 +@interface AMapPOIPolygonSearchRequestV1 : AMapPOISearchBaseRequestV1 +///查询关键字,多个关键字用“|”分割 +@property (nonatomic, copy) NSString *keywords; +///多边形 +@property (nonatomic, copy) AMapGeoPolygon *polygon; +@end + +///POI搜索返回 +@interface AMapPOISearchResponseV1 : AMapSearchObject +///返回的POI数目 +@property (nonatomic, assign) NSInteger count; +///关键字建议列表和城市建议列表 +@property (nonatomic, strong) AMapSuggestion *suggestion; +///POI结果,AMapPOI 数组 +@property (nonatomic, strong) NSArray *pois; +@end diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchVersion.h b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchVersion.h new file mode 100644 index 0000000..bf9ae4b --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Headers/AMapSearchVersion.h @@ -0,0 +1,26 @@ +// +// AMapSearchVersion.h +// AMapSearchKit +// +// Created by xiaoming han on 15/10/27. +// Copyright © 2015年 Amap. All rights reserved. +// + +#import +#import + +#ifndef AMapSearchVersion_h +#define AMapSearchVersion_h + +#define AMapSearchVersionNumber 90705 +#define AMapSearchMinRequiredFoundationVersion 10800 + +// 依赖库版本检测 +#if AMapFoundationVersionNumber < AMapSearchMinRequiredFoundationVersion +#error "The AMapFoundationKit version is less than minimum required, please update! Any questions please to visit http://lbs.amap.com" +#endif + +FOUNDATION_EXTERN NSString * const AMapSearchVersion; +FOUNDATION_EXTERN NSString * const AMapSearchName; + +#endif /* AMapSearchVersion_h */ diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Info.plist b/Pods/AMapSearch/AMapSearchKit.framework/Info.plist new file mode 100644 index 0000000..a88bd9e Binary files /dev/null and b/Pods/AMapSearch/AMapSearchKit.framework/Info.plist differ diff --git a/Pods/AMapSearch/AMapSearchKit.framework/Modules/module.modulemap b/Pods/AMapSearch/AMapSearchKit.framework/Modules/module.modulemap new file mode 100644 index 0000000..6917330 --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module AMapSearchKit { + umbrella header "AMapSearchKit.h" + export * + + module * { export * } +} diff --git a/Pods/AMapSearch/AMapSearchKit.framework/version.txt b/Pods/AMapSearch/AMapSearchKit.framework/version.txt new file mode 100644 index 0000000..7bbce3c --- /dev/null +++ b/Pods/AMapSearch/AMapSearchKit.framework/version.txt @@ -0,0 +1 @@ +9.7.5+sea.24115ca diff --git a/Pods/Alamofire/LICENSE b/Pods/Alamofire/LICENSE new file mode 100644 index 0000000..cae030a --- /dev/null +++ b/Pods/Alamofire/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2022 Alamofire Software Foundation (http://alamofire.org/) + +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. diff --git a/Pods/Alamofire/README.md b/Pods/Alamofire/README.md new file mode 100644 index 0000000..72ece56 --- /dev/null +++ b/Pods/Alamofire/README.md @@ -0,0 +1,269 @@ +![Alamofire: Elegant Networking in Swift](https://raw.githubusercontent.com/Alamofire/Alamofire/master/Resources/AlamofireLogo.png) + +[![Swift](https://img.shields.io/badge/Swift-5.9_5.10_6.0-orange?style=flat-square)](https://img.shields.io/badge/Swift-5.9_5.10_6.0-Orange?style=flat-square) +[![Platforms](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_visionOS_Linux_Windows_Android-yellowgreen?style=flat-square)](https://img.shields.io/badge/Platforms-macOS_iOS_tvOS_watchOS_vision_OS_Linux_Windows_Android-Green?style=flat-square) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Alamofire.svg?style=flat-square)](https://img.shields.io/cocoapods/v/Alamofire.svg) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat-square)](https://github.com/Carthage/Carthage) +[![Swift Package Manager](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square)](https://img.shields.io/badge/Swift_Package_Manager-compatible-orange?style=flat-square) +[![Swift Forums](https://img.shields.io/badge/Swift_Forums-Alamofire-orange?style=flat-square)](https://forums.swift.org/c/related-projects/alamofire/37) + +Alamofire is an HTTP networking library written in Swift. + +- [Features](#features) +- [Component Libraries](#component-libraries) +- [Requirements](#requirements) +- [Migration Guides](#migration-guides) +- [Communication](#communication) +- [Installation](#installation) +- [Contributing](#contributing) +- [Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#using-alamofire) + - [**Introduction -**](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#introduction) [Making Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#making-requests), [Response Handling](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-handling), [Response Validation](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-validation), [Response Caching](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#response-caching) + - **HTTP -** [HTTP Methods](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-methods), [Parameters and Parameter Encoder](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md##request-parameters-and-parameter-encoders), [HTTP Headers](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#http-headers), [Authentication](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#authentication) + - **Large Data -** [Downloading Data to a File](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#downloading-data-to-a-file), [Uploading Data to a Server](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#uploading-data-to-a-server) + - **Tools -** [Statistical Metrics](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#statistical-metrics), [cURL Command Output](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md#curl-command-output) +- [Advanced Usage](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md) + - **URL Session -** [Session Manager](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#session), [Session Delegate](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#sessiondelegate), [Request](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#request) + - **Routing -** [Routing Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#routing-requests), [Adapting and Retrying Requests](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests-with-requestinterceptor) + - **Model Objects -** [Custom Response Handlers](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#customizing-response-handlers) + - **Advanced Concurrency -** [Swift Concurrency](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#using-alamofire-with-swift-concurrency) and [Combine](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#using-alamofire-with-combine) + - **Connection -** [Security](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#security), [Network Reachability](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#network-reachability) +- [Open Radars](#open-radars) +- [FAQ](#faq) +- [Credits](#credits) +- [Donations](#donations) +- [License](#license) + +## Features + +- [x] Chainable Request / Response Methods +- [x] Swift Concurrency Support Back to iOS 13, macOS 10.15, tvOS 13, and watchOS 6. +- [x] Combine Support +- [x] URL / JSON Parameter Encoding +- [x] Upload File / Data / Stream / MultipartFormData +- [x] Download File using Request or Resume Data +- [x] Authentication with `URLCredential` +- [x] HTTP Response Validation +- [x] Upload and Download Progress Closures with Progress +- [x] cURL Command Output +- [x] Dynamically Adapt and Retry Requests +- [x] TLS Certificate and Public Key Pinning +- [x] Network Reachability +- [x] Comprehensive Unit and Integration Test Coverage +- [x] [Complete Documentation](https://alamofire.github.io/Alamofire) + +## Write Requests Fast! + +Alamofire's compact syntax and extensive feature set allow requests with powerful features like automatic retry to be written in just a few lines of code. + +```swift +// Automatic String to URL conversion, Swift concurrency support, and automatic retry. +let response = await AF.request("https://httpbin.org/get", interceptor: .retryPolicy) + // Automatic HTTP Basic Auth. + .authenticate(username: "user", password: "pass") + // Caching customization. + .cacheResponse(using: .cache) + // Redirect customization. + .redirect(using: .follow) + // Validate response code and Content-Type. + .validate() + // Produce a cURL command for the request. + .cURLDescription { description in + print(description) + } + // Automatic Decodable support with background parsing. + .serializingDecodable(DecodableType.self) + // Await the full response with metrics and a parsed body. + .response +// Detailed response description for easy debugging. +debugPrint(response) +``` + +## Component Libraries + +In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the [Alamofire Software Foundation](https://github.com/Alamofire/Foundation) to bring additional functionality to the Alamofire ecosystem. + +- [AlamofireImage](https://github.com/Alamofire/AlamofireImage) - An image library including image response serializers, `UIImage` and `UIImageView` extensions, custom image filters, an auto-purging in-memory cache, and a priority-based image downloading system. +- [AlamofireNetworkActivityIndicator](https://github.com/Alamofire/AlamofireNetworkActivityIndicator) - Controls the visibility of the network activity indicator on iOS using Alamofire. It contains configurable delay timers to help mitigate flicker and can support `URLSession` instances not managed by Alamofire. + +## Requirements + +| Platform | Minimum Swift Version | Installation | Status | +| ---------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ | 5.9 / Xcode 15.0 | [CocoaPods](#cocoapods), [Carthage](#carthage), [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested | +| Linux | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported | +| Windows | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported | +| Android | Latest Only | [Swift Package Manager](#swift-package-manager) | Building But Unsupported | + +#### Known Issues on Linux and Windows + +Alamofire builds on Linux, Windows, and Android but there are missing features and many issues in the underlying `swift-corelibs-foundation` that prevent full functionality and may cause crashes. These include: + +- `ServerTrustManager` and associated certificate functionality is unavailable, so there is no certificate pinning and no client certificate support. +- Various methods of HTTP authentication may crash, including HTTP Basic and HTTP Digest. Crashes may occur if responses contain server challenges. +- Cache control through `CachedResponseHandler` and associated APIs is unavailable, as the underlying delegate methods aren't called. +- `URLSessionTaskMetrics` are never gathered. +- `WebSocketRequest` is not available. + +Due to these issues, Alamofire is unsupported on Linux, Windows, and Android. Please report any crashes to the [Swift bug reporter](https://bugs.swift.org). + +## Migration Guides + +- [Alamofire 5.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%205.0%20Migration%20Guide.md) +- [Alamofire 4.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%204.0%20Migration%20Guide.md) +- [Alamofire 3.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%203.0%20Migration%20Guide.md) +- [Alamofire 2.0 Migration Guide](https://github.com/Alamofire/Alamofire/blob/master/Documentation/Alamofire%202.0%20Migration%20Guide.md) + +## Communication + +- If you **need help with making network requests** using Alamofire, use [Stack Overflow](https://stackoverflow.com/questions/tagged/alamofire) and tag `alamofire`. +- If you need to **find or understand an API**, check [our documentation](http://alamofire.github.io/Alamofire/) or [Apple's documentation for `URLSession`](https://developer.apple.com/documentation/foundation/url_loading_system), on top of which Alamofire is built. +- If you need **help with an Alamofire feature**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you'd like to **discuss Alamofire best practices**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you'd like to **discuss a feature request**, use [our forum on swift.org](https://forums.swift.org/c/related-projects/alamofire). +- If you **found a bug**, open an issue here on GitHub and follow the guide. The more detail the better! + +## Installation + +### Swift Package Manager + +The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. + +Once you have your Swift package set up, adding Alamofire as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift` or the Package list in Xcode. + +```swift +dependencies: [ + .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.10.0")) +] +``` + +Normally you'll want to depend on the `Alamofire` target: + +```swift +.product(name: "Alamofire", package: "Alamofire") +``` + +But if you want to force Alamofire to be dynamically linked (do not do this unless you're sure you need it), you can depend on the `AlamofireDynamic` target: + +```swift +.product(name: "AlamofireDynamic", package: "Alamofire") +``` + +### CocoaPods + +[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate Alamofire into your Xcode project using CocoaPods, specify it in your `Podfile`: + +```ruby +pod 'Alamofire' +``` + +### Carthage + +[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: + +```ogdl +github "Alamofire/Alamofire" +``` + +### Manually + +If you prefer not to use any of the aforementioned dependency managers, you can integrate Alamofire into your project manually. + +#### Embedded Framework + +- Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository: + + ```bash + $ git init + ``` + +- Add Alamofire as a git [submodule](https://git-scm.com/docs/git-submodule) by running the following command: + + ```bash + $ git submodule add https://github.com/Alamofire/Alamofire.git + ``` + +- Open the new `Alamofire` folder, and drag the `Alamofire.xcodeproj` into the Project Navigator of your application's Xcode project. + + > It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter. + +- Select the `Alamofire.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target. +- Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar. +- In the tab bar at the top of that window, open the "General" panel. +- Click on the `+` button under the "Embedded Binaries" section. +- You will see two different `Alamofire.xcodeproj` folders each with two different versions of the `Alamofire.framework` nested inside a `Products` folder. + + > It does not matter which `Products` folder you choose from, but it does matter whether you choose the top or bottom `Alamofire.framework`. + +- Select the top `Alamofire.framework` for iOS and the bottom one for macOS. + + > You can verify which one you selected by inspecting the build log for your project. The build target for `Alamofire` will be listed as `Alamofire iOS`, `Alamofire macOS`, `Alamofire tvOS`, or `Alamofire watchOS`. + +- And that's it! + + > The `Alamofire.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device. + +## Contributing + +Before contributing to Alamofire, please read the instructions detailed in our [contribution guide](https://github.com/Alamofire/Alamofire/blob/master/CONTRIBUTING.md). + +## Open Radars + +The following radars have some effect on the current implementation of Alamofire. + +- [`rdar://21349340`](http://www.openradar.me/radar?id=5517037090635776) - Compiler throwing warning due to toll-free bridging issue in the test case +- `rdar://26870455` - Background URL Session Configurations do not work in the simulator +- `rdar://26849668` - Some URLProtocol APIs do not properly handle `URLRequest` + +## Resolved Radars + +The following radars have been resolved over time after being filed against the Alamofire project. + +- [`rdar://26761490`](http://www.openradar.me/radar?id=5010235949318144) - Swift string interpolation causing memory leak with common usage. + - (Resolved): 9/1/17 in Xcode 9 beta 6. +- [`rdar://36082113`](http://openradar.appspot.com/radar?id=4942308441063424) - `URLSessionTaskMetrics` failing to link on watchOS 3.0+ + - (Resolved): Just add `CFNetwork` to your linked frameworks. +- `FB7624529` - `urlSession(_:task:didFinishCollecting:)` never called on watchOS + - (Resolved): Metrics now collected on watchOS 7+. + +## FAQ + +### What's the origin of the name Alamofire? + +Alamofire is named after the [Alamo Fire flower](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html), a hybrid variant of the Bluebonnet, the official state flower of Texas. + +## Credits + +Alamofire is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org). You can follow them on Twitter at [@AlamofireSF](https://twitter.com/AlamofireSF) for project updates and releases. + +### Security Disclosure + +If you believe you have identified a security vulnerability with Alamofire, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker. + +## Sponsorship + +The [ASF](https://github.com/Alamofire/Foundation#members) is looking to raise money to officially stay registered as a federal non-profit organization. +Registering will allow Foundation members to gain some legal protections and also allow us to put donations to use, tax-free. +Sponsoring the ASF will enable us to: + +- Pay our yearly legal fees to keep the non-profit in good status +- Pay for our mail servers to help us stay on top of all questions and security issues +- Potentially fund test servers to make it easier for us to test the edge cases +- Potentially fund developers to work on one of our projects full-time + +The community adoption of the ASF libraries has been amazing. +We are greatly humbled by your enthusiasm around the projects and want to continue to do everything we can to move the needle forward. +With your continued support, the ASF will be able to improve its reach and also provide better legal safety for the core members. +If you use any of our libraries for work, see if your employers would be interested in donating. +Any amount you can donate, whether once or monthly, to help us reach our goal would be greatly appreciated. + +[Sponsor Alamofire](https://github.com/sponsors/Alamofire) + +## Supporters + +[MacStadium](https://macstadium.com) provides Alamofire with a free, hosted Mac mini. + +![Powered by MacStadium](https://raw.githubusercontent.com/Alamofire/Alamofire/master/Resources/MacStadiumLogo.png) + +## License + +Alamofire is released under the MIT license. [See LICENSE](https://github.com/Alamofire/Alamofire/blob/master/LICENSE) for details. diff --git a/Pods/Alamofire/Source/Alamofire.swift b/Pods/Alamofire/Source/Alamofire.swift new file mode 100644 index 0000000..117c849 --- /dev/null +++ b/Pods/Alamofire/Source/Alamofire.swift @@ -0,0 +1,43 @@ +// +// Alamofire.swift +// +// Copyright (c) 2014-2021 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Dispatch +import Foundation +#if canImport(FoundationNetworking) +@_exported import FoundationNetworking +#endif + +// Enforce minimum Swift version for all platforms and build systems. +#if swift(<5.9.0) +#error("Alamofire doesn't support Swift versions below 5.9.") +#endif + +/// Reference to `Session.default` for quick bootstrapping and examples. +public let AF = Session.default + +/// Namespace for informational Alamofire values. +public enum AFInfo { + /// Current Alamofire version. + public static let version = "5.10.1" +} diff --git a/Pods/Alamofire/Source/Core/AFError.swift b/Pods/Alamofire/Source/Core/AFError.swift new file mode 100644 index 0000000..ea67852 --- /dev/null +++ b/Pods/Alamofire/Source/Core/AFError.swift @@ -0,0 +1,874 @@ +// +// AFError.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +#if canImport(Security) +@preconcurrency import Security +#endif + +/// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with +/// their own associated reasons. +public enum AFError: Error, Sendable { + /// The underlying reason the `.multipartEncodingFailed` error occurred. + public enum MultipartEncodingFailureReason: Sendable { + /// The `fileURL` provided for reading an encodable body part isn't a file `URL`. + case bodyPartURLInvalid(url: URL) + /// The filename of the `fileURL` provided has either an empty `lastPathComponent` or `pathExtension`. + case bodyPartFilenameInvalid(in: URL) + /// The file at the `fileURL` provided was not reachable. + case bodyPartFileNotReachable(at: URL) + /// Attempting to check the reachability of the `fileURL` provided threw an error. + case bodyPartFileNotReachableWithError(atURL: URL, error: any Error) + /// The file at the `fileURL` provided is actually a directory. + case bodyPartFileIsDirectory(at: URL) + /// The size of the file at the `fileURL` provided was not returned by the system. + case bodyPartFileSizeNotAvailable(at: URL) + /// The attempt to find the size of the file at the `fileURL` provided threw an error. + case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: any Error) + /// An `InputStream` could not be created for the provided `fileURL`. + case bodyPartInputStreamCreationFailed(for: URL) + /// An `OutputStream` could not be created when attempting to write the encoded data to disk. + case outputStreamCreationFailed(for: URL) + /// The encoded body data could not be written to disk because a file already exists at the provided `fileURL`. + case outputStreamFileAlreadyExists(at: URL) + /// The `fileURL` provided for writing the encoded body data to disk is not a file `URL`. + case outputStreamURLInvalid(url: URL) + /// The attempt to write the encoded body data to disk failed with an underlying error. + case outputStreamWriteFailed(error: any Error) + /// The attempt to read an encoded body part `InputStream` failed with underlying system error. + case inputStreamReadFailed(error: any Error) + } + + /// Represents unexpected input stream length that occur when encoding the `MultipartFormData`. Instances will be + /// embedded within an `AFError.multipartEncodingFailed` `.inputStreamReadFailed` case. + public struct UnexpectedInputStreamLength: Error { + /// The expected byte count to read. + public var bytesExpected: UInt64 + /// The actual byte count read. + public var bytesRead: UInt64 + } + + /// The underlying reason the `.parameterEncodingFailed` error occurred. + public enum ParameterEncodingFailureReason: Sendable { + /// The `URLRequest` did not have a `URL` to encode. + case missingURL + /// JSON serialization failed with an underlying system error during the encoding process. + case jsonEncodingFailed(error: any Error) + /// Custom parameter encoding failed due to the associated `Error`. + case customEncodingFailed(error: any Error) + } + + /// The underlying reason the `.parameterEncoderFailed` error occurred. + public enum ParameterEncoderFailureReason: Sendable { + /// Possible missing components. + public enum RequiredComponent: Sendable { + /// The `URL` was missing or unable to be extracted from the passed `URLRequest` or during encoding. + case url + /// The `HTTPMethod` could not be extracted from the passed `URLRequest`. + case httpMethod(rawValue: String) + } + + /// A `RequiredComponent` was missing during encoding. + case missingRequiredComponent(RequiredComponent) + /// The underlying encoder failed with the associated error. + case encoderFailed(error: any Error) + } + + /// The underlying reason the `.responseValidationFailed` error occurred. + public enum ResponseValidationFailureReason: Sendable { + /// The data file containing the server response did not exist. + case dataFileNil + /// The data file containing the server response at the associated `URL` could not be read. + case dataFileReadFailed(at: URL) + /// The response did not contain a `Content-Type` and the `acceptableContentTypes` provided did not contain a + /// wildcard type. + case missingContentType(acceptableContentTypes: [String]) + /// The response `Content-Type` did not match any type in the provided `acceptableContentTypes`. + case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String) + /// The response status code was not acceptable. + case unacceptableStatusCode(code: Int) + /// Custom response validation failed due to the associated `Error`. + case customValidationFailed(error: any Error) + } + + /// The underlying reason the response serialization error occurred. + public enum ResponseSerializationFailureReason: Sendable { + /// The server response contained no data or the data was zero length. + case inputDataNilOrZeroLength + /// The file containing the server response did not exist. + case inputFileNil + /// The file containing the server response could not be read from the associated `URL`. + case inputFileReadFailed(at: URL) + /// String serialization failed using the provided `String.Encoding`. + case stringSerializationFailed(encoding: String.Encoding) + /// JSON serialization failed with an underlying system error. + case jsonSerializationFailed(error: any Error) + /// A `DataDecoder` failed to decode the response due to the associated `Error`. + case decodingFailed(error: any Error) + /// A custom response serializer failed due to the associated `Error`. + case customSerializationFailed(error: any Error) + /// Generic serialization failed for an empty response that wasn't type `Empty` but instead the associated type. + case invalidEmptyResponse(type: String) + } + + #if canImport(Security) + /// Underlying reason a server trust evaluation error occurred. + public enum ServerTrustFailureReason: Sendable { + /// The output of a server trust evaluation. + public struct Output: Sendable { + /// The host for which the evaluation was performed. + public let host: String + /// The `SecTrust` value which was evaluated. + public let trust: SecTrust + /// The `OSStatus` of evaluation operation. + public let status: OSStatus + /// The result of the evaluation operation. + public let result: SecTrustResultType + + /// Creates an `Output` value from the provided values. + init(_ host: String, _ trust: SecTrust, _ status: OSStatus, _ result: SecTrustResultType) { + self.host = host + self.trust = trust + self.status = status + self.result = result + } + } + + /// No `ServerTrustEvaluator` was found for the associated host. + case noRequiredEvaluator(host: String) + /// No certificates were found with which to perform the trust evaluation. + case noCertificatesFound + /// No public keys were found with which to perform the trust evaluation. + case noPublicKeysFound + /// During evaluation, application of the associated `SecPolicy` failed. + case policyApplicationFailed(trust: SecTrust, policy: SecPolicy, status: OSStatus) + /// During evaluation, setting the associated anchor certificates failed. + case settingAnchorCertificatesFailed(status: OSStatus, certificates: [SecCertificate]) + /// During evaluation, creation of the revocation policy failed. + case revocationPolicyCreationFailed + /// `SecTrust` evaluation failed with the associated `Error`, if one was produced. + case trustEvaluationFailed(error: (any Error)?) + /// Default evaluation failed with the associated `Output`. + case defaultEvaluationFailed(output: Output) + /// Host validation failed with the associated `Output`. + case hostValidationFailed(output: Output) + /// Revocation check failed with the associated `Output` and options. + case revocationCheckFailed(output: Output, options: RevocationTrustEvaluator.Options) + /// Certificate pinning failed. + case certificatePinningFailed(host: String, trust: SecTrust, pinnedCertificates: [SecCertificate], serverCertificates: [SecCertificate]) + /// Public key pinning failed. + case publicKeyPinningFailed(host: String, trust: SecTrust, pinnedKeys: [SecKey], serverKeys: [SecKey]) + /// Custom server trust evaluation failed due to the associated `Error`. + case customEvaluationFailed(error: any Error) + } + #endif + + /// The underlying reason the `.urlRequestValidationFailed` error occurred. + public enum URLRequestValidationFailureReason: Sendable { + /// URLRequest with GET method had body data. + case bodyDataInGETRequest(Data) + } + + /// `UploadableConvertible` threw an error in `createUploadable()`. + case createUploadableFailed(error: any Error) + /// `URLRequestConvertible` threw an error in `asURLRequest()`. + case createURLRequestFailed(error: any Error) + /// `SessionDelegate` threw an error while attempting to move downloaded file to destination URL. + case downloadedFileMoveFailed(error: any Error, source: URL, destination: URL) + /// `Request` was explicitly cancelled. + case explicitlyCancelled + /// `URLConvertible` type failed to create a valid `URL`. + case invalidURL(url: any URLConvertible) + /// Multipart form encoding failed. + case multipartEncodingFailed(reason: MultipartEncodingFailureReason) + /// `ParameterEncoding` threw an error during the encoding process. + case parameterEncodingFailed(reason: ParameterEncodingFailureReason) + /// `ParameterEncoder` threw an error while running the encoder. + case parameterEncoderFailed(reason: ParameterEncoderFailureReason) + /// `RequestAdapter` threw an error during adaptation. + case requestAdaptationFailed(error: any Error) + /// `RequestRetrier` threw an error during the request retry process. + case requestRetryFailed(retryError: any Error, originalError: any Error) + /// Response validation failed. + case responseValidationFailed(reason: ResponseValidationFailureReason) + /// Response serialization failed. + case responseSerializationFailed(reason: ResponseSerializationFailureReason) + #if canImport(Security) + /// `ServerTrustEvaluating` instance threw an error during trust evaluation. + case serverTrustEvaluationFailed(reason: ServerTrustFailureReason) + #endif + /// `Session` which issued the `Request` was deinitialized, most likely because its reference went out of scope. + case sessionDeinitialized + /// `Session` was explicitly invalidated, possibly with the `Error` produced by the underlying `URLSession`. + case sessionInvalidated(error: (any Error)?) + /// `URLSessionTask` completed with error. + case sessionTaskFailed(error: any Error) + /// `URLRequest` failed validation. + case urlRequestValidationFailed(reason: URLRequestValidationFailureReason) +} + +extension Error { + /// Returns the instance cast as an `AFError`. + public var asAFError: AFError? { + self as? AFError + } + + /// Returns the instance cast as an `AFError`. If casting fails, a `fatalError` with the specified `message` is thrown. + public func asAFError(orFailWith message: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> AFError { + guard let afError = self as? AFError else { + fatalError(message(), file: file, line: line) + } + return afError + } + + /// Casts the instance as `AFError` or returns `defaultAFError` + func asAFError(or defaultAFError: @autoclosure () -> AFError) -> AFError { + self as? AFError ?? defaultAFError() + } +} + +// MARK: - Error Booleans + +extension AFError { + /// Returns whether the instance is `.sessionDeinitialized`. + public var isSessionDeinitializedError: Bool { + if case .sessionDeinitialized = self { return true } + return false + } + + /// Returns whether the instance is `.sessionInvalidated`. + public var isSessionInvalidatedError: Bool { + if case .sessionInvalidated = self { return true } + return false + } + + /// Returns whether the instance is `.explicitlyCancelled`. + public var isExplicitlyCancelledError: Bool { + if case .explicitlyCancelled = self { return true } + return false + } + + /// Returns whether the instance is `.invalidURL`. + public var isInvalidURLError: Bool { + if case .invalidURL = self { return true } + return false + } + + /// Returns whether the instance is `.parameterEncodingFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isParameterEncodingError: Bool { + if case .parameterEncodingFailed = self { return true } + return false + } + + /// Returns whether the instance is `.parameterEncoderFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isParameterEncoderError: Bool { + if case .parameterEncoderFailed = self { return true } + return false + } + + /// Returns whether the instance is `.multipartEncodingFailed`. When `true`, the `url` and `underlyingError` + /// properties will contain the associated values. + public var isMultipartEncodingError: Bool { + if case .multipartEncodingFailed = self { return true } + return false + } + + /// Returns whether the instance is `.requestAdaptationFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isRequestAdaptationError: Bool { + if case .requestAdaptationFailed = self { return true } + return false + } + + /// Returns whether the instance is `.responseValidationFailed`. When `true`, the `acceptableContentTypes`, + /// `responseContentType`, `responseCode`, and `underlyingError` properties will contain the associated values. + public var isResponseValidationError: Bool { + if case .responseValidationFailed = self { return true } + return false + } + + /// Returns whether the instance is `.responseSerializationFailed`. When `true`, the `failedStringEncoding` and + /// `underlyingError` properties will contain the associated values. + public var isResponseSerializationError: Bool { + if case .responseSerializationFailed = self { return true } + return false + } + + #if canImport(Security) + /// Returns whether the instance is `.serverTrustEvaluationFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isServerTrustEvaluationError: Bool { + if case .serverTrustEvaluationFailed = self { return true } + return false + } + #endif + + /// Returns whether the instance is `requestRetryFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isRequestRetryError: Bool { + if case .requestRetryFailed = self { return true } + return false + } + + /// Returns whether the instance is `createUploadableFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isCreateUploadableError: Bool { + if case .createUploadableFailed = self { return true } + return false + } + + /// Returns whether the instance is `createURLRequestFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isCreateURLRequestError: Bool { + if case .createURLRequestFailed = self { return true } + return false + } + + /// Returns whether the instance is `downloadedFileMoveFailed`. When `true`, the `destination` and `underlyingError` properties will + /// contain the associated values. + public var isDownloadedFileMoveError: Bool { + if case .downloadedFileMoveFailed = self { return true } + return false + } + + /// Returns whether the instance is `createURLRequestFailed`. When `true`, the `underlyingError` property will + /// contain the associated value. + public var isSessionTaskError: Bool { + if case .sessionTaskFailed = self { return true } + return false + } +} + +// MARK: - Convenience Properties + +extension AFError { + /// The `URLConvertible` associated with the error. + public var urlConvertible: (any URLConvertible)? { + guard case let .invalidURL(url) = self else { return nil } + return url + } + + /// The `URL` associated with the error. + public var url: URL? { + guard case let .multipartEncodingFailed(reason) = self else { return nil } + return reason.url + } + + /// The underlying `Error` responsible for generating the failure associated with `.sessionInvalidated`, + /// `.parameterEncodingFailed`, `.parameterEncoderFailed`, `.multipartEncodingFailed`, `.requestAdaptationFailed`, + /// `.responseSerializationFailed`, `.requestRetryFailed` errors. + public var underlyingError: (any Error)? { + switch self { + case let .multipartEncodingFailed(reason): + return reason.underlyingError + case let .parameterEncodingFailed(reason): + return reason.underlyingError + case let .parameterEncoderFailed(reason): + return reason.underlyingError + case let .requestAdaptationFailed(error): + return error + case let .requestRetryFailed(retryError, _): + return retryError + case let .responseValidationFailed(reason): + return reason.underlyingError + case let .responseSerializationFailed(reason): + return reason.underlyingError + #if canImport(Security) + case let .serverTrustEvaluationFailed(reason): + return reason.underlyingError + #endif + case let .sessionInvalidated(error): + return error + case let .createUploadableFailed(error): + return error + case let .createURLRequestFailed(error): + return error + case let .downloadedFileMoveFailed(error, _, _): + return error + case let .sessionTaskFailed(error): + return error + case .explicitlyCancelled, + .invalidURL, + .sessionDeinitialized, + .urlRequestValidationFailed: + return nil + } + } + + /// The acceptable `Content-Type`s of a `.responseValidationFailed` error. + public var acceptableContentTypes: [String]? { + guard case let .responseValidationFailed(reason) = self else { return nil } + return reason.acceptableContentTypes + } + + /// The response `Content-Type` of a `.responseValidationFailed` error. + public var responseContentType: String? { + guard case let .responseValidationFailed(reason) = self else { return nil } + return reason.responseContentType + } + + /// The response code of a `.responseValidationFailed` error. + public var responseCode: Int? { + guard case let .responseValidationFailed(reason) = self else { return nil } + return reason.responseCode + } + + /// The `String.Encoding` associated with a failed `.stringResponse()` call. + public var failedStringEncoding: String.Encoding? { + guard case let .responseSerializationFailed(reason) = self else { return nil } + return reason.failedStringEncoding + } + + /// The `source` URL of a `.downloadedFileMoveFailed` error. + public var sourceURL: URL? { + guard case let .downloadedFileMoveFailed(_, source, _) = self else { return nil } + return source + } + + /// The `destination` URL of a `.downloadedFileMoveFailed` error. + public var destinationURL: URL? { + guard case let .downloadedFileMoveFailed(_, _, destination) = self else { return nil } + return destination + } + + #if canImport(Security) + /// The download resume data of any underlying network error. Only produced by `DownloadRequest`s. + public var downloadResumeData: Data? { + (underlyingError as? URLError)?.userInfo[NSURLSessionDownloadTaskResumeData] as? Data + } + #endif +} + +extension AFError.ParameterEncodingFailureReason { + var underlyingError: (any Error)? { + switch self { + case let .jsonEncodingFailed(error), + let .customEncodingFailed(error): + error + case .missingURL: + nil + } + } +} + +extension AFError.ParameterEncoderFailureReason { + var underlyingError: (any Error)? { + switch self { + case let .encoderFailed(error): + error + case .missingRequiredComponent: + nil + } + } +} + +extension AFError.MultipartEncodingFailureReason { + var url: URL? { + switch self { + case let .bodyPartURLInvalid(url), + let .bodyPartFilenameInvalid(url), + let .bodyPartFileNotReachable(url), + let .bodyPartFileIsDirectory(url), + let .bodyPartFileSizeNotAvailable(url), + let .bodyPartInputStreamCreationFailed(url), + let .outputStreamCreationFailed(url), + let .outputStreamFileAlreadyExists(url), + let .outputStreamURLInvalid(url), + let .bodyPartFileNotReachableWithError(url, _), + let .bodyPartFileSizeQueryFailedWithError(url, _): + url + case .outputStreamWriteFailed, + .inputStreamReadFailed: + nil + } + } + + var underlyingError: (any Error)? { + switch self { + case let .bodyPartFileNotReachableWithError(_, error), + let .bodyPartFileSizeQueryFailedWithError(_, error), + let .outputStreamWriteFailed(error), + let .inputStreamReadFailed(error): + error + case .bodyPartURLInvalid, + .bodyPartFilenameInvalid, + .bodyPartFileNotReachable, + .bodyPartFileIsDirectory, + .bodyPartFileSizeNotAvailable, + .bodyPartInputStreamCreationFailed, + .outputStreamCreationFailed, + .outputStreamFileAlreadyExists, + .outputStreamURLInvalid: + nil + } + } +} + +extension AFError.ResponseValidationFailureReason { + var acceptableContentTypes: [String]? { + switch self { + case let .missingContentType(types), + let .unacceptableContentType(types, _): + types + case .dataFileNil, + .dataFileReadFailed, + .unacceptableStatusCode, + .customValidationFailed: + nil + } + } + + var responseContentType: String? { + switch self { + case let .unacceptableContentType(_, responseType): + responseType + case .dataFileNil, + .dataFileReadFailed, + .missingContentType, + .unacceptableStatusCode, + .customValidationFailed: + nil + } + } + + var responseCode: Int? { + switch self { + case let .unacceptableStatusCode(code): + code + case .dataFileNil, + .dataFileReadFailed, + .missingContentType, + .unacceptableContentType, + .customValidationFailed: + nil + } + } + + var underlyingError: (any Error)? { + switch self { + case let .customValidationFailed(error): + error + case .dataFileNil, + .dataFileReadFailed, + .missingContentType, + .unacceptableContentType, + .unacceptableStatusCode: + nil + } + } +} + +extension AFError.ResponseSerializationFailureReason { + var failedStringEncoding: String.Encoding? { + switch self { + case let .stringSerializationFailed(encoding): + encoding + case .inputDataNilOrZeroLength, + .inputFileNil, + .inputFileReadFailed(_), + .jsonSerializationFailed(_), + .decodingFailed(_), + .customSerializationFailed(_), + .invalidEmptyResponse: + nil + } + } + + var underlyingError: (any Error)? { + switch self { + case let .jsonSerializationFailed(error), + let .decodingFailed(error), + let .customSerializationFailed(error): + error + case .inputDataNilOrZeroLength, + .inputFileNil, + .inputFileReadFailed, + .stringSerializationFailed, + .invalidEmptyResponse: + nil + } + } +} + +#if canImport(Security) +extension AFError.ServerTrustFailureReason { + var output: AFError.ServerTrustFailureReason.Output? { + switch self { + case let .defaultEvaluationFailed(output), + let .hostValidationFailed(output), + let .revocationCheckFailed(output, _): + output + case .noRequiredEvaluator, + .noCertificatesFound, + .noPublicKeysFound, + .policyApplicationFailed, + .settingAnchorCertificatesFailed, + .revocationPolicyCreationFailed, + .trustEvaluationFailed, + .certificatePinningFailed, + .publicKeyPinningFailed, + .customEvaluationFailed: + nil + } + } + + var underlyingError: (any Error)? { + switch self { + case let .customEvaluationFailed(error): + error + case let .trustEvaluationFailed(error): + error + case .noRequiredEvaluator, + .noCertificatesFound, + .noPublicKeysFound, + .policyApplicationFailed, + .settingAnchorCertificatesFailed, + .revocationPolicyCreationFailed, + .defaultEvaluationFailed, + .hostValidationFailed, + .revocationCheckFailed, + .certificatePinningFailed, + .publicKeyPinningFailed: + nil + } + } +} +#endif + +// MARK: - Error Descriptions + +extension AFError: LocalizedError { + public var errorDescription: String? { + switch self { + case .explicitlyCancelled: + return "Request explicitly cancelled." + case let .invalidURL(url): + return "URL is not valid: \(url)" + case let .parameterEncodingFailed(reason): + return reason.localizedDescription + case let .parameterEncoderFailed(reason): + return reason.localizedDescription + case let .multipartEncodingFailed(reason): + return reason.localizedDescription + case let .requestAdaptationFailed(error): + return "Request adaption failed with error: \(error.localizedDescription)" + case let .responseValidationFailed(reason): + return reason.localizedDescription + case let .responseSerializationFailed(reason): + return reason.localizedDescription + case let .requestRetryFailed(retryError, originalError): + return """ + Request retry failed with retry error: \(retryError.localizedDescription), \ + original error: \(originalError.localizedDescription) + """ + case .sessionDeinitialized: + return """ + Session was invalidated without error, so it was likely deinitialized unexpectedly. \ + Be sure to retain a reference to your Session for the duration of your requests. + """ + case let .sessionInvalidated(error): + return "Session was invalidated with error: \(error?.localizedDescription ?? "No description.")" + #if canImport(Security) + case let .serverTrustEvaluationFailed(reason): + return "Server trust evaluation failed due to reason: \(reason.localizedDescription)" + #endif + case let .urlRequestValidationFailed(reason): + return "URLRequest validation failed due to reason: \(reason.localizedDescription)" + case let .createUploadableFailed(error): + return "Uploadable creation failed with error: \(error.localizedDescription)" + case let .createURLRequestFailed(error): + return "URLRequest creation failed with error: \(error.localizedDescription)" + case let .downloadedFileMoveFailed(error, source, destination): + return "Moving downloaded file from: \(source) to: \(destination) failed with error: \(error.localizedDescription)" + case let .sessionTaskFailed(error): + return "URLSessionTask failed with error: \(error.localizedDescription)" + } + } +} + +extension AFError.ParameterEncodingFailureReason { + var localizedDescription: String { + switch self { + case .missingURL: + "URL request to encode was missing a URL" + case let .jsonEncodingFailed(error): + "JSON could not be encoded because of error:\n\(error.localizedDescription)" + case let .customEncodingFailed(error): + "Custom parameter encoder failed with error: \(error.localizedDescription)" + } + } +} + +extension AFError.ParameterEncoderFailureReason { + var localizedDescription: String { + switch self { + case let .missingRequiredComponent(component): + "Encoding failed due to a missing request component: \(component)" + case let .encoderFailed(error): + "The underlying encoder failed with the error: \(error)" + } + } +} + +extension AFError.MultipartEncodingFailureReason { + var localizedDescription: String { + switch self { + case let .bodyPartURLInvalid(url): + "The URL provided is not a file URL: \(url)" + case let .bodyPartFilenameInvalid(url): + "The URL provided does not have a valid filename: \(url)" + case let .bodyPartFileNotReachable(url): + "The URL provided is not reachable: \(url)" + case let .bodyPartFileNotReachableWithError(url, error): + """ + The system returned an error while checking the provided URL for reachability. + URL: \(url) + Error: \(error) + """ + case let .bodyPartFileIsDirectory(url): + "The URL provided is a directory: \(url)" + case let .bodyPartFileSizeNotAvailable(url): + "Could not fetch the file size from the provided URL: \(url)" + case let .bodyPartFileSizeQueryFailedWithError(url, error): + """ + The system returned an error while attempting to fetch the file size from the provided URL. + URL: \(url) + Error: \(error) + """ + case let .bodyPartInputStreamCreationFailed(url): + "Failed to create an InputStream for the provided URL: \(url)" + case let .outputStreamCreationFailed(url): + "Failed to create an OutputStream for URL: \(url)" + case let .outputStreamFileAlreadyExists(url): + "A file already exists at the provided URL: \(url)" + case let .outputStreamURLInvalid(url): + "The provided OutputStream URL is invalid: \(url)" + case let .outputStreamWriteFailed(error): + "OutputStream write failed with error: \(error)" + case let .inputStreamReadFailed(error): + "InputStream read failed with error: \(error)" + } + } +} + +extension AFError.ResponseSerializationFailureReason { + var localizedDescription: String { + switch self { + case .inputDataNilOrZeroLength: + "Response could not be serialized, input data was nil or zero length." + case .inputFileNil: + "Response could not be serialized, input file was nil." + case let .inputFileReadFailed(url): + "Response could not be serialized, input file could not be read: \(url)." + case let .stringSerializationFailed(encoding): + "String could not be serialized with encoding: \(encoding)." + case let .jsonSerializationFailed(error): + "JSON could not be serialized because of error:\n\(error.localizedDescription)" + case let .invalidEmptyResponse(type): + """ + Empty response could not be serialized to type: \(type). \ + Use Empty as the expected type for such responses. + """ + case let .decodingFailed(error): + "Response could not be decoded because of error:\n\(error.localizedDescription)" + case let .customSerializationFailed(error): + "Custom response serializer failed with error:\n\(error.localizedDescription)" + } + } +} + +extension AFError.ResponseValidationFailureReason { + var localizedDescription: String { + switch self { + case .dataFileNil: + "Response could not be validated, data file was nil." + case let .dataFileReadFailed(url): + "Response could not be validated, data file could not be read: \(url)." + case let .missingContentType(types): + """ + Response Content-Type was missing and acceptable content types \ + (\(types.joined(separator: ","))) do not match "*/*". + """ + case let .unacceptableContentType(acceptableTypes, responseType): + """ + Response Content-Type "\(responseType)" does not match any acceptable types: \ + \(acceptableTypes.joined(separator: ",")). + """ + case let .unacceptableStatusCode(code): + "Response status code was unacceptable: \(code)." + case let .customValidationFailed(error): + "Custom response validation failed with error: \(error.localizedDescription)" + } + } +} + +#if canImport(Security) +extension AFError.ServerTrustFailureReason { + var localizedDescription: String { + switch self { + case let .noRequiredEvaluator(host): + "A ServerTrustEvaluating value is required for host \(host) but none was found." + case .noCertificatesFound: + "No certificates were found or provided for evaluation." + case .noPublicKeysFound: + "No public keys were found or provided for evaluation." + case .policyApplicationFailed: + "Attempting to set a SecPolicy failed." + case .settingAnchorCertificatesFailed: + "Attempting to set the provided certificates as anchor certificates failed." + case .revocationPolicyCreationFailed: + "Attempting to create a revocation policy failed." + case let .trustEvaluationFailed(error): + "SecTrust evaluation failed with error: \(error?.localizedDescription ?? "None")" + case let .defaultEvaluationFailed(output): + "Default evaluation failed for host \(output.host)." + case let .hostValidationFailed(output): + "Host validation failed for host \(output.host)." + case let .revocationCheckFailed(output, _): + "Revocation check failed for host \(output.host)." + case let .certificatePinningFailed(host, _, _, _): + "Certificate pinning failed for host \(host)." + case let .publicKeyPinningFailed(host, _, _, _): + "Public key pinning failed for host \(host)." + case let .customEvaluationFailed(error): + "Custom trust evaluation failed with error: \(error.localizedDescription)" + } + } +} +#endif + +extension AFError.URLRequestValidationFailureReason { + var localizedDescription: String { + switch self { + case let .bodyDataInGETRequest(data): + """ + Invalid URLRequest: Requests with GET method cannot have body data: + \(String(decoding: data, as: UTF8.self)) + """ + } + } +} diff --git a/Pods/Alamofire/Source/Core/DataRequest.swift b/Pods/Alamofire/Source/Core/DataRequest.swift new file mode 100644 index 0000000..fc40f36 --- /dev/null +++ b/Pods/Alamofire/Source/Core/DataRequest.swift @@ -0,0 +1,458 @@ +// +// DataRequest.swift +// +// Copyright (c) 2014-2024 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// `Request` subclass which handles in-memory `Data` download using `URLSessionDataTask`. +public class DataRequest: Request, @unchecked Sendable { + /// `URLRequestConvertible` value used to create `URLRequest`s for this instance. + public let convertible: any URLRequestConvertible + /// `Data` read from the server so far. + public var data: Data? { dataMutableState.data } + + private struct DataMutableState { + var data: Data? + var httpResponseHandler: (queue: DispatchQueue, + handler: @Sendable (_ response: HTTPURLResponse, + _ completionHandler: @escaping @Sendable (ResponseDisposition) -> Void) -> Void)? + } + + private let dataMutableState = Protected(DataMutableState()) + + /// Creates a `DataRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - convertible: `URLRequestConvertible` value used to create `URLRequest`s for this instance. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. + init(id: UUID = UUID(), + convertible: any URLRequestConvertible, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: (any EventMonitor)?, + interceptor: (any RequestInterceptor)?, + delegate: any RequestDelegate) { + self.convertible = convertible + + super.init(id: id, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + override func reset() { + super.reset() + + dataMutableState.write { mutableState in + mutableState.data = nil + } + } + + /// Called when `Data` is received by this instance. + /// + /// - Note: Also calls `updateDownloadProgress`. + /// + /// - Parameter data: The `Data` received. + func didReceive(data: Data) { + dataMutableState.write { mutableState in + if mutableState.data == nil { + mutableState.data = data + } else { + mutableState.data?.append(data) + } + } + + updateDownloadProgress() + } + + func didReceiveResponse(_ response: HTTPURLResponse, completionHandler: @escaping @Sendable (URLSession.ResponseDisposition) -> Void) { + dataMutableState.read { dataMutableState in + guard let httpResponseHandler = dataMutableState.httpResponseHandler else { + underlyingQueue.async { completionHandler(.allow) } + return + } + + httpResponseHandler.queue.async { + httpResponseHandler.handler(response) { disposition in + if disposition == .cancel { + self.mutableState.write { mutableState in + mutableState.state = .cancelled + mutableState.error = mutableState.error ?? AFError.explicitlyCancelled + } + } + + self.underlyingQueue.async { + completionHandler(disposition.sessionDisposition) + } + } + } + } + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + let copiedRequest = request + return session.dataTask(with: copiedRequest) + } + + /// Called to update the `downloadProgress` of the instance. + func updateDownloadProgress() { + let totalBytesReceived = Int64(data?.count ?? 0) + let totalBytesExpected = task?.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown + + downloadProgress.totalUnitCount = totalBytesExpected + downloadProgress.completedUnitCount = totalBytesReceived + + downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) } + } + + /// Validates the request, using the specified closure. + /// + /// - Note: If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Parameter validation: `Validation` closure used to validate the response. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validator: () -> Void = { [unowned self] in + guard error == nil, let response else { return } + + let result = validation(request, response, data) + + if case let .failure(error) = result { self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) } + + eventMonitor?.request(self, + didValidateRequest: request, + response: response, + data: data, + withResult: result) + } + + validators.write { $0.append(validator) } + + return self + } + + /// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse` and providing a completion + /// handler to return a `ResponseDisposition` value. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the closure will be called. `.main` by default. + /// - handler: Closure called when the instance produces an `HTTPURLResponse`. The `completionHandler` provided + /// MUST be called, otherwise the request will never complete. + /// + /// - Returns: The instance. + @_disfavoredOverload + @preconcurrency + @discardableResult + public func onHTTPResponse( + on queue: DispatchQueue = .main, + perform handler: @escaping @Sendable (_ response: HTTPURLResponse, + _ completionHandler: @escaping @Sendable (ResponseDisposition) -> Void) -> Void + ) -> Self { + dataMutableState.write { mutableState in + mutableState.httpResponseHandler = (queue, handler) + } + + return self + } + + /// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse`. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the closure will be called. `.main` by default. + /// - handler: Closure called when the instance produces an `HTTPURLResponse`. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func onHTTPResponse(on queue: DispatchQueue = .main, + perform handler: @escaping @Sendable (HTTPURLResponse) -> Void) -> Self { + onHTTPResponse(on: queue) { response, completionHandler in + handler(response) + completionHandler(.allow) + } + + return self + } + + // MARK: Response Serialization + + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func response(queue: DispatchQueue = .main, completionHandler: @escaping @Sendable (AFDataResponse) -> Void) -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let result = AFResult(value: self.data, error: self.error) + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DataResponse(request: self.request, + response: self.response, + data: self.data, + metrics: self.metrics, + serializationDuration: 0, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + } + } + + return self + } + + private func _response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping @Sendable (AFDataResponse) -> Void) + -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let start = ProcessInfo.processInfo.systemUptime + let result: AFResult = Result { + try responseSerializer.serialize(request: self.request, + response: self.response, + data: self.data, + error: self.error) + }.mapError { error in + error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) + } + + let end = ProcessInfo.processInfo.systemUptime + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DataResponse(request: self.request, + response: self.response, + data: self.data, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + guard !self.isCancelled, let serializerError = result.failure, let delegate = self.delegate else { + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + return + } + + delegate.retryResult(for: self, dueTo: serializerError) { retryResult in + var didComplete: (@Sendable () -> Void)? + + defer { + if let didComplete { + self.responseSerializerDidComplete { queue.async { didComplete() } } + } + } + + switch retryResult { + case .doNotRetry: + didComplete = { completionHandler(response) } + + case let .doNotRetryWithError(retryError): + let result: AFResult = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError")) + + let response = DataResponse(request: self.request, + response: self.response, + data: self.data, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + didComplete = { completionHandler(response) } + + case .retry, .retryWithDelay: + delegate.retryRequest(self, withDelay: retryResult.delay) + } + } + } + } + + return self + } + + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default + /// - responseSerializer: The response serializer responsible for serializing the request, response, and data. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping @Sendable (AFDataResponse) -> Void) + -> Self { + _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) + } + + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default + /// - responseSerializer: The response serializer responsible for serializing the request, response, and data. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping @Sendable (AFDataResponse) -> Void) + -> Self { + _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) + } + + /// Adds a handler using a `DataResponseSerializer` to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is called. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func responseData(queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods, + completionHandler: @escaping @Sendable (AFDataResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: DataResponseSerializer(dataPreprocessor: dataPreprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + completionHandler: completionHandler) + } + + /// Adds a handler using a `StringResponseSerializer` to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined + /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func responseString(queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods, + completionHandler: @escaping @Sendable (AFDataResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor, + encoding: encoding, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + completionHandler: completionHandler) + } + + /// Adds a handler using a `JSONResponseSerializer` to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - options: `JSONSerialization.ReadingOptions` used when parsing the response. `.allowFragments` + /// by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @available(*, deprecated, message: "responseJSON deprecated and will be removed in Alamofire 6. Use responseDecodable instead.") + @preconcurrency + @discardableResult + public func responseJSON(queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = JSONResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = JSONResponseSerializer.defaultEmptyRequestMethods, + options: JSONSerialization.ReadingOptions = .allowFragments, + completionHandler: @escaping @Sendable (AFDataResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: JSONResponseSerializer(dataPreprocessor: dataPreprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods, + options: options), + completionHandler: completionHandler) + } + + /// Adds a handler using a `DecodableResponseSerializer` to be called once the request has finished. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from response data. + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func responseDecodable(of type: Value.Type = Value.self, + queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods, + completionHandler: @escaping @Sendable (AFDataResponse) -> Void) -> Self where Value: Decodable, Value: Sendable { + response(queue: queue, + responseSerializer: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + completionHandler: completionHandler) + } +} diff --git a/Pods/Alamofire/Source/Core/DataStreamRequest.swift b/Pods/Alamofire/Source/Core/DataStreamRequest.swift new file mode 100644 index 0000000..603a278 --- /dev/null +++ b/Pods/Alamofire/Source/Core/DataStreamRequest.swift @@ -0,0 +1,590 @@ +// +// DataStreamRequest.swift +// +// Copyright (c) 2014-2024 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// `Request` subclass which streams HTTP response `Data` through a `Handler` closure. +public final class DataStreamRequest: Request, @unchecked Sendable { + /// Closure type handling `DataStreamRequest.Stream` values. + public typealias Handler = @Sendable (Stream) throws -> Void + + /// Type encapsulating an `Event` as it flows through the stream, as well as a `CancellationToken` which can be used + /// to stop the stream at any time. + public struct Stream: Sendable where Success: Sendable, Failure: Sendable { + /// Latest `Event` from the stream. + public let event: Event + /// Token used to cancel the stream. + public let token: CancellationToken + + /// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`. + public func cancel() { + token.cancel() + } + } + + /// Type representing an event flowing through the stream. Contains either the `Result` of processing streamed + /// `Data` or the completion of the stream. + public enum Event: Sendable where Success: Sendable, Failure: Sendable { + /// Output produced every time the instance receives additional `Data`. The associated value contains the + /// `Result` of processing the incoming `Data`. + case stream(Result) + /// Output produced when the instance has completed, whether due to stream end, cancellation, or an error. + /// Associated `Completion` value contains the final state. + case complete(Completion) + } + + /// Value containing the state of a `DataStreamRequest` when the stream was completed. + public struct Completion: Sendable { + /// Last `URLRequest` issued by the instance. + public let request: URLRequest? + /// Last `HTTPURLResponse` received by the instance. + public let response: HTTPURLResponse? + /// Last `URLSessionTaskMetrics` produced for the instance. + public let metrics: URLSessionTaskMetrics? + /// `AFError` produced for the instance, if any. + public let error: AFError? + } + + /// Type used to cancel an ongoing stream. + public struct CancellationToken: Sendable { + weak var request: DataStreamRequest? + + init(_ request: DataStreamRequest) { + self.request = request + } + + /// Cancel the ongoing stream by canceling the underlying `DataStreamRequest`. + public func cancel() { + request?.cancel() + } + } + + /// `URLRequestConvertible` value used to create `URLRequest`s for this instance. + public let convertible: any URLRequestConvertible + /// Whether or not the instance will be cancelled if stream parsing encounters an error. + public let automaticallyCancelOnStreamError: Bool + + /// Internal mutable state specific to this type. + struct StreamMutableState { + /// `OutputStream` bound to the `InputStream` produced by `asInputStream`, if it has been called. + var outputStream: OutputStream? + /// Stream closures called as `Data` is received. + var streams: [@Sendable (_ data: Data) -> Void] = [] + /// Number of currently executing streams. Used to ensure completions are only fired after all streams are + /// enqueued. + var numberOfExecutingStreams = 0 + /// Completion calls enqueued while streams are still executing. + var enqueuedCompletionEvents: [@Sendable () -> Void] = [] + /// Handler for any `HTTPURLResponse`s received. + var httpResponseHandler: (queue: DispatchQueue, + handler: @Sendable (_ response: HTTPURLResponse, + _ completionHandler: @escaping @Sendable (ResponseDisposition) -> Void) -> Void)? + } + + let streamMutableState = Protected(StreamMutableState()) + + /// Creates a `DataStreamRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` + /// by default. + /// - convertible: `URLRequestConvertible` value used to create `URLRequest`s for this + /// instance. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance will be cancelled when an `Error` + /// is thrown while serializing stream `Data`. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default + /// targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by + /// the `Request`. + init(id: UUID = UUID(), + convertible: any URLRequestConvertible, + automaticallyCancelOnStreamError: Bool, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: (any EventMonitor)?, + interceptor: (any RequestInterceptor)?, + delegate: any RequestDelegate) { + self.convertible = convertible + self.automaticallyCancelOnStreamError = automaticallyCancelOnStreamError + + super.init(id: id, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + let copiedRequest = request + return session.dataTask(with: copiedRequest) + } + + override func finish(error: AFError? = nil) { + streamMutableState.write { state in + state.outputStream?.close() + } + + super.finish(error: error) + } + + func didReceive(data: Data) { + streamMutableState.write { state in + #if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation. + if let stream = state.outputStream { + underlyingQueue.async { + var bytes = Array(data) + stream.write(&bytes, maxLength: bytes.count) + } + } + #endif + state.numberOfExecutingStreams += state.streams.count + underlyingQueue.async { [streams = state.streams] in streams.forEach { $0(data) } } + } + } + + func didReceiveResponse(_ response: HTTPURLResponse, completionHandler: @escaping @Sendable (URLSession.ResponseDisposition) -> Void) { + streamMutableState.read { dataMutableState in + guard let httpResponseHandler = dataMutableState.httpResponseHandler else { + underlyingQueue.async { completionHandler(.allow) } + return + } + + httpResponseHandler.queue.async { + httpResponseHandler.handler(response) { disposition in + if disposition == .cancel { + self.mutableState.write { mutableState in + mutableState.state = .cancelled + mutableState.error = mutableState.error ?? AFError.explicitlyCancelled + } + } + + self.underlyingQueue.async { + completionHandler(disposition.sessionDisposition) + } + } + } + } + } + + /// Validates the `URLRequest` and `HTTPURLResponse` received for the instance using the provided `Validation` closure. + /// + /// - Parameter validation: `Validation` closure used to validate the request and response. + /// + /// - Returns: The `DataStreamRequest`. + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validator: () -> Void = { [unowned self] in + guard error == nil, let response else { return } + + let result = validation(request, response) + + if case let .failure(error) = result { + self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) + } + + eventMonitor?.request(self, + didValidateRequest: request, + response: response, + withResult: result) + } + + validators.write { $0.append(validator) } + + return self + } + + #if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation. + /// Produces an `InputStream` that receives the `Data` received by the instance. + /// + /// - Note: The `InputStream` produced by this method must have `open()` called before being able to read `Data`. + /// Additionally, this method will automatically call `resume()` on the instance, regardless of whether or + /// not the creating session has `startRequestsImmediately` set to `true`. + /// + /// - Parameter bufferSize: Size, in bytes, of the buffer between the `OutputStream` and `InputStream`. + /// + /// - Returns: The `InputStream` bound to the internal `OutboundStream`. + public func asInputStream(bufferSize: Int = 1024) -> InputStream? { + defer { resume() } + + var inputStream: InputStream? + streamMutableState.write { state in + Foundation.Stream.getBoundStreams(withBufferSize: bufferSize, + inputStream: &inputStream, + outputStream: &state.outputStream) + state.outputStream?.open() + } + + return inputStream + } + #endif + + /// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse` and providing a completion + /// handler to return a `ResponseDisposition` value. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the closure will be called. `.main` by default. + /// - handler: Closure called when the instance produces an `HTTPURLResponse`. The `completionHandler` provided + /// MUST be called, otherwise the request will never complete. + /// + /// - Returns: The instance. + @_disfavoredOverload + @preconcurrency + @discardableResult + public func onHTTPResponse( + on queue: DispatchQueue = .main, + perform handler: @escaping @Sendable (_ response: HTTPURLResponse, + _ completionHandler: @escaping @Sendable (ResponseDisposition) -> Void) -> Void + ) -> Self { + streamMutableState.write { mutableState in + mutableState.httpResponseHandler = (queue, handler) + } + + return self + } + + /// Sets a closure called whenever the `DataRequest` produces an `HTTPURLResponse`. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the closure will be called. `.main` by default. + /// - handler: Closure called when the instance produces an `HTTPURLResponse`. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func onHTTPResponse(on queue: DispatchQueue = .main, + perform handler: @escaping @Sendable (HTTPURLResponse) -> Void) -> Self { + onHTTPResponse(on: queue) { response, completionHandler in + handler(response) + completionHandler(.allow) + } + + return self + } + + func capturingError(from closure: () throws -> Void) { + do { + try closure() + } catch { + self.error = error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) + cancel() + } + } + + func appendStreamCompletion(on queue: DispatchQueue, + stream: @escaping Handler) where Success: Sendable, Failure: Sendable { + appendResponseSerializer { + self.underlyingQueue.async { + self.responseSerializerDidComplete { + self.streamMutableState.write { state in + guard state.numberOfExecutingStreams == 0 else { + state.enqueuedCompletionEvents.append { + self.enqueueCompletion(on: queue, stream: stream) + } + + return + } + + self.enqueueCompletion(on: queue, stream: stream) + } + } + } + } + } + + func enqueueCompletion(on queue: DispatchQueue, + stream: @escaping Handler) where Success: Sendable, Failure: Sendable { + queue.async { + do { + let completion = Completion(request: self.request, + response: self.response, + metrics: self.metrics, + error: self.error) + try stream(.init(event: .complete(completion), token: .init(self))) + } catch { + // Ignore error, as errors on Completion can't be handled anyway. + } + } + } + + // MARK: Response Serialization + + /// Adds a `StreamHandler` which performs no parsing on incoming `Data`. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @preconcurrency + @discardableResult + public func responseStream(on queue: DispatchQueue = .main, stream: @escaping Handler) -> Self { + let parser = { @Sendable [unowned self] (data: Data) in + queue.async { + self.capturingError { + try stream(.init(event: .stream(.success(data)), token: .init(self))) + } + + self.updateAndCompleteIfPossible() + } + } + + streamMutableState.write { $0.streams.append(parser) } + appendStreamCompletion(on: queue, stream: stream) + + return self + } + + /// Adds a `StreamHandler` which uses the provided `DataStreamSerializer` to process incoming `Data`. + /// + /// - Parameters: + /// - serializer: `DataStreamSerializer` used to process incoming `Data`. Its work is done on the `serializationQueue`. + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @preconcurrency + @discardableResult + public func responseStream(using serializer: Serializer, + on queue: DispatchQueue = .main, + stream: @escaping Handler) -> Self { + let parser = { @Sendable [unowned self] (data: Data) in + serializationQueue.async { + // Start work on serialization queue. + let result = Result { try serializer.serialize(data) } + .mapError { $0.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: $0))) } + // End work on serialization queue. + self.underlyingQueue.async { + self.eventMonitor?.request(self, didParseStream: result) + + if result.isFailure, self.automaticallyCancelOnStreamError { + self.cancel() + } + + queue.async { + self.capturingError { + try stream(.init(event: .stream(result), token: .init(self))) + } + + self.updateAndCompleteIfPossible() + } + } + } + } + + streamMutableState.write { $0.streams.append(parser) } + appendStreamCompletion(on: queue, stream: stream) + + return self + } + + /// Adds a `StreamHandler` which parses incoming `Data` as a UTF8 `String`. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @preconcurrency + @discardableResult + public func responseStreamString(on queue: DispatchQueue = .main, + stream: @escaping Handler) -> Self { + let parser = { @Sendable [unowned self] (data: Data) in + serializationQueue.async { + // Start work on serialization queue. + let string = String(decoding: data, as: UTF8.self) + // End work on serialization queue. + self.underlyingQueue.async { + self.eventMonitor?.request(self, didParseStream: .success(string)) + + queue.async { + self.capturingError { + try stream(.init(event: .stream(.success(string)), token: .init(self))) + } + + self.updateAndCompleteIfPossible() + } + } + } + } + + streamMutableState.write { $0.streams.append(parser) } + appendStreamCompletion(on: queue, stream: stream) + + return self + } + + private func updateAndCompleteIfPossible() { + streamMutableState.write { state in + state.numberOfExecutingStreams -= 1 + + guard state.numberOfExecutingStreams == 0, !state.enqueuedCompletionEvents.isEmpty else { return } + + let completionEvents = state.enqueuedCompletionEvents + self.underlyingQueue.async { completionEvents.forEach { $0() } } + state.enqueuedCompletionEvents.removeAll() + } + } + + /// Adds a `StreamHandler` which parses incoming `Data` using the provided `DataDecoder`. + /// + /// - Parameters: + /// - type: `Decodable` type to parse incoming `Data` into. + /// - queue: `DispatchQueue` on which to perform `StreamHandler` closure. + /// - decoder: `DataDecoder` used to decode the incoming `Data`. + /// - preprocessor: `DataPreprocessor` used to process the incoming `Data` before it's passed to the `decoder`. + /// - stream: `StreamHandler` closure called as `Data` is received. May be called multiple times. + /// + /// - Returns: The `DataStreamRequest`. + @preconcurrency + @discardableResult + public func responseStreamDecodable(of type: T.Type = T.self, + on queue: DispatchQueue = .main, + using decoder: any DataDecoder = JSONDecoder(), + preprocessor: any DataPreprocessor = PassthroughPreprocessor(), + stream: @escaping Handler) -> Self where T: Sendable { + responseStream(using: DecodableStreamSerializer(decoder: decoder, dataPreprocessor: preprocessor), + on: queue, + stream: stream) + } +} + +extension DataStreamRequest.Stream { + /// Incoming `Result` values from `Event.stream`. + public var result: Result? { + guard case let .stream(result) = event else { return nil } + + return result + } + + /// `Success` value of the instance, if any. + public var value: Success? { + guard case let .success(value) = result else { return nil } + + return value + } + + /// `Failure` value of the instance, if any. + public var error: Failure? { + guard case let .failure(error) = result else { return nil } + + return error + } + + /// `Completion` value of the instance, if any. + public var completion: DataStreamRequest.Completion? { + guard case let .complete(completion) = event else { return nil } + + return completion + } +} + +// MARK: - Serialization + +/// A type which can serialize incoming `Data`. +public protocol DataStreamSerializer: Sendable { + /// Type produced from the serialized `Data`. + associatedtype SerializedObject: Sendable + + /// Serializes incoming `Data` into a `SerializedObject` value. + /// + /// - Parameter data: `Data` to be serialized. + /// + /// - Throws: Any error produced during serialization. + func serialize(_ data: Data) throws -> SerializedObject +} + +/// `DataStreamSerializer` which uses the provided `DataPreprocessor` and `DataDecoder` to serialize the incoming `Data`. +public struct DecodableStreamSerializer: DataStreamSerializer where T: Sendable { + /// `DataDecoder` used to decode incoming `Data`. + public let decoder: any DataDecoder + /// `DataPreprocessor` incoming `Data` is passed through before being passed to the `DataDecoder`. + public let dataPreprocessor: any DataPreprocessor + + /// Creates an instance with the provided `DataDecoder` and `DataPreprocessor`. + /// - Parameters: + /// - decoder: ` DataDecoder` used to decode incoming `Data`. `JSONDecoder()` by default. + /// - dataPreprocessor: `DataPreprocessor` used to process incoming `Data` before it's passed through the + /// `decoder`. `PassthroughPreprocessor()` by default. + public init(decoder: any DataDecoder = JSONDecoder(), dataPreprocessor: any DataPreprocessor = PassthroughPreprocessor()) { + self.decoder = decoder + self.dataPreprocessor = dataPreprocessor + } + + public func serialize(_ data: Data) throws -> T { + let processedData = try dataPreprocessor.preprocess(data) + do { + return try decoder.decode(T.self, from: processedData) + } catch { + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) + } + } +} + +/// `DataStreamSerializer` which performs no serialization on incoming `Data`. +public struct PassthroughStreamSerializer: DataStreamSerializer { + /// Creates an instance. + public init() {} + + public func serialize(_ data: Data) throws -> Data { data } +} + +/// `DataStreamSerializer` which serializes incoming stream `Data` into `UTF8`-decoded `String` values. +public struct StringStreamSerializer: DataStreamSerializer { + /// Creates an instance. + public init() {} + + public func serialize(_ data: Data) throws -> String { + String(decoding: data, as: UTF8.self) + } +} + +extension DataStreamSerializer { + /// Creates a `DecodableStreamSerializer` instance with the provided `DataDecoder` and `DataPreprocessor`. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from stream data. + /// - decoder: ` DataDecoder` used to decode incoming `Data`. `JSONDecoder()` by default. + /// - dataPreprocessor: `DataPreprocessor` used to process incoming `Data` before it's passed through the + /// `decoder`. `PassthroughPreprocessor()` by default. + public static func decodable(of type: T.Type, + decoder: any DataDecoder = JSONDecoder(), + dataPreprocessor: any DataPreprocessor = PassthroughPreprocessor()) -> Self where Self == DecodableStreamSerializer { + DecodableStreamSerializer(decoder: decoder, dataPreprocessor: dataPreprocessor) + } +} + +extension DataStreamSerializer where Self == PassthroughStreamSerializer { + /// Provides a `PassthroughStreamSerializer` instance. + public static var passthrough: PassthroughStreamSerializer { PassthroughStreamSerializer() } +} + +extension DataStreamSerializer where Self == StringStreamSerializer { + /// Provides a `StringStreamSerializer` instance. + public static var string: StringStreamSerializer { StringStreamSerializer() } +} diff --git a/Pods/Alamofire/Source/Core/DownloadRequest.swift b/Pods/Alamofire/Source/Core/DownloadRequest.swift new file mode 100644 index 0000000..85e287e --- /dev/null +++ b/Pods/Alamofire/Source/Core/DownloadRequest.swift @@ -0,0 +1,607 @@ +// +// DownloadRequest.swift +// +// Copyright (c) 2014-2024 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// `Request` subclass which downloads `Data` to a file on disk using `URLSessionDownloadTask`. +public final class DownloadRequest: Request, @unchecked Sendable { + /// A set of options to be executed prior to moving a downloaded file from the temporary `URL` to the destination + /// `URL`. + public struct Options: OptionSet, Sendable { + /// Specifies that intermediate directories for the destination URL should be created. + public static let createIntermediateDirectories = Options(rawValue: 1 << 0) + /// Specifies that any previous file at the destination `URL` should be removed. + public static let removePreviousFile = Options(rawValue: 1 << 1) + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + } + + // MARK: Destination + + /// A closure executed once a `DownloadRequest` has successfully completed in order to determine where to move the + /// temporary file written to during the download process. The closure takes two arguments: the temporary file URL + /// and the `HTTPURLResponse`, and returns two values: the file URL where the temporary file should be moved and + /// the options defining how the file should be moved. + /// + /// - Note: Downloads from a local `file://` `URL`s do not use the `Destination` closure, as those downloads do not + /// return an `HTTPURLResponse`. Instead the file is merely moved within the temporary directory. + public typealias Destination = @Sendable (_ temporaryURL: URL, + _ response: HTTPURLResponse) -> (destinationURL: URL, options: Options) + + /// Creates a download file destination closure which uses the default file manager to move the temporary file to a + /// file URL in the first available directory with the specified search path directory and search path domain mask. + /// + /// - Parameters: + /// - directory: The search path directory. `.documentDirectory` by default. + /// - domain: The search path domain mask. `.userDomainMask` by default. + /// - options: `DownloadRequest.Options` used when moving the downloaded file to its destination. None by + /// default. + /// - Returns: The `Destination` closure. + public class func suggestedDownloadDestination(for directory: FileManager.SearchPathDirectory = .documentDirectory, + in domain: FileManager.SearchPathDomainMask = .userDomainMask, + options: Options = []) -> Destination { + { temporaryURL, response in + let directoryURLs = FileManager.default.urls(for: directory, in: domain) + let url = directoryURLs.first?.appendingPathComponent(response.suggestedFilename!) ?? temporaryURL + + return (url, options) + } + } + + /// Default `Destination` used by Alamofire to ensure all downloads persist. This `Destination` prepends + /// `Alamofire_` to the automatically generated download name and moves it within the temporary directory. Files + /// with this destination must be additionally moved if they should survive the system reclamation of temporary + /// space. + static let defaultDestination: Destination = { url, _ in + (defaultDestinationURL(url), []) + } + + /// Default `URL` creation closure. Creates a `URL` in the temporary directory with `Alamofire_` prepended to the + /// provided file name. + static let defaultDestinationURL: @Sendable (URL) -> URL = { url in + let filename = "Alamofire_\(url.lastPathComponent)" + let destination = url.deletingLastPathComponent().appendingPathComponent(filename) + + return destination + } + + // MARK: Downloadable + + /// Type describing the source used to create the underlying `URLSessionDownloadTask`. + public enum Downloadable { + /// Download should be started from the `URLRequest` produced by the associated `URLRequestConvertible` value. + case request(any URLRequestConvertible) + /// Download should be started from the associated resume `Data` value. + case resumeData(Data) + } + + // MARK: Mutable State + + /// Type containing all mutable state for `DownloadRequest` instances. + private struct DownloadRequestMutableState { + /// Possible resume `Data` produced when cancelling the instance. + var resumeData: Data? + /// `URL` to which `Data` is being downloaded. + var fileURL: URL? + } + + /// Protected mutable state specific to `DownloadRequest`. + private let mutableDownloadState = Protected(DownloadRequestMutableState()) + + /// If the download is resumable and is eventually cancelled or fails, this value may be used to resume the download + /// using the `download(resumingWith data:)` API. + /// + /// - Note: For more information about `resumeData`, see [Apple's documentation](https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel). + public var resumeData: Data? { + #if !canImport(FoundationNetworking) // If we not using swift-corelibs-foundation. + return mutableDownloadState.resumeData ?? error?.downloadResumeData + #else + return mutableDownloadState.resumeData + #endif + } + + /// If the download is successful, the `URL` where the file was downloaded. + public var fileURL: URL? { mutableDownloadState.fileURL } + + // MARK: Initial State + + /// `Downloadable` value used for this instance. + public let downloadable: Downloadable + /// The `Destination` to which the downloaded file is moved. + let destination: Destination + + /// Creates a `DownloadRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - downloadable: `Downloadable` value used to create `URLSessionDownloadTasks` for the instance. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request` + /// - destination: `Destination` closure used to move the downloaded file to its final location. + init(id: UUID = UUID(), + downloadable: Downloadable, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: (any EventMonitor)?, + interceptor: (any RequestInterceptor)?, + delegate: any RequestDelegate, + destination: @escaping Destination) { + self.downloadable = downloadable + self.destination = destination + + super.init(id: id, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + override func reset() { + super.reset() + + mutableDownloadState.write { + $0.resumeData = nil + $0.fileURL = nil + } + } + + /// Called when a download has finished. + /// + /// - Parameters: + /// - task: `URLSessionTask` that finished the download. + /// - result: `Result` of the automatic move to `destination`. + func didFinishDownloading(using task: URLSessionTask, with result: Result) { + eventMonitor?.request(self, didFinishDownloadingUsing: task, with: result) + + switch result { + case let .success(url): mutableDownloadState.fileURL = url + case let .failure(error): self.error = error + } + } + + /// Updates the `downloadProgress` using the provided values. + /// + /// - Parameters: + /// - bytesWritten: Total bytes written so far. + /// - totalBytesExpectedToWrite: Total bytes expected to write. + func updateDownloadProgress(bytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + downloadProgress.totalUnitCount = totalBytesExpectedToWrite + downloadProgress.completedUnitCount += bytesWritten + + downloadProgressHandler?.queue.async { self.downloadProgressHandler?.handler(self.downloadProgress) } + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + session.downloadTask(with: request) + } + + /// Creates a `URLSessionTask` from the provided resume data. + /// + /// - Parameters: + /// - data: `Data` used to resume the download. + /// - session: `URLSession` used to create the `URLSessionTask`. + /// + /// - Returns: The `URLSessionTask` created. + public func task(forResumeData data: Data, using session: URLSession) -> URLSessionTask { + session.downloadTask(withResumeData: data) + } + + /// Cancels the instance. Once cancelled, a `DownloadRequest` can no longer be resumed or suspended. + /// + /// - Note: This method will NOT produce resume data. If you wish to cancel and produce resume data, use + /// `cancel(producingResumeData:)` or `cancel(byProducingResumeData:)`. + /// + /// - Returns: The instance. + @discardableResult + override public func cancel() -> Self { + cancel(producingResumeData: false) + } + + /// Cancels the instance, optionally producing resume data. Once cancelled, a `DownloadRequest` can no longer be + /// resumed or suspended. + /// + /// - Note: If `producingResumeData` is `true`, the `resumeData` property will be populated with any resume data, if + /// available. + /// + /// - Returns: The instance. + @discardableResult + public func cancel(producingResumeData shouldProduceResumeData: Bool) -> Self { + cancel(optionallyProducingResumeData: shouldProduceResumeData ? { @Sendable _ in } : nil) + } + + /// Cancels the instance while producing resume data. Once cancelled, a `DownloadRequest` can no longer be resumed + /// or suspended. + /// + /// - Note: The resume data passed to the completion handler will also be available on the instance's `resumeData` + /// property. + /// + /// - Parameter completionHandler: The completion handler that is called when the download has been successfully + /// cancelled. It is not guaranteed to be called on a particular queue, so you may + /// want use an appropriate queue to perform your work. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func cancel(byProducingResumeData completionHandler: @escaping @Sendable (_ data: Data?) -> Void) -> Self { + cancel(optionallyProducingResumeData: completionHandler) + } + + /// Internal implementation of cancellation that optionally takes a resume data handler. If no handler is passed, + /// cancellation is performed without producing resume data. + /// + /// - Parameter completionHandler: Optional resume data handler. + /// + /// - Returns: The instance. + private func cancel(optionallyProducingResumeData completionHandler: (@Sendable (_ resumeData: Data?) -> Void)?) -> Self { + mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.cancelled) else { return } + + mutableState.state = .cancelled + + underlyingQueue.async { self.didCancel() } + + guard let task = mutableState.tasks.last as? URLSessionDownloadTask, task.state != .completed else { + underlyingQueue.async { self.finish() } + return + } + + if let completionHandler { + // Resume to ensure metrics are gathered. + task.resume() + task.cancel { resumeData in + self.mutableDownloadState.resumeData = resumeData + self.underlyingQueue.async { self.didCancelTask(task) } + completionHandler(resumeData) + } + } else { + // Resume to ensure metrics are gathered. + task.resume() + task.cancel() + self.underlyingQueue.async { self.didCancelTask(task) } + } + } + + return self + } + + /// Validates the request, using the specified closure. + /// + /// - Note: If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Parameter validation: `Validation` closure to validate the response. + /// + /// - Returns: The instance. + @discardableResult + public func validate(_ validation: @escaping Validation) -> Self { + let validator: () -> Void = { [unowned self] in + guard error == nil, let response else { return } + + let result = validation(request, response, fileURL) + + if case let .failure(error) = result { + self.error = error.asAFError(or: .responseValidationFailed(reason: .customValidationFailed(error: error))) + } + + eventMonitor?.request(self, + didValidateRequest: request, + response: response, + fileURL: fileURL, + withResult: result) + } + + validators.write { $0.append(validator) } + + return self + } + + // MARK: - Response Serialization + + /// Adds a handler to be called once the request has finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func response(queue: DispatchQueue = .main, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) + -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let result = AFResult(value: self.fileURL, error: self.error) + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DownloadResponse(request: self.request, + response: self.response, + fileURL: self.fileURL, + resumeData: self.resumeData, + metrics: self.metrics, + serializationDuration: 0, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + } + } + + return self + } + + private func _response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) + -> Self { + appendResponseSerializer { + // Start work that should be on the serialization queue. + let start = ProcessInfo.processInfo.systemUptime + let result: AFResult = Result { + try responseSerializer.serializeDownload(request: self.request, + response: self.response, + fileURL: self.fileURL, + error: self.error) + }.mapError { error in + error.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: error))) + } + let end = ProcessInfo.processInfo.systemUptime + // End work that should be on the serialization queue. + + self.underlyingQueue.async { + let response = DownloadResponse(request: self.request, + response: self.response, + fileURL: self.fileURL, + resumeData: self.resumeData, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + self.eventMonitor?.request(self, didParseResponse: response) + + guard let serializerError = result.failure, let delegate = self.delegate else { + self.responseSerializerDidComplete { queue.async { completionHandler(response) } } + return + } + + delegate.retryResult(for: self, dueTo: serializerError) { retryResult in + var didComplete: (@Sendable () -> Void)? + + defer { + if let didComplete { + self.responseSerializerDidComplete { queue.async { didComplete() } } + } + } + + switch retryResult { + case .doNotRetry: + didComplete = { completionHandler(response) } + + case let .doNotRetryWithError(retryError): + let result: AFResult = .failure(retryError.asAFError(orFailWith: "Received retryError was not already AFError")) + + let response = DownloadResponse(request: self.request, + response: self.response, + fileURL: self.fileURL, + resumeData: self.resumeData, + metrics: self.metrics, + serializationDuration: end - start, + result: result) + + didComplete = { completionHandler(response) } + + case .retry, .retryWithDelay: + delegate.retryRequest(self, withDelay: retryResult.delay) + } + } + } + } + + return self + } + + /// Adds a handler to be called once the request has finished. + /// + /// - Note: This handler will read the entire downloaded file into memory, use with caution. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - responseSerializer: The response serializer responsible for serializing the request, response, and data + /// contained in the destination `URL`. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) + -> Self { + _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) + } + + /// Adds a handler to be called once the request has finished. + /// + /// - Note: This handler will read the entire downloaded file into memory, use with caution. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - responseSerializer: The response serializer responsible for serializing the request, response, and data + /// contained in the destination `URL`. + /// - completionHandler: The code to be executed once the request has finished. + /// + /// - Returns: The request. + @discardableResult + public func response(queue: DispatchQueue = .main, + responseSerializer: Serializer, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) + -> Self { + _response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler) + } + + /// Adds a handler using a `URLResponseSerializer` to be called once the request is finished. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is called. `.main` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func responseURL(queue: DispatchQueue = .main, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) -> Self { + response(queue: queue, responseSerializer: URLResponseSerializer(), completionHandler: completionHandler) + } + + /// Adds a handler using a `DataResponseSerializer` to be called once the request has finished. + /// + /// - Note: This handler will read the entire downloaded file into memory, use with caution. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is called. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func responseData(queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: DataResponseSerializer(dataPreprocessor: dataPreprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + completionHandler: completionHandler) + } + + /// Adds a handler using a `StringResponseSerializer` to be called once the request has finished. + /// + /// - Note: This handler will read the entire downloaded file into memory, use with caution. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined + /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func responseString(queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor, + encoding: encoding, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + completionHandler: completionHandler) + } + + /// Adds a handler using a `JSONResponseSerializer` to be called once the request has finished. + /// + /// - Note: This handler will read the entire downloaded file into memory, use with caution. + /// + /// - Parameters: + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - options: `JSONSerialization.ReadingOptions` used when parsing the response. `.allowFragments` + /// by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @available(*, deprecated, message: "responseJSON deprecated and will be removed in Alamofire 6. Use responseDecodable instead.") + @preconcurrency + @discardableResult + public func responseJSON(queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = JSONResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = JSONResponseSerializer.defaultEmptyRequestMethods, + options: JSONSerialization.ReadingOptions = .allowFragments, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) -> Self { + response(queue: queue, + responseSerializer: JSONResponseSerializer(dataPreprocessor: dataPreprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods, + options: options), + completionHandler: completionHandler) + } + + /// Adds a handler using a `DecodableResponseSerializer` to be called once the request has finished. + /// + /// - Note: This handler will read the entire downloaded file into memory, use with caution. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from response data. + /// - queue: The queue on which the completion handler is dispatched. `.main` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// `completionHandler`. `PassthroughPreprocessor()` by default. + /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// - completionHandler: A closure to be executed once the request has finished. + /// + /// - Returns: The request. + @preconcurrency + @discardableResult + public func responseDecodable(of type: T.Type = T.self, + queue: DispatchQueue = .main, + dataPreprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods, + completionHandler: @escaping @Sendable (AFDownloadResponse) -> Void) -> Self where T: Sendable { + response(queue: queue, + responseSerializer: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + completionHandler: completionHandler) + } +} diff --git a/Pods/Alamofire/Source/Core/HTTPHeaders.swift b/Pods/Alamofire/Source/Core/HTTPHeaders.swift new file mode 100644 index 0000000..dbdcabc --- /dev/null +++ b/Pods/Alamofire/Source/Core/HTTPHeaders.swift @@ -0,0 +1,465 @@ +// +// HTTPHeaders.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// An order-preserving and case-insensitive representation of HTTP headers. +public struct HTTPHeaders: Equatable, Hashable, Sendable { + private var headers: [HTTPHeader] = [] + + /// Creates an empty instance. + public init() {} + + /// Creates an instance from an array of `HTTPHeader`s. Duplicate case-insensitive names are collapsed into the last + /// name and value encountered. + public init(_ headers: [HTTPHeader]) { + headers.forEach { update($0) } + } + + /// Creates an instance from a `[String: String]`. Duplicate case-insensitive names are collapsed into the last name + /// and value encountered. + public init(_ dictionary: [String: String]) { + dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) } + } + + /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. + /// + /// - Parameters: + /// - name: The `HTTPHeader` name. + /// - value: The `HTTPHeader` value. + public mutating func add(name: String, value: String) { + update(HTTPHeader(name: name, value: value)) + } + + /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. + /// + /// - Parameter header: The `HTTPHeader` to update or append. + public mutating func add(_ header: HTTPHeader) { + update(header) + } + + /// Case-insensitively updates or appends an `HTTPHeader` into the instance using the provided `name` and `value`. + /// + /// - Parameters: + /// - name: The `HTTPHeader` name. + /// - value: The `HTTPHeader` value. + public mutating func update(name: String, value: String) { + update(HTTPHeader(name: name, value: value)) + } + + /// Case-insensitively updates or appends the provided `HTTPHeader` into the instance. + /// + /// - Parameter header: The `HTTPHeader` to update or append. + public mutating func update(_ header: HTTPHeader) { + guard let index = headers.index(of: header.name) else { + headers.append(header) + return + } + + headers.replaceSubrange(index...index, with: [header]) + } + + /// Case-insensitively removes an `HTTPHeader`, if it exists, from the instance. + /// + /// - Parameter name: The name of the `HTTPHeader` to remove. + public mutating func remove(name: String) { + guard let index = headers.index(of: name) else { return } + + headers.remove(at: index) + } + + /// Sort the current instance by header name, case insensitively. + public mutating func sort() { + headers.sort { $0.name.lowercased() < $1.name.lowercased() } + } + + /// Returns an instance sorted by header name. + /// + /// - Returns: A copy of the current instance sorted by name. + public func sorted() -> HTTPHeaders { + var headers = self + headers.sort() + + return headers + } + + /// Case-insensitively find a header's value by name. + /// + /// - Parameter name: The name of the header to search for, case-insensitively. + /// + /// - Returns: The value of header, if it exists. + public func value(for name: String) -> String? { + guard let index = headers.index(of: name) else { return nil } + + return headers[index].value + } + + /// Case-insensitively access the header with the given name. + /// + /// - Parameter name: The name of the header. + public subscript(_ name: String) -> String? { + get { value(for: name) } + set { + if let value = newValue { + update(name: name, value: value) + } else { + remove(name: name) + } + } + } + + /// The dictionary representation of all headers. + /// + /// This representation does not preserve the current order of the instance. + public var dictionary: [String: String] { + let namesAndValues = headers.map { ($0.name, $0.value) } + + return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last }) + } +} + +extension HTTPHeaders: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, String)...) { + elements.forEach { update(name: $0.0, value: $0.1) } + } +} + +extension HTTPHeaders: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: HTTPHeader...) { + self.init(elements) + } +} + +extension HTTPHeaders: Sequence { + public func makeIterator() -> IndexingIterator<[HTTPHeader]> { + headers.makeIterator() + } +} + +extension HTTPHeaders: Collection { + public var startIndex: Int { + headers.startIndex + } + + public var endIndex: Int { + headers.endIndex + } + + public subscript(position: Int) -> HTTPHeader { + headers[position] + } + + public func index(after i: Int) -> Int { + headers.index(after: i) + } +} + +extension HTTPHeaders: CustomStringConvertible { + public var description: String { + headers.map(\.description) + .joined(separator: "\n") + } +} + +// MARK: - HTTPHeader + +/// A representation of a single HTTP header's name / value pair. +public struct HTTPHeader: Equatable, Hashable, Sendable { + /// Name of the header. + public let name: String + + /// Value of the header. + public let value: String + + /// Creates an instance from the given `name` and `value`. + /// + /// - Parameters: + /// - name: The name of the header. + /// - value: The value of the header. + public init(name: String, value: String) { + self.name = name + self.value = value + } +} + +extension HTTPHeader: CustomStringConvertible { + public var description: String { + "\(name): \(value)" + } +} + +extension HTTPHeader { + /// Returns an `Accept` header. + /// + /// - Parameter value: The `Accept` value. + /// - Returns: The header. + public static func accept(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept", value: value) + } + + /// Returns an `Accept-Charset` header. + /// + /// - Parameter value: The `Accept-Charset` value. + /// - Returns: The header. + public static func acceptCharset(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept-Charset", value: value) + } + + /// Returns an `Accept-Language` header. + /// + /// Alamofire offers a default Accept-Language header that accumulates and encodes the system's preferred languages. + /// Use `HTTPHeader.defaultAcceptLanguage`. + /// + /// - Parameter value: The `Accept-Language` value. + /// + /// - Returns: The header. + public static func acceptLanguage(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept-Language", value: value) + } + + /// Returns an `Accept-Encoding` header. + /// + /// Alamofire offers a default accept encoding value that provides the most common values. Use + /// `HTTPHeader.defaultAcceptEncoding`. + /// + /// - Parameter value: The `Accept-Encoding` value. + /// + /// - Returns: The header + public static func acceptEncoding(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Accept-Encoding", value: value) + } + + /// Returns a `Basic` `Authorization` header using the `username` and `password` provided. + /// + /// - Parameters: + /// - username: The username of the header. + /// - password: The password of the header. + /// + /// - Returns: The header. + public static func authorization(username: String, password: String) -> HTTPHeader { + let credential = Data("\(username):\(password)".utf8).base64EncodedString() + + return authorization("Basic \(credential)") + } + + /// Returns a `Bearer` `Authorization` header using the `bearerToken` provided. + /// + /// - Parameter bearerToken: The bearer token. + /// + /// - Returns: The header. + public static func authorization(bearerToken: String) -> HTTPHeader { + authorization("Bearer \(bearerToken)") + } + + /// Returns an `Authorization` header. + /// + /// Alamofire provides built-in methods to produce `Authorization` headers. For a Basic `Authorization` header use + /// `HTTPHeader.authorization(username:password:)`. For a Bearer `Authorization` header, use + /// `HTTPHeader.authorization(bearerToken:)`. + /// + /// - Parameter value: The `Authorization` value. + /// + /// - Returns: The header. + public static func authorization(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Authorization", value: value) + } + + /// Returns a `Content-Disposition` header. + /// + /// - Parameter value: The `Content-Disposition` value. + /// + /// - Returns: The header. + public static func contentDisposition(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Content-Disposition", value: value) + } + + /// Returns a `Content-Encoding` header. + /// + /// - Parameter value: The `Content-Encoding`. + /// + /// - Returns: The header. + public static func contentEncoding(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Content-Encoding", value: value) + } + + /// Returns a `Content-Type` header. + /// + /// All Alamofire `ParameterEncoding`s and `ParameterEncoder`s set the `Content-Type` of the request, so it may not + /// be necessary to manually set this value. + /// + /// - Parameter value: The `Content-Type` value. + /// + /// - Returns: The header. + public static func contentType(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Content-Type", value: value) + } + + /// Returns a `User-Agent` header. + /// + /// - Parameter value: The `User-Agent` value. + /// + /// - Returns: The header. + public static func userAgent(_ value: String) -> HTTPHeader { + HTTPHeader(name: "User-Agent", value: value) + } + + /// Returns a `Sec-WebSocket-Protocol` header. + /// + /// - Parameter value: The `Sec-WebSocket-Protocol` value. + /// - Returns: The header. + public static func websocketProtocol(_ value: String) -> HTTPHeader { + HTTPHeader(name: "Sec-WebSocket-Protocol", value: value) + } +} + +extension [HTTPHeader] { + /// Case-insensitively finds the index of an `HTTPHeader` with the provided name, if it exists. + func index(of name: String) -> Int? { + let lowercasedName = name.lowercased() + return firstIndex { $0.name.lowercased() == lowercasedName } + } +} + +// MARK: - Defaults + +extension HTTPHeaders { + /// The default set of `HTTPHeaders` used by Alamofire. Includes `Accept-Encoding`, `Accept-Language`, and + /// `User-Agent`. + public static let `default`: HTTPHeaders = [.defaultAcceptEncoding, + .defaultAcceptLanguage, + .defaultUserAgent] +} + +extension HTTPHeader { + /// Returns Alamofire's default `Accept-Encoding` header, appropriate for the encodings supported by particular OS + /// versions. + /// + /// See the [Accept-Encoding HTTP header documentation](https://tools.ietf.org/html/rfc7230#section-4.2.3) . + public static let defaultAcceptEncoding: HTTPHeader = { + let encodings: [String] = if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) { + ["br", "gzip", "deflate"] + } else { + ["gzip", "deflate"] + } + + return .acceptEncoding(encodings.qualityEncoded()) + }() + + /// Returns Alamofire's default `Accept-Language` header, generated by querying `Locale` for the user's + /// `preferredLanguages`. + /// + /// See the [Accept-Language HTTP header documentation](https://tools.ietf.org/html/rfc7231#section-5.3.5). + public static let defaultAcceptLanguage: HTTPHeader = .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded()) + + /// Returns Alamofire's default `User-Agent` header. + /// + /// See the [User-Agent header documentation](https://tools.ietf.org/html/rfc7231#section-5.5.3). + /// + /// Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0` + public static let defaultUserAgent: HTTPHeader = { + let info = Bundle.main.infoDictionary + let executable = (info?["CFBundleExecutable"] as? String) ?? + (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ?? + "Unknown" + let bundle = info?["CFBundleIdentifier"] as? String ?? "Unknown" + let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown" + let appBuild = info?["CFBundleVersion"] as? String ?? "Unknown" + + let osNameVersion: String = { + let version = ProcessInfo.processInfo.operatingSystemVersion + let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)" + let osName: String = { + #if os(iOS) + #if targetEnvironment(macCatalyst) + return "macOS(Catalyst)" + #else + return "iOS" + #endif + #elseif os(watchOS) + return "watchOS" + #elseif os(tvOS) + return "tvOS" + #elseif os(macOS) + #if targetEnvironment(macCatalyst) + return "macOS(Catalyst)" + #else + return "macOS" + #endif + #elseif swift(>=5.9.2) && os(visionOS) + return "visionOS" + #elseif os(Linux) + return "Linux" + #elseif os(Windows) + return "Windows" + #elseif os(Android) + return "Android" + #else + return "Unknown" + #endif + }() + + return "\(osName) \(versionString)" + }() + + let alamofireVersion = "Alamofire/\(AFInfo.version)" + + let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)" + + return .userAgent(userAgent) + }() +} + +extension Collection { + func qualityEncoded() -> String { + enumerated().map { index, encoding in + let quality = 1.0 - (Double(index) * 0.1) + return "\(encoding);q=\(quality)" + }.joined(separator: ", ") + } +} + +// MARK: - System Type Extensions + +extension URLRequest { + /// Returns `allHTTPHeaderFields` as `HTTPHeaders`. + public var headers: HTTPHeaders { + get { allHTTPHeaderFields.map(HTTPHeaders.init) ?? HTTPHeaders() } + set { allHTTPHeaderFields = newValue.dictionary } + } +} + +extension HTTPURLResponse { + /// Returns `allHeaderFields` as `HTTPHeaders`. + public var headers: HTTPHeaders { + (allHeaderFields as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() + } +} + +extension URLSessionConfiguration { + /// Returns `httpAdditionalHeaders` as `HTTPHeaders`. + public var headers: HTTPHeaders { + get { (httpAdditionalHeaders as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() } + set { httpAdditionalHeaders = newValue.dictionary } + } +} diff --git a/Pods/Alamofire/Source/Core/HTTPMethod.swift b/Pods/Alamofire/Source/Core/HTTPMethod.swift new file mode 100644 index 0000000..ed51b68 --- /dev/null +++ b/Pods/Alamofire/Source/Core/HTTPMethod.swift @@ -0,0 +1,56 @@ +// +// HTTPMethod.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +/// Type representing HTTP methods. Raw `String` value is stored and compared case-sensitively, so +/// `HTTPMethod.get != HTTPMethod(rawValue: "get")`. +/// +/// See https://tools.ietf.org/html/rfc7231#section-4.3 +public struct HTTPMethod: RawRepresentable, Equatable, Hashable, Sendable { + /// `CONNECT` method. + public static let connect = HTTPMethod(rawValue: "CONNECT") + /// `DELETE` method. + public static let delete = HTTPMethod(rawValue: "DELETE") + /// `GET` method. + public static let get = HTTPMethod(rawValue: "GET") + /// `HEAD` method. + public static let head = HTTPMethod(rawValue: "HEAD") + /// `OPTIONS` method. + public static let options = HTTPMethod(rawValue: "OPTIONS") + /// `PATCH` method. + public static let patch = HTTPMethod(rawValue: "PATCH") + /// `POST` method. + public static let post = HTTPMethod(rawValue: "POST") + /// `PUT` method. + public static let put = HTTPMethod(rawValue: "PUT") + /// `QUERY` method. + public static let query = HTTPMethod(rawValue: "QUERY") + /// `TRACE` method. + public static let trace = HTTPMethod(rawValue: "TRACE") + + public let rawValue: String + + public init(rawValue: String) { + self.rawValue = rawValue + } +} diff --git a/Pods/Alamofire/Source/Core/Notifications.swift b/Pods/Alamofire/Source/Core/Notifications.swift new file mode 100644 index 0000000..6fa9dac --- /dev/null +++ b/Pods/Alamofire/Source/Core/Notifications.swift @@ -0,0 +1,118 @@ +// +// Notifications.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +extension Request { + /// Posted when a `Request` is resumed. The `Notification` contains the resumed `Request`. + public static let didResumeNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResume") + /// Posted when a `Request` is suspended. The `Notification` contains the suspended `Request`. + public static let didSuspendNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspend") + /// Posted when a `Request` is cancelled. The `Notification` contains the cancelled `Request`. + public static let didCancelNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancel") + /// Posted when a `Request` is finished. The `Notification` contains the completed `Request`. + public static let didFinishNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didFinish") + + /// Posted when a `URLSessionTask` is resumed. The `Notification` contains the `Request` associated with the `URLSessionTask`. + public static let didResumeTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didResumeTask") + /// Posted when a `URLSessionTask` is suspended. The `Notification` contains the `Request` associated with the `URLSessionTask`. + public static let didSuspendTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didSuspendTask") + /// Posted when a `URLSessionTask` is cancelled. The `Notification` contains the `Request` associated with the `URLSessionTask`. + public static let didCancelTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCancelTask") + /// Posted when a `URLSessionTask` is completed. The `Notification` contains the `Request` associated with the `URLSessionTask`. + public static let didCompleteTaskNotification = Notification.Name(rawValue: "org.alamofire.notification.name.request.didCompleteTask") +} + +// MARK: - + +extension Notification { + /// The `Request` contained by the instance's `userInfo`, `nil` otherwise. + public var request: Request? { + userInfo?[String.requestKey] as? Request + } + + /// Convenience initializer for a `Notification` containing a `Request` payload. + /// + /// - Parameters: + /// - name: The name of the notification. + /// - request: The `Request` payload. + init(name: Notification.Name, request: Request) { + self.init(name: name, object: nil, userInfo: [String.requestKey: request]) + } +} + +extension NotificationCenter { + /// Convenience function for posting notifications with `Request` payloads. + /// + /// - Parameters: + /// - name: The name of the notification. + /// - request: The `Request` payload. + func postNotification(named name: Notification.Name, with request: Request) { + let notification = Notification(name: name, request: request) + post(notification) + } +} + +extension String { + /// User info dictionary key representing the `Request` associated with the notification. + fileprivate static let requestKey = "org.alamofire.notification.key.request" +} + +/// `EventMonitor` that provides Alamofire's notifications. +public final class AlamofireNotifications: EventMonitor { + /// Creates an instance. + public init() {} + + public func requestDidResume(_ request: Request) { + NotificationCenter.default.postNotification(named: Request.didResumeNotification, with: request) + } + + public func requestDidSuspend(_ request: Request) { + NotificationCenter.default.postNotification(named: Request.didSuspendNotification, with: request) + } + + public func requestDidCancel(_ request: Request) { + NotificationCenter.default.postNotification(named: Request.didCancelNotification, with: request) + } + + public func requestDidFinish(_ request: Request) { + NotificationCenter.default.postNotification(named: Request.didFinishNotification, with: request) + } + + public func request(_ request: Request, didResumeTask task: URLSessionTask) { + NotificationCenter.default.postNotification(named: Request.didResumeTaskNotification, with: request) + } + + public func request(_ request: Request, didSuspendTask task: URLSessionTask) { + NotificationCenter.default.postNotification(named: Request.didSuspendTaskNotification, with: request) + } + + public func request(_ request: Request, didCancelTask task: URLSessionTask) { + NotificationCenter.default.postNotification(named: Request.didCancelTaskNotification, with: request) + } + + public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { + NotificationCenter.default.postNotification(named: Request.didCompleteTaskNotification, with: request) + } +} diff --git a/Pods/Alamofire/Source/Core/ParameterEncoder.swift b/Pods/Alamofire/Source/Core/ParameterEncoder.swift new file mode 100644 index 0000000..eadd3c7 --- /dev/null +++ b/Pods/Alamofire/Source/Core/ParameterEncoder.swift @@ -0,0 +1,213 @@ +// +// ParameterEncoder.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// A type that can encode any `Encodable` type into a `URLRequest`. +public protocol ParameterEncoder: Sendable { + /// Encode the provided `Encodable` parameters into `request`. + /// + /// - Parameters: + /// - parameters: The `Encodable` parameter value. + /// - request: The `URLRequest` into which to encode the parameters. + /// + /// - Returns: A `URLRequest` with the result of the encoding. + /// - Throws: An `Error` when encoding fails. For Alamofire provided encoders, this will be an instance of + /// `AFError.parameterEncoderFailed` with an associated `ParameterEncoderFailureReason`. + func encode(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest +} + +/// A `ParameterEncoder` that encodes types as JSON body data. +/// +/// If no `Content-Type` header is already set on the provided `URLRequest`s, it's set to `application/json`. +open class JSONParameterEncoder: @unchecked Sendable, ParameterEncoder { + /// Returns an encoder with default parameters. + public static var `default`: JSONParameterEncoder { JSONParameterEncoder() } + + /// Returns an encoder with `JSONEncoder.outputFormatting` set to `.prettyPrinted`. + public static var prettyPrinted: JSONParameterEncoder { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + return JSONParameterEncoder(encoder: encoder) + } + + /// Returns an encoder with `JSONEncoder.outputFormatting` set to `.sortedKeys`. + @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + public static var sortedKeys: JSONParameterEncoder { + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + return JSONParameterEncoder(encoder: encoder) + } + + /// `JSONEncoder` used to encode parameters. + public let encoder: JSONEncoder + + /// Creates an instance with the provided `JSONEncoder`. + /// + /// - Parameter encoder: The `JSONEncoder`. `JSONEncoder()` by default. + public init(encoder: JSONEncoder = JSONEncoder()) { + self.encoder = encoder + } + + open func encode(_ parameters: Parameters?, + into request: URLRequest) throws -> URLRequest { + guard let parameters else { return request } + + var request = request + + do { + let data = try encoder.encode(parameters) + request.httpBody = data + if request.headers["Content-Type"] == nil { + request.headers.update(.contentType("application/json")) + } + } catch { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) + } + + return request + } +} + +extension ParameterEncoder where Self == JSONParameterEncoder { + /// Provides a default `JSONParameterEncoder` instance. + public static var json: JSONParameterEncoder { JSONParameterEncoder() } + + /// Creates a `JSONParameterEncoder` using the provided `JSONEncoder`. + /// + /// - Parameter encoder: `JSONEncoder` used to encode parameters. `JSONEncoder()` by default. + /// - Returns: The `JSONParameterEncoder`. + public static func json(encoder: JSONEncoder = JSONEncoder()) -> JSONParameterEncoder { + JSONParameterEncoder(encoder: encoder) + } +} + +/// A `ParameterEncoder` that encodes types as URL-encoded query strings to be set on the URL or as body data, depending +/// on the `Destination` set. +/// +/// If no `Content-Type` header is already set on the provided `URLRequest`s, it will be set to +/// `application/x-www-form-urlencoded; charset=utf-8`. +/// +/// Encoding behavior can be customized by passing an instance of `URLEncodedFormEncoder` to the initializer. +open class URLEncodedFormParameterEncoder: @unchecked Sendable, ParameterEncoder { + /// Defines where the URL-encoded string should be set for each `URLRequest`. + public enum Destination { + /// Applies the encoded query string to any existing query string for `.get`, `.head`, and `.delete` request. + /// Sets it to the `httpBody` for all other methods. + case methodDependent + /// Applies the encoded query string to any existing query string from the `URLRequest`. + case queryString + /// Applies the encoded query string to the `httpBody` of the `URLRequest`. + case httpBody + + /// Determines whether the URL-encoded string should be applied to the `URLRequest`'s `url`. + /// + /// - Parameter method: The `HTTPMethod`. + /// + /// - Returns: Whether the URL-encoded string should be applied to a `URL`. + func encodesParametersInURL(for method: HTTPMethod) -> Bool { + switch self { + case .methodDependent: [.get, .head, .delete].contains(method) + case .queryString: true + case .httpBody: false + } + } + } + + /// Returns an encoder with default parameters. + public static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() } + + /// The `URLEncodedFormEncoder` to use. + public let encoder: URLEncodedFormEncoder + + /// The `Destination` for the URL-encoded string. + public let destination: Destination + + /// Creates an instance with the provided `URLEncodedFormEncoder` instance and `Destination` value. + /// + /// - Parameters: + /// - encoder: The `URLEncodedFormEncoder`. `URLEncodedFormEncoder()` by default. + /// - destination: The `Destination`. `.methodDependent` by default. + public init(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: Destination = .methodDependent) { + self.encoder = encoder + self.destination = destination + } + + open func encode(_ parameters: Parameters?, + into request: URLRequest) throws -> URLRequest { + guard let parameters else { return request } + + var request = request + + guard let url = request.url else { + throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url)) + } + + guard let method = request.method else { + let rawValue = request.method?.rawValue ?? "nil" + throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.httpMethod(rawValue: rawValue))) + } + + if destination.encodesParametersInURL(for: method), + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) { + let query: String = try Result { try encoder.encode(parameters) } + .mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get() + let newQueryString = [components.percentEncodedQuery, query].compactMap { $0 }.joinedWithAmpersands() + components.percentEncodedQuery = newQueryString.isEmpty ? nil : newQueryString + + guard let newURL = components.url else { + throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url)) + } + + request.url = newURL + } else { + if request.headers["Content-Type"] == nil { + request.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8")) + } + + request.httpBody = try Result { try encoder.encode(parameters) } + .mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get() + } + + return request + } +} + +extension ParameterEncoder where Self == URLEncodedFormParameterEncoder { + /// Provides a default `URLEncodedFormParameterEncoder` instance. + public static var urlEncodedForm: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() } + + /// Creates a `URLEncodedFormParameterEncoder` with the provided encoder and destination. + /// + /// - Parameters: + /// - encoder: `URLEncodedFormEncoder` used to encode the parameters. `URLEncodedFormEncoder()` by default. + /// - destination: `Destination` to which to encode the parameters. `.methodDependent` by default. + /// - Returns: The `URLEncodedFormParameterEncoder`. + public static func urlEncodedForm(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), + destination: URLEncodedFormParameterEncoder.Destination = .methodDependent) -> URLEncodedFormParameterEncoder { + URLEncodedFormParameterEncoder(encoder: encoder, destination: destination) + } +} diff --git a/Pods/Alamofire/Source/Core/ParameterEncoding.swift b/Pods/Alamofire/Source/Core/ParameterEncoding.swift new file mode 100644 index 0000000..d3341b0 --- /dev/null +++ b/Pods/Alamofire/Source/Core/ParameterEncoding.swift @@ -0,0 +1,349 @@ +// +// ParameterEncoding.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// A dictionary of parameters to apply to a `URLRequest`. +public typealias Parameters = [String: any Any & Sendable] + +/// A type used to define how a set of parameters are applied to a `URLRequest`. +public protocol ParameterEncoding: Sendable { + /// Creates a `URLRequest` by encoding parameters and applying them on the passed request. + /// + /// - Parameters: + /// - urlRequest: `URLRequestConvertible` value onto which parameters will be encoded. + /// - parameters: `Parameters` to encode onto the request. + /// + /// - Returns: The encoded `URLRequest`. + /// - Throws: Any `Error` produced during parameter encoding. + func encode(_ urlRequest: any URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest +} + +// MARK: - + +/// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP +/// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as +/// the HTTP body depends on the destination of the encoding. +/// +/// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to +/// `application/x-www-form-urlencoded; charset=utf-8`. +/// +/// There is no published specification for how to encode collection types. By default the convention of appending +/// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for +/// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the +/// square brackets appended to array keys. +/// +/// `BoolEncoding` can be used to configure how boolean values are encoded. The default behavior is to encode +/// `true` as 1 and `false` as 0. +public struct URLEncoding: ParameterEncoding { + // MARK: Helper Types + + /// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the + /// resulting URL request. + public enum Destination: Sendable { + /// Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE` requests and + /// sets as the HTTP body for requests with any other HTTP method. + case methodDependent + /// Sets or appends encoded query string result to existing query string. + case queryString + /// Sets encoded query string result as the HTTP body of the URL request. + case httpBody + + func encodesParametersInURL(for method: HTTPMethod) -> Bool { + switch self { + case .methodDependent: [.get, .head, .delete].contains(method) + case .queryString: true + case .httpBody: false + } + } + } + + /// Configures how `Array` parameters are encoded. + public enum ArrayEncoding: Sendable { + /// An empty set of square brackets is appended to the key for every value. This is the default behavior. + case brackets + /// No brackets are appended. The key is encoded as is. + case noBrackets + /// Brackets containing the item index are appended. This matches the jQuery and Node.js behavior. + case indexInBrackets + /// Provide a custom array key encoding with the given closure. + case custom(@Sendable (_ key: String, _ index: Int) -> String) + + func encode(key: String, atIndex index: Int) -> String { + switch self { + case .brackets: + "\(key)[]" + case .noBrackets: + key + case .indexInBrackets: + "\(key)[\(index)]" + case let .custom(encoding): + encoding(key, index) + } + } + } + + /// Configures how `Bool` parameters are encoded. + public enum BoolEncoding: Sendable { + /// Encode `true` as `1` and `false` as `0`. This is the default behavior. + case numeric + /// Encode `true` and `false` as string literals. + case literal + + func encode(value: Bool) -> String { + switch self { + case .numeric: + value ? "1" : "0" + case .literal: + value ? "true" : "false" + } + } + } + + // MARK: Properties + + /// Returns a default `URLEncoding` instance with a `.methodDependent` destination. + public static var `default`: URLEncoding { URLEncoding() } + + /// Returns a `URLEncoding` instance with a `.queryString` destination. + public static var queryString: URLEncoding { URLEncoding(destination: .queryString) } + + /// Returns a `URLEncoding` instance with an `.httpBody` destination. + public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) } + + /// The destination defining where the encoded query string is to be applied to the URL request. + public let destination: Destination + + /// The encoding to use for `Array` parameters. + public let arrayEncoding: ArrayEncoding + + /// The encoding to use for `Bool` parameters. + public let boolEncoding: BoolEncoding + + // MARK: Initialization + + /// Creates an instance using the specified parameters. + /// + /// - Parameters: + /// - destination: `Destination` defining where the encoded query string will be applied. `.methodDependent` by + /// default. + /// - arrayEncoding: `ArrayEncoding` to use. `.brackets` by default. + /// - boolEncoding: `BoolEncoding` to use. `.numeric` by default. + public init(destination: Destination = .methodDependent, + arrayEncoding: ArrayEncoding = .brackets, + boolEncoding: BoolEncoding = .numeric) { + self.destination = destination + self.arrayEncoding = arrayEncoding + self.boolEncoding = boolEncoding + } + + // MARK: Encoding + + public func encode(_ urlRequest: any URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let parameters else { return urlRequest } + + if let method = urlRequest.method, destination.encodesParametersInURL(for: method) { + guard let url = urlRequest.url else { + throw AFError.parameterEncodingFailed(reason: .missingURL) + } + + if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { + let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) + urlComponents.percentEncodedQuery = percentEncodedQuery + urlRequest.url = urlComponents.url + } + } else { + if urlRequest.headers["Content-Type"] == nil { + urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8")) + } + + urlRequest.httpBody = Data(query(parameters).utf8) + } + + return urlRequest + } + + /// Creates a percent-escaped, URL encoded query string components from the given key-value pair recursively. + /// + /// - Parameters: + /// - key: Key of the query component. + /// - value: Value of the query component. + /// + /// - Returns: The percent-escaped, URL encoded query string components. + public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { + var components: [(String, String)] = [] + switch value { + case let dictionary as [String: Any]: + for (nestedKey, value) in dictionary { + components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) + } + case let array as [Any]: + for (index, value) in array.enumerated() { + components += queryComponents(fromKey: arrayEncoding.encode(key: key, atIndex: index), value: value) + } + case let number as NSNumber: + if number.isBool { + components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue)))) + } else { + components.append((escape(key), escape("\(number)"))) + } + case let bool as Bool: + components.append((escape(key), escape(boolEncoding.encode(value: bool)))) + default: + components.append((escape(key), escape("\(value)"))) + } + return components + } + + /// Creates a percent-escaped string following RFC 3986 for a query string key or value. + /// + /// - Parameter string: `String` to be percent-escaped. + /// + /// - Returns: The percent-escaped `String`. + public func escape(_ string: String) -> String { + string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string + } + + private func query(_ parameters: [String: Any]) -> String { + var components: [(String, String)] = [] + + for key in parameters.keys.sorted(by: <) { + let value = parameters[key]! + components += queryComponents(fromKey: key, value: value) + } + return components.map { "\($0)=\($1)" }.joined(separator: "&") + } +} + +// MARK: - + +/// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the +/// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`. +public struct JSONEncoding: ParameterEncoding { + /// Error produced by `JSONEncoding`. + public enum Error: Swift.Error { + /// `JSONEncoding` attempted to encode an invalid JSON object. Usually this means the value included types not + /// representable in Objective-C. See `JSONSerialization.isValidJSONObject` for details. + case invalidJSONObject + } + + // MARK: Properties + + /// Returns a `JSONEncoding` instance with default writing options. + public static var `default`: JSONEncoding { JSONEncoding() } + + /// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options. + public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) } + + /// The options for writing the parameters as JSON data. + public let options: JSONSerialization.WritingOptions + + // MARK: Initialization + + /// Creates an instance using the specified `WritingOptions`. + /// + /// - Parameter options: `JSONSerialization.WritingOptions` to use. + public init(options: JSONSerialization.WritingOptions = []) { + self.options = options + } + + // MARK: Encoding + + public func encode(_ urlRequest: any URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let parameters else { return urlRequest } + + guard JSONSerialization.isValidJSONObject(parameters) else { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: Error.invalidJSONObject)) + } + + do { + let data = try JSONSerialization.data(withJSONObject: parameters, options: options) + + if urlRequest.headers["Content-Type"] == nil { + urlRequest.headers.update(.contentType("application/json")) + } + + urlRequest.httpBody = data + } catch { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) + } + + return urlRequest + } + + /// Encodes any JSON compatible object into a `URLRequest`. + /// + /// - Parameters: + /// - urlRequest: `URLRequestConvertible` value into which the object will be encoded. + /// - jsonObject: `Any` value (must be JSON compatible) to be encoded into the `URLRequest`. `nil` by default. + /// + /// - Returns: The encoded `URLRequest`. + /// - Throws: Any `Error` produced during encoding. + public func encode(_ urlRequest: any URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard let jsonObject else { return urlRequest } + + guard JSONSerialization.isValidJSONObject(jsonObject) else { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: Error.invalidJSONObject)) + } + + do { + let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options) + + if urlRequest.headers["Content-Type"] == nil { + urlRequest.headers.update(.contentType("application/json")) + } + + urlRequest.httpBody = data + } catch { + throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) + } + + return urlRequest + } +} + +extension JSONEncoding.Error { + public var localizedDescription: String { + """ + Invalid JSON object provided for parameter or object encoding. \ + This is most likely due to a value which can't be represented in Objective-C. + """ + } +} + +// MARK: - + +extension NSNumber { + fileprivate var isBool: Bool { + // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of + // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22). + String(cString: objCType) == "c" + } +} diff --git a/Pods/Alamofire/Source/Core/Protected.swift b/Pods/Alamofire/Source/Core/Protected.swift new file mode 100644 index 0000000..91a8ade --- /dev/null +++ b/Pods/Alamofire/Source/Core/Protected.swift @@ -0,0 +1,179 @@ +// +// Protected.swift +// +// Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +private protocol Lock: Sendable { + func lock() + func unlock() +} + +extension Lock { + /// Executes a closure returning a value while acquiring the lock. + /// + /// - Parameter closure: The closure to run. + /// + /// - Returns: The value the closure generated. + func around(_ closure: () throws -> T) rethrows -> T { + lock(); defer { unlock() } + return try closure() + } + + /// Execute a closure while acquiring the lock. + /// + /// - Parameter closure: The closure to run. + func around(_ closure: () throws -> Void) rethrows { + lock(); defer { unlock() } + try closure() + } +} + +#if canImport(Darwin) +// Number of Apple engineers who insisted on inspecting this: 5 +/// An `os_unfair_lock` wrapper. +final class UnfairLock: Lock, @unchecked Sendable { + private let unfairLock: os_unfair_lock_t + + init() { + unfairLock = .allocate(capacity: 1) + unfairLock.initialize(to: os_unfair_lock()) + } + + deinit { + unfairLock.deinitialize(count: 1) + unfairLock.deallocate() + } + + fileprivate func lock() { + os_unfair_lock_lock(unfairLock) + } + + fileprivate func unlock() { + os_unfair_lock_unlock(unfairLock) + } +} + +#elseif canImport(Foundation) +extension NSLock: Lock {} +#else +#error("This platform needs a Lock-conforming type without Foundation.") +#endif + +/// A thread-safe wrapper around a value. +@dynamicMemberLookup +final class Protected { + #if canImport(Darwin) + private let lock = UnfairLock() + #elseif canImport(Foundation) + private let lock = NSLock() + #else + #error("This platform needs a Lock-conforming type without Foundation.") + #endif + #if compiler(>=6) + private nonisolated(unsafe) var value: Value + #else + private var value: Value + #endif + + init(_ value: Value) { + self.value = value + } + + /// Synchronously read or transform the contained value. + /// + /// - Parameter closure: The closure to execute. + /// + /// - Returns: The return value of the closure passed. + func read(_ closure: (Value) throws -> U) rethrows -> U { + try lock.around { try closure(self.value) } + } + + /// Synchronously modify the protected value. + /// + /// - Parameter closure: The closure to execute. + /// + /// - Returns: The modified value. + @discardableResult + func write(_ closure: (inout Value) throws -> U) rethrows -> U { + try lock.around { try closure(&self.value) } + } + + /// Synchronously update the protected value. + /// + /// - Parameter value: The `Value`. + func write(_ value: Value) { + write { $0 = value } + } + + subscript(dynamicMember keyPath: WritableKeyPath) -> Property { + get { lock.around { value[keyPath: keyPath] } } + set { lock.around { value[keyPath: keyPath] = newValue } } + } + + subscript(dynamicMember keyPath: KeyPath) -> Property { + lock.around { value[keyPath: keyPath] } + } +} + +#if compiler(>=6) +extension Protected: Sendable {} +#else +extension Protected: @unchecked Sendable {} +#endif + +extension Protected where Value == Request.MutableState { + /// Attempts to transition to the passed `State`. + /// + /// - Parameter state: The `State` to attempt transition to. + /// + /// - Returns: Whether the transition occurred. + func attemptToTransitionTo(_ state: Request.State) -> Bool { + lock.around { + guard value.state.canTransitionTo(state) else { return false } + + value.state = state + + return true + } + } + + /// Perform a closure while locked with the provided `Request.State`. + /// + /// - Parameter perform: The closure to perform while locked. + func withState(perform: (Request.State) -> Void) { + lock.around { perform(value.state) } + } +} + +extension Protected: Equatable where Value: Equatable { + static func ==(lhs: Protected, rhs: Protected) -> Bool { + lhs.read { left in rhs.read { right in left == right }} + } +} + +extension Protected: Hashable where Value: Hashable { + func hash(into hasher: inout Hasher) { + read { hasher.combine($0) } + } +} diff --git a/Pods/Alamofire/Source/Core/Request.swift b/Pods/Alamofire/Source/Core/Request.swift new file mode 100644 index 0000000..2ffb17b --- /dev/null +++ b/Pods/Alamofire/Source/Core/Request.swift @@ -0,0 +1,1100 @@ +// +// Request.swift +// +// Copyright (c) 2014-2024 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// `Request` is the common superclass of all Alamofire request types and provides common state, delegate, and callback +/// handling. +public class Request: @unchecked Sendable { + /// State of the `Request`, with managed transitions between states set when calling `resume()`, `suspend()`, or + /// `cancel()` on the `Request`. + public enum State { + /// Initial state of the `Request`. + case initialized + /// `State` set when `resume()` is called. Any tasks created for the `Request` will have `resume()` called on + /// them in this state. + case resumed + /// `State` set when `suspend()` is called. Any tasks created for the `Request` will have `suspend()` called on + /// them in this state. + case suspended + /// `State` set when `cancel()` is called. Any tasks created for the `Request` will have `cancel()` called on + /// them. Unlike `resumed` or `suspended`, once in the `cancelled` state, the `Request` can no longer transition + /// to any other state. + case cancelled + /// `State` set when all response serialization completion closures have been cleared on the `Request` and + /// enqueued on their respective queues. + case finished + + /// Determines whether `self` can be transitioned to the provided `State`. + func canTransitionTo(_ state: State) -> Bool { + switch (self, state) { + case (.initialized, _): + true + case (_, .initialized), (.cancelled, _), (.finished, _): + false + case (.resumed, .cancelled), (.suspended, .cancelled), (.resumed, .suspended), (.suspended, .resumed): + true + case (.suspended, .suspended), (.resumed, .resumed): + false + case (_, .finished): + true + } + } + } + + // MARK: - Initial State + + /// `UUID` providing a unique identifier for the `Request`, used in the `Hashable` and `Equatable` conformances. + public let id: UUID + /// The serial queue for all internal async actions. + public let underlyingQueue: DispatchQueue + /// The queue used for all serialization actions. By default it's a serial queue that targets `underlyingQueue`. + public let serializationQueue: DispatchQueue + /// `EventMonitor` used for event callbacks. + public let eventMonitor: (any EventMonitor)? + /// The `Request`'s interceptor. + public let interceptor: (any RequestInterceptor)? + /// The `Request`'s delegate. + public private(set) weak var delegate: (any RequestDelegate)? + + // MARK: - Mutable State + + /// Type encapsulating all mutable state that may need to be accessed from anything other than the `underlyingQueue`. + struct MutableState { + /// State of the `Request`. + var state: State = .initialized + /// `ProgressHandler` and `DispatchQueue` provided for upload progress callbacks. + var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? + /// `ProgressHandler` and `DispatchQueue` provided for download progress callbacks. + var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? + /// `RedirectHandler` provided for to handle request redirection. + var redirectHandler: (any RedirectHandler)? + /// `CachedResponseHandler` provided to handle response caching. + var cachedResponseHandler: (any CachedResponseHandler)? + /// Queue and closure called when the `Request` is able to create a cURL description of itself. + var cURLHandler: (queue: DispatchQueue, handler: @Sendable (String) -> Void)? + /// Queue and closure called when the `Request` creates a `URLRequest`. + var urlRequestHandler: (queue: DispatchQueue, handler: @Sendable (URLRequest) -> Void)? + /// Queue and closure called when the `Request` creates a `URLSessionTask`. + var urlSessionTaskHandler: (queue: DispatchQueue, handler: @Sendable (URLSessionTask) -> Void)? + /// Response serialization closures that handle response parsing. + var responseSerializers: [@Sendable () -> Void] = [] + /// Response serialization completion closures executed once all response serializers are complete. + var responseSerializerCompletions: [@Sendable () -> Void] = [] + /// Whether response serializer processing is finished. + var responseSerializerProcessingFinished = false + /// `URLCredential` used for authentication challenges. + var credential: URLCredential? + /// All `URLRequest`s created by Alamofire on behalf of the `Request`. + var requests: [URLRequest] = [] + /// All `URLSessionTask`s created by Alamofire on behalf of the `Request`. + var tasks: [URLSessionTask] = [] + /// All `URLSessionTaskMetrics` values gathered by Alamofire on behalf of the `Request`. Should correspond + /// exactly the the `tasks` created. + var metrics: [URLSessionTaskMetrics] = [] + /// Number of times any retriers provided retried the `Request`. + var retryCount = 0 + /// Final `AFError` for the `Request`, whether from various internal Alamofire calls or as a result of a `task`. + var error: AFError? + /// Whether the instance has had `finish()` called and is running the serializers. Should be replaced with a + /// representation in the state machine in the future. + var isFinishing = false + /// Actions to run when requests are finished. Use for concurrency support. + var finishHandlers: [() -> Void] = [] + } + + /// Protected `MutableState` value that provides thread-safe access to state values. + let mutableState = Protected(MutableState()) + + /// `State` of the `Request`. + public var state: State { mutableState.state } + /// Returns whether `state` is `.initialized`. + public var isInitialized: Bool { state == .initialized } + /// Returns whether `state` is `.resumed`. + public var isResumed: Bool { state == .resumed } + /// Returns whether `state` is `.suspended`. + public var isSuspended: Bool { state == .suspended } + /// Returns whether `state` is `.cancelled`. + public var isCancelled: Bool { state == .cancelled } + /// Returns whether `state` is `.finished`. + public var isFinished: Bool { state == .finished } + + // MARK: Progress + + /// Closure type executed when monitoring the upload or download progress of a request. + public typealias ProgressHandler = @Sendable (_ progress: Progress) -> Void + + /// `Progress` of the upload of the body of the executed `URLRequest`. Reset to `0` if the `Request` is retried. + public let uploadProgress = Progress(totalUnitCount: 0) + /// `Progress` of the download of any response data. Reset to `0` if the `Request` is retried. + public let downloadProgress = Progress(totalUnitCount: 0) + /// `ProgressHandler` called when `uploadProgress` is updated, on the provided `DispatchQueue`. + public internal(set) var uploadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { + get { mutableState.uploadProgressHandler } + set { mutableState.uploadProgressHandler = newValue } + } + + /// `ProgressHandler` called when `downloadProgress` is updated, on the provided `DispatchQueue`. + public internal(set) var downloadProgressHandler: (handler: ProgressHandler, queue: DispatchQueue)? { + get { mutableState.downloadProgressHandler } + set { mutableState.downloadProgressHandler = newValue } + } + + // MARK: Redirect Handling + + /// `RedirectHandler` set on the instance. + public internal(set) var redirectHandler: (any RedirectHandler)? { + get { mutableState.redirectHandler } + set { mutableState.redirectHandler = newValue } + } + + // MARK: Cached Response Handling + + /// `CachedResponseHandler` set on the instance. + public internal(set) var cachedResponseHandler: (any CachedResponseHandler)? { + get { mutableState.cachedResponseHandler } + set { mutableState.cachedResponseHandler = newValue } + } + + // MARK: URLCredential + + /// `URLCredential` used for authentication challenges. Created by calling one of the `authenticate` methods. + public internal(set) var credential: URLCredential? { + get { mutableState.credential } + set { mutableState.credential = newValue } + } + + // MARK: Validators + + /// `Validator` callback closures that store the validation calls enqueued. + let validators = Protected<[() -> Void]>([]) + + // MARK: URLRequests + + /// All `URLRequest`s created on behalf of the `Request`, including original and adapted requests. + public var requests: [URLRequest] { mutableState.requests } + /// First `URLRequest` created on behalf of the `Request`. May not be the first one actually executed. + public var firstRequest: URLRequest? { requests.first } + /// Last `URLRequest` created on behalf of the `Request`. + public var lastRequest: URLRequest? { requests.last } + /// Current `URLRequest` created on behalf of the `Request`. + public var request: URLRequest? { lastRequest } + + /// `URLRequest`s from all of the `URLSessionTask`s executed on behalf of the `Request`. May be different from + /// `requests` due to `URLSession` manipulation. + public var performedRequests: [URLRequest] { mutableState.read { $0.tasks.compactMap(\.currentRequest) } } + + // MARK: HTTPURLResponse + + /// `HTTPURLResponse` received from the server, if any. If the `Request` was retried, this is the response of the + /// last `URLSessionTask`. + public var response: HTTPURLResponse? { lastTask?.response as? HTTPURLResponse } + + // MARK: Tasks + + /// All `URLSessionTask`s created on behalf of the `Request`. + public var tasks: [URLSessionTask] { mutableState.tasks } + /// First `URLSessionTask` created on behalf of the `Request`. + public var firstTask: URLSessionTask? { tasks.first } + /// Last `URLSessionTask` created on behalf of the `Request`. + public var lastTask: URLSessionTask? { tasks.last } + /// Current `URLSessionTask` created on behalf of the `Request`. + public var task: URLSessionTask? { lastTask } + + // MARK: Metrics + + /// All `URLSessionTaskMetrics` gathered on behalf of the `Request`. Should correspond to the `tasks` created. + public var allMetrics: [URLSessionTaskMetrics] { mutableState.metrics } + /// First `URLSessionTaskMetrics` gathered on behalf of the `Request`. + public var firstMetrics: URLSessionTaskMetrics? { allMetrics.first } + /// Last `URLSessionTaskMetrics` gathered on behalf of the `Request`. + public var lastMetrics: URLSessionTaskMetrics? { allMetrics.last } + /// Current `URLSessionTaskMetrics` gathered on behalf of the `Request`. + public var metrics: URLSessionTaskMetrics? { lastMetrics } + + // MARK: Retry Count + + /// Number of times the `Request` has been retried. + public var retryCount: Int { mutableState.retryCount } + + // MARK: Error + + /// `Error` returned from Alamofire internally, from the network request directly, or any validators executed. + public internal(set) var error: AFError? { + get { mutableState.error } + set { mutableState.error = newValue } + } + + /// Default initializer for the `Request` superclass. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. + init(id: UUID = UUID(), + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: (any EventMonitor)?, + interceptor: (any RequestInterceptor)?, + delegate: any RequestDelegate) { + self.id = id + self.underlyingQueue = underlyingQueue + self.serializationQueue = serializationQueue + self.eventMonitor = eventMonitor + self.interceptor = interceptor + self.delegate = delegate + } + + // MARK: - Internal Event API + + // All API must be called from underlyingQueue. + + /// Called when an initial `URLRequest` has been created on behalf of the instance. If a `RequestAdapter` is active, + /// the `URLRequest` will be adapted before being issued. + /// + /// - Parameter request: The `URLRequest` created. + func didCreateInitialURLRequest(_ request: URLRequest) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.write { $0.requests.append(request) } + + eventMonitor?.request(self, didCreateInitialURLRequest: request) + } + + /// Called when initial `URLRequest` creation has failed, typically through a `URLRequestConvertible`. + /// + /// - Note: Triggers retry. + /// + /// - Parameter error: `AFError` thrown from the failed creation. + func didFailToCreateURLRequest(with error: AFError) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = error + + eventMonitor?.request(self, didFailToCreateURLRequestWithError: error) + + callCURLHandlerIfNecessary() + + retryOrFinish(error: error) + } + + /// Called when a `RequestAdapter` has successfully adapted a `URLRequest`. + /// + /// - Parameters: + /// - initialRequest: The `URLRequest` that was adapted. + /// - adaptedRequest: The `URLRequest` returned by the `RequestAdapter`. + func didAdaptInitialRequest(_ initialRequest: URLRequest, to adaptedRequest: URLRequest) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.write { $0.requests.append(adaptedRequest) } + + eventMonitor?.request(self, didAdaptInitialRequest: initialRequest, to: adaptedRequest) + } + + /// Called when a `RequestAdapter` fails to adapt a `URLRequest`. + /// + /// - Note: Triggers retry. + /// + /// - Parameters: + /// - request: The `URLRequest` the adapter was called with. + /// - error: The `AFError` returned by the `RequestAdapter`. + func didFailToAdaptURLRequest(_ request: URLRequest, withError error: AFError) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = error + + eventMonitor?.request(self, didFailToAdaptURLRequest: request, withError: error) + + callCURLHandlerIfNecessary() + + retryOrFinish(error: error) + } + + /// Final `URLRequest` has been created for the instance. + /// + /// - Parameter request: The `URLRequest` created. + func didCreateURLRequest(_ request: URLRequest) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.read { state in + guard let urlRequestHandler = state.urlRequestHandler else { return } + + urlRequestHandler.queue.async { urlRequestHandler.handler(request) } + } + + eventMonitor?.request(self, didCreateURLRequest: request) + + callCURLHandlerIfNecessary() + } + + /// Asynchronously calls any stored `cURLHandler` and then removes it from `mutableState`. + private func callCURLHandlerIfNecessary() { + mutableState.write { mutableState in + guard let cURLHandler = mutableState.cURLHandler else { return } + + cURLHandler.queue.async { cURLHandler.handler(self.cURLDescription()) } + + mutableState.cURLHandler = nil + } + } + + /// Called when a `URLSessionTask` is created on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` created. + func didCreateTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.write { state in + state.tasks.append(task) + + guard let urlSessionTaskHandler = state.urlSessionTaskHandler else { return } + + urlSessionTaskHandler.queue.async { urlSessionTaskHandler.handler(task) } + } + + eventMonitor?.request(self, didCreateTask: task) + } + + /// Called when resumption is completed. + func didResume() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.requestDidResume(self) + } + + /// Called when a `URLSessionTask` is resumed on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` resumed. + func didResumeTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.request(self, didResumeTask: task) + } + + /// Called when suspension is completed. + func didSuspend() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.requestDidSuspend(self) + } + + /// Called when a `URLSessionTask` is suspended on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` suspended. + func didSuspendTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.request(self, didSuspendTask: task) + } + + /// Called when cancellation is completed, sets `error` to `AFError.explicitlyCancelled`. + func didCancel() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.write { mutableState in + mutableState.error = mutableState.error ?? AFError.explicitlyCancelled + } + + eventMonitor?.requestDidCancel(self) + } + + /// Called when a `URLSessionTask` is cancelled on behalf of the instance. + /// + /// - Parameter task: The `URLSessionTask` cancelled. + func didCancelTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + eventMonitor?.request(self, didCancelTask: task) + } + + /// Called when a `URLSessionTaskMetrics` value is gathered on behalf of the instance. + /// + /// - Parameter metrics: The `URLSessionTaskMetrics` gathered. + func didGatherMetrics(_ metrics: URLSessionTaskMetrics) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.write { $0.metrics.append(metrics) } + + eventMonitor?.request(self, didGatherMetrics: metrics) + } + + /// Called when a `URLSessionTask` fails before it is finished, typically during certificate pinning. + /// + /// - Parameters: + /// - task: The `URLSessionTask` which failed. + /// - error: The early failure `AFError`. + func didFailTask(_ task: URLSessionTask, earlyWithError error: AFError) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = error + + // Task will still complete, so didCompleteTask(_:with:) will handle retry. + eventMonitor?.request(self, didFailTask: task, earlyWithError: error) + } + + /// Called when a `URLSessionTask` completes. All tasks will eventually call this method. + /// + /// - Note: Response validation is synchronously triggered in this step. + /// + /// - Parameters: + /// - task: The `URLSessionTask` which completed. + /// - error: The `AFError` `task` may have completed with. If `error` has already been set on the instance, this + /// value is ignored. + func didCompleteTask(_ task: URLSessionTask, with error: AFError?) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + self.error = self.error ?? error + + let validators = validators.read { $0 } + validators.forEach { $0() } + + eventMonitor?.request(self, didCompleteTask: task, with: error) + + retryOrFinish(error: self.error) + } + + /// Called when the `RequestDelegate` is going to retry this `Request`. Calls `reset()`. + func prepareForRetry() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.write { $0.retryCount += 1 } + + reset() + + eventMonitor?.requestIsRetrying(self) + } + + /// Called to determine whether retry will be triggered for the particular error, or whether the instance should + /// call `finish()`. + /// + /// - Parameter error: The possible `AFError` which may trigger retry. + func retryOrFinish(error: AFError?) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + guard !isCancelled, let error, let delegate else { finish(); return } + + delegate.retryResult(for: self, dueTo: error) { retryResult in + switch retryResult { + case .doNotRetry: + self.finish() + case let .doNotRetryWithError(retryError): + self.finish(error: retryError.asAFError(orFailWith: "Received retryError was not already AFError")) + case .retry, .retryWithDelay: + delegate.retryRequest(self, withDelay: retryResult.delay) + } + } + } + + /// Finishes this `Request` and starts the response serializers. + /// + /// - Parameter error: The possible `Error` with which the instance will finish. + func finish(error: AFError? = nil) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + guard !mutableState.isFinishing else { return } + + mutableState.isFinishing = true + + if let error { self.error = error } + + // Start response handlers + processNextResponseSerializer() + + eventMonitor?.requestDidFinish(self) + } + + /// Appends the response serialization closure to the instance. + /// + /// - Note: This method will also `resume` the instance if `delegate.startImmediately` returns `true`. + /// + /// - Parameter closure: The closure containing the response serialization call. + func appendResponseSerializer(_ closure: @escaping @Sendable () -> Void) { + mutableState.write { mutableState in + mutableState.responseSerializers.append(closure) + + if mutableState.state == .finished { + mutableState.state = .resumed + } + + if mutableState.responseSerializerProcessingFinished { + underlyingQueue.async { self.processNextResponseSerializer() } + } + + if mutableState.state.canTransitionTo(.resumed) { + underlyingQueue.async { if self.delegate?.startImmediately == true { self.resume() } } + } + } + } + + /// Returns the next response serializer closure to execute if there's one left. + /// + /// - Returns: The next response serialization closure, if there is one. + func nextResponseSerializer() -> (@Sendable () -> Void)? { + var responseSerializer: (@Sendable () -> Void)? + + mutableState.write { mutableState in + let responseSerializerIndex = mutableState.responseSerializerCompletions.count + + if responseSerializerIndex < mutableState.responseSerializers.count { + responseSerializer = mutableState.responseSerializers[responseSerializerIndex] + } + } + + return responseSerializer + } + + /// Processes the next response serializer and calls all completions if response serialization is complete. + func processNextResponseSerializer() { + guard let responseSerializer = nextResponseSerializer() else { + // Execute all response serializer completions and clear them + var completions: [@Sendable () -> Void] = [] + + mutableState.write { mutableState in + completions = mutableState.responseSerializerCompletions + + // Clear out all response serializers and response serializer completions in mutable state since the + // request is complete. It's important to do this prior to calling the completion closures in case + // the completions call back into the request triggering a re-processing of the response serializers. + // An example of how this can happen is by calling cancel inside a response completion closure. + mutableState.responseSerializers.removeAll() + mutableState.responseSerializerCompletions.removeAll() + + if mutableState.state.canTransitionTo(.finished) { + mutableState.state = .finished + } + + mutableState.responseSerializerProcessingFinished = true + mutableState.isFinishing = false + } + + completions.forEach { $0() } + + // Cleanup the request + cleanup() + + return + } + + serializationQueue.async { responseSerializer() } + } + + /// Notifies the `Request` that the response serializer is complete. + /// + /// - Parameter completion: The completion handler provided with the response serializer, called when all serializers + /// are complete. + func responseSerializerDidComplete(completion: @escaping @Sendable () -> Void) { + mutableState.write { $0.responseSerializerCompletions.append(completion) } + processNextResponseSerializer() + } + + /// Resets all task and response serializer related state for retry. + func reset() { + error = nil + + uploadProgress.totalUnitCount = 0 + uploadProgress.completedUnitCount = 0 + downloadProgress.totalUnitCount = 0 + downloadProgress.completedUnitCount = 0 + + mutableState.write { state in + state.isFinishing = false + state.responseSerializerCompletions = [] + } + } + + /// Called when updating the upload progress. + /// + /// - Parameters: + /// - totalBytesSent: Total bytes sent so far. + /// - totalBytesExpectedToSend: Total bytes expected to send. + func updateUploadProgress(totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + uploadProgress.totalUnitCount = totalBytesExpectedToSend + uploadProgress.completedUnitCount = totalBytesSent + + uploadProgressHandler?.queue.async { self.uploadProgressHandler?.handler(self.uploadProgress) } + } + + /// Perform a closure on the current `state` while locked. + /// + /// - Parameter perform: The closure to perform. + func withState(perform: (State) -> Void) { + mutableState.withState(perform: perform) + } + + // MARK: Task Creation + + /// Called when creating a `URLSessionTask` for this `Request`. Subclasses must override. + /// + /// - Parameters: + /// - request: `URLRequest` to use to create the `URLSessionTask`. + /// - session: `URLSession` which creates the `URLSessionTask`. + /// + /// - Returns: The `URLSessionTask` created. + func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + fatalError("Subclasses must override.") + } + + // MARK: - Public API + + // These APIs are callable from any queue. + + // MARK: State + + /// Cancels the instance. Once cancelled, a `Request` can no longer be resumed or suspended. + /// + /// - Returns: The instance. + @discardableResult + public func cancel() -> Self { + mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.cancelled) else { return } + + mutableState.state = .cancelled + + underlyingQueue.async { self.didCancel() } + + guard let task = mutableState.tasks.last, task.state != .completed else { + underlyingQueue.async { self.finish() } + return + } + + // Resume to ensure metrics are gathered. + task.resume() + task.cancel() + underlyingQueue.async { self.didCancelTask(task) } + } + + return self + } + + /// Suspends the instance. + /// + /// - Returns: The instance. + @discardableResult + public func suspend() -> Self { + mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.suspended) else { return } + + mutableState.state = .suspended + + underlyingQueue.async { self.didSuspend() } + + guard let task = mutableState.tasks.last, task.state != .completed else { return } + + task.suspend() + underlyingQueue.async { self.didSuspendTask(task) } + } + + return self + } + + /// Resumes the instance. + /// + /// - Returns: The instance. + @discardableResult + public func resume() -> Self { + mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.resumed) else { return } + + mutableState.state = .resumed + + underlyingQueue.async { self.didResume() } + + guard let task = mutableState.tasks.last, task.state != .completed else { return } + + task.resume() + underlyingQueue.async { self.didResumeTask(task) } + } + + return self + } + + // MARK: - Closure API + + /// Associates a credential using the provided values with the instance. + /// + /// - Parameters: + /// - username: The username. + /// - password: The password. + /// - persistence: The `URLCredential.Persistence` for the created `URLCredential`. `.forSession` by default. + /// + /// - Returns: The instance. + @discardableResult + public func authenticate(username: String, password: String, persistence: URLCredential.Persistence = .forSession) -> Self { + let credential = URLCredential(user: username, password: password, persistence: persistence) + + return authenticate(with: credential) + } + + /// Associates the provided credential with the instance. + /// + /// - Parameter credential: The `URLCredential`. + /// + /// - Returns: The instance. + @discardableResult + public func authenticate(with credential: URLCredential) -> Self { + mutableState.credential = credential + + return self + } + + /// Sets a closure to be called periodically during the lifecycle of the instance as data is read from the server. + /// + /// - Note: Only the last closure provided is used. + /// + /// - Parameters: + /// - queue: The `DispatchQueue` to execute the closure on. `.main` by default. + /// - closure: The closure to be executed periodically as data is read from the server. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func downloadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { + mutableState.downloadProgressHandler = (handler: closure, queue: queue) + + return self + } + + /// Sets a closure to be called periodically during the lifecycle of the instance as data is sent to the server. + /// + /// - Note: Only the last closure provided is used. + /// + /// - Parameters: + /// - queue: The `DispatchQueue` to execute the closure on. `.main` by default. + /// - closure: The closure to be executed periodically as data is sent to the server. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func uploadProgress(queue: DispatchQueue = .main, closure: @escaping ProgressHandler) -> Self { + mutableState.uploadProgressHandler = (handler: closure, queue: queue) + + return self + } + + // MARK: Redirects + + /// Sets the redirect handler for the instance which will be used if a redirect response is encountered. + /// + /// - Note: Attempting to set the redirect handler more than once is a logic error and will crash. + /// + /// - Parameter handler: The `RedirectHandler`. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func redirect(using handler: any RedirectHandler) -> Self { + mutableState.write { mutableState in + precondition(mutableState.redirectHandler == nil, "Redirect handler has already been set.") + mutableState.redirectHandler = handler + } + + return self + } + + // MARK: Cached Responses + + /// Sets the cached response handler for the `Request` which will be used when attempting to cache a response. + /// + /// - Note: Attempting to set the cache handler more than once is a logic error and will crash. + /// + /// - Parameter handler: The `CachedResponseHandler`. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func cacheResponse(using handler: any CachedResponseHandler) -> Self { + mutableState.write { mutableState in + precondition(mutableState.cachedResponseHandler == nil, "Cached response handler has already been set.") + mutableState.cachedResponseHandler = handler + } + + return self + } + + // MARK: - Lifetime APIs + + /// Sets a handler to be called when the cURL description of the request is available. + /// + /// - Note: When waiting for a `Request`'s `URLRequest` to be created, only the last `handler` will be called. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which `handler` will be called. + /// - handler: Closure to be called when the cURL description is available. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func cURLDescription(on queue: DispatchQueue, calling handler: @escaping @Sendable (String) -> Void) -> Self { + mutableState.write { mutableState in + if mutableState.requests.last != nil { + queue.async { handler(self.cURLDescription()) } + } else { + mutableState.cURLHandler = (queue, handler) + } + } + + return self + } + + /// Sets a handler to be called when the cURL description of the request is available. + /// + /// - Note: When waiting for a `Request`'s `URLRequest` to be created, only the last `handler` will be called. + /// + /// - Parameter handler: Closure to be called when the cURL description is available. Called on the instance's + /// `underlyingQueue` by default. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func cURLDescription(calling handler: @escaping @Sendable (String) -> Void) -> Self { + cURLDescription(on: underlyingQueue, calling: handler) + + return self + } + + /// Sets a closure to called whenever Alamofire creates a `URLRequest` for this instance. + /// + /// - Note: This closure will be called multiple times if the instance adapts incoming `URLRequest`s or is retried. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which `handler` will be called. `.main` by default. + /// - handler: Closure to be called when a `URLRequest` is available. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func onURLRequestCreation(on queue: DispatchQueue = .main, perform handler: @escaping @Sendable (URLRequest) -> Void) -> Self { + mutableState.write { state in + if let request = state.requests.last { + queue.async { handler(request) } + } + + state.urlRequestHandler = (queue, handler) + } + + return self + } + + /// Sets a closure to be called whenever the instance creates a `URLSessionTask`. + /// + /// - Note: This API should only be used to provide `URLSessionTask`s to existing API, like `NSFileProvider`. It + /// **SHOULD NOT** be used to interact with tasks directly, as that may be break Alamofire features. + /// Additionally, this closure may be called multiple times if the instance is retried. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which `handler` will be called. `.main` by default. + /// - handler: Closure to be called when the `URLSessionTask` is available. + /// + /// - Returns: The instance. + @preconcurrency + @discardableResult + public func onURLSessionTaskCreation(on queue: DispatchQueue = .main, perform handler: @escaping @Sendable (URLSessionTask) -> Void) -> Self { + mutableState.write { state in + if let task = state.tasks.last { + queue.async { handler(task) } + } + + state.urlSessionTaskHandler = (queue, handler) + } + + return self + } + + // MARK: Cleanup + + /// Adds a `finishHandler` closure to be called when the request completes. + /// + /// - Parameter closure: Closure to be called when the request finishes. + func onFinish(perform finishHandler: @escaping () -> Void) { + guard !isFinished else { finishHandler(); return } + + mutableState.write { state in + state.finishHandlers.append(finishHandler) + } + } + + /// Final cleanup step executed when the instance finishes response serialization. + func cleanup() { + let handlers = mutableState.finishHandlers + handlers.forEach { $0() } + mutableState.write { state in + state.finishHandlers.removeAll() + } + + delegate?.cleanup(after: self) + } +} + +extension Request { + /// Type indicating how a `DataRequest` or `DataStreamRequest` should proceed after receiving an `HTTPURLResponse`. + public enum ResponseDisposition: Sendable { + /// Allow the request to continue normally. + case allow + /// Cancel the request, similar to calling `cancel()`. + case cancel + + var sessionDisposition: URLSession.ResponseDisposition { + switch self { + case .allow: .allow + case .cancel: .cancel + } + } + } +} + +// MARK: - Protocol Conformances + +extension Request: Equatable { + public static func ==(lhs: Request, rhs: Request) -> Bool { + lhs.id == rhs.id + } +} + +extension Request: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} + +extension Request: CustomStringConvertible { + /// A textual representation of this instance, including the `HTTPMethod` and `URL` if the `URLRequest` has been + /// created, as well as the response status code, if a response has been received. + public var description: String { + guard let request = performedRequests.last ?? lastRequest, + let url = request.url, + let method = request.httpMethod else { return "No request created yet." } + + let requestDescription = "\(method) \(url.absoluteString)" + + return response.map { "\(requestDescription) (\($0.statusCode))" } ?? requestDescription + } +} + +extension Request { + /// cURL representation of the instance. + /// + /// - Returns: The cURL equivalent of the instance. + public func cURLDescription() -> String { + guard + let request = lastRequest, + let url = request.url, + let host = url.host, + let method = request.httpMethod else { return "$ curl command could not be created" } + + var components = ["$ curl -v"] + + components.append("-X \(method)") + + if let credentialStorage = delegate?.sessionConfiguration.urlCredentialStorage { + let protectionSpace = URLProtectionSpace(host: host, + port: url.port ?? 0, + protocol: url.scheme, + realm: host, + authenticationMethod: NSURLAuthenticationMethodHTTPBasic) + + if let credentials = credentialStorage.credentials(for: protectionSpace)?.values { + for credential in credentials { + guard let user = credential.user, let password = credential.password else { continue } + components.append("-u \(user):\(password)") + } + } else { + if let credential, let user = credential.user, let password = credential.password { + components.append("-u \(user):\(password)") + } + } + } + + if let configuration = delegate?.sessionConfiguration, configuration.httpShouldSetCookies { + if + let cookieStorage = configuration.httpCookieStorage, + let cookies = cookieStorage.cookies(for: url), !cookies.isEmpty { + let allCookies = cookies.map { "\($0.name)=\($0.value)" }.joined(separator: ";") + + components.append("-b \"\(allCookies)\"") + } + } + + var headers = HTTPHeaders() + + if let sessionHeaders = delegate?.sessionConfiguration.headers { + for header in sessionHeaders where header.name != "Cookie" { + headers[header.name] = header.value + } + } + + for header in request.headers where header.name != "Cookie" { + headers[header.name] = header.value + } + + for header in headers { + let escapedValue = header.value.replacingOccurrences(of: "\"", with: "\\\"") + components.append("-H \"\(header.name): \(escapedValue)\"") + } + + if let httpBodyData = request.httpBody { + let httpBody = String(decoding: httpBodyData, as: UTF8.self) + var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"") + escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"") + + components.append("-d \"\(escapedBody)\"") + } + + components.append("\"\(url.absoluteString)\"") + + return components.joined(separator: " \\\n\t") + } +} + +/// Protocol abstraction for `Request`'s communication back to the `SessionDelegate`. +public protocol RequestDelegate: AnyObject, Sendable { + /// `URLSessionConfiguration` used to create the underlying `URLSessionTask`s. + var sessionConfiguration: URLSessionConfiguration { get } + + /// Determines whether the `Request` should automatically call `resume()` when adding the first response handler. + var startImmediately: Bool { get } + + /// Notifies the delegate the `Request` has reached a point where it needs cleanup. + /// + /// - Parameter request: The `Request` to cleanup after. + func cleanup(after request: Request) + + /// Asynchronously ask the delegate whether a `Request` will be retried. + /// + /// - Parameters: + /// - request: `Request` which failed. + /// - error: `Error` which produced the failure. + /// - completion: Closure taking the `RetryResult` for evaluation. + func retryResult(for request: Request, dueTo error: AFError, completion: @escaping @Sendable (RetryResult) -> Void) + + /// Asynchronously retry the `Request`. + /// + /// - Parameters: + /// - request: `Request` which will be retried. + /// - timeDelay: `TimeInterval` after which the retry will be triggered. + func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?) +} diff --git a/Pods/Alamofire/Source/Core/RequestTaskMap.swift b/Pods/Alamofire/Source/Core/RequestTaskMap.swift new file mode 100644 index 0000000..e49e564 --- /dev/null +++ b/Pods/Alamofire/Source/Core/RequestTaskMap.swift @@ -0,0 +1,150 @@ +// +// RequestTaskMap.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// A type that maintains a two way, one to one map of `URLSessionTask`s to `Request`s. +struct RequestTaskMap { + private typealias Events = (completed: Bool, metricsGathered: Bool) + + private var tasksToRequests: [URLSessionTask: Request] + private var requestsToTasks: [Request: URLSessionTask] + private var taskEvents: [URLSessionTask: Events] + + var requests: [Request] { + Array(tasksToRequests.values) + } + + init(tasksToRequests: [URLSessionTask: Request] = [:], + requestsToTasks: [Request: URLSessionTask] = [:], + taskEvents: [URLSessionTask: (completed: Bool, metricsGathered: Bool)] = [:]) { + self.tasksToRequests = tasksToRequests + self.requestsToTasks = requestsToTasks + self.taskEvents = taskEvents + } + + subscript(_ request: Request) -> URLSessionTask? { + get { requestsToTasks[request] } + set { + guard let newValue else { + guard let task = requestsToTasks[request] else { + fatalError("RequestTaskMap consistency error: no task corresponding to request found.") + } + + requestsToTasks.removeValue(forKey: request) + tasksToRequests.removeValue(forKey: task) + taskEvents.removeValue(forKey: task) + + return + } + + requestsToTasks[request] = newValue + tasksToRequests[newValue] = request + taskEvents[newValue] = (completed: false, metricsGathered: false) + } + } + + subscript(_ task: URLSessionTask) -> Request? { + get { tasksToRequests[task] } + set { + guard let newValue else { + guard let request = tasksToRequests[task] else { + fatalError("RequestTaskMap consistency error: no request corresponding to task found.") + } + + tasksToRequests.removeValue(forKey: task) + requestsToTasks.removeValue(forKey: request) + taskEvents.removeValue(forKey: task) + + return + } + + tasksToRequests[task] = newValue + requestsToTasks[newValue] = task + taskEvents[task] = (completed: false, metricsGathered: false) + } + } + + var count: Int { + precondition(tasksToRequests.count == requestsToTasks.count, + "RequestTaskMap.count invalid, requests.count: \(tasksToRequests.count) != tasks.count: \(requestsToTasks.count)") + + return tasksToRequests.count + } + + var eventCount: Int { + precondition(taskEvents.count == count, "RequestTaskMap.eventCount invalid, count: \(count) != taskEvents.count: \(taskEvents.count)") + + return taskEvents.count + } + + var isEmpty: Bool { + precondition(tasksToRequests.isEmpty == requestsToTasks.isEmpty, + "RequestTaskMap.isEmpty invalid, requests.isEmpty: \(tasksToRequests.isEmpty) != tasks.isEmpty: \(requestsToTasks.isEmpty)") + + return tasksToRequests.isEmpty + } + + var isEventsEmpty: Bool { + precondition(taskEvents.isEmpty == isEmpty, "RequestTaskMap.isEventsEmpty invalid, isEmpty: \(isEmpty) != taskEvents.isEmpty: \(taskEvents.isEmpty)") + + return taskEvents.isEmpty + } + + mutating func disassociateIfNecessaryAfterGatheringMetricsForTask(_ task: URLSessionTask) -> Bool { + guard let events = taskEvents[task] else { + fatalError("RequestTaskMap consistency error: no events corresponding to task found.") + } + + switch (events.completed, events.metricsGathered) { + case (_, true): fatalError("RequestTaskMap consistency error: duplicate metricsGatheredForTask call.") + case (false, false): taskEvents[task] = (completed: false, metricsGathered: true); return false + case (true, false): self[task] = nil; return true + } + } + + mutating func disassociateIfNecessaryAfterCompletingTask(_ task: URLSessionTask) -> Bool { + guard let events = taskEvents[task] else { + fatalError("RequestTaskMap consistency error: no events corresponding to task found.") + } + + switch (events.completed, events.metricsGathered) { + case (true, _): fatalError("RequestTaskMap consistency error: duplicate completionReceivedForTask call.") + // swift-corelibs-foundation doesn't gather metrics, so unconditionally remove the reference and return true. + #if canImport(FoundationNetworking) + default: self[task] = nil; return true + #else + case (false, false): + if #available(macOS 10.12, iOS 10, watchOS 7, tvOS 10, *) { + taskEvents[task] = (completed: true, metricsGathered: false); return false + } else { + // watchOS < 7 doesn't gather metrics, so unconditionally remove the reference and return true. + self[task] = nil; return true + } + case (false, true): + self[task] = nil; return true + #endif + } + } +} diff --git a/Pods/Alamofire/Source/Core/Response.swift b/Pods/Alamofire/Source/Core/Response.swift new file mode 100644 index 0000000..57486f6 --- /dev/null +++ b/Pods/Alamofire/Source/Core/Response.swift @@ -0,0 +1,453 @@ +// +// Response.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Default type of `DataResponse` returned by Alamofire, with an `AFError` `Failure` type. +public typealias AFDataResponse = DataResponse +/// Default type of `DownloadResponse` returned by Alamofire, with an `AFError` `Failure` type. +public typealias AFDownloadResponse = DownloadResponse + +/// Type used to store all values associated with a serialized response of a `DataRequest` or `UploadRequest`. +public struct DataResponse: Sendable where Success: Sendable, Failure: Sendable { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The data returned by the server. + public let data: Data? + + /// The final metrics of the response. + /// + /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.` + /// + public let metrics: URLSessionTaskMetrics? + + /// The time taken to serialize the response. + public let serializationDuration: TimeInterval + + /// The result of response serialization. + public let result: Result + + /// Returns the associated value of the result if it is a success, `nil` otherwise. + public var value: Success? { result.success } + + /// Returns the associated error value if the result if it is a failure, `nil` otherwise. + public var error: Failure? { result.failure } + + /// Creates a `DataResponse` instance with the specified parameters derived from the response serialization. + /// + /// - Parameters: + /// - request: The `URLRequest` sent to the server. + /// - response: The `HTTPURLResponse` from the server. + /// - data: The `Data` returned by the server. + /// - metrics: The `URLSessionTaskMetrics` of the `DataRequest` or `UploadRequest`. + /// - serializationDuration: The duration taken by serialization. + /// - result: The `Result` of response serialization. + public init(request: URLRequest?, + response: HTTPURLResponse?, + data: Data?, + metrics: URLSessionTaskMetrics?, + serializationDuration: TimeInterval, + result: Result) { + self.request = request + self.response = response + self.data = data + self.metrics = metrics + self.serializationDuration = serializationDuration + self.result = result + } +} + +// MARK: - + +extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + "\(result)" + } + + /// The debug textual representation used when written to an output stream, which includes (if available) a summary + /// of the `URLRequest`, the request's headers and body (if decodable as a `String` below 100KB); the + /// `HTTPURLResponse`'s status code, headers, and body; the duration of the network and serialization actions; and + /// the `Result` of serialization. + public var debugDescription: String { + guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" } + + let requestDescription = DebugDescription.description(of: urlRequest) + + let responseDescription = response.map { response in + let responseBodyDescription = DebugDescription.description(for: data, headers: response.headers) + + return """ + \(DebugDescription.description(of: response)) + \(responseBodyDescription.indentingNewlines()) + """ + } ?? "[Response]: None" + + let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None" + + return """ + \(requestDescription) + \(responseDescription) + [Network Duration]: \(networkDuration) + [Serialization Duration]: \(serializationDuration)s + [Result]: \(result) + """ + } +} + +// MARK: - + +extension DataResponse { + /// Evaluates the specified closure when the result of this `DataResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `map` method with a closure that does not throw. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleInt = possibleData.map { $0.count } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A `DataResponse` whose result wraps the value returned by the given closure. If this instance's + /// result is a failure, returns a response wrapping the same failure. + public func map(_ transform: (Success) -> NewSuccess) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.map(transform)) + } + + /// Evaluates the given closure when the result of this `DataResponse` is a success, passing the unwrapped result + /// value as a parameter. + /// + /// Use the `tryMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleObject = possibleData.tryMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A success or failure `DataResponse` depending on the result of the given closure. If this instance's + /// result is a failure, returns the same failure. + public func tryMap(_ transform: (Success) throws -> NewSuccess) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMap(transform)) + } + + /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `mapError` function with a closure that does not throw. For example: + /// + /// let possibleData: DataResponse = ... + /// let withMyError = possibleData.mapError { MyError.error($0) } + /// + /// - Parameter transform: A closure that takes the error of the instance. + /// + /// - Returns: A `DataResponse` instance containing the result of the transform. + public func mapError(_ transform: (Failure) -> NewFailure) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.mapError(transform)) + } + + /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `tryMapError` function with a closure that may throw an error. For example: + /// + /// let possibleData: DataResponse = ... + /// let possibleObject = possibleData.tryMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `DataResponse` instance containing the result of the transform. + public func tryMapError(_ transform: (Failure) throws -> NewFailure) -> DataResponse { + DataResponse(request: request, + response: response, + data: data, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMapError(transform)) + } +} + +// MARK: - + +/// Used to store all data associated with a serialized response of a download request. +public struct DownloadResponse: Sendable where Success: Sendable, Failure: Sendable { + /// The URL request sent to the server. + public let request: URLRequest? + + /// The server's response to the URL request. + public let response: HTTPURLResponse? + + /// The final destination URL of the data returned from the server after it is moved. + public let fileURL: URL? + + /// The resume data generated if the request was cancelled. + public let resumeData: Data? + + /// The final metrics of the response. + /// + /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.` + /// + public let metrics: URLSessionTaskMetrics? + + /// The time taken to serialize the response. + public let serializationDuration: TimeInterval + + /// The result of response serialization. + public let result: Result + + /// Returns the associated value of the result if it is a success, `nil` otherwise. + public var value: Success? { result.success } + + /// Returns the associated error value if the result if it is a failure, `nil` otherwise. + public var error: Failure? { result.failure } + + /// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization. + /// + /// - Parameters: + /// - request: The `URLRequest` sent to the server. + /// - response: The `HTTPURLResponse` from the server. + /// - fileURL: The final destination URL of the data returned from the server after it is moved. + /// - resumeData: The resume `Data` generated if the request was cancelled. + /// - metrics: The `URLSessionTaskMetrics` of the `DownloadRequest`. + /// - serializationDuration: The duration taken by serialization. + /// - result: The `Result` of response serialization. + public init(request: URLRequest?, + response: HTTPURLResponse?, + fileURL: URL?, + resumeData: Data?, + metrics: URLSessionTaskMetrics?, + serializationDuration: TimeInterval, + result: Result) { + self.request = request + self.response = response + self.fileURL = fileURL + self.resumeData = resumeData + self.metrics = metrics + self.serializationDuration = serializationDuration + self.result = result + } +} + +// MARK: - + +extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible { + /// The textual representation used when written to an output stream, which includes whether the result was a + /// success or failure. + public var description: String { + "\(result)" + } + + /// The debug textual representation used when written to an output stream, which includes the URL request, the URL + /// response, the temporary and destination URLs, the resume data, the durations of the network and serialization + /// actions, and the response serialization result. + public var debugDescription: String { + guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" } + + let requestDescription = DebugDescription.description(of: urlRequest) + let responseDescription = response.map(DebugDescription.description(of:)) ?? "[Response]: None" + let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None" + let resumeDataDescription = resumeData.map { "\($0)" } ?? "None" + + return """ + \(requestDescription) + \(responseDescription) + [File URL]: \(fileURL?.path ?? "None") + [Resume Data]: \(resumeDataDescription) + [Network Duration]: \(networkDuration) + [Serialization Duration]: \(serializationDuration)s + [Result]: \(result) + """ + } +} + +// MARK: - + +extension DownloadResponse { + /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `map` method with a closure that does not throw. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleInt = possibleData.map { $0.count } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A `DownloadResponse` whose result wraps the value returned by the given closure. If this instance's + /// result is a failure, returns a response wrapping the same failure. + public func map(_ transform: (Success) -> NewSuccess) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.map(transform)) + } + + /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped + /// result value as a parameter. + /// + /// Use the `tryMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleObject = possibleData.tryMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance's result. + /// + /// - returns: A success or failure `DownloadResponse` depending on the result of the given closure. If this + /// instance's result is a failure, returns the same failure. + public func tryMap(_ transform: (Success) throws -> NewSuccess) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMap(transform)) + } + + /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `mapError` function with a closure that does not throw. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let withMyError = possibleData.mapError { MyError.error($0) } + /// + /// - Parameter transform: A closure that takes the error of the instance. + /// + /// - Returns: A `DownloadResponse` instance containing the result of the transform. + public func mapError(_ transform: (Failure) -> NewFailure) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.mapError(transform)) + } + + /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `tryMapError` function with a closure that may throw an error. For example: + /// + /// let possibleData: DownloadResponse = ... + /// let possibleObject = possibleData.tryMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `DownloadResponse` instance containing the result of the transform. + public func tryMapError(_ transform: (Failure) throws -> NewFailure) -> DownloadResponse { + DownloadResponse(request: request, + response: response, + fileURL: fileURL, + resumeData: resumeData, + metrics: metrics, + serializationDuration: serializationDuration, + result: result.tryMapError(transform)) + } +} + +private enum DebugDescription { + static func description(of request: URLRequest) -> String { + let requestSummary = "\(request.httpMethod!) \(request)" + let requestHeadersDescription = DebugDescription.description(for: request.headers) + let requestBodyDescription = DebugDescription.description(for: request.httpBody, headers: request.headers) + + return """ + [Request]: \(requestSummary) + \(requestHeadersDescription.indentingNewlines()) + \(requestBodyDescription.indentingNewlines()) + """ + } + + static func description(of response: HTTPURLResponse) -> String { + """ + [Response]: + [Status Code]: \(response.statusCode) + \(DebugDescription.description(for: response.headers).indentingNewlines()) + """ + } + + static func description(for headers: HTTPHeaders) -> String { + guard !headers.isEmpty else { return "[Headers]: None" } + + let headerDescription = "\(headers.sorted())".indentingNewlines() + return """ + [Headers]: + \(headerDescription) + """ + } + + static func description(for data: Data?, + headers: HTTPHeaders, + allowingPrintableTypes printableTypes: [String] = ["json", "xml", "text"], + maximumLength: Int = 100_000) -> String { + guard let data, !data.isEmpty else { return "[Body]: None" } + + guard + data.count <= maximumLength, + printableTypes.compactMap({ headers["Content-Type"]?.contains($0) }).contains(true) + else { return "[Body]: \(data.count) bytes" } + + return """ + [Body]: + \(String(decoding: data, as: UTF8.self) + .trimmingCharacters(in: .whitespacesAndNewlines) + .indentingNewlines()) + """ + } +} + +extension String { + fileprivate func indentingNewlines(by spaceCount: Int = 4) -> String { + let spaces = String(repeating: " ", count: spaceCount) + return replacingOccurrences(of: "\n", with: "\n\(spaces)") + } +} diff --git a/Pods/Alamofire/Source/Core/Session.swift b/Pods/Alamofire/Source/Core/Session.swift new file mode 100644 index 0000000..341fd75 --- /dev/null +++ b/Pods/Alamofire/Source/Core/Session.swift @@ -0,0 +1,1349 @@ +// +// Session.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// `Session` creates and manages Alamofire's `Request` types during their lifetimes. It also provides common +/// functionality for all `Request`s, including queuing, interception, trust management, redirect handling, and response +/// cache handling. +open class Session: @unchecked Sendable { + /// Shared singleton instance used by all `AF.request` APIs. Cannot be modified. + public static let `default` = Session() + + /// Underlying `URLSession` used to create `URLSessionTasks` for this instance, and for which this instance's + /// `delegate` handles `URLSessionDelegate` callbacks. + /// + /// - Note: This instance should **NOT** be used to interact with the underlying `URLSessionTask`s. Doing so will + /// break internal Alamofire logic that tracks those tasks. + /// + public let session: URLSession + /// Instance's `SessionDelegate`, which handles the `URLSessionDelegate` methods and `Request` interaction. + public let delegate: SessionDelegate + /// Root `DispatchQueue` for all internal callbacks and state update. **MUST** be a serial queue. + public let rootQueue: DispatchQueue + /// Value determining whether this instance automatically calls `resume()` on all created `Request`s. + public let startRequestsImmediately: Bool + /// `DispatchQueue` on which `URLRequest`s are created asynchronously. By default this queue uses `rootQueue` as its + /// `target`, but a separate queue can be used if request creation is determined to be a bottleneck. Always profile + /// and test before introducing an additional queue. + public let requestQueue: DispatchQueue + /// `DispatchQueue` passed to all `Request`s on which they perform their response serialization. By default this + /// queue uses `rootQueue` as its `target` but a separate queue can be used if response serialization is determined + /// to be a bottleneck. Always profile and test before introducing an additional queue. + public let serializationQueue: DispatchQueue + /// `RequestInterceptor` used for all `Request` created by the instance. `RequestInterceptor`s can also be set on a + /// per-`Request` basis, in which case the `Request`'s interceptor takes precedence over this value. + public let interceptor: (any RequestInterceptor)? + /// `ServerTrustManager` instance used to evaluate all trust challenges and provide certificate and key pinning. + public let serverTrustManager: ServerTrustManager? + /// `RedirectHandler` instance used to provide customization for request redirection. + public let redirectHandler: (any RedirectHandler)? + /// `CachedResponseHandler` instance used to provide customization of cached response handling. + public let cachedResponseHandler: (any CachedResponseHandler)? + /// `CompositeEventMonitor` used to compose any passed `EventMonitor`s. + public let eventMonitor: CompositeEventMonitor + /// `EventMonitor`s included in all instances unless overwritten. `[AlamofireNotifications()]` by default. + @available(*, deprecated, message: "Use [AlamofireNotifications()] directly.") + public let defaultEventMonitors: [any EventMonitor] = [AlamofireNotifications()] + + /// Internal map between `Request`s and any `URLSessionTasks` that may be in flight for them. + var requestTaskMap = RequestTaskMap() + /// `Set` of currently active `Request`s. + var activeRequests: Set = [] + /// Completion events awaiting `URLSessionTaskMetrics`. + var waitingCompletions: [URLSessionTask: () -> Void] = [:] + + /// Creates a `Session` from a `URLSession` and other parameters. + /// + /// - Note: When passing a `URLSession`, you must create the `URLSession` with a specific `delegateQueue` value and + /// pass the `delegateQueue`'s `underlyingQueue` as the `rootQueue` parameter of this initializer. + /// + /// - Parameters: + /// - session: Underlying `URLSession` for this instance. + /// - delegate: `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request` + /// interaction. + /// - rootQueue: Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a + /// serial queue. + /// - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true` + /// by default. If set to `false`, all `Request`s created must have `.resume()` called. + /// on them for them to start. + /// - requestQueue: `DispatchQueue` on which to perform `URLRequest` creation. By default this queue + /// will use the `rootQueue` as its `target`. A separate queue can be used if it's + /// determined request creation is a bottleneck, but that should only be done after + /// careful testing and profiling. `nil` by default. + /// - serializationQueue: `DispatchQueue` on which to perform all response serialization. By default this + /// queue will use the `rootQueue` as its `target`. A separate queue can be used if + /// it's determined response serialization is a bottleneck, but that should only be + /// done after careful testing and profiling. `nil` by default. + /// - interceptor: `RequestInterceptor` to be used for all `Request`s created by this instance. `nil` + /// by default. + /// - serverTrustManager: `ServerTrustManager` to be used for all trust evaluations by this instance. `nil` + /// by default. + /// - redirectHandler: `RedirectHandler` to be used by all `Request`s created by this instance. `nil` by + /// default. + /// - cachedResponseHandler: `CachedResponseHandler` to be used by all `Request`s created by this instance. + /// `nil` by default. + /// - eventMonitors: `EventMonitor`s used by the instance. `[AlamofireNotifications()]` by default. + public init(session: URLSession, + delegate: SessionDelegate, + rootQueue: DispatchQueue, + startRequestsImmediately: Bool = true, + requestQueue: DispatchQueue? = nil, + serializationQueue: DispatchQueue? = nil, + interceptor: (any RequestInterceptor)? = nil, + serverTrustManager: ServerTrustManager? = nil, + redirectHandler: (any RedirectHandler)? = nil, + cachedResponseHandler: (any CachedResponseHandler)? = nil, + eventMonitors: [any EventMonitor] = [AlamofireNotifications()]) { + precondition(session.configuration.identifier == nil, + "Alamofire does not support background URLSessionConfigurations.") + precondition(session.delegateQueue.underlyingQueue === rootQueue, + "Session(session:) initializer must be passed the DispatchQueue used as the delegateQueue's underlyingQueue as rootQueue.") + + self.session = session + self.delegate = delegate + self.rootQueue = rootQueue + self.startRequestsImmediately = startRequestsImmediately + self.requestQueue = requestQueue ?? DispatchQueue(label: "\(rootQueue.label).requestQueue", target: rootQueue) + self.serializationQueue = serializationQueue ?? DispatchQueue(label: "\(rootQueue.label).serializationQueue", target: rootQueue) + self.interceptor = interceptor + self.serverTrustManager = serverTrustManager + self.redirectHandler = redirectHandler + self.cachedResponseHandler = cachedResponseHandler + eventMonitor = CompositeEventMonitor(monitors: eventMonitors) + delegate.eventMonitor = eventMonitor + delegate.stateProvider = self + } + + /// Creates a `Session` from a `URLSessionConfiguration`. + /// + /// - Note: This initializer lets Alamofire handle the creation of the underlying `URLSession` and its + /// `delegateQueue`, and is the recommended initializer for most uses. + /// + /// - Parameters: + /// - configuration: `URLSessionConfiguration` to be used to create the underlying `URLSession`. Changes + /// to this value after being passed to this initializer will have no effect. + /// `URLSessionConfiguration.af.default` by default. + /// - delegate: `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request` + /// interaction. `SessionDelegate()` by default. + /// - rootQueue: Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a + /// serial queue. `DispatchQueue(label: "org.alamofire.session.rootQueue")` by default. + /// - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true` + /// by default. If set to `false`, all `Request`s created must have `.resume()` called. + /// on them for them to start. + /// - requestQueue: `DispatchQueue` on which to perform `URLRequest` creation. By default this queue + /// will use the `rootQueue` as its `target`. A separate queue can be used if it's + /// determined request creation is a bottleneck, but that should only be done after + /// careful testing and profiling. `nil` by default. + /// - serializationQueue: `DispatchQueue` on which to perform all response serialization. By default this + /// queue will use the `rootQueue` as its `target`. A separate queue can be used if + /// it's determined response serialization is a bottleneck, but that should only be + /// done after careful testing and profiling. `nil` by default. + /// - interceptor: `RequestInterceptor` to be used for all `Request`s created by this instance. `nil` + /// by default. + /// - serverTrustManager: `ServerTrustManager` to be used for all trust evaluations by this instance. `nil` + /// by default. + /// - redirectHandler: `RedirectHandler` to be used by all `Request`s created by this instance. `nil` by + /// default. + /// - cachedResponseHandler: `CachedResponseHandler` to be used by all `Request`s created by this instance. + /// `nil` by default. + /// - eventMonitors: `EventMonitor`s used by the instance. `[AlamofireNotifications()]` by default. + public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default, + delegate: SessionDelegate = SessionDelegate(), + rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"), + startRequestsImmediately: Bool = true, + requestQueue: DispatchQueue? = nil, + serializationQueue: DispatchQueue? = nil, + interceptor: (any RequestInterceptor)? = nil, + serverTrustManager: ServerTrustManager? = nil, + redirectHandler: (any RedirectHandler)? = nil, + cachedResponseHandler: (any CachedResponseHandler)? = nil, + eventMonitors: [any EventMonitor] = [AlamofireNotifications()]) { + precondition(configuration.identifier == nil, "Alamofire does not support background URLSessionConfigurations.") + + // Retarget the incoming rootQueue for safety, unless it's the main queue, which we know is safe. + let serialRootQueue = (rootQueue === DispatchQueue.main) ? rootQueue : DispatchQueue(label: rootQueue.label, + target: rootQueue) + let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: serialRootQueue, name: "\(serialRootQueue.label).sessionDelegate") + let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue) + + self.init(session: session, + delegate: delegate, + rootQueue: serialRootQueue, + startRequestsImmediately: startRequestsImmediately, + requestQueue: requestQueue, + serializationQueue: serializationQueue, + interceptor: interceptor, + serverTrustManager: serverTrustManager, + redirectHandler: redirectHandler, + cachedResponseHandler: cachedResponseHandler, + eventMonitors: eventMonitors) + } + + deinit { + finishRequestsForDeinit() + session.invalidateAndCancel() + } + + // MARK: - All Requests API + + /// Perform an action on all active `Request`s. + /// + /// - Note: The provided `action` closure is performed asynchronously, meaning that some `Request`s may complete and + /// be unavailable by time it runs. Additionally, this action is performed on the instances's `rootQueue`, + /// so care should be taken that actions are fast. Once the work on the `Request`s is complete, any + /// additional work should be performed on another queue. + /// + /// - Parameters: + /// - action: Closure to perform with all `Request`s. + public func withAllRequests(perform action: @escaping @Sendable (Set) -> Void) { + rootQueue.async { + action(self.activeRequests) + } + } + + /// Cancel all active `Request`s, optionally calling a completion handler when complete. + /// + /// - Note: This is an asynchronous operation and does not block the creation of future `Request`s. Cancelled + /// `Request`s may not cancel immediately due internal work, and may not cancel at all if they are close to + /// completion when cancelled. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the completion handler is run. `.main` by default. + /// - completion: Closure to be called when all `Request`s have been cancelled. + public func cancelAllRequests(completingOnQueue queue: DispatchQueue = .main, completion: (@Sendable () -> Void)? = nil) { + withAllRequests { requests in + requests.forEach { $0.cancel() } + queue.async { + completion?() + } + } + } + + // MARK: - DataRequest + + /// Closure which provides a `URLRequest` for mutation. + public typealias RequestModifier = @Sendable (inout URLRequest) throws -> Void + + struct RequestConvertible: URLRequestConvertible { + let url: any URLConvertible + let method: HTTPMethod + let parameters: Parameters? + let encoding: any ParameterEncoding + let headers: HTTPHeaders? + let requestModifier: RequestModifier? + + func asURLRequest() throws -> URLRequest { + var request = try URLRequest(url: url, method: method, headers: headers) + try requestModifier?(&request) + + return try encoding.encode(request, with: parameters) + } + } + + /// Creates a `DataRequest` from a `URLRequest` created using the passed components and a `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Parameters` (a.k.a. `[String: Any]`) value to be encoded into the `URLRequest`. `nil` by + /// default. + /// - encoding: `ParameterEncoding` to be used to encode the `parameters` value into the `URLRequest`. + /// `URLEncoding.default` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `DataRequest`. + open func request(_ convertible: any URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: any ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil) -> DataRequest { + let convertible = RequestConvertible(url: convertible, + method: method, + parameters: parameters, + encoding: encoding, + headers: headers, + requestModifier: requestModifier) + + return request(convertible, interceptor: interceptor) + } + + struct RequestEncodableConvertible: URLRequestConvertible { + let url: any URLConvertible + let method: HTTPMethod + let parameters: Parameters? + let encoder: any ParameterEncoder + let headers: HTTPHeaders? + let requestModifier: RequestModifier? + + func asURLRequest() throws -> URLRequest { + var request = try URLRequest(url: url, method: method, headers: headers) + try requestModifier?(&request) + + return try parameters.map { try encoder.encode($0, into: request) } ?? request + } + } + + /// Creates a `DataRequest` from a `URLRequest` created using the passed components, `Encodable` parameters, and a + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Encodable` value to be encoded into the `URLRequest`. `nil` by default. + /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`. + /// `URLEncodedFormParameterEncoder.default` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from + /// the provided parameters. `nil` by default. + /// + /// - Returns: The created `DataRequest`. + open func request(_ convertible: any URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoder: any ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil) -> DataRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: parameters, + encoder: encoder, + headers: headers, + requestModifier: requestModifier) + + return request(convertible, interceptor: interceptor) + } + + /// Creates a `DataRequest` from a `URLRequestConvertible` value and a `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// + /// - Returns: The created `DataRequest`. + open func request(_ convertible: any URLRequestConvertible, interceptor: (any RequestInterceptor)? = nil) -> DataRequest { + let request = DataRequest(convertible: convertible, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self) + + perform(request) + + return request + } + + // MARK: - DataStreamRequest + + /// Creates a `DataStreamRequest` from the passed components, `Encodable` parameters, and `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Encodable` value to be encoded into the `URLRequest`. `nil` by default. + /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the + /// `URLRequest`. + /// `URLEncodedFormParameterEncoder.default` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` + /// is thrown while serializing stream `Data`. `false` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` + /// by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from + /// the provided parameters. `nil` by default. + /// + /// - Returns: The created `DataStream` request. + open func streamRequest(_ convertible: any URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoder: any ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + automaticallyCancelOnStreamError: Bool = false, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil) -> DataStreamRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: parameters, + encoder: encoder, + headers: headers, + requestModifier: requestModifier) + + return streamRequest(convertible, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + interceptor: interceptor) + } + + /// Creates a `DataStreamRequest` from the passed components and `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` + /// is thrown while serializing stream `Data`. `false` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` + /// by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from + /// the provided parameters. `nil` by default. + /// + /// - Returns: The created `DataStream` request. + open func streamRequest(_ convertible: any URLConvertible, + method: HTTPMethod = .get, + headers: HTTPHeaders? = nil, + automaticallyCancelOnStreamError: Bool = false, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil) -> DataStreamRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: Empty?.none, + encoder: URLEncodedFormParameterEncoder.default, + headers: headers, + requestModifier: requestModifier) + + return streamRequest(convertible, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + interceptor: interceptor) + } + + /// Creates a `DataStreamRequest` from the passed `URLRequestConvertible` value and `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - automaticallyCancelOnStreamError: `Bool` indicating whether the instance should be canceled when an `Error` + /// is thrown while serializing stream `Data`. `false` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` + /// by default. + /// + /// - Returns: The created `DataStreamRequest`. + open func streamRequest(_ convertible: any URLRequestConvertible, + automaticallyCancelOnStreamError: Bool = false, + interceptor: (any RequestInterceptor)? = nil) -> DataStreamRequest { + let request = DataStreamRequest(convertible: convertible, + automaticallyCancelOnStreamError: automaticallyCancelOnStreamError, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self) + + perform(request) + + return request + } + + #if canImport(Darwin) && !canImport(FoundationNetworking) // Only Apple platforms support URLSessionWebSocketTask. + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @_spi(WebSocket) open func webSocketRequest( + to url: any URLConvertible, + configuration: WebSocketRequest.Configuration = .default, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil + ) -> WebSocketRequest { + webSocketRequest( + to: url, + configuration: configuration, + parameters: Empty?.none, + encoder: URLEncodedFormParameterEncoder.default, + headers: headers, + interceptor: interceptor, + requestModifier: requestModifier + ) + } + + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @_spi(WebSocket) open func webSocketRequest( + to url: any URLConvertible, + configuration: WebSocketRequest.Configuration = .default, + parameters: Parameters? = nil, + encoder: any ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil + ) -> WebSocketRequest where Parameters: Encodable & Sendable { + let convertible = RequestEncodableConvertible(url: url, + method: .get, + parameters: parameters, + encoder: encoder, + headers: headers, + requestModifier: requestModifier) + let request = WebSocketRequest(convertible: convertible, + configuration: configuration, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self) + + perform(request) + + return request + } + + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + @_spi(WebSocket) open func webSocketRequest(performing convertible: any URLRequestConvertible, + configuration: WebSocketRequest.Configuration = .default, + interceptor: (any RequestInterceptor)? = nil) -> WebSocketRequest { + let request = WebSocketRequest(convertible: convertible, + configuration: configuration, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self) + + perform(request) + + return request + } + #endif + + // MARK: - DownloadRequest + + /// Creates a `DownloadRequest` using a `URLRequest` created using the passed components, `RequestInterceptor`, and + /// `Destination`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: `Parameters` (a.k.a. `[String: Any]`) value to be encoded into the `URLRequest`. `nil` by + /// default. + /// - encoding: `ParameterEncoding` to be used to encode the `parameters` value into the `URLRequest`. + /// Defaults to `URLEncoding.default`. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(_ convertible: any URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoding: any ParameterEncoding = URLEncoding.default, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let convertible = RequestConvertible(url: convertible, + method: method, + parameters: parameters, + encoding: encoding, + headers: headers, + requestModifier: requestModifier) + + return download(convertible, interceptor: interceptor, to: destination) + } + + /// Creates a `DownloadRequest` from a `URLRequest` created using the passed components, `Encodable` parameters, and + /// a `RequestInterceptor`. + /// + /// - Parameters: + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.get` by default. + /// - parameters: Value conforming to `Encodable` to be encoded into the `URLRequest`. `nil` by default. + /// - encoder: `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`. + /// Defaults to `URLEncodedFormParameterEncoder.default`. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(_ convertible: any URLConvertible, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + encoder: any ParameterEncoder = URLEncodedFormParameterEncoder.default, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + requestModifier: RequestModifier? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let convertible = RequestEncodableConvertible(url: convertible, + method: method, + parameters: parameters, + encoder: encoder, + headers: headers, + requestModifier: requestModifier) + + return download(convertible, interceptor: interceptor, to: destination) + } + + /// Creates a `DownloadRequest` from a `URLRequestConvertible` value, a `RequestInterceptor`, and a `Destination`. + /// + /// - Parameters: + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(_ convertible: any URLRequestConvertible, + interceptor: (any RequestInterceptor)? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let request = DownloadRequest(downloadable: .request(convertible), + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self, + destination: destination ?? DownloadRequest.defaultDestination) + + perform(request) + + return request + } + + /// Creates a `DownloadRequest` from the `resumeData` produced from a previously cancelled `DownloadRequest`, as + /// well as a `RequestInterceptor`, and a `Destination`. + /// + /// - Note: If `destination` is not specified, the download will be moved to a temporary location determined by + /// Alamofire. The file will not be deleted until the system purges the temporary files. + /// + /// - Note: On some versions of all Apple platforms (iOS 10 - 10.2, macOS 10.12 - 10.12.2, tvOS 10 - 10.1, watchOS 3 - 3.1.1), + /// `resumeData` is broken on background URL session configurations. There's an underlying bug in the `resumeData` + /// generation logic where the data is written incorrectly and will always fail to resume the download. For more + /// information about the bug and possible workarounds, please refer to the [this Stack Overflow post](http://stackoverflow.com/a/39347461/1342462). + /// + /// - Parameters: + /// - data: The resume data from a previously cancelled `DownloadRequest` or `URLSessionDownloadTask`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - destination: `DownloadRequest.Destination` closure used to determine how and where the downloaded file + /// should be moved. `nil` by default. + /// + /// - Returns: The created `DownloadRequest`. + open func download(resumingWith data: Data, + interceptor: (any RequestInterceptor)? = nil, + to destination: DownloadRequest.Destination? = nil) -> DownloadRequest { + let request = DownloadRequest(downloadable: .resumeData(data), + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: self, + destination: destination ?? DownloadRequest.defaultDestination) + + perform(request) + + return request + } + + // MARK: - UploadRequest + + struct ParameterlessRequestConvertible: URLRequestConvertible { + let url: any URLConvertible + let method: HTTPMethod + let headers: HTTPHeaders? + let requestModifier: RequestModifier? + + func asURLRequest() throws -> URLRequest { + var request = try URLRequest(url: url, method: method, headers: headers) + try requestModifier?(&request) + + return request + } + } + + struct Upload: UploadConvertible { + let request: any URLRequestConvertible + let uploadable: any UploadableConvertible + + func createUploadable() throws -> UploadRequest.Uploadable { + try uploadable.createUploadable() + } + + func asURLRequest() throws -> URLRequest { + try request.asURLRequest() + } + } + + // MARK: Data + + /// Creates an `UploadRequest` for the given `Data`, `URLRequest` components, and `RequestInterceptor`. + /// + /// - Parameters: + /// - data: The `Data` to upload. + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ data: Data, + to convertible: any URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: convertible, + method: method, + headers: headers, + requestModifier: requestModifier) + + return upload(data, with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the given `Data` using the `URLRequestConvertible` value and `RequestInterceptor`. + /// + /// - Parameters: + /// - data: The `Data` to upload. + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ data: Data, + with convertible: any URLRequestConvertible, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default) -> UploadRequest { + upload(.data(data), with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: File + + /// Creates an `UploadRequest` for the file at the given file `URL`, using a `URLRequest` from the provided + /// components and `RequestInterceptor`. + /// + /// - Parameters: + /// - fileURL: The `URL` of the file to upload. + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `UploadRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ fileURL: URL, + to convertible: any URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: convertible, + method: method, + headers: headers, + requestModifier: requestModifier) + + return upload(fileURL, with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the file at the given file `URL` using the `URLRequestConvertible` value and + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - fileURL: The `URL` of the file to upload. + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ fileURL: URL, + with convertible: any URLRequestConvertible, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default) -> UploadRequest { + upload(.file(fileURL, shouldRemove: false), with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: InputStream + + /// Creates an `UploadRequest` from the `InputStream` provided using a `URLRequest` from the provided components and + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - stream: The `InputStream` that provides the data to upload. + /// - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the provided + /// parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ stream: InputStream, + to convertible: any URLConvertible, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: convertible, + method: method, + headers: headers, + requestModifier: requestModifier) + + return upload(stream, with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` from the provided `InputStream` using the `URLRequestConvertible` value and + /// `RequestInterceptor`. + /// + /// - Parameters: + /// - stream: The `InputStream` that provides the data to upload. + /// - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(_ stream: InputStream, + with convertible: any URLRequestConvertible, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default) -> UploadRequest { + upload(.stream(stream), with: convertible, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: MultipartFormData + + /// Creates an `UploadRequest` for the multipart form data built using a closure and sent using the provided + /// `URLRequest` components and `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` building closure. + /// - url: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is + /// written to disk before being uploaded. `.default` instance by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the + /// provided parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: @escaping (MultipartFormData) -> Void, + to url: any URLConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: url, + method: method, + headers: headers, + requestModifier: requestModifier) + + let formData = MultipartFormData(fileManager: fileManager) + multipartFormData(formData) + + return upload(multipartFormData: formData, + with: convertible, + usingThreshold: encodingMemoryThreshold, + interceptor: interceptor, + fileManager: fileManager) + } + + /// Creates an `UploadRequest` using a `MultipartFormData` building closure, the provided `URLRequestConvertible` + /// value, and a `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` building closure. + /// - request: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is + /// written to disk before being uploaded. `.default` instance by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: @escaping (MultipartFormData) -> Void, + with request: any URLRequestConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default) -> UploadRequest { + let formData = MultipartFormData(fileManager: fileManager) + multipartFormData(formData) + + return upload(multipartFormData: formData, + with: request, + usingThreshold: encodingMemoryThreshold, + interceptor: interceptor, + fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the provided `URLRequest` components + /// and `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` instance to upload. + /// - url: `URLConvertible` value to be used as the `URLRequest`'s `URL`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - method: `HTTPMethod` for the `URLRequest`. `.post` by default. + /// - headers: `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` to be used if the form data exceeds the memory threshold and is + /// written to disk before being uploaded. `.default` instance by default. + /// - requestModifier: `RequestModifier` which will be applied to the `URLRequest` created from the + /// provided parameters. `nil` by default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: MultipartFormData, + to url: any URLConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + method: HTTPMethod = .post, + headers: HTTPHeaders? = nil, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default, + requestModifier: RequestModifier? = nil) -> UploadRequest { + let convertible = ParameterlessRequestConvertible(url: url, + method: method, + headers: headers, + requestModifier: requestModifier) + + let multipartUpload = MultipartUpload(encodingMemoryThreshold: encodingMemoryThreshold, + request: convertible, + multipartFormData: multipartFormData) + + return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager) + } + + /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the providing `URLRequestConvertible` + /// value and `RequestInterceptor`. + /// + /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative + /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most + /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to + /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory + /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be + /// used for larger payloads such as video content. + /// + /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory + /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`, + /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk + /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding + /// technique was used. + /// + /// - Parameters: + /// - multipartFormData: `MultipartFormData` instance to upload. + /// - request: `URLRequestConvertible` value to be used to create the `URLRequest`. + /// - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or + /// onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by + /// default. + /// - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default. + /// - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by + /// default. + /// + /// - Returns: The created `UploadRequest`. + open func upload(multipartFormData: MultipartFormData, + with request: any URLRequestConvertible, + usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold, + interceptor: (any RequestInterceptor)? = nil, + fileManager: FileManager = .default) -> UploadRequest { + let multipartUpload = MultipartUpload(encodingMemoryThreshold: encodingMemoryThreshold, + request: request, + multipartFormData: multipartFormData) + + return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager) + } + + // MARK: - Internal API + + // MARK: Uploadable + + func upload(_ uploadable: UploadRequest.Uploadable, + with convertible: any URLRequestConvertible, + interceptor: (any RequestInterceptor)?, + fileManager: FileManager) -> UploadRequest { + let uploadable = Upload(request: convertible, uploadable: uploadable) + + return upload(uploadable, interceptor: interceptor, fileManager: fileManager) + } + + func upload(_ upload: any UploadConvertible, interceptor: (any RequestInterceptor)?, fileManager: FileManager) -> UploadRequest { + let request = UploadRequest(convertible: upload, + underlyingQueue: rootQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + fileManager: fileManager, + delegate: self) + + perform(request) + + return request + } + + // MARK: Perform + + /// Starts performing the provided `Request`. + /// + /// - Parameter request: The `Request` to perform. + func perform(_ request: Request) { + rootQueue.async { + guard !request.isCancelled else { return } + + self.activeRequests.insert(request) + + self.requestQueue.async { + // Leaf types must come first, otherwise they will cast as their superclass. + switch request { + case let r as UploadRequest: self.performUploadRequest(r) // UploadRequest must come before DataRequest due to subtype relationship. + case let r as DataRequest: self.performDataRequest(r) + case let r as DownloadRequest: self.performDownloadRequest(r) + case let r as DataStreamRequest: self.performDataStreamRequest(r) + default: + #if canImport(Darwin) && !canImport(FoundationNetworking) + if #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *), + let request = request as? WebSocketRequest { + self.performWebSocketRequest(request) + } else { + fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))") + } + #else + fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))") + #endif + } + } + } + } + + func performDataRequest(_ request: DataRequest) { + dispatchPrecondition(condition: .onQueue(requestQueue)) + + performSetupOperations(for: request, convertible: request.convertible) + } + + func performDataStreamRequest(_ request: DataStreamRequest) { + dispatchPrecondition(condition: .onQueue(requestQueue)) + + performSetupOperations(for: request, convertible: request.convertible) + } + + #if canImport(Darwin) && !canImport(FoundationNetworking) + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + func performWebSocketRequest(_ request: WebSocketRequest) { + dispatchPrecondition(condition: .onQueue(requestQueue)) + + performSetupOperations(for: request, convertible: request.convertible) + } + #endif + + func performUploadRequest(_ request: UploadRequest) { + dispatchPrecondition(condition: .onQueue(requestQueue)) + + performSetupOperations(for: request, convertible: request.convertible) { + do { + let uploadable = try request.upload.createUploadable() + self.rootQueue.async { request.didCreateUploadable(uploadable) } + return true + } catch { + self.rootQueue.async { request.didFailToCreateUploadable(with: error.asAFError(or: .createUploadableFailed(error: error))) } + return false + } + } + } + + func performDownloadRequest(_ request: DownloadRequest) { + dispatchPrecondition(condition: .onQueue(requestQueue)) + + switch request.downloadable { + case let .request(convertible): + performSetupOperations(for: request, convertible: convertible) + case let .resumeData(resumeData): + rootQueue.async { self.didReceiveResumeData(resumeData, for: request) } + } + } + + func performSetupOperations(for request: Request, + convertible: any URLRequestConvertible, + shouldCreateTask: @escaping @Sendable () -> Bool = { true }) { + dispatchPrecondition(condition: .onQueue(requestQueue)) + + let initialRequest: URLRequest + + do { + initialRequest = try convertible.asURLRequest() + try initialRequest.validate() + } catch { + rootQueue.async { request.didFailToCreateURLRequest(with: error.asAFError(or: .createURLRequestFailed(error: error))) } + return + } + + rootQueue.async { request.didCreateInitialURLRequest(initialRequest) } + + guard !request.isCancelled else { return } + + guard let adapter = adapter(for: request) else { + guard shouldCreateTask() else { return } + rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) } + return + } + + let adapterState = RequestAdapterState(requestID: request.id, session: self) + + adapter.adapt(initialRequest, using: adapterState) { result in + do { + let adaptedRequest = try result.get() + try adaptedRequest.validate() + + self.rootQueue.async { request.didAdaptInitialRequest(initialRequest, to: adaptedRequest) } + + guard shouldCreateTask() else { return } + + self.rootQueue.async { self.didCreateURLRequest(adaptedRequest, for: request) } + } catch { + self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: .requestAdaptationFailed(error: error)) } + } + } + } + + // MARK: - Task Handling + + func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + request.didCreateURLRequest(urlRequest) + + guard !request.isCancelled else { return } + + let task = request.task(for: urlRequest, using: session) + requestTaskMap[request] = task + request.didCreateTask(task) + + updateStatesForTask(task, request: request) + } + + func didReceiveResumeData(_ data: Data, for request: DownloadRequest) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + guard !request.isCancelled else { return } + + let task = request.task(forResumeData: data, using: session) + requestTaskMap[request] = task + request.didCreateTask(task) + + updateStatesForTask(task, request: request) + } + + func updateStatesForTask(_ task: URLSessionTask, request: Request) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + request.withState { state in + switch state { + case .initialized, .finished: + // Do nothing. + break + case .resumed: + task.resume() + rootQueue.async { request.didResumeTask(task) } + case .suspended: + task.suspend() + rootQueue.async { request.didSuspendTask(task) } + case .cancelled: + // Resume to ensure metrics are gathered. + task.resume() + task.cancel() + rootQueue.async { request.didCancelTask(task) } + } + } + } + + // MARK: - Adapters and Retriers + + func adapter(for request: Request) -> (any RequestAdapter)? { + if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor { + Interceptor(adapters: [requestInterceptor, sessionInterceptor]) + } else { + request.interceptor ?? interceptor + } + } + + func retrier(for request: Request) -> (any RequestRetrier)? { + if let requestInterceptor = request.interceptor, let sessionInterceptor = interceptor { + Interceptor(retriers: [requestInterceptor, sessionInterceptor]) + } else { + request.interceptor ?? interceptor + } + } + + // MARK: - Invalidation + + func finishRequestsForDeinit() { + for request in requestTaskMap.requests { + rootQueue.async { + request.finish(error: AFError.sessionDeinitialized) + } + } + } +} + +// MARK: - RequestDelegate + +extension Session: RequestDelegate { + public var sessionConfiguration: URLSessionConfiguration { + session.configuration + } + + public var startImmediately: Bool { startRequestsImmediately } + + public func cleanup(after request: Request) { + activeRequests.remove(request) + } + + public func retryResult(for request: Request, dueTo error: AFError, completion: @escaping @Sendable (RetryResult) -> Void) { + guard let retrier = retrier(for: request) else { + rootQueue.async { completion(.doNotRetry) } + return + } + + retrier.retry(request, for: self, dueTo: error) { retryResult in + self.rootQueue.async { + guard let retryResultError = retryResult.error else { completion(retryResult); return } + + let retryError = AFError.requestRetryFailed(retryError: retryResultError, originalError: error) + completion(.doNotRetryWithError(retryError)) + } + } + } + + public func retryRequest(_ request: Request, withDelay timeDelay: TimeInterval?) { + rootQueue.async { + let retry: @Sendable () -> Void = { + guard !request.isCancelled else { return } + + request.prepareForRetry() + self.perform(request) + } + + if let retryDelay = timeDelay { + self.rootQueue.after(retryDelay) { retry() } + } else { + retry() + } + } + } +} + +// MARK: - SessionStateProvider + +extension Session: SessionStateProvider { + func request(for task: URLSessionTask) -> Request? { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + return requestTaskMap[task] + } + + func didGatherMetricsForTask(_ task: URLSessionTask) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterGatheringMetricsForTask(task) + + if didDisassociate { + waitingCompletions[task]?() + waitingCompletions[task] = nil + } + } + + func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + let didDisassociate = requestTaskMap.disassociateIfNecessaryAfterCompletingTask(task) + + if didDisassociate { + completion() + } else { + waitingCompletions[task] = completion + } + } + + func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential? { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + return requestTaskMap[task]?.credential ?? + session.configuration.urlCredentialStorage?.defaultCredential(for: protectionSpace) + } + + func cancelRequestsForSessionInvalidation(with error: (any Error)?) { + dispatchPrecondition(condition: .onQueue(rootQueue)) + + requestTaskMap.requests.forEach { $0.finish(error: AFError.sessionInvalidated(error: error)) } + } +} diff --git a/Pods/Alamofire/Source/Core/SessionDelegate.swift b/Pods/Alamofire/Source/Core/SessionDelegate.swift new file mode 100644 index 0000000..1d120e6 --- /dev/null +++ b/Pods/Alamofire/Source/Core/SessionDelegate.swift @@ -0,0 +1,387 @@ +// +// SessionDelegate.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Class which implements the various `URLSessionDelegate` methods to connect various Alamofire features. +open class SessionDelegate: NSObject, @unchecked Sendable { + private let fileManager: FileManager + + weak var stateProvider: (any SessionStateProvider)? + var eventMonitor: (any EventMonitor)? + + /// Creates an instance from the given `FileManager`. + /// + /// - Parameter fileManager: `FileManager` to use for underlying file management, such as moving downloaded files. + /// `.default` by default. + public init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + /// Internal method to find and cast requests while maintaining some integrity checking. + /// + /// - Parameters: + /// - task: The `URLSessionTask` for which to find the associated `Request`. + /// - type: The `Request` subclass type to cast any `Request` associate with `task`. + func request(for task: URLSessionTask, as type: R.Type) -> R? { + guard let provider = stateProvider else { + assertionFailure("StateProvider is nil for task \(task.taskIdentifier).") + return nil + } + + return provider.request(for: task) as? R + } +} + +/// Type which provides various `Session` state values. +protocol SessionStateProvider: AnyObject, Sendable { + var serverTrustManager: ServerTrustManager? { get } + var redirectHandler: (any RedirectHandler)? { get } + var cachedResponseHandler: (any CachedResponseHandler)? { get } + + func request(for task: URLSessionTask) -> Request? + func didGatherMetricsForTask(_ task: URLSessionTask) + func didCompleteTask(_ task: URLSessionTask, completion: @escaping () -> Void) + func credential(for task: URLSessionTask, in protectionSpace: URLProtectionSpace) -> URLCredential? + func cancelRequestsForSessionInvalidation(with error: (any Error)?) +} + +// MARK: URLSessionDelegate + +extension SessionDelegate: URLSessionDelegate { + open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: (any Error)?) { + eventMonitor?.urlSession(session, didBecomeInvalidWithError: error) + + stateProvider?.cancelRequestsForSessionInvalidation(with: error) + } +} + +// MARK: URLSessionTaskDelegate + +extension SessionDelegate: URLSessionTaskDelegate { + /// Result of a `URLAuthenticationChallenge` evaluation. + typealias ChallengeEvaluation = (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?, error: AFError?) + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + eventMonitor?.urlSession(session, task: task, didReceive: challenge) + + let evaluation: ChallengeEvaluation + switch challenge.protectionSpace.authenticationMethod { + case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM, + NSURLAuthenticationMethodNegotiate: + evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task) + #if canImport(Security) + case NSURLAuthenticationMethodServerTrust: + evaluation = attemptServerTrustAuthentication(with: challenge) + case NSURLAuthenticationMethodClientCertificate: + evaluation = attemptCredentialAuthentication(for: challenge, belongingTo: task) + #endif + default: + evaluation = (.performDefaultHandling, nil, nil) + } + + if let error = evaluation.error { + stateProvider?.request(for: task)?.didFailTask(task, earlyWithError: error) + } + + completionHandler(evaluation.disposition, evaluation.credential) + } + + #if canImport(Security) + /// Evaluates the server trust `URLAuthenticationChallenge` received. + /// + /// - Parameter challenge: The `URLAuthenticationChallenge`. + /// + /// - Returns: The `ChallengeEvaluation`. + func attemptServerTrustAuthentication(with challenge: URLAuthenticationChallenge) -> ChallengeEvaluation { + let host = challenge.protectionSpace.host + + guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, + let trust = challenge.protectionSpace.serverTrust + else { + return (.performDefaultHandling, nil, nil) + } + + do { + guard let evaluator = try stateProvider?.serverTrustManager?.serverTrustEvaluator(forHost: host) else { + return (.performDefaultHandling, nil, nil) + } + + try evaluator.evaluate(trust, forHost: host) + + return (.useCredential, URLCredential(trust: trust), nil) + } catch { + return (.cancelAuthenticationChallenge, nil, error.asAFError(or: .serverTrustEvaluationFailed(reason: .customEvaluationFailed(error: error)))) + } + } + #endif + + /// Evaluates the credential-based authentication `URLAuthenticationChallenge` received for `task`. + /// + /// - Parameters: + /// - challenge: The `URLAuthenticationChallenge`. + /// - task: The `URLSessionTask` which received the challenge. + /// + /// - Returns: The `ChallengeEvaluation`. + func attemptCredentialAuthentication(for challenge: URLAuthenticationChallenge, + belongingTo task: URLSessionTask) -> ChallengeEvaluation { + guard challenge.previousFailureCount == 0 else { + return (.rejectProtectionSpace, nil, nil) + } + + guard let credential = stateProvider?.credential(for: task, in: challenge.protectionSpace) else { + return (.performDefaultHandling, nil, nil) + } + + return (.useCredential, credential, nil) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) { + eventMonitor?.urlSession(session, + task: task, + didSendBodyData: bytesSent, + totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend) + + stateProvider?.request(for: task)?.updateUploadProgress(totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) { + eventMonitor?.urlSession(session, taskNeedsNewBodyStream: task) + + guard let request = request(for: task, as: UploadRequest.self) else { + assertionFailure("needNewBodyStream did not find UploadRequest.") + completionHandler(nil) + return + } + + completionHandler(request.inputStream()) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void) { + eventMonitor?.urlSession(session, task: task, willPerformHTTPRedirection: response, newRequest: request) + + if let redirectHandler = stateProvider?.request(for: task)?.redirectHandler ?? stateProvider?.redirectHandler { + redirectHandler.task(task, willBeRedirectedTo: request, for: response, completion: completionHandler) + } else { + completionHandler(request) + } + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + eventMonitor?.urlSession(session, task: task, didFinishCollecting: metrics) + + stateProvider?.request(for: task)?.didGatherMetrics(metrics) + + stateProvider?.didGatherMetricsForTask(task) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { +// NSLog("URLSession: \(session), task: \(task), didCompleteWithError: \(error)") + eventMonitor?.urlSession(session, task: task, didCompleteWithError: error) + + let request = stateProvider?.request(for: task) + + stateProvider?.didCompleteTask(task) { + request?.didCompleteTask(task, with: error.map { $0.asAFError(or: .sessionTaskFailed(error: $0)) }) + } + } + + @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { + eventMonitor?.urlSession(session, taskIsWaitingForConnectivity: task) + } +} + +// MARK: URLSessionDataDelegate + +extension SessionDelegate: URLSessionDataDelegate { + open func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + didReceive response: URLResponse, + completionHandler: @escaping @Sendable (URLSession.ResponseDisposition) -> Void) { + eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: response) + + guard let response = response as? HTTPURLResponse else { completionHandler(.allow); return } + + if let request = request(for: dataTask, as: DataRequest.self) { + request.didReceiveResponse(response, completionHandler: completionHandler) + } else if let request = request(for: dataTask, as: DataStreamRequest.self) { + request.didReceiveResponse(response, completionHandler: completionHandler) + } else { + assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive response") + completionHandler(.allow) + return + } + } + + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + eventMonitor?.urlSession(session, dataTask: dataTask, didReceive: data) + + if let request = request(for: dataTask, as: DataRequest.self) { + request.didReceive(data: data) + } else if let request = request(for: dataTask, as: DataStreamRequest.self) { + request.didReceive(data: data) + } else { + assertionFailure("dataTask did not find DataRequest or DataStreamRequest in didReceive data") + return + } + } + + open func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse, + completionHandler: @escaping (CachedURLResponse?) -> Void) { + eventMonitor?.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse) + + if let handler = stateProvider?.request(for: dataTask)?.cachedResponseHandler ?? stateProvider?.cachedResponseHandler { + handler.dataTask(dataTask, willCacheResponse: proposedResponse, completion: completionHandler) + } else { + completionHandler(proposedResponse) + } + } +} + +// MARK: URLSessionWebSocketDelegate + +#if canImport(Darwin) && !canImport(FoundationNetworking) + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension SessionDelegate: URLSessionWebSocketDelegate { + open func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) { + // TODO: Add event monitor method. +// NSLog("URLSession: \(session), webSocketTask: \(webSocketTask), didOpenWithProtocol: \(`protocol` ?? "None")") + guard let request = request(for: webSocketTask, as: WebSocketRequest.self) else { + return + } + + request.didConnect(protocol: `protocol`) + } + + open func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { + // TODO: Add event monitor method. +// NSLog("URLSession: \(session), webSocketTask: \(webSocketTask), didCloseWithCode: \(closeCode.rawValue), reason: \(reason ?? Data())") + guard let request = request(for: webSocketTask, as: WebSocketRequest.self) else { + return + } + + // On 2021 OSes and above, empty reason is returned as empty Data rather than nil, so make it nil always. + let reason = (reason?.isEmpty == true) ? nil : reason + request.didDisconnect(closeCode: closeCode, reason: reason) + } +} + +#endif + +// MARK: URLSessionDownloadDelegate + +extension SessionDelegate: URLSessionDownloadDelegate { + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) { + eventMonitor?.urlSession(session, + downloadTask: downloadTask, + didResumeAtOffset: fileOffset, + expectedTotalBytes: expectedTotalBytes) + guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else { + assertionFailure("downloadTask did not find DownloadRequest.") + return + } + + downloadRequest.updateDownloadProgress(bytesWritten: fileOffset, + totalBytesExpectedToWrite: expectedTotalBytes) + } + + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) { + eventMonitor?.urlSession(session, + downloadTask: downloadTask, + didWriteData: bytesWritten, + totalBytesWritten: totalBytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite) + guard let downloadRequest = request(for: downloadTask, as: DownloadRequest.self) else { + assertionFailure("downloadTask did not find DownloadRequest.") + return + } + + downloadRequest.updateDownloadProgress(bytesWritten: bytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite) + } + + open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + eventMonitor?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) + + guard let request = request(for: downloadTask, as: DownloadRequest.self) else { + assertionFailure("downloadTask did not find DownloadRequest.") + return + } + + let (destination, options): (URL, DownloadRequest.Options) + if let response = request.response { + (destination, options) = request.destination(location, response) + } else { + // If there's no response this is likely a local file download, so generate the temporary URL directly. + (destination, options) = (DownloadRequest.defaultDestinationURL(location), []) + } + + eventMonitor?.request(request, didCreateDestinationURL: destination) + + do { + if options.contains(.removePreviousFile), fileManager.fileExists(atPath: destination.path) { + try fileManager.removeItem(at: destination) + } + + if options.contains(.createIntermediateDirectories) { + let directory = destination.deletingLastPathComponent() + try fileManager.createDirectory(at: directory, withIntermediateDirectories: true) + } + + try fileManager.moveItem(at: location, to: destination) + + request.didFinishDownloading(using: downloadTask, with: .success(destination)) + } catch { + request.didFinishDownloading(using: downloadTask, with: .failure(.downloadedFileMoveFailed(error: error, + source: location, + destination: destination))) + } + } +} diff --git a/Pods/Alamofire/Source/Core/URLConvertible+URLRequestConvertible.swift b/Pods/Alamofire/Source/Core/URLConvertible+URLRequestConvertible.swift new file mode 100644 index 0000000..de62531 --- /dev/null +++ b/Pods/Alamofire/Source/Core/URLConvertible+URLRequestConvertible.swift @@ -0,0 +1,105 @@ +// +// URLConvertible+URLRequestConvertible.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Types adopting the `URLConvertible` protocol can be used to construct `URL`s, which can then be used to construct +/// `URLRequest`s. +public protocol URLConvertible: Sendable { + /// Returns a `URL` from the conforming instance or throws. + /// + /// - Returns: The `URL` created from the instance. + /// - Throws: Any error thrown while creating the `URL`. + func asURL() throws -> URL +} + +extension String: URLConvertible { + /// Returns a `URL` if `self` can be used to initialize a `URL` instance, otherwise throws. + /// + /// - Returns: The `URL` initialized with `self`. + /// - Throws: An `AFError.invalidURL` instance. + public func asURL() throws -> URL { + guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) } + + return url + } +} + +extension URL: URLConvertible { + /// Returns `self`. + public func asURL() throws -> URL { self } +} + +extension URLComponents: URLConvertible { + /// Returns a `URL` if the `self`'s `url` is not nil, otherwise throws. + /// + /// - Returns: The `URL` from the `url` property. + /// - Throws: An `AFError.invalidURL` instance. + public func asURL() throws -> URL { + guard let url else { throw AFError.invalidURL(url: self) } + + return url + } +} + +// MARK: - + +/// Types adopting the `URLRequestConvertible` protocol can be used to safely construct `URLRequest`s. +public protocol URLRequestConvertible: Sendable { + /// Returns a `URLRequest` or throws if an `Error` was encountered. + /// + /// - Returns: A `URLRequest`. + /// - Throws: Any error thrown while constructing the `URLRequest`. + func asURLRequest() throws -> URLRequest +} + +extension URLRequestConvertible { + /// The `URLRequest` returned by discarding any `Error` encountered. + public var urlRequest: URLRequest? { try? asURLRequest() } +} + +extension URLRequest: URLRequestConvertible { + /// Returns `self`. + public func asURLRequest() throws -> URLRequest { self } +} + +// MARK: - + +extension URLRequest { + /// Creates an instance with the specified `url`, `method`, and `headers`. + /// + /// - Parameters: + /// - url: The `URLConvertible` value. + /// - method: The `HTTPMethod`. + /// - headers: The `HTTPHeaders`, `nil` by default. + /// - Throws: Any error thrown while converting the `URLConvertible` to a `URL`. + public init(url: any URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws { + let url = try url.asURL() + + self.init(url: url) + + httpMethod = method.rawValue + allHTTPHeaderFields = headers?.dictionary + } +} diff --git a/Pods/Alamofire/Source/Core/UploadRequest.swift b/Pods/Alamofire/Source/Core/UploadRequest.swift new file mode 100644 index 0000000..62c01e8 --- /dev/null +++ b/Pods/Alamofire/Source/Core/UploadRequest.swift @@ -0,0 +1,174 @@ +// +// UploadRequest.swift +// +// Copyright (c) 2014-2024 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// `DataRequest` subclass which handles `Data` upload from memory, file, or stream using `URLSessionUploadTask`. +public final class UploadRequest: DataRequest, @unchecked Sendable { + /// Type describing the origin of the upload, whether `Data`, file, or stream. + public enum Uploadable: @unchecked Sendable { // Must be @unchecked Sendable due to InputStream. + /// Upload from the provided `Data` value. + case data(Data) + /// Upload from the provided file `URL`, as well as a `Bool` determining whether the source file should be + /// automatically removed once uploaded. + case file(URL, shouldRemove: Bool) + /// Upload from the provided `InputStream`. + case stream(InputStream) + } + + // MARK: Initial State + + /// The `UploadableConvertible` value used to produce the `Uploadable` value for this instance. + public let upload: any UploadableConvertible + + /// `FileManager` used to perform cleanup tasks, including the removal of multipart form encoded payloads written + /// to disk. + public let fileManager: FileManager + + // MARK: Mutable State + + /// `Uploadable` value used by the instance. + public var uploadable: Uploadable? + + /// Creates an `UploadRequest` using the provided parameters. + /// + /// - Parameters: + /// - id: `UUID` used for the `Hashable` and `Equatable` implementations. `UUID()` by default. + /// - convertible: `UploadConvertible` value used to determine the type of upload to be performed. + /// - underlyingQueue: `DispatchQueue` on which all internal `Request` work is performed. + /// - serializationQueue: `DispatchQueue` on which all serialization work is performed. By default targets + /// `underlyingQueue`, but can be passed another queue from a `Session`. + /// - eventMonitor: `EventMonitor` called for event callbacks from internal `Request` actions. + /// - interceptor: `RequestInterceptor` used throughout the request lifecycle. + /// - fileManager: `FileManager` used to perform cleanup tasks, including the removal of multipart form + /// encoded payloads written to disk. + /// - delegate: `RequestDelegate` that provides an interface to actions not performed by the `Request`. + init(id: UUID = UUID(), + convertible: any UploadConvertible, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: (any EventMonitor)?, + interceptor: (any RequestInterceptor)?, + fileManager: FileManager, + delegate: any RequestDelegate) { + upload = convertible + self.fileManager = fileManager + + super.init(id: id, + convertible: convertible, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + /// Called when the `Uploadable` value has been created from the `UploadConvertible`. + /// + /// - Parameter uploadable: The `Uploadable` that was created. + func didCreateUploadable(_ uploadable: Uploadable) { + self.uploadable = uploadable + + eventMonitor?.request(self, didCreateUploadable: uploadable) + } + + /// Called when the `Uploadable` value could not be created. + /// + /// - Parameter error: `AFError` produced by the failure. + func didFailToCreateUploadable(with error: AFError) { + self.error = error + + eventMonitor?.request(self, didFailToCreateUploadableWithError: error) + + retryOrFinish(error: error) + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + guard let uploadable else { + fatalError("Attempting to create a URLSessionUploadTask when Uploadable value doesn't exist.") + } + + switch uploadable { + case let .data(data): return session.uploadTask(with: request, from: data) + case let .file(url, _): return session.uploadTask(with: request, fromFile: url) + case .stream: return session.uploadTask(withStreamedRequest: request) + } + } + + override func reset() { + // Uploadable must be recreated on every retry. + uploadable = nil + + super.reset() + } + + /// Produces the `InputStream` from `uploadable`, if it can. + /// + /// - Note: Calling this method with a non-`.stream` `Uploadable` is a logic error and will crash. + /// + /// - Returns: The `InputStream`. + func inputStream() -> InputStream { + guard let uploadable else { + fatalError("Attempting to access the input stream but the uploadable doesn't exist.") + } + + guard case let .stream(stream) = uploadable else { + fatalError("Attempted to access the stream of an UploadRequest that wasn't created with one.") + } + + eventMonitor?.request(self, didProvideInputStream: stream) + + return stream + } + + override public func cleanup() { + defer { super.cleanup() } + + guard + let uploadable, + case let .file(url, shouldRemove) = uploadable, + shouldRemove + else { return } + + try? fileManager.removeItem(at: url) + } +} + +/// A type that can produce an `UploadRequest.Uploadable` value. +public protocol UploadableConvertible: Sendable { + /// Produces an `UploadRequest.Uploadable` value from the instance. + /// + /// - Returns: The `UploadRequest.Uploadable`. + /// - Throws: Any `Error` produced during creation. + func createUploadable() throws -> UploadRequest.Uploadable +} + +extension UploadRequest.Uploadable: UploadableConvertible { + public func createUploadable() throws -> UploadRequest.Uploadable { + self + } +} + +/// A type that can be converted to an upload, whether from an `UploadRequest.Uploadable` or `URLRequestConvertible`. +public protocol UploadConvertible: UploadableConvertible & URLRequestConvertible {} diff --git a/Pods/Alamofire/Source/Core/WebSocketRequest.swift b/Pods/Alamofire/Source/Core/WebSocketRequest.swift new file mode 100644 index 0000000..c835b94 --- /dev/null +++ b/Pods/Alamofire/Source/Core/WebSocketRequest.swift @@ -0,0 +1,568 @@ +// +// WebSocketRequest.swift +// +// Copyright (c) 2014-2024 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +#if canImport(Darwin) && !canImport(FoundationNetworking) // Only Apple platforms support URLSessionWebSocketTask. + +import Foundation + +/// `Request` subclass which manages a WebSocket connection using `URLSessionWebSocketTask`. +/// +/// - Note: This type is currently experimental. There will be breaking changes before the final public release, +/// especially around adoption of the typed throws feature in Swift 6. Please report any missing features or +/// bugs to https://github.com/Alamofire/Alamofire/issues. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +@_spi(WebSocket) public final class WebSocketRequest: Request, @unchecked Sendable { + enum IncomingEvent { + case connected(protocol: String?) + case receivedMessage(URLSessionWebSocketTask.Message) + case disconnected(closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) + case completed(Completion) + } + + public struct Event: Sendable { + public enum Kind: Sendable { + case connected(protocol: String?) + case receivedMessage(Success) + case serializerFailed(Failure) + // Only received if the server disconnects or we cancel with code, not if we do a simple cancel or error. + case disconnected(closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) + case completed(Completion) + } + + weak var socket: WebSocketRequest? + + public let kind: Kind + public var message: Success? { + guard case let .receivedMessage(message) = kind else { return nil } + + return message + } + + init(socket: WebSocketRequest, kind: Kind) { + self.socket = socket + self.kind = kind + } + + public func close(sending closeCode: URLSessionWebSocketTask.CloseCode, reason: Data? = nil) { + socket?.close(sending: closeCode, reason: reason) + } + + public func cancel() { + socket?.cancel() + } + + public func sendPing(respondingOn queue: DispatchQueue = .main, onResponse: @escaping @Sendable (PingResponse) -> Void) { + socket?.sendPing(respondingOn: queue, onResponse: onResponse) + } + } + + public struct Completion: Sendable { + /// Last `URLRequest` issued by the instance. + public let request: URLRequest? + /// Last `HTTPURLResponse` received by the instance. + public let response: HTTPURLResponse? + /// Last `URLSessionTaskMetrics` produced for the instance. + public let metrics: URLSessionTaskMetrics? + /// `AFError` produced for the instance, if any. + public let error: AFError? + } + + public struct Configuration { + public static var `default`: Self { Self() } + + public static func `protocol`(_ protocol: String) -> Self { + Self(protocol: `protocol`) + } + + public static func maximumMessageSize(_ maximumMessageSize: Int) -> Self { + Self(maximumMessageSize: maximumMessageSize) + } + + public static func pingInterval(_ pingInterval: TimeInterval) -> Self { + Self(pingInterval: pingInterval) + } + + public let `protocol`: String? + public let maximumMessageSize: Int + public let pingInterval: TimeInterval? + + init(protocol: String? = nil, maximumMessageSize: Int = 1_048_576, pingInterval: TimeInterval? = nil) { + self.protocol = `protocol` + self.maximumMessageSize = maximumMessageSize + self.pingInterval = pingInterval + } + } + + /// Response to a sent ping. + public enum PingResponse: Sendable { + public struct Pong: Sendable { + let start: Date + let end: Date + let latency: TimeInterval + } + + /// Received a pong with the associated state. + case pong(Pong) + /// Received an error. + case error(any Error) + /// Did not send the ping, the request is cancelled or suspended. + case unsent + } + + struct SocketMutableState { + var enqueuedSends: [(message: URLSessionWebSocketTask.Message, + queue: DispatchQueue, + completionHandler: @Sendable (Result) -> Void)] = [] + var handlers: [(queue: DispatchQueue, handler: (_ event: IncomingEvent) -> Void)] = [] + var pingTimerItem: DispatchWorkItem? + } + + let socketMutableState = Protected(SocketMutableState()) + + var socket: URLSessionWebSocketTask? { + task as? URLSessionWebSocketTask + } + + public let convertible: any URLRequestConvertible + public let configuration: Configuration + + init(id: UUID = UUID(), + convertible: any URLRequestConvertible, + configuration: Configuration, + underlyingQueue: DispatchQueue, + serializationQueue: DispatchQueue, + eventMonitor: (any EventMonitor)?, + interceptor: (any RequestInterceptor)?, + delegate: any RequestDelegate) { + self.convertible = convertible + self.configuration = configuration + + super.init(id: id, + underlyingQueue: underlyingQueue, + serializationQueue: serializationQueue, + eventMonitor: eventMonitor, + interceptor: interceptor, + delegate: delegate) + } + + override func task(for request: URLRequest, using session: URLSession) -> URLSessionTask { + var copiedRequest = request + let task: URLSessionWebSocketTask + if let `protocol` = configuration.protocol { + copiedRequest.headers.update(.websocketProtocol(`protocol`)) + task = session.webSocketTask(with: copiedRequest) + } else { + task = session.webSocketTask(with: copiedRequest) + } + task.maximumMessageSize = configuration.maximumMessageSize + + return task + } + + override func didCreateTask(_ task: URLSessionTask) { + super.didCreateTask(task) + + guard let webSocketTask = task as? URLSessionWebSocketTask else { + fatalError("Invalid task of type \(task.self) created for WebSocketRequest.") + } + // TODO: What about the any old tasks? Reset their receive? + listen(to: webSocketTask) + + // Empty pending messages. + socketMutableState.write { state in + guard !state.enqueuedSends.isEmpty else { return } + + let sends = state.enqueuedSends + self.underlyingQueue.async { + for send in sends { + webSocketTask.send(send.message) { error in + send.queue.async { + send.completionHandler(Result(value: (), error: error)) + } + } + } + } + + state.enqueuedSends = [] + } + } + + func didClose() { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + mutableState.write { mutableState in + // Check whether error is cancellation or other websocket closing error. + // If so, remove it. + // Otherwise keep it. + if case let .sessionTaskFailed(error) = mutableState.error, (error as? URLError)?.code == .cancelled { + mutableState.error = nil + } + } + + // TODO: Still issue this event? + eventMonitor?.requestDidCancel(self) + } + + @discardableResult + public func close(sending closeCode: URLSessionWebSocketTask.CloseCode, reason: Data? = nil) -> Self { + cancelAutomaticPing() + + mutableState.write { mutableState in + guard mutableState.state.canTransitionTo(.cancelled) else { return } + + mutableState.state = .cancelled + + underlyingQueue.async { self.didClose() } + + guard let task = mutableState.tasks.last, task.state != .completed else { + underlyingQueue.async { self.finish() } + return + } + + // Resume to ensure metrics are gathered. + task.resume() + // Cast from state directly, not the property, otherwise the lock is recursive. + (mutableState.tasks.last as? URLSessionWebSocketTask)?.cancel(with: closeCode, reason: reason) + underlyingQueue.async { self.didCancelTask(task) } + } + + return self + } + + @discardableResult + override public func cancel() -> Self { + cancelAutomaticPing() + + return super.cancel() + } + + func didConnect(protocol: String?) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + socketMutableState.read { state in + // TODO: Capture HTTPURLResponse here too? + for handler in state.handlers { + // Saved handler calls out to serializationQueue immediately, then to handler's queue. + handler.handler(.connected(protocol: `protocol`)) + } + } + + if let pingInterval = configuration.pingInterval { + startAutomaticPing(every: pingInterval) + } + } + + @preconcurrency + public func sendPing(respondingOn queue: DispatchQueue = .main, onResponse: @escaping @Sendable (PingResponse) -> Void) { + guard isResumed else { + queue.async { onResponse(.unsent) } + return + } + + let start = Date() + let startTimestamp = ProcessInfo.processInfo.systemUptime + socket?.sendPing { error in + // Calls back on delegate queue / rootQueue / underlyingQueue + if let error { + queue.async { + onResponse(.error(error)) + } + // TODO: What to do with failed ping? Configure for failure, auto retry, or stop pinging? + } else { + let end = Date() + let endTimestamp = ProcessInfo.processInfo.systemUptime + let pong = PingResponse.Pong(start: start, end: end, latency: endTimestamp - startTimestamp) + + queue.async { + onResponse(.pong(pong)) + } + } + } + } + + func startAutomaticPing(every pingInterval: TimeInterval) { + socketMutableState.write { mutableState in + guard isResumed else { + // Defer out of lock. + defer { cancelAutomaticPing() } + return + } + + let item = DispatchWorkItem { [weak self] in + guard let self, isResumed else { return } + + sendPing(respondingOn: underlyingQueue) { response in + guard case .pong = response else { return } + + self.startAutomaticPing(every: pingInterval) + } + } + + mutableState.pingTimerItem = item + underlyingQueue.asyncAfter(deadline: .now() + pingInterval, execute: item) + } + } + + @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) + func startAutomaticPing(every duration: Duration) { + let interval = TimeInterval(duration.components.seconds) + (Double(duration.components.attoseconds) / 1e18) + startAutomaticPing(every: interval) + } + + func cancelAutomaticPing() { + socketMutableState.write { mutableState in + mutableState.pingTimerItem?.cancel() + mutableState.pingTimerItem = nil + } + } + + func didDisconnect(closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { + dispatchPrecondition(condition: .onQueue(underlyingQueue)) + + cancelAutomaticPing() + socketMutableState.read { state in + for handler in state.handlers { + // Saved handler calls out to serializationQueue immediately, then to handler's queue. + handler.handler(.disconnected(closeCode: closeCode, reason: reason)) + } + } + } + + private func listen(to task: URLSessionWebSocketTask) { + // TODO: Do we care about the cycle while receiving? + task.receive { result in + switch result { + case let .success(message): + self.socketMutableState.read { state in + for handler in state.handlers { + // Saved handler calls out to serializationQueue immediately, then to handler's queue. + handler.handler(.receivedMessage(message)) + } + } + + self.listen(to: task) + case .failure: + // It doesn't seem like any relevant errors are received here, just incorrect garbage, like errors when + // the socket disconnects. + break + } + } + } + + @preconcurrency + @discardableResult + public func streamSerializer( + _ serializer: Serializer, + on queue: DispatchQueue = .main, + handler: @escaping @Sendable (_ event: Event) -> Void + ) -> Self where Serializer: WebSocketMessageSerializer, Serializer.Failure == any Error { + forIncomingEvent(on: queue) { incomingEvent in + let event: Event + switch incomingEvent { + case let .connected(`protocol`): + event = .init(socket: self, kind: .connected(protocol: `protocol`)) + case let .receivedMessage(message): + do { + let serializedMessage = try serializer.decode(message) + event = .init(socket: self, kind: .receivedMessage(serializedMessage)) + } catch { + event = .init(socket: self, kind: .serializerFailed(error)) + } + case let .disconnected(closeCode, reason): + event = .init(socket: self, kind: .disconnected(closeCode: closeCode, reason: reason)) + case let .completed(completion): + event = .init(socket: self, kind: .completed(completion)) + } + + queue.async { handler(event) } + } + } + + @preconcurrency + @discardableResult + public func streamDecodableEvents( + _ type: Value.Type = Value.self, + on queue: DispatchQueue = .main, + using decoder: any DataDecoder = JSONDecoder(), + handler: @escaping @Sendable (_ event: Event) -> Void + ) -> Self where Value: Decodable { + streamSerializer(DecodableWebSocketMessageDecoder(decoder: decoder), on: queue, handler: handler) + } + + @preconcurrency + @discardableResult + public func streamDecodable( + _ type: Value.Type = Value.self, + on queue: DispatchQueue = .main, + using decoder: any DataDecoder = JSONDecoder(), + handler: @escaping @Sendable (_ value: Value) -> Void + ) -> Self where Value: Decodable & Sendable { + streamDecodableEvents(Value.self, on: queue) { event in + event.message.map(handler) + } + } + + @preconcurrency + @discardableResult + public func streamMessageEvents( + on queue: DispatchQueue = .main, + handler: @escaping @Sendable (_ event: Event) -> Void + ) -> Self { + forIncomingEvent(on: queue) { incomingEvent in + let event: Event = switch incomingEvent { + case let .connected(`protocol`): + .init(socket: self, kind: .connected(protocol: `protocol`)) + case let .receivedMessage(message): + .init(socket: self, kind: .receivedMessage(message)) + case let .disconnected(closeCode, reason): + .init(socket: self, kind: .disconnected(closeCode: closeCode, reason: reason)) + case let .completed(completion): + .init(socket: self, kind: .completed(completion)) + } + + queue.async { handler(event) } + } + } + + @preconcurrency + @discardableResult + public func streamMessages( + on queue: DispatchQueue = .main, + handler: @escaping @Sendable (_ message: URLSessionWebSocketTask.Message) -> Void + ) -> Self { + streamMessageEvents(on: queue) { event in + event.message.map(handler) + } + } + + func forIncomingEvent(on queue: DispatchQueue, handler: @escaping @Sendable (IncomingEvent) -> Void) -> Self { + socketMutableState.write { state in + state.handlers.append((queue: queue, handler: { incomingEvent in + self.serializationQueue.async { + handler(incomingEvent) + } + })) + } + + appendResponseSerializer { + self.responseSerializerDidComplete { + self.serializationQueue.async { + handler(.completed(.init(request: self.request, + response: self.response, + metrics: self.metrics, + error: self.error))) + } + } + } + + return self + } + + @preconcurrency + public func send(_ message: URLSessionWebSocketTask.Message, + queue: DispatchQueue = .main, + completionHandler: @escaping @Sendable (Result) -> Void) { + guard !(isCancelled || isFinished) else { return } + + guard let socket else { + // URLSessionWebSocketTask not created yet, enqueue the send. + socketMutableState.write { mutableState in + mutableState.enqueuedSends.append((message, queue, completionHandler)) + } + + return + } + + socket.send(message) { error in + queue.async { + completionHandler(Result(value: (), error: error)) + } + } + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +public protocol WebSocketMessageSerializer: Sendable { + associatedtype Output: Sendable + associatedtype Failure: Error = any Error + + func decode(_ message: URLSessionWebSocketTask.Message) throws -> Output +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension WebSocketMessageSerializer { + public static func json( + decoding _: Value.Type = Value.self, + using decoder: JSONDecoder = JSONDecoder() + ) -> DecodableWebSocketMessageDecoder where Self == DecodableWebSocketMessageDecoder { + Self(decoder: decoder) + } + + static var passthrough: PassthroughWebSocketMessageDecoder { + PassthroughWebSocketMessageDecoder() + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +struct PassthroughWebSocketMessageDecoder: WebSocketMessageSerializer { + public typealias Failure = Never + + public func decode(_ message: URLSessionWebSocketTask.Message) -> URLSessionWebSocketTask.Message { + message + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +public struct DecodableWebSocketMessageDecoder: WebSocketMessageSerializer { + public enum Error: Swift.Error { + case decoding(any Swift.Error) + case unknownMessage(description: String) + } + + public let decoder: any DataDecoder + + public init(decoder: any DataDecoder) { + self.decoder = decoder + } + + public func decode(_ message: URLSessionWebSocketTask.Message) throws -> Value { + let data: Data + switch message { + case let .data(messageData): + data = messageData + case let .string(string): + data = Data(string.utf8) + @unknown default: + throw Error.unknownMessage(description: String(describing: message)) + } + + do { + return try decoder.decode(Value.self, from: data) + } catch { + throw Error.decoding(error) + } + } +} + +#endif diff --git a/Pods/Alamofire/Source/Extensions/DispatchQueue+Alamofire.swift b/Pods/Alamofire/Source/Extensions/DispatchQueue+Alamofire.swift new file mode 100644 index 0000000..132ca93 --- /dev/null +++ b/Pods/Alamofire/Source/Extensions/DispatchQueue+Alamofire.swift @@ -0,0 +1,37 @@ +// +// DispatchQueue+Alamofire.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Dispatch +import Foundation + +extension DispatchQueue { + /// Execute the provided closure after a `TimeInterval`. + /// + /// - Parameters: + /// - delay: `TimeInterval` to delay execution. + /// - closure: Closure to execute. + func after(_ delay: TimeInterval, execute closure: @escaping @Sendable () -> Void) { + asyncAfter(deadline: .now() + delay, execute: closure) + } +} diff --git a/Pods/Alamofire/Source/Extensions/OperationQueue+Alamofire.swift b/Pods/Alamofire/Source/Extensions/OperationQueue+Alamofire.swift new file mode 100644 index 0000000..b06a0cc --- /dev/null +++ b/Pods/Alamofire/Source/Extensions/OperationQueue+Alamofire.swift @@ -0,0 +1,49 @@ +// +// OperationQueue+Alamofire.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +extension OperationQueue { + /// Creates an instance using the provided parameters. + /// + /// - Parameters: + /// - qualityOfService: `QualityOfService` to be applied to the queue. `.default` by default. + /// - maxConcurrentOperationCount: Maximum concurrent operations. + /// `OperationQueue.defaultMaxConcurrentOperationCount` by default. + /// - underlyingQueue: Underlying `DispatchQueue`. `nil` by default. + /// - name: Name for the queue. `nil` by default. + /// - startSuspended: Whether the queue starts suspended. `false` by default. + convenience init(qualityOfService: QualityOfService = .default, + maxConcurrentOperationCount: Int = OperationQueue.defaultMaxConcurrentOperationCount, + underlyingQueue: DispatchQueue? = nil, + name: String? = nil, + startSuspended: Bool = false) { + self.init() + self.qualityOfService = qualityOfService + self.maxConcurrentOperationCount = maxConcurrentOperationCount + self.underlyingQueue = underlyingQueue + self.name = name + isSuspended = startSuspended + } +} diff --git a/Pods/Alamofire/Source/Extensions/Result+Alamofire.swift b/Pods/Alamofire/Source/Extensions/Result+Alamofire.swift new file mode 100644 index 0000000..4444db4 --- /dev/null +++ b/Pods/Alamofire/Source/Extensions/Result+Alamofire.swift @@ -0,0 +1,120 @@ +// +// Result+Alamofire.swift +// +// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Default type of `Result` returned by Alamofire, with an `AFError` `Failure` type. +public typealias AFResult = Result + +// MARK: - Internal APIs + +extension Result { + /// Returns whether the instance is `.success`. + var isSuccess: Bool { + guard case .success = self else { return false } + return true + } + + /// Returns whether the instance is `.failure`. + var isFailure: Bool { + !isSuccess + } + + /// Returns the associated value if the result is a success, `nil` otherwise. + var success: Success? { + guard case let .success(value) = self else { return nil } + return value + } + + /// Returns the associated error value if the result is a failure, `nil` otherwise. + var failure: Failure? { + guard case let .failure(error) = self else { return nil } + return error + } + + /// Initializes a `Result` from value or error. Returns `.failure` if the error is non-nil, `.success` otherwise. + /// + /// - Parameters: + /// - value: A value. + /// - error: An `Error`. + init(value: Success, error: Failure?) { + if let error { + self = .failure(error) + } else { + self = .success(value) + } + } + + /// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter. + /// + /// Use the `tryMap` method with a closure that may throw an error. For example: + /// + /// let possibleData: Result = .success(Data(...)) + /// let possibleObject = possibleData.tryMap { + /// try JSONSerialization.jsonObject(with: $0) + /// } + /// + /// - parameter transform: A closure that takes the success value of the instance. + /// + /// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the + /// same failure. + func tryMap(_ transform: (Success) throws -> NewSuccess) -> Result { + switch self { + case let .success(value): + do { + return try .success(transform(value)) + } catch { + return .failure(error) + } + case let .failure(error): + return .failure(error) + } + } + + /// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter. + /// + /// Use the `tryMapError` function with a closure that may throw an error. For example: + /// + /// let possibleData: Result = .success(Data(...)) + /// let possibleObject = possibleData.tryMapError { + /// try someFailableFunction(taking: $0) + /// } + /// + /// - Parameter transform: A throwing closure that takes the error of the instance. + /// + /// - Returns: A `Result` instance containing the result of the transform. If this instance is a success, returns + /// the same success. + func tryMapError(_ transform: (Failure) throws -> NewFailure) -> Result { + switch self { + case let .failure(error): + do { + return try .failure(transform(error)) + } catch { + return .failure(error) + } + case let .success(value): + return .success(value) + } + } +} diff --git a/Pods/Alamofire/Source/Extensions/StringEncoding+Alamofire.swift b/Pods/Alamofire/Source/Extensions/StringEncoding+Alamofire.swift new file mode 100644 index 0000000..8fa6133 --- /dev/null +++ b/Pods/Alamofire/Source/Extensions/StringEncoding+Alamofire.swift @@ -0,0 +1,55 @@ +// +// StringEncoding+Alamofire.swift +// +// Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +extension String.Encoding { + /// Creates an encoding from the IANA charset name. + /// + /// - Notes: These mappings match those [provided by CoreFoundation](https://opensource.apple.com/source/CF/CF-476.18/CFStringUtilities.c.auto.html) + /// + /// - Parameter name: IANA charset name. + init?(ianaCharsetName name: String) { + switch name.lowercased() { + case "utf-8": + self = .utf8 + case "iso-8859-1": + self = .isoLatin1 + case "unicode-1-1", "iso-10646-ucs-2", "utf-16": + self = .utf16 + case "utf-16be": + self = .utf16BigEndian + case "utf-16le": + self = .utf16LittleEndian + case "utf-32": + self = .utf32 + case "utf-32be": + self = .utf32BigEndian + case "utf-32le": + self = .utf32LittleEndian + default: + return nil + } + } +} diff --git a/Pods/Alamofire/Source/Extensions/URLRequest+Alamofire.swift b/Pods/Alamofire/Source/Extensions/URLRequest+Alamofire.swift new file mode 100644 index 0000000..ab72fb5 --- /dev/null +++ b/Pods/Alamofire/Source/Extensions/URLRequest+Alamofire.swift @@ -0,0 +1,39 @@ +// +// URLRequest+Alamofire.swift +// +// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +extension URLRequest { + /// Returns the `httpMethod` as Alamofire's `HTTPMethod` type. + public var method: HTTPMethod? { + get { httpMethod.map(HTTPMethod.init) } + set { httpMethod = newValue?.rawValue } + } + + public func validate() throws { + if method == .get, let bodyData = httpBody { + throw AFError.urlRequestValidationFailed(reason: .bodyDataInGETRequest(bodyData)) + } + } +} diff --git a/Pods/Alamofire/Source/Extensions/URLSessionConfiguration+Alamofire.swift b/Pods/Alamofire/Source/Extensions/URLSessionConfiguration+Alamofire.swift new file mode 100644 index 0000000..292a8fe --- /dev/null +++ b/Pods/Alamofire/Source/Extensions/URLSessionConfiguration+Alamofire.swift @@ -0,0 +1,46 @@ +// +// URLSessionConfiguration+Alamofire.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +extension URLSessionConfiguration: AlamofireExtended {} +extension AlamofireExtension where ExtendedType: URLSessionConfiguration { + /// Alamofire's default configuration. Same as `URLSessionConfiguration.default` but adds Alamofire default + /// `Accept-Language`, `Accept-Encoding`, and `User-Agent` headers. + public static var `default`: URLSessionConfiguration { + let configuration = URLSessionConfiguration.default + configuration.headers = .default + + return configuration + } + + /// `.ephemeral` configuration with Alamofire's default `Accept-Language`, `Accept-Encoding`, and `User-Agent` + /// headers. + public static var ephemeral: URLSessionConfiguration { + let configuration = URLSessionConfiguration.ephemeral + configuration.headers = .default + + return configuration + } +} diff --git a/Pods/Alamofire/Source/Features/AlamofireExtended.swift b/Pods/Alamofire/Source/Features/AlamofireExtended.swift new file mode 100644 index 0000000..280c6de --- /dev/null +++ b/Pods/Alamofire/Source/Features/AlamofireExtended.swift @@ -0,0 +1,61 @@ +// +// AlamofireExtended.swift +// +// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +/// Type that acts as a generic extension point for all `AlamofireExtended` types. +public struct AlamofireExtension { + /// Stores the type or meta-type of any extended type. + public private(set) var type: ExtendedType + + /// Create an instance from the provided value. + /// + /// - Parameter type: Instance being extended. + public init(_ type: ExtendedType) { + self.type = type + } +} + +/// Protocol describing the `af` extension points for Alamofire extended types. +public protocol AlamofireExtended { + /// Type being extended. + associatedtype ExtendedType + + /// Static Alamofire extension point. + static var af: AlamofireExtension.Type { get set } + /// Instance Alamofire extension point. + var af: AlamofireExtension { get set } +} + +extension AlamofireExtended { + /// Static Alamofire extension point. + public static var af: AlamofireExtension.Type { + get { AlamofireExtension.self } + set {} + } + + /// Instance Alamofire extension point. + public var af: AlamofireExtension { + get { AlamofireExtension(self) } + set {} + } +} diff --git a/Pods/Alamofire/Source/Features/AuthenticationInterceptor.swift b/Pods/Alamofire/Source/Features/AuthenticationInterceptor.swift new file mode 100644 index 0000000..44900f9 --- /dev/null +++ b/Pods/Alamofire/Source/Features/AuthenticationInterceptor.swift @@ -0,0 +1,402 @@ +// +// AuthenticationInterceptor.swift +// +// Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Types adopting the `AuthenticationCredential` protocol can be used to authenticate `URLRequest`s. +/// +/// One common example of an `AuthenticationCredential` is an OAuth2 credential containing an access token used to +/// authenticate all requests on behalf of a user. The access token generally has an expiration window of 60 minutes +/// which will then require a refresh of the credential using the refresh token to generate a new access token. +public protocol AuthenticationCredential { + /// Whether the credential requires a refresh. This property should always return `true` when the credential is + /// expired. It is also wise to consider returning `true` when the credential will expire in several seconds or + /// minutes depending on the expiration window of the credential. + /// + /// For example, if the credential is valid for 60 minutes, then it would be wise to return `true` when the + /// credential is only valid for 5 minutes or less. That ensures the credential will not expire as it is passed + /// around backend services. + var requiresRefresh: Bool { get } +} + +// MARK: - + +/// Types adopting the `Authenticator` protocol can be used to authenticate `URLRequest`s with an +/// `AuthenticationCredential` as well as refresh the `AuthenticationCredential` when required. +public protocol Authenticator: AnyObject, Sendable { + /// The type of credential associated with the `Authenticator` instance. + associatedtype Credential: AuthenticationCredential & Sendable + + /// Applies the `Credential` to the `URLRequest`. + /// + /// In the case of OAuth2, the access token of the `Credential` would be added to the `URLRequest` as a Bearer + /// token to the `Authorization` header. + /// + /// - Parameters: + /// - credential: The `Credential`. + /// - urlRequest: The `URLRequest`. + func apply(_ credential: Credential, to urlRequest: inout URLRequest) + + /// Refreshes the `Credential` and executes the `completion` closure with the `Result` once complete. + /// + /// Refresh can be called in one of two ways. It can be called before the `Request` is actually executed due to + /// a `requiresRefresh` returning `true` during the adapt portion of the `Request` creation process. It can also + /// be triggered by a failed `Request` where the authentication server denied access due to an expired or + /// invalidated access token. + /// + /// In the case of OAuth2, this method would use the refresh token of the `Credential` to generate a new + /// `Credential` using the authentication service. Once complete, the `completion` closure should be called with + /// the new `Credential`, or the error that occurred. + /// + /// In general, if the refresh call fails with certain status codes from the authentication server (commonly a 401), + /// the refresh token in the `Credential` can no longer be used to generate a valid `Credential`. In these cases, + /// you will need to reauthenticate the user with their username / password. + /// + /// Please note, these are just general examples of common use cases. They are not meant to solve your specific + /// authentication server challenges. Please work with your authentication server team to ensure your + /// `Authenticator` logic matches their expectations. + /// + /// - Parameters: + /// - credential: The `Credential` to refresh. + /// - session: The `Session` requiring the refresh. + /// - completion: The closure to be executed once the refresh is complete. + func refresh(_ credential: Credential, for session: Session, completion: @escaping @Sendable (Result) -> Void) + + /// Determines whether the `URLRequest` failed due to an authentication error based on the `HTTPURLResponse`. + /// + /// If the authentication server **CANNOT** invalidate credentials after they are issued, then simply return `false` + /// for this method. If the authentication server **CAN** invalidate credentials due to security breaches, then you + /// will need to work with your authentication server team to understand how to identify when this occurs. + /// + /// In the case of OAuth2, where an authentication server can invalidate credentials, you will need to inspect the + /// `HTTPURLResponse` or possibly the `Error` for when this occurs. This is commonly handled by the authentication + /// server returning a 401 status code and some additional header to indicate an OAuth2 failure occurred. + /// + /// It is very important to understand how your authentication server works to be able to implement this correctly. + /// For example, if your authentication server returns a 401 when an OAuth2 error occurs, and your downstream + /// service also returns a 401 when you are not authorized to perform that operation, how do you know which layer + /// of the backend returned you a 401? You do not want to trigger a refresh unless you know your authentication + /// server is actually the layer rejecting the request. Again, work with your authentication server team to understand + /// how to identify an OAuth2 401 error vs. a downstream 401 error to avoid endless refresh loops. + /// + /// - Parameters: + /// - urlRequest: The `URLRequest`. + /// - response: The `HTTPURLResponse`. + /// - error: The `Error`. + /// + /// - Returns: `true` if the `URLRequest` failed due to an authentication error, `false` otherwise. + func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: any Error) -> Bool + + /// Determines whether the `URLRequest` is authenticated with the `Credential`. + /// + /// If the authentication server **CANNOT** invalidate credentials after they are issued, then simply return `true` + /// for this method. If the authentication server **CAN** invalidate credentials due to security breaches, then + /// read on. + /// + /// When an authentication server can invalidate credentials, it means that you may have a non-expired credential + /// that appears to be valid, but will be rejected by the authentication server when used. Generally when this + /// happens, a number of requests are all sent when the application is foregrounded, and all of them will be + /// rejected by the authentication server in the order they are received. The first failed request will trigger a + /// refresh internally, which will update the credential, and then retry all the queued requests with the new + /// credential. However, it is possible that some of the original requests will not return from the authentication + /// server until the refresh has completed. This is where this method comes in. + /// + /// When the authentication server rejects a credential, we need to check to make sure we haven't refreshed the + /// credential while the request was in flight. If it has already refreshed, then we don't need to trigger an + /// additional refresh. If it hasn't refreshed, then we need to refresh. + /// + /// Now that it is understood how the result of this method is used in the refresh lifecycle, let's walk through how + /// to implement it. You should return `true` in this method if the `URLRequest` is authenticated in a way that + /// matches the values in the `Credential`. In the case of OAuth2, this would mean that the Bearer token in the + /// `Authorization` header of the `URLRequest` matches the access token in the `Credential`. If it matches, then we + /// know the `Credential` was used to authenticate the `URLRequest` and should return `true`. If the Bearer token + /// did not match the access token, then you should return `false`. + /// + /// - Parameters: + /// - urlRequest: The `URLRequest`. + /// - credential: The `Credential`. + /// + /// - Returns: `true` if the `URLRequest` is authenticated with the `Credential`, `false` otherwise. + func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: Credential) -> Bool +} + +// MARK: - + +/// Represents various authentication failures that occur when using the `AuthenticationInterceptor`. All errors are +/// still vended from Alamofire as `AFError` types. The `AuthenticationError` instances will be embedded within +/// `AFError` `.requestAdaptationFailed` or `.requestRetryFailed` cases. +public enum AuthenticationError: Error { + /// The credential was missing so the request could not be authenticated. + case missingCredential + /// The credential was refreshed too many times within the `RefreshWindow`. + case excessiveRefresh +} + +// MARK: - + +/// The `AuthenticationInterceptor` class manages the queuing and threading complexity of authenticating requests. +/// It relies on an `Authenticator` type to handle the actual `URLRequest` authentication and `Credential` refresh. +public final class AuthenticationInterceptor: RequestInterceptor, Sendable where AuthenticatorType: Authenticator { + // MARK: Typealiases + + /// Type of credential used to authenticate requests. + public typealias Credential = AuthenticatorType.Credential + + // MARK: Helper Types + + /// Type that defines a time window used to identify excessive refresh calls. When enabled, prior to executing a + /// refresh, the `AuthenticationInterceptor` compares the timestamp history of previous refresh calls against the + /// `RefreshWindow`. If more refreshes have occurred within the refresh window than allowed, the refresh is + /// cancelled and an `AuthorizationError.excessiveRefresh` error is thrown. + public struct RefreshWindow { + /// `TimeInterval` defining the duration of the time window before the current time in which the number of + /// refresh attempts is compared against `maximumAttempts`. For example, if `interval` is 30 seconds, then the + /// `RefreshWindow` represents the past 30 seconds. If more attempts occurred in the past 30 seconds than + /// `maximumAttempts`, an `.excessiveRefresh` error will be thrown. + public let interval: TimeInterval + + /// Total refresh attempts allowed within `interval` before throwing an `.excessiveRefresh` error. + public let maximumAttempts: Int + + /// Creates a `RefreshWindow` instance from the specified `interval` and `maximumAttempts`. + /// + /// - Parameters: + /// - interval: `TimeInterval` defining the duration of the time window before the current time. + /// - maximumAttempts: The maximum attempts allowed within the `TimeInterval`. + public init(interval: TimeInterval = 30.0, maximumAttempts: Int = 5) { + self.interval = interval + self.maximumAttempts = maximumAttempts + } + } + + private struct AdaptOperation { + let urlRequest: URLRequest + let session: Session + let completion: @Sendable (Result) -> Void + } + + private enum AdaptResult { + case adapt(Credential) + case doNotAdapt(AuthenticationError) + case adaptDeferred + } + + private struct MutableState { + var credential: Credential? + + var isRefreshing = false + var refreshTimestamps: [TimeInterval] = [] + var refreshWindow: RefreshWindow? + + var adaptOperations: [AdaptOperation] = [] + var requestsToRetry: [@Sendable (RetryResult) -> Void] = [] + } + + // MARK: Properties + + /// The `Credential` used to authenticate requests. + public var credential: Credential? { + get { mutableState.credential } + set { mutableState.credential = newValue } + } + + let authenticator: AuthenticatorType + let queue = DispatchQueue(label: "org.alamofire.authentication.inspector") + + private let mutableState: Protected + + // MARK: Initialization + + /// Creates an `AuthenticationInterceptor` instance from the specified parameters. + /// + /// A `nil` `RefreshWindow` will result in the `AuthenticationInterceptor` not checking for excessive refresh calls. + /// It is recommended to always use a `RefreshWindow` to avoid endless refresh cycles. + /// + /// - Parameters: + /// - authenticator: The `Authenticator` type. + /// - credential: The `Credential` if it exists. `nil` by default. + /// - refreshWindow: The `RefreshWindow` used to identify excessive refresh calls. `RefreshWindow()` by default. + public init(authenticator: AuthenticatorType, + credential: Credential? = nil, + refreshWindow: RefreshWindow? = RefreshWindow()) { + self.authenticator = authenticator + mutableState = Protected(MutableState(credential: credential, refreshWindow: refreshWindow)) + } + + // MARK: Adapt + + public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping @Sendable (Result) -> Void) { + let adaptResult: AdaptResult = mutableState.write { mutableState in + // Queue the adapt operation if a refresh is already in place. + guard !mutableState.isRefreshing else { + let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion) + mutableState.adaptOperations.append(operation) + return .adaptDeferred + } + + // Throw missing credential error is the credential is missing. + guard let credential = mutableState.credential else { + let error = AuthenticationError.missingCredential + return .doNotAdapt(error) + } + + // Queue the adapt operation and trigger refresh operation if credential requires refresh. + guard !credential.requiresRefresh else { + let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion) + mutableState.adaptOperations.append(operation) + refresh(credential, for: session, insideLock: &mutableState) + return .adaptDeferred + } + + return .adapt(credential) + } + + switch adaptResult { + case let .adapt(credential): + var authenticatedRequest = urlRequest + authenticator.apply(credential, to: &authenticatedRequest) + completion(.success(authenticatedRequest)) + + case let .doNotAdapt(adaptError): + completion(.failure(adaptError)) + + case .adaptDeferred: + // No-op: adapt operation captured during refresh. + break + } + } + + // MARK: Retry + + public func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping @Sendable (RetryResult) -> Void) { + // Do not attempt retry if there was not an original request and response from the server. + guard let urlRequest = request.request, let response = request.response else { + completion(.doNotRetry) + return + } + + // Do not attempt retry unless the `Authenticator` verifies failure was due to authentication error (i.e. 401 status code). + guard authenticator.didRequest(urlRequest, with: response, failDueToAuthenticationError: error) else { + completion(.doNotRetry) + return + } + + // Do not attempt retry if there is no credential. + guard let credential else { + let error = AuthenticationError.missingCredential + completion(.doNotRetryWithError(error)) + return + } + + // Retry the request if the `Authenticator` verifies it was authenticated with a previous credential. + guard authenticator.isRequest(urlRequest, authenticatedWith: credential) else { + completion(.retry) + return + } + + mutableState.write { mutableState in + mutableState.requestsToRetry.append(completion) + + guard !mutableState.isRefreshing else { return } + + refresh(credential, for: session, insideLock: &mutableState) + } + } + + // MARK: Refresh + + private func refresh(_ credential: Credential, for session: Session, insideLock mutableState: inout MutableState) { + guard !isRefreshExcessive(insideLock: &mutableState) else { + let error = AuthenticationError.excessiveRefresh + handleRefreshFailure(error, insideLock: &mutableState) + return + } + + mutableState.refreshTimestamps.append(ProcessInfo.processInfo.systemUptime) + mutableState.isRefreshing = true + + // Dispatch to queue to hop out of the lock in case authenticator.refresh is implemented synchronously. + queue.async { + self.authenticator.refresh(credential, for: session) { result in + self.mutableState.write { mutableState in + switch result { + case let .success(credential): + self.handleRefreshSuccess(credential, insideLock: &mutableState) + case let .failure(error): + self.handleRefreshFailure(error, insideLock: &mutableState) + } + } + } + } + } + + private func isRefreshExcessive(insideLock mutableState: inout MutableState) -> Bool { + guard let refreshWindow = mutableState.refreshWindow else { return false } + + let refreshWindowMin = ProcessInfo.processInfo.systemUptime - refreshWindow.interval + + let refreshAttemptsWithinWindow = mutableState.refreshTimestamps.reduce(into: 0) { attempts, refreshTimestamp in + guard refreshWindowMin <= refreshTimestamp else { return } + attempts += 1 + } + + let isRefreshExcessive = refreshAttemptsWithinWindow >= refreshWindow.maximumAttempts + + return isRefreshExcessive + } + + private func handleRefreshSuccess(_ credential: Credential, insideLock mutableState: inout MutableState) { + mutableState.credential = credential + + let adaptOperations = mutableState.adaptOperations + let requestsToRetry = mutableState.requestsToRetry + + mutableState.adaptOperations.removeAll() + mutableState.requestsToRetry.removeAll() + + mutableState.isRefreshing = false + + // Dispatch to queue to hop out of the mutable state lock + queue.async { + adaptOperations.forEach { self.adapt($0.urlRequest, for: $0.session, completion: $0.completion) } + requestsToRetry.forEach { $0(.retry) } + } + } + + private func handleRefreshFailure(_ error: any Error, insideLock mutableState: inout MutableState) { + let adaptOperations = mutableState.adaptOperations + let requestsToRetry = mutableState.requestsToRetry + + mutableState.adaptOperations.removeAll() + mutableState.requestsToRetry.removeAll() + + mutableState.isRefreshing = false + + // Dispatch to queue to hop out of the mutable state lock + queue.async { + adaptOperations.forEach { $0.completion(.failure(error)) } + requestsToRetry.forEach { $0(.doNotRetryWithError(error)) } + } + } +} diff --git a/Pods/Alamofire/Source/Features/CachedResponseHandler.swift b/Pods/Alamofire/Source/Features/CachedResponseHandler.swift new file mode 100644 index 0000000..c31f4b4 --- /dev/null +++ b/Pods/Alamofire/Source/Features/CachedResponseHandler.swift @@ -0,0 +1,107 @@ +// +// CachedResponseHandler.swift +// +// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// A type that handles whether the data task should store the HTTP response in the cache. +public protocol CachedResponseHandler: Sendable { + /// Determines whether the HTTP response should be stored in the cache. + /// + /// The `completion` closure should be passed one of three possible options: + /// + /// 1. The cached response provided by the server (this is the most common use case). + /// 2. A modified version of the cached response (you may want to modify it in some way before caching). + /// 3. A `nil` value to prevent the cached response from being stored in the cache. + /// + /// - Parameters: + /// - task: The data task whose request resulted in the cached response. + /// - response: The cached response to potentially store in the cache. + /// - completion: The closure to execute containing cached response, a modified response, or `nil`. + func dataTask(_ task: URLSessionDataTask, + willCacheResponse response: CachedURLResponse, + completion: @escaping (CachedURLResponse?) -> Void) +} + +// MARK: - + +/// `ResponseCacher` is a convenience `CachedResponseHandler` making it easy to cache, not cache, or modify a cached +/// response. +public struct ResponseCacher { + /// Defines the behavior of the `ResponseCacher` type. + public enum Behavior: Sendable { + /// Stores the cached response in the cache. + case cache + /// Prevents the cached response from being stored in the cache. + case doNotCache + /// Modifies the cached response before storing it in the cache. + case modify(@Sendable (_ task: URLSessionDataTask, _ cachedResponse: CachedURLResponse) -> CachedURLResponse?) + } + + /// Returns a `ResponseCacher` with a `.cache` `Behavior`. + public static let cache = ResponseCacher(behavior: .cache) + /// Returns a `ResponseCacher` with a `.doNotCache` `Behavior`. + public static let doNotCache = ResponseCacher(behavior: .doNotCache) + + /// The `Behavior` of the `ResponseCacher`. + public let behavior: Behavior + + /// Creates a `ResponseCacher` instance from the `Behavior`. + /// + /// - Parameter behavior: The `Behavior`. + public init(behavior: Behavior) { + self.behavior = behavior + } +} + +extension ResponseCacher: CachedResponseHandler { + public func dataTask(_ task: URLSessionDataTask, + willCacheResponse response: CachedURLResponse, + completion: @escaping (CachedURLResponse?) -> Void) { + switch behavior { + case .cache: + completion(response) + case .doNotCache: + completion(nil) + case let .modify(closure): + let response = closure(task, response) + completion(response) + } + } +} + +extension CachedResponseHandler where Self == ResponseCacher { + /// Provides a `ResponseCacher` which caches the response, if allowed. Equivalent to `ResponseCacher.cache`. + public static var cache: ResponseCacher { .cache } + + /// Provides a `ResponseCacher` which does not cache the response. Equivalent to `ResponseCacher.doNotCache`. + public static var doNotCache: ResponseCacher { .doNotCache } + + /// Creates a `ResponseCacher` which modifies the proposed `CachedURLResponse` using the provided closure. + /// + /// - Parameter closure: Closure used to modify the `CachedURLResponse`. + /// - Returns: The `ResponseCacher`. + public static func modify(using closure: @escaping (@Sendable (URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)) -> ResponseCacher { + ResponseCacher(behavior: .modify(closure)) + } +} diff --git a/Pods/Alamofire/Source/Features/Combine.swift b/Pods/Alamofire/Source/Features/Combine.swift new file mode 100644 index 0000000..d48610a --- /dev/null +++ b/Pods/Alamofire/Source/Features/Combine.swift @@ -0,0 +1,652 @@ +// +// Combine.swift +// +// Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +#if !((os(iOS) && (arch(i386) || arch(arm))) || os(Windows) || os(Linux) || os(Android)) + +import Combine +import Dispatch +import Foundation + +// MARK: - DataRequest / UploadRequest + +/// A Combine `Publisher` that publishes the `DataResponse` of the provided `DataRequest`. +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +public struct DataResponsePublisher: Publisher { + public typealias Output = DataResponse + public typealias Failure = Never + + private typealias Handler = (@escaping @Sendable (_ response: DataResponse) -> Void) -> DataRequest + + private let request: DataRequest + private let responseHandler: Handler + + /// Creates an instance which will serialize responses using the provided `ResponseSerializer`. + /// + /// - Parameters: + /// - request: `DataRequest` for which to publish the response. + /// - queue: `DispatchQueue` on which the `DataResponse` value will be published. `.main` by default. + /// - serializer: `ResponseSerializer` used to produce the published `DataResponse`. + public init(_ request: DataRequest, queue: DispatchQueue, serializer: Serializer) + where Value == Serializer.SerializedObject { + self.request = request + responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } + } + + /// Creates an instance which will serialize responses using the provided `DataResponseSerializerProtocol`. + /// + /// - Parameters: + /// - request: `DataRequest` for which to publish the response. + /// - queue: `DispatchQueue` on which the `DataResponse` value will be published. `.main` by default. + /// - serializer: `DataResponseSerializerProtocol` used to produce the published `DataResponse`. + public init(_ request: DataRequest, + queue: DispatchQueue, + serializer: Serializer) + where Value == Serializer.SerializedObject { + self.request = request + responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } + } + + /// Publishes only the `Result` of the `DataResponse` value. + /// + /// - Returns: The `AnyPublisher` publishing the `Result` value. + public func result() -> AnyPublisher, Never> { + map(\.result).eraseToAnyPublisher() + } + + /// Publishes the `Result` of the `DataResponse` as a single `Value` or fail with the `AFError` instance. + /// + /// - Returns: The `AnyPublisher` publishing the stream. + public func value() -> AnyPublisher { + setFailureType(to: AFError.self).flatMap(\.result.publisher).eraseToAnyPublisher() + } + + public func receive(subscriber: S) where S: Subscriber & Sendable, DataResponsePublisher.Failure == S.Failure, DataResponsePublisher.Output == S.Input { + subscriber.receive(subscription: Inner(request: request, + responseHandler: responseHandler, + downstream: subscriber)) + } + + private final class Inner: Subscription + where Downstream.Input == Output { + typealias Failure = Downstream.Failure + + private let downstream: Protected + private let request: DataRequest + private let responseHandler: Handler + + init(request: DataRequest, responseHandler: @escaping Handler, downstream: Downstream) { + self.request = request + self.responseHandler = responseHandler + self.downstream = Protected(downstream) + } + + func request(_ demand: Subscribers.Demand) { + assert(demand > 0) + + guard let downstream = downstream.read({ $0 }) else { return } + + self.downstream.write(nil) + responseHandler { response in + _ = downstream.receive(response) + downstream.receive(completion: .finished) + }.resume() + } + + func cancel() { + request.cancel() + downstream.write(nil) + } + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +extension DataResponsePublisher where Value == Data? { + /// Creates an instance which publishes a `DataResponse` value without serialization. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public init(_ request: DataRequest, queue: DispatchQueue) { + self.request = request + responseHandler = { request.response(queue: queue, completionHandler: $0) } + } +} + +extension DataRequest { + /// Creates a `DataResponsePublisher` for this instance using the given `ResponseSerializer` and `DispatchQueue`. + /// + /// - Parameters: + /// - serializer: `ResponseSerializer` used to serialize response `Data`. + /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. + /// + /// - Returns: The `DataResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishResponse(using serializer: Serializer, on queue: DispatchQueue = .main) -> DataResponsePublisher + where Serializer.SerializedObject == T { + DataResponsePublisher(self, queue: queue, serializer: serializer) + } + + /// Creates a `DataResponsePublisher` for this instance and uses a `DataResponseSerializer` to serialize the + /// response. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. + /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` + /// by default. + /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by + /// default. + /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of + /// status code. `[.head]` by default. + /// - Returns: The `DataResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishData(queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { + publishResponse(using: DataResponseSerializer(dataPreprocessor: preprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + on: queue) + } + + /// Creates a `DataResponsePublisher` for this instance and uses a `StringResponseSerializer` to serialize the + /// response. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. + /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` + /// by default. + /// - encoding: `String.Encoding` to parse the response. `nil` by default, in which case the encoding + /// will be determined by the server response, falling back to the default HTTP character + /// set, `ISO-8859-1`. + /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by + /// default. + /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of + /// status code. `[.head]` by default. + /// + /// - Returns: The `DataResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishString(queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { + publishResponse(using: StringResponseSerializer(dataPreprocessor: preprocessor, + encoding: encoding, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + on: queue) + } + + @_disfavoredOverload + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + @available(*, deprecated, message: "Renamed publishDecodable(type:queue:preprocessor:decoder:emptyResponseCodes:emptyRequestMethods).") + public func publishDecodable(type: T.Type = T.self, + queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyResponseMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { + publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyResponseMethods), + on: queue) + } + + /// Creates a `DataResponsePublisher` for this instance and uses a `DecodableResponseSerializer` to serialize the + /// response. + /// + /// - Parameters: + /// - type: `Decodable` type to which to decode response `Data`. Inferred from the context by + /// default. + /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. + /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. + /// `PassthroughPreprocessor()` by default. + /// - decoder: `DataDecoder` instance used to decode response `Data`. `JSONDecoder()` by default. + /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by + /// default. + /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of + /// status code. `[.head]` by default. + /// + /// - Returns: The `DataResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishDecodable(type: T.Type = T.self, + queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DataResponsePublisher { + publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + on: queue) + } + + /// Creates a `DataResponsePublisher` for this instance which does not serialize the response before publishing. + /// + /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. + /// + /// - Returns: The `DataResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishUnserialized(queue: DispatchQueue = .main) -> DataResponsePublisher { + DataResponsePublisher(self, queue: queue) + } +} + +// A Combine `Publisher` that publishes a sequence of `Stream` values received by the provided `DataStreamRequest`. +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +public struct DataStreamPublisher: Publisher { + public typealias Output = DataStreamRequest.Stream + public typealias Failure = Never + + private typealias Handler = (@escaping DataStreamRequest.Handler) -> DataStreamRequest + + private let request: DataStreamRequest + private let streamHandler: Handler + + /// Creates an instance which will serialize responses using the provided `DataStreamSerializer`. + /// + /// - Parameters: + /// - request: `DataStreamRequest` for which to publish the response. + /// - queue: `DispatchQueue` on which the `Stream` values will be published. `.main` by + /// default. + /// - serializer: `DataStreamSerializer` used to produce the published `Stream` values. + public init(_ request: DataStreamRequest, queue: DispatchQueue, serializer: Serializer) + where Value == Serializer.SerializedObject { + self.request = request + streamHandler = { request.responseStream(using: serializer, on: queue, stream: $0) } + } + + /// Publishes only the `Result` of the `DataStreamRequest.Stream`'s `Event`s. + /// + /// - Returns: The `AnyPublisher` publishing the `Result` value. + public func result() -> AnyPublisher, Never> { + compactMap { stream in + switch stream.event { + case let .stream(result): + result + // If the stream has completed with an error, send the error value downstream as a `.failure`. + case let .complete(completion): + completion.error.map(Result.failure) + } + } + .eraseToAnyPublisher() + } + + /// Publishes the streamed values of the `DataStreamRequest.Stream` as a sequence of `Value` or fail with the + /// `AFError` instance. + /// + /// - Returns: The `AnyPublisher` publishing the stream. + public func value() -> AnyPublisher { + result().setFailureType(to: AFError.self).flatMap(\.publisher).eraseToAnyPublisher() + } + + public func receive(subscriber: S) where S: Subscriber & Sendable, DataStreamPublisher.Failure == S.Failure, DataStreamPublisher.Output == S.Input { + subscriber.receive(subscription: Inner(request: request, + streamHandler: streamHandler, + downstream: subscriber)) + } + + private final class Inner: Subscription + where Downstream.Input == Output { + typealias Failure = Downstream.Failure + + private let downstream: Protected + private let request: DataStreamRequest + private let streamHandler: Handler + + init(request: DataStreamRequest, streamHandler: @escaping Handler, downstream: Downstream) { + self.request = request + self.streamHandler = streamHandler + self.downstream = Protected(downstream) + } + + func request(_ demand: Subscribers.Demand) { + assert(demand > 0) + + guard let downstream = downstream.read({ $0 }) else { return } + + self.downstream.write(nil) + streamHandler { stream in + _ = downstream.receive(stream) + if case .complete = stream.event { + downstream.receive(completion: .finished) + } + }.resume() + } + + func cancel() { + request.cancel() + downstream.write(nil) + } + } +} + +extension DataStreamRequest { + /// Creates a `DataStreamPublisher` for this instance using the given `DataStreamSerializer` and `DispatchQueue`. + /// + /// - Parameters: + /// - serializer: `DataStreamSerializer` used to serialize the streamed `Data`. + /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. + /// - Returns: The `DataStreamPublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishStream(using serializer: Serializer, + on queue: DispatchQueue = .main) -> DataStreamPublisher { + DataStreamPublisher(self, queue: queue, serializer: serializer) + } + + /// Creates a `DataStreamPublisher` for this instance which uses a `PassthroughStreamSerializer` to stream `Data` + /// unserialized. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. + /// - Returns: The `DataStreamPublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishData(queue: DispatchQueue = .main) -> DataStreamPublisher { + publishStream(using: PassthroughStreamSerializer(), on: queue) + } + + /// Creates a `DataStreamPublisher` for this instance which uses a `StringStreamSerializer` to serialize stream + /// `Data` values into `String` values. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. + /// - Returns: The `DataStreamPublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishString(queue: DispatchQueue = .main) -> DataStreamPublisher { + publishStream(using: StringStreamSerializer(), on: queue) + } + + /// Creates a `DataStreamPublisher` for this instance which uses a `DecodableStreamSerializer` with the provided + /// parameters to serialize stream `Data` values into the provided type. + /// + /// - Parameters: + /// - type: `Decodable` type to which to decode stream `Data`. Inferred from the context by default. + /// - queue: `DispatchQueue` on which the `DataRequest.Stream` values will be published. `.main` by default. + /// - decoder: `DataDecoder` instance used to decode stream `Data`. `JSONDecoder()` by default. + /// - preprocessor: `DataPreprocessor` which filters incoming stream `Data` before serialization. + /// `PassthroughPreprocessor()` by default. + /// - Returns: The `DataStreamPublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishDecodable(type: T.Type = T.self, + queue: DispatchQueue = .main, + decoder: any DataDecoder = JSONDecoder(), + preprocessor: any DataPreprocessor = PassthroughPreprocessor()) -> DataStreamPublisher { + publishStream(using: DecodableStreamSerializer(decoder: decoder, + dataPreprocessor: preprocessor), + on: queue) + } +} + +/// A Combine `Publisher` that publishes the `DownloadResponse` of the provided `DownloadRequest`. +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +public struct DownloadResponsePublisher: Publisher { + public typealias Output = DownloadResponse + public typealias Failure = Never + + private typealias Handler = (@escaping @Sendable (_ response: DownloadResponse) -> Void) -> DownloadRequest + + private let request: DownloadRequest + private let responseHandler: Handler + + /// Creates an instance which will serialize responses using the provided `ResponseSerializer`. + /// + /// - Parameters: + /// - request: `DownloadRequest` for which to publish the response. + /// - queue: `DispatchQueue` on which the `DownloadResponse` value will be published. `.main` by default. + /// - serializer: `ResponseSerializer` used to produce the published `DownloadResponse`. + public init(_ request: DownloadRequest, queue: DispatchQueue, serializer: Serializer) + where Value == Serializer.SerializedObject { + self.request = request + responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } + } + + /// Creates an instance which will serialize responses using the provided `DownloadResponseSerializerProtocol` value. + /// + /// - Parameters: + /// - request: `DownloadRequest` for which to publish the response. + /// - queue: `DispatchQueue` on which the `DataResponse` value will be published. `.main` by default. + /// - serializer: `DownloadResponseSerializerProtocol` used to produce the published `DownloadResponse`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public init(_ request: DownloadRequest, + queue: DispatchQueue, + serializer: Serializer) + where Value == Serializer.SerializedObject { + self.request = request + responseHandler = { request.response(queue: queue, responseSerializer: serializer, completionHandler: $0) } + } + + /// Publishes only the `Result` of the `DownloadResponse` value. + /// + /// - Returns: The `AnyPublisher` publishing the `Result` value. + public func result() -> AnyPublisher, Never> { + map(\.result).eraseToAnyPublisher() + } + + /// Publishes the `Result` of the `DownloadResponse` as a single `Value` or fail with the `AFError` instance. + /// + /// - Returns: The `AnyPublisher` publishing the stream. + public func value() -> AnyPublisher { + setFailureType(to: AFError.self).flatMap(\.result.publisher).eraseToAnyPublisher() + } + + public func receive(subscriber: S) where S: Subscriber & Sendable, DownloadResponsePublisher.Failure == S.Failure, DownloadResponsePublisher.Output == S.Input { + subscriber.receive(subscription: Inner(request: request, + responseHandler: responseHandler, + downstream: subscriber)) + } + + private final class Inner: Subscription + where Downstream.Input == Output { + typealias Failure = Downstream.Failure + + private let downstream: Protected + private let request: DownloadRequest + private let responseHandler: Handler + + init(request: DownloadRequest, responseHandler: @escaping Handler, downstream: Downstream) { + self.request = request + self.responseHandler = responseHandler + self.downstream = Protected(downstream) + } + + func request(_ demand: Subscribers.Demand) { + assert(demand > 0) + + guard let downstream = downstream.read({ $0 }) else { return } + + self.downstream.write(nil) + responseHandler { response in + _ = downstream.receive(response) + downstream.receive(completion: .finished) + }.resume() + } + + func cancel() { + request.cancel() + downstream.write(nil) + } + } +} + +extension DownloadRequest { + /// Creates a `DownloadResponsePublisher` for this instance using the given `ResponseSerializer` and `DispatchQueue`. + /// + /// - Parameters: + /// - serializer: `ResponseSerializer` used to serialize the response `Data` from disk. + /// - queue: `DispatchQueue` on which the `DownloadResponse` will be published.`.main` by default. + /// + /// - Returns: The `DownloadResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishResponse(using serializer: Serializer, on queue: DispatchQueue = .main) -> DownloadResponsePublisher + where Serializer.SerializedObject == T { + DownloadResponsePublisher(self, queue: queue, serializer: serializer) + } + + /// Creates a `DownloadResponsePublisher` for this instance using the given `DownloadResponseSerializerProtocol` and + /// `DispatchQueue`. + /// + /// - Parameters: + /// - serializer: `DownloadResponseSerializer` used to serialize the response `Data` from disk. + /// - queue: `DispatchQueue` on which the `DownloadResponse` will be published.`.main` by default. + /// + /// - Returns: The `DownloadResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishResponse(using serializer: Serializer, on queue: DispatchQueue = .main) -> DownloadResponsePublisher + where Serializer.SerializedObject == T { + DownloadResponsePublisher(self, queue: queue, serializer: serializer) + } + + /// Creates a `DownloadResponsePublisher` for this instance and uses a `URLResponseSerializer` to serialize the + /// response. + /// + /// - Parameter queue: `DispatchQueue` on which the `DownloadResponse` will be published. `.main` by default. + /// + /// - Returns: The `DownloadResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishURL(queue: DispatchQueue = .main) -> DownloadResponsePublisher { + publishResponse(using: URLResponseSerializer(), on: queue) + } + + /// Creates a `DownloadResponsePublisher` for this instance and uses a `DataResponseSerializer` to serialize the + /// response. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the `DownloadResponse` will be published. `.main` by default. + /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` + /// by default. + /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by + /// default. + /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of + /// status code. `[.head]` by default. + /// + /// - Returns: The `DownloadResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishData(queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { + publishResponse(using: DataResponseSerializer(dataPreprocessor: preprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + on: queue) + } + + /// Creates a `DownloadResponsePublisher` for this instance and uses a `StringResponseSerializer` to serialize the + /// response. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. + /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. `PassthroughPreprocessor()` + /// by default. + /// - encoding: `String.Encoding` to parse the response. `nil` by default, in which case the encoding + /// will be determined by the server response, falling back to the default HTTP character + /// set, `ISO-8859-1`. + /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by + /// default. + /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless of + /// status code. `[.head]` by default. + /// + /// - Returns: The `DownloadResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishString(queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { + publishResponse(using: StringResponseSerializer(dataPreprocessor: preprocessor, + encoding: encoding, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + on: queue) + } + + @_disfavoredOverload + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + @available(*, deprecated, message: "Renamed publishDecodable(type:queue:preprocessor:decoder:emptyResponseCodes:emptyRequestMethods).") + public func publishDecodable(type: T.Type = T.self, + queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyResponseMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { + publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyResponseMethods), + on: queue) + } + + /// Creates a `DownloadResponsePublisher` for this instance and uses a `DecodableResponseSerializer` to serialize + /// the response. + /// + /// - Parameters: + /// - type: `Decodable` type to which to decode response `Data`. Inferred from the context by default. + /// - queue: `DispatchQueue` on which the `DataResponse` will be published. `.main` by default. + /// - preprocessor: `DataPreprocessor` which filters the `Data` before serialization. + /// `PassthroughPreprocessor()` by default. + /// - decoder: `DataDecoder` instance used to decode response `Data`. `JSONDecoder()` by default. + /// - emptyResponseCodes: `Set` of HTTP status codes for which empty responses are allowed. `[204, 205]` by + /// default. + /// - emptyRequestMethods: `Set` of `HTTPMethod`s for which empty responses are allowed, regardless + /// of status code. `[.head]` by default. + /// + /// - Returns: The `DownloadResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishDecodable(type: T.Type = T.self, + queue: DispatchQueue = .main, + preprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DownloadResponsePublisher { + publishResponse(using: DecodableResponseSerializer(dataPreprocessor: preprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + on: queue) + } +} + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) +extension DownloadResponsePublisher where Value == URL? { + /// Creates an instance which publishes a `DownloadResponse` value without serialization. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public init(_ request: DownloadRequest, queue: DispatchQueue) { + self.request = request + responseHandler = { request.response(queue: queue, completionHandler: $0) } + } +} + +extension DownloadRequest { + /// Creates a `DownloadResponsePublisher` for this instance which does not serialize the response before publishing. + /// + /// - Parameter queue: `DispatchQueue` on which the `DownloadResponse` will be published. `.main` by default. + /// + /// - Returns: The `DownloadResponsePublisher`. + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) + public func publishUnserialized(on queue: DispatchQueue = .main) -> DownloadResponsePublisher { + DownloadResponsePublisher(self, queue: queue) + } +} + +#endif diff --git a/Pods/Alamofire/Source/Features/Concurrency.swift b/Pods/Alamofire/Source/Features/Concurrency.swift new file mode 100644 index 0000000..262fdd1 --- /dev/null +++ b/Pods/Alamofire/Source/Features/Concurrency.swift @@ -0,0 +1,962 @@ +// +// Concurrency.swift +// +// Copyright (c) 2021 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +#if canImport(_Concurrency) + +import Foundation + +// MARK: - Request Event Streams + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension Request { + /// Creates a `StreamOf` for the instance's upload progress. + /// + /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `StreamOf`. + public func uploadProgress(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { + stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + uploadProgress(queue: underlyingQueue) { progress in + continuation.yield(progress) + } + } + } + + /// Creates a `StreamOf` for the instance's download progress. + /// + /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `StreamOf`. + public func downloadProgress(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { + stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + downloadProgress(queue: underlyingQueue) { progress in + continuation.yield(progress) + } + } + } + + /// Creates a `StreamOf` for the `URLRequest`s produced for the instance. + /// + /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `StreamOf`. + public func urlRequests(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { + stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + onURLRequestCreation(on: underlyingQueue) { request in + continuation.yield(request) + } + } + } + + /// Creates a `StreamOf` for the `URLSessionTask`s produced for the instance. + /// + /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `StreamOf`. + public func urlSessionTasks(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { + stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + onURLSessionTaskCreation(on: underlyingQueue) { task in + continuation.yield(task) + } + } + } + + /// Creates a `StreamOf` for the cURL descriptions produced for the instance. + /// + /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `StreamOf`. + public func cURLDescriptions(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { + stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + cURLDescription(on: underlyingQueue) { description in + continuation.yield(description) + } + } + } + + fileprivate func stream(of type: T.Type = T.self, + bufferingPolicy: StreamOf.BufferingPolicy = .unbounded, + yielder: @escaping (StreamOf.Continuation) -> Void) -> StreamOf { + StreamOf(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + yielder(continuation) + // Must come after serializers run in order to catch retry progress. + onFinish { + continuation.finish() + } + } + } +} + +// MARK: - DataTask + +/// Value used to `await` a `DataResponse` and associated values. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +public struct DataTask: Sendable where Value: Sendable { + /// `DataResponse` produced by the `DataRequest` and its response handler. + public var response: DataResponse { + get async { + if shouldAutomaticallyCancel { + await withTaskCancellationHandler { + await task.value + } onCancel: { + cancel() + } + } else { + await task.value + } + } + } + + /// `Result` of any response serialization performed for the `response`. + public var result: Result { + get async { await response.result } + } + + /// `Value` returned by the `response`. + public var value: Value { + get async throws { + try await result.get() + } + } + + private let request: DataRequest + private let task: Task, Never> + private let shouldAutomaticallyCancel: Bool + + fileprivate init(request: DataRequest, task: Task, Never>, shouldAutomaticallyCancel: Bool) { + self.request = request + self.task = task + self.shouldAutomaticallyCancel = shouldAutomaticallyCancel + } + + /// Cancel the underlying `DataRequest` and `Task`. + public func cancel() { + task.cancel() + } + + /// Resume the underlying `DataRequest`. + public func resume() { + request.resume() + } + + /// Suspend the underlying `DataRequest`. + public func suspend() { + request.suspend() + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension DataRequest { + /// Creates a `StreamOf` for the instance's responses. + /// + /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `StreamOf`. + public func httpResponses(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { + stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + onHTTPResponse(on: underlyingQueue) { response in + continuation.yield(response) + } + } + } + + /// Sets an async closure returning a `Request.ResponseDisposition`, called whenever the `DataRequest` produces an + /// `HTTPURLResponse`. + /// + /// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries). + /// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams, + /// where responses after the first will contain the part headers. + /// + /// - Parameters: + /// - handler: Async closure executed when a new `HTTPURLResponse` is received and returning a + /// `ResponseDisposition` value. This value determines whether to continue the request or cancel it as + /// if `cancel()` had been called on the instance. Note, this closure is called on an arbitrary thread, + /// so any synchronous calls in it will execute in that context. + /// + /// - Returns: The instance. + @_disfavoredOverload + @discardableResult + public func onHTTPResponse( + perform handler: @escaping @Sendable (_ response: HTTPURLResponse) async -> ResponseDisposition + ) -> Self { + onHTTPResponse(on: underlyingQueue) { response, completionHandler in + Task { + let disposition = await handler(response) + completionHandler(disposition) + } + } + + return self + } + + /// Sets an async closure called whenever the `DataRequest` produces an `HTTPURLResponse`. + /// + /// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries). + /// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams, + /// where responses after the first will contain the part headers. + /// + /// - Parameters: + /// - handler: Async closure executed when a new `HTTPURLResponse` is received. Note, this closure is called on an + /// arbitrary thread, so any synchronous calls in it will execute in that context. + /// + /// - Returns: The instance. + @discardableResult + public func onHTTPResponse(perform handler: @escaping @Sendable (_ response: HTTPURLResponse) async -> Void) -> Self { + onHTTPResponse { response in + await handler(response) + return .allow + } + + return self + } + + /// Creates a `DataTask` to `await` a `Data` value. + /// + /// - Parameters: + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DataTask`'s async + /// properties. `true` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion. + /// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// + /// - Returns: The `DataTask`. + public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + dataPreprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DataTask { + serializingResponse(using: DataResponseSerializer(dataPreprocessor: dataPreprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + automaticallyCancelling: shouldAutomaticallyCancel) + } + + /// Creates a `DataTask` to `await` serialization of a `Decodable` value. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from response data. + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DataTask`'s async + /// properties. `true` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. + /// `PassthroughPreprocessor()` by default. + /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// + /// - Returns: The `DataTask`. + public func serializingDecodable(_ type: Value.Type = Value.self, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + dataPreprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DataTask { + serializingResponse(using: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + automaticallyCancelling: shouldAutomaticallyCancel) + } + + /// Creates a `DataTask` to `await` serialization of a `String` value. + /// + /// - Parameters: + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DataTask`'s async + /// properties. `true` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. + /// `PassthroughPreprocessor()` by default. + /// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case + /// the encoding will be determined from the server response, falling back to the + /// default HTTP character set, `ISO-8859-1`. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// + /// - Returns: The `DataTask`. + public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + dataPreprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DataTask { + serializingResponse(using: StringResponseSerializer(dataPreprocessor: dataPreprocessor, + encoding: encoding, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + automaticallyCancelling: shouldAutomaticallyCancel) + } + + /// Creates a `DataTask` to `await` serialization using the provided `ResponseSerializer` instance. + /// + /// - Parameters: + /// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data. + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DataTask`'s async + /// properties. `true` by default. + /// + /// - Returns: The `DataTask`. + public func serializingResponse(using serializer: Serializer, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true) + -> DataTask { + dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in + response(queue: underlyingQueue, + responseSerializer: serializer, + completionHandler: $0) + } + } + + /// Creates a `DataTask` to `await` serialization using the provided `DataResponseSerializerProtocol` instance. + /// + /// - Parameters: + /// - serializer: `DataResponseSerializerProtocol` responsible for serializing the request, + /// response, and data. + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DataTask`'s async + /// properties. `true` by default. + /// + /// - Returns: The `DataTask`. + public func serializingResponse(using serializer: Serializer, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true) + -> DataTask { + dataTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in + response(queue: underlyingQueue, + responseSerializer: serializer, + completionHandler: $0) + } + } + + private func dataTask(automaticallyCancelling shouldAutomaticallyCancel: Bool, + forResponse onResponse: @Sendable @escaping (@escaping @Sendable (DataResponse) -> Void) -> Void) + -> DataTask { + let task = Task { + await withTaskCancellationHandler { + await withCheckedContinuation { continuation in + onResponse { + continuation.resume(returning: $0) + } + } + } onCancel: { + self.cancel() + } + } + + return DataTask(request: self, task: task, shouldAutomaticallyCancel: shouldAutomaticallyCancel) + } +} + +// MARK: - DownloadTask + +/// Value used to `await` a `DownloadResponse` and associated values. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +public struct DownloadTask: Sendable where Value: Sendable { + /// `DownloadResponse` produced by the `DownloadRequest` and its response handler. + public var response: DownloadResponse { + get async { + if shouldAutomaticallyCancel { + await withTaskCancellationHandler { + await task.value + } onCancel: { + cancel() + } + } else { + await task.value + } + } + } + + /// `Result` of any response serialization performed for the `response`. + public var result: Result { + get async { await response.result } + } + + /// `Value` returned by the `response`. + public var value: Value { + get async throws { + try await result.get() + } + } + + private let task: Task, Never> + private let request: DownloadRequest + private let shouldAutomaticallyCancel: Bool + + fileprivate init(request: DownloadRequest, task: Task, Never>, shouldAutomaticallyCancel: Bool) { + self.request = request + self.task = task + self.shouldAutomaticallyCancel = shouldAutomaticallyCancel + } + + /// Cancel the underlying `DownloadRequest` and `Task`. + public func cancel() { + task.cancel() + } + + /// Resume the underlying `DownloadRequest`. + public func resume() { + request.resume() + } + + /// Suspend the underlying `DownloadRequest`. + public func suspend() { + request.suspend() + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension DownloadRequest { + /// Creates a `DownloadTask` to `await` a `Data` value. + /// + /// - Parameters: + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async + /// properties. `true` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before completion. + /// - emptyResponseCodes: HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// + /// - Returns: The `DownloadTask`. + public func serializingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + dataPreprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask { + serializingDownload(using: DataResponseSerializer(dataPreprocessor: dataPreprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + automaticallyCancelling: shouldAutomaticallyCancel) + } + + /// Creates a `DownloadTask` to `await` serialization of a `Decodable` value. + /// + /// - Note: This serializer reads the entire response into memory before parsing. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from response data. + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async + /// properties. `true` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the serializer. + /// `PassthroughPreprocessor()` by default. + /// - decoder: `DataDecoder` to use to decode the response. `JSONDecoder()` by default. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// + /// - Returns: The `DownloadTask`. + public func serializingDecodable(_ type: Value.Type = Value.self, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + dataPreprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask { + serializingDownload(using: DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + automaticallyCancelling: shouldAutomaticallyCancel) + } + + /// Creates a `DownloadTask` to `await` serialization of the downloaded file's `URL` on disk. + /// + /// - Parameters: + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async + /// properties. `true` by default. + /// + /// - Returns: The `DownloadTask`. + public func serializingDownloadedFileURL(automaticallyCancelling shouldAutomaticallyCancel: Bool = true) -> DownloadTask { + serializingDownload(using: URLResponseSerializer(), + automaticallyCancelling: shouldAutomaticallyCancel) + } + + /// Creates a `DownloadTask` to `await` serialization of a `String` value. + /// + /// - Parameters: + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async + /// properties. `true` by default. + /// - dataPreprocessor: `DataPreprocessor` which processes the received `Data` before calling the + /// serializer. `PassthroughPreprocessor()` by default. + /// - encoding: `String.Encoding` to use during serialization. Defaults to `nil`, in which case + /// the encoding will be determined from the server response, falling back to the + /// default HTTP character set, `ISO-8859-1`. + /// - emptyResponseCodes: HTTP status codes for which empty responses are always valid. `[204, 205]` by default. + /// - emptyRequestMethods: `HTTPMethod`s for which empty responses are always valid. `[.head]` by default. + /// + /// - Returns: The `DownloadTask`. + public func serializingString(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + dataPreprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> DownloadTask { + serializingDownload(using: StringResponseSerializer(dataPreprocessor: dataPreprocessor, + encoding: encoding, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods), + automaticallyCancelling: shouldAutomaticallyCancel) + } + + /// Creates a `DownloadTask` to `await` serialization using the provided `ResponseSerializer` instance. + /// + /// - Parameters: + /// - serializer: `ResponseSerializer` responsible for serializing the request, response, and data. + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async + /// properties. `true` by default. + /// + /// - Returns: The `DownloadTask`. + public func serializingDownload(using serializer: Serializer, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true) + -> DownloadTask { + downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in + response(queue: underlyingQueue, + responseSerializer: serializer, + completionHandler: $0) + } + } + + /// Creates a `DownloadTask` to `await` serialization using the provided `DownloadResponseSerializerProtocol` + /// instance. + /// + /// - Parameters: + /// - serializer: `DownloadResponseSerializerProtocol` responsible for serializing the request, + /// response, and data. + /// - shouldAutomaticallyCancel: `Bool` determining whether or not the request should be cancelled when the + /// enclosing async context is cancelled. Only applies to `DownloadTask`'s async + /// properties. `true` by default. + /// + /// - Returns: The `DownloadTask`. + public func serializingDownload(using serializer: Serializer, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true) + -> DownloadTask { + downloadTask(automaticallyCancelling: shouldAutomaticallyCancel) { [self] in + response(queue: underlyingQueue, + responseSerializer: serializer, + completionHandler: $0) + } + } + + private func downloadTask(automaticallyCancelling shouldAutomaticallyCancel: Bool, + forResponse onResponse: @Sendable @escaping (@escaping @Sendable (DownloadResponse) -> Void) -> Void) + -> DownloadTask { + let task = Task { + await withTaskCancellationHandler { + await withCheckedContinuation { continuation in + onResponse { + continuation.resume(returning: $0) + } + } + } onCancel: { + self.cancel() + } + } + + return DownloadTask(request: self, task: task, shouldAutomaticallyCancel: shouldAutomaticallyCancel) + } +} + +// MARK: - DataStreamTask + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +public struct DataStreamTask: Sendable { + // Type of created streams. + public typealias Stream = StreamOf> + + private let request: DataStreamRequest + + fileprivate init(request: DataStreamRequest) { + self.request = request + } + + /// Creates a `Stream` of `Data` values from the underlying `DataStreamRequest`. + /// + /// - Parameters: + /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled + /// which observation of the stream stops. `true` by default. + /// - bufferingPolicy: ` BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `Stream`. + public func streamingData(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, bufferingPolicy: Stream.BufferingPolicy = .unbounded) -> Stream { + createStream(automaticallyCancelling: shouldAutomaticallyCancel, bufferingPolicy: bufferingPolicy) { onStream in + request.responseStream(on: .streamCompletionQueue(forRequestID: request.id), stream: onStream) + } + } + + /// Creates a `Stream` of `UTF-8` `String`s from the underlying `DataStreamRequest`. + /// + /// - Parameters: + /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled + /// which observation of the stream stops. `true` by default. + /// - bufferingPolicy: ` BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// - Returns: + public func streamingStrings(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, bufferingPolicy: Stream.BufferingPolicy = .unbounded) -> Stream { + createStream(automaticallyCancelling: shouldAutomaticallyCancel, bufferingPolicy: bufferingPolicy) { onStream in + request.responseStreamString(on: .streamCompletionQueue(forRequestID: request.id), stream: onStream) + } + } + + /// Creates a `Stream` of `Decodable` values from the underlying `DataStreamRequest`. + /// + /// - Parameters: + /// - type: `Decodable` type to be serialized from stream payloads. + /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled + /// which observation of the stream stops. `true` by default. + /// - bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `Stream`. + public func streamingDecodables(_ type: T.Type = T.self, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + bufferingPolicy: Stream.BufferingPolicy = .unbounded) + -> Stream where T: Decodable & Sendable { + streamingResponses(serializedUsing: DecodableStreamSerializer(), + automaticallyCancelling: shouldAutomaticallyCancel, + bufferingPolicy: bufferingPolicy) + } + + /// Creates a `Stream` of values using the provided `DataStreamSerializer` from the underlying `DataStreamRequest`. + /// + /// - Parameters: + /// - serializer: `DataStreamSerializer` to use to serialize incoming `Data`. + /// - shouldAutomaticallyCancel: `Bool` indicating whether the underlying `DataStreamRequest` should be canceled + /// which observation of the stream stops. `true` by default. + /// - bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `Stream`. + public func streamingResponses(serializedUsing serializer: Serializer, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + bufferingPolicy: Stream.BufferingPolicy = .unbounded) + -> Stream { + createStream(automaticallyCancelling: shouldAutomaticallyCancel, bufferingPolicy: bufferingPolicy) { onStream in + request.responseStream(using: serializer, + on: .streamCompletionQueue(forRequestID: request.id), + stream: onStream) + } + } + + private func createStream(automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + bufferingPolicy: Stream.BufferingPolicy = .unbounded, + forResponse onResponse: @Sendable @escaping (@escaping @Sendable (DataStreamRequest.Stream) -> Void) -> Void) + -> Stream { + StreamOf(bufferingPolicy: bufferingPolicy) { + guard shouldAutomaticallyCancel, + request.isInitialized || request.isResumed || request.isSuspended else { return } + + cancel() + } builder: { continuation in + onResponse { stream in + continuation.yield(stream) + if case .complete = stream.event { + continuation.finish() + } + } + } + } + + /// Cancel the underlying `DataStreamRequest`. + public func cancel() { + request.cancel() + } + + /// Resume the underlying `DataStreamRequest`. + public func resume() { + request.resume() + } + + /// Suspend the underlying `DataStreamRequest`. + public func suspend() { + request.suspend() + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension DataStreamRequest { + /// Creates a `StreamOf` for the instance's responses. + /// + /// - Parameter bufferingPolicy: `BufferingPolicy` that determines the stream's buffering behavior.`.unbounded` by default. + /// + /// - Returns: The `StreamOf`. + public func httpResponses(bufferingPolicy: StreamOf.BufferingPolicy = .unbounded) -> StreamOf { + stream(bufferingPolicy: bufferingPolicy) { [unowned self] continuation in + onHTTPResponse(on: underlyingQueue) { response in + continuation.yield(response) + } + } + } + + /// Sets an async closure returning a `Request.ResponseDisposition`, called whenever the `DataStreamRequest` + /// produces an `HTTPURLResponse`. + /// + /// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries). + /// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams, + /// where responses after the first will contain the part headers. + /// + /// - Parameters: + /// - handler: Async closure executed when a new `HTTPURLResponse` is received and returning a + /// `ResponseDisposition` value. This value determines whether to continue the request or cancel it as + /// if `cancel()` had been called on the instance. Note, this closure is called on an arbitrary thread, + /// so any synchronous calls in it will execute in that context. + /// + /// - Returns: The instance. + @_disfavoredOverload + @discardableResult + public func onHTTPResponse(perform handler: @escaping @Sendable (HTTPURLResponse) async -> ResponseDisposition) -> Self { + onHTTPResponse(on: underlyingQueue) { response, completionHandler in + Task { + let disposition = await handler(response) + completionHandler(disposition) + } + } + + return self + } + + /// Sets an async closure called whenever the `DataStreamRequest` produces an `HTTPURLResponse`. + /// + /// - Note: Most requests will only produce a single response for each outgoing attempt (initial + retries). + /// However, some types of response may trigger multiple `HTTPURLResponse`s, such as multipart streams, + /// where responses after the first will contain the part headers. + /// + /// - Parameters: + /// - handler: Async closure executed when a new `HTTPURLResponse` is received. Note, this closure is called on an + /// arbitrary thread, so any synchronous calls in it will execute in that context. + /// + /// - Returns: The instance. + @discardableResult + public func onHTTPResponse(perform handler: @escaping @Sendable (HTTPURLResponse) async -> Void) -> Self { + onHTTPResponse { response in + await handler(response) + return .allow + } + + return self + } + + /// Creates a `DataStreamTask` used to `await` streams of serialized values. + /// + /// - Returns: The `DataStreamTask`. + public func streamTask() -> DataStreamTask { + DataStreamTask(request: self) + } +} + +#if canImport(Darwin) && !canImport(FoundationNetworking) // Only Apple platforms support URLSessionWebSocketTask. +// - MARK: WebSocketTask + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +@_spi(WebSocket) public struct WebSocketTask: Sendable { + private let request: WebSocketRequest + + fileprivate init(request: WebSocketRequest) { + self.request = request + } + + public typealias EventStreamOf = StreamOf> + + public func streamingMessageEvents( + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + bufferingPolicy: EventStreamOf.BufferingPolicy = .unbounded + ) -> EventStreamOf { + createStream(automaticallyCancelling: shouldAutomaticallyCancel, + bufferingPolicy: bufferingPolicy, + transform: { $0 }) { onEvent in + request.streamMessageEvents(on: .streamCompletionQueue(forRequestID: request.id), handler: onEvent) + } + } + + public func streamingMessages( + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + bufferingPolicy: StreamOf.BufferingPolicy = .unbounded + ) -> StreamOf { + createStream(automaticallyCancelling: shouldAutomaticallyCancel, + bufferingPolicy: bufferingPolicy, + transform: { $0.message }) { onEvent in + request.streamMessageEvents(on: .streamCompletionQueue(forRequestID: request.id), handler: onEvent) + } + } + + public func streamingDecodableEvents( + _ type: Value.Type = Value.self, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + using decoder: any DataDecoder = JSONDecoder(), + bufferingPolicy: EventStreamOf.BufferingPolicy = .unbounded + ) -> EventStreamOf { + createStream(automaticallyCancelling: shouldAutomaticallyCancel, + bufferingPolicy: bufferingPolicy, + transform: { $0 }) { onEvent in + request.streamDecodableEvents(Value.self, + on: .streamCompletionQueue(forRequestID: request.id), + using: decoder, + handler: onEvent) + } + } + + public func streamingDecodable( + _ type: Value.Type = Value.self, + automaticallyCancelling shouldAutomaticallyCancel: Bool = true, + using decoder: any DataDecoder = JSONDecoder(), + bufferingPolicy: StreamOf.BufferingPolicy = .unbounded + ) -> StreamOf { + createStream(automaticallyCancelling: shouldAutomaticallyCancel, + bufferingPolicy: bufferingPolicy, + transform: { $0.message }) { onEvent in + request.streamDecodableEvents(Value.self, + on: .streamCompletionQueue(forRequestID: request.id), + using: decoder, + handler: onEvent) + } + } + + private func createStream( + automaticallyCancelling shouldAutomaticallyCancel: Bool, + bufferingPolicy: StreamOf.BufferingPolicy, + transform: @escaping @Sendable (WebSocketRequest.Event) -> Value?, + forResponse onResponse: @Sendable @escaping (@escaping @Sendable (WebSocketRequest.Event) -> Void) -> Void + ) -> StreamOf { + StreamOf(bufferingPolicy: bufferingPolicy) { + guard shouldAutomaticallyCancel, + request.isInitialized || request.isResumed || request.isSuspended else { return } + + cancel() + } builder: { continuation in + onResponse { event in + if let value = transform(event) { + continuation.yield(value) + } + + if case .completed = event.kind { + continuation.finish() + } + } + } + } + + /// Send a `URLSessionWebSocketTask.Message`. + /// + /// - Parameter message: The `Message`. + /// + public func send(_ message: URLSessionWebSocketTask.Message) async throws { + try await withCheckedThrowingContinuation { continuation in + request.send(message, queue: .streamCompletionQueue(forRequestID: request.id)) { result in + continuation.resume(with: result) + } + } + } + + /// Close the underlying `WebSocketRequest`. + public func close(sending closeCode: URLSessionWebSocketTask.CloseCode, reason: Data? = nil) { + request.close(sending: closeCode, reason: reason) + } + + /// Cancel the underlying `WebSocketRequest`. + /// + /// Cancellation will produce an `AFError.explicitlyCancelled` instance. + public func cancel() { + request.cancel() + } + + /// Resume the underlying `WebSocketRequest`. + public func resume() { + request.resume() + } + + /// Suspend the underlying `WebSocketRequest`. + public func suspend() { + request.suspend() + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension WebSocketRequest { + public func webSocketTask() -> WebSocketTask { + WebSocketTask(request: self) + } +} +#endif + +extension DispatchQueue { + fileprivate static let singleEventQueue = DispatchQueue(label: "org.alamofire.concurrencySingleEventQueue", + attributes: .concurrent) + + fileprivate static func streamCompletionQueue(forRequestID id: UUID) -> DispatchQueue { + DispatchQueue(label: "org.alamofire.concurrencyStreamCompletionQueue-\(id)", target: .singleEventQueue) + } +} + +/// An asynchronous sequence generated from an underlying `AsyncStream`. Only produced by Alamofire. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +public struct StreamOf: AsyncSequence { + public typealias AsyncIterator = Iterator + public typealias BufferingPolicy = AsyncStream.Continuation.BufferingPolicy + fileprivate typealias Continuation = AsyncStream.Continuation + + private let bufferingPolicy: BufferingPolicy + private let onTermination: (() -> Void)? + private let builder: (Continuation) -> Void + + fileprivate init(bufferingPolicy: BufferingPolicy = .unbounded, + onTermination: (() -> Void)? = nil, + builder: @escaping (Continuation) -> Void) { + self.bufferingPolicy = bufferingPolicy + self.onTermination = onTermination + self.builder = builder + } + + public func makeAsyncIterator() -> Iterator { + var continuation: AsyncStream.Continuation? + let stream = AsyncStream(bufferingPolicy: bufferingPolicy) { innerContinuation in + continuation = innerContinuation + builder(innerContinuation) + } + + return Iterator(iterator: stream.makeAsyncIterator()) { + continuation?.finish() + onTermination?() + } + } + + public struct Iterator: AsyncIteratorProtocol { + private final class Token { + private let onDeinit: () -> Void + + init(onDeinit: @escaping () -> Void) { + self.onDeinit = onDeinit + } + + deinit { + onDeinit() + } + } + + private var iterator: AsyncStream.AsyncIterator + private let token: Token + + init(iterator: AsyncStream.AsyncIterator, onCancellation: @escaping () -> Void) { + self.iterator = iterator + token = Token(onDeinit: onCancellation) + } + + public mutating func next() async -> Element? { + await iterator.next() + } + } +} + +#endif diff --git a/Pods/Alamofire/Source/Features/EventMonitor.swift b/Pods/Alamofire/Source/Features/EventMonitor.swift new file mode 100644 index 0000000..875aeeb --- /dev/null +++ b/Pods/Alamofire/Source/Features/EventMonitor.swift @@ -0,0 +1,909 @@ +// +// EventMonitor.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Protocol outlining the lifetime events inside Alamofire. It includes both events received from the various +/// `URLSession` delegate protocols as well as various events from the lifetime of `Request` and its subclasses. +public protocol EventMonitor: Sendable { + /// The `DispatchQueue` onto which Alamofire's root `CompositeEventMonitor` will dispatch events. `.main` by default. + var queue: DispatchQueue { get } + + // MARK: - URLSession Events + + // MARK: URLSessionDelegate Events + + /// Event called during `URLSessionDelegate`'s `urlSession(_:didBecomeInvalidWithError:)` method. + func urlSession(_ session: URLSession, didBecomeInvalidWithError error: (any Error)?) + + // MARK: URLSessionTaskDelegate Events + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didReceive:completionHandler:)` method. + func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` method. + func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:needNewBodyStream:)` method. + func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` method. + func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didFinishCollecting:)` method. + func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:task:didCompleteWithError:)` method. + func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) + + /// Event called during `URLSessionTaskDelegate`'s `urlSession(_:taskIsWaitingForConnectivity:)` method. + func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) + + // MARK: URLSessionDataDelegate Events + + /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:completionHandler:)` method. + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) + + /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:didReceive:)` method. + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) + + /// Event called during `URLSessionDataDelegate`'s `urlSession(_:dataTask:willCacheResponse:completionHandler:)` method. + func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) + + // MARK: URLSessionDownloadDelegate Events + + /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)` method. + func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) + + /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` method. + func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) + + /// Event called during `URLSessionDownloadDelegate`'s `urlSession(_:downloadTask:didFinishDownloadingTo:)` method. + func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) + + // MARK: - Request Events + + /// Event called when a `URLRequest` is first created for a `Request`. If a `RequestAdapter` is active, the + /// `URLRequest` will be adapted before being issued. + func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) + + /// Event called when the attempt to create a `URLRequest` from a `Request`'s original `URLRequestConvertible` value fails. + func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) + + /// Event called when a `RequestAdapter` adapts the `Request`'s initial `URLRequest`. + func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) + + /// Event called when a `RequestAdapter` fails to adapt the `Request`'s initial `URLRequest`. + func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) + + /// Event called when a final `URLRequest` is created for a `Request`. + func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) + + /// Event called when a `URLSessionTask` subclass instance is created for a `Request`. + func request(_ request: Request, didCreateTask task: URLSessionTask) + + /// Event called when a `Request` receives a `URLSessionTaskMetrics` value. + func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) + + /// Event called when a `Request` fails due to an error created by Alamofire. e.g. When certificate pinning fails. + func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) + + /// Event called when a `Request`'s task completes, possibly with an error. A `Request` may receive this event + /// multiple times if it is retried. + func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) + + /// Event called when a `Request` is about to be retried. + func requestIsRetrying(_ request: Request) + + /// Event called when a `Request` finishes and response serializers are being called. + func requestDidFinish(_ request: Request) + + /// Event called when a `Request` receives a `resume` call. + func requestDidResume(_ request: Request) + + /// Event called when a `Request`'s associated `URLSessionTask` is resumed. + func request(_ request: Request, didResumeTask task: URLSessionTask) + + /// Event called when a `Request` receives a `suspend` call. + func requestDidSuspend(_ request: Request) + + /// Event called when a `Request`'s associated `URLSessionTask` is suspended. + func request(_ request: Request, didSuspendTask task: URLSessionTask) + + /// Event called when a `Request` receives a `cancel` call. + func requestDidCancel(_ request: Request) + + /// Event called when a `Request`'s associated `URLSessionTask` is cancelled. + func request(_ request: Request, didCancelTask task: URLSessionTask) + + // MARK: DataRequest Events + + /// Event called when a `DataRequest` calls a `Validation`. + func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) + + /// Event called when a `DataRequest` creates a `DataResponse` value without calling a `ResponseSerializer`. + func request(_ request: DataRequest, didParseResponse response: DataResponse) + + /// Event called when a `DataRequest` calls a `ResponseSerializer` and creates a generic `DataResponse`. + func request(_ request: DataRequest, didParseResponse response: DataResponse) + + // MARK: DataStreamRequest Events + + /// Event called when a `DataStreamRequest` calls a `Validation` closure. + /// + /// - Parameters: + /// - request: `DataStreamRequest` which is calling the `Validation`. + /// - urlRequest: `URLRequest` of the request being validated. + /// - response: `HTTPURLResponse` of the request being validated. + /// - result: Produced `ValidationResult`. + func request(_ request: DataStreamRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + withResult result: Request.ValidationResult) + + /// Event called when a `DataStreamSerializer` produces a value from streamed `Data`. + /// + /// - Parameters: + /// - request: `DataStreamRequest` for which the value was serialized. + /// - result: `Result` of the serialization attempt. + func request(_ request: DataStreamRequest, didParseStream result: Result) + + // MARK: UploadRequest Events + + /// Event called when an `UploadRequest` creates its `Uploadable` value, indicating the type of upload it represents. + func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) + + /// Event called when an `UploadRequest` failed to create its `Uploadable` value due to an error. + func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) + + /// Event called when an `UploadRequest` provides the `InputStream` from its `Uploadable` value. This only occurs if + /// the `InputStream` does not wrap a `Data` value or file `URL`. + func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) + + // MARK: DownloadRequest Events + + /// Event called when a `DownloadRequest`'s `URLSessionDownloadTask` finishes and the temporary file has been moved. + func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) + + /// Event called when a `DownloadRequest`'s `Destination` closure is called and creates the destination URL the + /// downloaded file will be moved to. + func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) + + /// Event called when a `DownloadRequest` calls a `Validation`. + func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) + + /// Event called when a `DownloadRequest` creates a `DownloadResponse` without calling a `ResponseSerializer`. + func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) + + /// Event called when a `DownloadRequest` calls a `DownloadResponseSerializer` and creates a generic `DownloadResponse` + func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) +} + +extension EventMonitor { + /// The default queue on which `CompositeEventMonitor`s will call the `EventMonitor` methods. `.main` by default. + public var queue: DispatchQueue { .main } + + // MARK: Default Implementations + + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: (any Error)?) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) {} + public func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) {} + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didFinishCollecting metrics: URLSessionTaskMetrics) {} + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) {} + public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {} + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) {} + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {} + public func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse) {} + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) {} + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) {} + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didFinishDownloadingTo location: URL) {} + public func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) {} + public func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) {} + public func request(_ request: Request, + didAdaptInitialRequest initialRequest: URLRequest, + to adaptedRequest: URLRequest) {} + public func request(_ request: Request, + didFailToAdaptURLRequest initialRequest: URLRequest, + withError error: AFError) {} + public func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) {} + public func request(_ request: Request, didCreateTask task: URLSessionTask) {} + public func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) {} + public func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) {} + public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) {} + public func requestIsRetrying(_ request: Request) {} + public func requestDidFinish(_ request: Request) {} + public func requestDidResume(_ request: Request) {} + public func request(_ request: Request, didResumeTask task: URLSessionTask) {} + public func requestDidSuspend(_ request: Request) {} + public func request(_ request: Request, didSuspendTask task: URLSessionTask) {} + public func requestDidCancel(_ request: Request) {} + public func request(_ request: Request, didCancelTask task: URLSessionTask) {} + public func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) {} + public func request(_ request: DataRequest, didParseResponse response: DataResponse) {} + public func request(_ request: DataRequest, didParseResponse response: DataResponse) {} + public func request(_ request: DataStreamRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + withResult result: Request.ValidationResult) {} + public func request(_ request: DataStreamRequest, didParseStream result: Result) {} + public func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) {} + public func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) {} + public func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) {} + public func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) {} + public func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) {} + public func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) {} + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) {} + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) {} +} + +/// An `EventMonitor` which can contain multiple `EventMonitor`s and calls their methods on their queues. +public final class CompositeEventMonitor: EventMonitor { + public let queue = DispatchQueue(label: "org.alamofire.compositeEventMonitor") + + let monitors: Protected<[any EventMonitor]> + + init(monitors: [any EventMonitor]) { + self.monitors = Protected(monitors) + } + + func performEvent(_ event: @escaping @Sendable (any EventMonitor) -> Void) { + queue.async { + self.monitors.read { monitors in + for monitor in monitors { + monitor.queue.async { event(monitor) } + } + } + } + } + + public func urlSession(_ session: URLSession, didBecomeInvalidWithError error: (any Error)?) { + performEvent { $0.urlSession(session, didBecomeInvalidWithError: error) } + } + + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didReceive challenge: URLAuthenticationChallenge) { + performEvent { $0.urlSession(session, task: task, didReceive: challenge) } + } + + public func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) { + performEvent { + $0.urlSession(session, + task: task, + didSendBodyData: bytesSent, + totalBytesSent: totalBytesSent, + totalBytesExpectedToSend: totalBytesExpectedToSend) + } + } + + public func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) { + performEvent { + $0.urlSession(session, taskNeedsNewBodyStream: task) + } + } + + public func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) { + performEvent { + $0.urlSession(session, + task: task, + willPerformHTTPRedirection: response, + newRequest: request) + } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + performEvent { $0.urlSession(session, task: task, didFinishCollecting: metrics) } + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { + performEvent { $0.urlSession(session, task: task, didCompleteWithError: error) } + } + + @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { + performEvent { $0.urlSession(session, taskIsWaitingForConnectivity: task) } + } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) { + performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: response) } + } + + public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + performEvent { $0.urlSession(session, dataTask: dataTask, didReceive: data) } + } + + public func urlSession(_ session: URLSession, + dataTask: URLSessionDataTask, + willCacheResponse proposedResponse: CachedURLResponse) { + performEvent { $0.urlSession(session, dataTask: dataTask, willCacheResponse: proposedResponse) } + } + + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) { + performEvent { + $0.urlSession(session, + downloadTask: downloadTask, + didResumeAtOffset: fileOffset, + expectedTotalBytes: expectedTotalBytes) + } + } + + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) { + performEvent { + $0.urlSession(session, + downloadTask: downloadTask, + didWriteData: bytesWritten, + totalBytesWritten: totalBytesWritten, + totalBytesExpectedToWrite: totalBytesExpectedToWrite) + } + } + + public func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didFinishDownloadingTo location: URL) { + performEvent { $0.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) } + } + + public func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) { + performEvent { $0.request(request, didCreateInitialURLRequest: urlRequest) } + } + + public func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) { + performEvent { $0.request(request, didFailToCreateURLRequestWithError: error) } + } + + public func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) { + performEvent { $0.request(request, didAdaptInitialRequest: initialRequest, to: adaptedRequest) } + } + + public func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) { + performEvent { $0.request(request, didFailToAdaptURLRequest: initialRequest, withError: error) } + } + + public func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) { + performEvent { $0.request(request, didCreateURLRequest: urlRequest) } + } + + public func request(_ request: Request, didCreateTask task: URLSessionTask) { + performEvent { $0.request(request, didCreateTask: task) } + } + + public func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) { + performEvent { $0.request(request, didGatherMetrics: metrics) } + } + + public func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) { + performEvent { $0.request(request, didFailTask: task, earlyWithError: error) } + } + + public func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { + performEvent { $0.request(request, didCompleteTask: task, with: error) } + } + + public func requestIsRetrying(_ request: Request) { + performEvent { $0.requestIsRetrying(request) } + } + + public func requestDidFinish(_ request: Request) { + performEvent { $0.requestDidFinish(request) } + } + + public func requestDidResume(_ request: Request) { + performEvent { $0.requestDidResume(request) } + } + + public func request(_ request: Request, didResumeTask task: URLSessionTask) { + performEvent { $0.request(request, didResumeTask: task) } + } + + public func requestDidSuspend(_ request: Request) { + performEvent { $0.requestDidSuspend(request) } + } + + public func request(_ request: Request, didSuspendTask task: URLSessionTask) { + performEvent { $0.request(request, didSuspendTask: task) } + } + + public func requestDidCancel(_ request: Request) { + performEvent { $0.requestDidCancel(request) } + } + + public func request(_ request: Request, didCancelTask task: URLSessionTask) { + performEvent { $0.request(request, didCancelTask: task) } + } + + public func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) { + performEvent { $0.request(request, + didValidateRequest: urlRequest, + response: response, + data: data, + withResult: result) + } + } + + public func request(_ request: DataRequest, didParseResponse response: DataResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } + + public func request(_ request: DataRequest, didParseResponse response: DataResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } + + public func request(_ request: DataStreamRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + withResult result: Request.ValidationResult) { + performEvent { $0.request(request, + didValidateRequest: urlRequest, + response: response, + withResult: result) + } + } + + public func request(_ request: DataStreamRequest, didParseStream result: Result) { + performEvent { $0.request(request, didParseStream: result) } + } + + public func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) { + performEvent { $0.request(request, didCreateUploadable: uploadable) } + } + + public func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) { + performEvent { $0.request(request, didFailToCreateUploadableWithError: error) } + } + + public func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) { + performEvent { $0.request(request, didProvideInputStream: stream) } + } + + public func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) { + performEvent { $0.request(request, didFinishDownloadingUsing: task, with: result) } + } + + public func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) { + performEvent { $0.request(request, didCreateDestinationURL: url) } + } + + public func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) { + performEvent { $0.request(request, + didValidateRequest: urlRequest, + response: response, + fileURL: fileURL, + withResult: result) } + } + + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } + + public func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { + performEvent { $0.request(request, didParseResponse: response) } + } +} + +/// `EventMonitor` that allows optional closures to be set to receive events. +open class ClosureEventMonitor: EventMonitor, @unchecked Sendable { + /// Closure called on the `urlSession(_:didBecomeInvalidWithError:)` event. + open var sessionDidBecomeInvalidWithError: ((URLSession, (any Error)?) -> Void)? + + /// Closure called on the `urlSession(_:task:didReceive:completionHandler:)`. + open var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> Void)? + + /// Closure that receives `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)` event. + open var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? + + /// Closure called on the `urlSession(_:task:needNewBodyStream:)` event. + open var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> Void)? + + /// Closure called on the `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` event. + open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> Void)? + + /// Closure called on the `urlSession(_:task:didFinishCollecting:)` event. + open var taskDidFinishCollectingMetrics: ((URLSession, URLSessionTask, URLSessionTaskMetrics) -> Void)? + + /// Closure called on the `urlSession(_:task:didCompleteWithError:)` event. + open var taskDidComplete: ((URLSession, URLSessionTask, (any Error)?) -> Void)? + + /// Closure called on the `urlSession(_:taskIsWaitingForConnectivity:)` event. + open var taskIsWaitingForConnectivity: ((URLSession, URLSessionTask) -> Void)? + + /// Closure called on the `urlSession(_:dataTask:didReceive:completionHandler:)` event. + open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> Void)? + + /// Closure that receives the `urlSession(_:dataTask:didReceive:)` event. + open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? + + /// Closure called on the `urlSession(_:dataTask:willCacheResponse:completionHandler:)` event. + open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> Void)? + + /// Closure called on the `urlSession(_:downloadTask:didFinishDownloadingTo:)` event. + open var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)? + + /// Closure called on the `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)` + /// event. + open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? + + /// Closure called on the `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)` event. + open var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)? + + // MARK: - Request Events + + /// Closure called on the `request(_:didCreateInitialURLRequest:)` event. + open var requestDidCreateInitialURLRequest: ((Request, URLRequest) -> Void)? + + /// Closure called on the `request(_:didFailToCreateURLRequestWithError:)` event. + open var requestDidFailToCreateURLRequestWithError: ((Request, AFError) -> Void)? + + /// Closure called on the `request(_:didAdaptInitialRequest:to:)` event. + open var requestDidAdaptInitialRequestToAdaptedRequest: ((Request, URLRequest, URLRequest) -> Void)? + + /// Closure called on the `request(_:didFailToAdaptURLRequest:withError:)` event. + open var requestDidFailToAdaptURLRequestWithError: ((Request, URLRequest, AFError) -> Void)? + + /// Closure called on the `request(_:didCreateURLRequest:)` event. + open var requestDidCreateURLRequest: ((Request, URLRequest) -> Void)? + + /// Closure called on the `request(_:didCreateTask:)` event. + open var requestDidCreateTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `request(_:didGatherMetrics:)` event. + open var requestDidGatherMetrics: ((Request, URLSessionTaskMetrics) -> Void)? + + /// Closure called on the `request(_:didFailTask:earlyWithError:)` event. + open var requestDidFailTaskEarlyWithError: ((Request, URLSessionTask, AFError) -> Void)? + + /// Closure called on the `request(_:didCompleteTask:with:)` event. + open var requestDidCompleteTaskWithError: ((Request, URLSessionTask, AFError?) -> Void)? + + /// Closure called on the `requestIsRetrying(_:)` event. + open var requestIsRetrying: ((Request) -> Void)? + + /// Closure called on the `requestDidFinish(_:)` event. + open var requestDidFinish: ((Request) -> Void)? + + /// Closure called on the `requestDidResume(_:)` event. + open var requestDidResume: ((Request) -> Void)? + + /// Closure called on the `request(_:didResumeTask:)` event. + open var requestDidResumeTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `requestDidSuspend(_:)` event. + open var requestDidSuspend: ((Request) -> Void)? + + /// Closure called on the `request(_:didSuspendTask:)` event. + open var requestDidSuspendTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `requestDidCancel(_:)` event. + open var requestDidCancel: ((Request) -> Void)? + + /// Closure called on the `request(_:didCancelTask:)` event. + open var requestDidCancelTask: ((Request, URLSessionTask) -> Void)? + + /// Closure called on the `request(_:didValidateRequest:response:data:withResult:)` event. + open var requestDidValidateRequestResponseDataWithResult: ((DataRequest, URLRequest?, HTTPURLResponse, Data?, Request.ValidationResult) -> Void)? + + /// Closure called on the `request(_:didParseResponse:)` event. + open var requestDidParseResponse: ((DataRequest, DataResponse) -> Void)? + + /// Closure called on the `request(_:didValidateRequest:response:withResult:)` event. + open var requestDidValidateRequestResponseWithResult: ((DataStreamRequest, URLRequest?, HTTPURLResponse, Request.ValidationResult) -> Void)? + + /// Closure called on the `request(_:didCreateUploadable:)` event. + open var requestDidCreateUploadable: ((UploadRequest, UploadRequest.Uploadable) -> Void)? + + /// Closure called on the `request(_:didFailToCreateUploadableWithError:)` event. + open var requestDidFailToCreateUploadableWithError: ((UploadRequest, AFError) -> Void)? + + /// Closure called on the `request(_:didProvideInputStream:)` event. + open var requestDidProvideInputStream: ((UploadRequest, InputStream) -> Void)? + + /// Closure called on the `request(_:didFinishDownloadingUsing:with:)` event. + open var requestDidFinishDownloadingUsingTaskWithResult: ((DownloadRequest, URLSessionTask, Result) -> Void)? + + /// Closure called on the `request(_:didCreateDestinationURL:)` event. + open var requestDidCreateDestinationURL: ((DownloadRequest, URL) -> Void)? + + /// Closure called on the `request(_:didValidateRequest:response:temporaryURL:destinationURL:withResult:)` event. + open var requestDidValidateRequestResponseFileURLWithResult: ((DownloadRequest, URLRequest?, HTTPURLResponse, URL?, Request.ValidationResult) -> Void)? + + /// Closure called on the `request(_:didParseResponse:)` event. + open var requestDidParseDownloadResponse: ((DownloadRequest, DownloadResponse) -> Void)? + + public let queue: DispatchQueue + + /// Creates an instance using the provided queue. + /// + /// - Parameter queue: `DispatchQueue` on which events will fired. `.main` by default. + public init(queue: DispatchQueue = .main) { + self.queue = queue + } + + open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: (any Error)?) { + sessionDidBecomeInvalidWithError?(session, error) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) { + taskDidReceiveChallenge?(session, task, challenge) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + didSendBodyData bytesSent: Int64, + totalBytesSent: Int64, + totalBytesExpectedToSend: Int64) { + taskDidSendBodyData?(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend) + } + + open func urlSession(_ session: URLSession, taskNeedsNewBodyStream task: URLSessionTask) { + taskNeedNewBodyStream?(session, task) + } + + open func urlSession(_ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest) { + taskWillPerformHTTPRedirection?(session, task, response, request) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { + taskDidFinishCollectingMetrics?(session, task, metrics) + } + + open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: (any Error)?) { + taskDidComplete?(session, task, error) + } + + @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) + open func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) { + taskIsWaitingForConnectivity?(session, task) + } + + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) { + dataTaskDidReceiveResponse?(session, dataTask, response) + } + + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { + dataTaskDidReceiveData?(session, dataTask, data) + } + + open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse) { + dataTaskWillCacheResponse?(session, dataTask, proposedResponse) + } + + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didResumeAtOffset fileOffset: Int64, + expectedTotalBytes: Int64) { + downloadTaskDidResumeAtOffset?(session, downloadTask, fileOffset, expectedTotalBytes) + } + + open func urlSession(_ session: URLSession, + downloadTask: URLSessionDownloadTask, + didWriteData bytesWritten: Int64, + totalBytesWritten: Int64, + totalBytesExpectedToWrite: Int64) { + downloadTaskDidWriteData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) + } + + open func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + downloadTaskDidFinishDownloadingToURL?(session, downloadTask, location) + } + + // MARK: Request Events + + open func request(_ request: Request, didCreateInitialURLRequest urlRequest: URLRequest) { + requestDidCreateInitialURLRequest?(request, urlRequest) + } + + open func request(_ request: Request, didFailToCreateURLRequestWithError error: AFError) { + requestDidFailToCreateURLRequestWithError?(request, error) + } + + open func request(_ request: Request, didAdaptInitialRequest initialRequest: URLRequest, to adaptedRequest: URLRequest) { + requestDidAdaptInitialRequestToAdaptedRequest?(request, initialRequest, adaptedRequest) + } + + open func request(_ request: Request, didFailToAdaptURLRequest initialRequest: URLRequest, withError error: AFError) { + requestDidFailToAdaptURLRequestWithError?(request, initialRequest, error) + } + + open func request(_ request: Request, didCreateURLRequest urlRequest: URLRequest) { + requestDidCreateURLRequest?(request, urlRequest) + } + + open func request(_ request: Request, didCreateTask task: URLSessionTask) { + requestDidCreateTask?(request, task) + } + + open func request(_ request: Request, didGatherMetrics metrics: URLSessionTaskMetrics) { + requestDidGatherMetrics?(request, metrics) + } + + open func request(_ request: Request, didFailTask task: URLSessionTask, earlyWithError error: AFError) { + requestDidFailTaskEarlyWithError?(request, task, error) + } + + open func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: AFError?) { + requestDidCompleteTaskWithError?(request, task, error) + } + + open func requestIsRetrying(_ request: Request) { + requestIsRetrying?(request) + } + + open func requestDidFinish(_ request: Request) { + requestDidFinish?(request) + } + + open func requestDidResume(_ request: Request) { + requestDidResume?(request) + } + + public func request(_ request: Request, didResumeTask task: URLSessionTask) { + requestDidResumeTask?(request, task) + } + + open func requestDidSuspend(_ request: Request) { + requestDidSuspend?(request) + } + + public func request(_ request: Request, didSuspendTask task: URLSessionTask) { + requestDidSuspendTask?(request, task) + } + + open func requestDidCancel(_ request: Request) { + requestDidCancel?(request) + } + + public func request(_ request: Request, didCancelTask task: URLSessionTask) { + requestDidCancelTask?(request, task) + } + + open func request(_ request: DataRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + data: Data?, + withResult result: Request.ValidationResult) { + requestDidValidateRequestResponseDataWithResult?(request, urlRequest, response, data, result) + } + + open func request(_ request: DataRequest, didParseResponse response: DataResponse) { + requestDidParseResponse?(request, response) + } + + public func request(_ request: DataStreamRequest, didValidateRequest urlRequest: URLRequest?, response: HTTPURLResponse, withResult result: Request.ValidationResult) { + requestDidValidateRequestResponseWithResult?(request, urlRequest, response, result) + } + + open func request(_ request: UploadRequest, didCreateUploadable uploadable: UploadRequest.Uploadable) { + requestDidCreateUploadable?(request, uploadable) + } + + open func request(_ request: UploadRequest, didFailToCreateUploadableWithError error: AFError) { + requestDidFailToCreateUploadableWithError?(request, error) + } + + open func request(_ request: UploadRequest, didProvideInputStream stream: InputStream) { + requestDidProvideInputStream?(request, stream) + } + + open func request(_ request: DownloadRequest, didFinishDownloadingUsing task: URLSessionTask, with result: Result) { + requestDidFinishDownloadingUsingTaskWithResult?(request, task, result) + } + + open func request(_ request: DownloadRequest, didCreateDestinationURL url: URL) { + requestDidCreateDestinationURL?(request, url) + } + + open func request(_ request: DownloadRequest, + didValidateRequest urlRequest: URLRequest?, + response: HTTPURLResponse, + fileURL: URL?, + withResult result: Request.ValidationResult) { + requestDidValidateRequestResponseFileURLWithResult?(request, + urlRequest, + response, + fileURL, + result) + } + + open func request(_ request: DownloadRequest, didParseResponse response: DownloadResponse) { + requestDidParseDownloadResponse?(request, response) + } +} diff --git a/Pods/Alamofire/Source/Features/MultipartFormData.swift b/Pods/Alamofire/Source/Features/MultipartFormData.swift new file mode 100644 index 0000000..7cc5e13 --- /dev/null +++ b/Pods/Alamofire/Source/Features/MultipartFormData.swift @@ -0,0 +1,585 @@ +// +// MultipartFormData.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +#if canImport(MobileCoreServices) +import MobileCoreServices +#elseif canImport(CoreServices) +import CoreServices +#endif + +/// Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode +/// multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead +/// to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the +/// data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for +/// larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset. +/// +/// For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well +/// and the w3 form documentation. +/// +/// - https://www.ietf.org/rfc/rfc2388.txt +/// - https://www.ietf.org/rfc/rfc2045.txt +/// - https://www.w3.org/TR/html401/interact/forms.html#h-17.13 +open class MultipartFormData { + // MARK: - Helper Types + + enum EncodingCharacters { + static let crlf = "\r\n" + } + + enum BoundaryGenerator { + enum BoundaryType { + case initial, encapsulated, final + } + + static func randomBoundary() -> String { + let first = UInt32.random(in: UInt32.min...UInt32.max) + let second = UInt32.random(in: UInt32.min...UInt32.max) + + return String(format: "alamofire.boundary.%08x%08x", first, second) + } + + static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data { + let boundaryText = switch boundaryType { + case .initial: + "--\(boundary)\(EncodingCharacters.crlf)" + case .encapsulated: + "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)" + case .final: + "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)" + } + + return Data(boundaryText.utf8) + } + } + + class BodyPart { + let headers: HTTPHeaders + let bodyStream: InputStream + let bodyContentLength: UInt64 + var hasInitialBoundary = false + var hasFinalBoundary = false + + init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) { + self.headers = headers + self.bodyStream = bodyStream + self.bodyContentLength = bodyContentLength + } + } + + // MARK: - Properties + + /// Default memory threshold used when encoding `MultipartFormData`, in bytes. + public static let encodingMemoryThreshold: UInt64 = 10_000_000 + + /// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`. + open lazy var contentType: String = "multipart/form-data; boundary=\(self.boundary)" + + /// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries. + public var contentLength: UInt64 { bodyParts.reduce(0) { $0 + $1.bodyContentLength } } + + /// The boundary used to separate the body parts in the encoded form data. + public let boundary: String + + let fileManager: FileManager + + private var bodyParts: [BodyPart] + private var bodyPartError: AFError? + private let streamBufferSize: Int + + // MARK: - Lifecycle + + /// Creates an instance. + /// + /// - Parameters: + /// - fileManager: `FileManager` to use for file operations, if needed. + /// - boundary: Boundary `String` used to separate body parts. + public init(fileManager: FileManager = .default, boundary: String? = nil) { + self.fileManager = fileManager + self.boundary = boundary ?? BoundaryGenerator.randomBoundary() + bodyParts = [] + + // + // The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more + // information, please refer to the following article: + // - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html + // + streamBufferSize = 1024 + } + + // MARK: - Body Parts + + /// Creates a body part from the data and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + /// - `Content-Type: #{mimeType}` (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// - Parameters: + /// - data: `Data` to encoding into the instance. + /// - name: Name to associate with the `Data` in the `Content-Disposition` HTTP header. + /// - fileName: Filename to associate with the `Data` in the `Content-Disposition` HTTP header. + /// - mimeType: MIME type to associate with the data in the `Content-Type` HTTP header. + public func append(_ data: Data, withName name: String, fileName: String? = nil, mimeType: String? = nil) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + let stream = InputStream(data: data) + let length = UInt64(data.count) + + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part from the file and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header) + /// - `Content-Type: #{generated mimeType}` (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// The filename in the `Content-Disposition` HTTP header is generated from the last path component of the + /// `fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the + /// system associated MIME type. + /// + /// - Parameters: + /// - fileURL: `URL` of the file whose content will be encoded into the instance. + /// - name: Name to associate with the file content in the `Content-Disposition` HTTP header. + public func append(_ fileURL: URL, withName name: String) { + let fileName = fileURL.lastPathComponent + let pathExtension = fileURL.pathExtension + + if !fileName.isEmpty && !pathExtension.isEmpty { + let mime = mimeType(forPathExtension: pathExtension) + append(fileURL, withName: name, fileName: fileName, mimeType: mime) + } else { + setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL)) + } + } + + /// Creates a body part from the file and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header) + /// - Content-Type: #{mimeType} (HTTP Header) + /// - Encoded file data + /// - Multipart form boundary + /// + /// - Parameters: + /// - fileURL: `URL` of the file whose content will be encoded into the instance. + /// - name: Name to associate with the file content in the `Content-Disposition` HTTP header. + /// - fileName: Filename to associate with the file content in the `Content-Disposition` HTTP header. + /// - mimeType: MIME type to associate with the file content in the `Content-Type` HTTP header. + public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + + //============================================================ + // Check 1 - is file URL? + //============================================================ + + guard fileURL.isFileURL else { + setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL)) + return + } + + //============================================================ + // Check 2 - is file URL reachable? + //============================================================ + + #if !(os(Linux) || os(Windows) || os(Android)) + do { + let isReachable = try fileURL.checkPromisedItemIsReachable() + guard isReachable else { + setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL)) + return + } + } catch { + setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error)) + return + } + #endif + + //============================================================ + // Check 3 - is file URL a directory? + //============================================================ + + var isDirectory: ObjCBool = false + let path = fileURL.path + + guard fileManager.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else { + setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL)) + return + } + + //============================================================ + // Check 4 - can the file size be extracted? + //============================================================ + + let bodyContentLength: UInt64 + + do { + guard let fileSize = try fileManager.attributesOfItem(atPath: path)[.size] as? NSNumber else { + setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL)) + return + } + + bodyContentLength = fileSize.uint64Value + } catch { + setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error)) + return + } + + //============================================================ + // Check 5 - can a stream be created from file URL? + //============================================================ + + guard let stream = InputStream(url: fileURL) else { + setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL)) + return + } + + append(stream, withLength: bodyContentLength, headers: headers) + } + + /// Creates a body part from the stream and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header) + /// - `Content-Type: #{mimeType}` (HTTP Header) + /// - Encoded stream data + /// - Multipart form boundary + /// + /// - Parameters: + /// - stream: `InputStream` to encode into the instance. + /// - length: Length, in bytes, of the stream. + /// - name: Name to associate with the stream content in the `Content-Disposition` HTTP header. + /// - fileName: Filename to associate with the stream content in the `Content-Disposition` HTTP header. + /// - mimeType: MIME type to associate with the stream content in the `Content-Type` HTTP header. + public func append(_ stream: InputStream, + withLength length: UInt64, + name: String, + fileName: String, + mimeType: String) { + let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType) + append(stream, withLength: length, headers: headers) + } + + /// Creates a body part with the stream, length, and headers and appends it to the instance. + /// + /// The body part data will be encoded using the following format: + /// + /// - HTTP headers + /// - Encoded stream data + /// - Multipart form boundary + /// + /// - Parameters: + /// - stream: `InputStream` to encode into the instance. + /// - length: Length, in bytes, of the stream. + /// - headers: `HTTPHeaders` for the body part. + public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) { + let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length) + bodyParts.append(bodyPart) + } + + // MARK: - Data Encoding + + /// Encodes all appended body parts into a single `Data` value. + /// + /// - Note: This method will load all the appended body parts into memory all at the same time. This method should + /// only be used when the encoded data will have a small memory footprint. For large data cases, please use + /// the `writeEncodedData(to:))` method. + /// + /// - Returns: The encoded `Data`, if encoding is successful. + /// - Throws: An `AFError` if encoding encounters an error. + public func encode() throws -> Data { + if let bodyPartError { + throw bodyPartError + } + + var encoded = Data() + + bodyParts.first?.hasInitialBoundary = true + bodyParts.last?.hasFinalBoundary = true + + for bodyPart in bodyParts { + let encodedData = try encode(bodyPart) + encoded.append(encodedData) + } + + return encoded + } + + /// Writes all appended body parts to the given file `URL`. + /// + /// This process is facilitated by reading and writing with input and output streams, respectively. Thus, + /// this approach is very memory efficient and should be used for large body part data. + /// + /// - Parameter fileURL: File `URL` to which to write the form data. + /// - Throws: An `AFError` if encoding encounters an error. + public func writeEncodedData(to fileURL: URL) throws { + if let bodyPartError { + throw bodyPartError + } + + if fileManager.fileExists(atPath: fileURL.path) { + throw AFError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL)) + } else if !fileURL.isFileURL { + throw AFError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL)) + } + + guard let outputStream = OutputStream(url: fileURL, append: false) else { + throw AFError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL)) + } + + outputStream.open() + defer { outputStream.close() } + + bodyParts.first?.hasInitialBoundary = true + bodyParts.last?.hasFinalBoundary = true + + for bodyPart in bodyParts { + try write(bodyPart, to: outputStream) + } + } + + // MARK: - Private - Body Part Encoding + + private func encode(_ bodyPart: BodyPart) throws -> Data { + var encoded = Data() + + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + encoded.append(initialData) + + let headerData = encodeHeaders(for: bodyPart) + encoded.append(headerData) + + let bodyStreamData = try encodeBodyStream(for: bodyPart) + encoded.append(bodyStreamData) + + if bodyPart.hasFinalBoundary { + encoded.append(finalBoundaryData()) + } + + return encoded + } + + private func encodeHeaders(for bodyPart: BodyPart) -> Data { + let headerText = bodyPart.headers.map { "\($0.name): \($0.value)\(EncodingCharacters.crlf)" } + .joined() + + EncodingCharacters.crlf + + return Data(headerText.utf8) + } + + private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data { + let inputStream = bodyPart.bodyStream + inputStream.open() + defer { inputStream.close() } + + var encoded = Data() + + while inputStream.hasBytesAvailable { + var buffer = [UInt8](repeating: 0, count: streamBufferSize) + let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize) + + if let error = inputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error)) + } + + if bytesRead > 0 { + encoded.append(buffer, count: bytesRead) + } else { + break + } + } + + guard UInt64(encoded.count) == bodyPart.bodyContentLength else { + let error = AFError.UnexpectedInputStreamLength(bytesExpected: bodyPart.bodyContentLength, + bytesRead: UInt64(encoded.count)) + throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error)) + } + + return encoded + } + + // MARK: - Private - Writing Body Part to Output Stream + + private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws { + try writeInitialBoundaryData(for: bodyPart, to: outputStream) + try writeHeaderData(for: bodyPart, to: outputStream) + try writeBodyStream(for: bodyPart, to: outputStream) + try writeFinalBoundaryData(for: bodyPart, to: outputStream) + } + + private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData() + return try write(initialData, to: outputStream) + } + + private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let headerData = encodeHeaders(for: bodyPart) + return try write(headerData, to: outputStream) + } + + private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws { + let inputStream = bodyPart.bodyStream + + inputStream.open() + defer { inputStream.close() } + + var bytesLeftToRead = bodyPart.bodyContentLength + while inputStream.hasBytesAvailable && bytesLeftToRead > 0 { + let bufferSize = min(streamBufferSize, Int(bytesLeftToRead)) + var buffer = [UInt8](repeating: 0, count: bufferSize) + let bytesRead = inputStream.read(&buffer, maxLength: bufferSize) + + if let streamError = inputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError)) + } + + if bytesRead > 0 { + if buffer.count != bytesRead { + buffer = Array(buffer[0.. 0, outputStream.hasSpaceAvailable { + let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite) + + if let error = outputStream.streamError { + throw AFError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error)) + } + + bytesToWrite -= bytesWritten + + if bytesToWrite > 0 { + buffer = Array(buffer[bytesWritten.. HTTPHeaders { + var disposition = "form-data; name=\"\(name)\"" + if let fileName { disposition += "; filename=\"\(fileName)\"" } + + var headers: HTTPHeaders = [.contentDisposition(disposition)] + if let mimeType { headers.add(.contentType(mimeType)) } + + return headers + } + + // MARK: - Private - Boundary Encoding + + private func initialBoundaryData() -> Data { + BoundaryGenerator.boundaryData(forBoundaryType: .initial, boundary: boundary) + } + + private func encapsulatedBoundaryData() -> Data { + BoundaryGenerator.boundaryData(forBoundaryType: .encapsulated, boundary: boundary) + } + + private func finalBoundaryData() -> Data { + BoundaryGenerator.boundaryData(forBoundaryType: .final, boundary: boundary) + } + + // MARK: - Private - Errors + + private func setBodyPartError(withReason reason: AFError.MultipartEncodingFailureReason) { + guard bodyPartError == nil else { return } + bodyPartError = AFError.multipartEncodingFailed(reason: reason) + } +} + +#if canImport(UniformTypeIdentifiers) +import UniformTypeIdentifiers + +extension MultipartFormData { + // MARK: - Private - Mime Type + + private func mimeType(forPathExtension pathExtension: String) -> String { + if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, visionOS 1, *) { + return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream" + } else { + if + let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), + let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() { + return contentType as String + } + + return "application/octet-stream" + } + } +} + +#else + +extension MultipartFormData { + // MARK: - Private - Mime Type + + private func mimeType(forPathExtension pathExtension: String) -> String { + #if canImport(CoreServices) || canImport(MobileCoreServices) + if + let id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(), + let contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue() { + return contentType as String + } + #endif + + return "application/octet-stream" + } +} + +#endif diff --git a/Pods/Alamofire/Source/Features/MultipartUpload.swift b/Pods/Alamofire/Source/Features/MultipartUpload.swift new file mode 100644 index 0000000..ae768a3 --- /dev/null +++ b/Pods/Alamofire/Source/Features/MultipartUpload.swift @@ -0,0 +1,99 @@ +// +// MultipartUpload.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Internal type which encapsulates a `MultipartFormData` upload. +final class MultipartUpload: @unchecked Sendable { // Must be @unchecked due to FileManager not being properly Sendable. + private let _result = Protected?>(nil) + var result: Result { + if let value = _result.read({ $0 }) { + return value + } else { + let result = Result { try build() } + _result.write(result) + + return result + } + } + + private let multipartFormData: Protected + + let encodingMemoryThreshold: UInt64 + let request: any URLRequestConvertible + let fileManager: FileManager + + init(encodingMemoryThreshold: UInt64, + request: any URLRequestConvertible, + multipartFormData: MultipartFormData) { + self.encodingMemoryThreshold = encodingMemoryThreshold + self.request = request + fileManager = multipartFormData.fileManager + self.multipartFormData = Protected(multipartFormData) + } + + func build() throws -> UploadRequest.Uploadable { + let uploadable: UploadRequest.Uploadable + if multipartFormData.contentLength < encodingMemoryThreshold { + let data = try multipartFormData.read { try $0.encode() } + + uploadable = .data(data) + } else { + let tempDirectoryURL = fileManager.temporaryDirectory + let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data") + let fileName = UUID().uuidString + let fileURL = directoryURL.appendingPathComponent(fileName) + + try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) + + do { + try multipartFormData.read { try $0.writeEncodedData(to: fileURL) } + } catch { + // Cleanup after attempted write if it fails. + try? fileManager.removeItem(at: fileURL) + throw error + } + + uploadable = .file(fileURL, shouldRemove: true) + } + + return uploadable + } +} + +extension MultipartUpload: UploadConvertible { + func asURLRequest() throws -> URLRequest { + var urlRequest = try request.asURLRequest() + + multipartFormData.read { multipartFormData in + urlRequest.headers.add(.contentType(multipartFormData.contentType)) + } + + return urlRequest + } + + func createUploadable() throws -> UploadRequest.Uploadable { + try result.get() + } +} diff --git a/Pods/Alamofire/Source/Features/NetworkReachabilityManager.swift b/Pods/Alamofire/Source/Features/NetworkReachabilityManager.swift new file mode 100644 index 0000000..bee946e --- /dev/null +++ b/Pods/Alamofire/Source/Features/NetworkReachabilityManager.swift @@ -0,0 +1,293 @@ +// +// NetworkReachabilityManager.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +#if canImport(SystemConfiguration) + +import Foundation +import SystemConfiguration + +/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both cellular and +/// WiFi network interfaces. +/// +/// Reachability can be used to determine background information about why a network operation failed, or to retry +/// network requests when a connection is established. It should not be used to prevent a user from initiating a network +/// request, as it's possible that an initial request may be required to establish reachability. +open class NetworkReachabilityManager: @unchecked Sendable { + /// Defines the various states of network reachability. + public enum NetworkReachabilityStatus: Sendable { + /// It is unknown whether the network is reachable. + case unknown + /// The network is not reachable. + case notReachable + /// The network is reachable on the associated `ConnectionType`. + case reachable(ConnectionType) + + init(_ flags: SCNetworkReachabilityFlags) { + guard flags.isActuallyReachable else { self = .notReachable; return } + + var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi) + + if flags.isCellular { networkStatus = .reachable(.cellular) } + + self = networkStatus + } + + /// Defines the various connection types detected by reachability flags. + public enum ConnectionType: Sendable { + /// The connection type is either over Ethernet or WiFi. + case ethernetOrWiFi + /// The connection type is a cellular connection. + case cellular + } + } + + /// A closure executed when the network reachability status changes. The closure takes a single argument: the + /// network reachability status. + public typealias Listener = @Sendable (NetworkReachabilityStatus) -> Void + + /// Default `NetworkReachabilityManager` for the zero address and a `listenerQueue` of `.main`. + public static let `default` = NetworkReachabilityManager() + + // MARK: - Properties + + /// Whether the network is currently reachable. + open var isReachable: Bool { isReachableOnCellular || isReachableOnEthernetOrWiFi } + + /// Whether the network is currently reachable over the cellular interface. + /// + /// - Note: Using this property to decide whether to make a high or low bandwidth request is not recommended. + /// Instead, set the `allowsCellularAccess` on any `URLRequest`s being issued. + /// + open var isReachableOnCellular: Bool { status == .reachable(.cellular) } + + /// Whether the network is currently reachable over Ethernet or WiFi interface. + open var isReachableOnEthernetOrWiFi: Bool { status == .reachable(.ethernetOrWiFi) } + + /// `DispatchQueue` on which reachability will update. + public let reachabilityQueue = DispatchQueue(label: "org.alamofire.reachabilityQueue") + + /// Flags of the current reachability type, if any. + open var flags: SCNetworkReachabilityFlags? { + var flags = SCNetworkReachabilityFlags() + + return SCNetworkReachabilityGetFlags(reachability, &flags) ? flags : nil + } + + /// The current network reachability status. + open var status: NetworkReachabilityStatus { + flags.map(NetworkReachabilityStatus.init) ?? .unknown + } + + /// Mutable state storage. + struct MutableState { + /// A closure executed when the network reachability status changes. + var listener: Listener? + /// `DispatchQueue` on which listeners will be called. + var listenerQueue: DispatchQueue? + /// Previously calculated status. + var previousStatus: NetworkReachabilityStatus? + } + + /// `SCNetworkReachability` instance providing notifications. + private let reachability: SCNetworkReachability + + /// Protected storage for mutable state. + private let mutableState = Protected(MutableState()) + + // MARK: - Initialization + + /// Creates an instance with the specified host. + /// + /// - Note: The `host` value must *not* contain a scheme, just the hostname. + /// + /// - Parameters: + /// - host: Host used to evaluate network reachability. Must *not* include the scheme (e.g. `https`). + public convenience init?(host: String) { + guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } + + self.init(reachability: reachability) + } + + /// Creates an instance that monitors the address 0.0.0.0. + /// + /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing + /// status of the device, both IPv4 and IPv6. + public convenience init?() { + var zero = sockaddr() + zero.sa_len = UInt8(MemoryLayout.size) + zero.sa_family = sa_family_t(AF_INET) + + guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil } + + self.init(reachability: reachability) + } + + private init(reachability: SCNetworkReachability) { + self.reachability = reachability + } + + deinit { + stopListening() + } + + // MARK: - Listening + + /// Starts listening for changes in network reachability status. + /// + /// - Note: Stops and removes any existing listener. + /// + /// - Parameters: + /// - queue: `DispatchQueue` on which to call the `listener` closure. `.main` by default. + /// - listener: `Listener` closure called when reachability changes. + /// + /// - Returns: `true` if listening was started successfully, `false` otherwise. + @preconcurrency + @discardableResult + open func startListening(onQueue queue: DispatchQueue = .main, + onUpdatePerforming listener: @escaping Listener) -> Bool { + stopListening() + + mutableState.write { state in + state.listenerQueue = queue + state.listener = listener + } + + let weakManager = WeakManager(manager: self) + + var context = SCNetworkReachabilityContext( + version: 0, + info: Unmanaged.passUnretained(weakManager).toOpaque(), + retain: { info in + let unmanaged = Unmanaged.fromOpaque(info) + _ = unmanaged.retain() + + return UnsafeRawPointer(unmanaged.toOpaque()) + }, + release: { info in + let unmanaged = Unmanaged.fromOpaque(info) + unmanaged.release() + }, + copyDescription: { info in + let unmanaged = Unmanaged.fromOpaque(info) + let weakManager = unmanaged.takeUnretainedValue() + let description = weakManager.manager?.flags?.readableDescription ?? "nil" + + return Unmanaged.passRetained(description as CFString) + } + ) + let callback: SCNetworkReachabilityCallBack = { _, flags, info in + guard let info else { return } + + let weakManager = Unmanaged.fromOpaque(info).takeUnretainedValue() + weakManager.manager?.notifyListener(flags) + } + + let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue) + let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context) + + // Manually call listener to give initial state, since the framework may not. + if let currentFlags = flags { + reachabilityQueue.async { + self.notifyListener(currentFlags) + } + } + + return callbackAdded && queueAdded + } + + /// Stops listening for changes in network reachability status. + open func stopListening() { + SCNetworkReachabilitySetCallback(reachability, nil, nil) + SCNetworkReachabilitySetDispatchQueue(reachability, nil) + mutableState.write { state in + state.listener = nil + state.listenerQueue = nil + state.previousStatus = nil + } + } + + // MARK: - Internal - Listener Notification + + /// Calls the `listener` closure of the `listenerQueue` if the computed status hasn't changed. + /// + /// - Note: Should only be called from the `reachabilityQueue`. + /// + /// - Parameter flags: `SCNetworkReachabilityFlags` to use to calculate the status. + func notifyListener(_ flags: SCNetworkReachabilityFlags) { + let newStatus = NetworkReachabilityStatus(flags) + + mutableState.write { [newStatus] state in + guard state.previousStatus != newStatus else { return } + + state.previousStatus = newStatus + + let listener = state.listener + state.listenerQueue?.async { listener?(newStatus) } + } + } + + private final class WeakManager { + weak var manager: NetworkReachabilityManager? + + init(manager: NetworkReachabilityManager?) { + self.manager = manager + } + } +} + +// MARK: - + +extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} + +extension SCNetworkReachabilityFlags { + var isReachable: Bool { contains(.reachable) } + var isConnectionRequired: Bool { contains(.connectionRequired) } + var canConnectAutomatically: Bool { contains(.connectionOnDemand) || contains(.connectionOnTraffic) } + var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) } + var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) } + var isCellular: Bool { + #if os(iOS) || os(tvOS) || (swift(>=5.9) && os(visionOS)) + return contains(.isWWAN) + #else + return false + #endif + } + + /// Human readable `String` for all states, to help with debugging. + var readableDescription: String { + let W = isCellular ? "W" : "-" + let R = isReachable ? "R" : "-" + let c = isConnectionRequired ? "c" : "-" + let t = contains(.transientConnection) ? "t" : "-" + let i = contains(.interventionRequired) ? "i" : "-" + let C = contains(.connectionOnTraffic) ? "C" : "-" + let D = contains(.connectionOnDemand) ? "D" : "-" + let l = contains(.isLocalAddress) ? "l" : "-" + let d = contains(.isDirect) ? "d" : "-" + let a = contains(.connectionAutomatic) ? "a" : "-" + + return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)\(a)" + } +} +#endif diff --git a/Pods/Alamofire/Source/Features/RedirectHandler.swift b/Pods/Alamofire/Source/Features/RedirectHandler.swift new file mode 100644 index 0000000..c63219d --- /dev/null +++ b/Pods/Alamofire/Source/Features/RedirectHandler.swift @@ -0,0 +1,111 @@ +// +// RedirectHandler.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// A type that handles how an HTTP redirect response from a remote server should be redirected to the new request. +public protocol RedirectHandler: Sendable { + /// Determines how the HTTP redirect response should be redirected to the new request. + /// + /// The `completion` closure should be passed one of three possible options: + /// + /// 1. The new request specified by the redirect (this is the most common use case). + /// 2. A modified version of the new request (you may want to route it somewhere else). + /// 3. A `nil` value to deny the redirect request and return the body of the redirect response. + /// + /// - Parameters: + /// - task: The `URLSessionTask` whose request resulted in a redirect. + /// - request: The `URLRequest` to the new location specified by the redirect response. + /// - response: The `HTTPURLResponse` containing the server's response to the original request. + /// - completion: The closure to execute containing the new `URLRequest`, a modified `URLRequest`, or `nil`. + func task(_ task: URLSessionTask, + willBeRedirectedTo request: URLRequest, + for response: HTTPURLResponse, + completion: @escaping (URLRequest?) -> Void) +} + +// MARK: - + +/// `Redirector` is a convenience `RedirectHandler` making it easy to follow, not follow, or modify a redirect. +public struct Redirector { + /// Defines the behavior of the `Redirector` type. + public enum Behavior: Sendable { + /// Follow the redirect as defined in the response. + case follow + /// Do not follow the redirect defined in the response. + case doNotFollow + /// Modify the redirect request defined in the response. + case modify(@Sendable (_ task: URLSessionTask, _ request: URLRequest, _ response: HTTPURLResponse) -> URLRequest?) + } + + /// Returns a `Redirector` with a `.follow` `Behavior`. + public static let follow = Redirector(behavior: .follow) + /// Returns a `Redirector` with a `.doNotFollow` `Behavior`. + public static let doNotFollow = Redirector(behavior: .doNotFollow) + + /// The `Behavior` of the `Redirector`. + public let behavior: Behavior + + /// Creates a `Redirector` instance from the `Behavior`. + /// + /// - Parameter behavior: The `Behavior`. + public init(behavior: Behavior) { + self.behavior = behavior + } +} + +// MARK: - + +extension Redirector: RedirectHandler { + public func task(_ task: URLSessionTask, + willBeRedirectedTo request: URLRequest, + for response: HTTPURLResponse, + completion: @escaping (URLRequest?) -> Void) { + switch behavior { + case .follow: + completion(request) + case .doNotFollow: + completion(nil) + case let .modify(closure): + let request = closure(task, request, response) + completion(request) + } + } +} + +extension RedirectHandler where Self == Redirector { + /// Provides a `Redirector` which follows redirects. Equivalent to `Redirector.follow`. + public static var follow: Redirector { .follow } + + /// Provides a `Redirector` which does not follow redirects. Equivalent to `Redirector.doNotFollow`. + public static var doNotFollow: Redirector { .doNotFollow } + + /// Creates a `Redirector` which modifies the redirected `URLRequest` using the provided closure. + /// + /// - Parameter closure: Closure used to modify the redirect. + /// - Returns: The `Redirector`. + public static func modify(using closure: @escaping @Sendable (URLSessionTask, URLRequest, HTTPURLResponse) -> URLRequest?) -> Redirector { + Redirector(behavior: .modify(closure)) + } +} diff --git a/Pods/Alamofire/Source/Features/RequestCompression.swift b/Pods/Alamofire/Source/Features/RequestCompression.swift new file mode 100644 index 0000000..86335de --- /dev/null +++ b/Pods/Alamofire/Source/Features/RequestCompression.swift @@ -0,0 +1,146 @@ +// +// RequestCompression.swift +// +// Copyright (c) 2023 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +#if canImport(zlib) && !os(Android) +import Foundation +import zlib + +/// `RequestAdapter` which compresses outgoing `URLRequest` bodies using the `deflate` `Content-Encoding` and adds the +/// appropriate header. +/// +/// - Note: Most requests to most APIs are small and so would only be slowed down by applying this adapter. Measure the +/// size of your request bodies and the performance impact of using this adapter before use. Using this adapter +/// with already compressed data, such as images, will, at best, have no effect. Additionally, body compression +/// is a synchronous operation, so measuring the performance impact may be important to determine whether you +/// want to use a dedicated `requestQueue` in your `Session` instance. Finally, not all servers support request +/// compression, so test with all of your server configurations before deploying. +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +public struct DeflateRequestCompressor: Sendable, RequestInterceptor { + /// Type that determines the action taken when the `URLRequest` already has a `Content-Encoding` header. + public enum DuplicateHeaderBehavior: Sendable { + /// Throws a `DuplicateHeaderError`. The default. + case error + /// Replaces the existing header value with `deflate`. + case replace + /// Silently skips compression when the header exists. + case skip + } + + /// `Error` produced when the outgoing `URLRequest` already has a `Content-Encoding` header, when the instance has + /// been configured to produce an error. + public struct DuplicateHeaderError: Error {} + + /// Behavior to use when the outgoing `URLRequest` already has a `Content-Encoding` header. + public let duplicateHeaderBehavior: DuplicateHeaderBehavior + /// Closure which determines whether the outgoing body data should be compressed. + public let shouldCompressBodyData: @Sendable (_ bodyData: Data) -> Bool + + /// Creates an instance with the provided parameters. + /// + /// - Parameters: + /// - duplicateHeaderBehavior: `DuplicateHeaderBehavior` to use. `.error` by default. + /// - shouldCompressBodyData: Closure which determines whether the outgoing body data should be compressed. `true` by default. + public init(duplicateHeaderBehavior: DuplicateHeaderBehavior = .error, + shouldCompressBodyData: @escaping @Sendable (_ bodyData: Data) -> Bool = { _ in true }) { + self.duplicateHeaderBehavior = duplicateHeaderBehavior + self.shouldCompressBodyData = shouldCompressBodyData + } + + public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { + // No need to compress unless we have body data. No support for compressing streams. + guard let bodyData = urlRequest.httpBody else { + completion(.success(urlRequest)) + return + } + + guard shouldCompressBodyData(bodyData) else { + completion(.success(urlRequest)) + return + } + + if urlRequest.headers.value(for: "Content-Encoding") != nil { + switch duplicateHeaderBehavior { + case .error: + completion(.failure(DuplicateHeaderError())) + return + case .replace: + // Header will be replaced once the body data is compressed. + break + case .skip: + completion(.success(urlRequest)) + return + } + } + + var compressedRequest = urlRequest + + do { + compressedRequest.httpBody = try deflate(bodyData) + compressedRequest.headers.update(.contentEncoding("deflate")) + completion(.success(compressedRequest)) + } catch { + completion(.failure(error)) + } + } + + func deflate(_ data: Data) throws -> Data { + var output = Data([0x78, 0x5E]) // Header + try output.append((data as NSData).compressed(using: .zlib) as Data) + var checksum = adler32Checksum(of: data).bigEndian + output.append(Data(bytes: &checksum, count: MemoryLayout.size)) + + return output + } + + func adler32Checksum(of data: Data) -> UInt32 { + data.withUnsafeBytes { buffer in + UInt32(adler32(1, buffer.baseAddress, UInt32(buffer.count))) + } + } +} + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension RequestInterceptor where Self == DeflateRequestCompressor { + /// Create a `DeflateRequestCompressor` with default `duplicateHeaderBehavior` and `shouldCompressBodyData` values. + public static var deflateCompressor: DeflateRequestCompressor { + DeflateRequestCompressor() + } + + /// Creates a `DeflateRequestCompressor` with the provided `DuplicateHeaderBehavior` and `shouldCompressBodyData` + /// closure. + /// + /// - Parameters: + /// - duplicateHeaderBehavior: `DuplicateHeaderBehavior` to use. + /// - shouldCompressBodyData: Closure which determines whether the outgoing body data should be compressed. `true` by default. + /// + /// - Returns: The `DeflateRequestCompressor`. + public static func deflateCompressor( + duplicateHeaderBehavior: DeflateRequestCompressor.DuplicateHeaderBehavior = .error, + shouldCompressBodyData: @escaping @Sendable (_ bodyData: Data) -> Bool = { _ in true } + ) -> DeflateRequestCompressor { + DeflateRequestCompressor(duplicateHeaderBehavior: duplicateHeaderBehavior, + shouldCompressBodyData: shouldCompressBodyData) + } +} +#endif diff --git a/Pods/Alamofire/Source/Features/RequestInterceptor.swift b/Pods/Alamofire/Source/Features/RequestInterceptor.swift new file mode 100644 index 0000000..6c438c5 --- /dev/null +++ b/Pods/Alamofire/Source/Features/RequestInterceptor.swift @@ -0,0 +1,376 @@ +// +// RequestInterceptor.swift +// +// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// Stores all state associated with a `URLRequest` being adapted. +public struct RequestAdapterState: Sendable { + /// The `UUID` of the `Request` associated with the `URLRequest` to adapt. + public let requestID: UUID + + /// The `Session` associated with the `URLRequest` to adapt. + public let session: Session +} + +// MARK: - + +/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary. +public protocol RequestAdapter: Sendable { + /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result. + /// + /// - Parameters: + /// - urlRequest: The `URLRequest` to adapt. + /// - session: The `Session` that will execute the `URLRequest`. + /// - completion: The completion handler that must be called when adaptation is complete. + func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping @Sendable (_ result: Result) -> Void) + + /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result. + /// + /// - Parameters: + /// - urlRequest: The `URLRequest` to adapt. + /// - state: The `RequestAdapterState` associated with the `URLRequest`. + /// - completion: The completion handler that must be called when adaptation is complete. + func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping @Sendable (_ result: Result) -> Void) +} + +extension RequestAdapter { + @preconcurrency + public func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping @Sendable (_ result: Result) -> Void) { + adapt(urlRequest, for: state.session, completion: completion) + } +} + +// MARK: - + +/// Outcome of determination whether retry is necessary. +public enum RetryResult: Sendable { + /// Retry should be attempted immediately. + case retry + /// Retry should be attempted after the associated `TimeInterval`. + case retryWithDelay(TimeInterval) + /// Do not retry. + case doNotRetry + /// Do not retry due to the associated `Error`. + case doNotRetryWithError(any Error) +} + +extension RetryResult { + var retryRequired: Bool { + switch self { + case .retry, .retryWithDelay: true + default: false + } + } + + var delay: TimeInterval? { + switch self { + case let .retryWithDelay(delay): delay + default: nil + } + } + + var error: (any Error)? { + guard case let .doNotRetryWithError(error) = self else { return nil } + return error + } +} + +/// A type that determines whether a request should be retried after being executed by the specified session manager +/// and encountering an error. +public protocol RequestRetrier: Sendable { + /// Determines whether the `Request` should be retried by calling the `completion` closure. + /// + /// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs + /// to be retried. The one requirement is that the completion closure is called to ensure the request is properly + /// cleaned up after. + /// + /// - Parameters: + /// - request: `Request` that failed due to the provided `Error`. + /// - session: `Session` that produced the `Request`. + /// - error: `Error` encountered while executing the `Request`. + /// - completion: Completion closure to be executed when a retry decision has been determined. + func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping @Sendable (RetryResult) -> Void) +} + +// MARK: - + +/// Type that provides both `RequestAdapter` and `RequestRetrier` functionality. +public protocol RequestInterceptor: RequestAdapter, RequestRetrier {} + +extension RequestInterceptor { + @preconcurrency + public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping @Sendable (Result) -> Void) { + completion(.success(urlRequest)) + } + + @preconcurrency + public func retry(_ request: Request, + for session: Session, + dueTo error: any Error, + completion: @escaping @Sendable (RetryResult) -> Void) { + completion(.doNotRetry) + } +} + +/// `RequestAdapter` closure definition. +public typealias AdaptHandler = @Sendable (_ request: URLRequest, + _ session: Session, + _ completion: @escaping @Sendable (Result) -> Void) -> Void + +/// `RequestRetrier` closure definition. +public typealias RetryHandler = @Sendable (_ request: Request, + _ session: Session, + _ error: any Error, + _ completion: @escaping @Sendable (RetryResult) -> Void) -> Void + +// MARK: - + +/// Closure-based `RequestAdapter`. +open class Adapter: @unchecked Sendable, RequestInterceptor { + private let adaptHandler: AdaptHandler + + /// Creates an instance using the provided closure. + /// + /// - Parameter adaptHandler: `AdaptHandler` closure to be executed when handling request adaptation. + /// + @preconcurrency + public init(_ adaptHandler: @escaping AdaptHandler) { + self.adaptHandler = adaptHandler + } + + @preconcurrency + open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping @Sendable (Result) -> Void) { + adaptHandler(urlRequest, session, completion) + } + + @preconcurrency + open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping @Sendable (Result) -> Void) { + adaptHandler(urlRequest, state.session, completion) + } +} + +extension RequestAdapter where Self == Adapter { + /// Creates an `Adapter` using the provided `AdaptHandler` closure. + /// + /// - Parameter closure: `AdaptHandler` to use to adapt the request. + /// - Returns: The `Adapter`. + @preconcurrency + public static func adapter(using closure: @escaping AdaptHandler) -> Adapter { + Adapter(closure) + } +} + +// MARK: - + +/// Closure-based `RequestRetrier`. +open class Retrier: @unchecked Sendable, RequestInterceptor { + private let retryHandler: RetryHandler + + /// Creates an instance using the provided closure. + /// + /// - Parameter retryHandler: `RetryHandler` closure to be executed when handling request retry. + @preconcurrency + public init(_ retryHandler: @escaping RetryHandler) { + self.retryHandler = retryHandler + } + + @preconcurrency + open func retry(_ request: Request, + for session: Session, + dueTo error: any Error, + completion: @escaping @Sendable (RetryResult) -> Void) { + retryHandler(request, session, error, completion) + } +} + +extension RequestRetrier where Self == Retrier { + /// Creates a `Retrier` using the provided `RetryHandler` closure. + /// + /// - Parameter closure: `RetryHandler` to use to retry the request. + /// - Returns: The `Retrier`. + @preconcurrency + public static func retrier(using closure: @escaping RetryHandler) -> Retrier { + Retrier(closure) + } +} + +// MARK: - + +/// `RequestInterceptor` which can use multiple `RequestAdapter` and `RequestRetrier` values. +open class Interceptor: @unchecked Sendable, RequestInterceptor { + /// All `RequestAdapter`s associated with the instance. These adapters will be run until one fails. + public let adapters: [any RequestAdapter] + /// All `RequestRetrier`s associated with the instance. These retriers will be run one at a time until one triggers retry. + public let retriers: [any RequestRetrier] + + /// Creates an instance from `AdaptHandler` and `RetryHandler` closures. + /// + /// - Parameters: + /// - adaptHandler: `AdaptHandler` closure to be used. + /// - retryHandler: `RetryHandler` closure to be used. + public init(adaptHandler: @escaping AdaptHandler, retryHandler: @escaping RetryHandler) { + adapters = [Adapter(adaptHandler)] + retriers = [Retrier(retryHandler)] + } + + /// Creates an instance from `RequestAdapter` and `RequestRetrier` values. + /// + /// - Parameters: + /// - adapter: `RequestAdapter` value to be used. + /// - retrier: `RequestRetrier` value to be used. + public init(adapter: any RequestAdapter, retrier: any RequestRetrier) { + adapters = [adapter] + retriers = [retrier] + } + + /// Creates an instance from the arrays of `RequestAdapter` and `RequestRetrier` values. + /// + /// - Parameters: + /// - adapters: `RequestAdapter` values to be used. + /// - retriers: `RequestRetrier` values to be used. + /// - interceptors: `RequestInterceptor`s to be used. + public init(adapters: [any RequestAdapter] = [], + retriers: [any RequestRetrier] = [], + interceptors: [any RequestInterceptor] = []) { + self.adapters = adapters + interceptors + self.retriers = retriers + interceptors + } + + @preconcurrency + open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping @Sendable (Result) -> Void) { + adapt(urlRequest, for: session, using: adapters, completion: completion) + } + + private func adapt(_ urlRequest: URLRequest, + for session: Session, + using adapters: [any RequestAdapter], + completion: @escaping @Sendable (Result) -> Void) { + var pendingAdapters = adapters + + guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return } + + let adapter = pendingAdapters.removeFirst() + + adapter.adapt(urlRequest, for: session) { [pendingAdapters] result in + switch result { + case let .success(urlRequest): + self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion) + case .failure: + completion(result) + } + } + } + + @preconcurrency + open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping @Sendable (Result) -> Void) { + adapt(urlRequest, using: state, adapters: adapters, completion: completion) + } + + private func adapt(_ urlRequest: URLRequest, + using state: RequestAdapterState, + adapters: [any RequestAdapter], + completion: @escaping @Sendable (Result) -> Void) { + var pendingAdapters = adapters + + guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return } + + let adapter = pendingAdapters.removeFirst() + + adapter.adapt(urlRequest, using: state) { [pendingAdapters] result in + switch result { + case let .success(urlRequest): + self.adapt(urlRequest, using: state, adapters: pendingAdapters, completion: completion) + case .failure: + completion(result) + } + } + } + + @preconcurrency + open func retry(_ request: Request, + for session: Session, + dueTo error: any Error, + completion: @escaping @Sendable (RetryResult) -> Void) { + retry(request, for: session, dueTo: error, using: retriers, completion: completion) + } + + private func retry(_ request: Request, + for session: Session, + dueTo error: any Error, + using retriers: [any RequestRetrier], + completion: @escaping @Sendable (RetryResult) -> Void) { + var pendingRetriers = retriers + + guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return } + + let retrier = pendingRetriers.removeFirst() + + retrier.retry(request, for: session, dueTo: error) { [pendingRetriers] result in + switch result { + case .retry, .retryWithDelay, .doNotRetryWithError: + completion(result) + case .doNotRetry: + // Only continue to the next retrier if retry was not triggered and no error was encountered + self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion) + } + } + } +} + +extension RequestInterceptor where Self == Interceptor { + /// Creates an `Interceptor` using the provided `AdaptHandler` and `RetryHandler` closures. + /// + /// - Parameters: + /// - adapter: `AdapterHandler`to use to adapt the request. + /// - retrier: `RetryHandler` to use to retry the request. + /// - Returns: The `Interceptor`. + @preconcurrency + public static func interceptor(adapter: @escaping AdaptHandler, retrier: @escaping RetryHandler) -> Interceptor { + Interceptor(adaptHandler: adapter, retryHandler: retrier) + } + + /// Creates an `Interceptor` using the provided `RequestAdapter` and `RequestRetrier` instances. + /// - Parameters: + /// - adapter: `RequestAdapter` to use to adapt the request + /// - retrier: `RequestRetrier` to use to retry the request. + /// - Returns: The `Interceptor`. + @preconcurrency + public static func interceptor(adapter: any RequestAdapter, retrier: any RequestRetrier) -> Interceptor { + Interceptor(adapter: adapter, retrier: retrier) + } + + /// Creates an `Interceptor` using the provided `RequestAdapter`s, `RequestRetrier`s, and `RequestInterceptor`s. + /// - Parameters: + /// - adapters: `RequestAdapter`s to use to adapt the request. These adapters will be run until one fails. + /// - retriers: `RequestRetrier`s to use to retry the request. These retriers will be run one at a time until + /// a retry is triggered. + /// - interceptors: `RequestInterceptor`s to use to intercept the request. + /// - Returns: The `Interceptor`. + @preconcurrency + public static func interceptor(adapters: [any RequestAdapter] = [], + retriers: [any RequestRetrier] = [], + interceptors: [any RequestInterceptor] = []) -> Interceptor { + Interceptor(adapters: adapters, retriers: retriers, interceptors: interceptors) + } +} diff --git a/Pods/Alamofire/Source/Features/ResponseSerialization.swift b/Pods/Alamofire/Source/Features/ResponseSerialization.swift new file mode 100644 index 0000000..230780f --- /dev/null +++ b/Pods/Alamofire/Source/Features/ResponseSerialization.swift @@ -0,0 +1,532 @@ +// +// ResponseSerialization.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// The type to which all data response serializers must conform in order to serialize a response. +public protocol DataResponseSerializerProtocol: Sendable { + /// The type of serialized object to be created. + associatedtype SerializedObject: Sendable + + /// Serialize the response `Data` into the provided type. + /// + /// - Parameters: + /// - request: `URLRequest` which was used to perform the request, if any. + /// - response: `HTTPURLResponse` received from the server, if any. + /// - data: `Data` returned from the server, if any. + /// - error: `Error` produced by Alamofire or the underlying `URLSession` during the request. + /// + /// - Returns: The `SerializedObject`. + /// - Throws: Any `Error` produced during serialization. + func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: (any Error)?) throws -> SerializedObject +} + +/// The type to which all download response serializers must conform in order to serialize a response. +public protocol DownloadResponseSerializerProtocol: Sendable { + /// The type of serialized object to be created. + associatedtype SerializedObject: Sendable + + /// Serialize the downloaded response `Data` from disk into the provided type. + /// + /// - Parameters: + /// - request: `URLRequest` which was used to perform the request, if any. + /// - response: `HTTPURLResponse` received from the server, if any. + /// - fileURL: File `URL` to which the response data was downloaded. + /// - error: `Error` produced by Alamofire or the underlying `URLSession` during the request. + /// + /// - Returns: The `SerializedObject`. + /// - Throws: Any `Error` produced during serialization. + func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: (any Error)?) throws -> SerializedObject +} + +/// A serializer that can handle both data and download responses. +public protocol ResponseSerializer: DataResponseSerializerProtocol & DownloadResponseSerializerProtocol { + /// `DataPreprocessor` used to prepare incoming `Data` for serialization. + var dataPreprocessor: any DataPreprocessor { get } + /// `HTTPMethod`s for which empty response bodies are considered appropriate. + var emptyRequestMethods: Set { get } + /// HTTP response codes for which empty response bodies are considered appropriate. + var emptyResponseCodes: Set { get } +} + +/// Type used to preprocess `Data` before it handled by a serializer. +public protocol DataPreprocessor: Sendable { + /// Process `Data` before it's handled by a serializer. + /// - Parameter data: The raw `Data` to process. + func preprocess(_ data: Data) throws -> Data +} + +/// `DataPreprocessor` that returns passed `Data` without any transform. +public struct PassthroughPreprocessor: DataPreprocessor { + /// Creates an instance. + public init() {} + + public func preprocess(_ data: Data) throws -> Data { data } +} + +/// `DataPreprocessor` that trims Google's typical `)]}',\n` XSSI JSON header. +public struct GoogleXSSIPreprocessor: DataPreprocessor { + /// Creates an instance. + public init() {} + + public func preprocess(_ data: Data) throws -> Data { + (data.prefix(6) == Data(")]}',\n".utf8)) ? data.dropFirst(6) : data + } +} + +extension DataPreprocessor where Self == PassthroughPreprocessor { + /// Provides a `PassthroughPreprocessor` instance. + public static var passthrough: PassthroughPreprocessor { PassthroughPreprocessor() } +} + +extension DataPreprocessor where Self == GoogleXSSIPreprocessor { + /// Provides a `GoogleXSSIPreprocessor` instance. + public static var googleXSSI: GoogleXSSIPreprocessor { GoogleXSSIPreprocessor() } +} + +extension ResponseSerializer { + /// Default `DataPreprocessor`. `PassthroughPreprocessor` by default. + public static var defaultDataPreprocessor: any DataPreprocessor { PassthroughPreprocessor() } + /// Default `HTTPMethod`s for which empty response bodies are always considered appropriate. `[.head]` by default. + public static var defaultEmptyRequestMethods: Set { [.head] } + /// HTTP response codes for which empty response bodies are always considered appropriate. `[204, 205]` by default. + public static var defaultEmptyResponseCodes: Set { [204, 205] } + + public var dataPreprocessor: any DataPreprocessor { Self.defaultDataPreprocessor } + public var emptyRequestMethods: Set { Self.defaultEmptyRequestMethods } + public var emptyResponseCodes: Set { Self.defaultEmptyResponseCodes } + + /// Determines whether the `request` allows empty response bodies, if `request` exists. + /// + /// - Parameter request: `URLRequest` to evaluate. + /// + /// - Returns: `Bool` representing the outcome of the evaluation, or `nil` if `request` was `nil`. + public func requestAllowsEmptyResponseData(_ request: URLRequest?) -> Bool? { + request.flatMap(\.httpMethod) + .flatMap(HTTPMethod.init) + .map { emptyRequestMethods.contains($0) } + } + + /// Determines whether the `response` allows empty response bodies, if `response` exists. + /// + /// - Parameter response: `HTTPURLResponse` to evaluate. + /// + /// - Returns: `Bool` representing the outcome of the evaluation, or `nil` if `response` was `nil`. + public func responseAllowsEmptyResponseData(_ response: HTTPURLResponse?) -> Bool? { + response.map(\.statusCode) + .map { emptyResponseCodes.contains($0) } + } + + /// Determines whether `request` and `response` allow empty response bodies. + /// + /// - Parameters: + /// - request: `URLRequest` to evaluate. + /// - response: `HTTPURLResponse` to evaluate. + /// + /// - Returns: `true` if `request` or `response` allow empty bodies, `false` otherwise. + public func emptyResponseAllowed(forRequest request: URLRequest?, response: HTTPURLResponse?) -> Bool { + (requestAllowsEmptyResponseData(request) == true) || (responseAllowsEmptyResponseData(response) == true) + } +} + +/// By default, any serializer declared to conform to both types will get file serialization for free, as it just feeds +/// the data read from disk into the data response serializer. +extension DownloadResponseSerializerProtocol where Self: DataResponseSerializerProtocol { + public func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: (any Error)?) throws -> Self.SerializedObject { + guard error == nil else { throw error! } + + guard let fileURL else { + throw AFError.responseSerializationFailed(reason: .inputFileNil) + } + + let data: Data + do { + data = try Data(contentsOf: fileURL) + } catch { + throw AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL)) + } + + do { + return try serialize(request: request, response: response, data: data, error: error) + } catch { + throw error + } + } +} + +// MARK: - URL + +/// A `DownloadResponseSerializerProtocol` that performs only `Error` checking and ensures that a downloaded `fileURL` +/// is present. +public struct URLResponseSerializer: DownloadResponseSerializerProtocol { + /// Creates an instance. + public init() {} + + public func serializeDownload(request: URLRequest?, + response: HTTPURLResponse?, + fileURL: URL?, + error: (any Error)?) throws -> URL { + guard error == nil else { throw error! } + + guard let url = fileURL else { + throw AFError.responseSerializationFailed(reason: .inputFileNil) + } + + return url + } +} + +extension DownloadResponseSerializerProtocol where Self == URLResponseSerializer { + /// Provides a `URLResponseSerializer` instance. + public static var url: URLResponseSerializer { URLResponseSerializer() } +} + +// MARK: - Data + +/// A `ResponseSerializer` that performs minimal response checking and returns any response `Data` as-is. By default, a +/// request returning `nil` or no data is considered an error. However, if the request has an `HTTPMethod` or the +/// response has an HTTP status code valid for empty responses, then an empty `Data` value is returned. +public final class DataResponseSerializer: ResponseSerializer { + public let dataPreprocessor: any DataPreprocessor + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + + /// Creates a `DataResponseSerializer` using the provided parameters. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + public init(dataPreprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) { + self.dataPreprocessor = dataPreprocessor + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: (any Error)?) throws -> Data { + guard error == nil else { throw error! } + + guard var data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + return Data() + } + + data = try dataPreprocessor.preprocess(data) + + return data + } +} + +extension ResponseSerializer where Self == DataResponseSerializer { + /// Provides a default `DataResponseSerializer` instance. + public static var data: DataResponseSerializer { DataResponseSerializer() } + + /// Creates a `DataResponseSerializer` using the provided parameters. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + /// + /// - Returns: The `DataResponseSerializer`. + public static func data(dataPreprocessor: any DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = DataResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DataResponseSerializer.defaultEmptyRequestMethods) -> DataResponseSerializer { + DataResponseSerializer(dataPreprocessor: dataPreprocessor, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods) + } +} + +// MARK: - String + +/// A `ResponseSerializer` that decodes the response data as a `String`. By default, a request returning `nil` or no +/// data is considered an error. However, if the request has an `HTTPMethod` or the response has an HTTP status code +/// valid for empty responses, then an empty `String` is returned. +public final class StringResponseSerializer: ResponseSerializer { + public let dataPreprocessor: any DataPreprocessor + /// Optional string encoding used to validate the response. + public let encoding: String.Encoding? + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + + /// Creates an instance with the provided values. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - encoding: A string encoding. Defaults to `nil`, in which case the encoding will be determined + /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + public init(dataPreprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) { + self.dataPreprocessor = dataPreprocessor + self.encoding = encoding + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: (any Error)?) throws -> String { + guard error == nil else { throw error! } + + guard var data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + return "" + } + + data = try dataPreprocessor.preprocess(data) + + var convertedEncoding = encoding + + if let encodingName = response?.textEncodingName, convertedEncoding == nil { + convertedEncoding = String.Encoding(ianaCharsetName: encodingName) + } + + let actualEncoding = convertedEncoding ?? .isoLatin1 + + guard let string = String(data: data, encoding: actualEncoding) else { + throw AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding)) + } + + return string + } +} + +extension ResponseSerializer where Self == StringResponseSerializer { + /// Provides a default `StringResponseSerializer` instance. + public static var string: StringResponseSerializer { StringResponseSerializer() } + + /// Creates a `StringResponseSerializer` with the provided values. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - encoding: A string encoding. Defaults to `nil`, in which case the encoding will be determined + /// from the server response, falling back to the default HTTP character set, `ISO-8859-1`. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + /// + /// - Returns: The `StringResponseSerializer`. + public static func string(dataPreprocessor: any DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor, + encoding: String.Encoding? = nil, + emptyResponseCodes: Set = StringResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = StringResponseSerializer.defaultEmptyRequestMethods) -> StringResponseSerializer { + StringResponseSerializer(dataPreprocessor: dataPreprocessor, + encoding: encoding, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods) + } +} + +// MARK: - JSON + +/// A `ResponseSerializer` that decodes the response data using `JSONSerialization`. By default, a request returning +/// `nil` or no data is considered an error. However, if the request has an `HTTPMethod` or the response has an +/// HTTP status code valid for empty responses, then an `NSNull` value is returned. +/// +/// - Note: This serializer is deprecated and should not be used. Instead, create concrete types conforming to +/// `Decodable` and use a `DecodableResponseSerializer`. +@available(*, deprecated, message: "JSONResponseSerializer deprecated and will be removed in Alamofire 6. Use DecodableResponseSerializer instead.") +public final class JSONResponseSerializer: ResponseSerializer { + public let dataPreprocessor: any DataPreprocessor + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + /// `JSONSerialization.ReadingOptions` used when serializing a response. + public let options: JSONSerialization.ReadingOptions + + /// Creates an instance with the provided values. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + /// - options: The options to use. `.allowFragments` by default. + public init(dataPreprocessor: any DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor, + emptyResponseCodes: Set = JSONResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = JSONResponseSerializer.defaultEmptyRequestMethods, + options: JSONSerialization.ReadingOptions = .allowFragments) { + self.dataPreprocessor = dataPreprocessor + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + self.options = options + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: (any Error)?) throws -> Any { + guard error == nil else { throw error! } + + guard var data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + return NSNull() + } + + data = try dataPreprocessor.preprocess(data) + + do { + return try JSONSerialization.jsonObject(with: data, options: options) + } catch { + throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)) + } + } +} + +// MARK: - Empty + +/// Protocol representing an empty response. Use `T.emptyValue()` to get an instance. +public protocol EmptyResponse: Sendable { + /// Empty value for the conforming type. + /// + /// - Returns: Value of `Self` to use for empty values. + static func emptyValue() -> Self +} + +/// Type representing an empty value. Use `Empty.value` to get the static instance. +public struct Empty: Codable, Sendable { + /// Static `Empty` instance used for all `Empty` responses. + public static let value = Empty() +} + +extension Empty: EmptyResponse { + public static func emptyValue() -> Empty { + value + } +} + +// MARK: - DataDecoder Protocol + +/// Any type which can decode `Data` into a `Decodable` type. +public protocol DataDecoder: Sendable { + /// Decode `Data` into the provided type. + /// + /// - Parameters: + /// - type: The `Type` to be decoded. + /// - data: The `Data` to be decoded. + /// + /// - Returns: The decoded value of type `D`. + /// - Throws: Any error that occurs during decode. + func decode(_ type: D.Type, from data: Data) throws -> D +} + +/// `JSONDecoder` automatically conforms to `DataDecoder`. +extension JSONDecoder: DataDecoder {} +/// `PropertyListDecoder` automatically conforms to `DataDecoder`. +extension PropertyListDecoder: DataDecoder {} + +// MARK: - Decodable + +/// A `ResponseSerializer` that decodes the response data as a `Decodable` value using any decoder that conforms to +/// `DataDecoder`. By default, this is an instance of `JSONDecoder`. +/// +/// - Note: A request returning `nil` or no data is considered an error. However, if the request has an `HTTPMethod` or +/// the response has an HTTP status code valid for empty responses then an empty value will be returned. If the +/// decoded type conforms to `EmptyResponse`, the type's `emptyValue()` will be returned. If the decoded type is +/// `Empty`, the `.value` instance is returned. If the decoded type *does not* conform to `EmptyResponse` and +/// isn't `Empty`, an error will be produced. +/// +/// - Note: `JSONDecoder` and `PropertyListDecoder` are not `Sendable` on Apple platforms until macOS 13+ or iOS 16+, so +/// instances passed to a serializer should not be used outside of the serializer. Additionally, ensure a new +/// serializer is created for each request, do not use a single, shared serializer, so as to ensure separate +/// decoder instances. +public final class DecodableResponseSerializer: ResponseSerializer where T: Sendable { + public let dataPreprocessor: any DataPreprocessor + /// The `DataDecoder` instance used to decode responses. + public let decoder: any DataDecoder + public let emptyResponseCodes: Set + public let emptyRequestMethods: Set + + /// Creates an instance using the values provided. + /// + /// - Parameters: + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - decoder: The `DataDecoder`. `JSONDecoder()` by default. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + public init(dataPreprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) { + self.dataPreprocessor = dataPreprocessor + self.decoder = decoder + self.emptyResponseCodes = emptyResponseCodes + self.emptyRequestMethods = emptyRequestMethods + } + + public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: (any Error)?) throws -> T { + guard error == nil else { throw error! } + + guard var data, !data.isEmpty else { + guard emptyResponseAllowed(forRequest: request, response: response) else { + throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) + } + + guard let emptyResponseType = T.self as? any EmptyResponse.Type, let emptyValue = emptyResponseType.emptyValue() as? T else { + throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)")) + } + + return emptyValue + } + + data = try dataPreprocessor.preprocess(data) + + do { + return try decoder.decode(T.self, from: data) + } catch { + throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) + } + } +} + +extension ResponseSerializer { + /// Creates a `DecodableResponseSerializer` using the values provided. + /// + /// - Parameters: + /// - type: `Decodable` type to decode from response data. + /// - dataPreprocessor: `DataPreprocessor` used to prepare the received `Data` for serialization. + /// - decoder: The `DataDecoder`. `JSONDecoder()` by default. + /// - emptyResponseCodes: The HTTP response codes for which empty responses are allowed. `[204, 205]` by default. + /// - emptyRequestMethods: The HTTP request methods for which empty responses are allowed. `[.head]` by default. + /// + /// - Returns: The `DecodableResponseSerializer`. + public static func decodable(of type: T.Type, + dataPreprocessor: any DataPreprocessor = DecodableResponseSerializer.defaultDataPreprocessor, + decoder: any DataDecoder = JSONDecoder(), + emptyResponseCodes: Set = DecodableResponseSerializer.defaultEmptyResponseCodes, + emptyRequestMethods: Set = DecodableResponseSerializer.defaultEmptyRequestMethods) -> DecodableResponseSerializer where Self == DecodableResponseSerializer { + DecodableResponseSerializer(dataPreprocessor: dataPreprocessor, + decoder: decoder, + emptyResponseCodes: emptyResponseCodes, + emptyRequestMethods: emptyRequestMethods) + } +} diff --git a/Pods/Alamofire/Source/Features/RetryPolicy.swift b/Pods/Alamofire/Source/Features/RetryPolicy.swift new file mode 100644 index 0000000..972788a --- /dev/null +++ b/Pods/Alamofire/Source/Features/RetryPolicy.swift @@ -0,0 +1,430 @@ +// +// RetryPolicy.swift +// +// Copyright (c) 2019-2020 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// A retry policy that retries requests using an exponential backoff for allowed HTTP methods and HTTP status codes +/// as well as certain types of networking errors. +open class RetryPolicy: @unchecked Sendable, RequestInterceptor { + /// The default retry limit for retry policies. + public static let defaultRetryLimit: UInt = 2 + + /// The default exponential backoff base for retry policies (must be a minimum of 2). + public static let defaultExponentialBackoffBase: UInt = 2 + + /// The default exponential backoff scale for retry policies. + public static let defaultExponentialBackoffScale: Double = 0.5 + + /// The default HTTP methods to retry. + /// See [RFC 2616 - Section 9.1.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) for more information. + public static let defaultRetryableHTTPMethods: Set = [.delete, // [Delete](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7) - not always idempotent + .get, // [GET](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3) - generally idempotent + .head, // [HEAD](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4) - generally idempotent + .options, // [OPTIONS](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2) - inherently idempotent + .put, // [PUT](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6) - not always idempotent + .trace // [TRACE](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8) - inherently idempotent + ] + + /// The default HTTP status codes to retry. + /// See [RFC 2616 - Section 10](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10) for more information. + public static let defaultRetryableHTTPStatusCodes: Set = [408, // [Request Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.9) + 500, // [Internal Server Error](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1) + 502, // [Bad Gateway](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.3) + 503, // [Service Unavailable](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4) + 504 // [Gateway Timeout](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.5) + ] + + /// The default URL error codes to retry. + public static let defaultRetryableURLErrorCodes: Set = [ // [Security] App Transport Security disallowed a connection because there is no secure network connection. + // - [Disabled] ATS settings do not change at runtime. + // .appTransportSecurityRequiresSecureConnection, + + // [System] An app or app extension attempted to connect to a background session that is already connected to a + // process. + // - [Enabled] The other process could release the background session. + .backgroundSessionInUseByAnotherProcess, + + // [System] The shared container identifier of the URL session configuration is needed but has not been set. + // - [Disabled] Cannot change at runtime. + // .backgroundSessionRequiresSharedContainer, + + // [System] The app is suspended or exits while a background data task is processing. + // - [Enabled] App can be foregrounded or launched to recover. + .backgroundSessionWasDisconnected, + + // [Network] The URL Loading system received bad data from the server. + // - [Enabled] Server could return valid data when retrying. + .badServerResponse, + + // [Resource] A malformed URL prevented a URL request from being initiated. + // - [Disabled] URL was most likely constructed incorrectly. + // .badURL, + + // [System] A connection was attempted while a phone call is active on a network that does not support + // simultaneous phone and data communication (EDGE or GPRS). + // - [Enabled] Phone call could be ended to allow request to recover. + .callIsActive, + + // [Client] An asynchronous load has been canceled. + // - [Disabled] Request was cancelled by the client. + // .cancelled, + + // [File System] A download task couldn’t close the downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotCloseFile, + + // [Network] An attempt to connect to a host failed. + // - [Enabled] Server or DNS lookup could recover during retry. + .cannotConnectToHost, + + // [File System] A download task couldn’t create the downloaded file on disk because of an I/O failure. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotCreateFile, + + // [Data] Content data received during a connection request had an unknown content encoding. + // - [Disabled] Server is unlikely to modify the content encoding during a retry. + // .cannotDecodeContentData, + + // [Data] Content data received during a connection request could not be decoded for a known content encoding. + // - [Disabled] Server is unlikely to modify the content encoding during a retry. + // .cannotDecodeRawData, + + // [Network] The host name for a URL could not be resolved. + // - [Enabled] Server or DNS lookup could recover during retry. + .cannotFindHost, + + // [Network] A request to load an item only from the cache could not be satisfied. + // - [Enabled] Cache could be populated during a retry. + .cannotLoadFromNetwork, + + // [File System] A download task was unable to move a downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotMoveFile, + + // [File System] A download task was unable to open the downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotOpenFile, + + // [Data] A task could not parse a response. + // - [Disabled] Invalid response is unlikely to recover with retry. + // .cannotParseResponse, + + // [File System] A download task was unable to remove a downloaded file from disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotRemoveFile, + + // [File System] A download task was unable to write to the downloaded file on disk. + // - [Disabled] File system error is unlikely to recover with retry. + // .cannotWriteToFile, + + // [Security] A client certificate was rejected. + // - [Disabled] Client certificate is unlikely to change with retry. + // .clientCertificateRejected, + + // [Security] A client certificate was required to authenticate an SSL connection during a request. + // - [Disabled] Client certificate is unlikely to be provided with retry. + // .clientCertificateRequired, + + // [Data] The length of the resource data exceeds the maximum allowed. + // - [Disabled] Resource will likely still exceed the length maximum on retry. + // .dataLengthExceedsMaximum, + + // [System] The cellular network disallowed a connection. + // - [Enabled] WiFi connection could be established during retry. + .dataNotAllowed, + + // [Network] The host address could not be found via DNS lookup. + // - [Enabled] DNS lookup could succeed during retry. + .dnsLookupFailed, + + // [Data] A download task failed to decode an encoded file during the download. + // - [Enabled] Server could correct the decoding issue with retry. + .downloadDecodingFailedMidStream, + + // [Data] A download task failed to decode an encoded file after downloading. + // - [Enabled] Server could correct the decoding issue with retry. + .downloadDecodingFailedToComplete, + + // [File System] A file does not exist. + // - [Disabled] File system error is unlikely to recover with retry. + // .fileDoesNotExist, + + // [File System] A request for an FTP file resulted in the server responding that the file is not a plain file, + // but a directory. + // - [Disabled] FTP directory is not likely to change to a file during a retry. + // .fileIsDirectory, + + // [Network] A redirect loop has been detected or the threshold for number of allowable redirects has been + // exceeded (currently 16). + // - [Disabled] The redirect loop is unlikely to be resolved within the retry window. + // .httpTooManyRedirects, + + // [System] The attempted connection required activating a data context while roaming, but international roaming + // is disabled. + // - [Enabled] WiFi connection could be established during retry. + .internationalRoamingOff, + + // [Connectivity] A client or server connection was severed in the middle of an in-progress load. + // - [Enabled] A network connection could be established during retry. + .networkConnectionLost, + + // [File System] A resource couldn’t be read because of insufficient permissions. + // - [Disabled] Permissions are unlikely to be granted during retry. + // .noPermissionsToReadFile, + + // [Connectivity] A network resource was requested, but an internet connection has not been established and + // cannot be established automatically. + // - [Enabled] A network connection could be established during retry. + .notConnectedToInternet, + + // [Resource] A redirect was specified by way of server response code, but the server did not accompany this + // code with a redirect URL. + // - [Disabled] The redirect URL is unlikely to be supplied during a retry. + // .redirectToNonExistentLocation, + + // [Client] A body stream is needed but the client did not provide one. + // - [Disabled] The client will be unlikely to supply a body stream during retry. + // .requestBodyStreamExhausted, + + // [Resource] A requested resource couldn’t be retrieved. + // - [Disabled] The resource is unlikely to become available during the retry window. + // .resourceUnavailable, + + // [Security] An attempt to establish a secure connection failed for reasons that can’t be expressed more + // specifically. + // - [Enabled] The secure connection could be established during a retry given the lack of specificity + // provided by the error. + .secureConnectionFailed, + + // [Security] A server certificate had a date which indicates it has expired, or is not yet valid. + // - [Enabled] The server certificate could become valid within the retry window. + .serverCertificateHasBadDate, + + // [Security] A server certificate was not signed by any root server. + // - [Disabled] The server certificate is unlikely to change during the retry window. + // .serverCertificateHasUnknownRoot, + + // [Security] A server certificate is not yet valid. + // - [Enabled] The server certificate could become valid within the retry window. + .serverCertificateNotYetValid, + + // [Security] A server certificate was signed by a root server that isn’t trusted. + // - [Disabled] The server certificate is unlikely to become trusted within the retry window. + // .serverCertificateUntrusted, + + // [Network] An asynchronous operation timed out. + // - [Enabled] The request timed out for an unknown reason and should be retried. + .timedOut + + // [System] The URL Loading System encountered an error that it can’t interpret. + // - [Disabled] The error could not be interpreted and is unlikely to be recovered from during a retry. + // .unknown, + + // [Resource] A properly formed URL couldn’t be handled by the framework. + // - [Disabled] The URL is unlikely to change during a retry. + // .unsupportedURL, + + // [Client] Authentication is required to access a resource. + // - [Disabled] The user authentication is unlikely to be provided by retrying. + // .userAuthenticationRequired, + + // [Client] An asynchronous request for authentication has been canceled by the user. + // - [Disabled] The user cancelled authentication and explicitly took action to not retry. + // .userCancelledAuthentication, + + // [Resource] A server reported that a URL has a non-zero content length, but terminated the network connection + // gracefully without sending any data. + // - [Disabled] The server is unlikely to provide data during the retry window. + // .zeroByteResource, + ] + + /// The total number of times the request is allowed to be retried. + public let retryLimit: UInt + + /// The base of the exponential backoff policy (should always be greater than or equal to 2). + public let exponentialBackoffBase: UInt + + /// The scale of the exponential backoff. + public let exponentialBackoffScale: Double + + /// The HTTP methods that are allowed to be retried. + public let retryableHTTPMethods: Set + + /// The HTTP status codes that are automatically retried by the policy. + public let retryableHTTPStatusCodes: Set + + /// The URL error codes that are automatically retried by the policy. + public let retryableURLErrorCodes: Set + + /// Creates a `RetryPolicy` from the specified parameters. + /// + /// - Parameters: + /// - retryLimit: The total number of times the request is allowed to be retried. `2` by default. + /// - exponentialBackoffBase: The base of the exponential backoff policy. `2` by default. + /// - exponentialBackoffScale: The scale of the exponential backoff. `0.5` by default. + /// - retryableHTTPMethods: The HTTP methods that are allowed to be retried. + /// `RetryPolicy.defaultRetryableHTTPMethods` by default. + /// - retryableHTTPStatusCodes: The HTTP status codes that are automatically retried by the policy. + /// `RetryPolicy.defaultRetryableHTTPStatusCodes` by default. + /// - retryableURLErrorCodes: The URL error codes that are automatically retried by the policy. + /// `RetryPolicy.defaultRetryableURLErrorCodes` by default. + public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit, + exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, + exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, + retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods, + retryableHTTPStatusCodes: Set = RetryPolicy.defaultRetryableHTTPStatusCodes, + retryableURLErrorCodes: Set = RetryPolicy.defaultRetryableURLErrorCodes) { + precondition(exponentialBackoffBase >= 2, "The `exponentialBackoffBase` must be a minimum of 2.") + + self.retryLimit = retryLimit + self.exponentialBackoffBase = exponentialBackoffBase + self.exponentialBackoffScale = exponentialBackoffScale + self.retryableHTTPMethods = retryableHTTPMethods + self.retryableHTTPStatusCodes = retryableHTTPStatusCodes + self.retryableURLErrorCodes = retryableURLErrorCodes + } + + open func retry(_ request: Request, + for session: Session, + dueTo error: any Error, + completion: @escaping (RetryResult) -> Void) { + if request.retryCount < retryLimit, shouldRetry(request: request, dueTo: error) { + completion(.retryWithDelay(pow(Double(exponentialBackoffBase), Double(request.retryCount)) * exponentialBackoffScale)) + } else { + completion(.doNotRetry) + } + } + + /// Determines whether or not to retry the provided `Request`. + /// + /// - Parameters: + /// - request: `Request` that failed due to the provided `Error`. + /// - error: `Error` encountered while executing the `Request`. + /// + /// - Returns: `Bool` determining whether or not to retry the `Request`. + open func shouldRetry(request: Request, dueTo error: any Error) -> Bool { + guard let httpMethod = request.request?.method, retryableHTTPMethods.contains(httpMethod) else { return false } + + if let statusCode = request.response?.statusCode, retryableHTTPStatusCodes.contains(statusCode) { + return true + } else { + let errorCode = (error as? URLError)?.code + let afErrorCode = (error.asAFError?.underlyingError as? URLError)?.code + + guard let code = errorCode ?? afErrorCode else { return false } + + return retryableURLErrorCodes.contains(code) + } + } +} + +extension RequestInterceptor where Self == RetryPolicy { + /// Provides a default `RetryPolicy` instance. + public static var retryPolicy: RetryPolicy { RetryPolicy() } + + /// Creates an `RetryPolicy` from the specified parameters. + /// + /// - Parameters: + /// - retryLimit: The total number of times the request is allowed to be retried. `2` by default. + /// - exponentialBackoffBase: The base of the exponential backoff policy. `2` by default. + /// - exponentialBackoffScale: The scale of the exponential backoff. `0.5` by default. + /// - retryableHTTPMethods: The HTTP methods that are allowed to be retried. + /// `RetryPolicy.defaultRetryableHTTPMethods` by default. + /// - retryableHTTPStatusCodes: The HTTP status codes that are automatically retried by the policy. + /// `RetryPolicy.defaultRetryableHTTPStatusCodes` by default. + /// - retryableURLErrorCodes: The URL error codes that are automatically retried by the policy. + /// `RetryPolicy.defaultRetryableURLErrorCodes` by default. + /// + /// - Returns: The `RetryPolicy` + public static func retryPolicy(retryLimit: UInt = RetryPolicy.defaultRetryLimit, + exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, + exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, + retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods, + retryableHTTPStatusCodes: Set = RetryPolicy.defaultRetryableHTTPStatusCodes, + retryableURLErrorCodes: Set = RetryPolicy.defaultRetryableURLErrorCodes) -> RetryPolicy { + RetryPolicy(retryLimit: retryLimit, + exponentialBackoffBase: exponentialBackoffBase, + exponentialBackoffScale: exponentialBackoffScale, + retryableHTTPMethods: retryableHTTPMethods, + retryableHTTPStatusCodes: retryableHTTPStatusCodes, + retryableURLErrorCodes: retryableURLErrorCodes) + } +} + +// MARK: - + +/// A retry policy that automatically retries idempotent requests for network connection lost errors. For more +/// information about retrying network connection lost errors, please refer to Apple's +/// [technical document](https://developer.apple.com/library/content/qa/qa1941/_index.html). +open class ConnectionLostRetryPolicy: RetryPolicy, @unchecked Sendable { + /// Creates a `ConnectionLostRetryPolicy` instance from the specified parameters. + /// + /// - Parameters: + /// - retryLimit: The total number of times the request is allowed to be retried. + /// `RetryPolicy.defaultRetryLimit` by default. + /// - exponentialBackoffBase: The base of the exponential backoff policy. + /// `RetryPolicy.defaultExponentialBackoffBase` by default. + /// - exponentialBackoffScale: The scale of the exponential backoff. + /// `RetryPolicy.defaultExponentialBackoffScale` by default. + /// - retryableHTTPMethods: The idempotent http methods to retry. + /// `RetryPolicy.defaultRetryableHTTPMethods` by default. + public init(retryLimit: UInt = RetryPolicy.defaultRetryLimit, + exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, + exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, + retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods) { + super.init(retryLimit: retryLimit, + exponentialBackoffBase: exponentialBackoffBase, + exponentialBackoffScale: exponentialBackoffScale, + retryableHTTPMethods: retryableHTTPMethods, + retryableHTTPStatusCodes: [], + retryableURLErrorCodes: [.networkConnectionLost]) + } +} + +extension RequestInterceptor where Self == ConnectionLostRetryPolicy { + /// Provides a default `ConnectionLostRetryPolicy` instance. + public static var connectionLostRetryPolicy: ConnectionLostRetryPolicy { ConnectionLostRetryPolicy() } + + /// Creates a `ConnectionLostRetryPolicy` instance from the specified parameters. + /// + /// - Parameters: + /// - retryLimit: The total number of times the request is allowed to be retried. + /// `RetryPolicy.defaultRetryLimit` by default. + /// - exponentialBackoffBase: The base of the exponential backoff policy. + /// `RetryPolicy.defaultExponentialBackoffBase` by default. + /// - exponentialBackoffScale: The scale of the exponential backoff. + /// `RetryPolicy.defaultExponentialBackoffScale` by default. + /// - retryableHTTPMethods: The idempotent http methods to retry. + /// + /// - Returns: The `ConnectionLostRetryPolicy`. + public static func connectionLostRetryPolicy(retryLimit: UInt = RetryPolicy.defaultRetryLimit, + exponentialBackoffBase: UInt = RetryPolicy.defaultExponentialBackoffBase, + exponentialBackoffScale: Double = RetryPolicy.defaultExponentialBackoffScale, + retryableHTTPMethods: Set = RetryPolicy.defaultRetryableHTTPMethods) -> ConnectionLostRetryPolicy { + ConnectionLostRetryPolicy(retryLimit: retryLimit, + exponentialBackoffBase: exponentialBackoffBase, + exponentialBackoffScale: exponentialBackoffScale, + retryableHTTPMethods: retryableHTTPMethods) + } +} diff --git a/Pods/Alamofire/Source/Features/ServerTrustEvaluation.swift b/Pods/Alamofire/Source/Features/ServerTrustEvaluation.swift new file mode 100644 index 0000000..e290c36 --- /dev/null +++ b/Pods/Alamofire/Source/Features/ServerTrustEvaluation.swift @@ -0,0 +1,723 @@ +// +// ServerTrustEvaluation.swift +// +// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation +#if canImport(Security) +@preconcurrency import Security +#endif + +/// Responsible for managing the mapping of `ServerTrustEvaluating` values to given hosts. +open class ServerTrustManager: @unchecked Sendable { + /// Determines whether all hosts for this `ServerTrustManager` must be evaluated. `true` by default. + public let allHostsMustBeEvaluated: Bool + + /// The dictionary of policies mapped to a particular host. + public let evaluators: [String: any ServerTrustEvaluating] + + /// Initializes the `ServerTrustManager` instance with the given evaluators. + /// + /// Since different servers and web services can have different leaf certificates, intermediate and even root + /// certificates, it is important to have the flexibility to specify evaluation policies on a per host basis. This + /// allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key + /// pinning for host3 and disabling evaluation for host4. + /// + /// - Parameters: + /// - allHostsMustBeEvaluated: The value determining whether all hosts for this instance must be evaluated. `true` + /// by default. + /// - evaluators: A dictionary of evaluators mapped to hosts. + public init(allHostsMustBeEvaluated: Bool = true, evaluators: [String: any ServerTrustEvaluating]) { + self.allHostsMustBeEvaluated = allHostsMustBeEvaluated + self.evaluators = evaluators + } + + #if canImport(Security) + /// Returns the `ServerTrustEvaluating` value for the given host, if one is set. + /// + /// By default, this method will return the policy that perfectly matches the given host. Subclasses could override + /// this method and implement more complex mapping implementations such as wildcards. + /// + /// - Parameter host: The host to use when searching for a matching policy. + /// + /// - Returns: The `ServerTrustEvaluating` value for the given host if found, `nil` otherwise. + /// - Throws: `AFError.serverTrustEvaluationFailed` if `allHostsMustBeEvaluated` is `true` and no matching + /// evaluators are found. + open func serverTrustEvaluator(forHost host: String) throws -> (any ServerTrustEvaluating)? { + guard let evaluator = evaluators[host] else { + if allHostsMustBeEvaluated { + throw AFError.serverTrustEvaluationFailed(reason: .noRequiredEvaluator(host: host)) + } + + return nil + } + + return evaluator + } + #endif +} + +/// A protocol describing the API used to evaluate server trusts. +public protocol ServerTrustEvaluating: Sendable { + #if !canImport(Security) + // Implement this once other platforms have API for evaluating server trusts. + #else + /// Evaluates the given `SecTrust` value for the given `host`. + /// + /// - Parameters: + /// - trust: The `SecTrust` value to evaluate. + /// - host: The host for which to evaluate the `SecTrust` value. + /// + /// - Returns: A `Bool` indicating whether the evaluator considers the `SecTrust` value valid for `host`. + func evaluate(_ trust: SecTrust, forHost host: String) throws + #endif +} + +// MARK: - Server Trust Evaluators + +#if canImport(Security) +/// An evaluator which uses the default server trust evaluation while allowing you to control whether to validate the +/// host provided by the challenge. Applications are encouraged to always validate the host in production environments +/// to guarantee the validity of the server's certificate chain. +public final class DefaultTrustEvaluator: ServerTrustEvaluating { + private let validateHost: Bool + + /// Creates a `DefaultTrustEvaluator`. + /// + /// - Parameter validateHost: Determines whether or not the evaluator should validate the host. `true` by default. + public init(validateHost: Bool = true) { + self.validateHost = validateHost + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + if validateHost { + try trust.af.performValidation(forHost: host) + } + + try trust.af.performDefaultValidation(forHost: host) + } +} + +/// An evaluator which Uses the default and revoked server trust evaluations allowing you to control whether to validate +/// the host provided by the challenge as well as specify the revocation flags for testing for revoked certificates. +/// Apple platforms did not start testing for revoked certificates automatically until iOS 10.1, macOS 10.12 and tvOS +/// 10.1 which is demonstrated in our TLS tests. Applications are encouraged to always validate the host in production +/// environments to guarantee the validity of the server's certificate chain. +public final class RevocationTrustEvaluator: ServerTrustEvaluating { + /// Represents the options to be use when evaluating the status of a certificate. + /// Only Revocation Policy Constants are valid, and can be found in [Apple's documentation](https://developer.apple.com/documentation/security/certificate_key_and_trust_services/policies/1563600-revocation_policy_constants). + public struct Options: OptionSet, Sendable { + /// Perform revocation checking using the CRL (Certification Revocation List) method. + public static let crl = Options(rawValue: kSecRevocationCRLMethod) + /// Consult only locally cached replies; do not use network access. + public static let networkAccessDisabled = Options(rawValue: kSecRevocationNetworkAccessDisabled) + /// Perform revocation checking using OCSP (Online Certificate Status Protocol). + public static let ocsp = Options(rawValue: kSecRevocationOCSPMethod) + /// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred. + public static let preferCRL = Options(rawValue: kSecRevocationPreferCRL) + /// Require a positive response to pass the policy. If the flag is not set, revocation checking is done on a + /// "best attempt" basis, where failure to reach the server is not considered fatal. + public static let requirePositiveResponse = Options(rawValue: kSecRevocationRequirePositiveResponse) + /// Perform either OCSP or CRL checking. The checking is performed according to the method(s) specified in the + /// certificate and the value of `preferCRL`. + public static let any = Options(rawValue: kSecRevocationUseAnyAvailableMethod) + + /// The raw value of the option. + public let rawValue: CFOptionFlags + + /// Creates an `Options` value with the given `CFOptionFlags`. + /// + /// - Parameter rawValue: The `CFOptionFlags` value to initialize with. + public init(rawValue: CFOptionFlags) { + self.rawValue = rawValue + } + } + + private let performDefaultValidation: Bool + private let validateHost: Bool + private let options: Options + + /// Creates a `RevocationTrustEvaluator` using the provided parameters. + /// + /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use + /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. + /// + /// - Parameters: + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to + /// performing the default evaluation, even if `performDefaultValidation` is `false`. + /// `true` by default. + /// - options: The `Options` to use to check the revocation status of the certificate. `.any` by + /// default. + public init(performDefaultValidation: Bool = true, validateHost: Bool = true, options: Options = .any) { + self.performDefaultValidation = performDefaultValidation + self.validateHost = validateHost + self.options = options + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + if performDefaultValidation { + try trust.af.performDefaultValidation(forHost: host) + } + + if validateHost { + try trust.af.performValidation(forHost: host) + } + + if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, visionOS 1, *) { + try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options)) + } else { + try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in + AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options)) + } + } + } +} + +extension ServerTrustEvaluating where Self == RevocationTrustEvaluator { + /// Provides a default `RevocationTrustEvaluator` instance. + public static var revocationChecking: RevocationTrustEvaluator { RevocationTrustEvaluator() } + + /// Creates a `RevocationTrustEvaluator` using the provided parameters. + /// + /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use + /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. + /// + /// - Parameters: + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition + /// to performing the default evaluation, even if `performDefaultValidation` is + /// `false`. `true` by default. + /// - options: The `Options` to use to check the revocation status of the certificate. `.any` + /// by default. + /// - Returns: The `RevocationTrustEvaluator`. + public static func revocationChecking(performDefaultValidation: Bool = true, + validateHost: Bool = true, + options: RevocationTrustEvaluator.Options = .any) -> RevocationTrustEvaluator { + RevocationTrustEvaluator(performDefaultValidation: performDefaultValidation, + validateHost: validateHost, + options: options) + } +} + +/// Uses the pinned certificates to validate the server trust. The server trust is considered valid if one of the pinned +/// certificates match one of the server certificates. By validating both the certificate chain and host, certificate +/// pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks. +/// Applications are encouraged to always validate the host and require a valid certificate chain in production +/// environments. +public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating { + private let certificates: [SecCertificate] + private let acceptSelfSignedCertificates: Bool + private let performDefaultValidation: Bool + private let validateHost: Bool + + /// Creates a `PinnedCertificatesTrustEvaluator` from the provided parameters. + /// + /// - Parameters: + /// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der` + /// certificates in `Bundle.main` by default. + /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing + /// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE + /// FALSE IN PRODUCTION! + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition + /// to performing the default evaluation, even if `performDefaultValidation` is + /// `false`. `true` by default. + public init(certificates: [SecCertificate] = Bundle.main.af.certificates, + acceptSelfSignedCertificates: Bool = false, + performDefaultValidation: Bool = true, + validateHost: Bool = true) { + self.certificates = certificates + self.acceptSelfSignedCertificates = acceptSelfSignedCertificates + self.performDefaultValidation = performDefaultValidation + self.validateHost = validateHost + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + guard !certificates.isEmpty else { + throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound) + } + + if acceptSelfSignedCertificates { + try trust.af.setAnchorCertificates(certificates) + } + + if performDefaultValidation { + try trust.af.performDefaultValidation(forHost: host) + } + + if validateHost { + try trust.af.performValidation(forHost: host) + } + + let serverCertificatesData = Set(trust.af.certificateData) + let pinnedCertificatesData = Set(certificates.af.data) + let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData) + if !pinnedCertificatesInServerData { + throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host, + trust: trust, + pinnedCertificates: certificates, + serverCertificates: trust.af.certificates)) + } + } +} + +extension ServerTrustEvaluating where Self == PinnedCertificatesTrustEvaluator { + /// Provides a default `PinnedCertificatesTrustEvaluator` instance. + public static var pinnedCertificates: PinnedCertificatesTrustEvaluator { PinnedCertificatesTrustEvaluator() } + + /// Creates a `PinnedCertificatesTrustEvaluator` using the provided parameters. + /// + /// - Parameters: + /// - certificates: The certificates to use to evaluate the trust. All `cer`, `crt`, and `der` + /// certificates in `Bundle.main` by default. + /// - acceptSelfSignedCertificates: Adds the provided certificates as anchors for the trust evaluation, allowing + /// self-signed certificates to pass. `false` by default. THIS SETTING SHOULD BE + /// FALSE IN PRODUCTION! + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition + /// to performing the default evaluation, even if `performDefaultValidation` is + /// `false`. `true` by default. + public static func pinnedCertificates(certificates: [SecCertificate] = Bundle.main.af.certificates, + acceptSelfSignedCertificates: Bool = false, + performDefaultValidation: Bool = true, + validateHost: Bool = true) -> PinnedCertificatesTrustEvaluator { + PinnedCertificatesTrustEvaluator(certificates: certificates, + acceptSelfSignedCertificates: acceptSelfSignedCertificates, + performDefaultValidation: performDefaultValidation, + validateHost: validateHost) + } +} + +/// Uses the pinned public keys to validate the server trust. The server trust is considered valid if one of the pinned +/// public keys match one of the server certificate public keys. By validating both the certificate chain and host, +/// public key pinning provides a very secure form of server trust validation mitigating most, if not all, MITM attacks. +/// Applications are encouraged to always validate the host and require a valid certificate chain in production +/// environments. +public final class PublicKeysTrustEvaluator: ServerTrustEvaluating { + private let keys: [SecKey] + private let performDefaultValidation: Bool + private let validateHost: Bool + + /// Creates a `PublicKeysTrustEvaluator` from the provided parameters. + /// + /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use + /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. + /// + /// - Parameters: + /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all + /// certificates included in the main bundle. + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to + /// performing the default evaluation, even if `performDefaultValidation` is `false`. + /// `true` by default. + public init(keys: [SecKey] = Bundle.main.af.publicKeys, + performDefaultValidation: Bool = true, + validateHost: Bool = true) { + self.keys = keys + self.performDefaultValidation = performDefaultValidation + self.validateHost = validateHost + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + guard !keys.isEmpty else { + throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound) + } + + if performDefaultValidation { + try trust.af.performDefaultValidation(forHost: host) + } + + if validateHost { + try trust.af.performValidation(forHost: host) + } + + let pinnedKeysInServerKeys: Bool = { + for serverPublicKey in trust.af.publicKeys { + if keys.contains(serverPublicKey) { + return true + } + } + return false + }() + + if !pinnedKeysInServerKeys { + throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host, + trust: trust, + pinnedKeys: keys, + serverKeys: trust.af.publicKeys)) + } + } +} + +extension ServerTrustEvaluating where Self == PublicKeysTrustEvaluator { + /// Provides a default `PublicKeysTrustEvaluator` instance. + public static var publicKeys: PublicKeysTrustEvaluator { PublicKeysTrustEvaluator() } + + /// Creates a `PublicKeysTrustEvaluator` from the provided parameters. + /// + /// - Note: Default and host validation will fail when using this evaluator with self-signed certificates. Use + /// `PinnedCertificatesTrustEvaluator` if you need to use self-signed certificates. + /// + /// - Parameters: + /// - keys: The `SecKey`s to use to validate public keys. Defaults to the public keys of all + /// certificates included in the main bundle. + /// - performDefaultValidation: Determines whether default validation should be performed in addition to + /// evaluating the pinned certificates. `true` by default. + /// - validateHost: Determines whether or not the evaluator should validate the host, in addition to + /// performing the default evaluation, even if `performDefaultValidation` is `false`. + /// `true` by default. + public static func publicKeys(keys: [SecKey] = Bundle.main.af.publicKeys, + performDefaultValidation: Bool = true, + validateHost: Bool = true) -> PublicKeysTrustEvaluator { + PublicKeysTrustEvaluator(keys: keys, performDefaultValidation: performDefaultValidation, validateHost: validateHost) + } +} + +/// Uses the provided evaluators to validate the server trust. The trust is only considered valid if all of the +/// evaluators consider it valid. +public final class CompositeTrustEvaluator: ServerTrustEvaluating { + private let evaluators: [any ServerTrustEvaluating] + + /// Creates a `CompositeTrustEvaluator` from the provided evaluators. + /// + /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust. + public init(evaluators: [any ServerTrustEvaluating]) { + self.evaluators = evaluators + } + + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + try evaluators.evaluate(trust, forHost: host) + } +} + +extension ServerTrustEvaluating where Self == CompositeTrustEvaluator { + /// Creates a `CompositeTrustEvaluator` from the provided evaluators. + /// + /// - Parameter evaluators: The `ServerTrustEvaluating` values used to evaluate the server trust. + public static func composite(evaluators: [any ServerTrustEvaluating]) -> CompositeTrustEvaluator { + CompositeTrustEvaluator(evaluators: evaluators) + } +} + +/// Disables all evaluation which in turn will always consider any server trust as valid. +/// +/// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test +/// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html). +/// +/// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!** +@available(*, deprecated, renamed: "DisabledTrustEvaluator", message: "DisabledEvaluator has been renamed DisabledTrustEvaluator.") +public typealias DisabledEvaluator = DisabledTrustEvaluator + +/// Disables all evaluation which in turn will always consider any server trust as valid. +/// +/// +/// - Note: Instead of disabling server trust evaluation, it's a better idea to configure systems to properly trust test +/// certificates, as outlined in [this Apple tech note](https://developer.apple.com/library/archive/qa/qa1948/_index.html). +/// +/// **THIS EVALUATOR SHOULD NEVER BE USED IN PRODUCTION!** +public final class DisabledTrustEvaluator: ServerTrustEvaluating { + /// Creates an instance. + public init() {} + + public func evaluate(_ trust: SecTrust, forHost host: String) throws {} +} + +// MARK: - Extensions + +extension [ServerTrustEvaluating] { + #if os(Linux) || os(Windows) || os(Android) + // Add this same convenience method for Linux/Windows. + #else + /// Evaluates the given `SecTrust` value for the given `host`. + /// + /// - Parameters: + /// - trust: The `SecTrust` value to evaluate. + /// - host: The host for which to evaluate the `SecTrust` value. + /// + /// - Returns: Whether or not the evaluator considers the `SecTrust` value valid for `host`. + public func evaluate(_ trust: SecTrust, forHost host: String) throws { + for evaluator in self { + try evaluator.evaluate(trust, forHost: host) + } + } + #endif +} + +extension Bundle: AlamofireExtended {} +extension AlamofireExtension where ExtendedType: Bundle { + /// Returns all valid `cer`, `crt`, and `der` certificates in the bundle. + public var certificates: [SecCertificate] { + paths(forResourcesOfTypes: [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]).compactMap { path in + guard + let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData, + let certificate = SecCertificateCreateWithData(nil, certificateData) else { return nil } + + return certificate + } + } + + /// Returns all public keys for the valid certificates in the bundle. + public var publicKeys: [SecKey] { + certificates.af.publicKeys + } + + /// Returns all pathnames for the resources identified by the provided file extensions. + /// + /// - Parameter types: The filename extensions locate. + /// + /// - Returns: All pathnames for the given filename extensions. + public func paths(forResourcesOfTypes types: [String]) -> [String] { + Array(Set(types.flatMap { type.paths(forResourcesOfType: $0, inDirectory: nil) })) + } +} + +extension SecTrust: AlamofireExtended {} +extension AlamofireExtension where ExtendedType == SecTrust { + /// Evaluates `self` after applying the `SecPolicy` value provided. + /// + /// - Parameter policy: The `SecPolicy` to apply to `self` before evaluation. + /// + /// - Throws: Any `Error` from applying the `SecPolicy` or from evaluation. + @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) + public func evaluate(afterApplying policy: SecPolicy) throws { + try apply(policy: policy).af.evaluate() + } + + /// Attempts to validate `self` using the `SecPolicy` provided and transforming any error produced using the closure passed. + /// + /// - Parameters: + /// - policy: The `SecPolicy` used to evaluate `self`. + /// - errorProducer: The closure used transform the failed `OSStatus` and `SecTrustResultType`. + /// - Throws: Any `Error` from applying the `policy`, or the result of `errorProducer` if validation fails. + @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)") + @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate(afterApplying:)") + @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)") + @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate(afterApplying:)") + public func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> any Error) throws { + try apply(policy: policy).af.validate(errorProducer: errorProducer) + } + + /// Applies a `SecPolicy` to `self`, throwing if it fails. + /// + /// - Parameter policy: The `SecPolicy`. + /// + /// - Returns: `self`, with the policy applied. + /// - Throws: An `AFError.serverTrustEvaluationFailed` instance with a `.policyApplicationFailed` reason. + public func apply(policy: SecPolicy) throws -> SecTrust { + let status = SecTrustSetPolicies(type, policy) + + guard status.af.isSuccess else { + throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: type, + policy: policy, + status: status)) + } + + return type + } + + /// Evaluate `self`, throwing an `Error` if evaluation fails. + /// + /// - Throws: `AFError.serverTrustEvaluationFailed` with reason `.trustValidationFailed` and associated error from + /// the underlying evaluation. + @available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) + public func evaluate() throws { + var error: CFError? + let evaluationSucceeded = SecTrustEvaluateWithError(type, &error) + + if !evaluationSucceeded { + throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(error: error)) + } + } + + /// Validate `self`, passing any failure values through `errorProducer`. + /// + /// - Parameter errorProducer: The closure used to transform the failed `OSStatus` and `SecTrustResultType` into an + /// `Error`. + /// - Throws: The `Error` produced by the `errorProducer` closure. + @available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate()") + @available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate()") + @available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate()") + @available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate()") + public func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> any Error) throws { + var result = SecTrustResultType.invalid + let status = SecTrustEvaluate(type, &result) + + guard status.af.isSuccess && result.af.isSuccess else { + throw errorProducer(status, result) + } + } + + /// Sets a custom certificate chain on `self`, allowing full validation of a self-signed certificate and its chain. + /// + /// - Parameter certificates: The `SecCertificate`s to add to the chain. + /// - Throws: Any error produced when applying the new certificate chain. + public func setAnchorCertificates(_ certificates: [SecCertificate]) throws { + // Add additional anchor certificates. + let status = SecTrustSetAnchorCertificates(type, certificates as CFArray) + guard status.af.isSuccess else { + throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: status, + certificates: certificates)) + } + + // Trust only the set anchor certs. + let onlyStatus = SecTrustSetAnchorCertificatesOnly(type, true) + guard onlyStatus.af.isSuccess else { + throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: onlyStatus, + certificates: certificates)) + } + } + + /// The public keys contained in `self`. + public var publicKeys: [SecKey] { + certificates.af.publicKeys + } + + /// The `SecCertificate`s contained in `self`. + public var certificates: [SecCertificate] { + if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, visionOS 1, *) { + (SecTrustCopyCertificateChain(type) as? [SecCertificate]) ?? [] + } else { + (0.. SecPolicy { + SecPolicyCreateSSL(true, hostname as CFString) + } + + /// Creates a `SecPolicy` which checks the revocation of certificates. + /// + /// - Parameter options: The `RevocationTrustEvaluator.Options` for evaluation. + /// + /// - Returns: The `SecPolicy`. + /// - Throws: An `AFError.serverTrustEvaluationFailed` error with reason `.revocationPolicyCreationFailed` + /// if the policy cannot be created. + public static func revocation(options: RevocationTrustEvaluator.Options) throws -> SecPolicy { + guard let policy = SecPolicyCreateRevocation(options.rawValue) else { + throw AFError.serverTrustEvaluationFailed(reason: .revocationPolicyCreationFailed) + } + + return policy + } +} + +extension Array: AlamofireExtended {} +extension AlamofireExtension where ExtendedType == [SecCertificate] { + /// All `Data` values for the contained `SecCertificate`s. + public var data: [Data] { + type.map { SecCertificateCopyData($0) as Data } + } + + /// All public `SecKey` values for the contained `SecCertificate`s. + public var publicKeys: [SecKey] { + type.compactMap(\.af.publicKey) + } +} + +extension SecCertificate: AlamofireExtended {} +extension AlamofireExtension where ExtendedType == SecCertificate { + /// The public key for `self`, if it can be extracted. + /// + /// - Note: On 2020 OSes and newer, only RSA and ECDSA keys are supported. + /// + public var publicKey: SecKey? { + let policy = SecPolicyCreateBasicX509() + var trust: SecTrust? + let trustCreationStatus = SecTrustCreateWithCertificates(type, policy, &trust) + + guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil } + + if #available(iOS 14, macOS 11, tvOS 14, watchOS 7, visionOS 1, *) { + return SecTrustCopyKey(createdTrust) + } else { + return SecTrustCopyPublicKey(createdTrust) + } + } +} + +extension OSStatus: AlamofireExtended {} +extension AlamofireExtension where ExtendedType == OSStatus { + /// Returns whether `self` is `errSecSuccess`. + public var isSuccess: Bool { type == errSecSuccess } +} + +extension SecTrustResultType: AlamofireExtended {} +extension AlamofireExtension where ExtendedType == SecTrustResultType { + /// Returns whether `self` is `.unspecified` or `.proceed`. + public var isSuccess: Bool { + type == .unspecified || type == .proceed + } +} +#endif diff --git a/Pods/Alamofire/Source/Features/URLEncodedFormEncoder.swift b/Pods/Alamofire/Source/Features/URLEncodedFormEncoder.swift new file mode 100644 index 0000000..dab6791 --- /dev/null +++ b/Pods/Alamofire/Source/Features/URLEncodedFormEncoder.swift @@ -0,0 +1,1151 @@ +// +// URLEncodedFormEncoder.swift +// +// Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +/// An object that encodes instances into URL-encoded query strings. +/// +/// `ArrayEncoding` can be used to configure how `Array` values are encoded. By default, the `.brackets` encoding is +/// used, encoding array values with brackets for each value. e.g `array[]=1&array[]=2`. +/// +/// `BoolEncoding` can be used to configure how `Bool` values are encoded. By default, the `.numeric` encoding is used, +/// encoding `true` as `1` and `false` as `0`. +/// +/// `DataEncoding` can be used to configure how `Data` values are encoded. By default, the `.deferredToData` encoding is +/// used, which encodes `Data` values using their default `Encodable` implementation. +/// +/// `DateEncoding` can be used to configure how `Date` values are encoded. By default, the `.deferredToDate` +/// encoding is used, which encodes `Date`s using their default `Encodable` implementation. +/// +/// `KeyEncoding` can be used to configure how keys are encoded. By default, the `.useDefaultKeys` encoding is used, +/// which encodes the keys directly from the `Encodable` implementation. +/// +/// `KeyPathEncoding` can be used to configure how paths within nested objects are encoded. By default, the `.brackets` +/// encoding is used, which encodes each sub-key in brackets. e.g. `parent[child][grandchild]=value`. +/// +/// `NilEncoding` can be used to configure how `nil` `Optional` values are encoded. By default, the `.dropKey` encoding +/// is used, which drops `nil` key / value pairs from the output entirely. +/// +/// `SpaceEncoding` can be used to configure how spaces are encoded. By default, the `.percentEscaped` encoding is used, +/// replacing spaces with `%20`. +/// +/// This type is largely based on Vapor's [`url-encoded-form`](https://github.com/vapor/url-encoded-form) project. +public final class URLEncodedFormEncoder { + /// Encoding to use for `Array` values. + public enum ArrayEncoding { + /// An empty set of square brackets ("[]") are appended to the key for every value. This is the default encoding. + case brackets + /// No brackets are appended to the key and the key is encoded as is. + case noBrackets + /// Brackets containing the item index are appended. This matches the jQuery and Node.js behavior. + case indexInBrackets + /// Provide a custom array key encoding with the given closure. + case custom((_ key: String, _ index: Int) -> String) + + /// Encodes the key according to the encoding. + /// + /// - Parameters: + /// - key: The `key` to encode. + /// - index: When this enum instance is `.indexInBrackets`, the `index` to encode. + /// + /// - Returns: The encoded key. + func encode(_ key: String, atIndex index: Int) -> String { + switch self { + case .brackets: "\(key)[]" + case .noBrackets: key + case .indexInBrackets: "\(key)[\(index)]" + case let .custom(encoding): encoding(key, index) + } + } + } + + /// Encoding to use for `Bool` values. + public enum BoolEncoding { + /// Encodes `true` as `1`, `false` as `0`. + case numeric + /// Encodes `true` as "true", `false` as "false". This is the default encoding. + case literal + + /// Encodes the given `Bool` as a `String`. + /// + /// - Parameter value: The `Bool` to encode. + /// + /// - Returns: The encoded `String`. + func encode(_ value: Bool) -> String { + switch self { + case .numeric: value ? "1" : "0" + case .literal: value ? "true" : "false" + } + } + } + + /// Encoding to use for `Data` values. + public enum DataEncoding { + /// Defers encoding to the `Data` type. + case deferredToData + /// Encodes `Data` as a Base64-encoded string. This is the default encoding. + case base64 + /// Encode the `Data` as a custom value encoded by the given closure. + case custom((Data) throws -> String) + + /// Encodes `Data` according to the encoding. + /// + /// - Parameter data: The `Data` to encode. + /// + /// - Returns: The encoded `String`, or `nil` if the `Data` should be encoded according to its + /// `Encodable` implementation. + func encode(_ data: Data) throws -> String? { + switch self { + case .deferredToData: nil + case .base64: data.base64EncodedString() + case let .custom(encoding): try encoding(data) + } + } + } + + /// Encoding to use for `Date` values. + public enum DateEncoding { + /// ISO8601 and RFC3339 formatter. + private static let iso8601Formatter = Protected({ + let formatter = ISO8601DateFormatter() + formatter.formatOptions = .withInternetDateTime + return formatter + }()) + + /// Defers encoding to the `Date` type. This is the default encoding. + case deferredToDate + /// Encodes `Date`s as seconds since midnight UTC on January 1, 1970. + case secondsSince1970 + /// Encodes `Date`s as milliseconds since midnight UTC on January 1, 1970. + case millisecondsSince1970 + /// Encodes `Date`s according to the ISO8601 and RFC3339 standards. + case iso8601 + /// Encodes `Date`s using the given `DateFormatter`. + case formatted(DateFormatter) + /// Encodes `Date`s using the given closure. + case custom((Date) throws -> String) + + /// Encodes the date according to the encoding. + /// + /// - Parameter date: The `Date` to encode. + /// + /// - Returns: The encoded `String`, or `nil` if the `Date` should be encoded according to its + /// `Encodable` implementation. + func encode(_ date: Date) throws -> String? { + switch self { + case .deferredToDate: + nil + case .secondsSince1970: + String(date.timeIntervalSince1970) + case .millisecondsSince1970: + String(date.timeIntervalSince1970 * 1000.0) + case .iso8601: + DateEncoding.iso8601Formatter.read { $0.string(from: date) } + case let .formatted(formatter): + formatter.string(from: date) + case let .custom(closure): + try closure(date) + } + } + } + + /// Encoding to use for keys. + /// + /// This type is derived from [`JSONEncoder`'s `KeyEncodingStrategy`](https://github.com/apple/swift/blob/6aa313b8dd5f05135f7f878eccc1db6f9fbe34ff/stdlib/public/Darwin/Foundation/JSONEncoder.swift#L128) + /// and [`XMLEncoder`s `KeyEncodingStrategy`](https://github.com/MaxDesiatov/XMLCoder/blob/master/Sources/XMLCoder/Encoder/XMLEncoder.swift#L102). + public enum KeyEncoding { + /// Use the keys specified by each type. This is the default encoding. + case useDefaultKeys + /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key. + /// + /// Capital characters are determined by testing membership in + /// `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` + /// (Unicode General Categories Lu and Lt). + /// The conversion to lower case uses `Locale.system`, also known as + /// the ICU "root" locale. This means the result is consistent + /// regardless of the current user's locale and language preferences. + /// + /// Converting from camel case to snake case: + /// 1. Splits words at the boundary of lower-case to upper-case + /// 2. Inserts `_` between words + /// 3. Lowercases the entire string + /// 4. Preserves starting and ending `_`. + /// + /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. + /// + /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. + case convertToSnakeCase + /// Same as convertToSnakeCase, but using `-` instead of `_`. + /// For example `oneTwoThree` becomes `one-two-three`. + case convertToKebabCase + /// Capitalize the first letter only. + /// For example `oneTwoThree` becomes `OneTwoThree`. + case capitalized + /// Uppercase all letters. + /// For example `oneTwoThree` becomes `ONETWOTHREE`. + case uppercased + /// Lowercase all letters. + /// For example `oneTwoThree` becomes `onetwothree`. + case lowercased + /// A custom encoding using the provided closure. + case custom((String) -> String) + + func encode(_ key: String) -> String { + switch self { + case .useDefaultKeys: key + case .convertToSnakeCase: convertToSnakeCase(key) + case .convertToKebabCase: convertToKebabCase(key) + case .capitalized: String(key.prefix(1).uppercased() + key.dropFirst()) + case .uppercased: key.uppercased() + case .lowercased: key.lowercased() + case let .custom(encoding): encoding(key) + } + } + + private func convertToSnakeCase(_ key: String) -> String { + convert(key, usingSeparator: "_") + } + + private func convertToKebabCase(_ key: String) -> String { + convert(key, usingSeparator: "-") + } + + private func convert(_ key: String, usingSeparator separator: String) -> String { + guard !key.isEmpty else { return key } + + var words: [Range] = [] + // The general idea of this algorithm is to split words on + // transition from lower to upper case, then on transition of >1 + // upper case characters to lowercase + // + // myProperty -> my_property + // myURLProperty -> my_url_property + // + // It is assumed, per Swift naming conventions, that the first character of the key is lowercase. + var wordStart = key.startIndex + var searchRange = key.index(after: wordStart)..1 capital letters. Turn those into a word, stopping at the capital before + // the lower case character. + let beforeLowerIndex = key.index(before: lowerCaseRange.lowerBound) + words.append(upperCaseRange.lowerBound.. String + + /// Creates an instance with the encoding closure called for each sub-key in a key path. + /// + /// - Parameter encoding: Closure used to perform the encoding. + public init(encoding: @escaping @Sendable (_ subkey: String) -> String) { + self.encoding = encoding + } + + func encodeKeyPath(_ keyPath: String) -> String { + encoding(keyPath) + } + } + + /// Encoding to use for `nil` values. + public struct NilEncoding: Sendable { + /// Encodes `nil` by dropping the entire key / value pair. + public static let dropKey = NilEncoding { nil } + /// Encodes `nil` by dropping only the value. e.g. `value1=one&nilValue=&value2=two`. + public static let dropValue = NilEncoding { "" } + /// Encodes `nil` as `null`. + public static let null = NilEncoding { "null" } + + private let encoding: @Sendable () -> String? + + /// Creates an instance with the encoding closure called for `nil` values. + /// + /// - Parameter encoding: Closure used to perform the encoding. + public init(encoding: @escaping @Sendable () -> String?) { + self.encoding = encoding + } + + func encodeNil() -> String? { + encoding() + } + } + + /// Encoding to use for spaces. + public enum SpaceEncoding { + /// Encodes spaces using percent escaping (`%20`). + case percentEscaped + /// Encodes spaces as `+`. + case plusReplaced + + /// Encodes the string according to the encoding. + /// + /// - Parameter string: The `String` to encode. + /// + /// - Returns: The encoded `String`. + func encode(_ string: String) -> String { + switch self { + case .percentEscaped: string.replacingOccurrences(of: " ", with: "%20") + case .plusReplaced: string.replacingOccurrences(of: " ", with: "+") + } + } + } + + /// `URLEncodedFormEncoder` error. + public enum Error: Swift.Error { + /// An invalid root object was created by the encoder. Only keyed values are valid. + case invalidRootObject(String) + + var localizedDescription: String { + switch self { + case let .invalidRootObject(object): + "URLEncodedFormEncoder requires keyed root object. Received \(object) instead." + } + } + } + + /// Whether or not to sort the encoded key value pairs. + /// + /// - Note: This setting ensures a consistent ordering for all encodings of the same parameters. When set to `false`, + /// encoded `Dictionary` values may have a different encoded order each time they're encoded due to + /// ` Dictionary`'s random storage order, but `Encodable` types will maintain their encoded order. + public let alphabetizeKeyValuePairs: Bool + /// The `ArrayEncoding` to use. + public let arrayEncoding: ArrayEncoding + /// The `BoolEncoding` to use. + public let boolEncoding: BoolEncoding + /// THe `DataEncoding` to use. + public let dataEncoding: DataEncoding + /// The `DateEncoding` to use. + public let dateEncoding: DateEncoding + /// The `KeyEncoding` to use. + public let keyEncoding: KeyEncoding + /// The `KeyPathEncoding` to use. + public let keyPathEncoding: KeyPathEncoding + /// The `NilEncoding` to use. + public let nilEncoding: NilEncoding + /// The `SpaceEncoding` to use. + public let spaceEncoding: SpaceEncoding + /// The `CharacterSet` of allowed (non-escaped) characters. + public var allowedCharacters: CharacterSet + + /// Creates an instance from the supplied parameters. + /// + /// - Parameters: + /// - alphabetizeKeyValuePairs: Whether or not to sort the encoded key value pairs. `true` by default. + /// - arrayEncoding: The `ArrayEncoding` to use. `.brackets` by default. + /// - boolEncoding: The `BoolEncoding` to use. `.numeric` by default. + /// - dataEncoding: The `DataEncoding` to use. `.base64` by default. + /// - dateEncoding: The `DateEncoding` to use. `.deferredToDate` by default. + /// - keyEncoding: The `KeyEncoding` to use. `.useDefaultKeys` by default. + /// - nilEncoding: The `NilEncoding` to use. `.drop` by default. + /// - spaceEncoding: The `SpaceEncoding` to use. `.percentEscaped` by default. + /// - allowedCharacters: The `CharacterSet` of allowed (non-escaped) characters. `.afURLQueryAllowed` by + /// default. + public init(alphabetizeKeyValuePairs: Bool = true, + arrayEncoding: ArrayEncoding = .brackets, + boolEncoding: BoolEncoding = .numeric, + dataEncoding: DataEncoding = .base64, + dateEncoding: DateEncoding = .deferredToDate, + keyEncoding: KeyEncoding = .useDefaultKeys, + keyPathEncoding: KeyPathEncoding = .brackets, + nilEncoding: NilEncoding = .dropKey, + spaceEncoding: SpaceEncoding = .percentEscaped, + allowedCharacters: CharacterSet = .afURLQueryAllowed) { + self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs + self.arrayEncoding = arrayEncoding + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + self.keyEncoding = keyEncoding + self.keyPathEncoding = keyPathEncoding + self.nilEncoding = nilEncoding + self.spaceEncoding = spaceEncoding + self.allowedCharacters = allowedCharacters + } + + func encode(_ value: any Encodable) throws -> URLEncodedFormComponent { + let context = URLEncodedFormContext(.object([])) + let encoder = _URLEncodedFormEncoder(context: context, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + try value.encode(to: encoder) + + return context.component + } + + /// Encodes the `value` as a URL form encoded `String`. + /// + /// - Parameter value: The `Encodable` value. + /// + /// - Returns: The encoded `String`. + /// - Throws: An `Error` or `EncodingError` instance if encoding fails. + public func encode(_ value: any Encodable) throws -> String { + let component: URLEncodedFormComponent = try encode(value) + + guard case let .object(object) = component else { + throw Error.invalidRootObject("\(component)") + } + + let serializer = URLEncodedFormSerializer(alphabetizeKeyValuePairs: alphabetizeKeyValuePairs, + arrayEncoding: arrayEncoding, + keyEncoding: keyEncoding, + keyPathEncoding: keyPathEncoding, + spaceEncoding: spaceEncoding, + allowedCharacters: allowedCharacters) + let query = serializer.serialize(object) + + return query + } + + /// Encodes the value as `Data`. This is performed by first creating an encoded `String` and then returning the + /// `.utf8` data. + /// + /// - Parameter value: The `Encodable` value. + /// + /// - Returns: The encoded `Data`. + /// + /// - Throws: An `Error` or `EncodingError` instance if encoding fails. + public func encode(_ value: any Encodable) throws -> Data { + let string: String = try encode(value) + + return Data(string.utf8) + } +} + +final class _URLEncodedFormEncoder { + var codingPath: [any CodingKey] + // Returns an empty dictionary, as this encoder doesn't support userInfo. + var userInfo: [CodingUserInfoKey: Any] { [:] } + + let context: URLEncodedFormContext + + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + private let nilEncoding: URLEncodedFormEncoder.NilEncoding + + init(context: URLEncodedFormContext, + codingPath: [any CodingKey] = [], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding, + nilEncoding: URLEncodedFormEncoder.NilEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + self.nilEncoding = nilEncoding + } +} + +extension _URLEncodedFormEncoder: Encoder { + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key: CodingKey { + let container = _URLEncodedFormEncoder.KeyedContainer(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + return KeyedEncodingContainer(container) + } + + func unkeyedContainer() -> any UnkeyedEncodingContainer { + _URLEncodedFormEncoder.UnkeyedContainer(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + } + + func singleValueContainer() -> any SingleValueEncodingContainer { + _URLEncodedFormEncoder.SingleValueContainer(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + } +} + +final class URLEncodedFormContext { + var component: URLEncodedFormComponent + + init(_ component: URLEncodedFormComponent) { + self.component = component + } +} + +enum URLEncodedFormComponent { + typealias Object = [(key: String, value: URLEncodedFormComponent)] + + case string(String) + case array([URLEncodedFormComponent]) + case object(Object) + + /// Converts self to an `[URLEncodedFormData]` or returns `nil` if not convertible. + var array: [URLEncodedFormComponent]? { + switch self { + case let .array(array): array + default: nil + } + } + + /// Converts self to an `Object` or returns `nil` if not convertible. + var object: Object? { + switch self { + case let .object(object): object + default: nil + } + } + + /// Sets self to the supplied value at a given path. + /// + /// data.set(to: "hello", at: ["path", "to", "value"]) + /// + /// - parameters: + /// - value: Value of `Self` to set at the supplied path. + /// - path: `CodingKey` path to update with the supplied value. + public mutating func set(to value: URLEncodedFormComponent, at path: [any CodingKey]) { + set(&self, to: value, at: path) + } + + /// Recursive backing method to `set(to:at:)`. + private func set(_ context: inout URLEncodedFormComponent, to value: URLEncodedFormComponent, at path: [any CodingKey]) { + guard !path.isEmpty else { + context = value + return + } + + let end = path[0] + var child: URLEncodedFormComponent + switch path.count { + case 1: + child = value + case 2...: + if let index = end.intValue { + let array = context.array ?? [] + if array.count > index { + child = array[index] + } else { + child = .array([]) + } + set(&child, to: value, at: Array(path[1...])) + } else { + child = context.object?.first { $0.key == end.stringValue }?.value ?? .object(.init()) + set(&child, to: value, at: Array(path[1...])) + } + default: fatalError("Unreachable") + } + + if let index = end.intValue { + if var array = context.array { + if array.count > index { + array[index] = child + } else { + array.append(child) + } + context = .array(array) + } else { + context = .array([child]) + } + } else { + if var object = context.object { + if let index = object.firstIndex(where: { $0.key == end.stringValue }) { + object[index] = (key: end.stringValue, value: child) + } else { + object.append((key: end.stringValue, value: child)) + } + context = .object(object) + } else { + context = .object([(key: end.stringValue, value: child)]) + } + } + } +} + +struct AnyCodingKey: CodingKey, Hashable { + let stringValue: String + let intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + intValue = nil + } + + init?(intValue: Int) { + stringValue = "\(intValue)" + self.intValue = intValue + } + + init(_ base: Key) where Key: CodingKey { + if let intValue = base.intValue { + self.init(intValue: intValue)! + } else { + self.init(stringValue: base.stringValue)! + } + } +} + +extension _URLEncodedFormEncoder { + final class KeyedContainer where Key: CodingKey { + var codingPath: [any CodingKey] + + private let context: URLEncodedFormContext + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + private let nilEncoding: URLEncodedFormEncoder.NilEncoding + + init(context: URLEncodedFormContext, + codingPath: [any CodingKey], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding, + nilEncoding: URLEncodedFormEncoder.NilEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + self.nilEncoding = nilEncoding + } + + private func nestedCodingPath(for key: any CodingKey) -> [any CodingKey] { + codingPath + [key] + } + } +} + +extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol { + func encodeNil(forKey key: Key) throws { + guard let nilValue = nilEncoding.encodeNil() else { return } + + try encode(nilValue, forKey: key) + } + + func encodeIfPresent(_ value: Bool?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: String?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Double?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Float?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Int?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Int8?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Int16?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Int32?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Int64?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: UInt?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: UInt8?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: UInt16?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: UInt32?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: UInt64?, forKey key: Key) throws { + try _encodeIfPresent(value, forKey: key) + } + + func encodeIfPresent(_ value: Value?, forKey key: Key) throws where Value: Encodable { + try _encodeIfPresent(value, forKey: key) + } + + func _encodeIfPresent(_ value: Value?, forKey key: Key) throws where Value: Encodable { + if let value { + try encode(value, forKey: key) + } else { + try encodeNil(forKey: key) + } + } + + func encode(_ value: T, forKey key: Key) throws where T: Encodable { + var container = nestedSingleValueEncoder(for: key) + try container.encode(value) + } + + func nestedSingleValueEncoder(for key: Key) -> any SingleValueEncodingContainer { + let container = _URLEncodedFormEncoder.SingleValueContainer(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + + return container + } + + func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer { + let container = _URLEncodedFormEncoder.UnkeyedContainer(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + + return container + } + + func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer where NestedKey: CodingKey { + let container = _URLEncodedFormEncoder.KeyedContainer(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + + return KeyedEncodingContainer(container) + } + + func superEncoder() -> any Encoder { + _URLEncodedFormEncoder(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + } + + func superEncoder(forKey key: Key) -> any Encoder { + _URLEncodedFormEncoder(context: context, + codingPath: nestedCodingPath(for: key), + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + } +} + +extension _URLEncodedFormEncoder { + final class SingleValueContainer { + var codingPath: [any CodingKey] + + private var canEncodeNewValue = true + + private let context: URLEncodedFormContext + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + private let nilEncoding: URLEncodedFormEncoder.NilEncoding + + init(context: URLEncodedFormContext, + codingPath: [any CodingKey], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding, + nilEncoding: URLEncodedFormEncoder.NilEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + self.nilEncoding = nilEncoding + } + + private func checkCanEncode(value: Any?) throws { + guard canEncodeNewValue else { + let context = EncodingError.Context(codingPath: codingPath, + debugDescription: "Attempt to encode value through single value container when previously value already encoded.") + throw EncodingError.invalidValue(value as Any, context) + } + } + } +} + +extension _URLEncodedFormEncoder.SingleValueContainer: SingleValueEncodingContainer { + func encodeNil() throws { + guard let nilValue = nilEncoding.encodeNil() else { return } + + try encode(nilValue) + } + + func encode(_ value: Bool) throws { + try encode(value, as: String(boolEncoding.encode(value))) + } + + func encode(_ value: String) throws { + try encode(value, as: value) + } + + func encode(_ value: Double) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Float) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int8) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int16) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int32) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: Int64) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt8) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt16) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt32) throws { + try encode(value, as: String(value)) + } + + func encode(_ value: UInt64) throws { + try encode(value, as: String(value)) + } + + private func encode(_ value: T, as string: String) throws where T: Encodable { + try checkCanEncode(value: value) + defer { canEncodeNewValue = false } + + context.component.set(to: .string(string), at: codingPath) + } + + func encode(_ value: T) throws where T: Encodable { + switch value { + case let date as Date: + guard let string = try dateEncoding.encode(date) else { + try attemptToEncode(value) + return + } + + try encode(value, as: string) + case let data as Data: + guard let string = try dataEncoding.encode(data) else { + try attemptToEncode(value) + return + } + + try encode(value, as: string) + case let decimal as Decimal: + // Decimal's `Encodable` implementation returns an object, not a single value, so override it. + try encode(value, as: String(describing: decimal)) + default: + try attemptToEncode(value) + } + } + + private func attemptToEncode(_ value: T) throws where T: Encodable { + try checkCanEncode(value: value) + defer { canEncodeNewValue = false } + + let encoder = _URLEncodedFormEncoder(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + try value.encode(to: encoder) + } +} + +extension _URLEncodedFormEncoder { + final class UnkeyedContainer { + var codingPath: [any CodingKey] + + var count = 0 + var nestedCodingPath: [any CodingKey] { + codingPath + [AnyCodingKey(intValue: count)!] + } + + private let context: URLEncodedFormContext + private let boolEncoding: URLEncodedFormEncoder.BoolEncoding + private let dataEncoding: URLEncodedFormEncoder.DataEncoding + private let dateEncoding: URLEncodedFormEncoder.DateEncoding + private let nilEncoding: URLEncodedFormEncoder.NilEncoding + + init(context: URLEncodedFormContext, + codingPath: [any CodingKey], + boolEncoding: URLEncodedFormEncoder.BoolEncoding, + dataEncoding: URLEncodedFormEncoder.DataEncoding, + dateEncoding: URLEncodedFormEncoder.DateEncoding, + nilEncoding: URLEncodedFormEncoder.NilEncoding) { + self.context = context + self.codingPath = codingPath + self.boolEncoding = boolEncoding + self.dataEncoding = dataEncoding + self.dateEncoding = dateEncoding + self.nilEncoding = nilEncoding + } + } +} + +extension _URLEncodedFormEncoder.UnkeyedContainer: UnkeyedEncodingContainer { + func encodeNil() throws { + guard let nilValue = nilEncoding.encodeNil() else { return } + + try encode(nilValue) + } + + func encode(_ value: T) throws where T: Encodable { + var container = nestedSingleValueContainer() + try container.encode(value) + } + + func nestedSingleValueContainer() -> any SingleValueEncodingContainer { + defer { count += 1 } + + return _URLEncodedFormEncoder.SingleValueContainer(context: context, + codingPath: nestedCodingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + } + + func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey: CodingKey { + defer { count += 1 } + let container = _URLEncodedFormEncoder.KeyedContainer(context: context, + codingPath: nestedCodingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + + return KeyedEncodingContainer(container) + } + + func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { + defer { count += 1 } + + return _URLEncodedFormEncoder.UnkeyedContainer(context: context, + codingPath: nestedCodingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + } + + func superEncoder() -> any Encoder { + defer { count += 1 } + + return _URLEncodedFormEncoder(context: context, + codingPath: codingPath, + boolEncoding: boolEncoding, + dataEncoding: dataEncoding, + dateEncoding: dateEncoding, + nilEncoding: nilEncoding) + } +} + +final class URLEncodedFormSerializer { + private let alphabetizeKeyValuePairs: Bool + private let arrayEncoding: URLEncodedFormEncoder.ArrayEncoding + private let keyEncoding: URLEncodedFormEncoder.KeyEncoding + private let keyPathEncoding: URLEncodedFormEncoder.KeyPathEncoding + private let spaceEncoding: URLEncodedFormEncoder.SpaceEncoding + private let allowedCharacters: CharacterSet + + init(alphabetizeKeyValuePairs: Bool, + arrayEncoding: URLEncodedFormEncoder.ArrayEncoding, + keyEncoding: URLEncodedFormEncoder.KeyEncoding, + keyPathEncoding: URLEncodedFormEncoder.KeyPathEncoding, + spaceEncoding: URLEncodedFormEncoder.SpaceEncoding, + allowedCharacters: CharacterSet) { + self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs + self.arrayEncoding = arrayEncoding + self.keyEncoding = keyEncoding + self.keyPathEncoding = keyPathEncoding + self.spaceEncoding = spaceEncoding + self.allowedCharacters = allowedCharacters + } + + func serialize(_ object: URLEncodedFormComponent.Object) -> String { + var output: [String] = [] + for (key, component) in object { + let value = serialize(component, forKey: key) + output.append(value) + } + output = alphabetizeKeyValuePairs ? output.sorted() : output + + return output.joinedWithAmpersands() + } + + func serialize(_ component: URLEncodedFormComponent, forKey key: String) -> String { + switch component { + case let .string(string): "\(escape(keyEncoding.encode(key)))=\(escape(string))" + case let .array(array): serialize(array, forKey: key) + case let .object(object): serialize(object, forKey: key) + } + } + + func serialize(_ object: URLEncodedFormComponent.Object, forKey key: String) -> String { + var segments: [String] = object.map { subKey, value in + let keyPath = keyPathEncoding.encodeKeyPath(subKey) + return serialize(value, forKey: key + keyPath) + } + segments = alphabetizeKeyValuePairs ? segments.sorted() : segments + + return segments.joinedWithAmpersands() + } + + func serialize(_ array: [URLEncodedFormComponent], forKey key: String) -> String { + var segments: [String] = array.enumerated().map { index, component in + let keyPath = arrayEncoding.encode(key, atIndex: index) + return serialize(component, forKey: keyPath) + } + segments = alphabetizeKeyValuePairs ? segments.sorted() : segments + + return segments.joinedWithAmpersands() + } + + func escape(_ query: String) -> String { + var allowedCharactersWithSpace = allowedCharacters + allowedCharactersWithSpace.insert(charactersIn: " ") + let escapedQuery = query.addingPercentEncoding(withAllowedCharacters: allowedCharactersWithSpace) ?? query + let spaceEncodedQuery = spaceEncoding.encode(escapedQuery) + + return spaceEncodedQuery + } +} + +extension [String] { + func joinedWithAmpersands() -> String { + joined(separator: "&") + } +} + +extension CharacterSet { + /// Creates a CharacterSet from RFC 3986 allowed characters. + /// + /// RFC 3986 states that the following characters are "reserved" characters. + /// + /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" + /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" + /// + /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow + /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" + /// should be percent-escaped in the query string. + public static let afURLQueryAllowed: CharacterSet = { + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 + let subDelimitersToEncode = "!$&'()*+,;=" + let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + + return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters) + }() +} diff --git a/Pods/Alamofire/Source/Features/Validation.swift b/Pods/Alamofire/Source/Features/Validation.swift new file mode 100644 index 0000000..807adf2 --- /dev/null +++ b/Pods/Alamofire/Source/Features/Validation.swift @@ -0,0 +1,302 @@ +// +// Validation.swift +// +// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) +// +// 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. +// + +import Foundation + +extension Request { + // MARK: Helper Types + + fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason + + /// Used to represent whether a validation succeeded or failed. + public typealias ValidationResult = Result + + fileprivate struct MIMEType { + let type: String + let subtype: String + + var isWildcard: Bool { type == "*" && subtype == "*" } + + init?(_ string: String) { + let components: [String] = { + let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines) + let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)] + + return split.components(separatedBy: "/") + }() + + if let type = components.first, let subtype = components.last { + self.type = type + self.subtype = subtype + } else { + return nil + } + } + + func matches(_ mime: MIMEType) -> Bool { + switch (type, subtype) { + case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"): + true + default: + false + } + } + } + + // MARK: Properties + + fileprivate var acceptableStatusCodes: Range { 200..<300 } + + fileprivate var acceptableContentTypes: [String] { + if let accept = request?.value(forHTTPHeaderField: "Accept") { + return accept.components(separatedBy: ",") + } + + return ["*/*"] + } + + // MARK: Status Code + + fileprivate func validate(statusCode acceptableStatusCodes: S, + response: HTTPURLResponse) + -> ValidationResult + where S.Iterator.Element == Int { + if acceptableStatusCodes.contains(response.statusCode) { + return .success(()) + } else { + let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode) + return .failure(AFError.responseValidationFailed(reason: reason)) + } + } + + // MARK: Content Type + + fileprivate func validate(contentType acceptableContentTypes: S, + response: HTTPURLResponse, + isEmpty: Bool) + -> ValidationResult + where S.Iterator.Element == String { + guard !isEmpty else { return .success(()) } + + return validate(contentType: acceptableContentTypes, response: response) + } + + fileprivate func validate(contentType acceptableContentTypes: S, + response: HTTPURLResponse) + -> ValidationResult + where S.Iterator.Element == String { + guard + let responseContentType = response.mimeType, + let responseMIMEType = MIMEType(responseContentType) + else { + for contentType in acceptableContentTypes { + if let mimeType = MIMEType(contentType), mimeType.isWildcard { + return .success(()) + } + } + + let error: AFError = { + let reason: ErrorReason = .missingContentType(acceptableContentTypes: acceptableContentTypes.sorted()) + return AFError.responseValidationFailed(reason: reason) + }() + + return .failure(error) + } + + for contentType in acceptableContentTypes { + if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) { + return .success(()) + } + } + + let error: AFError = { + let reason: ErrorReason = .unacceptableContentType(acceptableContentTypes: acceptableContentTypes.sorted(), + responseContentType: responseContentType) + + return AFError.responseValidationFailed(reason: reason) + }() + + return .failure(error) + } +} + +// MARK: - + +extension DataRequest { + /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the + /// request was valid. + public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult + + /// Validates that the response has a status code in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. + /// + /// - Returns: The instance. + @discardableResult + public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { + validate { [unowned self] _, response, _ in + self.validate(statusCode: acceptableStatusCodes, response: response) + } + } + + /// Validates that the response has a content type in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. + /// + /// - returns: The request. + @discardableResult + public func validate(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { + validate { [unowned self] _, response, data in + self.validate(contentType: acceptableContentTypes(), response: response, isEmpty: (data == nil || data?.isEmpty == true)) + } + } + + /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content + /// type matches any specified in the Accept HTTP header field. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - returns: The request. + @discardableResult + public func validate() -> Self { + let contentTypes: () -> [String] = { [unowned self] in + acceptableContentTypes + } + return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) + } +} + +extension DataStreamRequest { + /// A closure used to validate a request that takes a `URLRequest` and `HTTPURLResponse` and returns whether the + /// request was valid. + public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> ValidationResult + + /// Validates that the response has a status code in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. + /// + /// - Returns: The instance. + @discardableResult + public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { + validate { [unowned self] _, response in + self.validate(statusCode: acceptableStatusCodes, response: response) + } + } + + /// Validates that the response has a content type in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. + /// + /// - returns: The request. + @discardableResult + public func validate(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { + validate { [unowned self] _, response in + self.validate(contentType: acceptableContentTypes(), response: response) + } + } + + /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content + /// type matches any specified in the Accept HTTP header field. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Returns: The instance. + @discardableResult + public func validate() -> Self { + let contentTypes: () -> [String] = { [unowned self] in + acceptableContentTypes + } + return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) + } +} + +// MARK: - + +extension DownloadRequest { + /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a + /// destination URL, and returns whether the request was valid. + public typealias Validation = (_ request: URLRequest?, + _ response: HTTPURLResponse, + _ fileURL: URL?) + -> ValidationResult + + /// Validates that the response has a status code in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes. + /// + /// - Returns: The instance. + @discardableResult + public func validate(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int { + validate { [unowned self] _, response, _ in + self.validate(statusCode: acceptableStatusCodes, response: response) + } + } + + /// Validates that the response has a `Content-Type` in the specified sequence. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. + /// + /// - returns: The request. + @discardableResult + public func validate(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String { + validate { [unowned self] _, response, fileURL in + guard let fileURL else { + return .failure(AFError.responseValidationFailed(reason: .dataFileNil)) + } + + do { + let isEmpty = try (fileURL.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0) == 0 + return self.validate(contentType: acceptableContentTypes(), response: response, isEmpty: isEmpty) + } catch { + return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: fileURL))) + } + } + } + + /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content + /// type matches any specified in the Accept HTTP header field. + /// + /// If validation fails, subsequent calls to response handlers will have an associated error. + /// + /// - returns: The request. + @discardableResult + public func validate() -> Self { + let contentTypes = { [unowned self] in + acceptableContentTypes + } + return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes()) + } +} diff --git a/Pods/Alamofire/Source/PrivacyInfo.xcprivacy b/Pods/Alamofire/Source/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..488cbb1 --- /dev/null +++ b/Pods/Alamofire/Source/PrivacyInfo.xcprivacy @@ -0,0 +1,23 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + + diff --git a/Pods/CocoaLumberjack/LICENSE b/Pods/CocoaLumberjack/LICENSE new file mode 100644 index 0000000..34779d6 --- /dev/null +++ b/Pods/CocoaLumberjack/LICENSE @@ -0,0 +1,14 @@ +BSD 3-Clause License + +Copyright (c) 2010-2026, Deusty, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Pods/CocoaLumberjack/README.md b/Pods/CocoaLumberjack/README.md new file mode 100644 index 0000000..948e47a --- /dev/null +++ b/Pods/CocoaLumberjack/README.md @@ -0,0 +1,302 @@ +

+ +

+ +CocoaLumberjack +=============== +[![Version](https://img.shields.io/github/release/CocoaLumberjack/CocoaLumberjack.svg?display_name=tag&style=flat)](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/latest) +[![License](https://img.shields.io/github/license/CocoaLumberjack/CocoaLumberjack.svg?style=flat)](https://opensource.org/licenses/BSD-3-Clause) +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FCocoaLumberjack%2FCocoaLumberjack%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/CocoaLumberjack/CocoaLumberjack) +[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FCocoaLumberjack%2FCocoaLumberjack%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/CocoaLumberjack/CocoaLumberjack) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +![Unit Tests](https://github.com/CocoaLumberjack/CocoaLumberjack/workflows/Unit%20Tests/badge.svg) +[![codecov](https://codecov.io/gh/CocoaLumberjack/CocoaLumberjack/branch/master/graph/badge.svg)](https://codecov.io/gh/CocoaLumberjack/CocoaLumberjack) + + +**CocoaLumberjack** is a fast & simple, yet powerful & flexible logging framework for macOS, iOS, tvOS, watchOS and visionOS. + +## How to get started + +First, install CocoaLumberjack via [Swift Package Manager](https://swift.org/package-manager/), [CocoaPods](https://cocoapods.org), [Carthage](https://github.com/Carthage/Carthage) or manually. +Then use `DDOSLogger` for iOS 10 and later, or `DDTTYLogger` and `DDASLLogger` for earlier versions to begin logging messages. + + +### Swift Package Manager + +As of CocoaLumberjack 3.6.0, you can use the Swift Package Manager as integration method. +If you want to use the Swift Package Manager as integration method, either use Xcode to add the package dependency or add the following dependency to your Package.swift: + +```swift +.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack", from: "3.9.0"), +``` + +Note that you may need to add both products, `CocoaLumberjack` and `CocoaLumberjackSwift` to your target since SPM sometimes fails to detect that `CocoaLumberjackSwift` depends on `CocoaLumberjack`. + + +### CocoaPods + +```ruby +platform :ios, '11.0' + +target 'SampleTarget' do + use_frameworks! + pod 'CocoaLumberjack/Swift' +end +``` +Note: `Swift` is a subspec which will include all the Obj-C code plus the Swift one, so this is sufficient. +For more details about how to use Swift with Lumberjack, see [this conversation](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/405). + +For Objective-C use the following: +```ruby +platform :ios, '11.0' + +target 'SampleTarget' do + pod 'CocoaLumberjack' +end +``` + +### Carthage + +Carthage is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods. + +To install with Carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage) + +Cartfile +``` +github "CocoaLumberjack/CocoaLumberjack" +``` + +### Install manually + +If you want to install CocoaLumberjack manually, read the [manual installation](Documentation/GettingStarted.md#manual-installation) guide for more information. + +### Swift Usage + +Usually, you can simply `import CocoaLumberjackSwift`. If you installed CocoaLumberjack using CocoaPods, you need to use `import CocoaLumberjack` instead. + +```swift +DDLog.add(DDOSLogger.sharedInstance) // Uses os_log + +let fileLogger: DDFileLogger = DDFileLogger() // File Logger +fileLogger.rollingFrequency = 60 * 60 * 24 // 24 hours +fileLogger.logFileManager.maximumNumberOfLogFiles = 7 +DDLog.add(fileLogger) + +... + +DDLogVerbose("Verbose") +DDLogDebug("Debug") +DDLogInfo("Info") +DDLogWarn("Warn") +DDLogError("Error") +``` + +### Obj-C usage + +If you're using Lumberjack as a framework, you can `@import CocoaLumberjack;`. +Otherwise, `#import ` + +```objc +[DDLog addLogger:[DDOSLogger sharedInstance]]; // Uses os_log + +DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger +fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling +fileLogger.logFileManager.maximumNumberOfLogFiles = 7; +[DDLog addLogger:fileLogger]; + +... + +DDLogVerbose(@"Verbose"); +DDLogDebug(@"Debug"); +DDLogInfo(@"Info"); +DDLogWarn(@"Warn"); +DDLogError(@"Error"); +``` +### Objective-C ARC Semantic Issue + +When integrating Lumberjack into an existing Objective-C it is possible to run into `Multiple methods named 'tag' found with mismatched result, parameter type or attributes` build error. + +Add `#define DD_LEGACY_MESSAGE_TAG 0` before importing CocoaLumberjack or add `#define DD_LEGACY_MESSAGE_TAG 0` or add `-DDD_LEGACY_MESSAGE_TAG=0` to *Other C Flags*/*OTHER_CFLAGS* in your Xcode project. + +## [swift-log](https://github.com/apple/swift-log) backend + +CocoaLumberjack also ships with a backend implementation for [swift-log](https://github.com/apple/swift-log). +Simply add CocoaLumberjack as dependency to your SPM target (see above) and also add the `CocoaLumberjackSwiftLogBackend` product as dependency to your target. + +You can then use `DDLogHandler` as backend for swift-log, which will forward all messages to CocoaLumberjack's `DDLog`. You will still configure the loggers and log formatters you want via `DDLog`, but writing log messages will be done using `Logger` from swift-log. + +In your own log formatters, you can make use of the `swiftLogInfo` property on `DDLogMessage` to retrieve the details of a message that is logged via swift-log. + +To use swift-log with CocoaLumberjack, take a look the following code snippet to see how to get started. + +```swift +import CocoaLumberjack +import CocoaLumberjackSwiftLogBackend +import Logging + +// In your application's entry point (e.g. AppDelegate): +DDLog.add(DDOSLogger.sharedInstance) // Configure loggers +LoggingSystem.bootstrapWithCocoaLumberjack() // Use CocoaLumberjack as swift-log backend +``` + + +## More information + +- read the [Getting started](Documentation/GettingStarted.md) guide, check out the [FAQ](Documentation/FAQ.md) section or the other [docs](Documentation/) +- if you find issues or want to suggest improvements, create an issue or a pull request +- for all kinds of questions involving CocoaLumberjack, use the [Google group](https://groups.google.com/group/cocoalumberjack) or StackOverflow (use [#lumberjack](https://stackoverflow.com/questions/tagged/lumberjack)). + + +## CocoaLumberjack 3 + +### Migrating to 3.x + +* To be determined + +## Features + +### Lumberjack is Fast & Simple, yet Powerful & Flexible. + +It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically for Objective-C, and takes advantage of features such as multi-threading, grand central dispatch (if available), lockless atomic operations, and the dynamic nature of the Objective-C runtime. + +### Lumberjack is Fast + +In most cases it is an order of magnitude faster than NSLog. + +### Lumberjack is Simple + +It takes as little as a single line of code to configure lumberjack when your application launches. Then simply replace your NSLog statements with DDLog statements and that's about it. (And the DDLog macros have the exact same format and syntax as NSLog, so it's super easy.) + +### Lumberjack is Powerful: + +One log statement can be sent to multiple loggers, meaning you can log to a file and the console simultaneously. Want more? Create your own loggers (it's easy) and send your log statements over the network. Or to a database or distributed file system. The sky is the limit. + +### Lumberjack is Flexible: + +Configure your logging however you want. Change log levels per file (perfect for debugging). Change log levels per logger (verbose console, but concise log file). Change log levels per xcode configuration (verbose debug, but concise release). Have your log statements compiled out of the release build. Customize the number of log levels for your application. Add your own fine-grained logging. Dynamically change log levels during runtime. Choose how & when you want your log files to be rolled. Upload your log files to a central server. Compress archived log files to save disk space... + +## This framework is for you if: + +- You're looking for a way to track down that impossible-to-reproduce bug that keeps popping up in the field. +- You're frustrated with the super short console log on the iPhone. +- You're looking to take your application to the next level in terms of support and stability. +- You're looking for an enterprise level logging solution for your application (Mac or iPhone). + +## Documentation + +- **[Get started using Lumberjack](Documentation/GettingStarted.md)**
+- [Different log levels for Debug and Release builds](Documentation/XcodeTricks.md)
+- [Different log levels for each logger](Documentation/PerLoggerLogLevels.md)
+- [Use colors in the Xcode debugging console](Documentation/XcodeColors.md)
+- [Write your own custom formatters](Documentation/CustomFormatters.md)
+- [FAQ](Documentation/FAQ.md)
+- [Analysis of performance with benchmarks](Documentation/Performance.md)
+- [Common issues you may encounter and their solutions](Documentation/ProblemSolution.md)
+- [AppCode support](Documentation/AppCode-support.md) +- **[Full Lumberjack documentation](Documentation/)**
+ +## Requirements +The current version of Lumberjack requires: +- Xcode 14.1 or later +- Swift 5.5 or later +- macOS 10.13 or later +- iOS 11 or later +- tvOS 11 or later +- watchOS 4 or later + +### Backwards compatibility +- for iOS/tvOS up to 10, watchOS up to 3, macOS up to 10.12, Xcode up to 13 and Swift up to 5.4, use the 3.7.4 version +- for Xcode 11 and Swift up to 5.2, use the 3.6.2 version +- for Xcode 10 and Swift 4.2, use the 3.5.2 version +- for iOS 8, use the 3.6.1 version +- for iOS 6, iOS 7, OS X 10.8, OS X 10.9 and Xcode 9, use the 3.4.2 version +- for iOS 5 and OS X 10.7, use the 3.3 version +- for Xcode 8 and Swift 3, use the 3.2 version +- for Xcode 7.3 and Swift 2.3, use the 2.4.0 version +- for Xcode 7.3 and Swift 2.2, use the 2.3.0 version +- for Xcode 7.2 and 7.1, use the 2.2.0 version +- for Xcode 7.0 or earlier, use the 2.1.0 version +- for Xcode 6 or earlier, use the 2.0.x version +- for OS X < 10.7 support, use the 1.6.0 version + +## Communication + +- If you **need help**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/lumberjack). (Tag 'lumberjack') +- If you'd like to **ask a general question**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/lumberjack). +- If you **found a bug**, open an issue. +- If you **have a feature request**, open an issue. +- If you **want to contribute**, submit a pull request. + +## Data Collection Practices + +Per [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/), Apple is requesting app developers to provide info about their data collection, us SDK maintainers must provide them with the same data. + +### Data collection by the framework + +**By default, CocoaLumberjack does NOT collect any data on its own.** + +[See our Data Collection Practices list.](https://cocoalumberjack.github.io/DataCollection/index.html) + +### Indirect data collection through the framework + +CocoaLumberjack is a logging framework which makes it easy to send those logs to different platforms. + +This is why collecting data might happen quite easily, if app developers include any sensitive data into their log messages. + +**Important note: app developers are fully responsible for any sensitive data collected through our logging system!** + +In consequence, you must comply to the Apple's privacy details policy (mentioned above) and document the ways in which user data is being collected. +Since the number of scenarios where data might be indirectly collected through CocoaLumberjack is quite large, it's up to you, as app developers, to properly review your app's code and identify those cases. +What we can do to help is raise awareness about potential data collection through our framework. + +Private data includes but isn't limited to: + +- user info (name, email, address, ...) +- location info +- contacts +- identifiers (user id, device id, ...) +- app usage data +- performance data +- health and fitness info +- financial info +- sensitive info +- user content +- history (browsing, search, ...) +- purchases +- diagnostics +- ... + +_Example_: `DDLogInfo("User: \(myUser)")` will add the `myUser` info to the logs, so if those are forwarded to a 3rd party or sent via email, that may qualify as data collection. + +## Author + +- [Robbie Hanson](https://github.com/robbiehanson) +- Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UZRA26JPJB3DA) + +## Collaborators +- [Ernesto Rivera](https://github.com/rivera-ernesto) +- [Dmitry Vorobyov](https://github.com/dvor) +- [Bogdan Poplauschi](https://github.com/bpoplauschi) +- [C.W. Betts](https://github.com/MaddTheSane) +- [Koichi Yokota (sushichop)](https://github.com/sushichop) +- [Nick Brook](https://github.com/nrbrook) +- [Florian Friedrich](https://github.com/ffried) +- [Stephan Diederich](https://github.com/diederich) +- [Kent Sutherland](https://github.com/ksuther) +- [Dmitry Lobanov](https://github.com/lolgear) +- [Hakon Hanesand](https://github.com/hhanesand) + +## License +- CocoaLumberjack is available under the BSD 3 license. See the [LICENSE file](LICENSE). + +## Extensions +- [Birch-Lumberjack](https://github.com/gruffins/birch-lumberjack) A remote logger for CocoaLumberjack +- [BugfenderSDK-CocoaLumberjack](https://github.com/bugfender/BugfenderSDK-CocoaLumberjack) A Bugfender logger for CocoaLumberjack +- [LogIO-CocoaLumberjack](https://github.com/s4nchez/LogIO-CocoaLumberjack) A log.io logger for CocoaLumberjack +- [XCDLumberjackNSLogger](https://github.com/0xced/XCDLumberjackNSLogger) CocoaLumberjack logger which sends logs to NSLogger + +## Architecture + +

+ +

diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m new file mode 100644 index 0000000..095e3d0 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m @@ -0,0 +1,57 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if TARGET_OS_OSX + +#import + +@interface CLIColor () { + CGFloat _red, _green, _blue, _alpha; +} + +@end + + +@implementation CLIColor + ++ (instancetype)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha { + __auto_type color = [CLIColor new]; + color->_red = red; + color->_green = green; + color->_blue = blue; + color->_alpha = alpha; + return color; +} + +- (void)getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha { + if (red) { + *red = _red; + } + if (green) { + *green = _green; + } + if (blue) { + *blue = _blue; + } + if (alpha) { + *alpha = _alpha; + } +} + +@end + +#endif diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m new file mode 100644 index 0000000..dfd9bfd --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m @@ -0,0 +1,205 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if !TARGET_OS_WATCH + +#include +#include +#include +#include + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +static __auto_type _cancel = YES; +static __auto_type _captureLevel = DDLogLevelVerbose; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation DDASLLogCapture +#pragma clang diagnostic pop + ++ (void)start { + // Ignore subsequent calls + if (!_cancel) { + return; + } + + _cancel = NO; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + [self captureAslLogs]; + }); +} + ++ (void)stop { + _cancel = YES; +} + ++ (DDLogLevel)captureLevel { + return _captureLevel; +} + ++ (void)setCaptureLevel:(DDLogLevel)level { + _captureLevel = level; +} + +#pragma mark - Private methods + ++ (void)configureAslQuery:(aslmsg)query { + const char param[] = "7"; // ASL_LEVEL_DEBUG, which is everything. We'll rely on regular DDlog log level to filter + + asl_set_query(query, ASL_KEY_LEVEL, param, ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC); + + // Don't retrieve logs from our own DDASLLogger + asl_set_query(query, kDDASLKeyDDLog, kDDASLDDLogValue, ASL_QUERY_OP_NOT_EQUAL); + +#if !TARGET_OS_IPHONE || (defined(TARGET_SIMULATOR) && TARGET_SIMULATOR) + __auto_type processId = [[NSProcessInfo processInfo] processIdentifier]; + char pid[16]; + snprintf(pid, sizeof(pid), "%d", processId); + asl_set_query(query, ASL_KEY_PID, pid, ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_NUMERIC); +#endif +} + ++ (void)aslMessageReceived:(aslmsg)msg { + __auto_type messageCString = asl_get(msg, ASL_KEY_MSG); + if (messageCString == NULL) + return; + + DDLogFlag flag; + BOOL async; + + __auto_type levelCString = asl_get(msg, ASL_KEY_LEVEL); + switch (levelCString? atoi(levelCString) : 0) { + // By default all NSLog's with a ASL_LEVEL_WARNING level + case ASL_LEVEL_EMERG : + case ASL_LEVEL_ALERT : + case ASL_LEVEL_CRIT : flag = DDLogFlagError; async = NO; break; + case ASL_LEVEL_ERR : flag = DDLogFlagWarning; async = YES; break; + case ASL_LEVEL_WARNING : flag = DDLogFlagInfo; async = YES; break; + case ASL_LEVEL_NOTICE : flag = DDLogFlagDebug; async = YES; break; + case ASL_LEVEL_INFO : + case ASL_LEVEL_DEBUG : + default : flag = DDLogFlagVerbose; async = YES; break; + } + + if (!(_captureLevel & flag)) { + return; + } + + // NSString * sender = [NSString stringWithCString:asl_get(msg, ASL_KEY_SENDER) encoding:NSUTF8StringEncoding]; + NSString *message = @(messageCString); + + __auto_type secondsCString = asl_get(msg, ASL_KEY_TIME); + __auto_type nanoCString = asl_get(msg, ASL_KEY_TIME_NSEC); + __auto_type seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970; + __auto_type nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0; + __auto_type totalSeconds = seconds + (nanoSeconds / 1e9); + + __auto_type timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds]; + + __auto_type logMessage = [[DDLogMessage alloc] initWithMessage:message + level:_captureLevel + flag:flag + context:0 + file:@"DDASLLogCapture" + function:nil + line:0 + tag:nil + options:DDLogMessageDontCopyMessage + timestamp:timeStamp]; + + [DDLog log:async message:logMessage]; +} + ++ (void)captureAslLogs { + @autoreleasepool + { + /* + We use ASL_KEY_MSG_ID to see each message once, but there's no + obvious way to get the "next" ID. To bootstrap the process, we'll + search by timestamp until we've seen a message. + */ + + struct timeval timeval = { + .tv_sec = 0 + }; + gettimeofday(&timeval, NULL); + __auto_type startTime = (unsigned long long)timeval.tv_sec; + __block unsigned long long lastSeenID = 0; + + /* + syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message) + through the notify API when it saves messages to the ASL database. + There is some coalescing - currently it is sent at most twice per + second - but there is no documented guarantee about this. In any + case, there may be multiple messages per notification. + + Notify notifications don't carry any payload, so we need to search + for the messages. + */ + int notifyToken = 0; // Can be used to unregister with notify_cancel(). + notify_register_dispatch(kNotifyASLDBUpdate, ¬ifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token) + { + // At least one message has been posted; build a search query. + @autoreleasepool + { + __auto_type query = asl_new(ASL_TYPE_QUERY); + char stringValue[64]; + + if (lastSeenID > 0) { + snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID); + asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC); + } else { + snprintf(stringValue, sizeof stringValue, "%llu", startTime); + asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC); + } + + [self configureAslQuery:query]; + + // Iterate over new messages. + aslmsg msg; + __auto_type response = asl_search(NULL, query); + + while ((msg = asl_next(response))) + { + [self aslMessageReceived:msg]; + + // Keep track of which messages we've seen. + lastSeenID = (unsigned long long)atoll(asl_get(msg, ASL_KEY_MSG_ID)); + } + asl_release(response); + asl_free(query); + + if (_cancel) { + notify_cancel(token); + return; + } + + } + }); + } +} + +@end + +#endif diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m new file mode 100644 index 0000000..dabff3b --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m @@ -0,0 +1,133 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if !TARGET_OS_WATCH + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +#import + +const char* const kDDASLKeyDDLog = "DDLog"; +const char* const kDDASLDDLogValue = "1"; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +static DDASLLogger *sharedInstance; +#pragma clang diagnostic pop + +@interface DDASLLogger () { + aslclient _client; +} + +@end + + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation DDASLLogger +#pragma clang diagnostic pop + ++ (instancetype)sharedInstance { + static dispatch_once_t DDASLLoggerOnceToken; + + dispatch_once(&DDASLLoggerOnceToken, ^{ + sharedInstance = [[[self class] alloc] init]; + }); + + return sharedInstance; +} + +- (instancetype)init { + if (sharedInstance != nil) { + return nil; + } + + if ((self = [super init])) { + // A default asl client is provided for the main thread, + // but background threads need to create their own client. + + _client = asl_open(NULL, "com.apple.console", 0); + } + + return self; +} + +- (DDLoggerName)loggerName { + return DDLoggerNameASL; +} + +- (void)logMessage:(DDLogMessage *)logMessage { + // Skip captured log messages + if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) { + return; + } + + __auto_type message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message; + + if (message) { + __auto_type msg = [message UTF8String]; + + size_t aslLogLevel; + switch (logMessage->_flag) { + // Note: By default ASL will filter anything above level 5 (Notice). + // So our mappings shouldn't go above that level. + case DDLogFlagError : aslLogLevel = ASL_LEVEL_CRIT; break; + case DDLogFlagWarning : aslLogLevel = ASL_LEVEL_ERR; break; + case DDLogFlagInfo : aslLogLevel = ASL_LEVEL_WARNING; break; // Regular NSLog's level + case DDLogFlagDebug : + case DDLogFlagVerbose : + default : aslLogLevel = ASL_LEVEL_NOTICE; break; + } + + static char const *const level_strings[] = { "0", "1", "2", "3", "4", "5", "6", "7" }; + + // NSLog uses the current euid to set the ASL_KEY_READ_UID. + const __auto_type readUID = geteuid(); + + char readUIDString[16]; +#ifndef NS_BLOCK_ASSERTIONS + __auto_type l = (size_t)snprintf(readUIDString, sizeof(readUIDString), "%d", readUID); +#else + snprintf(readUIDString, sizeof(readUIDString), "%d", readUID); +#endif + + NSAssert(l < sizeof(readUIDString), + @"Formatted euid is too long."); + NSAssert(aslLogLevel < (sizeof(level_strings) / sizeof(level_strings[0])), + @"Unhandled ASL log level."); + + __auto_type m = asl_new(ASL_TYPE_MSG); + if (m != NULL) { + if (asl_set(m, ASL_KEY_LEVEL, level_strings[aslLogLevel]) == 0 && + asl_set(m, ASL_KEY_MSG, msg) == 0 && + asl_set(m, ASL_KEY_READ_UID, readUIDString) == 0 && + asl_set(m, kDDASLKeyDDLog, kDDASLDDLogValue) == 0) { + asl_send(_client, m); + } + asl_free(m); + } + //TODO handle asl_* failures non-silently? + } +} + +@end + +#endif diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m new file mode 100644 index 0000000..fd30648 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m @@ -0,0 +1,636 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +@interface DDAbstractDatabaseLogger () + +- (void)destroySaveTimer; +- (void)updateAndResumeSaveTimer; +- (void)createSuspendedSaveTimer; +- (void)destroyDeleteTimer; +- (void)updateDeleteTimer; +- (void)createAndStartDeleteTimer; + +@end + +#pragma mark - + +@implementation DDAbstractDatabaseLogger + +- (instancetype)init { + if ((self = [super init])) { + _saveThreshold = 500; + _saveInterval = 60; // 60 seconds + _maxAge = (60 * 60 * 24 * 7); // 7 days + _deleteInterval = (60 * 5); // 5 minutes + } + + return self; +} + +- (void)dealloc { + [self destroySaveTimer]; + [self destroyDeleteTimer]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Override Me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)db_log:(__unused DDLogMessage *)logMessage { + // Override me and add your implementation. + // + // Return YES if an item was added to the buffer. + // Return NO if the logMessage was ignored. + + return NO; +} + +- (void)db_save { + // Override me and add your implementation. +} + +- (void)db_delete { + // Override me and add your implementation. +} + +- (void)db_saveAndDelete { + // Override me and add your implementation. +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Private API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)performSaveAndSuspendSaveTimer { + if (_unsavedCount > 0) { + if (_deleteOnEverySave) { + [self db_saveAndDelete]; + } else { + [self db_save]; + } + } + + _unsavedCount = 0; + _unsavedTime = 0; + + if (_saveTimer != NULL && _saveTimerSuspended == 0) { + dispatch_suspend(_saveTimer); + _saveTimerSuspended = 1; + } +} + +- (void)performDelete { + if (_maxAge > 0.0) { + [self db_delete]; + + _lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Timers +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)destroySaveTimer { + if (_saveTimer != NULL) { + dispatch_source_cancel(_saveTimer); + + // Must activate a timer before releasing it (or it will crash) + if (_saveTimerSuspended < 0) { + dispatch_activate(_saveTimer); + } else if (_saveTimerSuspended > 0) { + dispatch_resume(_saveTimer); + } + +#if !OS_OBJECT_USE_OBJC + dispatch_release(_saveTimer); +#endif + _saveTimer = NULL; + _saveTimerSuspended = 0; + } +} + +- (void)updateAndResumeSaveTimer { + if ((_saveTimer != NULL) && (_saveInterval > 0.0) && (_unsavedTime > 0)) { + __auto_type interval = (uint64_t)(_saveInterval * (NSTimeInterval) NSEC_PER_SEC); + __auto_type startTime = dispatch_time(_unsavedTime, (int64_t)interval); + + dispatch_source_set_timer(_saveTimer, startTime, interval, 1ull * NSEC_PER_SEC); + + if (_saveTimerSuspended < 0) { + dispatch_activate(_saveTimer); + _saveTimerSuspended = 0; + } else if (_saveTimerSuspended > 0) { + dispatch_resume(_saveTimer); + _saveTimerSuspended = 0; + } + } +} + +- (void)createSuspendedSaveTimer { + if ((_saveTimer == NULL) && (_saveInterval > 0.0)) { + _saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue); + + dispatch_source_set_event_handler(_saveTimer, ^{ @autoreleasepool { + [self performSaveAndSuspendSaveTimer]; + } }); + + _saveTimerSuspended = -1; + } +} + +- (void)destroyDeleteTimer { + if (_deleteTimer != NULL) { + dispatch_source_cancel(_deleteTimer); +#if !OS_OBJECT_USE_OBJC + dispatch_release(_deleteTimer); +#endif + _deleteTimer = NULL; + } +} + +- (void)updateDeleteTimer { + if ((_deleteTimer != NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) { + __auto_type interval = (int64_t)(_deleteInterval * (NSTimeInterval) NSEC_PER_SEC); + dispatch_time_t startTime; + + if (_lastDeleteTime > 0) { + startTime = dispatch_time(_lastDeleteTime, interval); + } else { + startTime = dispatch_time(DISPATCH_TIME_NOW, interval); + } + + dispatch_source_set_timer(_deleteTimer, startTime, (uint64_t)interval, 1ull * NSEC_PER_SEC); + } +} + +- (void)createAndStartDeleteTimer { + if ((_deleteTimer == NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) { + _deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue); + + if (_deleteTimer != NULL) { + dispatch_source_set_event_handler(_deleteTimer, ^{ @autoreleasepool { + [self performDelete]; + } }); + + [self updateDeleteTimer]; + + // We are sure that -updateDeleteTimer did call dispatch_source_set_timer() + // since it has the same guards on _deleteInterval and _maxAge + dispatch_activate(_deleteTimer); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSUInteger)saveThreshold { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSUInteger result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_saveThreshold; + }); + }); + + return result; +} + +- (void)setSaveThreshold:(NSUInteger)threshold { + dispatch_block_t block = ^{ + @autoreleasepool { + if (self->_saveThreshold != threshold) { + self->_saveThreshold = threshold; + + // Since the saveThreshold has changed, + // we check to see if the current unsavedCount has surpassed the new threshold. + // + // If it has, we immediately save the log. + + if ((self->_unsavedCount >= self->_saveThreshold) && (self->_saveThreshold > 0)) { + [self performSaveAndSuspendSaveTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)saveInterval { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSTimeInterval result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_saveInterval; + }); + }); + + return result; +} + +- (void)setSaveInterval:(NSTimeInterval)interval { + __auto_type block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* saveInterval != interval */ islessgreater(self->_saveInterval, interval)) { + self->_saveInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the saveInterval was previously enabled and it just got disabled, + // then we need to stop the saveTimer. (And we might as well release it.) + // + // 2. If the saveInterval was previously disabled and it just got enabled, + // then we need to setup the saveTimer. (Plus we might need to do an immediate save.) + // + // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate save.) + + if (self->_saveInterval > 0.0) { + if (self->_saveTimer == NULL) { + // Handles #2 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self createSuspendedSaveTimer]; + [self updateAndResumeSaveTimer]; + } else { + // Handles #3 + // Handles #4 + // + // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateAndResumeSaveTimer]; + } + } else if (self->_saveTimer) { + // Handles #1 + + [self destroySaveTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)maxAge { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSTimeInterval result; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_maxAge; + }); + }); + + return result; +} + +- (void)setMaxAge:(NSTimeInterval)interval { + __auto_type block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* maxAge != interval */ islessgreater(self->_maxAge, interval)) { + __auto_type oldMaxAge = self->_maxAge; + __auto_type newMaxAge = interval; + + self->_maxAge = interval; + + // There are several cases we need to handle here. + // + // 1. If the maxAge was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the maxAge was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the maxAge was increased, + // then we don't need to do anything. + // + // 4. If the maxAge was decreased, + // then we should do an immediate delete. + + __auto_type shouldDeleteNow = NO; + + if (oldMaxAge > 0.0) { + if (newMaxAge <= 0.0) { + // Handles #1 + + [self destroyDeleteTimer]; + } else if (oldMaxAge > newMaxAge) { + // Handles #4 + shouldDeleteNow = YES; + } + } else if (newMaxAge > 0.0) { + // Handles #2 + shouldDeleteNow = YES; + } + + if (shouldDeleteNow) { + [self performDelete]; + + if (self->_deleteTimer) { + [self updateDeleteTimer]; + } else { + [self createAndStartDeleteTimer]; + } + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (NSTimeInterval)deleteInterval { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block NSTimeInterval result; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_deleteInterval; + }); + }); + + return result; +} + +- (void)setDeleteInterval:(NSTimeInterval)interval { + __auto_type block = ^{ + @autoreleasepool { + // C99 recommended floating point comparison macro + // Read: isLessThanOrGreaterThan(floatA, floatB) + + if (/* deleteInterval != interval */ islessgreater(self->_deleteInterval, interval)) { + self->_deleteInterval = interval; + + // There are several cases we need to handle here. + // + // 1. If the deleteInterval was previously enabled and it just got disabled, + // then we need to stop the deleteTimer. (And we might as well release it.) + // + // 2. If the deleteInterval was previously disabled and it just got enabled, + // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) + // + // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date. + // + // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. + // (Plus we might need to do an immediate delete.) + + if (self->_deleteInterval > 0.0) { + if (self->_deleteTimer == NULL) { + // Handles #2 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a delete is needed the timer will fire immediately. + + [self createAndStartDeleteTimer]; + } else { + // Handles #3 + // Handles #4 + // + // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, + // if a save is needed the timer will fire immediately. + + [self updateDeleteTimer]; + } + } else if (self->_deleteTimer) { + // Handles #1 + + [self destroyDeleteTimer]; + } + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (BOOL)deleteOnEverySave { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block BOOL result; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_deleteOnEverySave; + }); + }); + + return result; +} + +- (void)setDeleteOnEverySave:(BOOL)flag { + __auto_type block = ^{ + self->_deleteOnEverySave = flag; + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Public API +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)savePendingLogEntries { + __auto_type block = ^{ + @autoreleasepool { + [self performSaveAndSuspendSaveTimer]; + } + }; + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_async(self.loggerQueue, block); + } +} + +- (void)deleteOldLogEntries { + __auto_type block = ^{ + @autoreleasepool { + [self performDelete]; + } + }; + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_async(self.loggerQueue, block); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogger +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)didAddLogger { + // If you override me be sure to invoke [super didAddLogger]; + [self createSuspendedSaveTimer]; + [self createAndStartDeleteTimer]; +} + +- (void)willRemoveLogger { + // If you override me be sure to invoke [super willRemoveLogger]; + [self performSaveAndSuspendSaveTimer]; + [self destroySaveTimer]; + [self destroyDeleteTimer]; +} + +- (void)logMessage:(DDLogMessage *)logMessage { + if ([self db_log:logMessage]) { + __auto_type firstUnsavedEntry = (++_unsavedCount == 1); + + if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) { + [self performSaveAndSuspendSaveTimer]; + } else if (firstUnsavedEntry) { + _unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0); + [self updateAndResumeSaveTimer]; + } + } +} + +- (void)flush { + // This method is invoked by DDLog's flushLog method. + // + // It is called automatically when the application quits, + // or if the developer invokes DDLog's flushLog method prior to crashing or something. + [self performSaveAndSuspendSaveTimer]; +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h new file mode 100644 index 0000000..a092feb --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h @@ -0,0 +1,31 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DDFileLogger (Internal) + +- (void)logData:(NSData *)data; + +// Will assert if used outside logger's queue. +- (void)lt_logData:(NSData *)data; + +- (nullable NSData *)lt_dataForMessage:(DDLogMessage *)message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m new file mode 100644 index 0000000..ba2a89d --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m @@ -0,0 +1,1900 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import +#import +#import +#import + +#import "DDFileLogger+Internal.h" + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use primitive logging macros around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#ifndef DD_NSLOG_LEVEL + #define DD_NSLOG_LEVEL 2 +#endif + +#define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0) + + +#if TARGET_OS_IPHONE +BOOL doesAppRunInBackground(void); +#endif + +unsigned long long const kDDDefaultLogMaxFileSize = 1024 * 1024; // 1 MB +NSTimeInterval const kDDDefaultLogRollingFrequency = 60 * 60 * 24; // 24 Hours +NSUInteger const kDDDefaultLogMaxNumLogFiles = 5; // 5 Files +unsigned long long const kDDDefaultLogFilesDiskQuota = 20 * 1024 * 1024; // 20 MB + +NSTimeInterval const kDDRollingLeeway = 1.0; // 1s + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDFileLogPlainTextMessageSerializer + +- (instancetype)init { + return [super init]; +} + +- (NSData *)dataForString:(NSString *)string originatingFromMessage:(DDLogMessage *)message { + return [string dataUsingEncoding:NSUTF8StringEncoding] ?: [NSData data]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLogFileManagerDefault () { + NSDateFormatter *_fileDateFormatter; + NSUInteger _maximumNumberOfLogFiles; + unsigned long long _logFilesDiskQuota; + NSString *_logsDirectory; + BOOL _wasAddedToLogger; +#if TARGET_OS_IPHONE + NSFileProtectionType _defaultFileProtectionLevel; +#endif +} + +@end + +@implementation DDLogFileManagerDefault + +@synthesize fileManager = _fileManager; +@synthesize maximumNumberOfLogFiles = _maximumNumberOfLogFiles; +@synthesize logFilesDiskQuota = _logFilesDiskQuota; +@synthesize logMessageSerializer = _logMessageSerializer; + +- (instancetype)initWithLogsDirectory:(nullable NSString *)aLogsDirectory { + if ((self = [super init])) { + _maximumNumberOfLogFiles = kDDDefaultLogMaxNumLogFiles; + _logFilesDiskQuota = kDDDefaultLogFilesDiskQuota; + _wasAddedToLogger = NO; + + _fileDateFormatter = [[NSDateFormatter alloc] init]; + [_fileDateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; + [_fileDateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + [_fileDateFormatter setDateFormat: @"yyyy'-'MM'-'dd'--'HH'-'mm'-'ss'-'SSS'"]; + + _fileManager = [NSFileManager defaultManager]; + + if (aLogsDirectory.length > 0) { + _logsDirectory = [aLogsDirectory copy]; + } else { + _logsDirectory = [[self defaultLogsDirectory] copy]; + } + + _logMessageSerializer = [[DDFileLogPlainTextMessageSerializer alloc] init]; + + NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]); + NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]); + } + + return self; +} + +#if TARGET_OS_IPHONE +- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory + defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel { + + if ((self = [self initWithLogsDirectory:logsDirectory])) { + if ([fileProtectionLevel isEqualToString:NSFileProtectionNone] || + [fileProtectionLevel isEqualToString:NSFileProtectionComplete] || + [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUnlessOpen] || + [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) { + _defaultFileProtectionLevel = fileProtectionLevel; + } + } + + return self; +} +#endif + +- (instancetype)init { + return [self initWithLogsDirectory:nil]; +} + +- (void)didAddToFileLogger:(DDFileLogger *)fileLogger { + _wasAddedToLogger = YES; +} + +- (void)deleteOldFilesForConfigurationChange { + if (!_wasAddedToLogger) return; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + @autoreleasepool { + // See method header for queue reasoning. + [self deleteOldLogFilesWithError:nil]; + } + }); +} + +- (void)setLogFilesDiskQuota:(unsigned long long)logFilesDiskQuota { + if (_logFilesDiskQuota != logFilesDiskQuota) { + _logFilesDiskQuota = logFilesDiskQuota; + NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: logFilesDiskQuota"); + [self deleteOldFilesForConfigurationChange]; + } +} + +- (void)setMaximumNumberOfLogFiles:(NSUInteger)maximumNumberOfLogFiles { + if (_maximumNumberOfLogFiles != maximumNumberOfLogFiles) { + _maximumNumberOfLogFiles = maximumNumberOfLogFiles; + NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles"); + [self deleteOldFilesForConfigurationChange]; + } +} + +#if TARGET_OS_IPHONE +- (NSFileProtectionType)logFileProtection { + if (_defaultFileProtectionLevel.length > 0) { + return _defaultFileProtectionLevel; + } else if (doesAppRunInBackground()) { + return NSFileProtectionCompleteUntilFirstUserAuthentication; + } else { + return NSFileProtectionCompleteUnlessOpen; + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Deleting +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Deletes archived log files that exceed the maximumNumberOfLogFiles or logFilesDiskQuota configuration values. + * Method may take a while to execute since we're performing IO. It's not critical that this is synchronized with + * log output, since the files we're deleting are all archived and not in use, therefore this method is called on a + * background queue. + **/ +- (BOOL)deleteOldLogFilesWithError:(NSError *__autoreleasing _Nullable *)error { + NSLogVerbose(@"DDLogFileManagerDefault: %@", NSStringFromSelector(_cmd)); + + if (error) *error = nil; + + __auto_type sortedLogFileInfos = [self sortedLogFileInfos]; + NSUInteger firstIndexToDelete = NSNotFound; + + const unsigned long long diskQuota = self.logFilesDiskQuota; + const NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles; + + if (diskQuota) { + unsigned long long used = 0; + + for (NSUInteger i = 0; i < sortedLogFileInfos.count; i++) { + DDLogFileInfo *info = sortedLogFileInfos[i]; + used += info.fileSize; + + if (used > diskQuota) { + firstIndexToDelete = i; + break; + } + } + } + + if (maxNumLogFiles) { + if (firstIndexToDelete == NSNotFound) { + firstIndexToDelete = maxNumLogFiles; + } else { + firstIndexToDelete = MIN(firstIndexToDelete, maxNumLogFiles); + } + } + + if (firstIndexToDelete == 0) { + // Do we consider the first file? + // We are only supposed to be deleting archived files. + // In most cases, the first file is likely the log file that is currently being written to. + // So in most cases, we do not want to consider this file for deletion. + + if (sortedLogFileInfos.count > 0) { + if (!sortedLogFileInfos[0].isArchived) { + // Don't delete active file. + firstIndexToDelete++; + } + } + } + + if (firstIndexToDelete != NSNotFound) { + __auto_type fileManager = self.fileManager; + // removing all log files starting with firstIndexToDelete + for (NSUInteger i = firstIndexToDelete; i < sortedLogFileInfos.count; i++) { + __auto_type logFileInfo = sortedLogFileInfos[i]; + + __autoreleasing NSError *deletionError = nil; + __auto_type success = [fileManager removeItemAtPath:logFileInfo.filePath error:&deletionError]; + if (success) { + NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName); + } else { + NSLogError(@"DDLogFileManagerDefault: Error deleting file %@", deletionError); + if (error) { + *error = deletionError; + return NO; // If we were given an error, stop after the first failure! + } + } + } + } + + return YES; +} + +- (BOOL)cleanupLogFilesWithError:(NSError *__autoreleasing _Nullable *)error { + return [self deleteOldLogFilesWithError:error]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Log Files +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the path to the default logs directory. + * If the logs directory doesn't exist, this method automatically creates it. + **/ +- (NSString *)defaultLogsDirectory { +#if TARGET_OS_IPHONE + __auto_type paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + __auto_type baseDir = paths.firstObject; + __auto_type logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"]; +#else + __auto_type appName = [[NSProcessInfo processInfo] processName]; + __auto_type paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + __auto_type basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory(); + __auto_type logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName]; +#endif + + return logsDirectory; +} + +- (NSString *)logsDirectory { + // We could do this check once, during initialization, and not bother again. + // But this way the code continues to work if the directory gets deleted while the code is running. + + NSAssert(_logsDirectory.length > 0, @"Directory must be set."); + + __autoreleasing NSError *error = nil; + __auto_type success = [self.fileManager createDirectoryAtPath:_logsDirectory + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (!success) { + NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", error); + } + + return _logsDirectory; +} + +- (BOOL)isLogFile:(NSString *)fileName { + __auto_type appName = [self applicationName]; + + // We need to add a space to the name as otherwise we could match applications that have the name prefix. + return [fileName hasPrefix:[appName stringByAppendingString:@" "]] && [fileName hasSuffix:@".log"]; +} + +// if you change formatter, then change sortedLogFileInfos method also accordingly +- (NSDateFormatter *)logFileDateFormatter { + return _fileDateFormatter; +} + +- (NSArray *)unsortedLogFilePaths { + __auto_type logsDirectory = [self logsDirectory]; + + __autoreleasing NSError *error = nil; + __auto_type fileNames = [self.fileManager contentsOfDirectoryAtPath:logsDirectory error:&error]; + if (!fileNames && error) { + NSLogError(@"DDFileLogManagerDefault: Error listing log file directory: %@", error); + return [[NSArray alloc] init]; + } + + __auto_type unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]]; + + for (NSString *fileName in fileNames) { + // Filter out any files that aren't log files. (Just for extra safety) +#if TARGET_IPHONE_SIMULATOR + // This is only used on the iPhone simulator for backward compatibility reason. + // + // In case of iPhone simulator there can be 'archived' extension. isLogFile: + // method knows nothing about it. Thus removing it for this method. + __auto_type theFileName = [fileName stringByReplacingOccurrencesOfString:@".archived" + withString:@""]; + + if ([self isLogFile:theFileName]) +#else + if ([self isLogFile:fileName]) +#endif + { + __auto_type filePath = [logsDirectory stringByAppendingPathComponent:fileName]; + [unsortedLogFilePaths addObject:filePath]; + } + } + + return unsortedLogFilePaths; +} + +- (NSArray *)unsortedLogFileNames { + __auto_type unsortedLogFilePaths = [self unsortedLogFilePaths]; + __auto_type unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]]; + + for (NSString *filePath in unsortedLogFilePaths) { + [unsortedLogFileNames addObject:[filePath lastPathComponent]]; + } + + return unsortedLogFileNames; +} + +- (NSArray *)unsortedLogFileInfos { + __auto_type unsortedLogFilePaths = [self unsortedLogFilePaths]; + __auto_type unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]]; + + for (NSString *filePath in unsortedLogFilePaths) { + __auto_type logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath]; + [unsortedLogFileInfos addObject:logFileInfo]; + } + + return unsortedLogFileInfos; +} + +- (NSArray *)sortedLogFilePaths { + __auto_type sortedLogFileInfos = [self sortedLogFileInfos]; + __auto_type sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]]; + + for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) { + [sortedLogFilePaths addObject:[logFileInfo filePath]]; + } + + return sortedLogFilePaths; +} + +- (NSArray *)sortedLogFileNames { + __auto_type sortedLogFileInfos = [self sortedLogFileInfos]; + __auto_type sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]]; + + for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) { + [sortedLogFileNames addObject:[logFileInfo fileName]]; + } + + return sortedLogFileNames; +} + +- (NSArray *)sortedLogFileInfos { + return [[self unsortedLogFileInfos] sortedArrayUsingComparator:^NSComparisonResult(DDLogFileInfo *obj1, + DDLogFileInfo *obj2) { + NSDate *date1 = [NSDate date]; + NSDate *date2 = [NSDate date]; + + __auto_type arrayComponent = [[obj1 fileName] componentsSeparatedByString:@" "]; + if (arrayComponent.count > 0) { + NSString *stringDate = arrayComponent.lastObject; + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""]; +#if TARGET_IPHONE_SIMULATOR + // This is only used on the iPhone simulator for backward compatibility reason. + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""]; +#endif + date1 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj1 creationDate]; + } + + arrayComponent = [[obj2 fileName] componentsSeparatedByString:@" "]; + if (arrayComponent.count > 0) { + NSString *stringDate = arrayComponent.lastObject; + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""]; +#if TARGET_IPHONE_SIMULATOR + // This is only used on the iPhone simulator for backward compatibility reason. + stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""]; +#endif + date2 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj2 creationDate]; + } + + return [date2 compare:date1 ?: [NSDate date]]; + }]; + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Creation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// If you change newLogFileName, then change `isLogFile:` method also accordingly. +- (NSString *)newLogFileName { + __auto_type appName = [self applicationName]; + __auto_type dateFormatter = [self logFileDateFormatter]; + __auto_type formattedDate = [dateFormatter stringFromDate:[NSDate date]]; + + return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate]; +} + +- (nullable NSString *)logFileHeader { + return nil; +} + +- (NSData *)logFileHeaderData { + NSString *fileHeaderStr = [self logFileHeader]; + + if (fileHeaderStr.length == 0) { + return nil; + } + + if (![fileHeaderStr hasSuffix:@"\n"]) { + fileHeaderStr = [fileHeaderStr stringByAppendingString:@"\n"]; + } + + return [_logMessageSerializer dataForString:fileHeaderStr originatingFromMessage:nil]; +} + +- (NSString *)createNewLogFileWithError:(NSError *__autoreleasing _Nullable *)error { + static NSUInteger MAX_ALLOWED_ERROR = 5; + + __auto_type fileName = [self newLogFileName]; + __auto_type logsDirectory = [self logsDirectory]; + __auto_type fileHeader = [self logFileHeaderData] ?: [NSData data]; + + NSString *baseName = nil; + NSString *extension; + NSUInteger attempt = 1; + NSUInteger criticalErrors = 0; + NSError *lastCriticalError; + + if (error) *error = nil; + do { + if (criticalErrors >= MAX_ALLOWED_ERROR) { + NSLogError(@"DDLogFileManagerDefault: Bailing file creation, encountered %ld errors.", + (unsigned long)criticalErrors); + if (error) *error = lastCriticalError; + return nil; + } + + NSString *actualFileName; + if (attempt > 1) { + if (baseName == nil) { + baseName = [fileName stringByDeletingPathExtension]; + extension = [fileName pathExtension]; + } + + actualFileName = [baseName stringByAppendingFormat:@" %lu", (unsigned long)attempt]; + if (extension.length) { + actualFileName = [actualFileName stringByAppendingPathExtension:extension]; + } + } else { + actualFileName = fileName; + } + + __auto_type filePath = [logsDirectory stringByAppendingPathComponent:actualFileName]; + + __autoreleasing NSError *currentError = nil; + __auto_type success = [fileHeader writeToFile:filePath options:NSDataWritingAtomic error:¤tError]; + +#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST + if (success) { + // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen. + // + // But in case if app is able to launch from background we need to have an ability to open log file any time we + // want (even if device is locked). Thats why that attribute have to be changed to + // NSFileProtectionCompleteUntilFirstUserAuthentication. + NSDictionary *attributes = @{NSFileProtectionKey: [self logFileProtection]}; + success = [self.fileManager setAttributes:attributes + ofItemAtPath:filePath + error:¤tError]; + } +#endif + + if (success) { + NSLogVerbose(@"DDLogFileManagerDefault: Created new log file: %@", actualFileName); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + // Since we just created a new log file, we may need to delete some old log files + // Note that we don't on errors here! The new log file was created, so this method technically succeeded! + [self deleteOldLogFilesWithError:nil]; + }); + return filePath; + } else if (currentError.code == NSFileWriteFileExistsError) { + attempt++; + } else { + NSLogError(@"DDLogFileManagerDefault: Critical error while creating log file: %@", currentError); + criticalErrors++; + lastCriticalError = currentError; + } + } while (YES); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utility +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSString *)applicationName { + static NSString *_appName; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; + + if (_appName.length == 0) { + _appName = [[NSProcessInfo processInfo] processName]; + } + + if (_appName.length == 0) { + _appName = @""; + } + }); + + return _appName; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLogFileFormatterDefault () { + NSDateFormatter *_dateFormatter; +} + +@end + +@implementation DDLogFileFormatterDefault + +- (instancetype)init { + return [self initWithDateFormatter:nil]; +} + +- (instancetype)initWithDateFormatter:(nullable NSDateFormatter *)aDateFormatter { + if ((self = [super init])) { + if (aDateFormatter) { + _dateFormatter = aDateFormatter; + } else { + _dateFormatter = [[NSDateFormatter alloc] init]; + [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style + [_dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; + [_dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + [_dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"]; + } + } + + return self; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + __auto_type dateAndTime = [_dateFormatter stringFromDate:logMessage->_timestamp]; + // Note: There are two spaces between the date and the message. + return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->_message]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDFileLogger () { + id _logFileManager; + + DDLogFileInfo *_currentLogFileInfo; + NSFileHandle *_currentLogFileHandle; + + dispatch_source_t _currentLogFileVnode; + + NSTimeInterval _rollingFrequency; + dispatch_source_t _rollingTimer; + + unsigned long long _maximumFileSize; + + dispatch_queue_t _completionQueue; +} + +@end + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincomplete-implementation" +@implementation DDFileLogger +#pragma clang diagnostic pop + +- (instancetype)init { + return [self initWithLogFileManager:[[DDLogFileManagerDefault alloc] init] + completionQueue:nil]; +} + +- (instancetype)initWithLogFileManager:(id)logFileManager { + return [self initWithLogFileManager:logFileManager completionQueue:nil]; +} + +- (instancetype)initWithLogFileManager:(id )aLogFileManager + completionQueue:(nullable dispatch_queue_t)dispatchQueue { + if ((self = [super init])) { + _completionQueue = dispatchQueue ?: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + _maximumFileSize = kDDDefaultLogMaxFileSize; + _rollingFrequency = kDDDefaultLogRollingFrequency; + _automaticallyAppendNewlineForCustomFormatters = YES; + + _logFileManager = aLogFileManager; + _logFormatter = [DDLogFileFormatterDefault new]; + + if ([_logFileManager respondsToSelector:@selector(didAddToFileLogger:)]) { + [_logFileManager didAddToFileLogger:self]; + } + } + + return self; +} + +- (void)lt_cleanup { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_currentLogFileHandle != nil) { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error); + } + success = [_currentLogFileHandle closeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to close file: %@", error); + } + } else { + @try { + [_currentLogFileHandle synchronizeFile]; + } + @catch (NSException *exception) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception); + } + [_currentLogFileHandle closeFile]; + } + _currentLogFileHandle = nil; + } + + if (_currentLogFileVnode) { + dispatch_source_cancel(_currentLogFileVnode); + _currentLogFileVnode = NULL; + } + + if (_rollingTimer) { + dispatch_source_cancel(_rollingTimer); + _rollingTimer = NULL; + } +} + +- (void)dealloc { + if (self.isOnInternalLoggerQueue) { + [self lt_cleanup]; + } else { + dispatch_sync(self.loggerQueue, ^{ + [self lt_cleanup]; + }); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Properties +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (unsigned long long)maximumFileSize { + __block unsigned long long result; + + __auto_type block = ^{ + result = self->_maximumFileSize; + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + + return result; +} + +- (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize { + __auto_type block = ^{ + @autoreleasepool { + self->_maximumFileSize = newMaximumFileSize; + if (self->_currentLogFileHandle != nil) { + [self lt_maybeRollLogFileDueToSize]; + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the maximumFileSize variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); +} + +- (NSTimeInterval)rollingFrequency { + __block NSTimeInterval result; + + __auto_type block = ^{ + result = self->_rollingFrequency; + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation should access the rollingFrequency variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + + return result; +} + +- (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency { + __auto_type block = ^{ + @autoreleasepool { + self->_rollingFrequency = newRollingFrequency; + if (self->_currentLogFileHandle != nil) { + [self lt_maybeRollLogFileDueToAge]; + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation should access the rollingFrequency variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Rolling +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)lt_scheduleTimerToRollLogFileDueToAge { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_rollingTimer) { + dispatch_source_cancel(_rollingTimer); + _rollingTimer = NULL; + } + + if (_currentLogFileInfo == nil || _rollingFrequency <= 0.0) { + return; + } + + __auto_type logFileCreationDate = [_currentLogFileInfo creationDate]; + __auto_type frequency = MIN(_rollingFrequency, DBL_MAX - [logFileCreationDate timeIntervalSinceReferenceDate]); + __auto_type logFileRollingDate = [logFileCreationDate dateByAddingTimeInterval:frequency]; + + NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge"); + NSLogVerbose(@"DDFileLogger: logFileCreationDate : %@", logFileCreationDate); + NSLogVerbose(@"DDFileLogger: actual rollingFrequency: %f", frequency); + NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate); + + _rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _loggerQueue); + + __weak __auto_type weakSelf = self; + dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool { + [weakSelf lt_maybeRollLogFileDueToAge]; + } }); + +#if !OS_OBJECT_USE_OBJC + dispatch_source_t theRollingTimer = _rollingTimer; + dispatch_source_set_cancel_handler(_rollingTimer, ^{ + dispatch_release(theRollingTimer); + }); +#endif + + static NSTimeInterval const kDDMaxTimerDelay = LLONG_MAX / NSEC_PER_SEC; + __auto_type delay = (int64_t)(MIN([logFileRollingDate timeIntervalSinceNow], kDDMaxTimerDelay) * (NSTimeInterval)NSEC_PER_SEC); + __auto_type fireTime = dispatch_walltime(NULL, delay); // `NULL` uses `gettimeofday` internally + + dispatch_source_set_timer(_rollingTimer, fireTime, DISPATCH_TIME_FOREVER, (uint64_t)kDDRollingLeeway * NSEC_PER_SEC); + dispatch_activate(_rollingTimer); +} + +- (void)rollLogFile { + [self rollLogFileWithCompletionBlock:nil]; +} + +- (void)rollLogFileWithCompletionBlock:(nullable void (^)(void))completionBlock { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + __auto_type block = ^{ + @autoreleasepool { + [self lt_rollLogFileNow]; + + if (completionBlock) { + dispatch_async(self->_completionQueue, ^{ + completionBlock(); + }); + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)lt_rollLogFileNow { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + NSLogVerbose(@"DDFileLogger: %@", NSStringFromSelector(_cmd)); + + if (_currentLogFileHandle == nil) { + return; + } + + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error); + } + success = [_currentLogFileHandle closeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to close file: %@", error); + } + } else { + @try { + [_currentLogFileHandle synchronizeFile]; + } + @catch (NSException *exception) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception); + } + [_currentLogFileHandle closeFile]; + } + _currentLogFileHandle = nil; + + _currentLogFileInfo.isArchived = YES; + + const __auto_type logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)]; + const __auto_type logFileManagerRespondsToSelector = (logFileManagerRespondsToNewArchiveSelector + || [_logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]); + NSString *archivedFilePath = (logFileManagerRespondsToSelector) ? [_currentLogFileInfo.filePath copy] : nil; + _currentLogFileInfo = nil; + + if (logFileManagerRespondsToSelector) { + dispatch_block_t block; + if (logFileManagerRespondsToNewArchiveSelector) { + block = ^{ + [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:YES]; + }; + } else { + block = ^{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self->_logFileManager didRollAndArchiveLogFile:archivedFilePath]; +#pragma clang diagnostic pop + }; + } + dispatch_async(_completionQueue, block); + } + + if (_currentLogFileVnode) { + dispatch_source_cancel(_currentLogFileVnode); + _currentLogFileVnode = nil; + } + + if (_rollingTimer) { + dispatch_source_cancel(_rollingTimer); + _rollingTimer = nil; + } +} + +- (void)lt_maybeRollLogFileDueToAge { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_rollingFrequency > 0.0 && (_currentLogFileInfo.age + kDDRollingLeeway) >= _rollingFrequency) { + NSLogVerbose(@"DDFileLogger: Rolling log file due to age..."); + [self lt_rollLogFileNow]; + } else { + [self lt_scheduleTimerToRollLogFileDueToAge]; + } +} + +- (void)lt_maybeRollLogFileDueToSize { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + // This method is called from logMessage. + // Keep it FAST. + + // Note: Use direct access to maximumFileSize variable. + // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons). + + if (_currentLogFileHandle != nil && _maximumFileSize > 0) { + unsigned long long fileSize; + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle getOffset:&fileSize error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to get offset: %@", error); + return; + } + } else { + fileSize = [_currentLogFileHandle offsetInFile]; + } + + if (fileSize >= _maximumFileSize) { + NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize); + + [self lt_rollLogFileNow]; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark File Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)lt_shouldLogFileBeArchived:(DDLogFileInfo *)mostRecentLogFileInfo { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if ([self shouldArchiveRecentLogFileInfo:mostRecentLogFileInfo]) { + return YES; + } else if (_maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= _maximumFileSize) { + return YES; + } else if (_rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= _rollingFrequency) { + return YES; + } + +#if TARGET_OS_IPHONE + // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen. + // + // But in case if app is able to launch from background we need to have an ability to open log file any time we + // want (even if device is locked). Thats why that attribute have to be changed to + // NSFileProtectionCompleteUntilFirstUserAuthentication. + // + // If previous log was created when app wasn't running in background, but now it is - we archive it and create + // a new one. + // + // If user has overwritten to NSFileProtectionNone there is no need to create a new one. + if (doesAppRunInBackground()) { + NSFileProtectionType key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey]; + __auto_type isUntilFirstAuth = [key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]; + __auto_type isNone = [key isEqualToString:NSFileProtectionNone]; + + if (key != nil && !isUntilFirstAuth && !isNone) { + return YES; + } + } +#endif + + return NO; +} + +/** + * Returns the log file that should be used. + * If there is an existing log file that is suitable, within the + * constraints of maximumFileSize and rollingFrequency, then it is returned. + * + * Otherwise a new file is created and returned. + **/ +- (DDLogFileInfo *)currentLogFileInfo { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + // Do not access this method on any Lumberjack queue, will deadlock. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block DDLogFileInfo *info = nil; + __auto_type block = ^{ + info = [self lt_currentLogFileInfo]; + }; + + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self->_loggerQueue, block); + }); + + return info; +} + +- (DDLogFileInfo *)lt_currentLogFileInfo { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + // Get the current log file info ivar (might be nil). + __auto_type newCurrentLogFile = _currentLogFileInfo; + + // Check if we're resuming and if so, get the first of the sorted log file infos. + __auto_type isResuming = newCurrentLogFile == nil; + if (isResuming) { + NSArray *sortedLogFileInfos = [_logFileManager sortedLogFileInfos]; + newCurrentLogFile = sortedLogFileInfos.firstObject; + } + + // Check if the file we've found is still valid. Otherwise create a new one. + if (newCurrentLogFile != nil && [self lt_shouldUseLogFile:newCurrentLogFile isResuming:isResuming]) { + if (isResuming) { + NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", newCurrentLogFile.fileName); + } + _currentLogFileInfo = newCurrentLogFile; + } else { + const __auto_type logFileManagerImplementsModernCreationMethod = [_logFileManager respondsToSelector:@selector(createNewLogFileWithError:)]; + NSString *currentLogFilePath; + if (logFileManagerImplementsModernCreationMethod) { + __autoreleasing NSError *error; // Don't initialize error to nil since it will be done in -createNewLogFileWithError: + currentLogFilePath = [_logFileManager createNewLogFileWithError:&error]; + if (!currentLogFilePath) { + NSLogError(@"DDFileLogger: Failed to create new log file: %@", error); + } + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSAssert([_logFileManager respondsToSelector:@selector(createNewLogFile)], + @"Invalid log file manager! Responds neither to `-createNewLogFileWithError:` nor `-createNewLogFile`!"); + currentLogFilePath = [_logFileManager createNewLogFile]; +#pragma clang diagnostic pop + if (!currentLogFilePath) { + NSLogError(@"DDFileLogger: Failed to create new log file"); + } + } + if (!currentLogFilePath) { + _currentLogFileInfo = nil; + } else { + const __auto_type logFileManagerProvidesFileManager = [_logFileManager respondsToSelector:@selector(fileManager)]; + __auto_type fileManager = logFileManagerProvidesFileManager ? _logFileManager.fileManager : [NSFileManager defaultManager]; + _currentLogFileInfo = [[DDLogFileInfo alloc] initWithFilePath:currentLogFilePath fileManager:fileManager]; + } + } + + return _currentLogFileInfo; +} + +- (BOOL)lt_shouldUseLogFile:(nonnull DDLogFileInfo *)logFileInfo isResuming:(BOOL)isResuming { + NSParameterAssert(logFileInfo); + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + // Check if the log file is archived. We must not use archived log files. + if (logFileInfo.isArchived) { + return NO; + } + + // Don't follow symlink + if (logFileInfo.isSymlink) { + return NO; + } + + // If we're resuming, we need to check if the log file is allowed for reuse or needs to be archived. + if (isResuming && (_doNotReuseLogFiles || [self lt_shouldLogFileBeArchived:logFileInfo])) { + logFileInfo.isArchived = YES; + + const __auto_type logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)]; + if (logFileManagerRespondsToNewArchiveSelector || [_logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) { + NSString *archivedFilePath = [logFileInfo.filePath copy]; + dispatch_block_t block; + if (logFileManagerRespondsToNewArchiveSelector) { + block = ^{ + [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:NO]; + }; + } else { + block = ^{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self->_logFileManager didArchiveLogFile:archivedFilePath]; +#pragma clang diagnostic pop + }; + } + dispatch_async(_completionQueue, block); + } + + return NO; + } + + // All checks have passed. It's valid. + return YES; +} + +- (void)lt_monitorCurrentLogFileForExternalChanges { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + NSAssert(_currentLogFileHandle, @"Can not monitor without handle."); + + // This seems to work around crashes when an active source is replaced / released. + // See https://github.com/CocoaLumberjack/CocoaLumberjack/issues/1341 + // And https://stackoverflow.com/questions/36296528/what-does-this-dispatch-xref-dispose-error-mean + if (_currentLogFileVnode) { + dispatch_source_cancel(_currentLogFileVnode); + } + + _currentLogFileVnode = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, + (uintptr_t)[_currentLogFileHandle fileDescriptor], + DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE, + _loggerQueue); + + __weak __auto_type weakSelf = self; + dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool { + NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one"); + [weakSelf lt_rollLogFileNow]; + } }); + +#if !OS_OBJECT_USE_OBJC + dispatch_source_t vnode = _currentLogFileVnode; + dispatch_source_set_cancel_handler(_currentLogFileVnode, ^{ + dispatch_release(vnode); + }); +#endif + + dispatch_activate(_currentLogFileVnode); +} + +- (NSFileHandle *)lt_currentLogFileHandle { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_currentLogFileHandle == nil) { + __auto_type logFilePath = [[self lt_currentLogFileInfo] filePath]; + _currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; + if (_currentLogFileHandle != nil) { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle seekToEndReturningOffset:nil error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error); + } + } else { + [_currentLogFileHandle seekToEndOfFile]; + } + + [self lt_scheduleTimerToRollLogFileDueToAge]; + [self lt_monitorCurrentLogFileForExternalChanges]; + } else { + NSLogWarn(@"NSFileHandle returned nil for writing to log file at path: %@", logFilePath); + } + } + + return _currentLogFileHandle; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogger Protocol +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static int exception_count = 0; + +- (void)logMessage:(DDLogMessage *)logMessage { + // Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us. + NSData *data = [self lt_dataForMessage:logMessage]; + if (data.length == 0) { + return; + } + + [self lt_logData:data]; +} + +- (void)willLogMessage:(DDLogFileInfo *)logFileInfo {} + +- (void)didLogMessage:(DDLogFileInfo *)logFileInfo { + [self lt_maybeRollLogFileDueToSize]; +} + +- (BOOL)shouldArchiveRecentLogFileInfo:(__unused DDLogFileInfo *)recentLogFileInfo { + return NO; +} + +- (void)willRemoveLogger { + [self lt_rollLogFileNow]; +} + +- (void)flush { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + dispatch_block_t block = ^{ + @autoreleasepool { + [self lt_flush]; + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + } +} + +- (void)lt_flush { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (_currentLogFileHandle != nil) { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error); + } + } else { + @try { + [_currentLogFileHandle synchronizeFile]; + } @catch (NSException *exception) { + NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception); + } + } + } +} + +- (DDLoggerName)loggerName { + return DDLoggerNameFile; +} + +@end + +@implementation DDFileLogger (Internal) + +- (void)logData:(NSData *)data { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + __auto_type block = ^{ + @autoreleasepool { + [self lt_logData:data]; + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, block); + }); + } +} + +- (void)lt_deprecationCatchAll {} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + if (aSelector == @selector(willLogMessage) || aSelector == @selector(didLogMessage)) { + // Ignore calls to deprecated methods. + return [self methodSignatureForSelector:@selector(lt_deprecationCatchAll)]; + } + + return [super methodSignatureForSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation { + if (anInvocation.selector != @selector(lt_deprecationCatchAll)) { + [super forwardInvocation:anInvocation]; + } +} + +- (void)lt_logData:(NSData *)data { + static __auto_type implementsDeprecatedWillLog = NO; + static __auto_type implementsDeprecatedDidLog = NO; + static __auto_type implementsShouldLock = NO; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + implementsDeprecatedWillLog = [self respondsToSelector:@selector(willLogMessage)]; + implementsDeprecatedDidLog = [self respondsToSelector:@selector(didLogMessage)]; + implementsShouldLock = [self.logFileManager respondsToSelector:@selector(shouldLockLogFile:)]; + }); + + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + if (data.length == 0) { + return; + } + + @try { + // Make sure that _currentLogFileInfo is initialised before being used. + __auto_type handle = [self lt_currentLogFileHandle]; + + if (implementsDeprecatedWillLog) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self willLogMessage]; +#pragma clang diagnostic pop + } else { + [self willLogMessage:_currentLogFileInfo]; + } + + // use an advisory lock to coordinate write with other processes + __auto_type shouldLock = implementsShouldLock && [self.logFileManager shouldLockLogFile:_currentLogFileInfo.filePath]; + __auto_type fd = [handle fileDescriptor]; + if (shouldLock) { + while(flock(fd, LOCK_EX) != 0) { + NSLogError(@"DDFileLogger: Could not lock logfile, retrying in 1ms: %s (%d)", strerror(errno), errno); + usleep(1000); + } + } + + @try { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) { + __autoreleasing NSError *error = nil; + __auto_type success = [handle seekToEndReturningOffset:nil error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error); + } + success = [handle writeData:data error:&error]; + if (!success) { + NSLogError(@"DDFileLogger: Failed to write data: %@", error); + } + } else { + [handle seekToEndOfFile]; + [handle writeData:data]; + } + } + @finally { + if (shouldLock) { + flock(fd, LOCK_UN); + } + } + + if (implementsDeprecatedDidLog) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self didLogMessage]; +#pragma clang diagnostic pop + } else { + [self didLogMessage:_currentLogFileInfo]; + } + + } + @catch (NSException *exception) { + exception_count++; + + if (exception_count <= 10) { + NSLogError(@"DDFileLogger.logMessage: %@", exception); + + if (exception_count == 10) { + NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them."); + } + } + } +} + +- (id )lt_logFileSerializer { + if ([_logFileManager respondsToSelector:@selector(logMessageSerializer)]) { + return _logFileManager.logMessageSerializer; + } else { + return [[DDFileLogPlainTextMessageSerializer alloc] init]; + } +} + +- (NSData *)lt_dataForMessage:(DDLogMessage *)logMessage { + DDAbstractLoggerAssertOnInternalLoggerQueue(); + + __auto_type messageString = logMessage->_message; + __auto_type isFormatted = NO; + + if (_logFormatter != nil) { + messageString = [_logFormatter formatLogMessage:logMessage]; + isFormatted = messageString != logMessage->_message; + } + + if (messageString.length == 0) { + return nil; + } + + __auto_type shouldFormat = !isFormatted || _automaticallyAppendNewlineForCustomFormatters; + if (shouldFormat && ![messageString hasSuffix:@"\n"]) { + messageString = [messageString stringByAppendingString:@"\n"]; + } + + return [[self lt_logFileSerializer] dataForString:messageString originatingFromMessage:logMessage]; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static NSString * const kDDXAttrArchivedName = @"lumberjack.log.archived"; + +@interface DDLogFileInfo () { + __strong NSString *_filePath; + __strong NSString *_fileName; + + __strong NSFileManager *_fileManager; + + __strong NSDictionary *_fileAttributes; + + __strong NSDate *_creationDate; + __strong NSDate *_modificationDate; + + unsigned long long _fileSize; +} + +#if TARGET_IPHONE_SIMULATOR +// Old implementation of extended attributes on the simulator. +- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName; +- (void)_removeExtensionAttributeWithName:(NSString *)attrName; +#endif + +@end + + +@implementation DDLogFileInfo + +@synthesize filePath; + +@dynamic fileName; +@dynamic fileAttributes; +@dynamic creationDate; +@dynamic modificationDate; +@dynamic fileSize; +@dynamic age; + +@dynamic isArchived; + +#pragma mark Lifecycle + ++ (instancetype)logFileWithPath:(NSString *)aFilePath { + if (!aFilePath) return nil; + return [[self alloc] initWithFilePath:aFilePath]; +} + +- (instancetype)initWithFilePath:(NSString *)aFilePath { + NSParameterAssert(aFilePath); + if ((self = [super init])) { + filePath = [aFilePath copy]; + _fileManager = [NSFileManager defaultManager]; + } + + return self; +} + +- (instancetype)initWithFilePath:(NSString *)aFilePath fileManager:(NSFileManager *)fileManager { + NSParameterAssert(fileManager); + if ((self = [self initWithFilePath:aFilePath])) { + _fileManager = fileManager; + } + + return self; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Standard Info +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSDictionary *)fileAttributes { + if (_fileAttributes == nil && filePath != nil) { + __autoreleasing NSError *error = nil; + _fileAttributes = [_fileManager attributesOfItemAtPath:filePath error:&error]; + if (!_fileAttributes) { + NSLogError(@"DDLogFileInfo: Failed to read file attributes: %@", error); + } + } + + return _fileAttributes ?: @{}; +} + +- (NSString *)fileName { + if (_fileName == nil) { + _fileName = [filePath lastPathComponent]; + } + + return _fileName; +} + +- (NSDate *)modificationDate { + if (_modificationDate == nil) { + _modificationDate = self.fileAttributes[NSFileModificationDate]; + } + + return _modificationDate; +} + +- (NSDate *)creationDate { + if (_creationDate == nil) { + _creationDate = self.fileAttributes[NSFileCreationDate]; + } + + return _creationDate; +} + +- (unsigned long long)fileSize { + if (_fileSize == 0) { + _fileSize = [self.fileAttributes[NSFileSize] unsignedLongLongValue]; + } + + return _fileSize; +} + +- (NSTimeInterval)age { + return -[[self creationDate] timeIntervalSinceNow]; +} + +- (BOOL)isSymlink { + return self.fileAttributes[NSFileType] == NSFileTypeSymbolicLink; +} + +- (NSString *)description { + return [@{ @"filePath": self.filePath ? : @"", + @"fileName": self.fileName ? : @"", + @"fileAttributes": self.fileAttributes ? : @"", + @"creationDate": self.creationDate ? : @"", + @"modificationDate": self.modificationDate ? : @"", + @"fileSize": @(self.fileSize), + @"age": @(self.age), + @"isArchived": @(self.isArchived) } description]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Archiving +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isArchived { + return [self hasExtendedAttributeWithName:kDDXAttrArchivedName]; +} + +- (void)setIsArchived:(BOOL)flag { + if (flag) { + [self addExtendedAttributeWithName:kDDXAttrArchivedName]; + } else { + [self removeExtendedAttributeWithName:kDDXAttrArchivedName]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Changes +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)reset { + _fileName = nil; + _fileAttributes = nil; + _creationDate = nil; + _modificationDate = nil; +} + +- (void)renameFile:(NSString *)newFileName { + // This method is only used on the iPhone simulator, where normal extended attributes are broken. + // See full explanation in the header file. + + if (![newFileName isEqualToString:[self fileName]]) { + __auto_type fileDir = [filePath stringByDeletingLastPathComponent]; + __auto_type newFilePath = [fileDir stringByAppendingPathComponent:newFileName]; + + // We only want to assert when we're not using the simulator, as we're "archiving" a log file with this method in the sim + // (in which case the file might not exist anymore and neither does it parent folder). +#if defined(DEBUG) && (!defined(TARGET_IPHONE_SIMULATOR) || !TARGET_IPHONE_SIMULATOR) + __auto_type directory = NO; + [_fileManager fileExistsAtPath:fileDir isDirectory:&directory]; + NSAssert(directory, @"Containing directory must exist."); +#endif + + __autoreleasing NSError *error = nil; + __auto_type success = [_fileManager removeItemAtPath:newFilePath error:&error]; + if (!success && error.code != NSFileNoSuchFileError) { + NSLogError(@"DDLogFileInfo: Error deleting archive (%@): %@", self.fileName, error); + } + + success = [_fileManager moveItemAtPath:filePath toPath:newFilePath error:&error]; + + // When a log file is deleted, moved or renamed on the simulator, we attempt to rename it as a + // result of "archiving" it, but since the file doesn't exist anymore, needless error logs are printed + // We therefore ignore this error, and assert that the directory we are copying into exists (which + // is the only other case where this error code can come up). +#if TARGET_IPHONE_SIMULATOR + if (!success && error.code != NSFileNoSuchFileError) +#else + if (!success) +#endif + { + NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error); + } + + filePath = newFilePath; + [self reset]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Attribute Management +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if TARGET_IPHONE_SIMULATOR + +// Old implementation of extended attributes on the simulator. + +// Extended attributes were not working properly on the simulator +// due to misuse of setxattr() function. +// Now that this is fixed in the new implementation, we want to keep +// backward compatibility with previous simulator installations. + +static NSString * const kDDExtensionSeparator = @"."; + +static NSString *_xattrToExtensionName(NSString *attrName) { + static NSDictionary* _xattrToExtensionNameMap; + static dispatch_once_t _token; + dispatch_once(&_token, ^{ + _xattrToExtensionNameMap = @{ kDDXAttrArchivedName: @"archived" }; + }); + return [_xattrToExtensionNameMap objectForKey:attrName]; +} + +- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName { + // This method is only used on the iPhone simulator for backward compatibility reason. + + // Split the file name into components. File name may have various format, but generally + // structure is same: + // + // . and .archived. + // or + // and .archived + // + // So we want to search for the attrName in the components (ignoring the first array index). + + __auto_type components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator]; + + // Watch out for file names without an extension + + for (NSUInteger i = 1; i < components.count; i++) { + if ([attrName isEqualToString:components[i]]) { + return YES; + } + } + + return NO; +} + +- (void)_removeExtensionAttributeWithName:(NSString *)attrName { + // This method is only used on the iPhone simulator for backward compatibility reason. + + if ([attrName length] == 0) { + return; + } + + // Example: + // attrName = "archived" + // + // "mylog.archived.txt" -> "mylog.txt" + // "mylog.archived" -> "mylog" + + __auto_type components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator]; + + __auto_type count = [components count]; + + __auto_type estimatedNewLength = [[self fileName] length]; + __auto_type newFileName = [NSMutableString stringWithCapacity:estimatedNewLength]; + + if (count > 0) { + [newFileName appendString:components[0]]; + } + + __auto_type found = NO; + + NSUInteger i; + + for (i = 1; i < count; i++) { + __auto_type attr = components[i]; + + if ([attrName isEqualToString:attr]) { + found = YES; + } else { + [newFileName appendString:kDDExtensionSeparator]; + [newFileName appendString:attr]; + } + } + + if (found) { + [self renameFile:newFileName]; + } +} + +#endif /* if TARGET_IPHONE_SIMULATOR */ + +- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName { + __auto_type path = [filePath fileSystemRepresentation]; + __auto_type name = [attrName UTF8String]; + __auto_type hasExtendedAttribute = NO; + char buffer[1]; + + __auto_type result = getxattr(path, name, buffer, 1, 0, 0); + + // Fast path + if (result > 0 && buffer[0] == '\1') { + hasExtendedAttribute = YES; + } + // Maintain backward compatibility, but fix it for future checks + else if (result >= 0) { + hasExtendedAttribute = YES; + + [self addExtendedAttributeWithName:attrName]; + } +#if TARGET_IPHONE_SIMULATOR + else if ([self _hasExtensionAttributeWithName:_xattrToExtensionName(attrName)]) { + hasExtendedAttribute = YES; + + [self addExtendedAttributeWithName:attrName]; + } +#endif + + return hasExtendedAttribute; +} + +- (void)addExtendedAttributeWithName:(NSString *)attrName { + __auto_type path = [filePath fileSystemRepresentation]; + __auto_type name = [attrName UTF8String]; + + __auto_type result = setxattr(path, name, "\1", 1, 0, 0); + + if (result < 0) { + if (errno != ENOENT) { + NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %@", + attrName, + filePath, + @(strerror(errno))); + } else { + NSLogDebug(@"DDLogFileInfo: File does not exist in setxattr(%@, %@): error = %@", + attrName, + filePath, + @(strerror(errno))); + } + } +#if TARGET_IPHONE_SIMULATOR + else { + [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)]; + } +#endif +} + +- (void)removeExtendedAttributeWithName:(NSString *)attrName { + __auto_type path = [filePath fileSystemRepresentation]; + __auto_type name = [attrName UTF8String]; + + __auto_type result = removexattr(path, name, 0); + + if (result < 0 && errno != ENOATTR) { + NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %@", + attrName, + self.fileName, + @(strerror(errno))); + } + +#if TARGET_IPHONE_SIMULATOR + [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)]; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Comparisons +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[self class]]) { + __auto_type another = (DDLogFileInfo *)object; + + return [filePath isEqualToString:[another filePath]]; + } + + return NO; +} + +- (NSUInteger)hash { + return [filePath hash]; +} + +- (NSComparisonResult)reverseCompareDatesUs:(NSDate *_Nullable)us them:(NSDate *_Nullable)them { + if (us != nil && them != nil) { + return [them compare:(NSDate * _Nonnull)us]; + } else if (us == nil && them == nil) { + return NSOrderedSame; + } + return them == nil ? NSOrderedAscending : NSOrderedDescending; +} + +- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another { + return [self reverseCompareDatesUs:[self creationDate] them:[another creationDate]]; +} + +- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another { + return [self reverseCompareDatesUs:[self modificationDate] them:[another modificationDate]]; +} + +@end + +#if TARGET_OS_IPHONE +/** + * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen. + * + * But in case if app is able to launch from background we need to have an ability to open log file any time we + * want (even if device is locked). Thats why that attribute have to be changed to + * NSFileProtectionCompleteUntilFirstUserAuthentication. + */ +BOOL doesAppRunInBackground(void) { + if ([[[NSBundle mainBundle] executablePath] containsString:@".appex/"]) { + return YES; + } + + __auto_type answer = NO; + NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"]; + for (NSString *mode in backgroundModes) { + if (mode.length > 0) { + answer = YES; + break; + } + } + + return answer; +} +#endif diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m new file mode 100644 index 0000000..f19ed08 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m @@ -0,0 +1,1321 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import +#import +#import + +#if TARGET_OS_IOS + #import + #import +#elif !defined(DD_CLI) && __has_include() + #import +#endif + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use a primitive logging macro around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#ifndef DD_DEBUG + #define DD_DEBUG 0 +#endif + +#define NSLogDebug(frmt, ...) do{ if(DD_DEBUG) NSLog((frmt), ##__VA_ARGS__); } while(0) + +#define DDLogAssertOnGlobalLoggingQueue() \ +NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), @"This method must be called on the logging thread/queue!") +#define DDLogAssertNotOnGlobalLoggingQueue() \ +NSAssert(!dispatch_get_specific(GlobalLoggingQueueIdentityKey), @"This method must not be called on the logging thread/queue!") + +// The "global logging queue" refers to [DDLog loggingQueue]. +// It is the queue that all log statements go through. +// +// The logging queue sets a flag via dispatch_queue_set_specific using this key. +// We can check for this key via dispatch_get_specific() to see if we're on the "global logging queue". + +static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey; + +@interface DDLoggerNode : NSObject +{ + // Direct accessors to be used only for performance + @public + id _logger; + DDLogLevel _level; + dispatch_queue_t _loggerQueue; +} + +@property (nonatomic, readonly) id logger; +@property (nonatomic, readonly) DDLogLevel level; +@property (nonatomic, readonly) dispatch_queue_t loggerQueue; + ++ (instancetype)nodeWithLogger:(id )logger + loggerQueue:(dispatch_queue_t)loggerQueue + level:(DDLogLevel)level; + +@end + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLog () + +// An array used to manage all the individual loggers. +// The array is only modified on the loggingQueue/loggingThread. +@property (nonatomic, strong) NSMutableArray *_loggers; + +@end + +@implementation DDLog + +// All logging statements are added to the same queue to ensure FIFO operation. +static dispatch_queue_t _loggingQueue; + +// Individual loggers are executed concurrently per log statement. +// Each logger has it's own associated queue, and a dispatch group is used for synchronization. +static dispatch_group_t _loggingGroup; + +// Minor optimization for uniprocessor machines +static NSUInteger _numProcessors; + +/** + * Returns the singleton `DDLog`. + * The instance is used by `DDLog` class methods. + * + * @return The singleton `DDLog`. + */ ++ (instancetype)sharedInstance { + static DDLog *sharedInstance = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + +/** + * The runtime sends initialize to each class in a program exactly one time just before the class, + * or any class that inherits from it, is sent its first message from within the program. (Thus the + * method may never be invoked if the class is not used.) The runtime sends the initialize message to + * classes in a thread-safe manner. Superclasses receive this message before their subclasses. + * + * This method may also be called directly, hence the safety mechanism. + **/ ++ (void)initialize { + static dispatch_once_t DDLogOnceToken; + + dispatch_once(&DDLogOnceToken, ^{ + NSLogDebug(@"DDLog: Using grand central dispatch"); + + _loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL); + _loggingGroup = dispatch_group_create(); + + void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null + dispatch_queue_set_specific(_loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL); + + // Figure out how many processors are available. + // This may be used later for an optimization on uniprocessor machines. + + _numProcessors = MAX([NSProcessInfo processInfo].processorCount, (NSUInteger) 1); + + NSLogDebug(@"DDLog: numProcessors = %@", @(_numProcessors)); + }); +} + +/** + * The `DDLog` initializer. + * Static variables are set only once. + * + * @return An initialized `DDLog` instance. + */ +- (instancetype)init { + self = [super init]; + + if (self) { + self._loggers = [[NSMutableArray alloc] initWithCapacity:4]; + +#if TARGET_OS_IOS + __auto_type notificationName = UIApplicationWillTerminateNotification; +#else + NSString *notificationName = nil; + + // On Command Line Tool apps AppKit may not be available +#if !defined(DD_CLI) && __has_include() + if (NSApp) { + notificationName = NSApplicationWillTerminateNotification; + } +#endif + + if (!notificationName) { + // If there is no NSApp -> we are running Command Line Tool app. + // In this case terminate notification wouldn't be fired, so we use workaround. + __weak __auto_type weakSelf = self; + atexit_b (^{ + [weakSelf applicationWillTerminate:nil]; + }); + } + +#endif /* if TARGET_OS_IOS */ + + if (notificationName) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillTerminate:) + name:notificationName + object:nil]; + } + } + + return self; +} + +/** + * Provides access to the logging queue. + **/ ++ (dispatch_queue_t)loggingQueue { + return _loggingQueue; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Notifications +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)applicationWillTerminate:(NSNotification * __attribute__((unused)))notification { + [self flushLog]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Logger Management +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (void)addLogger:(id )logger { + [self.sharedInstance addLogger:logger]; +} + +- (void)addLogger:(id )logger { + [self addLogger:logger withLevel:DDLogLevelAll]; // DDLogLevelAll has all bits set +} + ++ (void)addLogger:(id )logger withLevel:(DDLogLevel)level { + [self.sharedInstance addLogger:logger withLevel:level]; +} + +- (void)addLogger:(id )logger withLevel:(DDLogLevel)level { + if (!logger) { + return; + } + + dispatch_async(_loggingQueue, ^{ @autoreleasepool { + [self lt_addLogger:logger level:level]; + } }); +} + ++ (void)removeLogger:(id )logger { + [self.sharedInstance removeLogger:logger]; +} + +- (void)removeLogger:(id )logger { + if (!logger) { + return; + } + + dispatch_async(_loggingQueue, ^{ @autoreleasepool { + [self lt_removeLogger:logger]; + } }); +} + ++ (void)removeAllLoggers { + [self.sharedInstance removeAllLoggers]; +} + +- (void)removeAllLoggers { + dispatch_async(_loggingQueue, ^{ @autoreleasepool { + [self lt_removeAllLoggers]; + } }); +} + ++ (NSArray> *)allLoggers { + return [self.sharedInstance allLoggers]; +} + +- (NSArray> *)allLoggers { + __block NSArray *theLoggers; + + dispatch_sync(_loggingQueue, ^{ @autoreleasepool { + theLoggers = [self lt_allLoggers]; + } }); + + return theLoggers; +} + ++ (NSArray *)allLoggersWithLevel { + return [self.sharedInstance allLoggersWithLevel]; +} + +- (NSArray *)allLoggersWithLevel { + __block NSArray *theLoggersWithLevel; + + dispatch_sync(_loggingQueue, ^{ @autoreleasepool { + theLoggersWithLevel = [self lt_allLoggersWithLevel]; + } }); + + return theLoggersWithLevel; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - Master Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag { + // We have a tricky situation here... + // + // In the common case, when the queueSize is below the maximumQueueSize, + // we want to simply enqueue the logMessage. And we want to do this as fast as possible, + // which means we don't want to block and we don't want to use any locks. + // + // However, if the queueSize gets too big, we want to block. + // But we have very strict requirements as to when we block, and how long we block. + // + // The following example should help illustrate our requirements: + // + // Imagine that the maximum queue size is configured to be 5, + // and that there are already 5 log messages queued. + // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed) + // + // Now if our thread issues a log statement (let us call the log message F), + // it should block before the message is added to the queue. + // Furthermore, it should be unblocked immediately after A has been unqueued. + // + // The requirements are strict in this manner so that we block only as long as necessary, + // and so that blocked threads are unblocked in the order in which they were blocked. + // + // Returning to our previous example, let us assume that log messages A through E are still queued. + // Our aforementioned thread is blocked attempting to queue log message F. + // Now assume we have another separate thread that attempts to issue log message G. + // It should block until log messages A and B have been unqueued. + + __auto_type logBlock = ^{ + // We're now sure we won't overflow the queue. + // It is time to queue our log message. + @autoreleasepool { + [self lt_log:logMessage]; + } + }; + + if (asyncFlag) { + dispatch_async(_loggingQueue, logBlock); + } else if (dispatch_get_specific(GlobalLoggingQueueIdentityKey)) { + // We've logged an error message while on the logging queue... + logBlock(); + } else { + dispatch_sync(_loggingQueue, logBlock); + } +} + ++ (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format, ... { + va_list args; + + if (format) { + va_start(args, format); + + [self log:asynchronous + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + format:format + args:args]; + + va_end(args); + } +} + +- (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format, ... { + va_list args; + + if (format) { + va_start(args, format); + + [self log:asynchronous + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + format:format + args:args]; + + va_end(args); + } +} + ++ (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format + args:(va_list)args { + [self.sharedInstance log:asynchronous level:level flag:flag context:context file:file function:function line:line tag:tag format:format args:args]; +} + +- (void)log:(BOOL)asynchronous + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(const char *)file + function:(const char *)function + line:(NSUInteger)line + tag:(id)tag + format:(NSString *)format + args:(va_list)args { + if (format) { + // Null checks are handled by -initWithMessage: +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" + __auto_type logMessage = [[DDLogMessage alloc] initWithFormat:format + args:args + level:level + flag:flag + context:context + file:@(file) + function:@(function) + line:line + tag:tag + options:(DDLogMessageOptions)0 + timestamp:nil]; +#pragma clang diagnostic pop + + [self queueLogMessage:logMessage asynchronously:asynchronous]; + } +} + ++ (void)log:(BOOL)asynchronous message:(DDLogMessage *)logMessage { + [self.sharedInstance log:asynchronous message:logMessage]; +} + +- (void)log:(BOOL)asynchronous message:(DDLogMessage *)logMessage { + [self queueLogMessage:logMessage asynchronously:asynchronous]; +} + ++ (void)flushLog { + [self.sharedInstance flushLog]; +} + +- (void)flushLog { + DDLogAssertNotOnGlobalLoggingQueue(); + dispatch_sync(_loggingQueue, ^{ + @autoreleasepool { + [self lt_flush]; + } + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Registered Dynamic Logging +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ++ (BOOL)isRegisteredClass:(Class)class { + __auto_type getterSel = @selector(ddLogLevel); + __auto_type setterSel = @selector(ddSetLogLevel:); + + // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4 + // Crash caused by class_getClassMethod(2). + // "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until + // users had VoiceOver enabled [...]. I was able to work around it by searching the + // result of class_copyMethodList() instead of calling class_getClassMethod()" + // + // Issue #24 (GitHub) - Crashing in in ARC+Simulator + // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator. + // For running in the Simulator, it needs to execute the non-iOS code. Unless we're running on iOS 17+. + +#if TARGET_OS_IPHONE +#if TARGET_OS_SIMULATOR + if (@available(iOS 17, tvOS 17, *)) { +#endif + __auto_type result = NO; + unsigned int methodCount, i; + __auto_type methodList = class_copyMethodList(object_getClass(class), &methodCount); + + if (methodList != NULL) { + __auto_type getterFound = NO; + __auto_type setterFound = NO; + + for (i = 0; i < methodCount; ++i) { + __auto_type currentSel = method_getName(methodList[i]); + + if (currentSel == getterSel) { + getterFound = YES; + } else if (currentSel == setterSel) { + setterFound = YES; + } + + if (getterFound && setterFound) { + result = YES; + break; + } + } + + free(methodList); + } + + return result; +#if TARGET_OS_SIMULATOR + } else { +#endif /* TARGET_OS_SIMULATOR */ +#endif /* TARGET_OS_IPHONE */ +#if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR + __auto_type getter = class_getClassMethod(class, getterSel); + __auto_type setter = class_getClassMethod(class, setterSel); + return (getter != NULL) && (setter != NULL); +#endif /* !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR */ +#if TARGET_OS_IPHONE && TARGET_OS_SIMULATOR + } +#endif /* TARGET_OS_IPHONE && TARGET_OS_SIMULATOR */ +} + ++ (NSArray *)registeredClasses { + // We're going to get the list of all registered classes. + // The Objective-C runtime library automatically registers all the classes defined in your source code. + // + // To do this we use the following method (documented in the Objective-C Runtime Reference): + // + // int objc_getClassList(Class *buffer, int bufferLen) + // + // We can pass (NULL, 0) to obtain the total number of + // registered class definitions without actually retrieving any class definitions. + // This allows us to allocate the minimum amount of memory needed for the application. + + NSUInteger numClasses = 0; + Class *classes = NULL; + + while (numClasses == 0) { + numClasses = (NSUInteger)MAX(objc_getClassList(NULL, 0), 0); + + // numClasses now tells us how many classes we have (but it might change) + // So we can allocate our buffer, and get pointers to all the class definitions. + __auto_type bufferSize = numClasses; + classes = numClasses ? (Class *)calloc(bufferSize, sizeof(Class)) : NULL; + if (classes == NULL) { + return @[]; // no memory or classes? + } + + numClasses = (NSUInteger)MAX(objc_getClassList(classes, (int)bufferSize),0); + if (numClasses > bufferSize || numClasses == 0) { + // apparently more classes added between calls (or a problem); try again + free(classes); + classes = NULL; + numClasses = 0; + } + } + + // We can now loop through the classes, and test each one to see if it is a DDLogging class. + __auto_type result = [NSMutableArray arrayWithCapacity:numClasses]; + for (NSUInteger i = 0; i < numClasses; i++) { + // Cannot use `__auto_type` here, since this will lead to crashes when deallocating! + Class class = classes[i]; + + if ([self isRegisteredClass:class]) { + [result addObject:class]; + } + } + + free(classes); + + return result; +} + ++ (NSArray *)registeredClassNames { + __auto_type registeredClasses = [self registeredClasses]; + __auto_type result = [NSMutableArray arrayWithCapacity:[registeredClasses count]]; + + for (Class class in registeredClasses) { + [result addObject:NSStringFromClass(class)]; + } + return result; +} + ++ (DDLogLevel)levelForClass:(Class)aClass { + if ([self isRegisteredClass:aClass]) { + return [aClass ddLogLevel]; + } + return (DDLogLevel)-1; +} + ++ (DDLogLevel)levelForClassWithName:(NSString *)aClassName { + Class clazz = NSClassFromString(aClassName); + if (clazz == nil) return (DDLogLevel)-1; + return [self levelForClass:clazz]; +} + ++ (void)setLevel:(DDLogLevel)level forClass:(Class)aClass { + if ([self isRegisteredClass:aClass]) { + [aClass ddSetLogLevel:level]; + } +} + ++ (void)setLevel:(DDLogLevel)level forClassWithName:(NSString *)aClassName { + Class clazz = NSClassFromString(aClassName); + if (clazz == nil) return; + [self setLevel:level forClass:clazz]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Logging Thread +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (void)lt_addLogger:(id )logger level:(DDLogLevel)level { + // Add to loggers array. + // Need to create loggerQueue if loggerNode doesn't provide one. + + for (DDLoggerNode *node in self._loggers) { + if (node->_logger == logger && node->_level == level) { + // Exactly same logger already added, exit + return; + } + } + + DDLogAssertOnGlobalLoggingQueue(); + + dispatch_queue_t loggerQueue = NULL; + if ([logger respondsToSelector:@selector(loggerQueue)]) { + // Logger may be providing its own queue + loggerQueue = logger.loggerQueue; + } + + if (loggerQueue == nil) { + // Automatically create queue for the logger. + // Use the logger name as the queue name if possible. + const char *loggerQueueName = NULL; + + if ([logger respondsToSelector:@selector(loggerName)]) { + loggerQueueName = logger.loggerName.UTF8String; + } + + loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + } + + __auto_type loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue level:level]; + [self._loggers addObject:loggerNode]; + + if ([logger respondsToSelector:@selector(didAddLoggerInQueue:)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [logger didAddLoggerInQueue:loggerNode->_loggerQueue]; + } }); + } else if ([logger respondsToSelector:@selector(didAddLogger)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [logger didAddLogger]; + } }); + } +} + +- (void)lt_removeLogger:(id )logger { + // Find associated loggerNode in list of added loggers + + DDLogAssertOnGlobalLoggingQueue(); + + DDLoggerNode *loggerNode = nil; + + for (DDLoggerNode *node in self._loggers) { + if (node->_logger == logger) { + loggerNode = node; + break; + } + } + + if (loggerNode == nil) { + NSLogDebug(@"DDLog: Request to remove logger which wasn't added"); + return; + } + + // Notify logger + if ([logger respondsToSelector:@selector(willRemoveLogger)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [logger willRemoveLogger]; + } }); + } + + // Remove from loggers array + [self._loggers removeObject:loggerNode]; +} + +- (void)lt_removeAllLoggers { + DDLogAssertOnGlobalLoggingQueue(); + + // Notify all loggers + for (DDLoggerNode *loggerNode in self._loggers) { + if ([loggerNode->_logger respondsToSelector:@selector(willRemoveLogger)]) { + dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger willRemoveLogger]; + } }); + } + } + + // Remove all loggers from array + [self._loggers removeAllObjects]; +} + +- (NSArray *)lt_allLoggers { + DDLogAssertOnGlobalLoggingQueue(); + + __auto_type loggerNodes = self._loggers; + __auto_type theLoggers = [NSMutableArray arrayWithCapacity:loggerNodes.count]; + + for (DDLoggerNode *loggerNode in loggerNodes) { + [theLoggers addObject:loggerNode->_logger]; + } + + return [theLoggers copy]; +} + +- (NSArray *)lt_allLoggersWithLevel { + DDLogAssertOnGlobalLoggingQueue(); + + + __auto_type loggerNodes = self._loggers; + __auto_type theLoggersWithLevel = [NSMutableArray arrayWithCapacity:loggerNodes.count]; + + for (DDLoggerNode *loggerNode in loggerNodes) { + [theLoggersWithLevel addObject:[DDLoggerInformation informationWithLogger:loggerNode->_logger + andLevel:loggerNode->_level]]; + } + + return [theLoggersWithLevel copy]; +} + +- (void)lt_log:(DDLogMessage *)logMessage { + DDLogAssertOnGlobalLoggingQueue(); + + // Execute the given log message on each of our loggers. + + if (_numProcessors > 1) { + // Execute each logger concurrently, each within its own queue. + // All blocks are added to same group. + // After each block has been queued, wait on group. + // + // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages. + // This would defeat the purpose of the efforts we made earlier to restrict the max queue size. + + for (DDLoggerNode *loggerNode in self._loggers) { + // skip the loggers that shouldn't write this message based on the log level + + if (!(logMessage->_flag & (DDLogFlag)(loggerNode->_level))) { + continue; + } + + dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger logMessage:logMessage]; + } }); + } + + dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER); + } else { + // Execute each logger serially, each within its own queue. + + for (DDLoggerNode *loggerNode in self._loggers) { + // skip the loggers that shouldn't write this message based on the log level + + if (!(logMessage->_flag & (DDLogFlag)(loggerNode->_level))) { + continue; + } + +#if DD_DEBUG + // we must assure that we aren not on loggerNode->_loggerQueue. + if (loggerNode->_loggerQueue == NULL) { + // tell that we can't dispatch logger node on queue that is NULL. + NSLogDebug(@"DDLog: current node has loggerQueue == NULL"); + } + else { + dispatch_async(loggerNode->_loggerQueue, ^{ + if (dispatch_get_specific(GlobalLoggingQueueIdentityKey)) { + // tell that we somehow on logging queue? + NSLogDebug(@"DDLog: current node has loggerQueue == globalLoggingQueue"); + } + }); + } +#endif + // next, we must check that node is OK. + dispatch_sync(loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger logMessage:logMessage]; + } }); + } + } +} + +- (void)lt_flush { + // All log statements issued before the flush method was invoked have now been executed. + // + // Now we need to propagate the flush request to any loggers that implement the flush method. + // This is designed for loggers that buffer IO. + + DDLogAssertOnGlobalLoggingQueue(); + + for (DDLoggerNode *loggerNode in self._loggers) { + if ([loggerNode->_logger respondsToSelector:@selector(flush)]) { + dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool { + [loggerNode->_logger flush]; + } }); + } + } + + dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Utilities +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +NSString * __nullable DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy) { + if (filePath == NULL) { + return nil; + } + + char *lastSlash = NULL; + char *lastDot = NULL; + + __auto_type p = (char *)filePath; + while (*p != '\0') { + if (*p == '/') { + lastSlash = p; + } else if (*p == '.') { + lastDot = p; + } + + p++; + } + + char *subStr; + NSUInteger subLen; + + if (lastSlash) { + if (lastDot) { + // lastSlash -> lastDot + subStr = lastSlash + 1; + subLen = (NSUInteger)(lastDot - subStr); + } else { + // lastSlash -> endOfString + subStr = lastSlash + 1; + subLen = (NSUInteger)(p - subStr); + } + } else { + if (lastDot) { + // startOfString -> lastDot + subStr = (char *)filePath; + subLen = (NSUInteger)(lastDot - subStr); + } else { + // startOfString -> endOfString + subStr = (char *)filePath; + subLen = (NSUInteger)(p - subStr); + } + } + + if (copy) { + return [[NSString alloc] initWithBytes:subStr + length:subLen + encoding:NSUTF8StringEncoding]; + } else { + // We can take advantage of the fact that __FILE__ is a string literal. + // Specifically, we don't need to waste time copying the string. + // We can just tell NSString to point to a range within the string literal. + + return [[NSString alloc] initWithBytesNoCopy:subStr + length:subLen + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLoggerNode + +- (instancetype)initWithLogger:(id )logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level { + if ((self = [super init])) { + _logger = logger; + + if (loggerQueue) { + _loggerQueue = loggerQueue; +#if !OS_OBJECT_USE_OBJC + dispatch_retain(loggerQueue); +#endif + } + + _level = level; + } + return self; +} + ++ (instancetype)nodeWithLogger:(id )logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level { + return [[self alloc] initWithLogger:logger loggerQueue:loggerQueue level:level]; +} + +- (void)dealloc { +#if !OS_OBJECT_USE_OBJC + if (_loggerQueue) { + dispatch_release(_loggerQueue); + } +#endif +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDLogMessage + +- (instancetype)init { + self = [super init]; + return self; +} + +- (instancetype)initWithFormat:(NSString *)messageFormat + formatted:(NSString *)message + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(NSString *)file + function:(NSString *)function + line:(NSUInteger)line + tag:(id)tag + options:(DDLogMessageOptions)options + timestamp:(NSDate *)timestamp { + NSParameterAssert(messageFormat); + NSParameterAssert(message); + NSParameterAssert(file); + + if ((self = [super init])) { + __auto_type copyMessage = (options & DDLogMessageDontCopyMessage) == 0; + _messageFormat = copyMessage ? [messageFormat copy] : messageFormat; + _message = copyMessage ? [message copy] : message; + _level = level; + _flag = flag; + _context = context; + + __auto_type copyFile = (options & DDLogMessageCopyFile) != 0; + _file = copyFile ? [file copy] : file; + + __auto_type copyFunction = (options & DDLogMessageCopyFunction) != 0; + _function = copyFunction ? [function copy] : function; + + _line = line; + _representedObject = tag; +#if DD_LEGACY_MESSAGE_TAG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + _tag = tag; +#pragma clang diagnostic pop +#endif + _options = options; + _timestamp = timestamp ?: [NSDate date]; + + __uint64_t tid; + if (pthread_threadid_np(NULL, &tid) == 0) { + _threadID = [[NSString alloc] initWithFormat:@"%llu", tid]; + } else { + _threadID = @"N/A"; + } + _threadName = NSThread.currentThread.name; + + // Get the file name without extension + _fileName = [_file lastPathComponent]; + __auto_type dotLocation = [_fileName rangeOfString:@"." options:NSBackwardsSearch].location; + if (dotLocation != NSNotFound) { + _fileName = [_fileName substringToIndex:dotLocation]; + } + + // Try to get the current queue's label + _queueLabel = @(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)); + _qos = (NSUInteger) qos_class_self(); + } + return self; +} + +- (instancetype)initWithFormat:(NSString *)messageFormat + args:(va_list)messageArgs + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(NSString *)file + function:(NSString *)function + line:(NSUInteger)line + tag:(id)tag + options:(DDLogMessageOptions)options + timestamp:(NSDate *)timestamp { + __auto_type copyMessage = (options & DDLogMessageDontCopyMessage) == 0; + NSString *format = copyMessage ? [messageFormat copy] : messageFormat; + self = [self initWithFormat:format + formatted:[[NSString alloc] initWithFormat:format arguments:messageArgs] + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:options | DDLogMessageDontCopyMessage // we already did the copying if needed. + timestamp:timestamp]; + return self; +} + +- (instancetype)initWithMessage:(NSString *)message + level:(DDLogLevel)level + flag:(DDLogFlag)flag + context:(NSInteger)context + file:(NSString *)file + function:(NSString *)function + line:(NSUInteger)line + tag:(id)tag + options:(DDLogMessageOptions)options + timestamp:(NSDate *)timestamp { + self = [self initWithFormat:message + formatted:message + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:options + timestamp:timestamp]; + return self; +} + +NS_INLINE BOOL _nullable_strings_equal(NSString* _Nullable lhs, NSString* _Nullable rhs) +{ + if (lhs == nil) { + if (rhs == nil) { + return YES; + } + } else if (rhs != nil && [lhs isEqualToString:(NSString* _Nonnull)rhs]) { + return YES; + } + return NO; +} + +- (BOOL)isEqual:(id)other { + // Subclasses of NSObject should not call [super isEqual:] here. + // See https://stackoverflow.com/questions/36593038/confused-about-the-default-isequal-and-hash-implements + if (other == self) { + return YES; + } else if (!other || ![other isKindOfClass:[DDLogMessage class]]) { + return NO; + } else { + __auto_type otherMsg = (DDLogMessage *)other; + return [otherMsg->_message isEqualToString:_message] + && [otherMsg->_messageFormat isEqualToString:_messageFormat] + && otherMsg->_level == _level + && otherMsg->_flag == _flag + && otherMsg->_context == _context + && [otherMsg->_file isEqualToString:_file] + && _nullable_strings_equal(otherMsg->_function, _function) + && otherMsg->_line == _line + && (([otherMsg->_representedObject respondsToSelector:@selector(isEqual:)] && [otherMsg->_representedObject isEqual:_representedObject]) || otherMsg->_representedObject == _representedObject) + && [otherMsg->_timestamp isEqualToDate:_timestamp] + && [otherMsg->_threadID isEqualToString:_threadID] // If the thread ID is the same, the name will likely be the same as well. + && [otherMsg->_queueLabel isEqualToString:_queueLabel] + && otherMsg->_qos == _qos; + } +} + +- (NSUInteger)hash { + // Subclasses of NSObject should not call [super hash] here. + // See https://stackoverflow.com/questions/36593038/confused-about-the-default-isequal-and-hash-implements + return _message.hash + ^ _messageFormat.hash + ^ _level + ^ _flag + ^ _context + ^ _file.hash + ^ _function.hash + ^ _line + ^ ([_representedObject respondsToSelector:@selector(hash)] ? [_representedObject hash] : (NSUInteger)_representedObject) + ^ _timestamp.hash + ^ _threadID.hash + ^ _queueLabel.hash + ^ _qos; +} + +- (id)copyWithZone:(NSZone * __attribute__((unused)))zone { + DDLogMessage *newMessage = [DDLogMessage new]; + + newMessage->_messageFormat = _messageFormat; + newMessage->_message = _message; + newMessage->_level = _level; + newMessage->_flag = _flag; + newMessage->_context = _context; + newMessage->_file = _file; + newMessage->_fileName = _fileName; + newMessage->_function = _function; + newMessage->_line = _line; + newMessage->_representedObject = _representedObject; +#if DD_LEGACY_MESSAGE_TAG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + newMessage->_tag = _tag; +#pragma clang diagnostic pop +#endif + newMessage->_options = _options; + newMessage->_timestamp = _timestamp; + newMessage->_threadID = _threadID; + newMessage->_threadName = _threadName; + newMessage->_queueLabel = _queueLabel; + newMessage->_qos = _qos; + + return newMessage; +} + +// ensure compatibility even when built with DD_LEGACY_MESSAGE_TAG to 0. +- (id)tag { + return _representedObject; +} + +@end + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@implementation DDAbstractLogger + +- (instancetype)init { + if ((self = [super init])) { + const char *loggerQueueName = NULL; + + if ([self respondsToSelector:@selector(loggerName)]) { + loggerQueueName = self.loggerName.UTF8String; + } + + _loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + + // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue. + // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue. + // The documentation states: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // > Specifying a pointer to a string constant is not recommended. + // + // So we're going to use the very convenient key of "self", + // which also works when multiple logger classes extend this class, as each will have a different "self" key. + // + // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below). + + __auto_type key = (__bridge void *)self; + __auto_type nonNullValue = (__bridge void *)self; + + dispatch_queue_set_specific(_loggerQueue, key, nonNullValue, NULL); + } + + return self; +} + +- (void)dealloc { +#if !OS_OBJECT_USE_OBJC + if (_loggerQueue) { + dispatch_release(_loggerQueue); + } +#endif +} + +- (void)logMessage:(DDLogMessage * __attribute__((unused)))logMessage { + // Override me +} + +- (id )logFormatter { + // This method must be thread safe and intuitive. + // Therefore if somebody executes the following code: + // + // [logger setLogFormatter:myFormatter]; + // formatter = [logger logFormatter]; + // + // They would expect formatter to equal myFormatter. + // This functionality must be ensured by the getter and setter method. + // + // The thread safety must not come at a cost to the performance of the logMessage method. + // This method is likely called sporadically, while the logMessage method is called repeatedly. + // This means, the implementation of this method: + // - Must NOT require the logMessage method to acquire a lock. + // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts). + // + // Thread safety is ensured by executing access to the formatter variable on the loggerQueue. + // This is the same queue that the logMessage method operates on. + // + // Note: The last time I benchmarked the performance of direct access vs atomic property access, + // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone. + // + // Furthermore, consider the following code: + // + // DDLogVerbose(@"log msg 1"); + // DDLogVerbose(@"log msg 2"); + // [logger setFormatter:myFormatter]; + // DDLogVerbose(@"log msg 3"); + // + // Our intuitive requirement means that the new formatter will only apply to the 3rd log message. + // This must remain true even when using asynchronous logging. + // We must keep in mind the various queue's that are in play here: + // + // loggerQueue : Our own private internal queue that the logMessage method runs on. + // Operations are added to this queue from the global loggingQueue. + // + // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue. + // + // All log statements go through the serial globalLoggingQueue before they arrive at our loggerQueue. + // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation. + + // IMPORTANT NOTE: + // + // Methods within the DDLogger implementation MUST access the formatter ivar directly. + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __block id result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self->_loggerQueue, ^{ + result = self->_logFormatter; + }); + }); + + return result; +} + +- (void)setLogFormatter:(id )logFormatter { + // The design of this method is documented extensively in the logFormatter message (above in code). + + DDAbstractLoggerAssertLockedPropertyAccess(); + + __auto_type block = ^{ + @autoreleasepool { + if (self->_logFormatter != logFormatter) { + if ([self->_logFormatter respondsToSelector:@selector(willRemoveFromLogger:)]) { + [self->_logFormatter willRemoveFromLogger:self]; + } + + self->_logFormatter = logFormatter; + + if ([self->_logFormatter respondsToSelector:@selector(didAddToLogger:inQueue:)]) { + [self->_logFormatter didAddToLogger:self inQueue:self->_loggerQueue]; + } else if ([self->_logFormatter respondsToSelector:@selector(didAddToLogger:)]) { + [self->_logFormatter didAddToLogger:self]; + } + } + } + }; + + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self->_loggerQueue, block); + }); +} + +- (dispatch_queue_t)loggerQueue { + return _loggerQueue; +} + +- (NSString *)loggerName { + return NSStringFromClass([self class]); +} + +- (BOOL)isOnGlobalLoggingQueue { + return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL); +} + +- (BOOL)isOnInternalLoggerQueue { + return dispatch_get_specific((__bridge void *)self) != NULL; +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLoggerInformation() +{ + // Direct accessors to be used only for performance + @public + id _logger; + DDLogLevel _level; +} + +@end + +@implementation DDLoggerInformation + +- (instancetype)initWithLogger:(id )logger andLevel:(DDLogLevel)level { + if ((self = [super init])) { + _logger = logger; + _level = level; + } + return self; +} + ++ (instancetype)informationWithLogger:(id )logger andLevel:(DDLogLevel)level { + return [[self alloc] initWithLogger:logger andLevel:level]; +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m new file mode 100644 index 0000000..fa6aeee --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m @@ -0,0 +1,21 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +DDLoggerName const DDLoggerNameASL = @"cocoa.lumberjack.aslLogger"; +DDLoggerName const DDLoggerNameTTY = @"cocoa.lumberjack.ttyLogger"; +DDLoggerName const DDLoggerNameOS = @"cocoa.lumberjack.osLogger"; +DDLoggerName const DDLoggerNameFile = @"cocoa.lumberjack.fileLogger"; diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m new file mode 100644 index 0000000..dfab9d3 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m @@ -0,0 +1,159 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import +#import + +#import + +@implementation DDOSLogLevelMapperDefault + +- (instancetype)init { + self = [super init]; + return self; +} + +- (os_log_type_t)osLogTypeForLogFlag:(DDLogFlag)logFlag { + switch (logFlag) { + case DDLogFlagError: + case DDLogFlagWarning: + return OS_LOG_TYPE_ERROR; + case DDLogFlagInfo: + return OS_LOG_TYPE_INFO; + case DDLogFlagDebug: + case DDLogFlagVerbose: + return OS_LOG_TYPE_DEBUG; + default: + return OS_LOG_TYPE_DEFAULT; + } +} + +@end + +#if TARGET_OS_SIMULATOR +@implementation DDOSLogLevelMapperSimulatorConsoleAppWorkaround + +- (os_log_type_t)osLogTypeForLogFlag:(DDLogFlag)logFlag { + __auto_type defaultMapping = [super osLogTypeForLogFlag:logFlag]; + return (defaultMapping == OS_LOG_TYPE_DEBUG) ? OS_LOG_TYPE_DEFAULT : defaultMapping; +} + +@end +#endif + +@interface DDOSLogger () + +@property (nonatomic, copy, readonly, nullable) NSString *subsystem; +@property (nonatomic, copy, readonly, nullable) NSString *category; +@property (nonatomic, strong, readonly, nonnull) os_log_t logger; + +@end + +@implementation DDOSLogger + +@synthesize subsystem = _subsystem; +@synthesize category = _category; +@synthesize logLevelMapper = _logLevelMapper; +@synthesize logger = _logger; + +#pragma mark - Shared Instance + +API_AVAILABLE(macos(10.12), ios(10.0), watchos(3.0), tvos(10.0)) +static DDOSLogger *sharedInstance; + ++ (instancetype)sharedInstance { + static dispatch_once_t DDOSLoggerOnceToken; + + dispatch_once(&DDOSLoggerOnceToken, ^{ + sharedInstance = [[[self class] alloc] init]; + }); + + return sharedInstance; +} + +#pragma mark - Initialization +- (instancetype)initWithSubsystem:(NSString *)subsystem + category:(NSString *)category + logLevelMapper:(id)logLevelMapper { + NSAssert((subsystem == nil) == (category == nil), + @"Either both subsystem and category or neither should be nil."); + NSParameterAssert(logLevelMapper); + if (self = [super init]) { + _subsystem = [subsystem copy]; + _category = [category copy]; + _logLevelMapper = logLevelMapper; + } + return self; +} + +- (instancetype)initWithSubsystem:(NSString *)subsystem category:(NSString *)category { + return [self initWithSubsystem:subsystem + category:category + logLevelMapper:[[DDOSLogLevelMapperDefault alloc] init]]; +} + +- (instancetype)init { + return [self initWithSubsystem:nil category:nil]; +} + + + +- (instancetype)initWithLogLevelMapper:(id)logLevelMapper { + return [self initWithSubsystem:nil category:nil logLevelMapper:logLevelMapper]; +} + +#pragma mark - Mapper +- (id)logLevelMapper { + if (_logLevelMapper == nil) { + _logLevelMapper = [[DDOSLogLevelMapperDefault alloc] init]; + } + return _logLevelMapper; +} + +#pragma mark - os_log +- (os_log_t)logger { + if (_logger == nil) { + if (self.subsystem == nil || self.category == nil) { + _logger = OS_LOG_DEFAULT; + } else { + _logger = os_log_create(self.subsystem.UTF8String, self.category.UTF8String); + } + } + return _logger; +} + +#pragma mark - DDLogger +- (DDLoggerName)loggerName { + return DDLoggerNameOS; +} + +- (void)logMessage:(DDLogMessage *)logMessage { +#if !TARGET_OS_WATCH // See DDASLLogCapture.m -> Was never supported on watchOS. + // Skip captured log messages. + if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) { + return; + } +#endif + + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + __auto_type message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message; + if (message != nil) { + __auto_type logType = [self.logLevelMapper osLogTypeForLogFlag:logMessage->_flag]; + os_log_with_type(self.logger, logType, "%{public}s", message.UTF8String); + } + } +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m new file mode 100644 index 0000000..60b89e6 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m @@ -0,0 +1,1449 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +#import + +// We probably shouldn't be using DDLog() statements within the DDLog implementation. +// But we still want to leave our log statements for any future debugging, +// and to allow other developers to trace the implementation (which is a great learning tool). +// +// So we use primitive logging macros around NSLog. +// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. + +#ifndef DD_NSLOG_LEVEL + #define DD_NSLOG_LEVEL 2 +#endif + +#define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0) +#define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0) + +// Xcode does NOT natively support colors in the Xcode debugging console. +// You'll need to install the XcodeColors plugin to see colors in the Xcode console. +// https://github.com/robbiehanson/XcodeColors +// +// The following is documentation from the XcodeColors project: +// +// +// How to apply color formatting to your log statements: +// +// To set the foreground color: +// Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255. +// +// To set the background color: +// Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36. +// +// To reset the foreground color (to default value): +// Insert the ESCAPE_SEQ into your string, followed by "fg;" +// +// To reset the background color (to default value): +// Insert the ESCAPE_SEQ into your string, followed by "bg;" +// +// To reset the foreground and background color (to default values) in one operation: +// Insert the ESCAPE_SEQ into your string, followed by ";" + +#define XCODE_COLORS_ESCAPE_SEQ "\033[" + +#define XCODE_COLORS_RESET_FG XCODE_COLORS_ESCAPE_SEQ "fg;" // Clear any foreground color +#define XCODE_COLORS_RESET_BG XCODE_COLORS_ESCAPE_SEQ "bg;" // Clear any background color +#define XCODE_COLORS_RESET XCODE_COLORS_ESCAPE_SEQ ";" // Clear any foreground or background color + +// If running in a shell, not all RGB colors will be supported. +// In this case we automatically map to the closest available color. +// In order to provide this mapping, we have a hard-coded set of the standard RGB values available in the shell. +// However, not every shell is the same, and Apple likes to think different even when it comes to shell colors. +// +// Map to standard Terminal.app colors (1), or +// map to standard xterm colors (0). + +#define MAP_TO_TERMINAL_APP_COLORS 1 + +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} DDRGBColor; + +@interface DDTTYLoggerColorProfile : NSObject { +@public + DDLogFlag mask; + NSInteger context; + + DDRGBColor fg; + DDRGBColor bg; + + NSUInteger fgCodeIndex; + NSString *fgCodeRaw; + + NSUInteger bgCodeIndex; + NSString *bgCodeRaw; + + char fgCode[24]; + size_t fgCodeLen; + + char bgCode[24]; + size_t bgCodeLen; + + char resetCode[8]; + size_t resetCodeLen; +} + +- (nullable instancetype)initWithForegroundColor:(nullable DDColor *)fgColor backgroundColor:(nullable DDColor *)bgColor flag:(DDLogFlag)mask context:(NSInteger)ctxt; + +@end + +@interface DDTTYLogger () { + NSString *_appName; + char *_app; + size_t _appLen; + + NSString *_processID; + char *_pid; + size_t _pidLen; + + BOOL _colorsEnabled; + NSMutableArray *_colorProfilesArray; + NSMutableDictionary *_colorProfilesDict; +} + +@end + +#pragma mark - + +@implementation DDTTYLogger + +static BOOL isaColorTTY; +static BOOL isaColor256TTY; +static BOOL isaXcodeColorTTY; + +static NSArray *codesFg = nil; +static NSArray *codesBg = nil; +static NSArray *colors = nil; + +static DDTTYLogger *sharedInstance; + +/** + * Initializes the colors array, as well as the `codesFg` and `codesBg` arrays, for 16 color mode. + * + * This method is used when the application is running from within a shell that only supports 16 color mode. + * This method is not invoked if the application is running within Xcode, or via normal UI app launch. + **/ ++ (void)initializeColors16 { + if (codesFg || codesBg || colors) { + return; + } + + __auto_type mColors = [NSMutableArray arrayWithCapacity:16]; + + // In a standard shell only 16 colors are supported. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + codesFg = @[ + @"30m", // normal - black + @"31m", // normal - red + @"32m", // normal - green + @"33m", // normal - yellow + @"34m", // normal - blue + @"35m", // normal - magenta + @"36m", // normal - cyan + @"37m", // normal - gray + @"1;30m", // bright - darkgray + @"1;31m", // bright - red + @"1;32m", // bright - green + @"1;33m", // bright - yellow + @"1;34m", // bright - blue + @"1;35m", // bright - magenta + @"1;36m", // bright - cyan + @"1;37m", // bright - white + ]; + + codesBg = @[ + @"40m", // normal - black + @"41m", // normal - red + @"42m", // normal - green + @"43m", // normal - yellow + @"44m", // normal - blue + @"45m", // normal - magenta + @"46m", // normal - cyan + @"47m", // normal - gray + @"1;40m", // bright - darkgray + @"1;41m", // bright - red + @"1;42m", // bright - green + @"1;43m", // bright - yellow + @"1;44m", // bright - blue + @"1;45m", // bright - magenta + @"1;46m", // bright - cyan + @"1;47m", // bright - white + ]; + +#if MAP_TO_TERMINAL_APP_COLORS + + // Standard Terminal.app colors: + // + // These are the default colors used by Apple's Terminal.app. + const DDRGBColor rgbColors[] = { + { 0, 0, 0}, // normal - black + {194, 54, 33}, // normal - red + { 37, 188, 36}, // normal - green + {173, 173, 39}, // normal - yellow + { 73, 46, 225}, // normal - blue + {211, 56, 211}, // normal - magenta + { 51, 187, 200}, // normal - cyan + {203, 204, 205}, // normal - gray + {129, 131, 131}, // bright - darkgray + {252, 57, 31}, // bright - red + { 49, 231, 34}, // bright - green + {234, 236, 35}, // bright - yellow + { 88, 51, 255}, // bright - blue + {249, 53, 248}, // bright - magenta + { 20, 240, 240}, // bright - cyan + {233, 235, 235}, // bright - white + }; + +#else /* if MAP_TO_TERMINAL_APP_COLORS */ + + // Standard xterm colors: + // + // These are the default colors used by most xterm shells. + const DDRGBColor rgbColors[] = { + { 0, 0, 0}, // normal - black + {205, 0, 0}, // normal - red + { 0, 205, 0}, // normal - green + {205, 205, 0}, // normal - yellow + { 0, 0, 238}, // normal - blue + {205, 0, 205}, // normal - magenta + { 0, 205, 205}, // normal - cyan + {229, 229, 229}, // normal - gray + {127, 127, 127}, // bright - darkgray + {255, 0, 0}, // bright - red + { 0, 255, 0}, // bright - green + {255, 255, 0}, // bright - yellow + { 92, 92, 255}, // bright - blue + {255, 0, 255}, // bright - magenta + { 0, 255, 255}, // bright - cyan + {255, 255, 255}, // bright - white + }; +#endif /* if MAP_TO_TERMINAL_APP_COLORS */ + + for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) { + [mColors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)]; + } + colors = [mColors copy]; + + NSAssert([codesFg count] == [codesBg count], @"Invalid colors/codes array(s)"); + NSAssert([codesFg count] == [colors count], @"Invalid colors/codes array(s)"); +} + +/** + * Initializes the colors array, as well as the `codesFg` and `codesBg` arrays, for 256 color mode. + * + * This method is used when the application is running from within a shell that supports 256 color mode. + * This method is not invoked if the application is running within Xcode, or via normal UI app launch. + **/ ++ (void)initializeColors256 { + if (codesFg || codesBg || colors) { + return; + } + + __auto_type mCodesFg = [NSMutableArray arrayWithCapacity:(256 - 16)]; + __auto_type mCodesBg = [NSMutableArray arrayWithCapacity:(256 - 16)]; + __auto_type mColors = [NSMutableArray arrayWithCapacity:(256 - 16)]; + +#if MAP_TO_TERMINAL_APP_COLORS + + // Standard Terminal.app colors: + // + // These are the colors the Terminal.app uses in xterm-256color mode. + // In this mode, the terminal supports 256 different colors, specified by 256 color codes. + // + // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode. + // These are actually configurable, and thus we ignore them for the purposes of mapping, + // as we can't rely on them being constant. They are largely duplicated anyway. + // + // The next 216 color codes are designed to run the spectrum, with several shades of every color. + // While the color codes are standardized, the actual RGB values for each color code is not. + // Apple's Terminal.app uses different RGB values from that of a standard xterm. + // Apple's choices in colors are designed to be a little nicer on the eyes. + // + // The last 24 color codes represent a grayscale. + // + // Unfortunately, unlike the standard xterm color chart, + // Apple's RGB values cannot be calculated using a simple formula (at least not that I know of). + // Also, I don't know of any ways to programmatically query the shell for the RGB values. + // So this big giant color chart had to be made by hand. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + + // Colors + const DDRGBColor rgbColors[] = { + { 47, 49, 49}, + { 60, 42, 144}, + { 66, 44, 183}, + { 73, 46, 222}, + { 81, 50, 253}, + { 88, 51, 255}, + + { 42, 128, 37}, + { 42, 127, 128}, + { 44, 126, 169}, + { 56, 125, 209}, + { 59, 124, 245}, + { 66, 123, 255}, + + { 51, 163, 41}, + { 39, 162, 121}, + { 42, 161, 162}, + { 53, 160, 202}, + { 45, 159, 240}, + { 58, 158, 255}, + + { 31, 196, 37}, + { 48, 196, 115}, + { 39, 195, 155}, + { 49, 195, 195}, + { 32, 194, 235}, + { 53, 193, 255}, + + { 50, 229, 35}, + { 40, 229, 109}, + { 27, 229, 149}, + { 49, 228, 189}, + { 33, 228, 228}, + { 53, 227, 255}, + + { 27, 254, 30}, + { 30, 254, 103}, + { 45, 254, 143}, + { 38, 253, 182}, + { 38, 253, 222}, + { 42, 253, 252}, + + {140, 48, 40}, + {136, 51, 136}, + {135, 52, 177}, + {134, 52, 217}, + {135, 56, 248}, + {134, 53, 255}, + + {125, 125, 38}, + {124, 125, 125}, + {122, 124, 166}, + {123, 124, 207}, + {123, 122, 247}, + {124, 121, 255}, + + {119, 160, 35}, + {117, 160, 120}, + {117, 160, 160}, + {115, 159, 201}, + {116, 158, 240}, + {117, 157, 255}, + + {113, 195, 39}, + {110, 194, 114}, + {111, 194, 154}, + {108, 194, 194}, + {109, 193, 234}, + {108, 192, 255}, + + {105, 228, 30}, + {103, 228, 109}, + {105, 228, 148}, + {100, 227, 188}, + { 99, 227, 227}, + { 99, 226, 253}, + + { 92, 253, 34}, + { 96, 253, 103}, + { 97, 253, 142}, + { 88, 253, 182}, + { 93, 253, 221}, + { 88, 254, 251}, + + {177, 53, 34}, + {174, 54, 131}, + {172, 55, 172}, + {171, 57, 213}, + {170, 55, 249}, + {170, 57, 255}, + + {165, 123, 37}, + {163, 123, 123}, + {162, 123, 164}, + {161, 122, 205}, + {161, 121, 241}, + {161, 121, 255}, + + {158, 159, 33}, + {157, 158, 118}, + {157, 158, 159}, + {155, 157, 199}, + {155, 157, 239}, + {154, 156, 255}, + + {152, 193, 40}, + {151, 193, 113}, + {150, 193, 153}, + {150, 192, 193}, + {148, 192, 232}, + {149, 191, 253}, + + {146, 227, 28}, + {144, 227, 108}, + {144, 227, 147}, + {144, 227, 187}, + {142, 226, 227}, + {142, 225, 252}, + + {138, 253, 36}, + {137, 253, 102}, + {136, 253, 141}, + {138, 254, 181}, + {135, 255, 220}, + {133, 255, 250}, + + {214, 57, 30}, + {211, 59, 126}, + {209, 57, 168}, + {208, 55, 208}, + {207, 58, 247}, + {206, 61, 255}, + + {204, 121, 32}, + {202, 121, 121}, + {201, 121, 161}, + {200, 120, 202}, + {200, 120, 241}, + {198, 119, 255}, + + {198, 157, 37}, + {196, 157, 116}, + {195, 156, 157}, + {195, 156, 197}, + {194, 155, 236}, + {193, 155, 255}, + + {191, 192, 36}, + {190, 191, 112}, + {189, 191, 152}, + {189, 191, 191}, + {188, 190, 230}, + {187, 190, 253}, + + {185, 226, 28}, + {184, 226, 106}, + {183, 225, 146}, + {183, 225, 186}, + {182, 225, 225}, + {181, 224, 252}, + + {178, 255, 35}, + {178, 255, 101}, + {177, 254, 141}, + {176, 254, 180}, + {176, 254, 220}, + {175, 253, 249}, + + {247, 56, 30}, + {245, 57, 122}, + {243, 59, 163}, + {244, 60, 204}, + {242, 59, 241}, + {240, 55, 255}, + + {241, 119, 36}, + {240, 120, 118}, + {238, 119, 158}, + {237, 119, 199}, + {237, 118, 238}, + {236, 118, 255}, + + {235, 154, 36}, + {235, 154, 114}, + {234, 154, 154}, + {232, 154, 194}, + {232, 153, 234}, + {232, 153, 255}, + + {230, 190, 30}, + {229, 189, 110}, + {228, 189, 150}, + {227, 189, 190}, + {227, 189, 229}, + {226, 188, 255}, + + {224, 224, 35}, + {223, 224, 105}, + {222, 224, 144}, + {222, 223, 184}, + {222, 223, 224}, + {220, 223, 253}, + + {217, 253, 28}, + {217, 253, 99}, + {216, 252, 139}, + {216, 252, 179}, + {215, 252, 218}, + {215, 251, 250}, + + {255, 61, 30}, + {255, 60, 118}, + {255, 58, 159}, + {255, 56, 199}, + {255, 55, 238}, + {255, 59, 255}, + + {255, 117, 29}, + {255, 117, 115}, + {255, 117, 155}, + {255, 117, 195}, + {255, 116, 235}, + {254, 116, 255}, + + {255, 152, 27}, + {255, 152, 111}, + {254, 152, 152}, + {255, 152, 192}, + {254, 151, 231}, + {253, 151, 253}, + + {255, 187, 33}, + {253, 187, 107}, + {252, 187, 148}, + {253, 187, 187}, + {254, 187, 227}, + {252, 186, 252}, + + {252, 222, 34}, + {251, 222, 103}, + {251, 222, 143}, + {250, 222, 182}, + {251, 221, 222}, + {252, 221, 252}, + + {251, 252, 15}, + {251, 252, 97}, + {249, 252, 137}, + {247, 252, 177}, + {247, 253, 217}, + {254, 255, 255}, + + // Grayscale + + { 52, 53, 53}, + { 57, 58, 59}, + { 66, 67, 67}, + { 75, 76, 76}, + { 83, 85, 85}, + { 92, 93, 94}, + + {101, 102, 102}, + {109, 111, 111}, + {118, 119, 119}, + {126, 127, 128}, + {134, 136, 136}, + {143, 144, 145}, + + {151, 152, 153}, + {159, 161, 161}, + {167, 169, 169}, + {176, 177, 177}, + {184, 185, 186}, + {192, 193, 194}, + + {200, 201, 202}, + {208, 209, 210}, + {216, 218, 218}, + {224, 226, 226}, + {232, 234, 234}, + {240, 242, 242}, + }; + + for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) { + [mColors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)]; + } + + // Color codes + int index = 16; + while (index < 256) { + [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + + index++; + } + +#else /* if MAP_TO_TERMINAL_APP_COLORS */ + + // Standard xterm colors: + // + // These are the colors xterm shells use in xterm-256color mode. + // In this mode, the shell supports 256 different colors, specified by 256 color codes. + // + // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode. + // These are generally configurable, and thus we ignore them for the purposes of mapping, + // as we can't rely on them being constant. They are largely duplicated anyway. + // + // The next 216 color codes are designed to run the spectrum, with several shades of every color. + // The last 24 color codes represent a grayscale. + // + // While the color codes are standardized, the actual RGB values for each color code is not. + // However most standard xterms follow a well known color chart, + // which can easily be calculated using the simple formula below. + // + // More information about ansi escape codes can be found online. + // http://en.wikipedia.org/wiki/ANSI_escape_code + + int index = 16; + + int r; // red + int g; // green + int b; // blue + + int ri; // r increment + int gi; // g increment + int bi; // b increment + + // Calculate xterm colors (using standard algorithm) + + int r = 0; + int g = 0; + int b = 0; + + for (ri = 0; ri < 6; ri++) { + r = (ri == 0) ? 0 : 95 + (40 * (ri - 1)); + + for (gi = 0; gi < 6; gi++) { + g = (gi == 0) ? 0 : 95 + (40 * (gi - 1)); + + for (bi = 0; bi < 6; bi++) { + b = (bi == 0) ? 0 : 95 + (40 * (bi - 1)); + + [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + [mColors addObject:DDMakeColor(r, g, b)]; + + index++; + } + } + } + + // Calculate xterm grayscale (using standard algorithm) + + r = 8; + g = 8; + b = 8; + + while (index < 256) { + [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]]; + [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]]; + [mColor s addObject:DDMakeColor(r, g, b)]; + + r += 10; + g += 10; + b += 10; + + index++; + } + +#endif /* if MAP_TO_TERMINAL_APP_COLORS */ + + codesFg = [mCodesFg copy]; + codesBg = [mCodesBg copy]; + colors = [mColors copy]; + + NSAssert([codesFg count] == [codesBg count], @"Invalid colors/codes array(s)"); + NSAssert([codesFg count] == [colors count], @"Invalid colors/codes array(s)"); +} + ++ (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromColor:(DDColor *)color { +#if TARGET_OS_IPHONE + + // iOS + __auto_type done = NO; + + if ([color respondsToSelector:@selector(getRed:green:blue:alpha:)]) { + done = [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + } + + if (!done) { + // The method getRed:green:blue:alpha: was only available starting iOS 5. + // So in iOS 4 and earlier, we have to jump through hoops. + + __auto_type rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + + unsigned char pixel[4]; + __auto_type context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, rgbColorSpace, (CGBitmapInfo)(((CGImageAlphaInfo)kCGBitmapAlphaInfoMask) & kCGImageAlphaNoneSkipLast)); + + CGContextSetFillColorWithColor(context, [color CGColor]); + CGContextFillRect(context, CGRectMake(0, 0, 1, 1)); + + if (rPtr) { + *rPtr = pixel[0] / 255.0; + } + if (gPtr) { + *gPtr = pixel[1] / 255.0; + } + if (bPtr) { + *bPtr = pixel[2] / 255.0; + } + + CGContextRelease(context); + CGColorSpaceRelease(rgbColorSpace); + } + +#elif defined(DD_CLI) || !__has_include() + + // OS X without AppKit + [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + +#else /* if TARGET_OS_IPHONE */ + + // OS X with AppKit + NSColor *safeColor; + if (@available(macOS 10.14,*)) { + safeColor = [color colorUsingColorSpace:NSColorSpace.deviceRGBColorSpace]; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + safeColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; +#pragma clang diagnostic pop + } + + [safeColor getRed:rPtr green:gPtr blue:bPtr alpha:NULL]; + +#endif /* if TARGET_OS_IPHONE */ +} + +/** + * Maps the given color to the closest available color supported by the shell. + * The shell may support 256 colors, or only 16. + * + * This method loops through the known supported color set, and calculates the closest color. + * The array index of that color, within the colors array, is then returned. + * This array index may also be used as the index within the `codesFg` and `codesBg` arrays. + **/ ++ (NSUInteger)codeIndexForColor:(DDColor *)inColor { + CGFloat inR, inG, inB; + + [self getRed:&inR green:&inG blue:&inB fromColor:inColor]; + + NSUInteger bestIndex = 0; + CGFloat lowestDistance = 100.0; + + NSUInteger i = 0; + + for (DDColor *color in colors) { + // Calculate Euclidean distance (lower value means closer to given color) + + CGFloat r, g, b; + [self getRed:&r green:&g blue:&b fromColor:color]; + +#if CGFLOAT_IS_DOUBLE + __auto_type distance = sqrt(pow(r - inR, 2.0) + pow(g - inG, 2.0) + pow(b - inB, 2.0)); +#else + __auto_type distance = sqrtf(powf(r - inR, 2.0f) + powf(g - inG, 2.0f) + powf(b - inB, 2.0f)); +#endif + + NSLogVerbose(@"DDTTYLogger: %3lu : %.3f,%.3f,%.3f & %.3f,%.3f,%.3f = %.6f", + (unsigned long)i, (double)inR, (double)inG, (double)inB, (double)r, (double)g, (double)b, (double)distance); + + if (distance < lowestDistance) { + bestIndex = i; + lowestDistance = distance; + + NSLogVerbose(@"DDTTYLogger: New best index = %lu", (unsigned long)bestIndex); + } + + i++; + } + + return bestIndex; +} + ++ (instancetype)sharedInstance { + static dispatch_once_t DDTTYLoggerOnceToken; + + dispatch_once(&DDTTYLoggerOnceToken, ^{ + // Xcode does NOT natively support colors in the Xcode debugging console. + // You'll need to install the XcodeColors plugin to see colors in the Xcode console. + // + // PS - Please read the header file before diving into the source code. + + __auto_type xcodeColors = getenv("XcodeColors"); + __auto_type term = getenv("TERM"); + + if (xcodeColors && (strcmp(xcodeColors, "YES") == 0)) { + isaXcodeColorTTY = YES; + } else if (term) { + if (strcasestr(term, "color") != NULL) { + isaColorTTY = YES; + isaColor256TTY = (strcasestr(term, "256") != NULL); + + if (isaColor256TTY) { + [self initializeColors256]; + } else { + [self initializeColors16]; + } + } + } + + NSLogInfo(@"DDTTYLogger: isaColorTTY = %@", (isaColorTTY ? @"YES" : @"NO")); + NSLogInfo(@"DDTTYLogger: isaColor256TTY: %@", (isaColor256TTY ? @"YES" : @"NO")); + NSLogInfo(@"DDTTYLogger: isaXcodeColorTTY: %@", (isaXcodeColorTTY ? @"YES" : @"NO")); + + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + +- (instancetype)init { + if (sharedInstance != nil) { + return nil; + } + +#if !defined(DD_CLI) || __has_include() + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + NSLogWarn(@"CocoaLumberjack: Warning: Usage of DDTTYLogger detected when DDOSLogger is available and can be used! Please consider migrating to DDOSLogger."); + } +#endif + + if ((self = [super init])) { + // Initialize 'app' variable (char *) + _appName = [[NSProcessInfo processInfo] processName]; + _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + if (_appLen == 0) { + _appName = @""; + _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + } + + _app = (char *)calloc(_appLen + 1, sizeof(char)); + if (_app == NULL) { + return nil; + } + + BOOL processedAppName = [_appName getCString:_app maxLength:(_appLen + 1) encoding:NSUTF8StringEncoding]; + if (!processedAppName) { + free(_app); + return nil; + } + + // Initialize 'pid' variable (char *) + + _processID = [NSString stringWithFormat:@"%i", (int)getpid()]; + + _pidLen = [_processID lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + _pid = (char *)calloc(_pidLen + 1, sizeof(char)); + + if (_pid == NULL) { + free(_app); + return nil; + } + + BOOL processedID = [_processID getCString:_pid maxLength:(_pidLen + 1) encoding:NSUTF8StringEncoding]; + if (!processedID) { + free(_app); + free(_pid); + return nil; + } + + // Initialize color stuff + + _colorsEnabled = NO; + _colorProfilesArray = [[NSMutableArray alloc] initWithCapacity:8]; + _colorProfilesDict = [[NSMutableDictionary alloc] initWithCapacity:8]; + + _automaticallyAppendNewlineForCustomFormatters = YES; + } + + return self; +} + +- (DDLoggerName)loggerName { + return DDLoggerNameTTY; +} + +- (void)loadDefaultColorProfiles { + [self setForegroundColor:DDMakeColor(214, 57, 30) backgroundColor:nil forFlag:DDLogFlagError]; + [self setForegroundColor:DDMakeColor(204, 121, 32) backgroundColor:nil forFlag:DDLogFlagWarning]; +} + +- (BOOL)colorsEnabled { + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + __block BOOL result; + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.loggerQueue, ^{ + result = self->_colorsEnabled; + }); + }); + + return result; +} + +- (void)setColorsEnabled:(BOOL)newColorsEnabled { + __auto_type block = ^{ + @autoreleasepool { + self->_colorsEnabled = newColorsEnabled; + + if ([self->_colorProfilesArray count] == 0) { + [self loadDefaultColorProfiles]; + } + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + // Note: The internal implementation MUST access the colorsEnabled variable directly, + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + DDAbstractLoggerAssertLockedPropertyAccess(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); +} + +- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask { + [self setForegroundColor:txtColor backgroundColor:bgColor forFlag:mask context:LOG_CONTEXT_ALL]; +} + +- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask context:(NSInteger)ctxt { + dispatch_block_t block = ^{ + @autoreleasepool { + DDTTYLoggerColorProfile *newColorProfile = [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor + backgroundColor:bgColor + flag:mask + context:ctxt]; + if (!newColorProfile) return; + + NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile); + + NSUInteger i = 0; + + for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) { + if ((colorProfile->mask == mask) && (colorProfile->context == ctxt)) { + break; + } + + i++; + } + + if (i < [self->_colorProfilesArray count]) { + self->_colorProfilesArray[i] = newColorProfile; + } else { + [self->_colorProfilesArray addObject:newColorProfile]; + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forTag:(id )tag { + NSAssert([(id )tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag"); + + __auto_type block = ^{ + @autoreleasepool { + __auto_type newColorProfile = [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor + backgroundColor:bgColor + flag:(DDLogFlag)0 + context:0]; + + NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile); + + self->_colorProfilesDict[tag] = newColorProfile; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForFlag:(DDLogFlag)mask { + [self clearColorsForFlag:mask context:0]; +} + +- (void)clearColorsForFlag:(DDLogFlag)mask context:(NSInteger)context { + __auto_type block = ^{ + @autoreleasepool { + NSUInteger i = 0; + + for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) { + if ((colorProfile->mask == mask) && (colorProfile->context == context)) { + break; + } + + i++; + } + + if (i < [self->_colorProfilesArray count]) { + [self->_colorProfilesArray removeObjectAtIndex:i]; + } + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForTag:(id )tag { + NSAssert([(id ) tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag"); + + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesDict removeObjectForKey:tag]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForAllFlags { + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesArray removeAllObjects]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearColorsForAllTags { + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesDict removeAllObjects]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)clearAllColors { + __auto_type block = ^{ + @autoreleasepool { + [self->_colorProfilesArray removeAllObjects]; + [self->_colorProfilesDict removeAllObjects]; + } + }; + + // The design of the setter logic below is taken from the DDAbstractLogger implementation. + // For documentation please refer to the DDAbstractLogger implementation. + + if ([self isOnInternalLoggerQueue]) { + block(); + } else { + DDAbstractLoggerAssertNotOnGlobalLoggingQueue(); + dispatch_async(DDLog.loggingQueue, ^{ + dispatch_async(self.loggerQueue, block); + }); + } +} + +- (void)logMessage:(DDLogMessage *)logMessage { + __auto_type logMsg = logMessage->_message; + __auto_type isFormatted = NO; + + if (_logFormatter) { + logMsg = [_logFormatter formatLogMessage:logMessage]; + isFormatted = logMsg != logMessage->_message; + } + + if (logMsg) { + // Search for a color profile associated with the log message + + DDTTYLoggerColorProfile *colorProfile = nil; + + if (_colorsEnabled) { + if (logMessage->_representedObject) { + colorProfile = _colorProfilesDict[logMessage->_representedObject]; + } + + if (colorProfile == nil) { + for (DDTTYLoggerColorProfile *cp in _colorProfilesArray) { + if (logMessage->_flag & cp->mask) { + // Color profile set for this context? + if (logMessage->_context == cp->context) { + colorProfile = cp; + + // Stop searching + break; + } + + // Check if LOG_CONTEXT_ALL was specified as a default color for this flag + if (cp->context == LOG_CONTEXT_ALL) { + colorProfile = cp; + + // We don't break to keep searching for more specific color profiles for the context + } + } + } + } + } + + // Convert log message to C string. + // + // We use the stack instead of the heap for speed if possible. + // But we're extra cautious to avoid a stack overflow. + + __auto_type msgLen = [logMsg lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + const __auto_type useStack = msgLen < (1024 * 4); + + char *msg; + if (useStack) { + msg = (char *)alloca(msgLen + 1); + } else { + msg = (char *)calloc(msgLen + 1, sizeof(char)); + } + if (msg == NULL) { + return; + } + + BOOL logMsgEnc = [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding]; + if (!logMsgEnc) { + if (!useStack) { + free(msg); + } + return; + } + + // Write the log message to STDERR + + if (isFormatted) { + // The log message has already been formatted. + + // Needs to be a define, because otherwise the compiler warns "Variable length array folded to constant array as an extension" +#define DD_TTYLOGGER_MAX_IOVEC_LEN 5 + + size_t iovecLen = _automaticallyAppendNewlineForCustomFormatters ? 5 : 4; + struct iovec v[DD_TTYLOGGER_MAX_IOVEC_LEN] = { 0 }; + + if (colorProfile) { + v[0].iov_base = colorProfile->fgCode; + v[0].iov_len = colorProfile->fgCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[DD_TTYLOGGER_MAX_IOVEC_LEN - 1].iov_base = colorProfile->resetCode; + v[DD_TTYLOGGER_MAX_IOVEC_LEN - 1].iov_len = colorProfile->resetCodeLen; + } + + v[2].iov_base = msg; + v[2].iov_len = (msgLen > SIZE_MAX - 1) ? SIZE_MAX - 1 : msgLen; + + if (_automaticallyAppendNewlineForCustomFormatters && (v[2].iov_len == 0 || msg[v[2].iov_len - 1] != '\n')) { + v[3].iov_base = "\n"; + v[3].iov_len = 1; + iovecLen = 5; + } + + writev(STDERR_FILENO, v, (int)iovecLen); + } else { + // The log message is unformatted, so apply standard NSLog style formatting. + + int len; + char ts[24] = ""; + size_t tsLen = 0; + + // Calculate timestamp. + // The technique below is faster than using NSDateFormatter. + if (logMessage->_timestamp) { + __auto_type epoch = [logMessage->_timestamp timeIntervalSince1970]; + double integral; + __auto_type fract = modf(epoch, &integral); + struct tm tm; + __auto_type time = (time_t)integral; + (void)localtime_r(&time, &tm); + __auto_type milliseconds = (long)(fract * 1000.0); + + len = snprintf(ts, 24, "%04d-%02d-%02d %02d:%02d:%02d:%03ld", // yyyy-MM-dd HH:mm:ss:SSS + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, milliseconds); + + tsLen = (NSUInteger)MAX(MIN(24 - 1, len), 0); + } + + // Calculate thread ID + // + // How many characters do we need for the thread id? + // logMessage->machThreadID is of type mach_port_t, which is an unsigned int. + // + // 1 hex char = 4 bits + // 8 hex chars for 32 bit, plus ending '\0' = 9 + + char tid[9]; + len = snprintf(tid, 9, "%s", [logMessage->_threadID cStringUsingEncoding:NSUTF8StringEncoding]); + + __auto_type tidLen = (NSUInteger)MAX(MIN(9 - 1, len), 0); + + // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg + + struct iovec v[13]; + + if (colorProfile) { + v[0].iov_base = colorProfile->fgCode; + v[0].iov_len = colorProfile->fgCodeLen; + + v[1].iov_base = colorProfile->bgCode; + v[1].iov_len = colorProfile->bgCodeLen; + + v[12].iov_base = colorProfile->resetCode; + v[12].iov_len = colorProfile->resetCodeLen; + } else { + v[0].iov_base = ""; + v[0].iov_len = 0; + + v[1].iov_base = ""; + v[1].iov_len = 0; + + v[12].iov_base = ""; + v[12].iov_len = 0; + } + + v[2].iov_base = ts; + v[2].iov_len = tsLen; + + v[3].iov_base = " "; + v[3].iov_len = 1; + + v[4].iov_base = _app; + v[4].iov_len = _appLen; + + v[5].iov_base = "["; + v[5].iov_len = 1; + + v[6].iov_base = _pid; + v[6].iov_len = _pidLen; + + v[7].iov_base = ":"; + v[7].iov_len = 1; + + v[8].iov_base = tid; + v[8].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think + + v[9].iov_base = "] "; + v[9].iov_len = 2; + + v[10].iov_base = (char *)msg; + v[10].iov_len = msgLen; + + v[11].iov_base = "\n"; + v[11].iov_len = (msg[msgLen] == '\n') ? 0 : 1; + + writev(STDERR_FILENO, v, 13); + } + + if (!useStack) { + free(msg); + } + } +} + +@end + +#pragma mark - + +@implementation DDTTYLoggerColorProfile + +- (instancetype)initWithForegroundColor:(DDColor *)fgColor backgroundColor:(DDColor *)bgColor flag:(DDLogFlag)aMask context:(NSInteger)ctxt { + if ((self = [super init])) { + mask = aMask; + context = ctxt; + + CGFloat r, g, b; + + if (fgColor) { + [DDTTYLogger getRed:&r green:&g blue:&b fromColor:fgColor]; + + fg.r = (uint8_t)(r * (CGFloat)255.0); + fg.g = (uint8_t)(g * (CGFloat)255.0); + fg.b = (uint8_t)(b * (CGFloat)255.0); + } + + if (bgColor) { + [DDTTYLogger getRed:&r green:&g blue:&b fromColor:bgColor]; + + bg.r = (uint8_t)(r * (CGFloat)255.0); + bg.g = (uint8_t)(g * (CGFloat)255.0); + bg.b = (uint8_t)(b * (CGFloat)255.0); + } + + if (fgColor && isaColorTTY) { + // Map foreground color to closest available shell color + + fgCodeIndex = [DDTTYLogger codeIndexForColor:fgColor]; + fgCodeRaw = codesFg[fgCodeIndex]; + + const __auto_type escapeSeq = @"\033["; + + __auto_type len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + __auto_type len2 = [fgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + BOOL escapeSeqEnc = [escapeSeq getCString:(fgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding]; + BOOL fgCodeRawEsc = [fgCodeRaw getCString:(fgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding]; + + if (!escapeSeqEnc || !fgCodeRawEsc) { + return nil; + } + + fgCodeLen = len1 + len2; + } else if (fgColor && isaXcodeColorTTY) { + // Convert foreground color to color code sequence + const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ; + __auto_type result = snprintf(fgCode, 24, "%sfg%u,%u,%u;", escapeSeq, fg.r, fg.g, fg.b); + fgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0); + } else { + // No foreground color or no color support + fgCode[0] = '\0'; + fgCodeLen = 0; + } + + if (bgColor && isaColorTTY) { + // Map background color to closest available shell color + + bgCodeIndex = [DDTTYLogger codeIndexForColor:bgColor]; + bgCodeRaw = codesBg[bgCodeIndex]; + + const __auto_type escapeSeq = @"\033["; + + __auto_type len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + __auto_type len2 = [bgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + + BOOL escapeSeqEnc = [escapeSeq getCString:(bgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding]; + BOOL bgCodeRawEsc = [bgCodeRaw getCString:(bgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding]; + + if (!escapeSeqEnc || !bgCodeRawEsc) { + return nil; + } + + bgCodeLen = len1 + len2; + } else if (bgColor && isaXcodeColorTTY) { + // Convert background color to color code sequence + const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ; + __auto_type result = snprintf(bgCode, 24, "%sbg%u,%u,%u;", escapeSeq, bg.r, bg.g, bg.b); + bgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0); + } else { + // No background color or no color support + bgCode[0] = '\0'; + bgCodeLen = 0; + } + + if (isaColorTTY) { + resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, "\033[0m"), 0); + } else if (isaXcodeColorTTY) { + resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, XCODE_COLORS_RESET), 0); + } else { + resetCode[0] = '\0'; + resetCodeLen = 0; + } + } + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat: + @"", + self, (int)mask, (long)context, fg.r, fg.g, fg.b, bg.r, bg.g, bg.b, fgCodeRaw, bgCodeRaw]; +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m new file mode 100644 index 0000000..3deaf70 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m @@ -0,0 +1,57 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +@implementation DDContextAllowlistFilterLogFormatter (Deprecated) + +- (void)addToWhitelist:(NSInteger)loggingContext { + [self addToAllowlist:loggingContext]; +} + +- (void)removeFromWhitelist:(NSInteger)loggingContext { + [self removeFromAllowlist:loggingContext]; +} + +- (NSArray *)whitelist { + return [self allowlist]; +} + +- (BOOL)isOnWhitelist:(NSInteger)loggingContext { + return [self isOnAllowlist:loggingContext]; +} + +@end + + +@implementation DDContextDenylistFilterLogFormatter (Deprecated) + +- (void)addToBlacklist:(NSInteger)loggingContext { + [self addToDenylist:loggingContext]; +} + +- (void)removeFromBlacklist:(NSInteger)loggingContext { + [self removeFromDenylist:loggingContext]; +} + +- (NSArray *)blacklist { + return [self denylist]; +} + +- (BOOL)isOnBlacklist:(NSInteger)loggingContext { + return [self isOnDenylist:loggingContext]; +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m new file mode 100755 index 0000000..ea82ad1 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m @@ -0,0 +1,185 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +#import + +@interface DDLoggingContextSet : NSObject + +@property (readonly, copy, nonnull) NSArray *currentSet; + +- (void)addToSet:(NSInteger)loggingContext; +- (void)removeFromSet:(NSInteger)loggingContext; + +- (BOOL)isInSet:(NSInteger)loggingContext; + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDContextAllowlistFilterLogFormatter () { + DDLoggingContextSet *_contextSet; +} +@end + +@implementation DDContextAllowlistFilterLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _contextSet = [[DDLoggingContextSet alloc] init]; + } + return self; +} + +- (void)addToAllowlist:(NSInteger)loggingContext { + [_contextSet addToSet:loggingContext]; +} + +- (void)removeFromAllowlist:(NSInteger)loggingContext { + [_contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)allowlist { + return [_contextSet currentSet]; +} + +- (BOOL)isOnAllowlist:(NSInteger)loggingContext { + return [_contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + if ([self isOnAllowlist:logMessage->_context]) { + return logMessage->_message; + } else { + return nil; + } +} + +@end + + +@interface DDContextDenylistFilterLogFormatter () { + DDLoggingContextSet *_contextSet; +} +@end + +@implementation DDContextDenylistFilterLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _contextSet = [[DDLoggingContextSet alloc] init]; + } + return self; +} + +- (void)addToDenylist:(NSInteger)loggingContext { + [_contextSet addToSet:loggingContext]; +} + +- (void)removeFromDenylist:(NSInteger)loggingContext { + [_contextSet removeFromSet:loggingContext]; +} + +- (NSArray *)denylist { + return [_contextSet currentSet]; +} + +- (BOOL)isOnDenylist:(NSInteger)loggingContext { + return [_contextSet isInSet:loggingContext]; +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + if ([self isOnDenylist:logMessage->_context]) { + return nil; + } else { + return logMessage->_message; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@interface DDLoggingContextSet () { + pthread_mutex_t _mutex; + NSMutableSet *_set; +} +@end + +@implementation DDLoggingContextSet + +- (instancetype)init { + if ((self = [super init])) { + _set = [[NSMutableSet alloc] init]; + pthread_mutex_init(&_mutex, NULL); + } + + return self; +} + +- (void)dealloc { + pthread_mutex_destroy(&_mutex); +} + +- (void)addToSet:(NSInteger)loggingContext { + pthread_mutex_lock(&_mutex); + { + [_set addObject:@(loggingContext)]; + } + pthread_mutex_unlock(&_mutex); +} + +- (void)removeFromSet:(NSInteger)loggingContext { + pthread_mutex_lock(&_mutex); + { + [_set removeObject:@(loggingContext)]; + } + pthread_mutex_unlock(&_mutex); +} + +- (NSArray *)currentSet { + NSArray *result = nil; + + pthread_mutex_lock(&_mutex); + { + result = [_set allObjects]; + } + pthread_mutex_unlock(&_mutex); + + return result; +} + +- (BOOL)isInSet:(NSInteger)loggingContext { + __auto_type result = NO; + + pthread_mutex_lock(&_mutex); + { + result = [_set containsObject:@(loggingContext)]; + } + pthread_mutex_unlock(&_mutex); + + return result; +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m new file mode 100755 index 0000000..7bfea6f --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m @@ -0,0 +1,240 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import +#import +#import + +#import + +DDQualityOfServiceName const DDQualityOfServiceUserInteractive = @"UI"; +DDQualityOfServiceName const DDQualityOfServiceUserInitiated = @"IN"; +DDQualityOfServiceName const DDQualityOfServiceDefault = @"DF"; +DDQualityOfServiceName const DDQualityOfServiceUtility = @"UT"; +DDQualityOfServiceName const DDQualityOfServiceBackground = @"BG"; +DDQualityOfServiceName const DDQualityOfServiceUnspecified = @"UN"; + +static DDQualityOfServiceName _qos_name(NSUInteger qos) { + switch ((qos_class_t) qos) { + case QOS_CLASS_USER_INTERACTIVE: return DDQualityOfServiceUserInteractive; + case QOS_CLASS_USER_INITIATED: return DDQualityOfServiceUserInitiated; + case QOS_CLASS_DEFAULT: return DDQualityOfServiceDefault; + case QOS_CLASS_UTILITY: return DDQualityOfServiceUtility; + case QOS_CLASS_BACKGROUND: return DDQualityOfServiceBackground; + default: return DDQualityOfServiceUnspecified; + } +} + +#pragma mark - DDDispatchQueueLogFormatter + +@interface DDDispatchQueueLogFormatter () { + NSDateFormatter *_dateFormatter; // Use [self stringFromDate] + + pthread_mutex_t _mutex; + + NSUInteger _minQueueLength; // _prefix == Only access via atomic property + NSUInteger _maxQueueLength; // _prefix == Only access via atomic property + NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock +} +@end + + +@implementation DDDispatchQueueLogFormatter + +- (instancetype)init { + if ((self = [super init])) { + _dateFormatter = [self createDateFormatter]; + + pthread_mutex_init(&_mutex, NULL); + _replacements = [[NSMutableDictionary alloc] init]; + + // Set default replacements: + _replacements[@"com.apple.main-thread"] = @"main"; + } + + return self; +} + +- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode { + return [self init]; +} + +- (void)dealloc { + pthread_mutex_destroy(&_mutex); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Configuration +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@synthesize minQueueLength = _minQueueLength; +@synthesize maxQueueLength = _maxQueueLength; + +- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel { + NSString *result = nil; + + pthread_mutex_lock(&_mutex); + { + result = _replacements[longLabel]; + } + pthread_mutex_unlock(&_mutex); + + return result; +} + +- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel { + pthread_mutex_lock(&_mutex); + { + if (shortLabel) { + _replacements[longLabel] = shortLabel; + } else { + [_replacements removeObjectForKey:longLabel]; + } + } + pthread_mutex_unlock(&_mutex); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark DDLogFormatter +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (NSDateFormatter *)createDateFormatter { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [self configureDateFormatter:formatter]; + return formatter; +} + +- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter { + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"]; + [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; + [dateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]]; +} + +- (NSString *)stringFromDate:(NSDate *)date { + return [_dateFormatter stringFromDate:date]; +} + +- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage { + // As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue + + __auto_type useQueueLabel = NO; + if (logMessage->_queueLabel) { + useQueueLabel = YES; + + // If you manually create a thread, it's dispatch_queue will have one of the thread names below. + // Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID. + const NSArray *names = @[ + @"com.apple.root.low-priority", + @"com.apple.root.default-priority", + @"com.apple.root.high-priority", + @"com.apple.root.low-overcommit-priority", + @"com.apple.root.default-overcommit-priority", + @"com.apple.root.high-overcommit-priority", + @"com.apple.root.default-qos.overcommit", + ]; + for (NSString *name in names) { + if ([logMessage->_queueLabel isEqualToString:name]) { + useQueueLabel = NO; + break; + } + } + } + + // Get the name of the queue, thread, or machID (whichever we are to use). + NSString *queueThreadLabel; + if (useQueueLabel || [logMessage->_threadName length] > 0) { + __auto_type fullLabel = useQueueLabel ? logMessage->_queueLabel : logMessage->_threadName; + + NSString *abrvLabel; + pthread_mutex_lock(&_mutex); + { + abrvLabel = _replacements[fullLabel]; + } + pthread_mutex_unlock(&_mutex); + + queueThreadLabel = abrvLabel ?: fullLabel; + } else { + queueThreadLabel = logMessage->_threadID; + } + + // Now use the thread label in the output + // labelLength > maxQueueLength : truncate + // labelLength < minQueueLength : padding + // : exact + __auto_type minQueueLength = self.minQueueLength; + __auto_type maxQueueLength = self.maxQueueLength; + __auto_type labelLength = [queueThreadLabel length]; + if (maxQueueLength > 0 && labelLength > maxQueueLength) { + // Truncate + return [queueThreadLabel substringToIndex:maxQueueLength]; + } else if (labelLength < minQueueLength) { + // Padding + return [queueThreadLabel stringByPaddingToLength:minQueueLength + withString:@" " + startingAtIndex:0]; + } else { + // Exact + return queueThreadLabel; + } +} + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + __auto_type timestamp = [self stringFromDate:logMessage->_timestamp]; + __auto_type queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage]; + + return [NSString stringWithFormat:@"%@ [%@ (QOS:%@)] %@", timestamp, queueThreadLabel, _qos_name(logMessage->_qos), logMessage->_message]; +} + +@end + +#pragma mark - DDAtomicCounter + +@interface DDAtomicCounter() { + atomic_int_fast32_t _value; +} +@end + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +@implementation DDAtomicCounter +#pragma clang diagnostic pop + +- (instancetype)initWithDefaultValue:(int32_t)defaultValue { + if ((self = [super init])) { + atomic_init(&_value, defaultValue); + } + return self; +} + +- (int32_t)value { + return atomic_load_explicit(&_value, memory_order_relaxed); +} + +- (int32_t)increment { + int32_t old = atomic_fetch_add_explicit(&_value, 1, memory_order_relaxed); + return (old + 1); +} + +- (int32_t)decrement { + int32_t old = atomic_fetch_sub_explicit(&_value, 1, memory_order_relaxed); + return (old - 1); +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m new file mode 100644 index 0000000..f59675c --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m @@ -0,0 +1,202 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#import +#import "../DDFileLogger+Internal.h" + +static const NSUInteger kDDDefaultBufferSize = 4096; // 4 kB, block f_bsize on iphone7 +static const NSUInteger kDDMaxBufferSize = 1048576; // ~1 mB, f_iosize on iphone7 + +// Reads attributes from base file system to determine buffer size. +// see statfs in sys/mount.h for descriptions of f_iosize and f_bsize. +// f_bsize == "default", and f_iosize == "max" +static inline NSUInteger p_DDGetDefaultBufferSizeBytesMax(const BOOL max) { + struct statfs *mountedFileSystems = NULL; + __auto_type count = getmntinfo(&mountedFileSystems, 0); + + for (int i = 0; i < count; i++) { + __auto_type mounted = mountedFileSystems[i]; + __auto_type name = mounted.f_mntonname; + + // We can use 2 as max here, since any length > 1 will fail the if-statement. + if (strnlen(name, 2) == 1 && *name == '/') { + return max ? (NSUInteger)mounted.f_iosize : (NSUInteger)mounted.f_bsize; + } + } + + return max ? kDDMaxBufferSize : kDDDefaultBufferSize; +} + +static NSUInteger DDGetMaxBufferSizeBytes(void) { + static NSUInteger maxBufferSize = 0; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + maxBufferSize = p_DDGetDefaultBufferSizeBytesMax(YES); + }); + return maxBufferSize; +} + +static NSUInteger DDGetDefaultBufferSizeBytes(void) { + static NSUInteger defaultBufferSize = 0; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaultBufferSize = p_DDGetDefaultBufferSizeBytesMax(NO); + }); + return defaultBufferSize; +} + +@interface DDBufferedProxy : NSProxy + +@property (nonatomic) DDFileLogger *fileLogger; +@property (nonatomic) NSOutputStream *buffer; + +@property (nonatomic) NSUInteger maxBufferSizeBytes; +@property (nonatomic) NSUInteger currentBufferSizeBytes; + +@end + +@implementation DDBufferedProxy + +- (instancetype)initWithFileLogger:(DDFileLogger *)fileLogger { + _fileLogger = fileLogger; + _maxBufferSizeBytes = DDGetDefaultBufferSizeBytes(); + [self flushBuffer]; + + return self; +} + +- (void)dealloc { + __auto_type block = ^{ + [self lt_sendBufferedDataToFileLogger]; + self.fileLogger = nil; + }; + + if ([self->_fileLogger isOnInternalLoggerQueue]) { + block(); + } else { + dispatch_sync(self->_fileLogger.loggerQueue, block); + } +} + +#pragma mark - Buffering + +- (void)flushBuffer { + [_buffer close]; + _buffer = [NSOutputStream outputStreamToMemory]; + [_buffer open]; + _currentBufferSizeBytes = 0; +} + +- (void)lt_sendBufferedDataToFileLogger { + NSData *data = [_buffer propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + [_fileLogger lt_logData:data]; + [self flushBuffer]; +} + +#pragma mark - Logging + +- (void)logMessage:(DDLogMessage *)logMessage { + // Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us. + __auto_type data = [_fileLogger lt_dataForMessage:logMessage]; + + if (data.length == 0) { + return; + } + + [data enumerateByteRangesUsingBlock:^(const void * __nonnull bytes, NSRange byteRange, BOOL * __nonnull __unused stop) { + __auto_type bytesLength = byteRange.length; +#ifdef NS_BLOCK_ASSERTIONS + __unused +#endif + __auto_type written = [_buffer write:bytes maxLength:bytesLength]; + NSAssert(written > 0 && (NSUInteger)written == bytesLength, @"Failed to write to memory buffer."); + + _currentBufferSizeBytes += bytesLength; + + if (_currentBufferSizeBytes >= _maxBufferSizeBytes) { + [self lt_sendBufferedDataToFileLogger]; + } + }]; +} + +- (void)flush { + // This method is public. + // We need to execute the rolling on our logging thread/queue. + + __auto_type block = ^{ + @autoreleasepool { + [self lt_sendBufferedDataToFileLogger]; + [self.fileLogger flush]; + } + }; + + // The design of this method is taken from the DDAbstractLogger implementation. + // For extensive documentation please refer to the DDAbstractLogger implementation. + + if ([self.fileLogger isOnInternalLoggerQueue]) { + block(); + } else { + NSAssert(![self.fileLogger isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + dispatch_sync(DDLog.loggingQueue, ^{ + dispatch_sync(self.fileLogger.loggerQueue, block); + }); + } +} + +#pragma mark - Properties + +- (void)setMaxBufferSizeBytes:(NSUInteger)newBufferSizeBytes { + _maxBufferSizeBytes = MIN(newBufferSizeBytes, DDGetMaxBufferSizeBytes()); +} + +#pragma mark - Wrapping + +- (DDFileLogger *)wrapWithBuffer { + return (DDFileLogger *)self; +} + +- (DDFileLogger *)unwrapFromBuffer { + return (DDFileLogger *)self.fileLogger; +} + +#pragma mark - NSProxy + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { + return [self.fileLogger methodSignatureForSelector:sel]; +} + +- (BOOL)respondsToSelector:(SEL)aSelector { + return [self.fileLogger respondsToSelector:aSelector]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation { + [invocation invokeWithTarget:self.fileLogger]; +} + +@end + +@implementation DDFileLogger (Buffering) + +- (instancetype)wrapWithBuffer { + return (DDFileLogger *)[[DDBufferedProxy alloc] initWithFileLogger:self]; +} + +- (instancetype)unwrapFromBuffer { + return self; +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m new file mode 100644 index 0000000..aa98bb1 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m @@ -0,0 +1,110 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#if !__has_feature(objc_arc) +#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). +#endif + +#import + +@interface DDMultiFormatter () { + dispatch_queue_t _queue; + NSMutableArray *_formatters; +} + +- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message; + +@end + + +@implementation DDMultiFormatter + +- (instancetype)init { + self = [super init]; + + if (self) { + _queue = dispatch_queue_create("cocoa.lumberjack.multiformatter", DISPATCH_QUEUE_CONCURRENT); + _formatters = [NSMutableArray new]; + } + + return self; +} + +#pragma mark Processing + +- (NSString *)formatLogMessage:(DDLogMessage *)logMessage { + __block __auto_type line = logMessage->_message; + + dispatch_sync(_queue, ^{ + for (id formatter in self->_formatters) { + __auto_type message = [self logMessageForLine:line originalMessage:logMessage]; + line = [formatter formatLogMessage:message]; + + if (!line) { + break; + } + } + }); + + return line; +} + +- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message { + DDLogMessage *newMessage = [message copy]; + newMessage->_message = line; + return newMessage; +} + +#pragma mark Formatters + +- (NSArray *)formatters { + __block NSArray *formatters; + + dispatch_sync(_queue, ^{ + formatters = [self->_formatters copy]; + }); + + return formatters; +} + +- (void)addFormatter:(id)formatter { + dispatch_barrier_async(_queue, ^{ + [self->_formatters addObject:formatter]; + }); +} + +- (void)removeFormatter:(id)formatter { + dispatch_barrier_async(_queue, ^{ + [self->_formatters removeObject:formatter]; + }); +} + +- (void)removeAllFormatters { + dispatch_barrier_async(_queue, ^{ + [self->_formatters removeAllObjects]; + }); +} + +- (BOOL)isFormattingWithFormatter:(id)formatter { + __block BOOL hasFormatter; + + dispatch_sync(_queue, ^{ + hasFormatter = [self->_formatters containsObject:formatter]; + }); + + return hasFormatter; +} + +@end diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..ec783fe --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy @@ -0,0 +1,30 @@ + + + + + NSPrivacyTracking + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + 0A2A.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h new file mode 100644 index 0000000..fad7ade --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h @@ -0,0 +1,104 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * Welcome to CocoaLumberjack! + * + * The project page has a wealth of documentation if you have any questions. + * https://github.com/CocoaLumberjack/CocoaLumberjack + * + * If you're new to the project you may wish to read "Getting Started" at: + * Documentation/GettingStarted.md + * + * Otherwise, here is a quick refresher. + * There are three steps to using the macros: + * + * Step 1: + * Import the header in your implementation or prefix file: + * + * #import + * + * Step 2: + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const DDLogLevel ddLogLevel = DDLogLevelVerbose; + * + * Step 2 [3rd party frameworks]: + * + * Define your LOG_LEVEL_DEF to a different variable/function than ddLogLevel: + * + * // #undef LOG_LEVEL_DEF // Undefine first only if needed + * #define LOG_LEVEL_DEF myLibLogLevel + * + * Define your logging level in your implementation file: + * + * // Log levels: off, error, warn, info, verbose + * static const DDLogLevel myLibLogLevel = DDLogLevelVerbose; + * + * Step 3: + * Replace your NSLog statements with DDLog statements according to the severity of the message. + * + * NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!"); + * + * DDLog works exactly the same as NSLog. + * This means you can pass it multiple variables just like NSLog. + **/ + +#import + +//! Project version number for CocoaLumberjack. +FOUNDATION_EXPORT double CocoaLumberjackVersionNumber; + +//! Project version string for CocoaLumberjack. +FOUNDATION_EXPORT const unsigned char CocoaLumberjackVersionString[]; + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +// Core +#import + +// Main macros +#import +#import + +// Capture ASL +#import + +// Loggers +#import + +#import +#import +#import +#import + +// Extensions +#import +#import +#import +#import +#import + +// CLI +#import + +// etc +#import +#import +#import diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h new file mode 100644 index 0000000..df0d3d9 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h @@ -0,0 +1,75 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * Legacy macros used for 1.9.x backwards compatibility. + * + * Imported by default when importing a DDLog.h directly and DD_LEGACY_MACROS is not defined and set to 0. + **/ +#if DD_LEGACY_MACROS + +#warning CocoaLumberjack 1.9.x legacy macros enabled. \ +Disable legacy macros by importing CocoaLumberjack.h or DDLogMacros.h instead of DDLog.h or add `#define DD_LEGACY_MACROS 0` before importing DDLog.h. + +#ifndef LOG_LEVEL_DEF + #define LOG_LEVEL_DEF ddLogLevel +#endif + +#define LOG_FLAG_ERROR DDLogFlagError +#define LOG_FLAG_WARN DDLogFlagWarning +#define LOG_FLAG_INFO DDLogFlagInfo +#define LOG_FLAG_DEBUG DDLogFlagDebug +#define LOG_FLAG_VERBOSE DDLogFlagVerbose + +#define LOG_LEVEL_OFF DDLogLevelOff +#define LOG_LEVEL_ERROR DDLogLevelError +#define LOG_LEVEL_WARN DDLogLevelWarning +#define LOG_LEVEL_INFO DDLogLevelInfo +#define LOG_LEVEL_DEBUG DDLogLevelDebug +#define LOG_LEVEL_VERBOSE DDLogLevelVerbose +#define LOG_LEVEL_ALL DDLogLevelAll + +#define LOG_ASYNC_ENABLED YES + +#define LOG_ASYNC_ERROR ( NO && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_WARN (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_INFO (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_DEBUG (YES && LOG_ASYNC_ENABLED) +#define LOG_ASYNC_VERBOSE (YES && LOG_ASYNC_ENABLED) + +#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \ + [DDLog log : isAsynchronous \ + level : lvl \ + flag : flg \ + context : ctx \ + file : __FILE__ \ + function : fnct \ + line : __LINE__ \ + tag : atag \ + format : (frmt), ## __VA_ARGS__] + +#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...) \ + do { if((lvl & flg) != 0) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0) + +#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \ + LOG_MAYBE(async, lvl, flg, ctx, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__) + +#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, LOG_LEVEL_DEF, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__) +#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, LOG_LEVEL_DEF, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__) +#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, LOG_LEVEL_DEF, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__) +#define DDLogDebug(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_DEBUG, LOG_LEVEL_DEF, LOG_FLAG_DEBUG, 0, frmt, ##__VA_ARGS__) +#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, LOG_LEVEL_DEF, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__) + +#endif diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h new file mode 100644 index 0000000..cf96e4d --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h @@ -0,0 +1,54 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +#if TARGET_OS_OSX + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class represents an NSColor replacement for CLI projects that don't link with AppKit + **/ +@interface CLIColor : NSObject + +/** + * Convenience method for creating a `CLIColor` instance from RGBA params + * + * @param red red channel, between 0 and 1 + * @param green green channel, between 0 and 1 + * @param blue blue channel, between 0 and 1 + * @param alpha alpha channel, between 0 and 1 + */ ++ (instancetype)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha; + +/** + * Get the RGBA components from a `CLIColor` + * + * @param red red channel, between 0 and 1 + * @param green green channel, between 0 and 1 + * @param blue blue channel, between 0 and 1 + * @param alpha alpha channel, between 0 and 1 + */ +- (void)getRed:(nullable CGFloat *)red green:(nullable CGFloat *)green blue:(nullable CGFloat *)blue alpha:(nullable CGFloat *)alpha NS_SWIFT_NAME(get(red:green:blue:alpha:)); + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h new file mode 100644 index 0000000..5302675 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h @@ -0,0 +1,46 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +@protocol DDLogger; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides the ability to capture the ASL (Apple System Logs) + */ +API_DEPRECATED("Use DDOSLogger instead", macosx(10.4,10.12), ios(2.0,10.0), watchos(2.0,3.0), tvos(9.0,10.0)) +@interface DDASLLogCapture : NSObject + +/** + * Start capturing logs + */ ++ (void)start; + +/** + * Stop capturing logs + */ ++ (void)stop; + +/** + * The current capture level. + * @note Default log level: DDLogLevelVerbose (i.e. capture all ASL messages). + */ +@property (class) DDLogLevel captureLevel; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h new file mode 100644 index 0000000..2ec3394 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h @@ -0,0 +1,63 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +// Custom key set on messages sent to ASL +extern const char* const kDDASLKeyDDLog; + +// Value set for kDDASLKeyDDLog +extern const char* const kDDASLDDLogValue; + +/** + * This class provides a logger for the Apple System Log facility. + * + * As described in the "Getting Started" page, + * the traditional NSLog() function directs its output to two places: + * + * - Apple System Log + * - StdErr (if stderr is a TTY) so log statements show up in Xcode console + * + * To duplicate NSLog() functionality you can simply add this logger and a tty logger. + * However, if you instead choose to use file logging (for faster performance), + * you may choose to use a file logger and a tty logger. + **/ +API_DEPRECATED("Use DDOSLogger instead", macosx(10.4,10.12), ios(2.0,10.0), watchos(2.0,3.0), tvos(9.0,10.0)) +@interface DDASLLogger : DDAbstractLogger + +/** + * Singleton method + * + * @return the shared instance + */ +@property (nonatomic, class, readonly, strong) DDASLLogger *sharedInstance; + +// Inherited from DDAbstractLogger + +// - (id )logFormatter; +// - (void)setLogFormatter:(id )formatter; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h new file mode 100644 index 0000000..60f38c3 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h @@ -0,0 +1,127 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides an abstract implementation of a database logger. + * + * That is, it provides the base implementation for a database logger to build atop of. + * All that is needed for a concrete database logger is to extend this class + * and override the methods in the implementation file that are prefixed with "db_". + **/ +@interface DDAbstractDatabaseLogger : DDAbstractLogger { + +@protected + NSUInteger _saveThreshold; + NSTimeInterval _saveInterval; + NSTimeInterval _maxAge; + NSTimeInterval _deleteInterval; + BOOL _deleteOnEverySave; + + NSInteger _saveTimerSuspended; + NSUInteger _unsavedCount; + dispatch_time_t _unsavedTime; + dispatch_source_t _saveTimer; + dispatch_time_t _lastDeleteTime; + dispatch_source_t _deleteTimer; +} + +/** + * Specifies how often to save the data to disk. + * Since saving is an expensive operation (disk io) it is not done after every log statement. + * These properties allow you to configure how/when the logger saves to disk. + * + * A save is done when either (whichever happens first): + * + * - The number of unsaved log entries reaches saveThreshold + * - The amount of time since the oldest unsaved log entry was created reaches saveInterval + * + * You can optionally disable the saveThreshold by setting it to zero. + * If you disable the saveThreshold you are entirely dependent on the saveInterval. + * + * You can optionally disable the saveInterval by setting it to zero (or a negative value). + * If you disable the saveInterval you are entirely dependent on the saveThreshold. + * + * It's not wise to disable both saveThreshold and saveInterval. + * + * The default saveThreshold is 500. + * The default saveInterval is 60 seconds. + **/ +@property (assign, readwrite) NSUInteger saveThreshold; + +/** + * See the description for the `saveThreshold` property + */ +@property (assign, readwrite) NSTimeInterval saveInterval; + +/** + * It is likely you don't want the log entries to persist forever. + * Doing so would allow the database to grow infinitely large over time. + * + * The maxAge property provides a way to specify how old a log statement can get + * before it should get deleted from the database. + * + * The deleteInterval specifies how often to sweep for old log entries. + * Since deleting is an expensive operation (disk io) is is done on a fixed interval. + * + * An alternative to the deleteInterval is the deleteOnEverySave option. + * This specifies that old log entries should be deleted during every save operation. + * + * You can optionally disable the maxAge by setting it to zero (or a negative value). + * If you disable the maxAge then old log statements are not deleted. + * + * You can optionally disable the deleteInterval by setting it to zero (or a negative value). + * + * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted. + * + * It's not wise to enable both deleteInterval and deleteOnEverySave. + * + * The default maxAge is 7 days. + * The default deleteInterval is 5 minutes. + * The default deleteOnEverySave is NO. + **/ +@property (assign, readwrite) NSTimeInterval maxAge; + +/** + * See the description for the `maxAge` property + */ +@property (assign, readwrite) NSTimeInterval deleteInterval; + +/** + * See the description for the `maxAge` property + */ +@property (assign, readwrite) BOOL deleteOnEverySave; + +/** + * Forces a save of any pending log entries (flushes log entries to disk). + **/ +- (void)savePendingLogEntries; + +/** + * Removes any log entries that are older than maxAge. + **/ +- (void)deleteOldLogEntries; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h new file mode 100644 index 0000000..988b122 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h @@ -0,0 +1,30 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +/** + * NSAssert replacement that will output a log message even when assertions are disabled. + **/ +#define DDAssert(condition, frmt, ...) \ + if (!(condition)) { \ + NSString *description = [NSString stringWithFormat:frmt, ## __VA_ARGS__]; \ + DDLogError(@"%@", description); \ + NSAssert(NO, @"%@", description); \ + } +#define DDAssertCondition(condition) DDAssert(condition, @"Condition not satisfied: %@", @(#condition)) + +/** + * Analog to `DDAssertionFailure` from DDAssert.swift for use in Objective C + */ +#define DDAssertionFailure(frmt, ...) DDAssert(NO, frmt, ##__VA_ARGS__) diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h new file mode 100644 index 0000000..3ef0178 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h @@ -0,0 +1,119 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides a log formatter that filters log statements from a logging context not on the whitelist. + * @deprecated Use DDContextAllowlistFilterLogFormatter instead. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * You can learn more about logging context's here: + * Documentation/CustomContext.md + * + * But here's a quick overview / refresher: + * + * Every log statement has a logging context. + * These come from the underlying logging macros defined in DDLog.h. + * The default logging context is zero. + * You can define multiple logging context's for use in your application. + * For example, logically separate parts of your app each have a different logging context. + * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context. + **/ +__attribute__((deprecated("Use DDContextAllowlistFilterLogFormatter instead"))) +typedef DDContextAllowlistFilterLogFormatter DDContextWhitelistFilterLogFormatter; + +@interface DDContextAllowlistFilterLogFormatter (Deprecated) + +/** + * Add a context to the whitelist + * @deprecated Use -addToAllowlist: instead. + * + * @param loggingContext the context + */ +- (void)addToWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -addToAllowlist: instead"))); + +/** + * Remove context from whitelist + * @deprecated Use -removeFromAllowlist: instead. + * + * @param loggingContext the context + */ +- (void)removeFromWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -removeFromAllowlist: instead"))); + +/** + * Return the whitelist + * @deprecated Use allowlist instead. + */ +@property (nonatomic, readonly, copy) NSArray *whitelist __attribute__((deprecated("Use allowlist instead"))); + +/** + * Check if a context is on the whitelist + * @deprecated Use -isOnAllowlist: instead. + * + * @param loggingContext the context + */ +- (BOOL)isOnWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -isOnAllowlist: instead"))); + +@end + + +/** + * This class provides a log formatter that filters log statements from a logging context on the blacklist. + * @deprecated Use DDContextDenylistFilterLogFormatter instead. + **/ +__attribute__((deprecated("Use DDContextDenylistFilterLogFormatter instead"))) +typedef DDContextDenylistFilterLogFormatter DDContextBlacklistFilterLogFormatter; + +@interface DDContextDenylistFilterLogFormatter (Deprecated) + +/** + * Add a context to the blacklist + * @deprecated Use -addToDenylist: instead. + * + * @param loggingContext the context + */ +- (void)addToBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -addToDenylist: instead"))); + +/** + * Remove context from blacklist + * @deprecated Use -removeFromDenylist: instead. + * + * @param loggingContext the context + */ +- (void)removeFromBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -removeFromDenylist: instead"))); + +/** + * Return the blacklist + * @deprecated Use denylist instead. + */ +@property (readonly, copy) NSArray *blacklist __attribute__((deprecated("Use denylist instead"))); + +/** + * Check if a context is on the blacklist + * @deprecated Use -isOnDenylist: instead. + * + * @param loggingContext the context + */ +- (BOOL)isOnBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -isOnDenylist: instead"))); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h new file mode 100644 index 0000000..9febbcc --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h @@ -0,0 +1,117 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides a log formatter that filters log statements from a logging context not on the allowlist. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * You can learn more about logging context's here: + * Documentation/CustomContext.md + * + * But here's a quick overview / refresher: + * + * Every log statement has a logging context. + * These come from the underlying logging macros defined in DDLog.h. + * The default logging context is zero. + * You can define multiple logging context's for use in your application. + * For example, logically separate parts of your app each have a different logging context. + * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context. + **/ +@interface DDContextAllowlistFilterLogFormatter : NSObject + +/** + * Designated default initializer + */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Add a context to the allowlist + * + * @param loggingContext the context + */ +- (void)addToAllowlist:(NSInteger)loggingContext; + +/** + * Remove context from allowlist + * + * @param loggingContext the context + */ +- (void)removeFromAllowlist:(NSInteger)loggingContext; + +/** + * Return the allowlist + */ +@property (nonatomic, readonly, copy) NSArray *allowlist; + +/** + * Check if a context is on the allowlist + * + * @param loggingContext the context + */ +- (BOOL)isOnAllowlist:(NSInteger)loggingContext; + +@end + + +/** + * This class provides a log formatter that filters log statements from a logging context on the denylist. + **/ +@interface DDContextDenylistFilterLogFormatter : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Add a context to the denylist + * + * @param loggingContext the context + */ +- (void)addToDenylist:(NSInteger)loggingContext; + +/** + * Remove context from denylist + * + * @param loggingContext the context + */ +- (void)removeFromDenylist:(NSInteger)loggingContext; + +/** + * Return the denylist + */ +@property (readonly, copy) NSArray *denylist; + +/** + * Check if a context is on the denylist + * + * @param loggingContext the context + */ +- (BOOL)isOnDenylist:(NSInteger)loggingContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h new file mode 100644 index 0000000..60e8ab4 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h @@ -0,0 +1,223 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Log formatter mode + */ +__attribute__((deprecated("DDDispatchQueueLogFormatter is always shareable"))) +typedef NS_ENUM(NSUInteger, DDDispatchQueueLogFormatterMode){ + /** + * This is the default option, means the formatter can be reused between multiple loggers and therefore is thread-safe. + * There is, of course, a performance cost for the thread-safety + */ + DDDispatchQueueLogFormatterModeShareble = 0, + /** + * If the formatter will only be used by a single logger, then the thread-safety can be removed + * @note: there is an assert checking if the formatter is added to multiple loggers and the mode is non-shareble + */ + DDDispatchQueueLogFormatterModeNonShareble, +}; + +/** + * Quality of Service names. + * + * Since macOS 10.10 and iOS 8.0, pthreads, dispatch queues and NSOperations express their + * scheduling priority by using an abstract classification called Quality of Service (QOS). + * + * This formatter will add a representation of this QOS in the log message by using those + * string constants. + * For example: + * + * `2011-10-17 20:21:45.435 AppName[19928:5207 (QOS:DF)] Your log message here` + * + * Where QOS is one of: + * `- UI = User Interactive` + * `- IN = User Initiated` + * `- DF = Default` + * `- UT = Utility` + * `- BG = Background` + * `- UN = Unspecified` + * + * Note: QOS will be absent in the log messages if running on OS versions that don't support it. + **/ +typedef NSString * DDQualityOfServiceName NS_STRING_ENUM; + +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUserInteractive NS_SWIFT_NAME(DDQualityOfServiceName.userInteractive) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUserInitiated NS_SWIFT_NAME(DDQualityOfServiceName.userInitiated) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceDefault NS_SWIFT_NAME(DDQualityOfServiceName.default) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUtility NS_SWIFT_NAME(DDQualityOfServiceName.utility) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceBackground NS_SWIFT_NAME(DDQualityOfServiceName.background) API_AVAILABLE(macos(10.10), ios(8.0)); +FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUnspecified NS_SWIFT_NAME(DDQualityOfServiceName.unspecified) API_AVAILABLE(macos(10.10), ios(8.0)); + +/** + * This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id. + * + * A log formatter can be added to any logger to format and/or filter its output. + * You can learn more about log formatters here: + * Documentation/CustomFormatters.md + * + * A typical `NSLog` (or `DDTTYLogger`) prints detailed info as `[:]`. + * For example: + * + * `2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here` + * + * Where: + * `- 19928 = process id` + * `- 5207 = thread id (mach_thread_id printed in hex)` + * + * When using grand central dispatch (GCD), this information is less useful. + * This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool. + * For example: + * + * `2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue` + * + * This formatter allows you to replace the standard `[box:info]` with the dispatch_queue name. + * For example: + * + * `2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue` + * `2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue` + * + * If the dispatch_queue doesn't have a set name, then it falls back to the thread name. + * If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal). + * + * Note: If manually creating your own background threads (via `NSThread/alloc/init` or `NSThread/detachNeThread`), + * you can use `[[NSThread currentThread] setName:(NSString *)]`. + **/ +@interface DDDispatchQueueLogFormatter : NSObject + +/** + * Standard init method. + * Configure using properties as desired. + **/ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Initializer with ability to set the queue mode + * + * @param mode choose between DDDispatchQueueLogFormatterModeShareble and DDDispatchQueueLogFormatterModeNonShareble, depending if the formatter is shared between several loggers or not + */ +- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode __attribute__((deprecated("DDDispatchQueueLogFormatter is always shareable"))); + +/** + * The minQueueLength restricts the minimum size of the [detail box]. + * If the minQueueLength is set to 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the minQueueLength is 0: [diskIO] + * If the minQueueLength is 4: [diskIO] + * If the minQueueLength is 5: [diskIO] + * If the minQueueLength is 6: [diskIO] + * If the minQueueLength is 7: [diskIO ] + * If the minQueueLength is 8: [diskIO ] + * + * The default minQueueLength is 0 (no minimum, so [detail box] won't be padded). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. + **/ +@property (assign, atomic) NSUInteger minQueueLength; + +/** + * The maxQueueLength restricts the number of characters that will be inside the [detail box]. + * If the maxQueueLength is 0, there is no restriction. + * + * For example, say a dispatch_queue has a label of "diskIO": + * + * If the maxQueueLength is 0: [diskIO] + * If the maxQueueLength is 4: [disk] + * If the maxQueueLength is 5: [diskI] + * If the maxQueueLength is 6: [diskIO] + * If the maxQueueLength is 7: [diskIO] + * If the maxQueueLength is 8: [diskIO] + * + * The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated). + * + * If you want every [detail box] to have the exact same width, + * set both minQueueLength and maxQueueLength to the same value. + **/ +@property (assign, atomic) NSUInteger maxQueueLength; + +/** + * Sometimes queue labels have long names like "com.apple.main-queue", + * but you'd prefer something shorter like simply "main". + * + * This method allows you to set such preferred replacements. + * The above example is set by default. + * + * To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter. + **/ +- (nullable NSString *)replacementStringForQueueLabel:(NSString *)longLabel; + +/** + * See the `replacementStringForQueueLabel:` description + */ +- (void)setReplacementString:(nullable NSString *)shortLabel forQueueLabel:(NSString *)longLabel; + +@end + +/** + * Category on `DDDispatchQueueLogFormatter` to make method declarations easier to extend/modify + **/ +@interface DDDispatchQueueLogFormatter (OverridableMethods) + +/** + * Date formatter default configuration + */ +- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter; + +/** + * Formatter method to transfrom from date to string + */ +- (NSString *)stringFromDate:(NSDate *)date; + +/** + * Method to compute the queue thread label + */ +- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage; + +@end + +#pragma mark - DDAtomicCountable + +__attribute__((deprecated("DDAtomicCountable is useless since DDDispatchQueueLogFormatter is always shareable now"))) +@protocol DDAtomicCountable + +- (instancetype)initWithDefaultValue:(int32_t)defaultValue; +- (int32_t)increment; +- (int32_t)decrement; +- (int32_t)value; + +@end + +__attribute__((deprecated("DDAtomicCountable is deprecated"))) +@interface DDAtomicCounter: NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h new file mode 100644 index 0000000..2b3a40c --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h @@ -0,0 +1,27 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DDFileLogger (Buffering) + +- (instancetype)wrapWithBuffer; +- (instancetype)unwrapFromBuffer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h new file mode 100644 index 0000000..97ac446 --- /dev/null +++ b/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h @@ -0,0 +1,590 @@ +// Software License Agreement (BSD License) +// +// Copyright (c) 2010-2026, Deusty, LLC +// All rights reserved. +// +// Redistribution and use of this software in source and binary forms, +// with or without modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Neither the name of Deusty nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific +// prior written permission of Deusty, LLC. + +// Disable legacy macros +#ifndef DD_LEGACY_MACROS + #define DD_LEGACY_MACROS 0 +#endif + +#import + +@class DDLogFileInfo; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This class provides a logger to write log statements to a file. + **/ + + +// Default configuration and safety/sanity values. +// +// maximumFileSize -> kDDDefaultLogMaxFileSize +// rollingFrequency -> kDDDefaultLogRollingFrequency +// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles +// logFilesDiskQuota -> kDDDefaultLogFilesDiskQuota +// +// You should carefully consider the proper configuration values for your application. + +extern unsigned long long const kDDDefaultLogMaxFileSize; +extern NSTimeInterval const kDDDefaultLogRollingFrequency; +extern NSUInteger const kDDDefaultLogMaxNumLogFiles; +extern unsigned long long const kDDDefaultLogFilesDiskQuota; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +/// The serializer is responsible for turning a log message into binary for writing into a file. +/// It allows storing log messages in a non-text format. +/// The serialier should not be used for filtering or formatting messages! +/// Also, it must be fast! +@protocol DDFileLogMessageSerializer +@required + +/// Returns the binary representation of the message. +/// - Parameter message: The formatted log message to serialize. +// + +/// Returns the binary representation of the message. +/// - Parameters: +/// - string: The string to serialize. Usually, this is the formatted message, but it can also be e.g. a log file header. +/// - message: The message which represents the `string`. This is null, if `string` is e.g. a log file header. +/// - Note: The `message` parameter should not be used for formatting! It should simply be used to extract the necessary metadata for serializing. +- (NSData *)dataForString:(NSString *)string + originatingFromMessage:(nullable DDLogMessage *)message NS_SWIFT_NAME(dataForString(_:originatingFrom:)); + +@end + +/// The (default) plain text message serializer. +@interface DDFileLogPlainTextMessageSerializer : NSObject + +- (instancetype)init; + +@end + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +@class DDFileLogger; +/** + * The LogFileManager protocol is designed to allow you to control all aspects of your log files. + * + * The primary purpose of this is to allow you to do something with the log files after they have been rolled. + * Perhaps you want to compress them to save disk space. + * Perhaps you want to upload them to an FTP server. + * Perhaps you want to run some analytics on the file. + * + * A default LogFileManager is, of course, provided. + * The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property. + * + * This protocol provides various methods to fetch the list of log files. + * + * There are two variants: sorted and unsorted. + * If sorting is not necessary, the unsorted variant is obviously faster. + * The sorted variant will return an array sorted by when the log files were created, + * with the most recently created log file at index 0, and the oldest log file at the end of the array. + * + * You can fetch only the log file paths (full path including name), log file names (name only), + * or an array of `DDLogFileInfo` objects. + * The `DDLogFileInfo` class is documented below, and provides a handy wrapper that + * gives you easy access to various file attributes such as the creation date or the file size. + */ +@protocol DDLogFileManager +@required + +// Public properties + +/** + * The maximum number of archived log files to keep on disk. + * For example, if this property is set to 3, + * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk. + * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted. + * + * You may optionally disable this option by setting it to zero. + **/ +@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles; + +/** + * The maximum space that logs can take. On rolling logfile all old log files that exceed logFilesDiskQuota will + * be deleted. + * + * You may optionally disable this option by setting it to zero. + **/ +@property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota; + +// Public methods + +/** + * Returns the logs directory (path) + */ +@property (nonatomic, readonly, copy) NSString *logsDirectory; + +/** + * Returns an array of `NSString` objects, + * each of which is the filePath to an existing log file on disk. + **/ +@property (nonatomic, readonly, strong) NSArray *unsortedLogFilePaths; + +/** + * Returns an array of `NSString` objects, + * each of which is the fileName of an existing log file on disk. + **/ +@property (nonatomic, readonly, strong) NSArray *unsortedLogFileNames; + +/** + * Returns an array of `DDLogFileInfo` objects, + * each representing an existing log file on disk, + * and containing important information about the log file such as it's modification date and size. + **/ +@property (nonatomic, readonly, strong) NSArray *unsortedLogFileInfos; + +/** + * Just like the `unsortedLogFilePaths` method, but sorts the array. + * The items in the array are sorted by creation date. + * The first item in the array will be the most recently created log file. + **/ +@property (nonatomic, readonly, strong) NSArray *sortedLogFilePaths; + +/** + * Just like the `unsortedLogFileNames` method, but sorts the array. + * The items in the array are sorted by creation date. + * The first item in the array will be the most recently created log file. + **/ +@property (nonatomic, readonly, strong) NSArray *sortedLogFileNames; + +/** + * Just like the `unsortedLogFileInfos` method, but sorts the array. + * The items in the array are sorted by creation date. + * The first item in the array will be the most recently created log file. + **/ +@property (nonatomic, readonly, strong) NSArray *sortedLogFileInfos; + +// Private methods (only to be used by DDFileLogger) + +/** + * Generates a new unique log file path, and creates the corresponding log file. + * This method is executed directly on the file logger's internal queue. + * The file has to exist by the time the method returns. + **/ +- (nullable NSString *)createNewLogFileWithError:(NSError **)error; + +@optional + +/// The log message serializer. +@property (nonatomic, readonly, strong) id logMessageSerializer; + +/// The file manager to use. Defaults to `[NSFileManager defaultManager]`. +@property (nonatomic, readonly, strong) NSFileManager *fileManager; + +/// Whether the log file should be locked by the file logger before writing to it (and unlocked after). +/// - Parameter logFilePath: The path to the log file for which to decide locking. +/// - Remark: Logging from multiple processes (e.g. an app extensions) to the same log file without file locking will result in interleaved and possibly even overwritten log messages. +/// Without locking, the resulting logfile could be described as "best effort" and might become corrupted. +/// The downside of locking is that if the process holds the file lock when it gets suspended by the system, the system will kill the process. +/// This could happen by inproper handling of app suspension (e.g. not properly calling `+[DDLog flushLog]` and waiting for its completion before suspension). +/// Regardless of locking, you should always call `+[DDLog flushLog]` before your app gets suspended or terminated to make sure every log message makes it to your disk. +- (BOOL)shouldLockLogFile:(NSString *)logFilePath; + +/// Manually perform a cleanup of the log files managed by this manager. +/// This can be called from any queue! +- (BOOL)cleanupLogFilesWithError:(NSError **)error; + +// MARK: Private methods (only to be used by DDFileLogger) + +// MARK: Notifications from DDFileLogger +/// Called when the log file manager was added to a file logger. +/// This should be used to make the manager "active" - like starting internal timers etc. +/// Executed on global queue with default priority. +/// - Parameter fileLogger: The file logger this manager was added to. +/// - Important: The manager **must not** keep a strong reference to `fileLogger` or a retain cycle will be created! +- (void)didAddToFileLogger:(DDFileLogger *)fileLogger; + +/// Called when a log file was archived. Executed on global queue with default priority. +/// @param logFilePath The path to the log file that was archived. +/// @param wasRolled Whether or not the archiving happend after rolling the log file. +- (void)didArchiveLogFile:(NSString *)logFilePath wasRolled:(BOOL)wasRolled NS_SWIFT_NAME(didArchiveLogFile(atPath:wasRolled:)); + +// MARK: Deprecated APIs +/// Creates a new log file ignoring any errors. Deprecated in favor of `-createNewLogFileWithError:`. +/// Will only be called if `-createNewLogFileWithError:` is not implemented. +- (nullable NSString *)createNewLogFile __attribute__((deprecated("Use -createNewLogFileWithError:"))) NS_SWIFT_UNAVAILABLE("Use -createNewLogFileWithError:"); + +/// Called when a log file was archived. Executed on global queue with default priority. +- (void)didArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didArchiveLogFile(atPath:)) __attribute__((deprecated("Use -didArchiveLogFile:wasRolled:"))); + +/// Called when the roll action was executed and the log was archived. Executed on global queue with default priority. +- (void)didRollAndArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didRollAndArchiveLogFile(atPath:)) __attribute__((deprecated("Use -didArchiveLogFile:wasRolled:"))); + +@end + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Default log file manager. + * + * All log files are placed inside the logsDirectory. + * If a specific logsDirectory isn't specified, the default directory is used. + * On Mac, this is in `~/Library/Logs/`. + * On iPhone, this is in `~/Library/Caches/Logs`. + * + * Log files are named `"