commit ca06b501d8ebf772a233ce82de9c4a131114fd37 Author: pengfei.zhou Date: Wed Dec 4 13:29:26 2019 +0800 seperate iOS project from main project diff --git a/Doric.podspec b/Doric.podspec new file mode 100644 index 00000000..8eb2cf33 --- /dev/null +++ b/Doric.podspec @@ -0,0 +1,48 @@ +# +# Be sure to run `pod lib lint Doric.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'Doric' + s.version = '0.1.0' + s.summary = 'A short description of Doric.' + +# This description is used to generate tags and improve search results. +# * Think: What does it do? Why did you write it? What is the focus? +# * Try to keep it short, snappy and to the point. +# * Write the description between the DESC delimiters below. +# * Finally, don't worry about the indent, CocoaPods strips it! + + s.description = <<-DESC +TODO: Add long description of the pod here. + DESC + + s.homepage = 'https://github.com/doric-pub/doric' + # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' + s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } + s.author = { 'pengfei.zhou' => 'pengfeizhou@foxmail.com' } + s.source = { :git => 'git@github.com:penfeizhou/doric.git', :tag => s.version.to_s } + # s.social_media_url = 'https://twitter.com/' + + s.ios.deployment_target = '8.0' + + s.source_files = 'Pod/Classes/**/*' + s.resource = "Pod/Assets/*.js" + s.resource_bundles = { + 'Doric' => ['Pod/Assets/**/*'] + } + + s.public_header_files = 'Pod/Classes/**/*.h' + # s.frameworks = 'UIKit', 'MapKit' + # s.dependency 'AFNetworking', '~> 2.3' + # s.dependency 'SDWebImage', '~> 5.0' + s.dependency 'YYWebImage', '~>1.0.5' + s.dependency 'YYImage/WebP' + s.dependency 'SocketRocket', '~> 0.5.1' + s.dependency 'GCDWebServer', '~> 3.0' + s.dependency 'YYCache', '~> 1.0.4' +end diff --git a/Example/.gitignore b/Example/.gitignore new file mode 100644 index 00000000..62c89355 --- /dev/null +++ b/Example/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj new file mode 100644 index 00000000..6edeba9e --- /dev/null +++ b/Example/Example.xcodeproj/project.pbxproj @@ -0,0 +1,856 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 1B5560FDC1A57040A84A35AB /* libPods-ExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 30CF3CCBAF1F8AD594763E4A /* libPods-ExampleTests.a */; }; + 2EB14575543B0285648B858D /* libPods-Example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C3CFA4394FF1344386170022 /* libPods-Example.a */; }; + 835F4CD080BABFE95A6BFD16 /* libPods-ExampleUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 563C5AC823878F4913BBCDF2 /* libPods-ExampleUITests.a */; }; + D751D4B065D8D4FA6594B5EE /* DemoVC.m in Sources */ = {isa = PBXBuildFile; fileRef = D751D19E97EF4EDD7588FEBE /* DemoVC.m */; }; + D751D4FCC0A2322211DE3D55 /* QRScanViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D751DA399F1ADB6D34563B5D /* QRScanViewController.m */; }; + E2334AF022E9D2060098A085 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334AEF22E9D2060098A085 /* AppDelegate.m */; }; + E2334AF322E9D2060098A085 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334AF222E9D2060098A085 /* ViewController.m */; }; + E2334AF622E9D2060098A085 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E2334AF422E9D2060098A085 /* Main.storyboard */; }; + E2334AF822E9D2070098A085 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2334AF722E9D2070098A085 /* Assets.xcassets */; }; + E2334AFB22E9D2070098A085 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E2334AF922E9D2070098A085 /* LaunchScreen.storyboard */; }; + E2334AFE22E9D2070098A085 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334AFD22E9D2070098A085 /* main.m */; }; + E2334B0822E9D2070098A085 /* ExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334B0722E9D2070098A085 /* ExampleTests.m */; }; + E2334B1322E9D2070098A085 /* ExampleUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = E2334B1222E9D2070098A085 /* ExampleUITests.m */; }; + E2F4481723839AC500073C7F /* demo in Resources */ = {isa = PBXBuildFile; fileRef = E2F4481623839AC500073C7F /* demo */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + E2334B0422E9D2070098A085 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E2334AE322E9D2060098A085 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E2334AEA22E9D2060098A085; + remoteInfo = Example; + }; + E2334B0F22E9D2070098A085 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E2334AE322E9D2060098A085 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E2334AEA22E9D2060098A085; + remoteInfo = Example; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 016E930415B91D826F9FFF47 /* Pods-ExampleUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.debug.xcconfig"; sourceTree = ""; }; + 30CF3CCBAF1F8AD594763E4A /* libPods-ExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3D75F592D76A665674B31A66 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; + 563C5AC823878F4913BBCDF2 /* libPods-ExampleUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ExampleUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8231E841CCAF382F85C9F576 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; + B93423722F2E06DC238CDD18 /* Pods-ExampleUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.release.xcconfig"; sourceTree = ""; }; + B93D4DB00FD244178B7CE7C4 /* Pods-ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.release.xcconfig"; sourceTree = ""; }; + C3CFA4394FF1344386170022 /* libPods-Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D751D18AD6496F4A9BE1AB45 /* QRScanViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QRScanViewController.h; sourceTree = ""; }; + D751D19E97EF4EDD7588FEBE /* DemoVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoVC.m; sourceTree = ""; }; + D751DA399F1ADB6D34563B5D /* QRScanViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QRScanViewController.m; sourceTree = ""; }; + D751DDEC114E037231257E64 /* DemoVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoVC.h; sourceTree = ""; }; + D91241144B5A3356A3C60644 /* Pods-ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.debug.xcconfig"; sourceTree = ""; }; + E2334AEB22E9D2060098A085 /* Doric Playground.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Doric Playground.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + E2334AEE22E9D2060098A085 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + E2334AEF22E9D2060098A085 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + E2334AF122E9D2060098A085 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + E2334AF222E9D2060098A085 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + E2334AF522E9D2060098A085 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + E2334AF722E9D2070098A085 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + E2334AFA22E9D2070098A085 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + E2334AFC22E9D2070098A085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E2334AFD22E9D2070098A085 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + E2334B0322E9D2070098A085 /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E2334B0722E9D2070098A085 /* ExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleTests.m; sourceTree = ""; }; + E2334B0922E9D2070098A085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E2334B0E22E9D2070098A085 /* ExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E2334B1222E9D2070098A085 /* ExampleUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleUITests.m; sourceTree = ""; }; + E2334B1422E9D2070098A085 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E2F4481623839AC500073C7F /* demo */ = {isa = PBXFileReference; lastKnownFileType = folder; path = demo; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E2334AE822E9D2060098A085 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2EB14575543B0285648B858D /* libPods-Example.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E2334B0022E9D2070098A085 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1B5560FDC1A57040A84A35AB /* libPods-ExampleTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E2334B0B22E9D2070098A085 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 835F4CD080BABFE95A6BFD16 /* libPods-ExampleUITests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6CBE6FA5F47A90E57AB456B2 /* Pods */ = { + isa = PBXGroup; + children = ( + 8231E841CCAF382F85C9F576 /* Pods-Example.debug.xcconfig */, + 3D75F592D76A665674B31A66 /* Pods-Example.release.xcconfig */, + D91241144B5A3356A3C60644 /* Pods-ExampleTests.debug.xcconfig */, + B93D4DB00FD244178B7CE7C4 /* Pods-ExampleTests.release.xcconfig */, + 016E930415B91D826F9FFF47 /* Pods-ExampleUITests.debug.xcconfig */, + B93423722F2E06DC238CDD18 /* Pods-ExampleUITests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + D80A9B07B39AD04027CAE17B /* Frameworks */ = { + isa = PBXGroup; + children = ( + C3CFA4394FF1344386170022 /* libPods-Example.a */, + 30CF3CCBAF1F8AD594763E4A /* libPods-ExampleTests.a */, + 563C5AC823878F4913BBCDF2 /* libPods-ExampleUITests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + E2334AE222E9D2060098A085 = { + isa = PBXGroup; + children = ( + E2334AED22E9D2060098A085 /* Example */, + E2334B0622E9D2070098A085 /* ExampleTests */, + E2334B1122E9D2070098A085 /* ExampleUITests */, + E2334AEC22E9D2060098A085 /* Products */, + 6CBE6FA5F47A90E57AB456B2 /* Pods */, + D80A9B07B39AD04027CAE17B /* Frameworks */, + ); + sourceTree = ""; + }; + E2334AEC22E9D2060098A085 /* Products */ = { + isa = PBXGroup; + children = ( + E2334AEB22E9D2060098A085 /* Doric Playground.app */, + E2334B0322E9D2070098A085 /* ExampleTests.xctest */, + E2334B0E22E9D2070098A085 /* ExampleUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + E2334AED22E9D2060098A085 /* Example */ = { + isa = PBXGroup; + children = ( + E2F4481623839AC500073C7F /* demo */, + E2334AEE22E9D2060098A085 /* AppDelegate.h */, + E2334AEF22E9D2060098A085 /* AppDelegate.m */, + E2334AF122E9D2060098A085 /* ViewController.h */, + E2334AF222E9D2060098A085 /* ViewController.m */, + E2334AF422E9D2060098A085 /* Main.storyboard */, + E2334AF722E9D2070098A085 /* Assets.xcassets */, + E2334AFC22E9D2070098A085 /* Info.plist */, + E2334AF922E9D2070098A085 /* LaunchScreen.storyboard */, + E2334AFD22E9D2070098A085 /* main.m */, + D751D19E97EF4EDD7588FEBE /* DemoVC.m */, + D751DDEC114E037231257E64 /* DemoVC.h */, + D751DA399F1ADB6D34563B5D /* QRScanViewController.m */, + D751D18AD6496F4A9BE1AB45 /* QRScanViewController.h */, + ); + path = Example; + sourceTree = ""; + }; + E2334B0622E9D2070098A085 /* ExampleTests */ = { + isa = PBXGroup; + children = ( + E2334B0722E9D2070098A085 /* ExampleTests.m */, + E2334B0922E9D2070098A085 /* Info.plist */, + ); + path = ExampleTests; + sourceTree = ""; + }; + E2334B1122E9D2070098A085 /* ExampleUITests */ = { + isa = PBXGroup; + children = ( + E2334B1222E9D2070098A085 /* ExampleUITests.m */, + E2334B1422E9D2070098A085 /* Info.plist */, + ); + path = ExampleUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E2334AEA22E9D2060098A085 /* Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = E2334B1722E9D2070098A085 /* Build configuration list for PBXNativeTarget "Example" */; + buildPhases = ( + BE34CD8291B20D26F2ADE3E1 /* [CP] Check Pods Manifest.lock */, + E24A030C22EED0D500AB4631 /* Package JS Bundle */, + E2334AE722E9D2060098A085 /* Sources */, + E2334AE822E9D2060098A085 /* Frameworks */, + E2334AE922E9D2060098A085 /* Resources */, + 2719DACF05C7A7C1EB4AD553 /* [CP] Embed Pods Frameworks */, + C232600D894D85358A6ABF4D /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Example; + productName = Example; + productReference = E2334AEB22E9D2060098A085 /* Doric Playground.app */; + productType = "com.apple.product-type.application"; + }; + E2334B0222E9D2070098A085 /* ExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E2334B1A22E9D2070098A085 /* Build configuration list for PBXNativeTarget "ExampleTests" */; + buildPhases = ( + 48D050F720D3A879060292A8 /* [CP] Check Pods Manifest.lock */, + E2334AFF22E9D2070098A085 /* Sources */, + E2334B0022E9D2070098A085 /* Frameworks */, + E2334B0122E9D2070098A085 /* Resources */, + 68729F70B03CECDBAD2022E4 /* [CP] Embed Pods Frameworks */, + D5C1C722575685F99C382E01 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + E2334B0522E9D2070098A085 /* PBXTargetDependency */, + ); + name = ExampleTests; + productName = ExampleTests; + productReference = E2334B0322E9D2070098A085 /* ExampleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + E2334B0D22E9D2070098A085 /* ExampleUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E2334B1D22E9D2070098A085 /* Build configuration list for PBXNativeTarget "ExampleUITests" */; + buildPhases = ( + 074533B9C1C5204E8750B07B /* [CP] Check Pods Manifest.lock */, + E2334B0A22E9D2070098A085 /* Sources */, + E2334B0B22E9D2070098A085 /* Frameworks */, + E2334B0C22E9D2070098A085 /* Resources */, + AC45D4A1B85D5D5A2F6E3DBD /* [CP] Embed Pods Frameworks */, + F0AA1A5ADB99F5ECA1F9331E /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + E2334B1022E9D2070098A085 /* PBXTargetDependency */, + ); + name = ExampleUITests; + productName = ExampleUITests; + productReference = E2334B0E22E9D2070098A085 /* ExampleUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E2334AE322E9D2060098A085 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = pengfei.zhou; + TargetAttributes = { + E2334AEA22E9D2060098A085 = { + CreatedOnToolsVersion = 10.3; + }; + E2334B0222E9D2070098A085 = { + CreatedOnToolsVersion = 10.3; + TestTargetID = E2334AEA22E9D2060098A085; + }; + E2334B0D22E9D2070098A085 = { + CreatedOnToolsVersion = 10.3; + TestTargetID = E2334AEA22E9D2060098A085; + }; + }; + }; + buildConfigurationList = E2334AE622E9D2060098A085 /* Build configuration list for PBXProject "Example" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E2334AE222E9D2060098A085; + productRefGroup = E2334AEC22E9D2060098A085 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E2334AEA22E9D2060098A085 /* Example */, + E2334B0222E9D2070098A085 /* ExampleTests */, + E2334B0D22E9D2070098A085 /* ExampleUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E2334AE922E9D2060098A085 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E2334AFB22E9D2070098A085 /* LaunchScreen.storyboard in Resources */, + E2F4481723839AC500073C7F /* demo in Resources */, + E2334AF822E9D2070098A085 /* Assets.xcassets in Resources */, + E2334AF622E9D2060098A085 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E2334B0122E9D2070098A085 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E2334B0C22E9D2070098A085 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 074533B9C1C5204E8750B07B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ExampleUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2719DACF05C7A7C1EB4AD553 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 48D050F720D3A879060292A8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ExampleTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 68729F70B03CECDBAD2022E4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + AC45D4A1B85D5D5A2F6E3DBD /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + BE34CD8291B20D26F2ADE3E1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C232600D894D85358A6ABF4D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/Doric/Doric.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Doric.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D5C1C722575685F99C382E01 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + E24A030C22EED0D500AB4631 /* Package JS Bundle */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Package JS Bundle"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\nexport NVM_DIR=\"$HOME/.nvm\"\n[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\" # This loads nvm\n[ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\" # This loads nvm bash_completion\n\ncd ../../js-framework && npm run build\ncd ../demo && npm run build\n"; + }; + F0AA1A5ADB99F5ECA1F9331E /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E2334AE722E9D2060098A085 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E2334AF322E9D2060098A085 /* ViewController.m in Sources */, + E2334AFE22E9D2070098A085 /* main.m in Sources */, + E2334AF022E9D2060098A085 /* AppDelegate.m in Sources */, + D751D4B065D8D4FA6594B5EE /* DemoVC.m in Sources */, + D751D4FCC0A2322211DE3D55 /* QRScanViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E2334AFF22E9D2070098A085 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E2334B0822E9D2070098A085 /* ExampleTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E2334B0A22E9D2070098A085 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E2334B1322E9D2070098A085 /* ExampleUITests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + E2334B0522E9D2070098A085 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E2334AEA22E9D2060098A085 /* Example */; + targetProxy = E2334B0422E9D2070098A085 /* PBXContainerItemProxy */; + }; + E2334B1022E9D2070098A085 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E2334AEA22E9D2060098A085 /* Example */; + targetProxy = E2334B0F22E9D2070098A085 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + E2334AF422E9D2060098A085 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E2334AF522E9D2060098A085 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + E2334AF922E9D2070098A085 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E2334AFA22E9D2070098A085 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E2334B1522E9D2070098A085 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + E2334B1622E9D2070098A085 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + E2334B1822E9D2070098A085 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8231E841CCAF382F85C9F576 /* Pods-Example.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 7EE2RX3L3P; + INFOPLIST_FILE = Example/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = pub.doric.Example; + PRODUCT_NAME = "Doric Playground"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E2334B1922E9D2070098A085 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3D75F592D76A665674B31A66 /* Pods-Example.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 7EE2RX3L3P; + INFOPLIST_FILE = Example/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = pub.doric.Example; + PRODUCT_NAME = "Doric Playground"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + E2334B1B22E9D2070098A085 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D91241144B5A3356A3C60644 /* Pods-ExampleTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = pub.doric.ExampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; + }; + name = Debug; + }; + E2334B1C22E9D2070098A085 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B93D4DB00FD244178B7CE7C4 /* Pods-ExampleTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = pub.doric.ExampleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; + }; + name = Release; + }; + E2334B1E22E9D2070098A085 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 016E930415B91D826F9FFF47 /* Pods-ExampleUITests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = pub.doric.ExampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Example; + }; + name = Debug; + }; + E2334B1F22E9D2070098A085 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B93423722F2E06DC238CDD18 /* Pods-ExampleUITests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = ExampleUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = pub.doric.ExampleUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = Example; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E2334AE622E9D2060098A085 /* Build configuration list for PBXProject "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E2334B1522E9D2070098A085 /* Debug */, + E2334B1622E9D2070098A085 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E2334B1722E9D2070098A085 /* Build configuration list for PBXNativeTarget "Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E2334B1822E9D2070098A085 /* Debug */, + E2334B1922E9D2070098A085 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E2334B1A22E9D2070098A085 /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E2334B1B22E9D2070098A085 /* Debug */, + E2334B1C22E9D2070098A085 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E2334B1D22E9D2070098A085 /* Build configuration list for PBXNativeTarget "ExampleUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E2334B1E22E9D2070098A085 /* Debug */, + E2334B1F22E9D2070098A085 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E2334AE322E9D2060098A085 /* Project object */; +} diff --git a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..6d2a51bb --- /dev/null +++ b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme new file mode 100644 index 00000000..fb23330f --- /dev/null +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/Example.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..ee3458dd --- /dev/null +++ b/Example/Example.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Example/Example.xcworkspace/contents.xcworkspacedata b/Example/Example.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..a37cf193 --- /dev/null +++ b/Example/Example.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/UserInterfaceState.xcuserstate b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..259eaeb6 Binary files /dev/null and b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..f3a23345 --- /dev/null +++ b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcdebugger/Expressions.xcexplist b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcdebugger/Expressions.xcexplist new file mode 100644 index 00000000..5b3a9234 --- /dev/null +++ b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcdebugger/Expressions.xcexplist @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..ee3458dd --- /dev/null +++ b/Example/Example.xcworkspace/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/Example/Example/AppDelegate.h b/Example/Example/AppDelegate.h new file mode 100644 index 00000000..2e2a4c08 --- /dev/null +++ b/Example/Example/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// Example +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 pengfei.zhou. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/Example/Example/AppDelegate.m b/Example/Example/AppDelegate.m new file mode 100644 index 00000000..28002f7c --- /dev/null +++ b/Example/Example/AppDelegate.m @@ -0,0 +1,61 @@ +// +// AppDelegate.m +// Example +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 pengfei.zhou. All rights reserved. +// + +#import "AppDelegate.h" +#import "ViewController.h" + +@interface AppDelegate () +@property(nonatomic, strong) UIViewController *rootVC; +@property(nonatomic, strong) UINavigationController *navigationController; +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.rootVC = [[ViewController alloc] init]; + + self.window.rootViewController = self.rootVC; + self.navigationController = [[UINavigationController + alloc] initWithRootViewController:self.rootVC]; + [self.window addSubview:self.navigationController.view]; + [self.window makeKeyAndVisible]; + return YES; +} + + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. +} + + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. +} + + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + + +@end diff --git a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Example/Assets.xcassets/Contents.json b/Example/Example/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Example/Example/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Example/Base.lproj/LaunchScreen.storyboard b/Example/Example/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..bfa36129 --- /dev/null +++ b/Example/Example/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard new file mode 100644 index 00000000..942f0bc4 --- /dev/null +++ b/Example/Example/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/DemoVC.h b/Example/Example/DemoVC.h new file mode 100644 index 00000000..ea808936 --- /dev/null +++ b/Example/Example/DemoVC.h @@ -0,0 +1,12 @@ +// +// Created by pengfei.zhou on 2019/11/19. +// Copyright (c) 2019 pengfei.zhou. All rights reserved. +// + +#import +#import + + +@interface DemoVC : UIViewController +- (instancetype)initWithPath:(NSString *)filePath; +@end \ No newline at end of file diff --git a/Example/Example/DemoVC.m b/Example/Example/DemoVC.m new file mode 100644 index 00000000..02f7f354 --- /dev/null +++ b/Example/Example/DemoVC.m @@ -0,0 +1,39 @@ +// +// Created by pengfei.zhou on 2019/11/19. +// Copyright (c) 2019 pengfei.zhou. All rights reserved. +// + +#import "DemoVC.h" +#import "Doric.h" + +@interface DemoVC () +@property(nonatomic, copy) NSString *filePath; +@end + +@implementation DemoVC +- (instancetype)initWithPath:(NSString *)filePath { + if (self = [self init]) { + _filePath = filePath; + } + return self; +} + +- (void)viewDidLoad { + self.title = self.filePath; + self.view.backgroundColor = [UIColor whiteColor]; + NSString *path = [[NSBundle mainBundle] bundlePath]; + NSString *demoPath = [path stringByAppendingPathComponent:@"demo"]; + NSString *fullPath = [demoPath stringByAppendingPathComponent:self.filePath]; + NSString *jsContent = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:nil]; + DoricPanel *panel = [DoricPanel new]; + [panel.view also:^(UIView *it) { + it.width = self.view.width; + it.height = self.view.height - 88; + it.top = 88; + [self.view addSubview:it]; + }]; + [self addChildViewController:panel]; + [panel config:jsContent alias:self.filePath]; +} + +@end \ No newline at end of file diff --git a/Example/Example/Info.plist b/Example/Example/Info.plist new file mode 100644 index 00000000..dc0608e9 --- /dev/null +++ b/Example/Example/Info.plist @@ -0,0 +1,54 @@ + + + + + NSCameraUsageDescription + Scan QR Code + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + LSApplicationCategoryType + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Example/Example/QRScanViewController.h b/Example/Example/QRScanViewController.h new file mode 100644 index 00000000..05ffa4e6 --- /dev/null +++ b/Example/Example/QRScanViewController.h @@ -0,0 +1,10 @@ +// +// Created by pengfei.zhou on 2019/11/21. +// Copyright (c) 2019 pengfei.zhou. All rights reserved. +// + +#import +#import + +@interface QRScanViewController : UIViewController +@end \ No newline at end of file diff --git a/Example/Example/QRScanViewController.m b/Example/Example/QRScanViewController.m new file mode 100644 index 00000000..81aa3292 --- /dev/null +++ b/Example/Example/QRScanViewController.m @@ -0,0 +1,93 @@ +// +// Created by pengfei.zhou on 2019/11/21. +// Copyright (c) 2019 pengfei.zhou. All rights reserved. +// + +#import "QRScanViewController.h" +#import +#import "Doric.h" + +@interface QRScanViewController () +@property(strong, nonatomic) AVCaptureDevice *device; +@property(strong, nonatomic) AVCaptureDeviceInput *input; +@property(strong, nonatomic) AVCaptureMetadataOutput *output; +@property(strong, nonatomic) AVCaptureSession *session; +@property(strong, nonatomic) AVCaptureVideoPreviewLayer *previewLayer; +@property(strong, nonatomic) UIPinchGestureRecognizer *pinchGes; +@property(assign, nonatomic) CGFloat scanRegion_W; +@property(assign, nonatomic) CGFloat initScale; +@end + +@implementation QRScanViewController +- (void)viewDidLoad { + [super viewDidLoad]; + self.title = @"扫一扫"; + [self configBasicDevice]; + [self configPinchGes]; + [self.session startRunning]; +} + +- (void)configBasicDevice { + self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + self.input = [[AVCaptureDeviceInput alloc] initWithDevice:self.device error:nil]; + self.output = [[AVCaptureMetadataOutput alloc] init]; + [self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; + self.session = [[AVCaptureSession alloc] init]; + [self.session setSessionPreset:AVCaptureSessionPresetHigh]; + if ([self.session canAddInput:self.input]) { + [self.session addInput:self.input]; + } + if ([self.session canAddOutput:self.output]) { + [self.session addOutput:self.output]; + } + [self.output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]]; + [self.output setRectOfInterest:CGRectMake(0, 0, 1, 1)]; + self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session]; + self.previewLayer.frame = CGRectMake(0, 0, self.view.width, self.view.height); + self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + [self.view.layer addSublayer:self.previewLayer]; +} + +- (void)configPinchGes { + self.pinchGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchDetected:)]; + [self.view addGestureRecognizer:self.pinchGes]; +} + +- (void)pinchDetected:(UIPinchGestureRecognizer *)recogniser { + if (!_device) { + return; + } + if (recogniser.state == UIGestureRecognizerStateBegan) { + _initScale = _device.videoZoomFactor; + } + NSError *error = nil; + [_device lockForConfiguration:&error]; + if (!error) { + CGFloat zoomFactor; + CGFloat scale = recogniser.scale; + if (scale < 1.0f) { + zoomFactor = self.initScale - pow(self.device.activeFormat.videoMaxZoomFactor, 1.0f - recogniser.scale); + } else { + zoomFactor = self.initScale + pow(self.device.activeFormat.videoMaxZoomFactor, (recogniser.scale - 1.0f) / 2.0f); + } + zoomFactor = MIN(15.0f, zoomFactor); + zoomFactor = MAX(1.0f, zoomFactor); + _device.videoZoomFactor = zoomFactor; + [_device unlockForConfiguration]; + } +} + +#pragma mark - AVCaptureMetadataOutputObjectsDelegate + +- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { + [self.session stopRunning]; + if ([metadataObjects count] >= 1) { + AVMetadataMachineReadableCodeObject *qrObject = [metadataObjects lastObject]; + NSString *result = qrObject.stringValue; + NSLog(@"Scan result is %@", result); + [[DoricDriver instance] connectDevKit:[NSString stringWithFormat:@"ws://%@:7777", result]]; + ShowToast([NSString stringWithFormat:@"Connected to %@", result], BOTTOM); + [self.navigationController popViewControllerAnimated:NO]; + } +} +@end \ No newline at end of file diff --git a/Example/Example/ViewController.h b/Example/Example/ViewController.h new file mode 100644 index 00000000..6e210c8a --- /dev/null +++ b/Example/Example/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// Example +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 pengfei.zhou. All rights reserved. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/Example/Example/ViewController.m b/Example/Example/ViewController.m new file mode 100644 index 00000000..8acb2e3e --- /dev/null +++ b/Example/Example/ViewController.m @@ -0,0 +1,83 @@ +// +// ViewController.m +// Example +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 pengfei.zhou. All rights reserved. +// + +#import "ViewController.h" +#import "Doric.h" +#import "DemoVC.h" +#import "QRScanViewController.h" + +@interface ViewController () +@property(nonatomic, copy) NSArray *demoFilePaths; +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.title = @"Doric Demo"; + NSString *path = [[NSBundle mainBundle] bundlePath]; + NSString *demoPath = [path stringByAppendingPathComponent:@"demo"]; + NSFileManager *mgr = [NSFileManager defaultManager]; + self.demoFilePaths = [[mgr subpathsAtPath:demoPath] filter:^BOOL(NSString *obj) { + return ![obj containsString:@".map"]; + }]; + NSMutableArray *tmp = [self.demoFilePaths mutableCopy]; + [tmp insertObject:@"Dev Kit" atIndex:0]; + self.demoFilePaths = tmp; + [self.view addSubview:[[UITableView new] also:^(UITableView *it) { + it.width = self.view.width; + it.height = self.view.height; + it.left = it.top = 0; + it.dataSource = self; + it.delegate = self; + }]]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.demoFilePaths.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + NSString *path = self.demoFilePaths[(NSUInteger) indexPath.row]; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellID"]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + } + cell.textLabel.text = path; + return cell; +} + +- (BOOL)isSimulator { + return TARGET_OS_SIMULATOR == 1; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row == 0) { + if (self.isSimulator) { + NSString *result = @"127.0.0.1"; + [[DoricDriver instance] connectDevKit:[NSString stringWithFormat:@"ws://%@:7777", result]]; + ShowToast([NSString stringWithFormat:@"Connected to %@", result], BOTTOM); + } else { + [self.navigationController pushViewController:[QRScanViewController new] animated:NO]; + } + return; + } + NSString *file = self.demoFilePaths[(NSUInteger) indexPath.row]; + if ([file containsString:@"NavigatorDemo"]) { + DoricViewController *doricViewController = [[DoricViewController alloc] + initWithScheme:[NSString stringWithFormat:@"assets://demo/%@", file] + alias:self.demoFilePaths[(NSUInteger) indexPath.row]]; + [self.navigationController pushViewController:doricViewController animated:NO]; + } else { + DemoVC *demoVC = [[DemoVC alloc] initWithPath:file]; + [self.navigationController pushViewController:demoVC animated:NO]; + } +} + +@end diff --git a/Example/Example/demo b/Example/Example/demo new file mode 120000 index 00000000..7506393d --- /dev/null +++ b/Example/Example/demo @@ -0,0 +1 @@ +../../../demo/bundle/src/ \ No newline at end of file diff --git a/Example/Example/main.m b/Example/Example/main.m new file mode 100644 index 00000000..bd949033 --- /dev/null +++ b/Example/Example/main.m @@ -0,0 +1,16 @@ +// +// main.m +// Example +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 pengfei.zhou. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/Example/ExampleTests/ExampleTests.m b/Example/ExampleTests/ExampleTests.m new file mode 100644 index 00000000..3c72c126 --- /dev/null +++ b/Example/ExampleTests/ExampleTests.m @@ -0,0 +1,37 @@ +// +// ExampleTests.m +// ExampleTests +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 pengfei.zhou. All rights reserved. +// + +#import + +@interface ExampleTests : XCTestCase + +@end + +@implementation ExampleTests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/Example/ExampleTests/Info.plist b/Example/ExampleTests/Info.plist new file mode 100644 index 00000000..6c40a6cd --- /dev/null +++ b/Example/ExampleTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example/ExampleUITests/ExampleUITests.m b/Example/ExampleUITests/ExampleUITests.m new file mode 100644 index 00000000..b34459a7 --- /dev/null +++ b/Example/ExampleUITests/ExampleUITests.m @@ -0,0 +1,38 @@ +// +// ExampleUITests.m +// ExampleUITests +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 pengfei.zhou. All rights reserved. +// + +#import + +@interface ExampleUITests : XCTestCase + +@end + +@implementation ExampleUITests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + self.continueAfterFailure = NO; + + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + [[[XCUIApplication alloc] init] launch]; + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +@end diff --git a/Example/ExampleUITests/Info.plist b/Example/ExampleUITests/Info.plist new file mode 100644 index 00000000..6c40a6cd --- /dev/null +++ b/Example/ExampleUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 00000000..86f37ee2 --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,17 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +target 'Example' do + + pod 'Doric', :path => '../' + target 'ExampleTests' do + inherit! :search_paths + # Pods for testing + end + + target 'ExampleUITests' do + inherit! :search_paths + # Pods for testing + end + +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock new file mode 100644 index 00000000..93a0efb8 --- /dev/null +++ b/Example/Podfile.lock @@ -0,0 +1,47 @@ +PODS: + - Doric (0.1.0): + - GCDWebServer (~> 3.0) + - SocketRocket (~> 0.5.1) + - YYCache (~> 1.0.4) + - YYImage/WebP + - YYWebImage (~> 1.0.5) + - GCDWebServer (3.5.3): + - GCDWebServer/Core (= 3.5.3) + - GCDWebServer/Core (3.5.3) + - SocketRocket (0.5.1) + - YYCache (1.0.4) + - YYImage (1.0.4): + - YYImage/Core (= 1.0.4) + - YYImage/Core (1.0.4) + - YYImage/WebP (1.0.4): + - YYImage/Core + - YYWebImage (1.0.5): + - YYCache + - YYImage + +DEPENDENCIES: + - Doric (from `../`) + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - GCDWebServer + - SocketRocket + - YYCache + - YYImage + - YYWebImage + +EXTERNAL SOURCES: + Doric: + :path: "../" + +SPEC CHECKSUMS: + Doric: 38ac111ee84ca27c3838f65f286daf11b537801c + GCDWebServer: c0ab22c73e1b84f358d1e2f74bf6afd1c60829f2 + SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 + YYCache: 8105b6638f5e849296c71f331ff83891a4942952 + YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 + YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928 + +PODFILE CHECKSUM: 012563d71439e7e33e976dca3b59664ed56cee39 + +COCOAPODS: 1.7.5 diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServer.h b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServer.h new file mode 100644 index 00000000..70cb70c6 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServer.h @@ -0,0 +1,637 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +#import "GCDWebServerRequest.h" +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerMatchBlock is called for every handler added to the + * GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have + * been received). The block is passed the basic info for the request (HTTP method, + * URL, headers...) and must decide if it wants to handle it or not. + * + * If the handler can handle the request, the block must return a new + * GCDWebServerRequest instance created with the same basic info. + * Otherwise, it simply returns nil. + */ +typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); + +/** + * The GCDWebServerProcessBlock is called after the HTTP request has been fully + * received (i.e. the entire HTTP body has been read). The block is passed the + * GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock. + * + * The block must return a GCDWebServerResponse or nil on error, which will + * result in a 500 HTTP status code returned to the client. It's however + * recommended to return a GCDWebServerErrorResponse on error so more useful + * information can be returned to the client. + */ +typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request); + +/** + * The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock + * except the GCDWebServerResponse can be returned to the server at a later time + * allowing for asynchronous generation of the response. + * + * The block must eventually call "completionBlock" passing a GCDWebServerResponse + * or nil on error, which will result in a 500 HTTP status code returned to the client. + * It's however recommended to return a GCDWebServerErrorResponse on error so more + * useful information can be returned to the client. + */ +typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response); +typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock); + +/** + * The GCDWebServerBuiltInLoggerBlock is used to override the built-in logger at runtime. + * The block will be passed the log level and the log message, see setLogLevel for + * documentation of the log levels for the built-in logger. + */ +typedef void (^GCDWebServerBuiltInLoggerBlock)(int level, NSString* _Nonnull message); + +/** + * The port used by the GCDWebServer (NSNumber / NSUInteger). + * + * The default value is 0 i.e. let the OS pick a random port. + */ +extern NSString* const GCDWebServerOption_Port; + +/** + * The Bonjour name used by the GCDWebServer (NSString). If set to an empty string, + * the name will automatically take the value of the GCDWebServerOption_ServerName + * option. If this option is set to nil, Bonjour will be disabled. + * + * The default value is nil. + */ +extern NSString* const GCDWebServerOption_BonjourName; + +/** + * The Bonjour service type used by the GCDWebServer (NSString). + * + * The default value is "_http._tcp", the service type for HTTP web servers. + */ +extern NSString* const GCDWebServerOption_BonjourType; + +/** + * Request a port mapping in the NAT gateway (NSNumber / BOOL). + * + * This uses the DNSService API under the hood which supports IPv4 mappings only. + * + * The default value is NO. + * + * @warning The external port set up by the NAT gateway may be different than + * the one used by the GCDWebServer. + */ +extern NSString* const GCDWebServerOption_RequestNATPortMapping; + +/** + * Only accept HTTP requests coming from localhost i.e. not from the outside + * network (NSNumber / BOOL). + * + * The default value is NO. + * + * @warning Bonjour and NAT port mapping should be disabled if using this option + * since the server will not be reachable from the outside network anyway. + */ +extern NSString* const GCDWebServerOption_BindToLocalhost; + +/** + * The maximum number of incoming HTTP requests that can be queued waiting to + * be handled before new ones are dropped (NSNumber / NSUInteger). + * + * The default value is 16. + */ +extern NSString* const GCDWebServerOption_MaxPendingConnections; + +/** + * The value for "Server" HTTP header used by the GCDWebServer (NSString). + * + * The default value is the GCDWebServer class name. + */ +extern NSString* const GCDWebServerOption_ServerName; + +/** + * The authentication method used by the GCDWebServer + * (one of "GCDWebServerAuthenticationMethod_..."). + * + * The default value is nil i.e. authentication is disabled. + */ +extern NSString* const GCDWebServerOption_AuthenticationMethod; + +/** + * The authentication realm used by the GCDWebServer (NSString). + * + * The default value is the same as the GCDWebServerOption_ServerName option. + */ +extern NSString* const GCDWebServerOption_AuthenticationRealm; + +/** + * The authentication accounts used by the GCDWebServer + * (NSDictionary of username / password pairs). + * + * The default value is nil i.e. no accounts. + */ +extern NSString* const GCDWebServerOption_AuthenticationAccounts; + +/** + * The class used by the GCDWebServer when instantiating GCDWebServerConnection + * (subclass of GCDWebServerConnection). + * + * The default value is the GCDWebServerConnection class. + */ +extern NSString* const GCDWebServerOption_ConnectionClass; + +/** + * Allow the GCDWebServer to pretend "HEAD" requests are actually "GET" ones + * and automatically discard the HTTP body of the response (NSNumber / BOOL). + * + * The default value is YES. + */ +extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; + +/** + * The interval expressed in seconds used by the GCDWebServer to decide how to + * coalesce calls to -webServerDidConnect: and -webServerDidDisconnect: + * (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0. + * + * The default value is 1.0 second. + */ +extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; + +/** + * Set the dispatch queue priority on which server connection will be + * run (NSNumber / long). + * + * + * The default value is DISPATCH_QUEUE_PRIORITY_DEFAULT. + */ +extern NSString* const GCDWebServerOption_DispatchQueuePriority; + +#if TARGET_OS_IPHONE + +/** + * Enables the GCDWebServer to automatically suspend itself (as if -stop was + * called) when the iOS app goes into the background and the last + * GCDWebServerConnection is closed, then resume itself (as if -start was called) + * when the iOS app comes back to the foreground (NSNumber / BOOL). + * + * See the README.md file for more information about this option. + * + * The default value is YES. + * + * @warning The running property will be NO while the GCDWebServer is suspended. + */ +extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; + +#endif + +/** + * HTTP Basic Authentication scheme (see https://tools.ietf.org/html/rfc2617). + * + * @warning Use of this authentication scheme is not recommended as the + * passwords are sent in clear. + */ +extern NSString* const GCDWebServerAuthenticationMethod_Basic; + +/** + * HTTP Digest Access Authentication scheme (see https://tools.ietf.org/html/rfc2617). + */ +extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess; + +@class GCDWebServer; + +/** + * Delegate methods for GCDWebServer. + * + * @warning These methods are always called on the main thread in a serialized way. + */ +@protocol GCDWebServerDelegate +@optional + +/** + * This method is called after the server has successfully started. + */ +- (void)webServerDidStart:(GCDWebServer*)server; + +/** + * This method is called after the Bonjour registration for the server has + * successfully completed. + * + * Use the "bonjourServerURL" property to retrieve the Bonjour address of the + * server. + */ +- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server; + +/** + * This method is called after the NAT port mapping for the server has been + * updated. + * + * Use the "publicServerURL" property to retrieve the public address of the + * server. + */ +- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server; + +/** + * This method is called when the first GCDWebServerConnection is opened by the + * server to serve a series of HTTP requests. + * + * A series of HTTP requests is considered ongoing as long as new HTTP requests + * keep coming (and new GCDWebServerConnection instances keep being opened), + * until before the last HTTP request has been responded to (and the + * corresponding last GCDWebServerConnection closed). + */ +- (void)webServerDidConnect:(GCDWebServer*)server; + +/** + * This method is called when the last GCDWebServerConnection is closed after + * the server has served a series of HTTP requests. + * + * The GCDWebServerOption_ConnectedStateCoalescingInterval option can be used + * to have the server wait some extra delay before considering that the series + * of HTTP requests has ended (in case there some latency between consecutive + * requests). This effectively coalesces the calls to -webServerDidConnect: + * and -webServerDidDisconnect:. + */ +- (void)webServerDidDisconnect:(GCDWebServer*)server; + +/** + * This method is called after the server has stopped. + */ +- (void)webServerDidStop:(GCDWebServer*)server; + +@end + +/** + * The GCDWebServer class listens for incoming HTTP requests on a given port, + * then passes each one to a "handler" capable of generating an HTTP response + * for it, which is then sent back to the client. + * + * GCDWebServer instances can be created and used from any thread but it's + * recommended to have the main thread's runloop be running so internal callbacks + * can be handled e.g. for Bonjour registration. + * + * See the README.md file for more information about the architecture of GCDWebServer. + */ +@interface GCDWebServer : NSObject + +/** + * Sets the delegate for the server. + */ +@property(nonatomic, weak, nullable) id delegate; + +/** + * Returns YES if the server is currently running. + */ +@property(nonatomic, readonly, getter=isRunning) BOOL running; + +/** + * Returns the port used by the server. + * + * @warning This property is only valid if the server is running. + */ +@property(nonatomic, readonly) NSUInteger port; + +/** + * Returns the Bonjour name used by the server. + * + * @warning This property is only valid if the server is running and Bonjour + * registration has successfully completed, which can take up to a few seconds. + */ +@property(nonatomic, readonly, nullable) NSString* bonjourName; + +/** + * Returns the Bonjour service type used by the server. + * + * @warning This property is only valid if the server is running and Bonjour + * registration has successfully completed, which can take up to a few seconds. + */ +@property(nonatomic, readonly, nullable) NSString* bonjourType; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)init; + +/** + * Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests. + * + * Handlers are called in a LIFO queue, so if multiple handlers can potentially + * respond to a given request, the latest added one wins. + * + * @warning Addling handlers while the server is running is not allowed. + */ +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock; + +/** + * Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests. + * + * Handlers are called in a LIFO queue, so if multiple handlers can potentially + * respond to a given request, the latest added one wins. + * + * @warning Addling handlers while the server is running is not allowed. + */ +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock; + +/** + * Removes all handlers previously added to the server. + * + * @warning Removing handlers while the server is running is not allowed. + */ +- (void)removeAllHandlers; + +/** + * Starts the server with explicit options. This method is the designated way + * to start the server. + * + * Returns NO if the server failed to start and sets "error" argument if not NULL. + */ +- (BOOL)startWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error; + +/** + * Stops the server and prevents it to accepts new HTTP requests. + * + * @warning Stopping the server does not abort GCDWebServerConnection instances + * currently handling already received HTTP requests. These connections will + * continue to execute normally until completion. + */ +- (void)stop; + +@end + +@interface GCDWebServer (Extensions) + +/** + * Returns the server's URL. + * + * @warning This property is only valid if the server is running. + */ +@property(nonatomic, readonly, nullable) NSURL* serverURL; + +/** + * Returns the server's Bonjour URL. + * + * @warning This property is only valid if the server is running and Bonjour + * registration has successfully completed, which can take up to a few seconds. + * Also be aware this property will not automatically update if the Bonjour hostname + * has been dynamically changed after the server started running (this should be rare). + */ +@property(nonatomic, readonly, nullable) NSURL* bonjourServerURL; + +/** + * Returns the server's public URL. + * + * @warning This property is only valid if the server is running and NAT port + * mapping is active. + */ +@property(nonatomic, readonly, nullable) NSURL* publicServerURL; + +/** + * Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS) + * using the default Bonjour name. + * + * Returns NO if the server failed to start. + */ +- (BOOL)start; + +/** + * Starts the server on a given port and with a specific Bonjour name. + * Pass a nil Bonjour name to disable Bonjour entirely or an empty string to + * use the default name. + * + * Returns NO if the server failed to start. + */ +- (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name; + +#if !TARGET_OS_IPHONE + +/** + * Runs the server synchronously using -startWithPort:bonjourName: until a + * SIGINT signal is received i.e. Ctrl-C. This method is intended to be used + * by command line tools. + * + * Returns NO if the server failed to start. + * + * @warning This method must be used from the main thread only. + */ +- (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name; + +/** + * Runs the server synchronously using -startWithOptions: until a SIGTERM or + * SIGINT signal is received i.e. Ctrl-C in Terminal. This method is intended to + * be used by command line tools. + * + * Returns NO if the server failed to start and sets "error" argument if not NULL. + * + * @warning This method must be used from the main thread only. + */ +- (BOOL)runWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error; + +#endif + +@end + +@interface GCDWebServer (Handlers) + +/** + * Adds a default handler to the server to handle all incoming HTTP requests + * with a given HTTP method and generate responses synchronously. + */ +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; + +/** + * Adds a default handler to the server to handle all incoming HTTP requests + * with a given HTTP method and generate responses asynchronously. + */ +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a specific case-insensitive path and generate responses + * synchronously. + */ +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a specific case-insensitive path and generate responses + * asynchronously. + */ +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a path matching a case-insensitive regular expression and + * generate responses synchronously. + */ +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a path matching a case-insensitive regular expression and + * generate responses asynchronously. + */ +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block; + +@end + +@interface GCDWebServer (GETHandlers) + +/** + * Adds a handler to the server to respond to incoming "GET" HTTP requests + * with a specific case-insensitive path with in-memory data. + */ +- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(nullable NSString*)contentType cacheAge:(NSUInteger)cacheAge; + +/** + * Adds a handler to the server to respond to incoming "GET" HTTP requests + * with a specific case-insensitive path with a file. + */ +- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; + +/** + * Adds a handler to the server to respond to incoming "GET" HTTP requests + * with a case-insensitive path inside a base path with the corresponding file + * inside a local directory. If no local file matches the request path, a 401 + * HTTP status code is returned to the client. + * + * The "indexFilename" argument allows to specify an "index" file name to use + * when the request path corresponds to a directory. + */ +- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(nullable NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; + +@end + +/** + * GCDWebServer provides its own built-in logging facility which is used by + * default. It simply sends log messages to stderr assuming it is connected + * to a terminal type device. + * + * GCDWebServer is also compatible with a limited set of third-party logging + * facilities. If one of them is available at compile time, GCDWebServer will + * automatically use it in place of the built-in one. + * + * Currently supported third-party logging facilities are: + * - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility + * + * For the built-in logging facility, the default logging level is INFO + * (or DEBUG if the preprocessor constant "DEBUG" evaluates to non-zero at + * compile time). + * + * It's possible to have GCDWebServer use a custom logging facility by defining + * the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build + * settings to the name of a custom header file (escaped like \"MyLogging.h\"). + * This header file must define the following set of macros: + * + * GWS_LOG_DEBUG(...) + * GWS_LOG_VERBOSE(...) + * GWS_LOG_INFO(...) + * GWS_LOG_WARNING(...) + * GWS_LOG_ERROR(...) + * + * IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() + * macro should not do anything unless the preprocessor constant "DEBUG" evaluates + * to non-zero. + * + * The logging methods below send log messages to the same logging facility + * used by GCDWebServer. They can be used for consistency wherever you interact + * with GCDWebServer in your code (e.g. in the implementation of handlers). + */ +@interface GCDWebServer (Logging) + +/** + * Sets the log level of the logging facility below which log messages are discarded. + * + * @warning The interpretation of the "level" argument depends on the logging + * facility used at compile time. + * + * If using the built-in logging facility, the log levels are as follow: + * DEBUG = 0 + * VERBOSE = 1 + * INFO = 2 + * WARNING = 3 + * ERROR = 4 + */ ++ (void)setLogLevel:(int)level; + +/** + * Set a logger to be used instead of the built-in logger which logs to stderr. + * + * IMPORTANT: In order for this override to work, you should not be specifying + * a custom logger at compile time with "__GCDWEBSERVER_LOGGING_HEADER__". + */ ++ (void)setBuiltInLogger:(GCDWebServerBuiltInLoggerBlock)block; + +/** + * Logs a message to the logging facility at the VERBOSE level. + */ +- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +/** + * Logs a message to the logging facility at the INFO level. + */ +- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +/** + * Logs a message to the logging facility at the WARNING level. + */ +- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +/** + * Logs a message to the logging facility at the ERROR level. + */ +- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +@end + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + +@interface GCDWebServer (Testing) + +/** + * Activates recording of HTTP requests and responses which create files in the + * current directory containing the raw data for all requests and responses. + * + * @warning The current directory must not contain any prior recording files. + */ +@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled; + +/** + * Runs tests by playing back pre-recorded HTTP requests in the given directory + * and comparing the generated responses with the pre-recorded ones. + * + * Returns the number of failed tests or -1 if server failed to start. + */ +- (NSInteger)runTestsWithOptions:(nullable NSDictionary*)options inDirectory:(NSString*)path; + +@end + +#endif + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServer.m b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServer.m new file mode 100644 index 00000000..cb1b2955 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServer.m @@ -0,0 +1,1306 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import +#if TARGET_OS_IPHONE +#import +#else +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ +#import +#endif +#endif +#import +#import + +#import "GCDWebServerPrivate.h" + +#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR +#define kDefaultPort 80 +#else +#define kDefaultPort 8080 +#endif + +#define kBonjourResolutionTimeout 5.0 + +NSString* const GCDWebServerOption_Port = @"Port"; +NSString* const GCDWebServerOption_BonjourName = @"BonjourName"; +NSString* const GCDWebServerOption_BonjourType = @"BonjourType"; +NSString* const GCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping"; +NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost"; +NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections"; +NSString* const GCDWebServerOption_ServerName = @"ServerName"; +NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod"; +NSString* const GCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm"; +NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts"; +NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass"; +NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET"; +NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval"; +NSString* const GCDWebServerOption_DispatchQueuePriority = @"DispatchQueuePriority"; +#if TARGET_OS_IPHONE +NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground"; +#endif + +NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic"; +NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess"; + +#if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__) +#if DEBUG +GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Debug; +#else +GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info; +#endif +#endif + +#if !TARGET_OS_IPHONE +static BOOL _run; +#endif + +#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ + +static GCDWebServerBuiltInLoggerBlock _builtInLoggerBlock; + +void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) { + static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR"}; + static int enableLogging = -1; + if (enableLogging < 0) { + enableLogging = (isatty(STDERR_FILENO) ? 1 : 0); + } + if (_builtInLoggerBlock || enableLogging) { + va_list arguments; + va_start(arguments, format); + NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; + va_end(arguments); + if (_builtInLoggerBlock) { + _builtInLoggerBlock(level, message); + } else { + fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]); + } + } +} + +#endif + +#if !TARGET_OS_IPHONE + +static void _SignalHandler(int signal) { + _run = NO; + printf("\n"); +} + +#endif + +#if !TARGET_OS_IPHONE || defined(__GCDWEBSERVER_ENABLE_TESTING__) + +// This utility function is used to ensure scheduled callbacks on the main thread are called when running the server synchronously +// https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html +// The main queue works with the application’s run loop to interleave the execution of queued tasks with the execution of other event sources attached to the run loop +// TODO: Ensure all scheduled blocks on the main queue are also executed +static void _ExecuteMainThreadRunLoopSources() { + SInt32 result; + do { + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); + } while (result == kCFRunLoopRunHandledSource); +} + +#endif + +@implementation GCDWebServerHandler + +- (instancetype)initWithMatchBlock:(GCDWebServerMatchBlock _Nonnull)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock _Nonnull)processBlock { + if ((self = [super init])) { + _matchBlock = [matchBlock copy]; + _asyncProcessBlock = [processBlock copy]; + } + return self; +} + +@end + +@implementation GCDWebServer { + dispatch_queue_t _syncQueue; + dispatch_group_t _sourceGroup; + NSMutableArray* _handlers; + NSInteger _activeConnections; // Accessed through _syncQueue only + BOOL _connected; // Accessed on main thread only + CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only + + NSDictionary* _options; + NSMutableDictionary* _authenticationBasicAccounts; + NSMutableDictionary* _authenticationDigestAccounts; + Class _connectionClass; + CFTimeInterval _disconnectDelay; + dispatch_source_t _source4; + dispatch_source_t _source6; + CFNetServiceRef _registrationService; + CFNetServiceRef _resolutionService; + DNSServiceRef _dnsService; + CFSocketRef _dnsSocket; + CFRunLoopSourceRef _dnsSource; + NSString* _dnsAddress; + NSUInteger _dnsPort; + BOOL _bindToLocalhost; +#if TARGET_OS_IPHONE + BOOL _suspendInBackground; + UIBackgroundTaskIdentifier _backgroundTask; +#endif +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + BOOL _recording; +#endif +} + ++ (void)initialize { + GCDWebServerInitializeFunctions(); +} + +- (instancetype)init { + if ((self = [super init])) { + _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL); + _sourceGroup = dispatch_group_create(); + _handlers = [[NSMutableArray alloc] init]; +#if TARGET_OS_IPHONE + _backgroundTask = UIBackgroundTaskInvalid; +#endif + } + return self; +} + +- (void)dealloc { + GWS_DCHECK(_connected == NO); + GWS_DCHECK(_activeConnections == 0); + GWS_DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source + GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle + +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(_sourceGroup); + dispatch_release(_syncQueue); +#endif +} + +#if TARGET_OS_IPHONE + +// Always called on main thread +- (void)_startBackgroundTask { + GWS_DCHECK([NSThread isMainThread]); + if (_backgroundTask == UIBackgroundTaskInvalid) { + GWS_LOG_DEBUG(@"Did start background task"); + _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]); + [self _endBackgroundTask]; + }]; + } else { + GWS_DNOT_REACHED(); + } +} + +#endif + +// Always called on main thread +- (void)_didConnect { + GWS_DCHECK([NSThread isMainThread]); + GWS_DCHECK(_connected == NO); + _connected = YES; + GWS_LOG_DEBUG(@"Did connect"); + +#if TARGET_OS_IPHONE + if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) { + [self _startBackgroundTask]; + } +#endif + + if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) { + [_delegate webServerDidConnect:self]; + } +} + +- (void)willStartConnection:(GCDWebServerConnection*)connection { + dispatch_sync(_syncQueue, ^{ + GWS_DCHECK(self->_activeConnections >= 0); + if (self->_activeConnections == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_disconnectTimer) { + CFRunLoopTimerInvalidate(self->_disconnectTimer); + CFRelease(self->_disconnectTimer); + self->_disconnectTimer = NULL; + } + if (self->_connected == NO) { + [self _didConnect]; + } + }); + } + self->_activeConnections += 1; + }); +} + +#if TARGET_OS_IPHONE + +// Always called on main thread +- (void)_endBackgroundTask { + GWS_DCHECK([NSThread isMainThread]); + if (_backgroundTask != UIBackgroundTaskInvalid) { + if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) { + [self _stop]; + } + [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask]; + _backgroundTask = UIBackgroundTaskInvalid; + GWS_LOG_DEBUG(@"Did end background task"); + } +} + +#endif + +// Always called on main thread +- (void)_didDisconnect { + GWS_DCHECK([NSThread isMainThread]); + GWS_DCHECK(_connected == YES); + _connected = NO; + GWS_LOG_DEBUG(@"Did disconnect"); + +#if TARGET_OS_IPHONE + [self _endBackgroundTask]; +#endif + + if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) { + [_delegate webServerDidDisconnect:self]; + } +} + +- (void)didEndConnection:(GCDWebServerConnection*)connection { + dispatch_sync(_syncQueue, ^{ + GWS_DCHECK(self->_activeConnections > 0); + self->_activeConnections -= 1; + if (self->_activeConnections == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + if ((self->_disconnectDelay > 0.0) && (self->_source4 != NULL)) { + if (self->_disconnectTimer) { + CFRunLoopTimerInvalidate(self->_disconnectTimer); + CFRelease(self->_disconnectTimer); + } + self->_disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + self->_disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) { + GWS_DCHECK([NSThread isMainThread]); + [self _didDisconnect]; + CFRelease(self->_disconnectTimer); + self->_disconnectTimer = NULL; + }); + CFRunLoopAddTimer(CFRunLoopGetMain(), self->_disconnectTimer, kCFRunLoopCommonModes); + } else { + [self _didDisconnect]; + } + }); + } + }); +} + +- (NSString*)bonjourName { + CFStringRef name = _resolutionService ? CFNetServiceGetName(_resolutionService) : NULL; + return name && CFStringGetLength(name) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil; +} + +- (NSString*)bonjourType { + CFStringRef type = _resolutionService ? CFNetServiceGetType(_resolutionService) : NULL; + return type && CFStringGetLength(type) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil; +} + +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock { + [self addHandlerWithMatchBlock:matchBlock + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(processBlock(request)); + }]; +} + +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock { + GWS_DCHECK(_options == nil); + GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock]; + [_handlers insertObject:handler atIndex:0]; +} + +- (void)removeAllHandlers { + GWS_DCHECK(_options == nil); + [_handlers removeAllObjects]; +} + +static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + if (error->error) { + GWS_LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain); + } else { + GCDWebServer* server = (__bridge GCDWebServer*)info; + GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]); + if (!CFNetServiceResolveWithTimeout(server->_resolutionService, kBonjourResolutionTimeout, NULL)) { + GWS_LOG_ERROR(@"Failed starting Bonjour resolution"); + GWS_DNOT_REACHED(); + } + } + } +} + +static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + if (error->error) { + if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) { + GWS_LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain); + } + } else { + GCDWebServer* server = (__bridge GCDWebServer*)info; + GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL); + if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) { + [server.delegate webServerDidCompleteBonjourRegistration:server]; + } + } + } +} + +static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + GCDWebServer* server = (__bridge GCDWebServer*)context; + if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) { + struct sockaddr_in addr4; + bzero(&addr4, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_addr.s_addr = externalAddress; // Already in network byte order + server->_dnsAddress = GCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO); + server->_dnsPort = ntohs(externalPort); + GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL); + } else { + GWS_LOG_ERROR(@"DNS service error %i", errorCode); + server->_dnsAddress = nil; + server->_dnsPort = 0; + } + if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) { + [server.delegate webServerDidUpdateNATPortMapping:server]; + } + } +} + +static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + GCDWebServer* server = (__bridge GCDWebServer*)info; + DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService); + if (status != kDNSServiceErr_NoError) { + GWS_LOG_ERROR(@"DNS service error %i", status); + } + } +} + +static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) { + id value = [options objectForKey:key]; + return value ? value : defaultValue; +} + +static inline NSString* _EncodeBase64(NSString* string) { + NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding]; +#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9) + return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]; +#else + if (@available(macOS 10.9, *)) { + return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]; + } + return [data base64Encoding]; +#endif +} + +- (int)_createListeningSocket:(BOOL)useIPv6 + localAddress:(const void*)address + length:(socklen_t)length + maxPendingConnections:(NSUInteger)maxPendingConnections + error:(NSError**)error { + int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listeningSocket > 0) { + int yes = 1; + setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + if (bind(listeningSocket, address, length) == 0) { + if (listen(listeningSocket, (int)maxPendingConnections) == 0) { + GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket); + return listeningSocket; + } else { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + close(listeningSocket); + } + } else { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + close(listeningSocket); + } + + } else { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } + return -1; +} + +- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 { + dispatch_group_enter(_sourceGroup); + dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0)); + dispatch_source_set_cancel_handler(source, ^{ + @autoreleasepool { + int result = close(listeningSocket); + if (result != 0) { + GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } else { + GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket); + } + } + dispatch_group_leave(self->_sourceGroup); + }); + dispatch_source_set_event_handler(source, ^{ + @autoreleasepool { + struct sockaddr_storage remoteSockAddr; + socklen_t remoteAddrLen = sizeof(remoteSockAddr); + int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen); + if (socket > 0) { + NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen]; + + struct sockaddr_storage localSockAddr; + socklen_t localAddrLen = sizeof(localSockAddr); + NSData* localAddress = nil; + if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) { + localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen]; + GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6)); + } else { + GWS_DNOT_REACHED(); + } + + int noSigPipe = 1; + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE + + GCDWebServerConnection* connection = [(GCDWebServerConnection*)[self->_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened + [connection self]; // Prevent compiler from complaining about unused variable / useless statement + } else { + GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } + } + }); + return source; +} + +- (BOOL)_start:(NSError**)error { + GWS_DCHECK(_source4 == NULL); + + NSUInteger port = [(NSNumber*)_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; + BOOL bindToLocalhost = [(NSNumber*)_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue]; + NSUInteger maxPendingConnections = [(NSNumber*)_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; + + struct sockaddr_in addr4; + bzero(&addr4, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY); + int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error]; + if (listeningSocket4 <= 0) { + return NO; + } + if (port == 0) { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) { + port = ntohs(addr.sin_port); + } else { + GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno); + } + } + + struct sockaddr_in6 addr6; + bzero(&addr6, sizeof(addr6)); + addr6.sin6_len = sizeof(addr6); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(port); + addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any; + int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error]; + if (listeningSocket6 <= 0) { + close(listeningSocket4); + return NO; + } + + _serverName = [(NSString*)_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; + NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil); + if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) { + _authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; + _authenticationBasicAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [self->_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username]; + }]; + } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) { + _authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; + _authenticationDigestAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [self->_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, self->_authenticationRealm, password) forKey:username]; + }]; + } + _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); + _shouldAutomaticallyMapHEADToGET = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; + _disconnectDelay = [(NSNumber*)_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue]; + _dispatchQueuePriority = [(NSNumber*)_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue]; + + _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO]; + _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES]; + _port = port; + _bindToLocalhost = bindToLocalhost; + + NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil); + NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); + if (bonjourName) { + _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port); + if (_registrationService) { + CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + + CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context); + CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFStreamError streamError = {0}; + CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError); + + _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService); + if (_resolutionService) { + CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context); + CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + } else { + GWS_LOG_ERROR(@"Failed creating CFNetService for resolution"); + } + } else { + GWS_LOG_ERROR(@"Failed creating CFNetService for registration"); + } + } + + if ([(NSNumber*)_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) { + DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self); + if (status == kDNSServiceErr_NoError) { + CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context); + if (_dnsSocket) { + CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate); + _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0); + if (_dnsSource) { + CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes); + } else { + GWS_LOG_ERROR(@"Failed creating CFRunLoopSource"); + GWS_DNOT_REACHED(); + } + } else { + GWS_LOG_ERROR(@"Failed creating CFSocket"); + GWS_DNOT_REACHED(); + } + } else { + GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status); + } + } + + dispatch_resume(_source4); + dispatch_resume(_source6); + GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL); + if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_delegate webServerDidStart:self]; + }); + } + + return YES; +} + +- (void)_stop { + GWS_DCHECK(_source4 != NULL); + + if (_dnsService) { + _dnsAddress = nil; + _dnsPort = 0; + if (_dnsSource) { + CFRunLoopSourceInvalidate(_dnsSource); + CFRelease(_dnsSource); + _dnsSource = NULL; + } + if (_dnsSocket) { + CFRelease(_dnsSocket); + _dnsSocket = NULL; + } + DNSServiceRefDeallocate(_dnsService); + _dnsService = NULL; + } + + if (_registrationService) { + if (_resolutionService) { + CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFNetServiceSetClient(_resolutionService, NULL, NULL); + CFNetServiceCancel(_resolutionService); + CFRelease(_resolutionService); + _resolutionService = NULL; + } + CFNetServiceUnscheduleFromRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFNetServiceSetClient(_registrationService, NULL, NULL); + CFNetServiceCancel(_registrationService); + CFRelease(_registrationService); + _registrationService = NULL; + } + + dispatch_source_cancel(_source6); + dispatch_source_cancel(_source4); + dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(_source6); +#endif + _source6 = NULL; +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(_source4); +#endif + _source4 = NULL; + _port = 0; + _bindToLocalhost = NO; + + _serverName = nil; + _authenticationRealm = nil; + _authenticationBasicAccounts = nil; + _authenticationDigestAccounts = nil; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_disconnectTimer) { + CFRunLoopTimerInvalidate(self->_disconnectTimer); + CFRelease(self->_disconnectTimer); + self->_disconnectTimer = NULL; + [self _didDisconnect]; + } + }); + + GWS_LOG_INFO(@"%@ stopped", [self class]); + if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_delegate webServerDidStop:self]; + }); + } +} + +#if TARGET_OS_IPHONE + +- (void)_didEnterBackground:(NSNotification*)notification { + GWS_DCHECK([NSThread isMainThread]); + GWS_LOG_DEBUG(@"Did enter background"); + if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) { + [self _stop]; + } +} + +- (void)_willEnterForeground:(NSNotification*)notification { + GWS_DCHECK([NSThread isMainThread]); + GWS_LOG_DEBUG(@"Will enter foreground"); + if (!_source4) { + [self _start:NULL]; // TODO: There's probably nothing we can do on failure + } +} + +#endif + +- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error { + if (_options == nil) { + _options = options ? [options copy] : @{}; +#if TARGET_OS_IPHONE + _suspendInBackground = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue]; + if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error]) +#else + if (![self _start:error]) +#endif + { + _options = nil; + return NO; + } +#if TARGET_OS_IPHONE + if (_suspendInBackground) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + } +#endif + return YES; + } else { + GWS_DNOT_REACHED(); + } + return NO; +} + +- (BOOL)isRunning { + return (_options ? YES : NO); +} + +- (void)stop { + if (_options) { +#if TARGET_OS_IPHONE + if (_suspendInBackground) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + } +#endif + if (_source4) { + [self _stop]; + } + _options = nil; + } else { + GWS_DNOT_REACHED(); + } +} + +@end + +@implementation GCDWebServer (Extensions) + +- (NSURL*)serverURL { + if (_source4) { + NSString* ipAddress = _bindToLocalhost ? @"localhost" : GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice + if (ipAddress) { + if (_port != 80) { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]]; + } else { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", ipAddress]]; + } + } + } + return nil; +} + +- (NSURL*)bonjourServerURL { + if (_source4 && _resolutionService) { + NSString* name = (__bridge NSString*)CFNetServiceGetTargetHost(_resolutionService); + if (name.length) { + name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain + if (_port != 80) { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", name, (int)_port]]; + } else { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", name]]; + } + } + } + return nil; +} + +- (NSURL*)publicServerURL { + if (_source4 && _dnsService && _dnsAddress && _dnsPort) { + if (_dnsPort != 80) { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]]; + } else { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]]; + } + } + return nil; +} + +- (BOOL)start { + return [self startWithPort:kDefaultPort bonjourName:@""]; +} + +- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name { + NSMutableDictionary* options = [NSMutableDictionary dictionary]; + [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port]; + [options setValue:name forKey:GCDWebServerOption_BonjourName]; + return [self startWithOptions:options error:NULL]; +} + +#if !TARGET_OS_IPHONE + +- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name { + NSMutableDictionary* options = [NSMutableDictionary dictionary]; + [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port]; + [options setValue:name forKey:GCDWebServerOption_BonjourName]; + return [self runWithOptions:options error:NULL]; +} + +- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error { + GWS_DCHECK([NSThread isMainThread]); + BOOL success = NO; + _run = YES; + void (*termHandler)(int) = signal(SIGTERM, _SignalHandler); + void (*intHandler)(int) = signal(SIGINT, _SignalHandler); + if ((termHandler != SIG_ERR) && (intHandler != SIG_ERR)) { + if ([self startWithOptions:options error:error]) { + while (_run) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true); + } + [self stop]; + success = YES; + } + _ExecuteMainThreadRunLoopSources(); + signal(SIGINT, intHandler); + signal(SIGTERM, termHandler); + } + return success; +} + +#endif + +@end + +@implementation GCDWebServer (Handlers) + +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { + [self addDefaultHandlerForMethod:method + requestClass:aClass + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(block(request)); + }]; +} + +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:method]) { + return nil; + } + return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + } + asyncProcessBlock:block]; +} + +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { + [self addHandlerForMethod:method + path:path + requestClass:aClass + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(block(request)); + }]; +} + +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { + if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) { + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:method]) { + return nil; + } + if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) { + return nil; + } + return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + } + asyncProcessBlock:block]; + } else { + GWS_DNOT_REACHED(); + } +} + +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { + [self addHandlerForMethod:method + pathRegex:regex + requestClass:aClass + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(block(request)); + }]; +} + +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { + NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL]; + if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) { + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:method]) { + return nil; + } + + NSArray* matches = [expression matchesInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)]; + if (matches.count == 0) { + return nil; + } + + NSMutableArray* captures = [NSMutableArray array]; + for (NSTextCheckingResult* result in matches) { + // Start at 1; index 0 is the whole string + for (NSUInteger i = 1; i < result.numberOfRanges; i++) { + NSRange range = [result rangeAtIndex:i]; + // range is {NSNotFound, 0} "if one of the capture groups did not participate in this particular match" + // see discussion in -[NSRegularExpression firstMatchInString:options:range:] + if (range.location != NSNotFound) { + [captures addObject:[urlPath substringWithRange:range]]; + } + } + } + + GCDWebServerRequest* request = [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + [request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures]; + return request; + } + asyncProcessBlock:block]; + } else { + GWS_DNOT_REACHED(); + } +} + +@end + +@implementation GCDWebServer (GETHandlers) + +- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge { + [self addHandlerForMethod:@"GET" + path:path + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) { + GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType]; + response.cacheControlMaxAge = cacheAge; + return response; + }]; +} + +- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { + [self addHandlerForMethod:@"GET" + path:path + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) { + GCDWebServerResponse* response = nil; + if (allowRangeRequests) { + response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment]; + [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; + } else { + response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment]; + } + response.cacheControlMaxAge = cacheAge; + return response; + }]; +} + +- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path { + NSArray* contents = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)]; + if (contents == nil) { + return nil; + } + NSMutableString* html = [NSMutableString string]; + [html appendString:@"\n"]; + [html appendString:@"\n"]; + [html appendString:@"
    \n"]; + for (NSString* entry in contents) { + if (![entry hasPrefix:@"."]) { + NSString* type = [[[NSFileManager defaultManager] attributesOfItemAtPath:[path stringByAppendingPathComponent:entry] error:NULL] objectForKey:NSFileType]; + GWS_DCHECK(type); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSString* escapedFile = [entry stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; +#pragma clang diagnostic pop + GWS_DCHECK(escapedFile); + if ([type isEqualToString:NSFileTypeRegular]) { + [html appendFormat:@"
  • %@
  • \n", escapedFile, entry]; + } else if ([type isEqualToString:NSFileTypeDirectory]) { + [html appendFormat:@"
  • %@/
  • \n", escapedFile, entry]; + } + } + } + [html appendString:@"
\n"]; + [html appendString:@"\n"]; + return [GCDWebServerDataResponse responseWithHTML:html]; +} + +- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { + if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) { + GCDWebServer* __unsafe_unretained server = self; + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:@"GET"]) { + return nil; + } + if (![urlPath hasPrefix:basePath]) { + return nil; + } + return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + } + processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) { + GCDWebServerResponse* response = nil; + NSString* filePath = [directoryPath stringByAppendingPathComponent:GCDWebServerNormalizePath([request.path substringFromIndex:basePath.length])]; + NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType]; + if (fileType) { + if ([fileType isEqualToString:NSFileTypeDirectory]) { + if (indexFilename) { + NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename]; + NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType]; + if ([indexType isEqualToString:NSFileTypeRegular]) { + return [GCDWebServerFileResponse responseWithFile:indexPath]; + } + } + response = [server _responseWithContentsOfDirectory:filePath]; + } else if ([fileType isEqualToString:NSFileTypeRegular]) { + if (allowRangeRequests) { + response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange]; + [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; + } else { + response = [GCDWebServerFileResponse responseWithFile:filePath]; + } + } + } + if (response) { + response.cacheControlMaxAge = cacheAge; + } else { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound]; + } + return response; + }]; + } else { + GWS_DNOT_REACHED(); + } +} + +@end + +@implementation GCDWebServer (Logging) + ++ (void)setLogLevel:(int)level { +#if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__) + [XLSharedFacility setMinLogLevel:level]; +#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__) + GCDWebServerLogLevel = level; +#endif +} + ++ (void)setBuiltInLogger:(GCDWebServerBuiltInLoggerBlock)block { +#if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__) + _builtInLoggerBlock = block; +#else + GWS_DNOT_REACHED(); // Built-in logger must be enabled in order to override +#endif +} + +- (void)logVerbose:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_VERBOSE(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +- (void)logInfo:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_INFO(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +- (void)logWarning:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_WARNING(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +- (void)logError:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_ERROR(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +@end + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + +@implementation GCDWebServer (Testing) + +- (void)setRecordingEnabled:(BOOL)flag { + _recording = flag; +} + +- (BOOL)isRecordingEnabled { + return _recording; +} + +static CFHTTPMessageRef _CreateHTTPMessageFromData(NSData* data, BOOL isRequest) { + CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, isRequest); + if (CFHTTPMessageAppendBytes(message, data.bytes, data.length)) { + return message; + } + CFRelease(message); + return NULL; +} + +static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData, NSUInteger port) { + CFHTTPMessageRef response = NULL; + int httpSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (httpSocket > 0) { + struct sockaddr_in addr4; + bzero(&addr4, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + addr4.sin_addr.s_addr = htonl(INADDR_ANY); + if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) { + if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) { + NSMutableData* outData = [[NSMutableData alloc] initWithLength:(256 * 1024)]; + NSUInteger length = 0; + while (1) { + ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length); + if (result < 0) { + length = NSUIntegerMax; + break; + } else if (result == 0) { + break; + } + length += result; + if (length >= outData.length) { + outData.length = 2 * outData.length; + } + } + if (length != NSUIntegerMax) { + outData.length = length; + response = _CreateHTTPMessageFromData(outData, NO); + } else { + GWS_DNOT_REACHED(); + } + } + } + close(httpSocket); + } + return response; +} + +static void _LogResult(NSString* format, ...) { + va_list arguments; + va_start(arguments, format); + NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; + va_end(arguments); + fprintf(stdout, "%s\n", [message UTF8String]); +} + +- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path { + GWS_DCHECK([NSThread isMainThread]); + NSArray* ignoredHeaders = @[ @"Date", @"Etag" ]; // Dates are always different by definition and ETags depend on file system node IDs + NSInteger result = -1; + if ([self startWithOptions:options error:NULL]) { + _ExecuteMainThreadRunLoopSources(); + + result = 0; + NSArray* files = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)]; + for (NSString* requestFile in files) { + if (![requestFile hasSuffix:@".request"]) { + continue; + } + @autoreleasepool { + NSString* index = [[requestFile componentsSeparatedByString:@"-"] firstObject]; + BOOL success = NO; + NSData* requestData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:requestFile]]; + if (requestData) { + CFHTTPMessageRef request = _CreateHTTPMessageFromData(requestData, YES); + if (request) { + NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(request)); + NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(request)); + _LogResult(@"[%i] %@ %@", (int)[index integerValue], requestMethod, requestURL.path); + NSString* prefix = [index stringByAppendingString:@"-"]; + for (NSString* responseFile in files) { + if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) { + NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]]; + if (responseData) { + CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO); + if (expectedResponse) { + CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port); + if (actualResponse) { + success = YES; + + CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse); + CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse); + if (actualStatusCode != expectedStatusCode) { + _LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode); + success = NO; + } + + NSDictionary* expectedHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(expectedResponse)); + NSDictionary* actualHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(actualResponse)); + for (NSString* expectedHeader in expectedHeaders) { + if ([ignoredHeaders containsObject:expectedHeader]) { + continue; + } + NSString* expectedValue = [expectedHeaders objectForKey:expectedHeader]; + NSString* actualValue = [actualHeaders objectForKey:expectedHeader]; + if (![actualValue isEqualToString:expectedValue]) { + _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", expectedHeader, expectedValue, actualValue); + success = NO; + } + } + for (NSString* actualHeader in actualHeaders) { + if (![expectedHeaders objectForKey:actualHeader]) { + _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", actualHeader, nil, [actualHeaders objectForKey:actualHeader]); + success = NO; + } + } + + NSString* expectedContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length"))); + NSData* expectedBody = CFBridgingRelease(CFHTTPMessageCopyBody(expectedResponse)); + NSString* actualContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length"))); + NSData* actualBody = CFBridgingRelease(CFHTTPMessageCopyBody(actualResponse)); + if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file) + actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)]; + } + if ((actualBody && expectedBody && ![actualBody isEqualToData:expectedBody]) || (actualBody && !expectedBody) || (!actualBody && expectedBody)) { + _LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length); + success = NO; +#if !TARGET_OS_IPHONE +#if DEBUG + if (GCDWebServerIsTextContentType((NSString*)[expectedHeaders objectForKey:@"Content-Type"])) { + NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; + NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; + if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) { + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath:@"/usr/bin/opendiff"]; + [task setArguments:@[ expectedPath, actualPath ]]; + [task launch]; + } + } +#endif +#endif + } + + CFRelease(actualResponse); + } + CFRelease(expectedResponse); + } + } else { + GWS_DNOT_REACHED(); + } + break; + } + } + CFRelease(request); + } + } else { + GWS_DNOT_REACHED(); + } + _LogResult(@""); + if (!success) { + ++result; + } + } + _ExecuteMainThreadRunLoopSources(); + } + + [self stop]; + + _ExecuteMainThreadRunLoopSources(); + } + return result; +} + +@end + +#endif diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.h b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.h new file mode 100644 index 00000000..4d59b9f1 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.h @@ -0,0 +1,183 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServer.h" + +NS_ASSUME_NONNULL_BEGIN + +@class GCDWebServerHandler; + +/** + * The GCDWebServerConnection class is instantiated by GCDWebServer to handle + * each new HTTP connection. Each instance stays alive until the connection is + * closed. + * + * You cannot use this class directly, but it is made public so you can + * subclass it to override some hooks. Use the GCDWebServerOption_ConnectionClass + * option for GCDWebServer to install your custom subclass. + * + * @warning The GCDWebServerConnection retains the GCDWebServer until the + * connection is closed. + */ +@interface GCDWebServerConnection : NSObject + +/** + * Returns the GCDWebServer that owns the connection. + */ +@property(nonatomic, readonly) GCDWebServer* server; + +/** + * Returns YES if the connection is using IPv6. + */ +@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6; + +/** + * Returns the address of the local peer (i.e. server) of the connection + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* localAddressData; + +/** + * Returns the address of the local peer (i.e. server) of the connection + * as a string. + */ +@property(nonatomic, readonly) NSString* localAddressString; + +/** + * Returns the address of the remote peer (i.e. client) of the connection + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* remoteAddressData; + +/** + * Returns the address of the remote peer (i.e. client) of the connection + * as a string. + */ +@property(nonatomic, readonly) NSString* remoteAddressString; + +/** + * Returns the total number of bytes received from the remote peer (i.e. client) + * so far. + */ +@property(nonatomic, readonly) NSUInteger totalBytesRead; + +/** + * Returns the total number of bytes sent to the remote peer (i.e. client) so far. + */ +@property(nonatomic, readonly) NSUInteger totalBytesWritten; + +@end + +/** + * Hooks to customize the behavior of GCDWebServer HTTP connections. + * + * @warning These methods can be called on any GCD thread. + * Be sure to also call "super" when overriding them. + */ +@interface GCDWebServerConnection (Subclassing) + +/** + * This method is called when the connection is opened. + * + * Return NO to reject the connection e.g. after validating the local + * or remote address. + */ +- (BOOL)open; + +/** + * This method is called whenever data has been received + * from the remote peer (i.e. client). + * + * @warning Do not attempt to modify this data. + */ +- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; + +/** + * This method is called whenever data has been sent + * to the remote peer (i.e. client). + * + * @warning Do not attempt to modify this data. + */ +- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; + +/** + * This method is called after the HTTP headers have been received to + * allow replacing the request URL by another one. + * + * The default implementation returns the original URL. + */ +- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers; + +/** + * Assuming a valid HTTP request was received, this method is called before + * the request is processed. + * + * Return a non-nil GCDWebServerResponse to bypass the request processing entirely. + * + * The default implementation checks for HTTP authentication if applicable + * and returns a barebone 401 status code response if authentication failed. + */ +- (nullable GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request; + +/** + * Assuming a valid HTTP request was received and -preflightRequest: returned nil, + * this method is called to process the request by executing the handler's + * process block. + */ +- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion; + +/** + * Assuming a valid HTTP request was received and either -preflightRequest: + * or -processRequest:completion: returned a non-nil GCDWebServerResponse, + * this method is called to override the response. + * + * You can either modify the current response and return it, or return a + * completely new one. + * + * The default implementation replaces any response matching the "ETag" or + * "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) + * one. + */ +- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request; + +/** + * This method is called if any error happens while validing or processing + * the request or if no GCDWebServerResponse was generated during processing. + * + * @warning If the request was invalid (e.g. the HTTP headers were malformed), + * the "request" argument will be nil. + */ +- (void)abortRequest:(nullable GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; + +/** + * Called when the connection is closed. + */ +- (void)close; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.m b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.m new file mode 100644 index 00000000..b48edc61 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.m @@ -0,0 +1,843 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import +#import +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ +#import +#endif + +#import "GCDWebServerPrivate.h" + +#define kHeadersReadCapacity (1 * 1024) +#define kBodyReadCapacity (256 * 1024) + +typedef void (^ReadDataCompletionBlock)(BOOL success); +typedef void (^ReadHeadersCompletionBlock)(NSData* extraData); +typedef void (^ReadBodyCompletionBlock)(BOOL success); + +typedef void (^WriteDataCompletionBlock)(BOOL success); +typedef void (^WriteHeadersCompletionBlock)(BOOL success); +typedef void (^WriteBodyCompletionBlock)(BOOL success); + +static NSData* _CRLFData = nil; +static NSData* _CRLFCRLFData = nil; +static NSData* _continueData = nil; +static NSData* _lastChunkData = nil; +static NSString* _digestAuthenticationNonce = nil; +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ +static int32_t _connectionCounter = 0; +#endif + +NS_ASSUME_NONNULL_BEGIN + +@interface GCDWebServerConnection (Read) +- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block; +- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block; +- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block; +- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block; +@end + +@interface GCDWebServerConnection (Write) +- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block; +- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block; +- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block; +@end + +NS_ASSUME_NONNULL_END + +@implementation GCDWebServerConnection { + CFSocketNativeHandle _socket; + BOOL _virtualHEAD; + + CFHTTPMessageRef _requestMessage; + GCDWebServerRequest* _request; + GCDWebServerHandler* _handler; + CFHTTPMessageRef _responseMessage; + GCDWebServerResponse* _response; + NSInteger _statusCode; + + BOOL _opened; +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + NSUInteger _connectionIndex; + NSString* _requestPath; + int _requestFD; + NSString* _responsePath; + int _responseFD; +#endif +} + ++ (void)initialize { + if (_CRLFData == nil) { + _CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2]; + GWS_DCHECK(_CRLFData); + } + if (_CRLFCRLFData == nil) { + _CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; + GWS_DCHECK(_CRLFCRLFData); + } + if (_continueData == nil) { + CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1); + _continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); + CFRelease(message); + GWS_DCHECK(_continueData); + } + if (_lastChunkData == nil) { + _lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5]; + } + if (_digestAuthenticationNonce == nil) { + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + _digestAuthenticationNonce = GCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid))); + CFRelease(uuid); + } +} + +- (BOOL)isUsingIPv6 { + const struct sockaddr* localSockAddr = _localAddressData.bytes; + return (localSockAddr->sa_family == AF_INET6); +} + +- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode { + _statusCode = statusCode; + _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close")); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (__bridge CFStringRef)_server.serverName); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (__bridge CFStringRef)GCDWebServerFormatRFC822([NSDate date])); +} + +- (void)_startProcessingRequest { + GWS_DCHECK(_responseMessage == NULL); + + GCDWebServerResponse* preflightResponse = [self preflightRequest:_request]; + if (preflightResponse) { + [self _finishProcessingRequest:preflightResponse]; + } else { + [self processRequest:_request + completion:^(GCDWebServerResponse* processResponse) { + [self _finishProcessingRequest:processResponse]; + }]; + } +} + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +- (void)_finishProcessingRequest:(GCDWebServerResponse*)response { + GWS_DCHECK(_responseMessage == NULL); + BOOL hasBody = NO; + + if (response) { + response = [self overrideResponse:response forRequest:_request]; + } + if (response) { + if ([response hasBody]) { + [response prepareForReading]; + hasBody = !_virtualHEAD; + } + NSError* error = nil; + if (hasBody && ![response performOpen:&error]) { + GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error); + } else { + _response = response; + } + } + + if (_response) { + [self _initializeResponseHeadersWithStatusCode:_response.statusCode]; + if (_response.lastModifiedDate) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822((NSDate*)_response.lastModifiedDate)); + } + if (_response.eTag) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag); + } + if ((_response.statusCode >= 200) && (_response.statusCode < 300)) { + if (_response.cacheControlMaxAge > 0) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]); + } else { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache")); + } + } + if (_response.contentType != nil) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType)); + } + if (_response.contentLength != NSUIntegerMax) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]); + } + if (_response.usesChunkedTransferEncoding) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked")); + } + [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) { + CFHTTPMessageSetHeaderFieldValue(self->_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + [self writeHeadersWithCompletionBlock:^(BOOL success) { + if (success) { + if (hasBody) { + [self writeBodyWithCompletionBlock:^(BOOL successInner) { + [self->_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent + }]; + } + } else if (hasBody) { + [self->_response performClose]; + } + }]; + } else { + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } +} + +- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData { + NSError* error = nil; + if (![_request performOpen:&error]) { + GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + return; + } + + if (initialData.length) { + if (![_request performWriteData:initialData error:&error]) { + GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); + if (![_request performClose:&error]) { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); + } + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + return; + } + length -= initialData.length; + } + + if (length) { + [self readBodyWithRemainingLength:length + completionBlock:^(BOOL success) { + NSError* localError = nil; + if ([self->_request performClose:&localError]) { + [self _startProcessingRequest]; + } else { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + }]; + } else { + if ([_request performClose:&error]) { + [self _startProcessingRequest]; + } else { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + } +} + +- (void)_readChunkedBodyWithInitialData:(NSData*)initialData { + NSError* error = nil; + if (![_request performOpen:&error]) { + GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + return; + } + + NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData]; + [self readNextBodyChunk:chunkData + completionBlock:^(BOOL success) { + NSError* localError = nil; + if ([self->_request performClose:&localError]) { + [self _startProcessingRequest]; + } else { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + }]; +} + +- (void)_readRequestHeaders { + _requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true); + NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity]; + [self readHeaders:headersData + withCompletionBlock:^(NSData* extraData) { + if (extraData) { + NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(self->_requestMessage)); // Method verbs are case-sensitive and uppercase + if (self->_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) { + requestMethod = @"GET"; + self->_virtualHEAD = YES; + } + NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self->_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones + NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(self->_requestMessage)); + if (requestURL) { + requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders]; + GWS_DCHECK(requestURL); + } + NSString* urlPath = requestURL ? CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL)) : nil; // Don't use -[NSURL path] which strips the ending slash + if (urlPath == nil) { + urlPath = @"/"; // CFURLCopyPath() returns NULL for a relative URL with path "//" contrary to -[NSURL path] which returns "/" + } + NSString* requestPath = urlPath ? GCDWebServerUnescapeURLString(urlPath) : nil; + NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped; + NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{}; + if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) { + for (self->_handler in self->_server.handlers) { + self->_request = self->_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery); + if (self->_request) { + break; + } + } + if (self->_request) { + self->_request.localAddressData = self.localAddressData; + self->_request.remoteAddressData = self.remoteAddressData; + if ([self->_request hasBody]) { + [self->_request prepareForWriting]; + if (self->_request.usesChunkedTransferEncoding || (extraData.length <= self->_request.contentLength)) { + NSString* expectHeader = [requestHeaders objectForKey:@"Expect"]; + if (expectHeader) { + if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing + [self writeData:_continueData + withCompletionBlock:^(BOOL success) { + if (success) { + if (self->_request.usesChunkedTransferEncoding) { + [self _readChunkedBodyWithInitialData:extraData]; + } else { + [self _readBodyWithLength:self->_request.contentLength initialData:extraData]; + } + } + }]; + } else { + GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", self->_socket); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed]; + } + } else { + if (self->_request.usesChunkedTransferEncoding) { + [self _readChunkedBodyWithInitialData:extraData]; + } else { + [self _readBodyWithLength:self->_request.contentLength initialData:extraData]; + } + } + } else { + GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", self->_socket); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest]; + } + } else { + [self _startProcessingRequest]; + } + } else { + self->_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery]; + GWS_DCHECK(self->_request); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_NotImplemented]; + } + } else { + [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + GWS_DNOT_REACHED(); + } + } else { + [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + }]; +} + +- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket { + if ((self = [super init])) { + _server = server; + _localAddressData = localAddress; + _remoteAddressData = remoteAddress; + _socket = socket; + GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket); + + [_server willStartConnection:self]; + + if (![self open]) { + close(_socket); + return nil; + } + _opened = YES; + + [self _readRequestHeaders]; + } + return self; +} + +- (NSString*)localAddressString { + return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES); +} + +- (NSString*)remoteAddressString { + return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES); +} + +- (void)dealloc { + int result = close(_socket); + if (result != 0) { + GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno); + } else { + GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket); + } + + if (_opened) { + [self close]; + } + + [_server didEndConnection:self]; + + if (_requestMessage) { + CFRelease(_requestMessage); + } + + if (_responseMessage) { + CFRelease(_responseMessage); + } +} + +@end + +@implementation GCDWebServerConnection (Read) + +- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block { + dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) { + @autoreleasepool { + if (error == 0) { + size_t size = dispatch_data_get_size(buffer); + if (size > 0) { + NSUInteger originalLength = data.length; + dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { + [data appendBytes:chunkBytes length:chunkSize]; + return true; + }); + [self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)]; + block(YES); + } else { + if (self->_totalBytesRead > 0) { + GWS_LOG_ERROR(@"No more data available on socket %i", self->_socket); + } else { + GWS_LOG_WARNING(@"No data received from socket %i", self->_socket); + } + block(NO); + } + } else { + GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", self->_socket, strerror(error), error); + block(NO); + } + } + }); +} + +- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block { + GWS_DCHECK(_requestMessage); + [self readData:headersData + withLength:NSUIntegerMax + completionBlock:^(BOOL success) { + if (success) { + NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)]; + if (range.location == NSNotFound) { + [self readHeaders:headersData withCompletionBlock:block]; + } else { + NSUInteger length = range.location + range.length; + if (CFHTTPMessageAppendBytes(self->_requestMessage, headersData.bytes, length)) { + if (CFHTTPMessageIsHeaderComplete(self->_requestMessage)) { + block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]); + } else { + GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", self->_socket); + block(nil); + } + } else { + GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", self->_socket); + block(nil); + } + } + } else { + block(nil); + } + }]; +} + +- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block { + GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]); + NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity]; + [self readData:bodyData + withLength:length + completionBlock:^(BOOL success) { + if (success) { + if (bodyData.length <= length) { + NSError* error = nil; + if ([self->_request performWriteData:bodyData error:&error]) { + NSUInteger remainingLength = length - bodyData.length; + if (remainingLength) { + [self readBodyWithRemainingLength:remainingLength completionBlock:block]; + } else { + block(YES); + } + } else { + GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", self->_socket, error); + block(NO); + } + } else { + GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", self->_socket); + block(NO); + GWS_DNOT_REACHED(); + } + } else { + block(NO); + } + }]; +} + +static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { + char buffer[size + 1]; + bcopy(bytes, buffer, size); + buffer[size] = 0; + char* end = NULL; + long result = strtol(buffer, &end, 16); + return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound); +} + +- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block { + GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]); + + while (1) { + NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)]; + if (range.location == NSNotFound) { + break; + } + NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions + NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location); + if (length != NSNotFound) { + if (length) { + if (chunkData.length < range.location + range.length + length + 2) { + break; + } + const char* ptr = (char*)chunkData.bytes + range.location + range.length + length; + if ((*ptr == '\r') && (*(ptr + 1) == '\n')) { + NSError* error = nil; + if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) { + [chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0]; + } else { + GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); + block(NO); + return; + } + } else { + GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket); + block(NO); + return; + } + } else { + NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers + if (trailerRange.location != NSNotFound) { + block(YES); + return; + } + } + } else { + GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket); + block(NO); + return; + } + } + + [self readData:chunkData + withLength:NSUIntegerMax + completionBlock:^(BOOL success) { + if (success) { + [self readNextBodyChunk:chunkData completionBlock:block]; + } else { + block(NO); + } + }]; +} + +@end + +@implementation GCDWebServerConnection (Write) + +- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block { + dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{ + [data self]; // Keeps ARC from releasing data too early + }); + dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) { + @autoreleasepool { + if (error == 0) { + GWS_DCHECK(remainingData == NULL); + [self didWriteBytes:data.bytes length:data.length]; + block(YES); + } else { + GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", self->_socket, strerror(error), error); + block(NO); + } + } + }); +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(buffer); +#endif +} + +- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block { + GWS_DCHECK(_responseMessage); + CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage); + [self writeData:(__bridge NSData*)data withCompletionBlock:block]; + CFRelease(data); +} + +- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block { + GWS_DCHECK([_response hasBody]); + [_response performReadDataWithCompletion:^(NSData* data, NSError* error) { + if (data) { + if (data.length) { + if (self->_response.usesChunkedTransferEncoding) { + const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String]; + size_t hexLength = strlen(hexString); + NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)]; + if (chunk == nil) { + GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", self->_socket, error); + block(NO); + return; + } + char* ptr = (char*)[(NSMutableData*)chunk mutableBytes]; + bcopy(hexString, ptr, hexLength); + ptr += hexLength; + *ptr++ = '\r'; + *ptr++ = '\n'; + bcopy(data.bytes, ptr, data.length); + ptr += data.length; + *ptr++ = '\r'; + *ptr = '\n'; + data = chunk; + } + [self writeData:data + withCompletionBlock:^(BOOL success) { + if (success) { + [self writeBodyWithCompletionBlock:block]; + } else { + block(NO); + } + }]; + } else { + if (self->_response.usesChunkedTransferEncoding) { + [self writeData:_lastChunkData + withCompletionBlock:^(BOOL success) { + block(success); + }]; + } else { + block(YES); + } + } + } else { + GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", self->_socket, error); + block(NO); + } + }]; +} + +@end + +@implementation GCDWebServerConnection (Subclassing) + +- (BOOL)open { +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if (_server.recordingEnabled) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + _connectionIndex = OSAtomicIncrement32(&_connectionCounter); +#pragma clang diagnostic pop + + _requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + _requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + GWS_DCHECK(_requestFD > 0); + + _responsePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + _responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + GWS_DCHECK(_responseFD > 0); + } +#endif + + return YES; +} + +- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length { + GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket); + _totalBytesRead += length; + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) { + GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno); + close(_requestFD); + _requestFD = 0; + } +#endif +} + +- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length { + GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket); + _totalBytesWritten += length; + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) { + GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno); + close(_responseFD); + _responseFD = 0; + } +#endif +} + +- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers { + return url; +} + +// https://tools.ietf.org/html/rfc2617 +- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request { + GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead); + GCDWebServerResponse* response = nil; + if (_server.authenticationBasicAccounts) { + __block BOOL authenticated = NO; + NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; + if ([authorizationHeader hasPrefix:@"Basic "]) { + NSString* basicAccount = [authorizationHeader substringFromIndex:6]; + [_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) { + if ([basicAccount isEqualToString:digest]) { + authenticated = YES; + *stop = YES; + } + }]; + } + if (!authenticated) { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized]; + [response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"]; + } + } else if (_server.authenticationDigestAccounts) { + BOOL authenticated = NO; + BOOL isStaled = NO; + NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; + if ([authorizationHeader hasPrefix:@"Digest "]) { + NSString* realm = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm"); + if (realm && [_server.authenticationRealm isEqualToString:realm]) { + NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce"); + if ([nonce isEqualToString:_digestAuthenticationNonce]) { + NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username"); + NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri"); + NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response"); + NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username]; + NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required + NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2); + if ([actualResponse isEqualToString:expectedResponse]) { + authenticated = YES; + } + } else if (nonce.length) { + isStaled = YES; + } + } + } + if (!authenticated) { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized]; + [response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop") + } + } + return response; +} + +- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion { + GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead); + _handler.asyncProcessBlock(request, [completion copy]); +} + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 +static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) { + if (requestLastModified && responseLastModified) { + if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) { + return YES; + } + } + if (requestETag && responseETag) { // Per the specs "If-None-Match" must be checked after "If-Modified-Since" + if ([requestETag isEqualToString:@"*"]) { + return YES; + } + if ([responseETag isEqualToString:requestETag]) { + return YES; + } + } + return NO; +} + +- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request { + if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) { + NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed; + GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code]; + newResponse.cacheControlMaxAge = response.cacheControlMaxAge; + newResponse.lastModifiedDate = response.lastModifiedDate; + newResponse.eTag = response.eTag; + GWS_DCHECK(newResponse); + return newResponse; + } + return response; +} + +- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode { + GWS_DCHECK(_responseMessage == NULL); + GWS_DCHECK((statusCode >= 400) && (statusCode < 600)); + [self _initializeResponseHeadersWithStatusCode:statusCode]; + [self writeHeadersWithCompletionBlock:^(BOOL success) { + ; // Nothing more to do + }]; + GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket); +} + +- (void)close { +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if (_requestPath) { + BOOL success = NO; + NSError* error = nil; + if (_requestFD > 0) { + close(_requestFD); + NSString* name = [NSString stringWithFormat:@"%03lu-%@.request", (unsigned long)_connectionIndex, _virtualHEAD ? @"HEAD" : _request.method]; + success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; + } + if (!success) { + GWS_LOG_ERROR(@"Failed saving recorded request: %@", error); + GWS_DNOT_REACHED(); + } + unlink([_requestPath fileSystemRepresentation]); + } + + if (_responsePath) { + BOOL success = NO; + NSError* error = nil; + if (_responseFD > 0) { + close(_responseFD); + NSString* name = [NSString stringWithFormat:@"%03lu-%i.response", (unsigned long)_connectionIndex, (int)_statusCode]; + success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; + } + if (!success) { + GWS_LOG_ERROR(@"Failed saving recorded response: %@", error); + GWS_DNOT_REACHED(); + } + unlink([_responsePath fileSystemRepresentation]); + } +#endif + + if (_request) { + GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten); + } else { + GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten); + } +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.h b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.h new file mode 100644 index 00000000..217fb143 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.h @@ -0,0 +1,114 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Converts a file extension to the corresponding MIME type. + * If there is no match, "application/octet-stream" is returned. + * + * Overrides allow to customize the built-in mapping from extensions to MIME + * types. Keys of the dictionary must be lowercased file extensions without + * the period, and the values must be the corresponding MIME types. + */ +NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides); + +/** + * Add percent-escapes to a string so it can be used in a URL. + * The legal characters ":@/?&=+" are also escaped to ensure compatibility + * with URL encoded forms and URL queries. + */ +NSString* _Nullable GCDWebServerEscapeURLString(NSString* string); + +/** + * Unescapes a URL percent-encoded string. + */ +NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string); + +/** + * Extracts the unescaped names and values from an + * "application/x-www-form-urlencoded" form. + * http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 + */ +NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form); + +/** + * On OS X, returns the IPv4 or IPv6 address as a string of the primary + * connected service or nil if not available. + * + * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi + * interface if connected or nil otherwise. + */ +NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6); + +/** + * Converts a date into a string using RFC822 formatting. + * https://tools.ietf.org/html/rfc822#section-5 + * https://tools.ietf.org/html/rfc1123#section-5.2.14 + */ +NSString* GCDWebServerFormatRFC822(NSDate* date); + +/** + * Converts a RFC822 formatted string into a date. + * https://tools.ietf.org/html/rfc822#section-5 + * https://tools.ietf.org/html/rfc1123#section-5.2.14 + * + * @warning Timezones other than GMT are not supported by this function. + */ +NSDate* _Nullable GCDWebServerParseRFC822(NSString* string); + +/** + * Converts a date into a string using IOS 8601 formatting. + * http://tools.ietf.org/html/rfc3339#section-5.6 + */ +NSString* GCDWebServerFormatISO8601(NSDate* date); + +/** + * Converts a ISO 8601 formatted string into a date. + * http://tools.ietf.org/html/rfc3339#section-5.6 + * + * @warning Only "calendar" variant is supported at this time and timezones + * other than GMT are not supported either. + */ +NSDate* _Nullable GCDWebServerParseISO8601(NSString* string); + +/** + * Removes "//", "/./" and "/../" components from path as well as any trailing slash. + */ +NSString* GCDWebServerNormalizePath(NSString* path); + +#ifdef __cplusplus +} +#endif + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.m b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.m new file mode 100644 index 00000000..7787884d --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.m @@ -0,0 +1,331 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import +#if TARGET_OS_IPHONE +#import +#else +#import +#endif +#import + +#import +#import +#import + +#import "GCDWebServerPrivate.h" + +static NSDateFormatter* _dateFormatterRFC822 = nil; +static NSDateFormatter* _dateFormatterISO8601 = nil; +static dispatch_queue_t _dateFormatterQueue = NULL; + +// TODO: Handle RFC 850 and ANSI C's asctime() format +void GCDWebServerInitializeFunctions() { + GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread + if (_dateFormatterRFC822 == nil) { + _dateFormatterRFC822 = [[NSDateFormatter alloc] init]; + _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"; + _dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + GWS_DCHECK(_dateFormatterRFC822); + } + if (_dateFormatterISO8601 == nil) { + _dateFormatterISO8601 = [[NSDateFormatter alloc] init]; + _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'"; + _dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + GWS_DCHECK(_dateFormatterISO8601); + } + if (_dateFormatterQueue == NULL) { + _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + GWS_DCHECK(_dateFormatterQueue); + } +} + +NSString* GCDWebServerNormalizeHeaderValue(NSString* value) { + if (value) { + NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive + if (range.location != NSNotFound) { + value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]]; + } else { + value = [value lowercaseString]; + } + } + return value; +} + +NSString* GCDWebServerTruncateHeaderValue(NSString* value) { + if (value) { + NSRange range = [value rangeOfString:@";"]; + if (range.location != NSNotFound) { + return [value substringToIndex:range.location]; + } + } + return value; +} + +NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { + NSString* parameter = nil; + if (value) { + NSScanner* scanner = [[NSScanner alloc] initWithString:value]; + [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive + NSString* string = [NSString stringWithFormat:@"%@=", name]; + if ([scanner scanUpToString:string intoString:NULL]) { + [scanner scanString:string intoString:NULL]; + if ([scanner scanString:@"\"" intoString:NULL]) { + [scanner scanUpToString:@"\"" intoString:¶meter]; + } else { + [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter]; + } + } + } + return parameter; +} + +// http://www.w3schools.com/tags/ref_charactersets.asp +NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) { + NSStringEncoding encoding = kCFStringEncodingInvalidId; + if (charset) { + encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset)); + } + return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding); +} + +NSString* GCDWebServerFormatRFC822(NSDate* date) { + __block NSString* string; + dispatch_sync(_dateFormatterQueue, ^{ + string = [_dateFormatterRFC822 stringFromDate:date]; + }); + return string; +} + +NSDate* GCDWebServerParseRFC822(NSString* string) { + __block NSDate* date; + dispatch_sync(_dateFormatterQueue, ^{ + date = [_dateFormatterRFC822 dateFromString:string]; + }); + return date; +} + +NSString* GCDWebServerFormatISO8601(NSDate* date) { + __block NSString* string; + dispatch_sync(_dateFormatterQueue, ^{ + string = [_dateFormatterISO8601 stringFromDate:date]; + }); + return string; +} + +NSDate* GCDWebServerParseISO8601(NSString* string) { + __block NSDate* date; + dispatch_sync(_dateFormatterQueue, ^{ + date = [_dateFormatterISO8601 dateFromString:string]; + }); + return date; +} + +BOOL GCDWebServerIsTextContentType(NSString* type) { + return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]); +} + +NSString* GCDWebServerDescribeData(NSData* data, NSString* type) { + if (GCDWebServerIsTextContentType(type)) { + NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset"); + NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + if (string) { + return string; + } + } + return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length]; +} + +NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* overrides) { + NSDictionary* builtInOverrides = @{@"css" : @"text/css"}; + NSString* mimeType = nil; + extension = [extension lowercaseString]; + if (extension.length) { + mimeType = [overrides objectForKey:extension]; + if (mimeType == nil) { + mimeType = [builtInOverrides objectForKey:extension]; + } + if (mimeType == nil) { + CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); + if (uti) { + mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); + CFRelease(uti); + } + } + } + return mimeType ? mimeType : kGCDWebServerDefaultMimeType; +} + +NSString* GCDWebServerEscapeURLString(NSString* string) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8)); +#pragma clang diagnostic pop +} + +NSString* GCDWebServerUnescapeURLString(NSString* string) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8)); +#pragma clang diagnostic pop +} + +NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) { + NSMutableDictionary* parameters = [NSMutableDictionary dictionary]; + NSScanner* scanner = [[NSScanner alloc] initWithString:form]; + [scanner setCharactersToBeSkipped:nil]; + while (1) { + NSString* key = nil; + if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) { + break; + } + [scanner setScanLocation:([scanner scanLocation] + 1)]; + + NSString* value = nil; + [scanner scanUpToString:@"&" intoString:&value]; + if (value == nil) { + value = @""; + } + + key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil; + value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil; + if (unescapedKey && unescapedValue) { + [parameters setObject:unescapedValue forKey:unescapedKey]; + } else { + GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value); + GWS_DNOT_REACHED(); + } + + if ([scanner isAtEnd]) { + break; + } + [scanner setScanLocation:([scanner scanLocation] + 1)]; + } + return parameters; +} + +NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) { + char hostBuffer[NI_MAXHOST]; + char serviceBuffer[NI_MAXSERV]; + if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) { +#if DEBUG + GWS_DNOT_REACHED(); +#else + return @""; +#endif + } + return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer]; +} + +NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) { + NSString* address = nil; +#if TARGET_OS_IPHONE +#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV + const char* primaryInterface = "en0"; // WiFi interface on iOS +#endif +#else + const char* primaryInterface = NULL; + SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL); + if (store) { + CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same + if (info) { + NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]; + if (interface) { + primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool + } + CFRelease(info); + } + CFRelease(store); + } + if (primaryInterface == NULL) { + primaryInterface = "lo0"; + } +#endif + struct ifaddrs* list; + if (getifaddrs(&list) >= 0) { + for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) { +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV + // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator + // Assumption holds for Apple TV running tvOS + if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1")) +#else + if (strcmp(ifap->ifa_name, primaryInterface)) +#endif + { + continue; + } + if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) { + address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO); + break; + } + } + freeifaddrs(list); + } + return address; +} + +NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) { + va_list arguments; + va_start(arguments, format); + const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String]; + va_end(arguments); + unsigned char md5[CC_MD5_DIGEST_LENGTH]; + CC_MD5(string, (CC_LONG)strlen(string), md5); + char buffer[2 * CC_MD5_DIGEST_LENGTH + 1]; + for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) { + unsigned char byte = md5[i]; + unsigned char byteHi = (byte & 0xF0) >> 4; + buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi; + unsigned char byteLo = byte & 0x0F; + buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo; + } + buffer[2 * CC_MD5_DIGEST_LENGTH] = 0; + return (NSString*)[NSString stringWithUTF8String:buffer]; +} + +NSString* GCDWebServerNormalizePath(NSString* path) { + NSMutableArray* components = [[NSMutableArray alloc] init]; + for (NSString* component in [path componentsSeparatedByString:@"/"]) { + if ([component isEqualToString:@".."]) { + [components removeLastObject]; + } else if (component.length && ![component isEqualToString:@"."]) { + [components addObject:component]; + } + } + if (path.length && ([path characterAtIndex:0] == '/')) { + return [@"/" stringByAppendingString:[components componentsJoinedByString:@"/"]]; // Preserve initial slash + } + return [components componentsJoinedByString:@"/"]; +} diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h new file mode 100644 index 00000000..12b697e3 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h @@ -0,0 +1,116 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + +#import + +/** + * Convenience constants for "informational" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_Continue = 100, + kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101, + kGCDWebServerHTTPStatusCode_Processing = 102 +}; + +/** + * Convenience constants for "successful" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_OK = 200, + kGCDWebServerHTTPStatusCode_Created = 201, + kGCDWebServerHTTPStatusCode_Accepted = 202, + kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203, + kGCDWebServerHTTPStatusCode_NoContent = 204, + kGCDWebServerHTTPStatusCode_ResetContent = 205, + kGCDWebServerHTTPStatusCode_PartialContent = 206, + kGCDWebServerHTTPStatusCode_MultiStatus = 207, + kGCDWebServerHTTPStatusCode_AlreadyReported = 208 +}; + +/** + * Convenience constants for "redirection" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_MultipleChoices = 300, + kGCDWebServerHTTPStatusCode_MovedPermanently = 301, + kGCDWebServerHTTPStatusCode_Found = 302, + kGCDWebServerHTTPStatusCode_SeeOther = 303, + kGCDWebServerHTTPStatusCode_NotModified = 304, + kGCDWebServerHTTPStatusCode_UseProxy = 305, + kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307, + kGCDWebServerHTTPStatusCode_PermanentRedirect = 308 +}; + +/** + * Convenience constants for "client error" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_BadRequest = 400, + kGCDWebServerHTTPStatusCode_Unauthorized = 401, + kGCDWebServerHTTPStatusCode_PaymentRequired = 402, + kGCDWebServerHTTPStatusCode_Forbidden = 403, + kGCDWebServerHTTPStatusCode_NotFound = 404, + kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405, + kGCDWebServerHTTPStatusCode_NotAcceptable = 406, + kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407, + kGCDWebServerHTTPStatusCode_RequestTimeout = 408, + kGCDWebServerHTTPStatusCode_Conflict = 409, + kGCDWebServerHTTPStatusCode_Gone = 410, + kGCDWebServerHTTPStatusCode_LengthRequired = 411, + kGCDWebServerHTTPStatusCode_PreconditionFailed = 412, + kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413, + kGCDWebServerHTTPStatusCode_RequestURITooLong = 414, + kGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415, + kGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416, + kGCDWebServerHTTPStatusCode_ExpectationFailed = 417, + kGCDWebServerHTTPStatusCode_UnprocessableEntity = 422, + kGCDWebServerHTTPStatusCode_Locked = 423, + kGCDWebServerHTTPStatusCode_FailedDependency = 424, + kGCDWebServerHTTPStatusCode_UpgradeRequired = 426, + kGCDWebServerHTTPStatusCode_PreconditionRequired = 428, + kGCDWebServerHTTPStatusCode_TooManyRequests = 429, + kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431 +}; + +/** + * Convenience constants for "server error" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_InternalServerError = 500, + kGCDWebServerHTTPStatusCode_NotImplemented = 501, + kGCDWebServerHTTPStatusCode_BadGateway = 502, + kGCDWebServerHTTPStatusCode_ServiceUnavailable = 503, + kGCDWebServerHTTPStatusCode_GatewayTimeout = 504, + kGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505, + kGCDWebServerHTTPStatusCode_InsufficientStorage = 507, + kGCDWebServerHTTPStatusCode_LoopDetected = 508, + kGCDWebServerHTTPStatusCode_NotExtended = 510, + kGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511 +}; diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerPrivate.h b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerPrivate.h new file mode 100644 index 00000000..4668a978 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerPrivate.h @@ -0,0 +1,224 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import +#import + +/** + * All GCDWebServer headers. + */ + +#import "GCDWebServerHTTPStatusCodes.h" +#import "GCDWebServerFunctions.h" + +#import "GCDWebServer.h" +#import "GCDWebServerConnection.h" + +#import "GCDWebServerDataRequest.h" +#import "GCDWebServerFileRequest.h" +#import "GCDWebServerMultiPartFormRequest.h" +#import "GCDWebServerURLEncodedFormRequest.h" + +#import "GCDWebServerDataResponse.h" +#import "GCDWebServerErrorResponse.h" +#import "GCDWebServerFileResponse.h" +#import "GCDWebServerStreamedResponse.h" + +/** + * Check if a custom logging facility should be used instead. + */ + +#if defined(__GCDWEBSERVER_LOGGING_HEADER__) + +#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__ + +#import __GCDWEBSERVER_LOGGING_HEADER__ + +/** + * Automatically detect if XLFacility is available and if so use it as a + * logging facility. + */ + +#elif defined(__has_include) && __has_include("XLFacilityMacros.h") + +#define __GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__ + +#undef XLOG_TAG +#define XLOG_TAG @"gcdwebserver.internal" + +#import "XLFacilityMacros.h" + +#define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__) +#define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__) +#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__) +#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__) +#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__) + +#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__) +#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE() + +/** + * If all of the above fail, then use GCDWebServer built-in + * logging facility. + */ + +#else + +#define __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ + +typedef NS_ENUM(int, GCDWebServerLoggingLevel) { + kGCDWebServerLoggingLevel_Debug = 0, + kGCDWebServerLoggingLevel_Verbose, + kGCDWebServerLoggingLevel_Info, + kGCDWebServerLoggingLevel_Warning, + kGCDWebServerLoggingLevel_Error +}; + +extern GCDWebServerLoggingLevel GCDWebServerLogLevel; +extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* _Nonnull format, ...) NS_FORMAT_FUNCTION(2, 3); + +#if DEBUG +#define GWS_LOG_DEBUG(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); \ + } while (0) +#else +#define GWS_LOG_DEBUG(...) +#endif +#define GWS_LOG_VERBOSE(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); \ + } while (0) +#define GWS_LOG_INFO(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); \ + } while (0) +#define GWS_LOG_WARNING(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); \ + } while (0) +#define GWS_LOG_ERROR(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); \ + } while (0) + +#endif + +/** + * Consistency check macros used when building Debug only. + */ + +#if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED) + +#if DEBUG + +#define GWS_DCHECK(__CONDITION__) \ + do { \ + if (!(__CONDITION__)) { \ + abort(); \ + } \ + } while (0) +#define GWS_DNOT_REACHED() abort() + +#else + +#define GWS_DCHECK(__CONDITION__) +#define GWS_DNOT_REACHED() + +#endif + +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + * GCDWebServer internal constants and APIs. + */ + +#define kGCDWebServerDefaultMimeType @"application/octet-stream" +#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain" + +static inline BOOL GCDWebServerIsValidByteRange(NSRange range) { + return ((range.location != NSUIntegerMax) || (range.length > 0)); +} + +static inline NSError* GCDWebServerMakePosixError(int code) { + return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : (NSString*)[NSString stringWithUTF8String:strerror(code)]}]; +} + +extern void GCDWebServerInitializeFunctions(void); +extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value); +extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value); +extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute); +extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset); +extern BOOL GCDWebServerIsTextContentType(NSString* type); +extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType); +extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2); +extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService); + +@interface GCDWebServerConnection () +- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; +@end + +@interface GCDWebServer () +@property(nonatomic, readonly) NSMutableArray* handlers; +@property(nonatomic, readonly, nullable) NSString* serverName; +@property(nonatomic, readonly, nullable) NSString* authenticationRealm; +@property(nonatomic, readonly, nullable) NSMutableDictionary* authenticationBasicAccounts; +@property(nonatomic, readonly, nullable) NSMutableDictionary* authenticationDigestAccounts; +@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; +@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority; +- (void)willStartConnection:(GCDWebServerConnection*)connection; +- (void)didEndConnection:(GCDWebServerConnection*)connection; +@end + +@interface GCDWebServerHandler : NSObject +@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock; +@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock; +@end + +@interface GCDWebServerRequest () +@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; +@property(nonatomic) NSData* localAddressData; +@property(nonatomic) NSData* remoteAddressData; +- (void)prepareForWriting; +- (BOOL)performOpen:(NSError**)error; +- (BOOL)performWriteData:(NSData*)data error:(NSError**)error; +- (BOOL)performClose:(NSError**)error; +- (void)setAttribute:(nullable id)attribute forKey:(NSString*)key; +@end + +@interface GCDWebServerResponse () +@property(nonatomic, readonly) NSDictionary* additionalHeaders; +@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; +- (void)prepareForReading; +- (BOOL)performOpen:(NSError**)error; +- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block; +- (void)performClose; +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.h b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.h new file mode 100644 index 00000000..79b446a5 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.h @@ -0,0 +1,210 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest + * with the contents of any regular expression captures done on the request path. + * + * @warning This attribute will only be set on the request if adding a handler using + * -addHandlerForMethod:pathRegex:requestClass:processBlock:. + */ +extern NSString* const GCDWebServerRequestAttribute_RegexCaptures; + +/** + * This protocol is used by the GCDWebServerConnection to communicate with + * the GCDWebServerRequest and write the received HTTP body data. + * + * Note that multiple GCDWebServerBodyWriter objects can be chained together + * internally e.g. to automatically decode gzip encoded content before + * passing it on to the GCDWebServerRequest. + * + * @warning These methods can be called on any GCD thread. + */ +@protocol GCDWebServerBodyWriter + +/** + * This method is called before any body data is received. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)open:(NSError**)error; + +/** + * This method is called whenever body data has been received. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)writeData:(NSData*)data error:(NSError**)error; + +/** + * This method is called after all body data has been received. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)close:(NSError**)error; + +@end + +/** + * The GCDWebServerRequest class is instantiated by the GCDWebServerConnection + * after the HTTP headers have been received. Each instance wraps a single HTTP + * request. If a body is present, the methods from the GCDWebServerBodyWriter + * protocol will be called by the GCDWebServerConnection to receive it. + * + * The default implementation of the GCDWebServerBodyWriter protocol on the class + * simply ignores the body data. + * + * @warning GCDWebServerRequest instances can be created and used on any GCD thread. + */ +@interface GCDWebServerRequest : NSObject + +/** + * Returns the HTTP method for the request. + */ +@property(nonatomic, readonly) NSString* method; + +/** + * Returns the URL for the request. + */ +@property(nonatomic, readonly) NSURL* URL; + +/** + * Returns the HTTP headers for the request. + */ +@property(nonatomic, readonly) NSDictionary* headers; + +/** + * Returns the path component of the URL for the request. + */ +@property(nonatomic, readonly) NSString* path; + +/** + * Returns the parsed and unescaped query component of the URL for the request. + * + * @warning This property will be nil if there is no query in the URL. + */ +@property(nonatomic, readonly, nullable) NSDictionary* query; + +/** + * Returns the content type for the body of the request parsed from the + * "Content-Type" header. + * + * This property will be nil if the request has no body or set to + * "application/octet-stream" if a body is present but there was no + * "Content-Type" header. + */ +@property(nonatomic, readonly, nullable) NSString* contentType; + +/** + * Returns the content length for the body of the request parsed from the + * "Content-Length" header. + * + * This property will be set to "NSUIntegerMax" if the request has no body or + * if there is a body but no "Content-Length" header, typically because + * chunked transfer encoding is used. + */ +@property(nonatomic, readonly) NSUInteger contentLength; + +/** + * Returns the parsed "If-Modified-Since" header or nil if absent or malformed. + */ +@property(nonatomic, readonly, nullable) NSDate* ifModifiedSince; + +/** + * Returns the parsed "If-None-Match" header or nil if absent or malformed. + */ +@property(nonatomic, readonly, nullable) NSString* ifNoneMatch; + +/** + * Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed. + * The range will be set to (offset, length) if expressed from the beginning + * of the entity body, or (NSUIntegerMax, length) if expressed from its end. + */ +@property(nonatomic, readonly) NSRange byteRange; + +/** + * Returns YES if the client supports gzip content encoding according to the + * "Accept-Encoding" header. + */ +@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding; + +/** + * Returns the address of the local peer (i.e. server) for the request + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* localAddressData; + +/** + * Returns the address of the local peer (i.e. server) for the request + * as a string. + */ +@property(nonatomic, readonly) NSString* localAddressString; + +/** + * Returns the address of the remote peer (i.e. client) for the request + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* remoteAddressData; + +/** + * Returns the address of the remote peer (i.e. client) for the request + * as a string. + */ +@property(nonatomic, readonly) NSString* remoteAddressString; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(nullable NSDictionary*)query; + +/** + * Convenience method that checks if the contentType property is defined. + */ +- (BOOL)hasBody; + +/** + * Convenience method that checks if the byteRange property is defined. + */ +- (BOOL)hasByteRange; + +/** + * Retrieves an attribute associated with this request using the given key. + * + * @return The attribute value for the key. + */ +- (nullable id)attributeForKey:(NSString*)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.m b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.m new file mode 100644 index 00000000..5648eff7 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.m @@ -0,0 +1,303 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import + +#import "GCDWebServerPrivate.h" + +NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerRequestAttribute_RegexCaptures"; + +#define kZlibErrorDomain @"ZlibErrorDomain" +#define kGZipInitialBufferSize (256 * 1024) + +@interface GCDWebServerBodyDecoder : NSObject +@end + +@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder +@end + +@implementation GCDWebServerBodyDecoder { + GCDWebServerRequest* __unsafe_unretained _request; + id __unsafe_unretained _writer; +} + +- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id _Nonnull)writer { + if ((self = [super init])) { + _request = request; + _writer = writer; + } + return self; +} + +- (BOOL)open:(NSError**)error { + return [_writer open:error]; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + return [_writer writeData:data error:error]; +} + +- (BOOL)close:(NSError**)error { + return [_writer close:error]; +} + +@end + +@implementation GCDWebServerGZipDecoder { + z_stream _stream; + BOOL _finished; +} + +- (BOOL)open:(NSError**)error { + int result = inflateInit2(&_stream, 15 + 16); + if (result != Z_OK) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return NO; + } + if (![super open:error]) { + inflateEnd(&_stream); + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + GWS_DCHECK(!_finished); + _stream.next_in = (Bytef*)data.bytes; + _stream.avail_in = (uInt)data.length; + NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; + if (decodedData == nil) { + GWS_DNOT_REACHED(); + return NO; + } + NSUInteger length = 0; + while (1) { + NSUInteger maxLength = decodedData.length - length; + _stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length); + _stream.avail_out = (uInt)maxLength; + int result = inflate(&_stream, Z_NO_FLUSH); + if ((result != Z_OK) && (result != Z_STREAM_END)) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return NO; + } + length += maxLength - _stream.avail_out; + if (_stream.avail_out > 0) { + if (result == Z_STREAM_END) { + _finished = YES; + } + break; + } + decodedData.length = 2 * decodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available + } + decodedData.length = length; + BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet + return success; +} + +- (BOOL)close:(NSError**)error { + GWS_DCHECK(_finished); + inflateEnd(&_stream); + return [super close:error]; +} + +@end + +@implementation GCDWebServerRequest { + BOOL _opened; + NSMutableArray* _decoders; + id __unsafe_unretained _writer; + NSMutableDictionary* _attributes; +} + +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { + if ((self = [super init])) { + _method = [method copy]; + _URL = url; + _headers = headers; + _path = [path copy]; + _query = query; + + _contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]); + _usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"]; + NSString* lengthHeader = [_headers objectForKey:@"Content-Length"]; + if (lengthHeader) { + NSInteger length = [lengthHeader integerValue]; + if (_usesChunkedTransferEncoding || (length < 0)) { + GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL); + GWS_DNOT_REACHED(); + return nil; + } + _contentLength = length; + if (_contentType == nil) { + _contentType = kGCDWebServerDefaultMimeType; + } + } else if (_usesChunkedTransferEncoding) { + if (_contentType == nil) { + _contentType = kGCDWebServerDefaultMimeType; + } + _contentLength = NSUIntegerMax; + } else { + if (_contentType) { + GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL); + _contentType = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense + } + _contentLength = NSUIntegerMax; + } + + NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"]; + if (modifiedHeader) { + _ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy]; + } + _ifNoneMatch = [_headers objectForKey:@"If-None-Match"]; + + _byteRange = NSMakeRange(NSUIntegerMax, 0); + NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]); + if (rangeHeader) { + if ([rangeHeader hasPrefix:@"bytes="]) { + NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","]; + if (components.count == 1) { + components = [(NSString*)[components firstObject] componentsSeparatedByString:@"-"]; + if (components.count == 2) { + NSString* startString = [components objectAtIndex:0]; + NSInteger startValue = [startString integerValue]; + NSString* endString = [components objectAtIndex:1]; + NSInteger endValue = [endString integerValue]; + if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999" + _byteRange.location = startValue; + _byteRange.length = endValue - startValue + 1; + } else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-" + _byteRange.location = startValue; + _byteRange.length = NSUIntegerMax; + } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500" + _byteRange.location = NSUIntegerMax; + _byteRange.length = endValue; + } + } + } + } + if ((_byteRange.location == NSUIntegerMax) && (_byteRange.length == 0)) { // Ignore "Range" header if syntactically invalid + GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url); + } + } + + if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) { + _acceptsGzipContentEncoding = YES; + } + + _decoders = [[NSMutableArray alloc] init]; + _attributes = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (BOOL)hasBody { + return _contentType ? YES : NO; +} + +- (BOOL)hasByteRange { + return GCDWebServerIsValidByteRange(_byteRange); +} + +- (id)attributeForKey:(NSString*)key { + return [_attributes objectForKey:key]; +} + +- (BOOL)open:(NSError**)error { + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + return YES; +} + +- (BOOL)close:(NSError**)error { + return YES; +} + +- (void)prepareForWriting { + _writer = self; + if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) { + GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer]; + [_decoders addObject:decoder]; + _writer = decoder; + } +} + +- (BOOL)performOpen:(NSError**)error { + GWS_DCHECK(_contentType); + GWS_DCHECK(_writer); + if (_opened) { + GWS_DNOT_REACHED(); + return NO; + } + _opened = YES; + return [_writer open:error]; +} + +- (BOOL)performWriteData:(NSData*)data error:(NSError**)error { + GWS_DCHECK(_opened); + return [_writer writeData:data error:error]; +} + +- (BOOL)performClose:(NSError**)error { + GWS_DCHECK(_opened); + return [_writer close:error]; +} + +- (void)setAttribute:(id)attribute forKey:(NSString*)key { + [_attributes setValue:attribute forKey:key]; +} + +- (NSString*)localAddressString { + return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES); +} + +- (NSString*)remoteAddressString { + return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES); +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path]; + for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n %@ = %@", argument, [_query objectForKey:argument]]; + } + [description appendString:@"\n"]; + for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]]; + } + return description; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.h b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.h new file mode 100644 index 00000000..2e1365bf --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.h @@ -0,0 +1,212 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the + * GCDWebServerBodyReader object when reading data from it asynchronously. + */ +typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* _Nullable data, NSError* _Nullable error); + +/** + * This protocol is used by the GCDWebServerConnection to communicate with + * the GCDWebServerResponse and read the HTTP body data to send. + * + * Note that multiple GCDWebServerBodyReader objects can be chained together + * internally e.g. to automatically apply gzip encoding to the content before + * passing it on to the GCDWebServerResponse. + * + * @warning These methods can be called on any GCD thread. + */ +@protocol GCDWebServerBodyReader + +@required + +/** + * This method is called before any body data is sent. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)open:(NSError**)error; + +/** + * This method is called whenever body data is sent. + * + * It should return a non-empty NSData if there is body data available, + * or an empty NSData there is no more body data, or nil on error and set + * the "error" argument which is guaranteed to be non-NULL. + */ +- (nullable NSData*)readData:(NSError**)error; + +/** + * This method is called after all body data has been sent. + */ +- (void)close; + +@optional + +/** + * If this method is implemented, it will be preferred over -readData:. + * + * It must call the passed block when data is available, passing a non-empty + * NSData if there is body data available, or an empty NSData there is no more + * body data, or nil on error and pass an NSError along. + */ +- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block; + +@end + +/** + * The GCDWebServerResponse class is used to wrap a single HTTP response. + * It is instantiated by the handler of the GCDWebServer that handled the request. + * If a body is present, the methods from the GCDWebServerBodyReader protocol + * will be called by the GCDWebServerConnection to send it. + * + * The default implementation of the GCDWebServerBodyReader protocol + * on the class simply returns an empty body. + * + * @warning GCDWebServerResponse instances can be created and used on any GCD thread. + */ +@interface GCDWebServerResponse : NSObject + +/** + * Sets the content type for the body of the response. + * + * The default value is nil i.e. the response has no body. + * + * @warning This property must be set if a body is present. + */ +@property(nonatomic, copy, nullable) NSString* contentType; + +/** + * Sets the content length for the body of the response. If a body is present + * but this property is set to "NSUIntegerMax", this means the length of the body + * cannot be known ahead of time. Chunked transfer encoding will be + * automatically enabled by the GCDWebServerConnection to comply with HTTP/1.1 + * specifications. + * + * The default value is "NSUIntegerMax" i.e. the response has no body or its length + * is undefined. + */ +@property(nonatomic) NSUInteger contentLength; + +/** + * Sets the HTTP status code for the response. + * + * The default value is 200 i.e. "OK". + */ +@property(nonatomic) NSInteger statusCode; + +/** + * Sets the caching hint for the response using the "Cache-Control" header. + * This value is expressed in seconds. + * + * The default value is 0 i.e. "no-cache". + */ +@property(nonatomic) NSUInteger cacheControlMaxAge; + +/** + * Sets the last modified date for the response using the "Last-Modified" header. + * + * The default value is nil. + */ +@property(nonatomic, nullable) NSDate* lastModifiedDate; + +/** + * Sets the ETag for the response using the "ETag" header. + * + * The default value is nil. + */ +@property(nonatomic, copy, nullable) NSString* eTag; + +/** + * Enables gzip encoding for the response body. + * + * The default value is NO. + * + * @warning Enabling gzip encoding will remove any "Content-Length" header + * since the length of the body is not known anymore. The client will still + * be able to determine the body length when connection is closed per + * HTTP/1.1 specifications. + */ +@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled; + +/** + * Creates an empty response. + */ ++ (instancetype)response; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)init; + +/** + * Sets an additional HTTP header on the response. + * Pass a nil value to remove an additional header. + * + * @warning Do not attempt to override the primary headers used + * by GCDWebServerResponse like "Content-Type", "ETag", etc... + */ +- (void)setValue:(nullable NSString*)value forAdditionalHeader:(NSString*)header; + +/** + * Convenience method that checks if the contentType property is defined. + */ +- (BOOL)hasBody; + +@end + +@interface GCDWebServerResponse (Extensions) + +/** + * Creates a empty response with a specific HTTP status code. + */ ++ (instancetype)responseWithStatusCode:(NSInteger)statusCode; + +/** + * Creates an HTTP redirect response to a new URL. + */ ++ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent; + +/** + * Initializes an empty response with a specific HTTP status code. + */ +- (instancetype)initWithStatusCode:(NSInteger)statusCode; + +/** + * Initializes an HTTP redirect response to a new URL. + */ +- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.m b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.m new file mode 100644 index 00000000..46042b21 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.m @@ -0,0 +1,284 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import + +#import "GCDWebServerPrivate.h" + +#define kZlibErrorDomain @"ZlibErrorDomain" +#define kGZipInitialBufferSize (256 * 1024) + +@interface GCDWebServerBodyEncoder : NSObject +@end + +@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder +@end + +@implementation GCDWebServerBodyEncoder { + GCDWebServerResponse* __unsafe_unretained _response; + id __unsafe_unretained _reader; +} + +- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id _Nonnull)reader { + if ((self = [super init])) { + _response = response; + _reader = reader; + } + return self; +} + +- (BOOL)open:(NSError**)error { + return [_reader open:error]; +} + +- (NSData*)readData:(NSError**)error { + return [_reader readData:error]; +} + +- (void)close { + [_reader close]; +} + +@end + +@implementation GCDWebServerGZipEncoder { + z_stream _stream; + BOOL _finished; +} + +- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id _Nonnull)reader { + if ((self = [super initWithResponse:response reader:reader])) { + response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it + [response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"]; + } + return self; +} + +- (BOOL)open:(NSError**)error { + int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); + if (result != Z_OK) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return NO; + } + if (![super open:error]) { + deflateEnd(&_stream); + return NO; + } + return YES; +} + +- (NSData*)readData:(NSError**)error { + NSMutableData* encodedData; + if (_finished) { + encodedData = [[NSMutableData alloc] init]; + } else { + encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; + if (encodedData == nil) { + GWS_DNOT_REACHED(); + return nil; + } + NSUInteger length = 0; + do { + NSData* data = [super readData:error]; + if (data == nil) { + return nil; + } + _stream.next_in = (Bytef*)data.bytes; + _stream.avail_in = (uInt)data.length; + while (1) { + NSUInteger maxLength = encodedData.length - length; + _stream.next_out = (Bytef*)((char*)encodedData.mutableBytes + length); + _stream.avail_out = (uInt)maxLength; + int result = deflate(&_stream, data.length ? Z_NO_FLUSH : Z_FINISH); + if (result == Z_STREAM_END) { + _finished = YES; + } else if (result != Z_OK) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return nil; + } + length += maxLength - _stream.avail_out; + if (_stream.avail_out > 0) { + break; + } + encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available + } + GWS_DCHECK(_stream.avail_in == 0); + } while (length == 0); // Make sure we don't return an empty NSData if not in finished state + encodedData.length = length; + } + return encodedData; +} + +- (void)close { + deflateEnd(&_stream); + [super close]; +} + +@end + +@implementation GCDWebServerResponse { + BOOL _opened; + NSMutableArray* _encoders; + id __unsafe_unretained _reader; +} + ++ (instancetype)response { + return [(GCDWebServerResponse*)[[self class] alloc] init]; +} + +- (instancetype)init { + if ((self = [super init])) { + _contentType = nil; + _contentLength = NSUIntegerMax; + _statusCode = kGCDWebServerHTTPStatusCode_OK; + _cacheControlMaxAge = 0; + _additionalHeaders = [[NSMutableDictionary alloc] init]; + _encoders = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header { + [_additionalHeaders setValue:value forKey:header]; +} + +- (BOOL)hasBody { + return _contentType ? YES : NO; +} + +- (BOOL)usesChunkedTransferEncoding { + return (_contentType != nil) && (_contentLength == NSUIntegerMax); +} + +- (BOOL)open:(NSError**)error { + return YES; +} + +- (NSData*)readData:(NSError**)error { + return [NSData data]; +} + +- (void)close { + ; +} + +- (void)prepareForReading { + _reader = self; + if (_gzipContentEncodingEnabled) { + GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader]; + [_encoders addObject:encoder]; + _reader = encoder; + } +} + +- (BOOL)performOpen:(NSError**)error { + GWS_DCHECK(_contentType); + GWS_DCHECK(_reader); + if (_opened) { + GWS_DNOT_REACHED(); + return NO; + } + _opened = YES; + return [_reader open:error]; +} + +- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block { + GWS_DCHECK(_opened); + if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) { + [_reader asyncReadDataWithCompletion:[block copy]]; + } else { + NSError* error = nil; + NSData* data = [_reader readData:&error]; + block(data, error); + } +} + +- (void)performClose { + GWS_DCHECK(_opened); + [_reader close]; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode]; + if (_contentType) { + [description appendFormat:@"\nContent Type = %@", _contentType]; + } + if (_contentLength != NSUIntegerMax) { + [description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength]; + } + [description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge]; + if (_lastModifiedDate) { + [description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate]; + } + if (_eTag) { + [description appendFormat:@"\nETag = %@", _eTag]; + } + if (_additionalHeaders.count) { + [description appendString:@"\n"]; + for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]]; + } + } + return description; +} + +@end + +@implementation GCDWebServerResponse (Extensions) + ++ (instancetype)responseWithStatusCode:(NSInteger)statusCode { + return [(GCDWebServerResponse*)[self alloc] initWithStatusCode:statusCode]; +} + ++ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent { + return [(GCDWebServerResponse*)[self alloc] initWithRedirect:location permanent:permanent]; +} + +- (instancetype)initWithStatusCode:(NSInteger)statusCode { + if ((self = [self init])) { + self.statusCode = statusCode; + } + return self; +} + +- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent { + if ((self = [self init])) { + self.statusCode = permanent ? kGCDWebServerHTTPStatusCode_MovedPermanently : kGCDWebServerHTTPStatusCode_TemporaryRedirect; + [self setValue:[location absoluteString] forAdditionalHeader:@"Location"]; + } + return self; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h new file mode 100644 index 00000000..b105ce76 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body + * of the HTTP request in memory. + */ +@interface GCDWebServerDataRequest : GCDWebServerRequest + +/** + * Returns the data for the request body. + */ +@property(nonatomic, readonly) NSData* data; + +@end + +@interface GCDWebServerDataRequest (Extensions) + +/** + * Returns the data for the request body interpreted as text. If the content + * type of the body is not a text one, or if an error occurs, nil is returned. + * + * The text encoding used to interpret the data is extracted from the + * "Content-Type" header or defaults to UTF-8. + */ +@property(nonatomic, readonly, nullable) NSString* text; + +/** + * Returns the data for the request body interpreted as a JSON object. If the + * content type of the body is not JSON, or if an error occurs, nil is returned. + */ +@property(nonatomic, readonly, nullable) id jsonObject; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.m b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.m new file mode 100644 index 00000000..fcb29321 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.m @@ -0,0 +1,104 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@interface GCDWebServerDataRequest () +@property(nonatomic) NSMutableData* data; +@end + +@implementation GCDWebServerDataRequest { + NSString* _text; + id _jsonObject; +} + +- (BOOL)open:(NSError**)error { + if (self.contentLength != NSUIntegerMax) { + _data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; + } else { + _data = [[NSMutableData alloc] init]; + } + if (_data == nil) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed allocating memory"}]; + } + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + [_data appendData:data]; + return YES; +} + +- (BOOL)close:(NSError**)error { + return YES; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + if (_data) { + [description appendString:@"\n\n"]; + [description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)]; + } + return description; +} + +@end + +@implementation GCDWebServerDataRequest (Extensions) + +- (NSString*)text { + if (_text == nil) { + if ([self.contentType hasPrefix:@"text/"]) { + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); + _text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + } else { + GWS_DNOT_REACHED(); + } + } + return _text; +} + +- (id)jsonObject { + if (_jsonObject == nil) { + NSString* mimeType = GCDWebServerTruncateHeaderValue(self.contentType); + if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) { + _jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]; + } else { + GWS_DNOT_REACHED(); + } + } + return _jsonObject; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h new file mode 100644 index 00000000..3c6a6d24 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body + * of the HTTP request to a file on disk. + */ +@interface GCDWebServerFileRequest : GCDWebServerRequest + +/** + * Returns the path to the temporary file containing the request body. + * + * @warning This temporary file will be automatically deleted when the + * GCDWebServerFileRequest is deallocated. If you want to preserve this file, + * you must move it to a different location beforehand. + */ +@property(nonatomic, readonly) NSString* temporaryPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.m b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.m new file mode 100644 index 00000000..0845d5e9 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.m @@ -0,0 +1,102 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerFileRequest { + int _file; +} + +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { + if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { + _temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + } + return self; +} + +- (void)dealloc { + unlink([_temporaryPath fileSystemRepresentation]); +} + +- (BOOL)open:(NSError**)error { + _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (_file <= 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } + return YES; +} + +- (BOOL)close:(NSError**)error { + if (close(_file) < 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"]; + if (creationDateHeader) { + NSDate* date = GCDWebServerParseISO8601(creationDateHeader); + if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) { + return NO; + } + } + NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"]; + if (modifiedDateHeader) { + NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader); + if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) { + return NO; + } + } +#endif + return YES; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendFormat:@"\n\n{%@}", _temporaryPath]; + return description; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h new file mode 100644 index 00000000..cbe838ea --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h @@ -0,0 +1,136 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerMultiPart class is an abstract class that wraps the content + * of a part. + */ +@interface GCDWebServerMultiPart : NSObject + +/** + * Returns the control name retrieved from the part headers. + */ +@property(nonatomic, readonly) NSString* controlName; + +/** + * Returns the content type retrieved from the part headers or "text/plain" + * if not available (per HTTP specifications). + */ +@property(nonatomic, readonly) NSString* contentType; + +/** + * Returns the MIME type component of the content type for the part. + */ +@property(nonatomic, readonly) NSString* mimeType; + +@end + +/** + * The GCDWebServerMultiPartArgument subclass of GCDWebServerMultiPart wraps + * the content of a part as data in memory. + */ +@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart + +/** + * Returns the data for the part. + */ +@property(nonatomic, readonly) NSData* data; + +/** + * Returns the data for the part interpreted as text. If the content + * type of the part is not a text one, or if an error occurs, nil is returned. + * + * The text encoding used to interpret the data is extracted from the + * "Content-Type" header or defaults to UTF-8. + */ +@property(nonatomic, readonly, nullable) NSString* string; + +@end + +/** + * The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps + * the content of a part as a file on disk. + */ +@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart + +/** + * Returns the file name retrieved from the part headers. + */ +@property(nonatomic, readonly) NSString* fileName; + +/** + * Returns the path to the temporary file containing the part data. + * + * @warning This temporary file will be automatically deleted when the + * GCDWebServerMultiPartFile is deallocated. If you want to preserve this file, + * you must move it to a different location beforehand. + */ +@property(nonatomic, readonly) NSString* temporaryPath; + +@end + +/** + * The GCDWebServerMultiPartFormRequest subclass of GCDWebServerRequest + * parses the body of the HTTP request as a multipart encoded form. + */ +@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest + +/** + * Returns the argument parts from the multipart encoded form as + * name / GCDWebServerMultiPartArgument pairs. + */ +@property(nonatomic, readonly) NSArray* arguments; + +/** + * Returns the files parts from the multipart encoded form as + * name / GCDWebServerMultiPartFile pairs. + */ +@property(nonatomic, readonly) NSArray* files; + +/** + * Returns the MIME type for multipart encoded forms + * i.e. "multipart/form-data". + */ ++ (NSString*)mimeType; + +/** + * Returns the first argument for a given control name or nil if not found. + */ +- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name; + +/** + * Returns the first file for a given control name or nil if not found. + */ +- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m new file mode 100644 index 00000000..7550a021 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m @@ -0,0 +1,405 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +#define kMultiPartBufferSize (256 * 1024) + +typedef enum { + kParserState_Undefined = 0, + kParserState_Start, + kParserState_Headers, + kParserState_Content, + kParserState_End +} ParserState; + +@interface GCDWebServerMIMEStreamParser : NSObject +@end + +static NSData* _newlineData = nil; +static NSData* _newlinesData = nil; +static NSData* _dashNewlineData = nil; + +@implementation GCDWebServerMultiPart + +- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type { + if ((self = [super init])) { + _controlName = [name copy]; + _contentType = [type copy]; + _mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType); + } + return self; +} + +@end + +@implementation GCDWebServerMultiPartArgument + +- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data { + if ((self = [super initWithControlName:name contentType:type])) { + _data = data; + + if ([self.contentType hasPrefix:@"text/"]) { + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); + _string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + } + } + return self; +} + +- (NSString*)description { + return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length]; +} + +@end + +@implementation GCDWebServerMultiPartFile + +- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath { + if ((self = [super initWithControlName:name contentType:type])) { + _fileName = [fileName copy]; + _temporaryPath = [temporaryPath copy]; + } + return self; +} + +- (void)dealloc { + unlink([_temporaryPath fileSystemRepresentation]); +} + +- (NSString*)description { + return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName]; +} + +@end + +@implementation GCDWebServerMIMEStreamParser { + NSData* _boundary; + NSString* _defaultcontrolName; + ParserState _state; + NSMutableData* _data; + NSMutableArray* _arguments; + NSMutableArray* _files; + + NSString* _controlName; + NSString* _fileName; + NSString* _contentType; + NSString* _tmpPath; + int _tmpFile; + GCDWebServerMIMEStreamParser* _subParser; +} + ++ (void)initialize { + if (_newlineData == nil) { + _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2]; + GWS_DCHECK(_newlineData); + } + if (_newlinesData == nil) { + _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; + GWS_DCHECK(_newlinesData); + } + if (_dashNewlineData == nil) { + _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4]; + GWS_DCHECK(_dashNewlineData); + } +} + +- (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray* _Nonnull)arguments files:(NSMutableArray* _Nonnull)files { + NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + if ((self = [super init])) { + _boundary = data; + _defaultcontrolName = name; + _arguments = arguments; + _files = files; + _data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize]; + _state = kParserState_Start; + } + return self; +} + +- (void)dealloc { + if (_tmpFile > 0) { + close(_tmpFile); + unlink([_tmpPath fileSystemRepresentation]); + } +} + +// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 +- (BOOL)_parseData { + BOOL success = YES; + + if (_state == kParserState_Headers) { + NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)]; + if (range.location != NSNotFound) { + _controlName = nil; + _fileName = nil; + _contentType = nil; + _tmpPath = nil; + _subParser = nil; + NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding]; + if (headers) { + for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) { + NSRange subRange = [header rangeOfString:@":"]; + if (subRange.location != NSNotFound) { + NSString* name = [header substringToIndex:subRange.location]; + NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) { + _contentType = GCDWebServerNormalizeHeaderValue(value); + } else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) { + NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value); + if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) { + _controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"); + _fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); + } else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) { + _controlName = _defaultcontrolName; + _fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); + } + } + } else { + GWS_DNOT_REACHED(); + } + } + if (_contentType == nil) { + _contentType = @"text/plain"; + } + } else { + GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'"); + GWS_DNOT_REACHED(); + } + if (_controlName) { + if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) { + NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary"); + _subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files]; + if (_subParser == nil) { + GWS_DNOT_REACHED(); + success = NO; + } + } else if (_fileName) { + NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (_tmpFile > 0) { + _tmpPath = [path copy]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } + } else { + GWS_DNOT_REACHED(); + success = NO; + } + + [_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; + _state = kParserState_Content; + } + } + + if ((_state == kParserState_Start) || (_state == kParserState_Content)) { + NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)]; + if (range.location != NSNotFound) { + NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length); + NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; + NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; + if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) { + if (_state == kParserState_Content) { + const void* dataBytes = _data.bytes; + NSUInteger dataLength = range.location - 2; + if (_subParser) { + if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) { + GWS_DNOT_REACHED(); + success = NO; + } + _subParser = nil; + } else if (_tmpPath) { + ssize_t result = write(_tmpFile, dataBytes, dataLength); + if (result == (ssize_t)dataLength) { + if (close(_tmpFile) == 0) { + _tmpFile = 0; + GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath]; + [_files addObject:file]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } else { + GWS_DNOT_REACHED(); + success = NO; + } + _tmpPath = nil; + } else { + NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength]; + GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data]; + [_arguments addObject:argument]; + } + } + + if (subRange1.location != NSNotFound) { + [_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; + _state = kParserState_Headers; + success = [self _parseData]; + } else { + _state = kParserState_End; + } + } + } else { + NSUInteger margin = 2 * _boundary.length; + if (_data.length > margin) { + NSUInteger length = _data.length - margin; + if (_subParser) { + if ([_subParser appendBytes:_data.bytes length:length]) { + [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } else if (_tmpPath) { + ssize_t result = write(_tmpFile, _data.bytes, length); + if (result == (ssize_t)length) { + [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } + } + } + } + + return success; +} + +- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length { + [_data appendBytes:bytes length:length]; + return [self _parseData]; +} + +- (BOOL)isAtEnd { + return (_state == kParserState_End); +} + +@end + +@interface GCDWebServerMultiPartFormRequest () +@property(nonatomic) NSMutableArray* arguments; +@property(nonatomic) NSMutableArray* files; +@end + +@implementation GCDWebServerMultiPartFormRequest { + GCDWebServerMIMEStreamParser* _parser; +} + ++ (NSString*)mimeType { + return @"multipart/form-data"; +} + +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { + if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { + _arguments = [[NSMutableArray alloc] init]; + _files = [[NSMutableArray alloc] init]; + } + return self; +} + +- (BOOL)open:(NSError**)error { + NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); + _parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files]; + if (_parser == nil) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed starting to parse multipart form data"}]; + } + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + if (![_parser appendBytes:data.bytes length:data.length]) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed continuing to parse multipart form data"}]; + } + return NO; + } + return YES; +} + +- (BOOL)close:(NSError**)error { + BOOL atEnd = [_parser isAtEnd]; + _parser = nil; + if (!atEnd) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed finishing to parse multipart form data"}]; + } + return NO; + } + return YES; +} + +- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name { + for (GCDWebServerMultiPartArgument* argument in _arguments) { + if ([argument.controlName isEqualToString:name]) { + return argument; + } + } + return nil; +} + +- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name { + for (GCDWebServerMultiPartFile* file in _files) { + if ([file.controlName isEqualToString:name]) { + return file; + } + } + return nil; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + if (_arguments.count) { + [description appendString:@"\n"]; + for (GCDWebServerMultiPartArgument* argument in _arguments) { + [description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType]; + [description appendString:GCDWebServerDescribeData(argument.data, argument.contentType)]; + } + } + if (_files.count) { + [description appendString:@"\n"]; + for (GCDWebServerMultiPartFile* file in _files) { + [description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath]; + } + } + return description; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h new file mode 100644 index 00000000..c0168b76 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerDataRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest + * parses the body of the HTTP request as a URL encoded form using + * GCDWebServerParseURLEncodedForm(). + */ +@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest + +/** + * Returns the unescaped control names and values for the URL encoded form. + * + * The text encoding used to interpret the data is extracted from the + * "Content-Type" header or defaults to UTF-8. + */ +@property(nonatomic, readonly) NSDictionary* arguments; + +/** + * Returns the MIME type for URL encoded forms + * i.e. "application/x-www-form-urlencoded". + */ ++ (NSString*)mimeType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m new file mode 100644 index 00000000..d4d89e43 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m @@ -0,0 +1,60 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerURLEncodedFormRequest + ++ (NSString*)mimeType { + return @"application/x-www-form-urlencoded"; +} + +- (BOOL)close:(NSError**)error { + if (![super close:error]) { + return NO; + } + + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); + NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + _arguments = GCDWebServerParseURLEncodedForm(string); + return YES; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendString:@"\n"]; + for (NSString* argument in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n%@ = %@", argument, [_arguments objectForKey:argument]]; + } + return description; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h new file mode 100644 index 00000000..e5121c33 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h @@ -0,0 +1,113 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body + * of the HTTP response from memory. + */ +@interface GCDWebServerDataResponse : GCDWebServerResponse +@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null + +/** + * Creates a response with data in memory and a given content type. + */ ++ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; + +@end + +@interface GCDWebServerDataResponse (Extensions) + +/** + * Creates a data response from text encoded using UTF-8. + */ ++ (nullable instancetype)responseWithText:(NSString*)text; + +/** + * Creates a data response from HTML encoded using UTF-8. + */ ++ (nullable instancetype)responseWithHTML:(NSString*)html; + +/** + * Creates a data response from an HTML template encoded using UTF-8. + * See -initWithHTMLTemplate:variables: for details. + */ ++ (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; + +/** + * Creates a data response from a serialized JSON object and the default + * "application/json" content type. + */ ++ (nullable instancetype)responseWithJSONObject:(id)object; + +/** + * Creates a data response from a serialized JSON object and a custom + * content type. + */ ++ (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; + +/** + * Initializes a data response from text encoded using UTF-8. + */ +- (nullable instancetype)initWithText:(NSString*)text; + +/** + * Initializes a data response from HTML encoded using UTF-8. + */ +- (nullable instancetype)initWithHTML:(NSString*)html; + +/** + * Initializes a data response from an HTML template encoded using UTF-8. + * + * All occurences of "%variable%" within the HTML template are replaced with + * their corresponding values. + */ +- (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; + +/** + * Initializes a data response from a serialized JSON object and the default + * "application/json" content type. + */ +- (nullable instancetype)initWithJSONObject:(id)object; + +/** + * Initializes a data response from a serialized JSON object and a custom + * content type. + */ +- (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.m b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.m new file mode 100644 index 00000000..2ea2ce90 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.m @@ -0,0 +1,136 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerDataResponse { + NSData* _data; + BOOL _done; +} + +@dynamic contentType; + ++ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { + return [(GCDWebServerDataResponse*)[[self class] alloc] initWithData:data contentType:type]; +} + +- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { + if ((self = [super init])) { + _data = data; + + self.contentType = type; + self.contentLength = data.length; + } + return self; +} + +- (NSData*)readData:(NSError**)error { + NSData* data; + if (_done) { + data = [NSData data]; + } else { + data = _data; + _done = YES; + } + return data; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendString:@"\n\n"]; + [description appendString:GCDWebServerDescribeData(_data, self.contentType)]; + return description; +} + +@end + +@implementation GCDWebServerDataResponse (Extensions) + ++ (instancetype)responseWithText:(NSString*)text { + return [(GCDWebServerDataResponse*)[self alloc] initWithText:text]; +} + ++ (instancetype)responseWithHTML:(NSString*)html { + return [(GCDWebServerDataResponse*)[self alloc] initWithHTML:html]; +} + ++ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { + return [(GCDWebServerDataResponse*)[self alloc] initWithHTMLTemplate:path variables:variables]; +} + ++ (instancetype)responseWithJSONObject:(id)object { + return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object]; +} + ++ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type { + return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object contentType:type]; +} + +- (instancetype)initWithText:(NSString*)text { + NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + return [self initWithData:data contentType:@"text/plain; charset=utf-8"]; +} + +- (instancetype)initWithHTML:(NSString*)html { + NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding]; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + return [self initWithData:data contentType:@"text/html; charset=utf-8"]; +} + +- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { + NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { + [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; + }]; + return [self initWithHTML:html]; +} + +- (instancetype)initWithJSONObject:(id)object { + return [self initWithJSONObject:object contentType:@"application/json"]; +} + +- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type { + NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL]; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + return [self initWithData:data contentType:type]; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h new file mode 100644 index 00000000..7e10d5bf --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h @@ -0,0 +1,85 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerDataResponse.h" +#import "GCDWebServerHTTPStatusCodes.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates + * an HTML body from an HTTP status code and an error message. + */ +@interface GCDWebServerErrorResponse : GCDWebServerDataResponse + +/** + * Creates a client error response with the corresponding HTTP status code. + */ ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Creates a server error response with the corresponding HTTP status code. + */ ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Creates a client error response with the corresponding HTTP status code + * and an underlying NSError. + */ ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +/** + * Creates a server error response with the corresponding HTTP status code + * and an underlying NSError. + */ ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +/** + * Initializes a client error response with the corresponding HTTP status code. + */ +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Initializes a server error response with the corresponding HTTP status code. + */ +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Initializes a client error response with the corresponding HTTP status code + * and an underlying NSError. + */ +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +/** + * Initializes a server error response with the corresponding HTTP status code + * and an underlying NSError. + */ +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.m b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.m new file mode 100644 index 00000000..ab3788f6 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.m @@ -0,0 +1,124 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerErrorResponse + ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + +static inline NSString* _EscapeHTMLString(NSString* string) { + return [string stringByReplacingOccurrencesOfString:@"\"" withString:@"""]; +} + +- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments { + NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; + NSString* title = [NSString stringWithFormat:@"HTTP Error %i", (int)statusCode]; + NSString* error = underlyingError ? [NSString stringWithFormat:@"[%@] %@ (%li)", underlyingError.domain, _EscapeHTMLString(underlyingError.localizedDescription), (long)underlyingError.code] : @""; + NSString* html = [NSString stringWithFormat:@"%@

%@: %@

%@

", + title, title, _EscapeHTMLString(message), error]; + if ((self = [self initWithHTML:html])) { + self.statusCode = statusCode; + } + return self; +} + +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h new file mode 100644 index 00000000..78931bc6 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h @@ -0,0 +1,108 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body + * of the HTTP response from a file on disk. + * + * It will automatically set the contentType, lastModifiedDate and eTag + * properties of the GCDWebServerResponse according to the file extension and + * metadata. + */ +@interface GCDWebServerFileResponse : GCDWebServerResponse +@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null +@property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null +@property(nonatomic, copy) NSString* eTag; // Redeclare as non-null + +/** + * Creates a response with the contents of a file. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path; + +/** + * Creates a response like +responseWithFile: and sets the "Content-Disposition" + * HTTP header for a download if the "attachment" argument is YES. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; + +/** + * Creates a response like +responseWithFile: but restricts the file contents + * to a specific byte range. + * + * See -initWithFile:byteRange: for details. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; + +/** + * Creates a response like +responseWithFile:byteRange: and sets the + * "Content-Disposition" HTTP header for a download if the "attachment" + * argument is YES. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; + +/** + * Initializes a response with the contents of a file. + */ +- (nullable instancetype)initWithFile:(NSString*)path; + +/** + * Initializes a response like +responseWithFile: and sets the + * "Content-Disposition" HTTP header for a download if the "attachment" + * argument is YES. + */ +- (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; + +/** + * Initializes a response like -initWithFile: but restricts the file contents + * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for + * the full file, (offset, length) if expressed from the beginning of the file, + * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset" + * and "length" values will be automatically adjusted to be compatible with the + * actual size of the file. + * + * This argument would typically be set to the value of the byteRange property + * of the current GCDWebServerRequest. + */ +- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; + +/** + * This method is the designated initializer for the class. + * + * If MIME type overrides are specified, they allow to customize the built-in + * mapping from extensions to MIME types. Keys of the dictionary must be lowercased + * file extensions without the period, and the values must be the corresponding + * MIME types. + */ +- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.m b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.m new file mode 100644 index 00000000..78233063 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.m @@ -0,0 +1,185 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import + +#import "GCDWebServerPrivate.h" + +#define kFileReadBufferSize (32 * 1024) + +@implementation GCDWebServerFileResponse { + NSString* _path; + NSUInteger _offset; + NSUInteger _size; + int _file; +} + +@dynamic contentType, lastModifiedDate, eTag; + ++ (instancetype)responseWithFile:(NSString*)path { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path]; +} + ++ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path isAttachment:attachment]; +} + ++ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range]; +} + ++ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment mimeTypeOverrides:nil]; +} + +- (instancetype)initWithFile:(NSString*)path { + return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO mimeTypeOverrides:nil]; +} + +- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { + return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment mimeTypeOverrides:nil]; +} + +- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range { + return [self initWithFile:path byteRange:range isAttachment:NO mimeTypeOverrides:nil]; +} + +static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) { + return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)]; +} + +- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(NSDictionary*)overrides { + struct stat info; + if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) { + GWS_DNOT_REACHED(); + return nil; + } +#ifndef __LP64__ + if (info.st_size >= (off_t)4294967295) { // In 32 bit mode, we can't handle files greater than 4 GiBs (don't use "NSUIntegerMax" here to avoid potential unsigned to signed conversion issues) + GWS_DNOT_REACHED(); + return nil; + } +#endif + NSUInteger fileSize = (NSUInteger)info.st_size; + + BOOL hasByteRange = GCDWebServerIsValidByteRange(range); + if (hasByteRange) { + if (range.location != NSUIntegerMax) { + range.location = MIN(range.location, fileSize); + range.length = MIN(range.length, fileSize - range.location); + } else { + range.length = MIN(range.length, fileSize); + range.location = fileSize - range.length; + } + if (range.length == 0) { + return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header + } + } else { + range.location = 0; + range.length = fileSize; + } + + if ((self = [super init])) { + _path = [path copy]; + _offset = range.location; + _size = range.length; + if (hasByteRange) { + [self setStatusCode:kGCDWebServerHTTPStatusCode_PartialContent]; + [self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"]; + GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path); + } + + if (attachment) { + NSString* fileName = [path lastPathComponent]; + NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]; + NSString* lossyFileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil; + if (lossyFileName) { + NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, GCDWebServerEscapeURLString(fileName)]; + [self setValue:value forAdditionalHeader:@"Content-Disposition"]; + } else { + GWS_DNOT_REACHED(); + } + } + + self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension], overrides); + self.contentLength = _size; + self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec); + self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec]; + } + return self; +} + +- (BOOL)open:(NSError**)error { + _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY); + if (_file <= 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } + if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + close(_file); + return NO; + } + return YES; +} + +- (NSData*)readData:(NSError**)error { + size_t length = MIN((NSUInteger)kFileReadBufferSize, _size); + NSMutableData* data = [[NSMutableData alloc] initWithLength:length]; + ssize_t result = read(_file, data.mutableBytes, length); + if (result < 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return nil; + } + if (result > 0) { + [data setLength:result]; + _size -= result; + } + return data; +} + +- (void)close { + close(_file); +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendFormat:@"\n\n{%@}", _path]; + return description; +} + +@end diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h new file mode 100644 index 00000000..211f6303 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h @@ -0,0 +1,80 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerStreamBlock is called to stream the data for the HTTP body. + * The block must return either a chunk of data, an empty NSData when done, or + * nil on error and set the "error" argument which is guaranteed to be non-NULL. + */ +typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error); + +/** + * The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock + * except the streamed data can be returned at a later time allowing for + * truly asynchronous generation of the data. + * + * The block must call "completionBlock" passing the new chunk of data when ready, + * an empty NSData when done, or nil on error and pass a NSError. + * + * The block cannot call "completionBlock" more than once per invocation. + */ +typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock); + +/** + * The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams + * the body of the HTTP response using a GCD block. + */ +@interface GCDWebServerStreamedResponse : GCDWebServerResponse +@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null + +/** + * Creates a response with streamed data and a given content type. + */ ++ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; + +/** + * Creates a response with async streamed data and a given content type. + */ ++ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; + +/** + * Initializes a response with streamed data and a given content type. + */ +- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.m b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.m new file mode 100644 index 00000000..c05e91b4 --- /dev/null +++ b/Example/Pods/GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.m @@ -0,0 +1,76 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerStreamedResponse { + GCDWebServerAsyncStreamBlock _block; +} + +@dynamic contentType; + ++ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { + return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type streamBlock:block]; +} + ++ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block { + return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type asyncStreamBlock:block]; +} + +- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { + return [self initWithContentType:type + asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) { + NSError* error = nil; + NSData* data = block(&error); + completionBlock(data, error); + }]; +} + +- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block { + if ((self = [super init])) { + _block = [block copy]; + + self.contentType = type; + } + return self; +} + +- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block { + _block(block); +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendString:@"\n\n"]; + return description; +} + +@end diff --git a/Example/Pods/GCDWebServer/LICENSE b/Example/Pods/GCDWebServer/LICENSE new file mode 100644 index 00000000..12335de4 --- /dev/null +++ b/Example/Pods/GCDWebServer/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012-2014, Pierre-Olivier Latour +All rights reserved. + +Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + +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 PIERRE-OLIVIER LATOUR 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/Example/Pods/GCDWebServer/README.md b/Example/Pods/GCDWebServer/README.md new file mode 100644 index 00000000..4cbaccf6 --- /dev/null +++ b/Example/Pods/GCDWebServer/README.md @@ -0,0 +1,469 @@ +Overview +======== + +[![Build Status](https://travis-ci.org/swisspol/GCDWebServer.svg?branch=master)](https://travis-ci.org/swisspol/GCDWebServer) +[![Version](http://cocoapod-badges.herokuapp.com/v/GCDWebServer/badge.png)](http://cocoadocs.org/docsets/GCDWebServer) +[![Platform](http://cocoapod-badges.herokuapp.com/p/GCDWebServer/badge.png)](https://github.com/swisspol/GCDWebServer) +[![License](http://img.shields.io/cocoapods/l/GCDWebServer.svg)](LICENSE) + +GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in iOS, macOS & tvOS apps. It was written from scratch with the following goals in mind: +* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below) +* Well designed API with fully documented headers for easy integration and customization +* Entirely built with an event-driven design using [Grand Central Dispatch](http://en.wikipedia.org/wiki/Grand_Central_Dispatch) for best performance and concurrency +* No dependencies on third-party source code +* Available under a friendly [New BSD License](LICENSE) + +Extra built-in features: +* Allow implementation of fully asynchronous handlers of incoming HTTP requests +* Minimize memory usage with disk streaming of large HTTP request or response bodies +* Parser for [web forms](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4) submitted using "application/x-www-form-urlencoded" or "multipart/form-data" encodings (including file uploads) +* [JSON](http://www.json.org/) parsing and serialization for request and response HTTP bodies +* [Chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) for request and response HTTP bodies +* [HTTP compression](https://en.wikipedia.org/wiki/HTTP_compression) with gzip for request and response HTTP bodies +* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files +* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection +* Automatically handle transitions between foreground, background and suspended modes in iOS apps +* Full support for both IPv4 and IPv6 +* NAT port mapping (IPv4 only) + +Included extensions: +* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser +* [GCDWebDAVServer](GCDWebDAVServer/GCDWebDAVServer.h): subclass of ```GCDWebServer``` that implements a class 1 [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server (with partial class 2 support for macOS Finder) + +What's not supported (but not really required from an embedded HTTP server): +* Keep-alive connections +* HTTPS + +Requirements: +* macOS 10.7 or later (x86_64) +* iOS 8.0 or later (armv7, armv7s or arm64) +* tvOS 9.0 or later (arm64) +* ARC memory management only (if you need MRC support use GCDWebServer 3.1 or earlier) + +Getting Started +=============== + +Download or check out the [latest release](https://github.com/swisspol/GCDWebServer/releases) of GCDWebServer then add the entire "GCDWebServer" subfolder to your Xcode project. If you intend to use one of the extensions like GCDWebDAVServer or GCDWebUploader, add these subfolders as well. Finally link to `libz` (via Target > Build Phases > Link Binary With Libraries) and add `$(SDKROOT)/usr/include/libxml2` to your header search paths (via Target > Build Settings > HEADER_SEARCH_PATHS). + +Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Podfile: +``` +pod "GCDWebServer", "~> 3.0" +``` +If you want to use GCDWebUploader, use this line instead: +``` +pod "GCDWebServer/WebUploader", "~> 3.0" +``` +Or this line for GCDWebDAVServer: +``` +pod "GCDWebServer/WebDAV", "~> 3.0" +``` + +And finally run `$ pod install`. + +You can also use [Carthage](https://github.com/Carthage/Carthage) by adding this line to your Cartfile (3.2.5 is the first release with Carthage support): +``` +github "swisspol/GCDWebServer" ~> 3.2.5 +``` + +Then run `$ carthage update` and add the generated frameworks to your Xcode projects (see [Carthage instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)). + +Help & Support +============== + +For help with using GCDWebServer, it's best to ask your question on Stack Overflow with the [`gcdwebserver`](http://stackoverflow.com/questions/tagged/gcdwebserver) tag. For bug reports and enhancement requests you can use [issues](https://github.com/swisspol/GCDWebServer/issues) in this project. + +Be sure to read this entire README first though! + +Hello World +=========== + +These code snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code. + +**IMPORTANT:** If not using CocoaPods, be sure to add the `libz` shared system library to the Xcode target for your app. + +**macOS version (command line tool):** +```objectivec +#import "GCDWebServer.h" +#import "GCDWebServerDataResponse.h" + +int main(int argc, const char* argv[]) { + @autoreleasepool { + + // Create server + GCDWebServer* webServer = [[GCDWebServer alloc] init]; + + // Add a handler to respond to GET requests on any URL + [webServer addDefaultHandlerForMethod:@"GET" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + return [GCDWebServerDataResponse responseWithHTML:@"

Hello World

"]; + + }]; + + // Use convenience method that runs server on port 8080 + // until SIGINT (Ctrl-C in Terminal) or SIGTERM is received + [webServer runWithPort:8080 bonjourName:nil]; + NSLog(@"Visit %@ in your web browser", webServer.serverURL); + + } + return 0; +} +``` + +**iOS version:** +```objectivec +#import "GCDWebServer.h" +#import "GCDWebServerDataResponse.h" + +@interface AppDelegate : NSObject { + GCDWebServer* _webServer; +} +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + + // Create server + _webServer = [[GCDWebServer alloc] init]; + + // Add a handler to respond to GET requests on any URL + [_webServer addDefaultHandlerForMethod:@"GET" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + return [GCDWebServerDataResponse responseWithHTML:@"

Hello World

"]; + + }]; + + // Start server on port 8080 + [_webServer startWithPort:8080 bonjourName:nil]; + NSLog(@"Visit %@ in your web browser", _webServer.serverURL); + + return YES; +} + +@end +``` + +**macOS Swift version (command line tool):** + +***webServer.swift*** +```swift +import Foundation +import GCDWebServer + +func initWebServer() { + + let webServer = GCDWebServer() + + webServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self, processBlock: {request in + return GCDWebServerDataResponse(html:"

Hello World

") + + }) + + webServer.start(withPort: 8080, bonjourName: "GCD Web Server") + + print("Visit \(webServer.serverURL) in your web browser") +} +``` + +***WebServer-Bridging-Header.h*** +```objectivec +#import +#import +``` + +Web Based Uploads in iOS Apps +============================= + +GCDWebUploader is a subclass of ```GCDWebServer``` that provides a ready-to-use HTML 5 file uploader & downloader. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using a clean user interface in their web browser. + +Simply instantiate and run a ```GCDWebUploader``` instance then visit ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` from your web browser: + +```objectivec +#import "GCDWebUploader.h" + +@interface AppDelegate : NSObject { + GCDWebUploader* _webUploader; +} +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + _webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath]; + [_webUploader start]; + NSLog(@"Visit %@ in your web browser", _webUploader.serverURL); + return YES; +} + +@end +``` + +WebDAV Server in iOS Apps +========================= + +GCDWebDAVServer is a subclass of ```GCDWebServer``` that provides a class 1 compliant [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server. This lets users upload, download, delete files and create directories from a directory inside your iOS app's sandbox using any WebDAV client like [Transmit](https://panic.com/transmit/) (Mac), [ForkLift](http://binarynights.com/forklift/) (Mac) or [CyberDuck](http://cyberduck.io/) (Mac / Windows). + +GCDWebDAVServer should also work with the [macOS Finder](http://support.apple.com/kb/PH13859) as it is partially class 2 compliant (but only when the client is the macOS WebDAV implementation). + +Simply instantiate and run a ```GCDWebDAVServer``` instance then connect to ```http://{YOUR-IOS-DEVICE-IP-ADDRESS}/``` using a WebDAV client: + +```objectivec +#import "GCDWebDAVServer.h" + +@interface AppDelegate : NSObject { + GCDWebDAVServer* _davServer; +} +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + _davServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:documentsPath]; + [_davServer start]; + NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL); + return YES; +} + +@end +``` + +Serving a Static Website +======================== + +GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the ["Cache-Control"](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) header should be set): + +**macOS version (command line tool):** +```objectivec +#import "GCDWebServer.h" + +int main(int argc, const char* argv[]) { + @autoreleasepool { + + GCDWebServer* webServer = [[GCDWebServer alloc] init]; + [webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES]; + [webServer runWithPort:8080]; + + } + return 0; +} +``` + +Using GCDWebServer +================== + +You start by creating an instance of the ```GCDWebServer``` class. Note that you can have multiple web servers running in the same app as long as they listen on different ports. + +Then you add one or more "handlers" to the server: each handler gets a chance to handle an incoming web request and provide a response. Handlers are called in a LIFO queue, so the latest added handler overrides any previously added ones. + +Finally you start the server on a given port. + +Understanding GCDWebServer's Architecture +========================================= + +GCDWebServer's architecture consists of only 4 core classes: +* [GCDWebServer](GCDWebServer/Core/GCDWebServer.h) manages the socket that listens for new HTTP connections and the list of handlers used by the server. +* [GCDWebServerConnection](GCDWebServer/Core/GCDWebServerConnection.h) is instantiated by ```GCDWebServer``` to handle each new HTTP connection. Each instance stays alive until the connection is closed. You cannot use this class directly, but it is exposed so you can subclass it to override some hooks. +* [GCDWebServerRequest](GCDWebServer/Core/GCDWebServerRequest.h) is created by the ```GCDWebServerConnection``` instance after HTTP headers have been received. It wraps the request and handles the HTTP body if any. GCDWebServer comes with [several subclasses](GCDWebServer/Requests) of ```GCDWebServerRequest``` to handle common cases like storing the body in memory or stream it to a file on disk. +* [GCDWebServerResponse](GCDWebServer/Core/GCDWebServerResponse.h) is created by the request handler and wraps the response HTTP headers and optional body. GCDWebServer comes with [several subclasses](GCDWebServer/Responses) of ```GCDWebServerResponse``` to handle common cases like HTML text in memory or streaming a file from disk. + +Implementing Handlers +===================== + +GCDWebServer relies on "handlers" to process incoming web requests and generating responses. Handlers are implemented with GCD blocks which makes it very easy to provide your own. However, they are executed on arbitrary threads within GCD so __special attention must be paid to thread-safety and re-entrancy__. + +Handlers require 2 GCD blocks: +* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil. +* The ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return synchronously (if using ```GCDWebServerProcessBlock```) or asynchronously (if using ```GCDWebServerAsyncProcessBlock```) a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client. + +Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex. + +Asynchronous HTTP Responses +=========================== + +New in GCDWebServer 3.0 is the ability to process HTTP requests asynchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example: + +**(Synchronous version)** The handler blocks while generating the HTTP response: +```objectivec +[webServer addDefaultHandlerForMethod:@"GET" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"

Hello World

"]; + return response; + +}]; +``` + +**(Asynchronous version)** The handler returns immediately and calls back GCDWebServer later with the generated HTTP response: +```objectivec +[webServer addDefaultHandlerForMethod:@"GET" + requestClass:[GCDWebServerRequest class] + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + + // Do some async operation like network access or file I/O (simulated here using dispatch_after()) + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"

Hello World

"]; + completionBlock(response); + }); + +}]; +``` + +**(Advanced asynchronous version)** The handler returns immediately a streamed HTTP response which itself generates its contents asynchronously: +```objectivec +[webServer addDefaultHandlerForMethod:@"GET" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + NSMutableArray* contents = [NSMutableArray arrayWithObjects:@"

\n", @"Hello World!\n", @"

\n", nil]; // Fake data source we are reading from + GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/html" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) { + + // Simulate a delay reading from the fake data source + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString* string = contents.firstObject; + if (string) { + [contents removeObjectAtIndex:0]; + completionBlock([string dataUsingEncoding:NSUTF8StringEncoding], nil); // Generate the 2nd part of the stream data + } else { + completionBlock([NSData data], nil); // Must pass an empty NSData to signal the end of the stream + } + }); + + }]; + return response; + +}]; +``` + +*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!* + +GCDWebServer & Background Mode for iOS Apps +=========================================== + +When doing networking operations in iOS apps, you must handle carefully [what happens when iOS puts the app in the background](https://developer.apple.com/library/ios/technotes/tn2277/_index.html). Typically you must stop any network servers while the app is in the background and restart them when the app comes back to the foreground. This can become quite complex considering servers might have ongoing connections when they need to be stopped. + +Fortunately, GCDWebServer does all of this automatically for you: +- GCDWebServer begins a [background task](https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html) whenever the first HTTP connection is opened and ends it only when the last one is closed. This prevents iOS from suspending the app after it goes in the background, which would immediately kill HTTP connections to the client. + - While the app is in the background, as long as new HTTP connections keep being initiated, the background task will continue to exist and iOS will not suspend the app **for up to 10 minutes** (unless under sudden and unexpected memory pressure). + - If the app is still in the background when the last HTTP connection is closed, GCDWebServer will suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option). +- If the app goes in the background while no HTTP connections are opened, GCDWebServer will immediately suspend itself and stop accepting new connections as if you had called ```-stop``` (this behavior can be disabled with the ```GCDWebServerOption_AutomaticallySuspendInBackground``` option). +- If the app comes back to the foreground and GCDWebServer had been suspended, it will automatically resume itself and start accepting again new HTTP connections as if you had called ```-start```. + +HTTP connections are often initiated in batches (or bursts), for instance when loading a web page with multiple resources. This makes it difficult to accurately detect when the *very last* HTTP connection has been closed: it's possible 2 consecutive HTTP connections part of the same batch would be separated by a small delay instead of overlapping. It would be bad for the client if GCDWebServer suspended itself right in between. The ```GCDWebServerOption_ConnectedStateCoalescingInterval``` option solves this problem elegantly by forcing GCDWebServer to wait some extra delay before performing any action after the last HTTP connection has been closed, just in case a new HTTP connection is initiated within this delay. + +Logging in GCDWebServer +======================= + +Both for debugging and informational purpose, GCDWebServer logs messages extensively whenever something happens. Furthermore, when building GCDWebServer in "Debug" mode versus "Release" mode, it logs even more information but also performs a number of internal consistency checks. To enable this behavior, define the preprocessor constant ```DEBUG=1``` when compiling GCDWebServer. In Xcode target settings, this can be done by adding ```DEBUG=1``` to the build setting ```GCC_PREPROCESSOR_DEFINITIONS``` when building in "Debug" configuration. Finally, you can also control the logging verbosity at run time by calling ```+[GCDWebServer setLogLevel:]```. + +By default, all messages logged by GCDWebServer are sent to its built-in logging facility, which simply outputs to ```stderr``` (assuming a terminal type device is connected). In order to better integrate with the rest of your app or because of the amount of information logged, you might want to use another logging facility. + +GCDWebServer has automatic support for [XLFacility](https://github.com/swisspol/XLFacility) (by the same author as GCDWebServer and also open-source): if it is in the same Xcode project, GCDWebServer should use it automatically instead of the built-in logging facility (see [GCDWebServerPrivate.h](GCDWebServer/Core/GCDWebServerPrivate.h) for the implementation details). + +It's also possible to use a custom logging facility - see [GCDWebServer.h](GCDWebServer/Core/GCDWebServer.h) for more information. + +Advanced Example 1: Implementing HTTP Redirects +=============================================== + +Here's an example handler that redirects "/" to "/index.html" using the convenience method on ```GCDWebServerResponse``` (it sets the HTTP status code and "Location" header automatically): + +```objectivec +[self addHandlerForMethod:@"GET" + path:@"/" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL] + permanent:NO]; + +}]; +``` + +Advanced Example 2: Implementing Forms +====================================== + +To implement an HTTP form, you need a pair of handlers: +* The GET handler does not expect any body in the HTTP request and therefore uses the ```GCDWebServerRequest``` class. The handler generates a response containing a simple HTML form. +* The POST handler expects the form values to be in the body of the HTTP request and percent-encoded. Fortunately, GCDWebServer provides the request class ```GCDWebServerURLEncodedFormRequest``` which can automatically parse such bodies. The handler simply echoes back the value from the user submitted form. + +```objectivec +[webServer addHandlerForMethod:@"GET" + path:@"/" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + NSString* html = @" \ + \ +
\ + Value: \ + \ +
\ + \ + "; + return [GCDWebServerDataResponse responseWithHTML:html]; + +}]; + +[webServer addHandlerForMethod:@"POST" + path:@"/" + requestClass:[GCDWebServerURLEncodedFormRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"]; + NSString* html = [NSString stringWithFormat:@"

%@

", value]; + return [GCDWebServerDataResponse responseWithHTML:html]; + +}]; +``` + +Advanced Example 3: Serving a Dynamic Website +============================================= + +GCDWebServer provides an extension to the ```GCDWebServerDataResponse``` class that can return HTML content generated from a template and a set of variables (using the format ```%variable%```). It is a very basic template system and is really intended as a starting point to building more advanced template systems by subclassing ```GCDWebServerResponse```. + +Assuming you have a website directory in your app containing HTML template files along with the corresponding CSS, scripts and images, it's pretty easy to turn it into a dynamic website: + +```objectivec +// Get the path to the website directory +NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil]; + +// Add a default handler to serve static files (i.e. anything other than HTML files) +[self addGETHandlerForBasePath:@"/" directoryPath:websitePath indexFilename:nil cacheAge:3600 allowRangeRequests:YES]; + +// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization +[self addHandlerForMethod:@"GET" + pathRegex:@"/.*\.html" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + NSDictionary* variables = [NSDictionary dictionaryWithObjectsAndKeys:@"value", @"variable", nil]; + return [GCDWebServerDataResponse responseWithHTMLTemplate:[websitePath stringByAppendingPathComponent:request.path] + variables:variables]; + +}]; + +// Add an override handler to redirect "/" URL to "/index.html" +[self addHandlerForMethod:@"GET" + path:@"/" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { + + return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL] + permanent:NO]; + +]; + +``` + +Final Example: File Downloads and Uploads From iOS App +====================================================== + +GCDWebServer was originally written for the [ComicFlow](http://itunes.apple.com/us/app/comicflow/id409290355?mt=8) comic reader app for iPad. It allow users to connect to their iPad with their web browser over WiFi and then upload, download and organize comic files inside the app. + +ComicFlow is [entirely open-source](https://github.com/swisspol/ComicFlow) and you can see how it uses GCDWebServer in the [WebServer.h](https://github.com/swisspol/ComicFlow/blob/master/Classes/WebServer.h) and [WebServer.m](https://github.com/swisspol/ComicFlow/blob/master/Classes/WebServer.m) files. diff --git a/Example/Pods/Headers/Private/Doric/Doric.h b/Example/Pods/Headers/Private/Doric/Doric.h new file mode 120000 index 00000000..bb37c112 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/Doric.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Doric.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricAnimatePlugin.h b/Example/Pods/Headers/Private/Doric/DoricAnimatePlugin.h new file mode 120000 index 00000000..a12124e4 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricAnimatePlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricAnimatePlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricAsyncResult.h b/Example/Pods/Headers/Private/Doric/DoricAsyncResult.h new file mode 120000 index 00000000..dc8ffde2 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricAsyncResult.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricAsyncResult.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricBridgeExtension.h b/Example/Pods/Headers/Private/Doric/DoricBridgeExtension.h new file mode 120000 index 00000000..c958222e --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricBridgeExtension.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Extension/DoricBridgeExtension.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricConstant.h b/Example/Pods/Headers/Private/Doric/DoricConstant.h new file mode 120000 index 00000000..6680f76a --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricConstant.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricConstant.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricContext.h b/Example/Pods/Headers/Private/Doric/DoricContext.h new file mode 120000 index 00000000..d2e13664 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricContext.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricContext.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricContextHolder.h b/Example/Pods/Headers/Private/Doric/DoricContextHolder.h new file mode 120000 index 00000000..2ad994ce --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricContextHolder.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricContextHolder.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricContextManager.h b/Example/Pods/Headers/Private/Doric/DoricContextManager.h new file mode 120000 index 00000000..09beaea0 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricContextManager.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricContextManager.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricDriver.h b/Example/Pods/Headers/Private/Doric/DoricDriver.h new file mode 120000 index 00000000..a9c106e1 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricDriver.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricDriver.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricExtensions.h b/Example/Pods/Headers/Private/Doric/DoricExtensions.h new file mode 120000 index 00000000..350d3f83 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricExtensions.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricExtensions.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricFlowLayoutItemNode.h b/Example/Pods/Headers/Private/Doric/DoricFlowLayoutItemNode.h new file mode 120000 index 00000000..dc8a572d --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricFlowLayoutItemNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricFlowLayoutItemNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricFlowLayoutNode.h b/Example/Pods/Headers/Private/Doric/DoricFlowLayoutNode.h new file mode 120000 index 00000000..036911b9 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricFlowLayoutNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricFlowLayoutNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricGroupNode.h b/Example/Pods/Headers/Private/Doric/DoricGroupNode.h new file mode 120000 index 00000000..684ecdfe --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricGroupNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricGroupNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricHLayoutNode.h b/Example/Pods/Headers/Private/Doric/DoricHLayoutNode.h new file mode 120000 index 00000000..69ec5090 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricHLayoutNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricHLayoutNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricHttpJSLoader.h b/Example/Pods/Headers/Private/Doric/DoricHttpJSLoader.h new file mode 120000 index 00000000..58e4f31d --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricHttpJSLoader.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricHttpJSLoader.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricImageNode.h b/Example/Pods/Headers/Private/Doric/DoricImageNode.h new file mode 120000 index 00000000..3ca6b0d5 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricImageNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricImageNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricJSCoreExecutor.h b/Example/Pods/Headers/Private/Doric/DoricJSCoreExecutor.h new file mode 120000 index 00000000..92c65095 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricJSCoreExecutor.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSCoreExecutor.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricJSEngine.h b/Example/Pods/Headers/Private/Doric/DoricJSEngine.h new file mode 120000 index 00000000..b97a9e2d --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricJSEngine.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSEngine.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricJSExecutorProtocol.h b/Example/Pods/Headers/Private/Doric/DoricJSExecutorProtocol.h new file mode 120000 index 00000000..ba38bc3a --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricJSExecutorProtocol.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSExecutorProtocol.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricJSLoaderManager.h b/Example/Pods/Headers/Private/Doric/DoricJSLoaderManager.h new file mode 120000 index 00000000..5d937ca7 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricJSLoaderManager.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricJSLoaderManager.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricJSRemoteArgType.h b/Example/Pods/Headers/Private/Doric/DoricJSRemoteArgType.h new file mode 120000 index 00000000..36bb22aa --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricJSRemoteArgType.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricJSRemoteArgType.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricJSRemoteExecutor.h b/Example/Pods/Headers/Private/Doric/DoricJSRemoteExecutor.h new file mode 120000 index 00000000..767bfc63 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricJSRemoteExecutor.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSRemoteExecutor.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricLayouts.h b/Example/Pods/Headers/Private/Doric/DoricLayouts.h new file mode 120000 index 00000000..99bc6e6e --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricLayouts.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricLayouts.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricListItemNode.h b/Example/Pods/Headers/Private/Doric/DoricListItemNode.h new file mode 120000 index 00000000..6dc52176 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricListItemNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricListItemNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricListNode.h b/Example/Pods/Headers/Private/Doric/DoricListNode.h new file mode 120000 index 00000000..07dcf57c --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricListNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricListNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricLoaderProtocol.h b/Example/Pods/Headers/Private/Doric/DoricLoaderProtocol.h new file mode 120000 index 00000000..56932a3e --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricLoaderProtocol.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricLoaderProtocol.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricLocalServer.h b/Example/Pods/Headers/Private/Doric/DoricLocalServer.h new file mode 120000 index 00000000..b1d57955 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricLocalServer.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Dev/DoricLocalServer.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricMainBundleJSLoader.h b/Example/Pods/Headers/Private/Doric/DoricMainBundleJSLoader.h new file mode 120000 index 00000000..0ae3636f --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricMainBundleJSLoader.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricMainBundleJSLoader.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricModalPlugin.h b/Example/Pods/Headers/Private/Doric/DoricModalPlugin.h new file mode 120000 index 00000000..d147e1e2 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricModalPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricModalPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricNativePlugin.h b/Example/Pods/Headers/Private/Doric/DoricNativePlugin.h new file mode 120000 index 00000000..a1cc9008 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricNativePlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNativePlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricNavBarDelegate.h b/Example/Pods/Headers/Private/Doric/DoricNavBarDelegate.h new file mode 120000 index 00000000..0bdf4473 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricNavBarDelegate.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/NavBar/DoricNavBarDelegate.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricNavBarPlugin.h b/Example/Pods/Headers/Private/Doric/DoricNavBarPlugin.h new file mode 120000 index 00000000..c105c807 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricNavBarPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNavBarPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricNavigatorDelegate.h b/Example/Pods/Headers/Private/Doric/DoricNavigatorDelegate.h new file mode 120000 index 00000000..1b17899a --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricNavigatorDelegate.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Navigator/DoricNavigatorDelegate.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricNavigatorPlugin.h b/Example/Pods/Headers/Private/Doric/DoricNavigatorPlugin.h new file mode 120000 index 00000000..ddc14c51 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricNavigatorPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNavigatorPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricNetworkPlugin.h b/Example/Pods/Headers/Private/Doric/DoricNetworkPlugin.h new file mode 120000 index 00000000..6db4a0df --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricNetworkPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNetworkPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricPanel.h b/Example/Pods/Headers/Private/Doric/DoricPanel.h new file mode 120000 index 00000000..029dd567 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricPanel.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricPanel.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricPopoverPlugin.h b/Example/Pods/Headers/Private/Doric/DoricPopoverPlugin.h new file mode 120000 index 00000000..1df8f593 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricPopoverPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricPopoverPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricPromise.h b/Example/Pods/Headers/Private/Doric/DoricPromise.h new file mode 120000 index 00000000..cf1e8a71 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricPromise.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricPromise.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricRefreshableNode.h b/Example/Pods/Headers/Private/Doric/DoricRefreshableNode.h new file mode 120000 index 00000000..3b184c56 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricRefreshableNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Refresh/DoricRefreshableNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricRegistry.h b/Example/Pods/Headers/Private/Doric/DoricRegistry.h new file mode 120000 index 00000000..3bc74247 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricRegistry.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricRegistry.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricRootNode.h b/Example/Pods/Headers/Private/Doric/DoricRootNode.h new file mode 120000 index 00000000..9c34b186 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricRootNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricRootNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricScrollerNode.h b/Example/Pods/Headers/Private/Doric/DoricScrollerNode.h new file mode 120000 index 00000000..fc0bfaa0 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricScrollerNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricScrollerNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricShaderPlugin.h b/Example/Pods/Headers/Private/Doric/DoricShaderPlugin.h new file mode 120000 index 00000000..ef0174f1 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricShaderPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricShaderPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricSlideItemNode.h b/Example/Pods/Headers/Private/Doric/DoricSlideItemNode.h new file mode 120000 index 00000000..42373673 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricSlideItemNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricSlideItemNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricSliderNode.h b/Example/Pods/Headers/Private/Doric/DoricSliderNode.h new file mode 120000 index 00000000..f2a66df1 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricSliderNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricSliderNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricStackNode.h b/Example/Pods/Headers/Private/Doric/DoricStackNode.h new file mode 120000 index 00000000..717b56d6 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricStackNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricStackNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricStoragePlugin.h b/Example/Pods/Headers/Private/Doric/DoricStoragePlugin.h new file mode 120000 index 00000000..dbefc329 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricStoragePlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricStoragePlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricSuperNode.h b/Example/Pods/Headers/Private/Doric/DoricSuperNode.h new file mode 120000 index 00000000..71a2c250 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricSuperNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricSuperNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricSwipeRefreshLayout.h b/Example/Pods/Headers/Private/Doric/DoricSwipeRefreshLayout.h new file mode 120000 index 00000000..6a0f956b --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricSwipeRefreshLayout.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Refresh/DoricSwipeRefreshLayout.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricTextNode.h b/Example/Pods/Headers/Private/Doric/DoricTextNode.h new file mode 120000 index 00000000..077ae1e2 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricTextNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricTextNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricUtil.h b/Example/Pods/Headers/Private/Doric/DoricUtil.h new file mode 120000 index 00000000..e9b242c5 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricUtil.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricUtil.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricVLayoutNode.h b/Example/Pods/Headers/Private/Doric/DoricVLayoutNode.h new file mode 120000 index 00000000..761dcd57 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricVLayoutNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricVLayoutNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricViewController.h b/Example/Pods/Headers/Private/Doric/DoricViewController.h new file mode 120000 index 00000000..5148b1ae --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricViewController.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricViewController.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricViewNode.h b/Example/Pods/Headers/Private/Doric/DoricViewNode.h new file mode 120000 index 00000000..bdb19d2b --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricViewNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricViewNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/DoricWSClient.h b/Example/Pods/Headers/Private/Doric/DoricWSClient.h new file mode 120000 index 00000000..3546c797 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/DoricWSClient.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Dev/DoricWSClient.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/NSString+JsonString.h b/Example/Pods/Headers/Private/Doric/NSString+JsonString.h new file mode 120000 index 00000000..bf91f3f4 --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/NSString+JsonString.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/Category/NSString+JsonString.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/Doric/UIView+Doric.h b/Example/Pods/Headers/Private/Doric/UIView+Doric.h new file mode 120000 index 00000000..ad01609c --- /dev/null +++ b/Example/Pods/Headers/Private/Doric/UIView+Doric.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/UIView+Doric.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServer.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServer.h new file mode 120000 index 00000000..484d2854 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServer.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServer.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerConnection.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerConnection.h new file mode 120000 index 00000000..255f110d --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerConnection.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerDataRequest.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerDataRequest.h new file mode 120000 index 00000000..83753e86 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerDataRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerDataResponse.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerDataResponse.h new file mode 120000 index 00000000..f31d3c83 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerDataResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerErrorResponse.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerErrorResponse.h new file mode 120000 index 00000000..2c1259dc --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerErrorResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFileRequest.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFileRequest.h new file mode 120000 index 00000000..99a73289 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFileRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFileResponse.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFileResponse.h new file mode 120000 index 00000000..ff33e300 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFileResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFunctions.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFunctions.h new file mode 120000 index 00000000..cd98671b --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerFunctions.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerHTTPStatusCodes.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerHTTPStatusCodes.h new file mode 120000 index 00000000..393ae7a6 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerHTTPStatusCodes.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerMultiPartFormRequest.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerMultiPartFormRequest.h new file mode 120000 index 00000000..593694c2 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerMultiPartFormRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerPrivate.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerPrivate.h new file mode 120000 index 00000000..20e5daf1 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerPrivate.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerPrivate.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerRequest.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerRequest.h new file mode 120000 index 00000000..d1b92e5d --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerResponse.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerResponse.h new file mode 120000 index 00000000..26c37123 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerStreamedResponse.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerStreamedResponse.h new file mode 120000 index 00000000..484c4b1e --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerStreamedResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerURLEncodedFormRequest.h b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerURLEncodedFormRequest.h new file mode 120000 index 00000000..f13ce540 --- /dev/null +++ b/Example/Pods/Headers/Private/GCDWebServer/GCDWebServerURLEncodedFormRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/SocketRocket/SRWebSocket.h b/Example/Pods/Headers/Private/SocketRocket/SRWebSocket.h new file mode 120000 index 00000000..b41c8671 --- /dev/null +++ b/Example/Pods/Headers/Private/SocketRocket/SRWebSocket.h @@ -0,0 +1 @@ +../../../SocketRocket/SocketRocket/SRWebSocket.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/SocketRocket/SocketRocket.h b/Example/Pods/Headers/Private/SocketRocket/SocketRocket.h new file mode 120000 index 00000000..fffaf68d --- /dev/null +++ b/Example/Pods/Headers/Private/SocketRocket/SocketRocket.h @@ -0,0 +1 @@ +../../../SocketRocket/SocketRocket/SocketRocket.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYCache/YYCache.h b/Example/Pods/Headers/Private/YYCache/YYCache.h new file mode 120000 index 00000000..2ba9cf81 --- /dev/null +++ b/Example/Pods/Headers/Private/YYCache/YYCache.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYCache/YYDiskCache.h b/Example/Pods/Headers/Private/YYCache/YYDiskCache.h new file mode 120000 index 00000000..9627fddb --- /dev/null +++ b/Example/Pods/Headers/Private/YYCache/YYDiskCache.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYDiskCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYCache/YYKVStorage.h b/Example/Pods/Headers/Private/YYCache/YYKVStorage.h new file mode 120000 index 00000000..ac9f3181 --- /dev/null +++ b/Example/Pods/Headers/Private/YYCache/YYKVStorage.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYKVStorage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYCache/YYMemoryCache.h b/Example/Pods/Headers/Private/YYCache/YYMemoryCache.h new file mode 120000 index 00000000..e33b84c7 --- /dev/null +++ b/Example/Pods/Headers/Private/YYCache/YYMemoryCache.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYMemoryCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYImage/YYAnimatedImageView.h b/Example/Pods/Headers/Private/YYImage/YYAnimatedImageView.h new file mode 120000 index 00000000..d90694fd --- /dev/null +++ b/Example/Pods/Headers/Private/YYImage/YYAnimatedImageView.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYAnimatedImageView.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYImage/YYFrameImage.h b/Example/Pods/Headers/Private/YYImage/YYFrameImage.h new file mode 120000 index 00000000..173675cd --- /dev/null +++ b/Example/Pods/Headers/Private/YYImage/YYFrameImage.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYFrameImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYImage/YYImage.h b/Example/Pods/Headers/Private/YYImage/YYImage.h new file mode 120000 index 00000000..4abef6cd --- /dev/null +++ b/Example/Pods/Headers/Private/YYImage/YYImage.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYImage/YYImageCoder.h b/Example/Pods/Headers/Private/YYImage/YYImageCoder.h new file mode 120000 index 00000000..dc4425f3 --- /dev/null +++ b/Example/Pods/Headers/Private/YYImage/YYImageCoder.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYImageCoder.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYImage/YYSpriteSheetImage.h b/Example/Pods/Headers/Private/YYImage/YYSpriteSheetImage.h new file mode 120000 index 00000000..ee6cef7d --- /dev/null +++ b/Example/Pods/Headers/Private/YYImage/YYSpriteSheetImage.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYSpriteSheetImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/CALayer+YYWebImage.h b/Example/Pods/Headers/Private/YYWebImage/CALayer+YYWebImage.h new file mode 120000 index 00000000..c4ed8bda --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/CALayer+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/MKAnnotationView+YYWebImage.h b/Example/Pods/Headers/Private/YYWebImage/MKAnnotationView+YYWebImage.h new file mode 120000 index 00000000..1d567032 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/MKAnnotationView+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/UIButton+YYWebImage.h b/Example/Pods/Headers/Private/YYWebImage/UIButton+YYWebImage.h new file mode 120000 index 00000000..e3e36a69 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/UIButton+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/UIImage+YYWebImage.h b/Example/Pods/Headers/Private/YYWebImage/UIImage+YYWebImage.h new file mode 120000 index 00000000..04998ea5 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/UIImage+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/UIImageView+YYWebImage.h b/Example/Pods/Headers/Private/YYWebImage/UIImageView+YYWebImage.h new file mode 120000 index 00000000..556bf052 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/UIImageView+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/YYImageCache.h b/Example/Pods/Headers/Private/YYWebImage/YYImageCache.h new file mode 120000 index 00000000..890cb197 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/YYImageCache.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYImageCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/YYWebImage.h b/Example/Pods/Headers/Private/YYWebImage/YYWebImage.h new file mode 120000 index 00000000..b8d60246 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/YYWebImageManager.h b/Example/Pods/Headers/Private/YYWebImage/YYWebImageManager.h new file mode 120000 index 00000000..b34e2d7b --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/YYWebImageManager.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYWebImageManager.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/YYWebImageOperation.h b/Example/Pods/Headers/Private/YYWebImage/YYWebImageOperation.h new file mode 120000 index 00000000..41dff6a2 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/YYWebImageOperation.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYWebImageOperation.h \ No newline at end of file diff --git a/Example/Pods/Headers/Private/YYWebImage/_YYWebImageSetter.h b/Example/Pods/Headers/Private/YYWebImage/_YYWebImageSetter.h new file mode 120000 index 00000000..9386cd95 --- /dev/null +++ b/Example/Pods/Headers/Private/YYWebImage/_YYWebImageSetter.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/_YYWebImageSetter.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/Doric.h b/Example/Pods/Headers/Public/Doric/Doric.h new file mode 120000 index 00000000..bb37c112 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/Doric.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Doric.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricAnimatePlugin.h b/Example/Pods/Headers/Public/Doric/DoricAnimatePlugin.h new file mode 120000 index 00000000..a12124e4 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricAnimatePlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricAnimatePlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricAsyncResult.h b/Example/Pods/Headers/Public/Doric/DoricAsyncResult.h new file mode 120000 index 00000000..dc8ffde2 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricAsyncResult.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricAsyncResult.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricBridgeExtension.h b/Example/Pods/Headers/Public/Doric/DoricBridgeExtension.h new file mode 120000 index 00000000..c958222e --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricBridgeExtension.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Extension/DoricBridgeExtension.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricConstant.h b/Example/Pods/Headers/Public/Doric/DoricConstant.h new file mode 120000 index 00000000..6680f76a --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricConstant.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricConstant.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricContext.h b/Example/Pods/Headers/Public/Doric/DoricContext.h new file mode 120000 index 00000000..d2e13664 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricContext.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricContext.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricContextHolder.h b/Example/Pods/Headers/Public/Doric/DoricContextHolder.h new file mode 120000 index 00000000..2ad994ce --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricContextHolder.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricContextHolder.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricContextManager.h b/Example/Pods/Headers/Public/Doric/DoricContextManager.h new file mode 120000 index 00000000..09beaea0 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricContextManager.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricContextManager.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricDriver.h b/Example/Pods/Headers/Public/Doric/DoricDriver.h new file mode 120000 index 00000000..a9c106e1 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricDriver.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricDriver.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricExtensions.h b/Example/Pods/Headers/Public/Doric/DoricExtensions.h new file mode 120000 index 00000000..350d3f83 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricExtensions.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricExtensions.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricFlowLayoutItemNode.h b/Example/Pods/Headers/Public/Doric/DoricFlowLayoutItemNode.h new file mode 120000 index 00000000..dc8a572d --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricFlowLayoutItemNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricFlowLayoutItemNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricFlowLayoutNode.h b/Example/Pods/Headers/Public/Doric/DoricFlowLayoutNode.h new file mode 120000 index 00000000..036911b9 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricFlowLayoutNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricFlowLayoutNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricGroupNode.h b/Example/Pods/Headers/Public/Doric/DoricGroupNode.h new file mode 120000 index 00000000..684ecdfe --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricGroupNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricGroupNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricHLayoutNode.h b/Example/Pods/Headers/Public/Doric/DoricHLayoutNode.h new file mode 120000 index 00000000..69ec5090 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricHLayoutNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricHLayoutNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricHttpJSLoader.h b/Example/Pods/Headers/Public/Doric/DoricHttpJSLoader.h new file mode 120000 index 00000000..58e4f31d --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricHttpJSLoader.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricHttpJSLoader.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricImageNode.h b/Example/Pods/Headers/Public/Doric/DoricImageNode.h new file mode 120000 index 00000000..3ca6b0d5 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricImageNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricImageNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricJSCoreExecutor.h b/Example/Pods/Headers/Public/Doric/DoricJSCoreExecutor.h new file mode 120000 index 00000000..92c65095 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricJSCoreExecutor.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSCoreExecutor.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricJSEngine.h b/Example/Pods/Headers/Public/Doric/DoricJSEngine.h new file mode 120000 index 00000000..b97a9e2d --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricJSEngine.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSEngine.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricJSExecutorProtocol.h b/Example/Pods/Headers/Public/Doric/DoricJSExecutorProtocol.h new file mode 120000 index 00000000..ba38bc3a --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricJSExecutorProtocol.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSExecutorProtocol.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricJSLoaderManager.h b/Example/Pods/Headers/Public/Doric/DoricJSLoaderManager.h new file mode 120000 index 00000000..5d937ca7 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricJSLoaderManager.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricJSLoaderManager.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricJSRemoteArgType.h b/Example/Pods/Headers/Public/Doric/DoricJSRemoteArgType.h new file mode 120000 index 00000000..36bb22aa --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricJSRemoteArgType.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricJSRemoteArgType.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricJSRemoteExecutor.h b/Example/Pods/Headers/Public/Doric/DoricJSRemoteExecutor.h new file mode 120000 index 00000000..767bfc63 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricJSRemoteExecutor.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Engine/DoricJSRemoteExecutor.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricLayouts.h b/Example/Pods/Headers/Public/Doric/DoricLayouts.h new file mode 120000 index 00000000..99bc6e6e --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricLayouts.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricLayouts.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricListItemNode.h b/Example/Pods/Headers/Public/Doric/DoricListItemNode.h new file mode 120000 index 00000000..6dc52176 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricListItemNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricListItemNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricListNode.h b/Example/Pods/Headers/Public/Doric/DoricListNode.h new file mode 120000 index 00000000..07dcf57c --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricListNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricListNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricLoaderProtocol.h b/Example/Pods/Headers/Public/Doric/DoricLoaderProtocol.h new file mode 120000 index 00000000..56932a3e --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricLoaderProtocol.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricLoaderProtocol.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricLocalServer.h b/Example/Pods/Headers/Public/Doric/DoricLocalServer.h new file mode 120000 index 00000000..b1d57955 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricLocalServer.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Dev/DoricLocalServer.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricMainBundleJSLoader.h b/Example/Pods/Headers/Public/Doric/DoricMainBundleJSLoader.h new file mode 120000 index 00000000..0ae3636f --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricMainBundleJSLoader.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Loader/DoricMainBundleJSLoader.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricModalPlugin.h b/Example/Pods/Headers/Public/Doric/DoricModalPlugin.h new file mode 120000 index 00000000..d147e1e2 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricModalPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricModalPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricNativePlugin.h b/Example/Pods/Headers/Public/Doric/DoricNativePlugin.h new file mode 120000 index 00000000..a1cc9008 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricNativePlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNativePlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricNavBarDelegate.h b/Example/Pods/Headers/Public/Doric/DoricNavBarDelegate.h new file mode 120000 index 00000000..0bdf4473 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricNavBarDelegate.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/NavBar/DoricNavBarDelegate.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricNavBarPlugin.h b/Example/Pods/Headers/Public/Doric/DoricNavBarPlugin.h new file mode 120000 index 00000000..c105c807 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricNavBarPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNavBarPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricNavigatorDelegate.h b/Example/Pods/Headers/Public/Doric/DoricNavigatorDelegate.h new file mode 120000 index 00000000..1b17899a --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricNavigatorDelegate.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Navigator/DoricNavigatorDelegate.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricNavigatorPlugin.h b/Example/Pods/Headers/Public/Doric/DoricNavigatorPlugin.h new file mode 120000 index 00000000..ddc14c51 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricNavigatorPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNavigatorPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricNetworkPlugin.h b/Example/Pods/Headers/Public/Doric/DoricNetworkPlugin.h new file mode 120000 index 00000000..6db4a0df --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricNetworkPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricNetworkPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricPanel.h b/Example/Pods/Headers/Public/Doric/DoricPanel.h new file mode 120000 index 00000000..029dd567 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricPanel.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricPanel.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricPopoverPlugin.h b/Example/Pods/Headers/Public/Doric/DoricPopoverPlugin.h new file mode 120000 index 00000000..1df8f593 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricPopoverPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricPopoverPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricPromise.h b/Example/Pods/Headers/Public/Doric/DoricPromise.h new file mode 120000 index 00000000..cf1e8a71 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricPromise.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricPromise.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricRefreshableNode.h b/Example/Pods/Headers/Public/Doric/DoricRefreshableNode.h new file mode 120000 index 00000000..3b184c56 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricRefreshableNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Refresh/DoricRefreshableNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricRegistry.h b/Example/Pods/Headers/Public/Doric/DoricRegistry.h new file mode 120000 index 00000000..3bc74247 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricRegistry.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricRegistry.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricRootNode.h b/Example/Pods/Headers/Public/Doric/DoricRootNode.h new file mode 120000 index 00000000..9c34b186 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricRootNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricRootNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricScrollerNode.h b/Example/Pods/Headers/Public/Doric/DoricScrollerNode.h new file mode 120000 index 00000000..fc0bfaa0 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricScrollerNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricScrollerNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricShaderPlugin.h b/Example/Pods/Headers/Public/Doric/DoricShaderPlugin.h new file mode 120000 index 00000000..ef0174f1 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricShaderPlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricShaderPlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricSlideItemNode.h b/Example/Pods/Headers/Public/Doric/DoricSlideItemNode.h new file mode 120000 index 00000000..42373673 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricSlideItemNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricSlideItemNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricSliderNode.h b/Example/Pods/Headers/Public/Doric/DoricSliderNode.h new file mode 120000 index 00000000..f2a66df1 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricSliderNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricSliderNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricStackNode.h b/Example/Pods/Headers/Public/Doric/DoricStackNode.h new file mode 120000 index 00000000..717b56d6 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricStackNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricStackNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricStoragePlugin.h b/Example/Pods/Headers/Public/Doric/DoricStoragePlugin.h new file mode 120000 index 00000000..dbefc329 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricStoragePlugin.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Plugin/DoricStoragePlugin.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricSuperNode.h b/Example/Pods/Headers/Public/Doric/DoricSuperNode.h new file mode 120000 index 00000000..71a2c250 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricSuperNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricSuperNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricSwipeRefreshLayout.h b/Example/Pods/Headers/Public/Doric/DoricSwipeRefreshLayout.h new file mode 120000 index 00000000..6a0f956b --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricSwipeRefreshLayout.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Refresh/DoricSwipeRefreshLayout.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricTextNode.h b/Example/Pods/Headers/Public/Doric/DoricTextNode.h new file mode 120000 index 00000000..077ae1e2 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricTextNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricTextNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricUtil.h b/Example/Pods/Headers/Public/Doric/DoricUtil.h new file mode 120000 index 00000000..e9b242c5 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricUtil.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/DoricUtil.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricVLayoutNode.h b/Example/Pods/Headers/Public/Doric/DoricVLayoutNode.h new file mode 120000 index 00000000..761dcd57 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricVLayoutNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricVLayoutNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricViewController.h b/Example/Pods/Headers/Public/Doric/DoricViewController.h new file mode 120000 index 00000000..5148b1ae --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricViewController.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/DoricViewController.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricViewNode.h b/Example/Pods/Headers/Public/Doric/DoricViewNode.h new file mode 120000 index 00000000..bdb19d2b --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricViewNode.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/DoricViewNode.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/DoricWSClient.h b/Example/Pods/Headers/Public/Doric/DoricWSClient.h new file mode 120000 index 00000000..3546c797 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/DoricWSClient.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Dev/DoricWSClient.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/NSString+JsonString.h b/Example/Pods/Headers/Public/Doric/NSString+JsonString.h new file mode 120000 index 00000000..bf91f3f4 --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/NSString+JsonString.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Util/Category/NSString+JsonString.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/Doric/UIView+Doric.h b/Example/Pods/Headers/Public/Doric/UIView+Doric.h new file mode 120000 index 00000000..ad01609c --- /dev/null +++ b/Example/Pods/Headers/Public/Doric/UIView+Doric.h @@ -0,0 +1 @@ +../../../../../Pod/Classes/Shader/UIView+Doric.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServer.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServer.h new file mode 120000 index 00000000..484d2854 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServer.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServer.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerConnection.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerConnection.h new file mode 120000 index 00000000..255f110d --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerConnection.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerConnection.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerDataRequest.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerDataRequest.h new file mode 120000 index 00000000..83753e86 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerDataRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerDataResponse.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerDataResponse.h new file mode 120000 index 00000000..f31d3c83 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerDataResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerErrorResponse.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerErrorResponse.h new file mode 120000 index 00000000..2c1259dc --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerErrorResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFileRequest.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFileRequest.h new file mode 120000 index 00000000..99a73289 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFileRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFileResponse.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFileResponse.h new file mode 120000 index 00000000..ff33e300 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFileResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFunctions.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFunctions.h new file mode 120000 index 00000000..cd98671b --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerFunctions.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerFunctions.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerHTTPStatusCodes.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerHTTPStatusCodes.h new file mode 120000 index 00000000..393ae7a6 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerHTTPStatusCodes.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerMultiPartFormRequest.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerMultiPartFormRequest.h new file mode 120000 index 00000000..593694c2 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerMultiPartFormRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerRequest.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerRequest.h new file mode 120000 index 00000000..d1b92e5d --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerResponse.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerResponse.h new file mode 120000 index 00000000..26c37123 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Core/GCDWebServerResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerStreamedResponse.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerStreamedResponse.h new file mode 120000 index 00000000..484c4b1e --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerStreamedResponse.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerURLEncodedFormRequest.h b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerURLEncodedFormRequest.h new file mode 120000 index 00000000..f13ce540 --- /dev/null +++ b/Example/Pods/Headers/Public/GCDWebServer/GCDWebServerURLEncodedFormRequest.h @@ -0,0 +1 @@ +../../../GCDWebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/SocketRocket/SRWebSocket.h b/Example/Pods/Headers/Public/SocketRocket/SRWebSocket.h new file mode 120000 index 00000000..b41c8671 --- /dev/null +++ b/Example/Pods/Headers/Public/SocketRocket/SRWebSocket.h @@ -0,0 +1 @@ +../../../SocketRocket/SocketRocket/SRWebSocket.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/SocketRocket/SocketRocket.h b/Example/Pods/Headers/Public/SocketRocket/SocketRocket.h new file mode 120000 index 00000000..fffaf68d --- /dev/null +++ b/Example/Pods/Headers/Public/SocketRocket/SocketRocket.h @@ -0,0 +1 @@ +../../../SocketRocket/SocketRocket/SocketRocket.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYCache/YYCache.h b/Example/Pods/Headers/Public/YYCache/YYCache.h new file mode 120000 index 00000000..2ba9cf81 --- /dev/null +++ b/Example/Pods/Headers/Public/YYCache/YYCache.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYCache/YYDiskCache.h b/Example/Pods/Headers/Public/YYCache/YYDiskCache.h new file mode 120000 index 00000000..9627fddb --- /dev/null +++ b/Example/Pods/Headers/Public/YYCache/YYDiskCache.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYDiskCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYCache/YYKVStorage.h b/Example/Pods/Headers/Public/YYCache/YYKVStorage.h new file mode 120000 index 00000000..ac9f3181 --- /dev/null +++ b/Example/Pods/Headers/Public/YYCache/YYKVStorage.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYKVStorage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYCache/YYMemoryCache.h b/Example/Pods/Headers/Public/YYCache/YYMemoryCache.h new file mode 120000 index 00000000..e33b84c7 --- /dev/null +++ b/Example/Pods/Headers/Public/YYCache/YYMemoryCache.h @@ -0,0 +1 @@ +../../../YYCache/YYCache/YYMemoryCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYImage/YYAnimatedImageView.h b/Example/Pods/Headers/Public/YYImage/YYAnimatedImageView.h new file mode 120000 index 00000000..d90694fd --- /dev/null +++ b/Example/Pods/Headers/Public/YYImage/YYAnimatedImageView.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYAnimatedImageView.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYImage/YYFrameImage.h b/Example/Pods/Headers/Public/YYImage/YYFrameImage.h new file mode 120000 index 00000000..173675cd --- /dev/null +++ b/Example/Pods/Headers/Public/YYImage/YYFrameImage.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYFrameImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYImage/YYImage.h b/Example/Pods/Headers/Public/YYImage/YYImage.h new file mode 120000 index 00000000..4abef6cd --- /dev/null +++ b/Example/Pods/Headers/Public/YYImage/YYImage.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYImage/YYImageCoder.h b/Example/Pods/Headers/Public/YYImage/YYImageCoder.h new file mode 120000 index 00000000..dc4425f3 --- /dev/null +++ b/Example/Pods/Headers/Public/YYImage/YYImageCoder.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYImageCoder.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYImage/YYSpriteSheetImage.h b/Example/Pods/Headers/Public/YYImage/YYSpriteSheetImage.h new file mode 120000 index 00000000..ee6cef7d --- /dev/null +++ b/Example/Pods/Headers/Public/YYImage/YYSpriteSheetImage.h @@ -0,0 +1 @@ +../../../YYImage/YYImage/YYSpriteSheetImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/CALayer+YYWebImage.h b/Example/Pods/Headers/Public/YYWebImage/CALayer+YYWebImage.h new file mode 120000 index 00000000..c4ed8bda --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/CALayer+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/MKAnnotationView+YYWebImage.h b/Example/Pods/Headers/Public/YYWebImage/MKAnnotationView+YYWebImage.h new file mode 120000 index 00000000..1d567032 --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/MKAnnotationView+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/UIButton+YYWebImage.h b/Example/Pods/Headers/Public/YYWebImage/UIButton+YYWebImage.h new file mode 120000 index 00000000..e3e36a69 --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/UIButton+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/UIImage+YYWebImage.h b/Example/Pods/Headers/Public/YYWebImage/UIImage+YYWebImage.h new file mode 120000 index 00000000..04998ea5 --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/UIImage+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/UIImageView+YYWebImage.h b/Example/Pods/Headers/Public/YYWebImage/UIImageView+YYWebImage.h new file mode 120000 index 00000000..556bf052 --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/UIImageView+YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/YYImageCache.h b/Example/Pods/Headers/Public/YYWebImage/YYImageCache.h new file mode 120000 index 00000000..890cb197 --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/YYImageCache.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYImageCache.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/YYWebImage.h b/Example/Pods/Headers/Public/YYWebImage/YYWebImage.h new file mode 120000 index 00000000..b8d60246 --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/YYWebImage.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYWebImage.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/YYWebImageManager.h b/Example/Pods/Headers/Public/YYWebImage/YYWebImageManager.h new file mode 120000 index 00000000..b34e2d7b --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/YYWebImageManager.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYWebImageManager.h \ No newline at end of file diff --git a/Example/Pods/Headers/Public/YYWebImage/YYWebImageOperation.h b/Example/Pods/Headers/Public/YYWebImage/YYWebImageOperation.h new file mode 120000 index 00000000..41dff6a2 --- /dev/null +++ b/Example/Pods/Headers/Public/YYWebImage/YYWebImageOperation.h @@ -0,0 +1 @@ +../../../YYWebImage/YYWebImage/YYWebImageOperation.h \ No newline at end of file diff --git a/Example/Pods/Local Podspecs/Doric.podspec.json b/Example/Pods/Local Podspecs/Doric.podspec.json new file mode 100644 index 00000000..0c56c46e --- /dev/null +++ b/Example/Pods/Local Podspecs/Doric.podspec.json @@ -0,0 +1,46 @@ +{ + "name": "Doric", + "version": "0.1.0", + "summary": "A short description of Doric.", + "description": "TODO: Add long description of the pod here.", + "homepage": "https://github.com/doric-pub/doric", + "license": { + "type": "Apache-2.0", + "file": "LICENSE" + }, + "authors": { + "pengfei.zhou": "pengfeizhou@foxmail.com" + }, + "source": { + "git": "git@github.com:penfeizhou/doric.git", + "tag": "0.1.0" + }, + "platforms": { + "ios": "8.0" + }, + "source_files": "Pod/Classes/**/*", + "resources": "Pod/Assets/*.js", + "resource_bundles": { + "Doric": [ + "Pod/Assets/**/*" + ] + }, + "public_header_files": "Pod/Classes/**/*.h", + "dependencies": { + "YYWebImage": [ + "~>1.0.5" + ], + "YYImage/WebP": [ + + ], + "SocketRocket": [ + "~> 0.5.1" + ], + "GCDWebServer": [ + "~> 3.0" + ], + "YYCache": [ + "~> 1.0.4" + ] + } +} diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock new file mode 100644 index 00000000..93a0efb8 --- /dev/null +++ b/Example/Pods/Manifest.lock @@ -0,0 +1,47 @@ +PODS: + - Doric (0.1.0): + - GCDWebServer (~> 3.0) + - SocketRocket (~> 0.5.1) + - YYCache (~> 1.0.4) + - YYImage/WebP + - YYWebImage (~> 1.0.5) + - GCDWebServer (3.5.3): + - GCDWebServer/Core (= 3.5.3) + - GCDWebServer/Core (3.5.3) + - SocketRocket (0.5.1) + - YYCache (1.0.4) + - YYImage (1.0.4): + - YYImage/Core (= 1.0.4) + - YYImage/Core (1.0.4) + - YYImage/WebP (1.0.4): + - YYImage/Core + - YYWebImage (1.0.5): + - YYCache + - YYImage + +DEPENDENCIES: + - Doric (from `../`) + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - GCDWebServer + - SocketRocket + - YYCache + - YYImage + - YYWebImage + +EXTERNAL SOURCES: + Doric: + :path: "../" + +SPEC CHECKSUMS: + Doric: 38ac111ee84ca27c3838f65f286daf11b537801c + GCDWebServer: c0ab22c73e1b84f358d1e2f74bf6afd1c60829f2 + SocketRocket: d57c7159b83c3c6655745cd15302aa24b6bae531 + YYCache: 8105b6638f5e849296c71f331ff83891a4942952 + YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 + YYWebImage: 5f7f36aee2ae293f016d418c7d6ba05c4863e928 + +PODFILE CHECKSUM: 012563d71439e7e33e976dca3b59664ed56cee39 + +COCOAPODS: 1.7.5 diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 00000000..162cf271 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,2497 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 00612BA0FDFFF760AA31897B256552D3 /* DoricSwipeRefreshLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A08665774EAAEF8A1660FA1D111197E /* DoricSwipeRefreshLayout.m */; }; + 01225045F0F04131A2DCCB419C5A43AF /* MKAnnotationView+YYWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 42D503ACEA2717C15EB4DF8CF7A201FE /* MKAnnotationView+YYWebImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 02F04EB7E8D481AF83AF79996BD87792 /* GCDWebServer-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E45811603BF8F9506F73F073CF69DED /* GCDWebServer-dummy.m */; }; + 03082882CBBA1667A93B7D61BC55B4B4 /* DoricBridgeExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 393ABDC63D186E9C4B3FE6BF2702A7F0 /* DoricBridgeExtension.m */; }; + 046D4C719884BEE359F3E4056B3ADC91 /* YYWebImageOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = F4CDB6C6F9D82E80CAAB690123B906B1 /* YYWebImageOperation.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 071B7DACEB0E33381A899175AD7719CA /* UIButton+YYWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D1FEE803DDD7EDF75D50BC2463E64D7 /* UIButton+YYWebImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 07EB9BB703FC850650DC41BEA946EA62 /* NSString+JsonString.m in Sources */ = {isa = PBXBuildFile; fileRef = 926E73E6AFC56158E380B54AB31357C8 /* NSString+JsonString.m */; }; + 08AF19B42AB684E88852CD6CB8A3AA75 /* DoricNativePlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = D9F3BD70EDB58FADFA2469045295B9F0 /* DoricNativePlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 08C8A6242B5F8A6D3A5817FF9416A518 /* YYFrameImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 6F0D56623E9801EA024276066A9F82DF /* YYFrameImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 08EF09EB19754EE509818C5790D31DA9 /* DoricFlowLayoutNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 5160B449E0CB6266AA04E0124E1C9177 /* DoricFlowLayoutNode.m */; }; + 0B59A1F5BD1F123DB97060971DCF1D99 /* CALayer+YYWebImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BD06F78C15864BFD45D5B8A25C3998C /* CALayer+YYWebImage.m */; }; + 0CC1B3B497098C4159B8A348C85EC4B3 /* YYDiskCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 297A5C040632A2268999EFE516D8BDA8 /* YYDiskCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 0D2A7AA3E2BA05FEB6CC5463F9BEFF15 /* DoricSlideItemNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A0D31EB38392CD7A552D20173BFE8F8 /* DoricSlideItemNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 11B5C2686382CB0A191CBB1065B398B1 /* YYFrameImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 23D1B9EB936178579F8FE3397E1E520B /* YYFrameImage.m */; }; + 11C9CB703C2C92A588EA39CCFE5BCBC2 /* DoricRootNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CB646B53BBE623587150E6AC6766F116 /* DoricRootNode.m */; }; + 1253265B1B37678C169BF6A10A260F9E /* DoricModalPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 4299E4F7E5CCB5540DF92919DFFEDF04 /* DoricModalPlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 144143A659E0FEC033EB44B5C1707BCA /* DoricUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 74F34A3EFBF17EEBFC54EF2F762160E9 /* DoricUtil.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 148E041F6FE689C65ECC968D22391F6B /* DoricExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = BE821F4AD3FE9FD0AE2CDE4A0CB0046C /* DoricExtensions.m */; }; + 15A100A4553D6DAA81BBF7C892BADCA4 /* Doric-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E8B1A44958B7CD262D3DBC144361926 /* Doric-dummy.m */; }; + 191E9735AC97FC3CEE416088EC325F62 /* DoricConstant.h in Headers */ = {isa = PBXBuildFile; fileRef = 194FF25327094949F8678C52FEF82DB0 /* DoricConstant.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1BA55E660DBDCCE258A0E2136464ABD7 /* YYCache-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BE10DD8B0A5B83FCC0B288E5B0A18E3 /* YYCache-dummy.m */; }; + 1D68CC8FCA5AD66ACB98AC5A90B8D6BC /* DoricRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B9E73D207FF6B336BA1D24BB2F45D7A /* DoricRegistry.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1D72D6B46AD74A46178DB3DA00B792E3 /* DoricViewNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 010065ADFC9A1AAAE9671B3153E1721C /* DoricViewNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 1DA05AE266C79E2CC17F3F527E34F7CC /* DoricShaderPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D189235FEF49CB09F61BBF1E61F027C /* DoricShaderPlugin.m */; }; + 1DC222879921DD8E01BDCA6348F0FAEF /* UIImage+YYWebImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 897E4027B05E43D194A2F5003AD48C9F /* UIImage+YYWebImage.m */; }; + 2285FC52EAE17821471935D517C7DACB /* DoricViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D6D5146022097BDD38DF3E944B756D91 /* DoricViewController.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 22B9F8152B9E980E1FEB00B0CA6B27BB /* YYImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = F15350531BD5482A0A2999F0125F07C0 /* YYImageCoder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 235867C4157F363474F02CFECB820F45 /* DoricScrollerNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC27308520C53C314813857696C87F5A /* DoricScrollerNode.m */; }; + 246C99114EE2CC85724B2F425B51D787 /* DoricShaderPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 52E7BB0575F9CA51BC44B597B2E61004 /* DoricShaderPlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 27CAB568DB504CAB9E87A5949ACBF92A /* UIButton+YYWebImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EA8656B3E5CB824E7FFE065291CE2A2 /* UIButton+YYWebImage.m */; }; + 289FEAACBCE2F0DF88ABB6978CFA4AC9 /* YYCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E91879C3619D14D5D1DDA6497E1BA4D /* YYCache.m */; }; + 29F425B1A1A60C85C87E2EA6A84CCBF1 /* DoricTextNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CE2C4FAB25A4BE2CDB1080B7F5E022BA /* DoricTextNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2ADB39CC8762C3F34068C0AE84DDCFF4 /* YYSpriteSheetImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 59C5E7984250BADCE0464A5A7AE3A4AC /* YYSpriteSheetImage.m */; }; + 2BF2F43EB0EDA27FBF764B004723F8F9 /* DoricStoragePlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 71EE75F7A7546F0B0B4CAAABC3A1A221 /* DoricStoragePlugin.m */; }; + 2D97BA46A9241177D41D943032EB6386 /* GCDWebServerDataResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 1ED20AF454A53D1112EA705702F141B8 /* GCDWebServerDataResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 2F5FF6C6FBEE2F4E3BF522CBA1D5ABDC /* YYMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = ACBEDCADBB031E9C2FD75BB370970B85 /* YYMemoryCache.m */; }; + 325A01C30B880AEE4052C5FFABC08513 /* DoricHLayoutNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CDE30A80DC94398F86F48D99C4F77B0 /* DoricHLayoutNode.m */; }; + 33FA845C063B814C8EEEF56B48CD41F8 /* DoricLayouts.h in Headers */ = {isa = PBXBuildFile; fileRef = 91DA09580FAA07E0536E1A1A2629D357 /* DoricLayouts.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 34ADBE5772A6192B7B304B0004D12A75 /* DoricLayouts.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E3E64E2684AC22559A4B60F71F9D093 /* DoricLayouts.m */; }; + 35AA6FEA17185BA5249C86BF97CBBAB5 /* GCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 7112332F6BD7610F1B869696C2154AC5 /* GCDWebServer.m */; }; + 35E55CC007A4866A1F5E3ADE81679130 /* CALayer+YYWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = B99A3A34AEE3672CEEDE0A236C330C5B /* CALayer+YYWebImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3683FDAA91B21FEF6656962C0B18E61B /* YYImage-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 184188E0BB292926A6800D9F590C1F9E /* YYImage-dummy.m */; }; + 3721F80C3FDE8A2B101A119D78201031 /* YYSpriteSheetImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B7668033E6256C31E99870A20817F18 /* YYSpriteSheetImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 38B0845A4B83CE0959531E40A36AADC4 /* YYWebImageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = D9C1B314EECDF555386CE99356E5E8CA /* YYWebImageManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 3925A9BD5798E09BE65375ED85D5A080 /* DoricGroupNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADD883025D3EFB87B7F07F9193AD2C5 /* DoricGroupNode.m */; }; + 39B36A38566CBE323E76AE77CF7F14C8 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E22AAD1FD657A163541F19331362EB37 /* GCDWebServerStreamedResponse.m */; }; + 3F95372A5D8DB12F879C15F58389A702 /* DoricAsyncResult.m in Sources */ = {isa = PBXBuildFile; fileRef = A335B4D91BE640B6C3D4AD680B89F387 /* DoricAsyncResult.m */; }; + 3F9991E0D126667F253AFD5ADE153D75 /* GCDWebServerResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = FAA54994F6C4CB7A1FB7BE3D563728F1 /* GCDWebServerResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 414869E5655141E747BB94588B59075B /* DoricMainBundleJSLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BA6819C98E9CC361D6FA297291BA04C /* DoricMainBundleJSLoader.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 415078A044EA3AF3C8099845471A1FC9 /* DoricLoaderProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B732C93FEF68A791A00E07F7A99C1918 /* DoricLoaderProtocol.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4152104C6EDDADD63E2A0FD4346C704A /* DoricMainBundleJSLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E9713FBFF7F5E5044E6D47719303B01 /* DoricMainBundleJSLoader.m */; }; + 41E53FC8F7F5F7443EA81735B185621F /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = F8C78BC8A578353725FDC61B593A1215 /* GCDWebServerDataResponse.m */; }; + 431B1130829E710EA3C4334BB232430A /* DoricJSCoreExecutor.h in Headers */ = {isa = PBXBuildFile; fileRef = E86208B42727852B3E99D5974D3C200D /* DoricJSCoreExecutor.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 43E035EA4D7778C3A5750C4AA067D517 /* DoricFlowLayoutItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2090560BF70593C57CA6023743148E11 /* DoricFlowLayoutItemNode.m */; }; + 450A3D0C5E5889C279AC63532D0409AD /* DoricImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 74873EAB3FB9D4C0483B89BAF77BA343 /* DoricImageNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 454BDBC91B37775CE9FEAED5EB98A318 /* DoricContextHolder.m in Sources */ = {isa = PBXBuildFile; fileRef = 60848AB93046FA9180CA87F3E762625D /* DoricContextHolder.m */; }; + 456B4EDB3C253A44406F77BC05A674DF /* GCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 957FAE9188CA957E24FA4ED47EA9622F /* GCDWebServerRequest.m */; }; + 45B6A13373ABB67043AC103DC249C1B3 /* DoricContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E2F0A74FC8C6533C7F11B5FB037D850 /* DoricContext.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 46869F78B152AA301623F3FE82F6C0E3 /* DoricAsyncResult.h in Headers */ = {isa = PBXBuildFile; fileRef = DF75BDF041CCE69AB4472C081D5BB413 /* DoricAsyncResult.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 469A1604CBB39942455BBDCC31C15BA3 /* bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0C6AA7C8B725DA881A77BA01E3C34442 /* bundle */; }; + 46C1F76C0AD5370A5F5C10425F0F2BAE /* DoricStoragePlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BBDF9C8B9FB6CF5A8749EB21092432B /* DoricStoragePlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 46C7437D30623CA97CA6E34487731AB9 /* DoricRefreshableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 26D78EFF40478832FD5657A597DE1AF7 /* DoricRefreshableNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 49CFE195ADFA55E91789E15D0C87C01D /* Pods-ExampleTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A44D4CFEB27EB7F87F52864C6B32A80 /* Pods-ExampleTests-dummy.m */; }; + 4D1906A236B1E3163D1F273CA7504B1B /* DoricImageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DE98B1DF2576CD010FCE9BEDE09839B3 /* DoricImageNode.m */; }; + 4D1F2333AFCA191C99238E2F7555D6C0 /* DoricRootNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D67FDB6FFB2698EC2F221337BFD02417 /* DoricRootNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4E6CF5CCD8D1EC5E9A646749383DEC66 /* DoricNavigatorPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = E81407FCC09BBCDBEE1EC5A7CEC70FB8 /* DoricNavigatorPlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 4EA7628A9D84272317EB18D602CB6101 /* DoricJSRemoteExecutor.h in Headers */ = {isa = PBXBuildFile; fileRef = 62B1BB46C6104B4699E35A59FBDCE1B3 /* DoricJSRemoteExecutor.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5117ED4BE4507C48781C5C3DDF810D31 /* DoricNavBarPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AAD7381E4B6574C4526FBC79BA20CDE /* DoricNavBarPlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 517E021259A0510131206568BD7128CC /* DoricScrollerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C30CC1ECCC7B3952D3947A068E1C965 /* DoricScrollerNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 525B0D4969B3DC70F910745701B6FFDE /* YYKVStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 27C5EC2FB50F0055581B04397C8FCE53 /* YYKVStorage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 528525C1C62D4C0D269D0ADEC868C28F /* DoricFlowLayoutNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C15572E97B2FF255C7711CA6F2CAF5D /* DoricFlowLayoutNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5374F9AB9AA0600E3CCA16E92C8896DF /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 74D5C5C6BF3910CB033C8FF9A117A6BF /* GCDWebServerMultiPartFormRequest.m */; }; + 53D32F9170417C4D434CE02C29D28D6D /* DoricJSRemoteExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = D69ACC0B5B7679176EAE83C369DE3C68 /* DoricJSRemoteExecutor.m */; }; + 54EF62B95670DDDF59F337561BA72092 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F249CF0C4C6FE10EDDE26089800C1DA /* GCDWebServerURLEncodedFormRequest.m */; }; + 56E0DDB21CAA1DB9B2209F2F10C5CDFC /* DoricPopoverPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B5F2AD14C3EB39CB7A069965B83AF84 /* DoricPopoverPlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 58EAEE34AF06823DB5089E796271876E /* DoricJSLoaderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A7F3FF1FB691A3120E58F78CD10E6144 /* DoricJSLoaderManager.m */; }; + 59186150B3A10FED46BB81E923BC3803 /* GCDWebServerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 06403BCF3EAD8E815A6E853490C3AD68 /* GCDWebServerPrivate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5A6F8B4FA356338E0C4F7E7BF66DF665 /* DoricJSRemoteArgType.m in Sources */ = {isa = PBXBuildFile; fileRef = 796A5DCDCA570E615FD646C000E98BB7 /* DoricJSRemoteArgType.m */; }; + 5BECF1ACCAAAEBA8D91A27C0A8202E95 /* Pods-ExampleUITests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FE99AB24E829DD35491764E2DED6ED75 /* Pods-ExampleUITests-dummy.m */; }; + 5C6582CABB0B8973C08D1654AA69B835 /* YYImage.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE4F0A156BA817D331B8F2B08D52C94 /* YYImage.m */; }; + 5CD6C1E489C8975D293C7C8EBE642359 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = A2A8B0BD64E3EE6FD74E776143CEABBD /* SocketRocket.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 5DDE21F723A5F11642AE78829F3E48E2 /* DoricJSEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 47A38F2A154C0A098F399ACF6A6BEB0A /* DoricJSEngine.m */; }; + 5EB1DD47EC3AC8BA4BF3D04B510FCDC5 /* DoricNavigatorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 728D29C57333CFD144D9FEBA48C43D24 /* DoricNavigatorDelegate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 601AA4A9A51A6295EA0C587E314B4878 /* DoricContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F3007DD50A74B00DA2B7C46EC8884AE /* DoricContext.m */; }; + 628C00DE711BDAF2D01F87D4E3F507C9 /* DoricDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = 37FC112ED3BAB91DC102CE3A608F44D8 /* DoricDriver.m */; }; + 642A88A3A60378104204D90AF865DCE7 /* Pods-Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 674AA61D65F8AA0A85296F972EB84840 /* Pods-Example-dummy.m */; }; + 64A7E44B95ADC998CAC69B4173F28F5D /* DoricPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 30D26C72C541C3C71C71985CFFAA9FBB /* DoricPanel.m */; }; + 660C17830ED9DAF3CD14FEFEC16572D1 /* GCDWebServerHTTPStatusCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D2AD00F375FC11C8A1A006F89F22DA9 /* GCDWebServerHTTPStatusCodes.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 690D114CE33D424D91E65BCC5AEB0666 /* DoricJSLoaderManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E954660781F9D33A67ED91C8301AEF7D /* DoricJSLoaderManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 695907AB3DC305929A1E5CB534DD6788 /* DoricListItemNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D3C44C69746FC60AE1C36AAC8CA1214 /* DoricListItemNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6C945AC43FC9252239BD4EFA591834C4 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 99A078A2150409AF2197032DC49D9154 /* GCDWebServerFunctions.m */; }; + 6CB9C3842889740D1A1FED2D7A4E839D /* DoricListNode.h in Headers */ = {isa = PBXBuildFile; fileRef = A0267CAB68267A442DFACDDC267F8889 /* DoricListNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 6DEB097B36E0E54C1AB014D6F03C022D /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E5BAB81F1BAD8C5801EF364BA119C61E /* GCDWebServerDataRequest.m */; }; + 6E3B68A0E99E0B47F320EF2331E19B7A /* GCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E538FAF2C02B15BB10C7FD3F0F7A1E7 /* GCDWebServerConnection.m */; }; + 6E5E67F3816F41DDADE88F7461B1DC68 /* DoricGroupNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6EF7D823AD5D2ECD06C2F348E4C6A5EA /* DoricGroupNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7134381ADFB49E13A492C8842DD9136E /* YYWebImage-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6358ABB187AFEBA11B8651FCEAA62B65 /* YYWebImage-dummy.m */; }; + 715F955E5C63DB36EB84F6A043C89F51 /* GCDWebServerFileResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F0CB0F446B24D888852B2364F36277F /* GCDWebServerFileResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 742D298906BD7E18FDE81D0690CE6707 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 4180A5D36AD0F103338812693D8B57A6 /* SRWebSocket.m */; }; + 74DD6E1DC8F5E8EAD77AB903DB2CF145 /* DoricSwipeRefreshLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 43EDD5936F96DC2FA156A398FD5B439A /* DoricSwipeRefreshLayout.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 785D30ACE0CA96360935E2B84255B603 /* SocketRocket-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FDCFEBA818269E535D32E1DD7BB8346B /* SocketRocket-dummy.m */; }; + 7913064CD98682308E4DB915BF9035C2 /* DoricPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = 0743ED4C66205A494631BAF9B34017CB /* DoricPromise.m */; }; + 7A24C07A15E0F108E4737F07A7027B15 /* DoricPanel.h in Headers */ = {isa = PBXBuildFile; fileRef = DB1B8FA21DCFCF77D0E911125BB4D6C4 /* DoricPanel.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7C89E8BE51394D7BAEA8EC62422F805D /* DoricContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 345221108007185F555BBEFC3B40A397 /* DoricContextManager.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 7DAB96BECDDC0A6A989C3B33D4F0E08F /* DoricExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A1D9F56EC0EBE6FF18C4182F5A398 /* DoricExtensions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8189756A6783D7BE61E08FE409BFD871 /* GCDWebServerDataRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B3784257BDE2761D43DC51427849586 /* GCDWebServerDataRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8203F1FE8748893B7FB0F9330E74E40E /* GCDWebServerRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 156A26BD50C08C5F6154CCD8E2EF92BC /* GCDWebServerRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8304203ACC6B6C1F74F891D156D76CFC /* NSString+JsonString.h in Headers */ = {isa = PBXBuildFile; fileRef = 4048EC8080915DFB5BB7BDBA40D63859 /* NSString+JsonString.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 83FE02E47F9826D81EA6A3848189C301 /* DoricSuperNode.h in Headers */ = {isa = PBXBuildFile; fileRef = E06ED02D51FF564980F5EE60C2D0BF3C /* DoricSuperNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 85FCA81F31A3D7F20A3B8573E4EC0CCD /* DoricNativePlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 16C752E94DC71B16E717B961DFA88C27 /* DoricNativePlugin.m */; }; + 8890A8A88FA2ED811319B234B388321C /* GCDWebServerErrorResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 02EAAD3467FB5D76D3EBDC713104BA9C /* GCDWebServerErrorResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 89F0597FC8000460FB5CC2E7FC6319F9 /* DoricViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8471854325653CC0587DFBA14F0E5881 /* DoricViewController.m */; }; + 8BF99A86864A6A5A96D92FDB6E0D4FDB /* GCDWebServerConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = D10710C759F21524183FD049995772B9 /* GCDWebServerConnection.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 8D1B144A742F49683BE2828D810E4F3E /* DoricJSCoreExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CA54A1EB9EB8E6A1B4706A0833E6005 /* DoricJSCoreExecutor.m */; }; + 90CA1D7B5E55DD73D1E54DF69ABAF037 /* YYKVStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = EB0D8BB22CA3C0215357241370EAEAC6 /* YYKVStorage.m */; }; + 9456535481F6736CF42CC6C0CA0F05CC /* DoricConstant.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DCCA68E1E5ABBC70D3D07B2CC8F256F /* DoricConstant.m */; }; + 96A0EDF8241D65DBEB01DDEA2551B6AB /* YYAnimatedImageView.h in Headers */ = {isa = PBXBuildFile; fileRef = 50ED2806A9B5F444A52A22655766AF40 /* YYAnimatedImageView.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9BB07ECB1EFF8E745E9185C4E2C27366 /* DoricNavBarDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = EA15D96F597B03524D56ACE6456BA273 /* DoricNavBarDelegate.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9BDB5E4397AAD710F4446BA3952A2060 /* Doric.h in Headers */ = {isa = PBXBuildFile; fileRef = CF0E2A75C9ED58C64D9679A0FD5B5736 /* Doric.h */; settings = {ATTRIBUTES = (Project, ); }; }; + 9E438C93DCD92B560E17734920ECA41E /* DoricNavigatorPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = B07CF2A8DE9FFCA72BFC4892C4B07622 /* DoricNavigatorPlugin.m */; }; + A44016F8566693840BB920EA21DB7500 /* DoricWSClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F510C5557CFB6D0554B63EC63DD93696 /* DoricWSClient.m */; }; + A57D92BA915B59A21168F7BBF9D965A6 /* UIImage+YYWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 242BBB3556D614E9BAC17FD3A6A6165B /* UIImage+YYWebImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + A595FE2D135AA39316E3E3BC29297BA1 /* DoricListNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DF3AB02E400F4E40C79084448D1A559 /* DoricListNode.m */; }; + A5F7BC027290310D20EDBA4AA2217461 /* YYImageCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B11F16372DCBEBC7EB6991994CAD36D /* YYImageCoder.m */; }; + A603D2CA37138F4E0D2DB37C781A06BF /* DoricRefreshableNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 001CAE6E5551B7443D52904059305768 /* DoricRefreshableNode.m */; }; + A8FC102FD1822315CE435B5E7E0E9D14 /* DoricHttpJSLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EE15792329EC630A175A3896C245D90 /* DoricHttpJSLoader.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AAA8E5B65C1E2F2FF885712766554A58 /* DoricHLayoutNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0933426FA6EC94016EDA5FED7229FA53 /* DoricHLayoutNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AB84AE28B27CF582EF19D36BDC43F98B /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 19A3AC359451033E9EE30FF4A5524918 /* GCDWebServerErrorResponse.m */; }; + AC49DC5F6394CD3B4222E74C0FCC6A27 /* DoricSliderNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B8591B854D2597F78D60308854D9926 /* DoricSliderNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + AE50A6FC10341E2B72B7DAF028954805 /* YYAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = A03175B6FDC3D438CB6057D11FC1174B /* YYAnimatedImageView.m */; }; + B303B392A2EB6D99E98BE556D969CE6C /* GCDWebServerMultiPartFormRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 188F9C29A193D759B8D1D8F7329F12D3 /* GCDWebServerMultiPartFormRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B325F28821AA9ED7BB4684C3E6952965 /* UIImageView+YYWebImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8A03456D6FC2891C95938036545262 /* UIImageView+YYWebImage.m */; }; + B6B56C166B186B33A5A87C791A284F36 /* DoricVLayoutNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 00BABCE2F6DD5317484E2B251ABB7D47 /* DoricVLayoutNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B6DBD08C47B1B79A1BFB12885900AD59 /* DoricAnimatePlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = F1833FF9D06D7E34248B2169098D6C2B /* DoricAnimatePlugin.m */; }; + B73DE88797B200F90C3123B9582631EB /* GCDWebServerStreamedResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 12BD78ED93E0CF7366A357A05AE3631F /* GCDWebServerStreamedResponse.h */; settings = {ATTRIBUTES = (Project, ); }; }; + B87B726F4418B01AF502A9E9C6119098 /* _YYWebImageSetter.m in Sources */ = {isa = PBXBuildFile; fileRef = FCAB9544BCE7D9EA4E5C5DAF6765F84A /* _YYWebImageSetter.m */; }; + B99C4ED46C0396496F882C7254E186C5 /* YYWebImageOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 37E4D0D28BB5E740A02CFA1CC47F2944 /* YYWebImageOperation.m */; }; + BB23FABE2628260AFFF63CD70FBFD25F /* UIImageView+YYWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = D1D1F23C13AA8F4A60395689C2A2972F /* UIImageView+YYWebImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BC8DFE1DD3D2B39277EEFA6D70CCF302 /* DoricFlowLayoutItemNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F7AD6923D5CCBFE31219597055CDCD1 /* DoricFlowLayoutItemNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BCCA8B4ECCE73780AF092C376700FFAA /* DoricNetworkPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 744CAADE898A5F9DBB0C6C22012B3D6D /* DoricNetworkPlugin.m */; }; + BE2A19E5C02AD94897CCDA7324827BCA /* DoricBridgeExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EE7CA83B5FDFD3AB422E44AC3D0BA54 /* DoricBridgeExtension.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BE80E6672A724B9C99DFDB16291E7920 /* YYWebImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C13120C8E5BD1D524867899DCDD16443 /* YYWebImageManager.m */; }; + BEFD64DBD549FDA265DA763D50F83662 /* YYWebImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 97A29E9FC4A082D04395F34455FE48DC /* YYWebImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + BFF89C93F769127B541C6B48460ED91D /* DoricPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D58E98F171A9E38028D0DF093EB0330 /* DoricPromise.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C02DE514B2104B7F5BF56CA865D60E1D /* DoricModalPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = FC17BE12FE8C2FFCCB253BF7E707FA2B /* DoricModalPlugin.m */; }; + C11CBD0FC0CBA29F1F2DAC1AC37AF364 /* _YYWebImageSetter.h in Headers */ = {isa = PBXBuildFile; fileRef = 80C87054F1E43F5CF294CF98FD364DCB /* _YYWebImageSetter.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C3BC5A74FD1503DFA41D5A4E5EC3B672 /* DoricViewNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C739B7C9969E47087480B3F81EA1E14 /* DoricViewNode.m */; }; + C830AF2D550187C31E5AC8A46AA4E76F /* UIView+Doric.h in Headers */ = {isa = PBXBuildFile; fileRef = D61A91E711568804C04D530ECA84B347 /* UIView+Doric.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C83B012BD9B3B5F541AB506122EF966C /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0FA454CFC822DF97658EA00D249150DB /* SRWebSocket.h */; settings = {ATTRIBUTES = (Project, ); }; }; + C907F8799CB0E289D53A5D7BE38EE6FF /* MKAnnotationView+YYWebImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A7242990DCCE6D3F50B2C608F8C7DAA /* MKAnnotationView+YYWebImage.m */; }; + CA21DC6FA6E5CB45B41924611ADC6C9A /* DoricNetworkPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = DF8E338C24524A360CD70AE8C5C4BD9B /* DoricNetworkPlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CC7DF4168FA66F94C7FAC4722D625DE7 /* YYDiskCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F0A42D3B9F17159C2E8EF209BF368966 /* YYDiskCache.m */; }; + CCB8E3339DFB68E7605BE381F977AEF9 /* GCDWebServerURLEncodedFormRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 8AE526EFF2B40DF374482ED45DBF4112 /* GCDWebServerURLEncodedFormRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CE2B11F33301B8F7CEAB17059F0DF2EA /* DoricContextHolder.h in Headers */ = {isa = PBXBuildFile; fileRef = 2545576A3AA348E9CF7FC785DF90BCDE /* DoricContextHolder.h */; settings = {ATTRIBUTES = (Project, ); }; }; + CEBDF3FD9ABF5D2B3C326B21F58D4640 /* DoricJSExecutorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = CC658E0105F8AB9C2152B76052BC481C /* DoricJSExecutorProtocol.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D1014DBF2654D76D637022D52CC87885 /* GCDWebServerFileRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AC63A48456BCE52364F8F75F11114A6 /* GCDWebServerFileRequest.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D23D009449BDA013320B83E96FD34F0C /* DoricListItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9727EA66A9F093D8FA1308F11FDB18A9 /* DoricListItemNode.m */; }; + D4A9AB151E41873EBA01F779AC2DADF4 /* DoricAnimatePlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = F431AB7C271DD9A3026FB8E02A680F7F /* DoricAnimatePlugin.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D67AD5E402E954CDE1C16B744E39BDB1 /* DoricJSEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = B9F783173337956660EADDB6852F3988 /* DoricJSEngine.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D69495C5934065FB2F245BA3A332DE86 /* DoricSliderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = EB2E14EF40F3D133983D89FB98A23289 /* DoricSliderNode.m */; }; + D840F5A06B9893F618D61BB4983DF373 /* YYCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EBC58D82F141C654BB539D797418A5B /* YYCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + D8F9C2440532E05025C22711C00FEDF4 /* GCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 250467AAE5F772A373866D1B28EC4209 /* GCDWebServerResponse.m */; }; + D9B4599A181CC306A7435AD154685666 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CA9CDEF595948D04DA1741FF71C81D9D /* GCDWebServerFileRequest.m */; }; + DA5C200F877DDC6B376E80FC59DB5810 /* DoricPopoverPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = A6E6D86C860C651FF21E3259F3848538 /* DoricPopoverPlugin.m */; }; + DD862728E67ABA06851177E86348C67D /* DoricStackNode.m in Sources */ = {isa = PBXBuildFile; fileRef = C7DFC32B543A9AC293FA82E57A2EEF45 /* DoricStackNode.m */; }; + DDD55545A6FA8C0944DA1A34B564F876 /* DoricWSClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 74700F4F68E283F40E2DE79D53B9523A /* DoricWSClient.h */; settings = {ATTRIBUTES = (Project, ); }; }; + DE1EEF389F8CB34534EAF7F8E4E6A7B7 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 75453DA51095496D971EC3387AAA68AB /* GCDWebServerFileResponse.m */; }; + DECC4D2D1BE5BC5F10509E4C42C1A898 /* DoricHttpJSLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = F1CDF99B5A1018AEF83D898999D07779 /* DoricHttpJSLoader.m */; }; + E5C994E9D29FEFF8CFA6A824D99EAB05 /* YYImage.h in Headers */ = {isa = PBXBuildFile; fileRef = B02FB78D23DCB6E556DB28E6788FBC32 /* YYImage.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E661D35C67269E4A6F71760F5E2A2432 /* DoricSlideItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = FE05C6D16D438BF63DA4C5FBD51D65BD /* DoricSlideItemNode.m */; }; + E6F8BC0728AC2154EF471B332F30ECB2 /* YYImageCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 24541E60AC8A9C984313EC52106ADB35 /* YYImageCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + E95755F0A09A57B158365C41F25AE7A7 /* YYMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A3E3FB0D781D456B0A7EAA3E2F963F5 /* YYMemoryCache.h */; settings = {ATTRIBUTES = (Project, ); }; }; + EAC6F94876E051A213D3D556453486CC /* DoricVLayoutNode.m in Sources */ = {isa = PBXBuildFile; fileRef = B103E58B040A9B8A692C37CDA18D30DB /* DoricVLayoutNode.m */; }; + ECE9EB5004C807E82D576D1E4AFA13D2 /* DoricRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C5F8B8A7C7449D22667D8BE1ECCF225 /* DoricRegistry.m */; }; + ECF57F8EB1CD164D28E1FA2D172F2A1A /* DoricJSRemoteArgType.h in Headers */ = {isa = PBXBuildFile; fileRef = 23763066D1E210BB7DA34D895A030912 /* DoricJSRemoteArgType.h */; settings = {ATTRIBUTES = (Project, ); }; }; + ED616E6493400CB7638DE29CCCD7ABF6 /* DoricDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = E633C6EE0A00DFDEC7C940219332CB2B /* DoricDriver.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F0DD942BF93F6B6E560D77691E37FD4B /* GCDWebServer.h in Headers */ = {isa = PBXBuildFile; fileRef = EC1EF6312171A3F3BBC065A16A025DCD /* GCDWebServer.h */; settings = {ATTRIBUTES = (Project, ); }; }; + F29D4EC239C9F0D6705C9E1CC22576F9 /* DoricTextNode.m in Sources */ = {isa = PBXBuildFile; fileRef = EBD7F2437166A99BA4B48F7B4A0D0A3D /* DoricTextNode.m */; }; + F966178401EEC590EF0A26C79481E4D1 /* DoricSuperNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AFA360A449171381B173D42448150B3 /* DoricSuperNode.m */; }; + FA22D26FA65988469855488530673176 /* DoricContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B222B94B13B3C219D4E51EDF297508AE /* DoricContextManager.m */; }; + FABB9B51C2FFDED22D686E59516612D0 /* DoricUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = EFC7A426556D925CB8F88FFEF8A86603 /* DoricUtil.m */; }; + FBBC942DFE11B502C5C608D9BABBE130 /* UIView+Doric.m in Sources */ = {isa = PBXBuildFile; fileRef = 480534A816C4B0024FA369A3C407F59A /* UIView+Doric.m */; }; + FD1DCE441D6702175FC35DE58D019CAA /* DoricStackNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DA02943572CEA445F2F8653C90453DB7 /* DoricStackNode.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FD404E60462D931E6041A96D39DC024A /* GCDWebServerFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1D40E088948B09DDDA870482BF145738 /* GCDWebServerFunctions.h */; settings = {ATTRIBUTES = (Project, ); }; }; + FE345403F7CCECEE9CBF80C4C2969636 /* YYImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = BCF14F6215D2D31E2C3EE1590B48EA6B /* YYImageCache.m */; }; + FF61AAFF955979FB5139A45AAA356476 /* DoricNavBarPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 49A782176196E9E7E6302765A505C4DF /* DoricNavBarPlugin.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 2313449E8824345CBDB9ED4D3E3BCF32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1948D0B63D2CF6A48E18B0B292BC6091; + remoteInfo = SocketRocket; + }; + 253670C8605F5FFDE1D6606BA634381B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0AEE99A309977BD12A049FF48AF9BA4B; + remoteInfo = "Pods-Example"; + }; + 5D80C63145B83493E429D6C7B05394BB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 488E8C830C5A5694EE916251B9ADAADA; + remoteInfo = YYWebImage; + }; + 5EF22E1C74B909EA29E090C50719BBD9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 488E8C830C5A5694EE916251B9ADAADA; + remoteInfo = YYWebImage; + }; + 5F800553AF429EC9C1F3C6E5C826D04E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1948D0B63D2CF6A48E18B0B292BC6091; + remoteInfo = SocketRocket; + }; + 751D1FF2822550D322644F951A7BDA9F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0AEE99A309977BD12A049FF48AF9BA4B; + remoteInfo = "Pods-Example"; + }; + 77CF2158783C6995303A85B86AFD0AB9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CFDAE6EB02F58B9A37CADCF439AE6082; + remoteInfo = YYCache; + }; + 8479FE8F8C2EC600F1603EF1BFD6CC05 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 822E44240F2922DAB12018A6B649BD19; + remoteInfo = YYImage; + }; + 856CD558A6A4C84FD434D6D2A50A352C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CFDAE6EB02F58B9A37CADCF439AE6082; + remoteInfo = YYCache; + }; + 8BCCEE3CE39180DCABAD08A2A88ABED2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5305059FAA2A7B3246F1874D047D8000; + remoteInfo = "Doric-Doric"; + }; + A79DF282FD90B4C36A64A1AF9B145EDB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AD904F32069787EFB2DFFE05EB82F5BD; + remoteInfo = GCDWebServer; + }; + BE67A29C8EE3211F6E4264C92C4C5784 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 822E44240F2922DAB12018A6B649BD19; + remoteInfo = YYImage; + }; + CBBE8CF6CC81DB32DA2DDF836FC41947 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AD904F32069787EFB2DFFE05EB82F5BD; + remoteInfo = GCDWebServer; + }; + D478AD36DA51D0362FE11615C051F15B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3467588C9AC1920A96217C14EB70DA40; + remoteInfo = Doric; + }; + DD8E4C66891B230FFF1D4817E48DAB1A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 822E44240F2922DAB12018A6B649BD19; + remoteInfo = YYImage; + }; + F5D4CA413DB8AE6664EA56196D8C0C4A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CFDAE6EB02F58B9A37CADCF439AE6082; + remoteInfo = YYCache; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 001CAE6E5551B7443D52904059305768 /* DoricRefreshableNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricRefreshableNode.m; sourceTree = ""; }; + 00BABCE2F6DD5317484E2B251ABB7D47 /* DoricVLayoutNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricVLayoutNode.h; sourceTree = ""; }; + 010065ADFC9A1AAAE9671B3153E1721C /* DoricViewNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricViewNode.h; sourceTree = ""; }; + 02EAAD3467FB5D76D3EBDC713104BA9C /* GCDWebServerErrorResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerErrorResponse.h; path = GCDWebServer/Responses/GCDWebServerErrorResponse.h; sourceTree = ""; }; + 06403BCF3EAD8E815A6E853490C3AD68 /* GCDWebServerPrivate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerPrivate.h; path = GCDWebServer/Core/GCDWebServerPrivate.h; sourceTree = ""; }; + 0743ED4C66205A494631BAF9B34017CB /* DoricPromise.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricPromise.m; sourceTree = ""; }; + 0933426FA6EC94016EDA5FED7229FA53 /* DoricHLayoutNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricHLayoutNode.h; sourceTree = ""; }; + 0A0D31EB38392CD7A552D20173BFE8F8 /* DoricSlideItemNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricSlideItemNode.h; sourceTree = ""; }; + 0AC63A48456BCE52364F8F75F11114A6 /* GCDWebServerFileRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerFileRequest.h; path = GCDWebServer/Requests/GCDWebServerFileRequest.h; sourceTree = ""; }; + 0C6AA7C8B725DA881A77BA01E3C34442 /* bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder; name = bundle; path = "../js-framework/bundle"; sourceTree = ""; }; + 0C6F4829D47D1826F47CDC4264F9F6A8 /* libYYImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libYYImage.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D1FEE803DDD7EDF75D50BC2463E64D7 /* UIButton+YYWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIButton+YYWebImage.h"; path = "YYWebImage/Categories/UIButton+YYWebImage.h"; sourceTree = ""; }; + 0EBC58D82F141C654BB539D797418A5B /* YYCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYCache.h; path = YYCache/YYCache.h; sourceTree = ""; }; + 0F249CF0C4C6FE10EDDE26089800C1DA /* GCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerURLEncodedFormRequest.m; path = GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m; sourceTree = ""; }; + 0FA454CFC822DF97658EA00D249150DB /* SRWebSocket.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SRWebSocket.h; path = SocketRocket/SRWebSocket.h; sourceTree = ""; }; + 12BD78ED93E0CF7366A357A05AE3631F /* GCDWebServerStreamedResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerStreamedResponse.h; path = GCDWebServer/Responses/GCDWebServerStreamedResponse.h; sourceTree = ""; }; + 156A26BD50C08C5F6154CCD8E2EF92BC /* GCDWebServerRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerRequest.h; path = GCDWebServer/Core/GCDWebServerRequest.h; sourceTree = ""; }; + 16C752E94DC71B16E717B961DFA88C27 /* DoricNativePlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricNativePlugin.m; sourceTree = ""; }; + 184188E0BB292926A6800D9F590C1F9E /* YYImage-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "YYImage-dummy.m"; sourceTree = ""; }; + 188F9C29A193D759B8D1D8F7329F12D3 /* GCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerMultiPartFormRequest.h; path = GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h; sourceTree = ""; }; + 194FF25327094949F8678C52FEF82DB0 /* DoricConstant.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricConstant.h; sourceTree = ""; }; + 19A3AC359451033E9EE30FF4A5524918 /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerErrorResponse.m; path = GCDWebServer/Responses/GCDWebServerErrorResponse.m; sourceTree = ""; }; + 1ADD883025D3EFB87B7F07F9193AD2C5 /* DoricGroupNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricGroupNode.m; sourceTree = ""; }; + 1C5F8B8A7C7449D22667D8BE1ECCF225 /* DoricRegistry.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DoricRegistry.m; path = Pod/Classes/DoricRegistry.m; sourceTree = ""; }; + 1D40E088948B09DDDA870482BF145738 /* GCDWebServerFunctions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerFunctions.h; path = GCDWebServer/Core/GCDWebServerFunctions.h; sourceTree = ""; }; + 1DC97F881F42239F4D5EE57FE5266B1F /* Pods-ExampleUITests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ExampleUITests-acknowledgements.plist"; sourceTree = ""; }; + 1E19479558E0A9431B2B5F5A39136939 /* Doric.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Doric.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 1E2F0A74FC8C6533C7F11B5FB037D850 /* DoricContext.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DoricContext.h; path = Pod/Classes/DoricContext.h; sourceTree = ""; }; + 1E538FAF2C02B15BB10C7FD3F0F7A1E7 /* GCDWebServerConnection.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerConnection.m; path = GCDWebServer/Core/GCDWebServerConnection.m; sourceTree = ""; }; + 1ED20AF454A53D1112EA705702F141B8 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerDataResponse.h; path = GCDWebServer/Responses/GCDWebServerDataResponse.h; sourceTree = ""; }; + 1F667CC0E19EAF34E5A4119E2121F585 /* libPods-Example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2090560BF70593C57CA6023743148E11 /* DoricFlowLayoutItemNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricFlowLayoutItemNode.m; sourceTree = ""; }; + 23426BADD33356B8F68F1DA4CA9D59F2 /* ResourceBundle-Doric-Doric-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-Doric-Doric-Info.plist"; sourceTree = ""; }; + 23763066D1E210BB7DA34D895A030912 /* DoricJSRemoteArgType.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricJSRemoteArgType.h; sourceTree = ""; }; + 23D1B9EB936178579F8FE3397E1E520B /* YYFrameImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYFrameImage.m; path = YYImage/YYFrameImage.m; sourceTree = ""; }; + 242BBB3556D614E9BAC17FD3A6A6165B /* UIImage+YYWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+YYWebImage.h"; path = "YYWebImage/Categories/UIImage+YYWebImage.h"; sourceTree = ""; }; + 24541E60AC8A9C984313EC52106ADB35 /* YYImageCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYImageCache.h; path = YYWebImage/YYImageCache.h; sourceTree = ""; }; + 250467AAE5F772A373866D1B28EC4209 /* GCDWebServerResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerResponse.m; path = GCDWebServer/Core/GCDWebServerResponse.m; sourceTree = ""; }; + 2545576A3AA348E9CF7FC785DF90BCDE /* DoricContextHolder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DoricContextHolder.h; path = Pod/Classes/DoricContextHolder.h; sourceTree = ""; }; + 26D78EFF40478832FD5657A597DE1AF7 /* DoricRefreshableNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricRefreshableNode.h; sourceTree = ""; }; + 27C5EC2FB50F0055581B04397C8FCE53 /* YYKVStorage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYKVStorage.h; path = YYCache/YYKVStorage.h; sourceTree = ""; }; + 297A5C040632A2268999EFE516D8BDA8 /* YYDiskCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYDiskCache.h; path = YYCache/YYDiskCache.h; sourceTree = ""; }; + 2AFA360A449171381B173D42448150B3 /* DoricSuperNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricSuperNode.m; sourceTree = ""; }; + 2BD06F78C15864BFD45D5B8A25C3998C /* CALayer+YYWebImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "CALayer+YYWebImage.m"; path = "YYWebImage/Categories/CALayer+YYWebImage.m"; sourceTree = ""; }; + 2C739B7C9969E47087480B3F81EA1E14 /* DoricViewNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricViewNode.m; sourceTree = ""; }; + 2DF3AB02E400F4E40C79084448D1A559 /* DoricListNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricListNode.m; sourceTree = ""; }; + 2E45811603BF8F9506F73F073CF69DED /* GCDWebServer-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GCDWebServer-dummy.m"; sourceTree = ""; }; + 2EE15792329EC630A175A3896C245D90 /* DoricHttpJSLoader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricHttpJSLoader.h; sourceTree = ""; }; + 2EE7CA83B5FDFD3AB422E44AC3D0BA54 /* DoricBridgeExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricBridgeExtension.h; sourceTree = ""; }; + 30D26C72C541C3C71C71985CFFAA9FBB /* DoricPanel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DoricPanel.m; path = Pod/Classes/DoricPanel.m; sourceTree = ""; }; + 345221108007185F555BBEFC3B40A397 /* DoricContextManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DoricContextManager.h; path = Pod/Classes/DoricContextManager.h; sourceTree = ""; }; + 35D3ADD1FF0C9A33D86DB110C181048B /* Pods-ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ExampleTests.debug.xcconfig"; sourceTree = ""; }; + 37E4D0D28BB5E740A02CFA1CC47F2944 /* YYWebImageOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYWebImageOperation.m; path = YYWebImage/YYWebImageOperation.m; sourceTree = ""; }; + 37FC112ED3BAB91DC102CE3A608F44D8 /* DoricDriver.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DoricDriver.m; path = Pod/Classes/DoricDriver.m; sourceTree = ""; }; + 393ABDC63D186E9C4B3FE6BF2702A7F0 /* DoricBridgeExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricBridgeExtension.m; sourceTree = ""; }; + 3B11F16372DCBEBC7EB6991994CAD36D /* YYImageCoder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYImageCoder.m; path = YYImage/YYImageCoder.m; sourceTree = ""; }; + 3B7668033E6256C31E99870A20817F18 /* YYSpriteSheetImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYSpriteSheetImage.h; path = YYImage/YYSpriteSheetImage.h; sourceTree = ""; }; + 3D189235FEF49CB09F61BBF1E61F027C /* DoricShaderPlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricShaderPlugin.m; sourceTree = ""; }; + 3E3E64E2684AC22559A4B60F71F9D093 /* DoricLayouts.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricLayouts.m; sourceTree = ""; }; + 3F3007DD50A74B00DA2B7C46EC8884AE /* DoricContext.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DoricContext.m; path = Pod/Classes/DoricContext.m; sourceTree = ""; }; + 4048EC8080915DFB5BB7BDBA40D63859 /* NSString+JsonString.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "NSString+JsonString.h"; sourceTree = ""; }; + 4180A5D36AD0F103338812693D8B57A6 /* SRWebSocket.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = SRWebSocket.m; path = SocketRocket/SRWebSocket.m; sourceTree = ""; }; + 4299E4F7E5CCB5540DF92919DFFEDF04 /* DoricModalPlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricModalPlugin.h; sourceTree = ""; }; + 42D503ACEA2717C15EB4DF8CF7A201FE /* MKAnnotationView+YYWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "MKAnnotationView+YYWebImage.h"; path = "YYWebImage/Categories/MKAnnotationView+YYWebImage.h"; sourceTree = ""; }; + 43EDD5936F96DC2FA156A398FD5B439A /* DoricSwipeRefreshLayout.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricSwipeRefreshLayout.h; sourceTree = ""; }; + 4426D67B66065EF97BCB0FBC9E35BEE6 /* Pods-Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Example-acknowledgements.markdown"; sourceTree = ""; }; + 47A38F2A154C0A098F399ACF6A6BEB0A /* DoricJSEngine.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricJSEngine.m; sourceTree = ""; }; + 480534A816C4B0024FA369A3C407F59A /* UIView+Doric.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "UIView+Doric.m"; sourceTree = ""; }; + 48ACF38225AF5129416A1F090F6D3286 /* libYYCache.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libYYCache.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 49A782176196E9E7E6302765A505C4DF /* DoricNavBarPlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricNavBarPlugin.m; sourceTree = ""; }; + 4A6BBD5D599973673610A24AF01EF5D7 /* YYWebImage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = YYWebImage.xcconfig; sourceTree = ""; }; + 4B8591B854D2597F78D60308854D9926 /* DoricSliderNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricSliderNode.h; sourceTree = ""; }; + 4C15572E97B2FF255C7711CA6F2CAF5D /* DoricFlowLayoutNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricFlowLayoutNode.h; sourceTree = ""; }; + 4D2AD00F375FC11C8A1A006F89F22DA9 /* GCDWebServerHTTPStatusCodes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerHTTPStatusCodes.h; path = GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h; sourceTree = ""; }; + 4E8A03456D6FC2891C95938036545262 /* UIImageView+YYWebImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+YYWebImage.m"; path = "YYWebImage/Categories/UIImageView+YYWebImage.m"; sourceTree = ""; }; + 4FC1978206616CE319648B302B05AA8A /* libYYWebImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libYYWebImage.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 50CE420621B9D9ED01E06A8EDBD3E990 /* Pods-ExampleUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ExampleUITests.release.xcconfig"; sourceTree = ""; }; + 50ED2806A9B5F444A52A22655766AF40 /* YYAnimatedImageView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYAnimatedImageView.h; path = YYImage/YYAnimatedImageView.h; sourceTree = ""; }; + 5160B449E0CB6266AA04E0124E1C9177 /* DoricFlowLayoutNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricFlowLayoutNode.m; sourceTree = ""; }; + 52E7BB0575F9CA51BC44B597B2E61004 /* DoricShaderPlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricShaderPlugin.h; sourceTree = ""; }; + 57728CFE03A4D88821681C3D55A1FA85 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Example.release.xcconfig"; sourceTree = ""; }; + 59C5E7984250BADCE0464A5A7AE3A4AC /* YYSpriteSheetImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYSpriteSheetImage.m; path = YYImage/YYSpriteSheetImage.m; sourceTree = ""; }; + 5AAD7381E4B6574C4526FBC79BA20CDE /* DoricNavBarPlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricNavBarPlugin.h; sourceTree = ""; }; + 5BA6819C98E9CC361D6FA297291BA04C /* DoricMainBundleJSLoader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricMainBundleJSLoader.h; sourceTree = ""; }; + 5BD2FB5A84E2481DF889A8718306023F /* YYImage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = YYImage.xcconfig; sourceTree = ""; }; + 5C30CC1ECCC7B3952D3947A068E1C965 /* DoricScrollerNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricScrollerNode.h; sourceTree = ""; }; + 60848AB93046FA9180CA87F3E762625D /* DoricContextHolder.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DoricContextHolder.m; path = Pod/Classes/DoricContextHolder.m; sourceTree = ""; }; + 62B1BB46C6104B4699E35A59FBDCE1B3 /* DoricJSRemoteExecutor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricJSRemoteExecutor.h; sourceTree = ""; }; + 630CABE8F692FCDBFD1B122A2D0CE388 /* libPods-ExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6358ABB187AFEBA11B8651FCEAA62B65 /* YYWebImage-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "YYWebImage-dummy.m"; sourceTree = ""; }; + 674AA61D65F8AA0A85296F972EB84840 /* Pods-Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Example-dummy.m"; sourceTree = ""; }; + 677BC4F0CCD6EB2C9FBCB3B375E7349F /* Doric.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Doric.xcconfig; sourceTree = ""; }; + 67CCE9A47A1F1E452AF6E94DFA6F93F7 /* YYWebImage-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "YYWebImage-prefix.pch"; sourceTree = ""; }; + 6B3784257BDE2761D43DC51427849586 /* GCDWebServerDataRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerDataRequest.h; path = GCDWebServer/Requests/GCDWebServerDataRequest.h; sourceTree = ""; }; + 6B5F2AD14C3EB39CB7A069965B83AF84 /* DoricPopoverPlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricPopoverPlugin.h; sourceTree = ""; }; + 6B9E73D207FF6B336BA1D24BB2F45D7A /* DoricRegistry.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DoricRegistry.h; path = Pod/Classes/DoricRegistry.h; sourceTree = ""; }; + 6DCCA68E1E5ABBC70D3D07B2CC8F256F /* DoricConstant.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricConstant.m; sourceTree = ""; }; + 6E2E83CCA4E2E6955BC3C11025F963AA /* WebP.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebP.framework; path = Vendor/WebP.framework; sourceTree = ""; }; + 6EF7D823AD5D2ECD06C2F348E4C6A5EA /* DoricGroupNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricGroupNode.h; sourceTree = ""; }; + 6F0D56623E9801EA024276066A9F82DF /* YYFrameImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYFrameImage.h; path = YYImage/YYFrameImage.h; sourceTree = ""; }; + 7112332F6BD7610F1B869696C2154AC5 /* GCDWebServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServer.m; path = GCDWebServer/Core/GCDWebServer.m; sourceTree = ""; }; + 71EE75F7A7546F0B0B4CAAABC3A1A221 /* DoricStoragePlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricStoragePlugin.m; sourceTree = ""; }; + 728D29C57333CFD144D9FEBA48C43D24 /* DoricNavigatorDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricNavigatorDelegate.h; sourceTree = ""; }; + 744CAADE898A5F9DBB0C6C22012B3D6D /* DoricNetworkPlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricNetworkPlugin.m; sourceTree = ""; }; + 74700F4F68E283F40E2DE79D53B9523A /* DoricWSClient.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricWSClient.h; sourceTree = ""; }; + 74873EAB3FB9D4C0483B89BAF77BA343 /* DoricImageNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricImageNode.h; sourceTree = ""; }; + 74D5C5C6BF3910CB033C8FF9A117A6BF /* GCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerMultiPartFormRequest.m; path = GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m; sourceTree = ""; }; + 74F34A3EFBF17EEBFC54EF2F762160E9 /* DoricUtil.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricUtil.h; sourceTree = ""; }; + 75453DA51095496D971EC3387AAA68AB /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerFileResponse.m; path = GCDWebServer/Responses/GCDWebServerFileResponse.m; sourceTree = ""; }; + 786AB58F4C2451575BBF7722ACDE9BEB /* Pods-ExampleUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ExampleUITests.debug.xcconfig"; sourceTree = ""; }; + 796A5DCDCA570E615FD646C000E98BB7 /* DoricJSRemoteArgType.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricJSRemoteArgType.m; sourceTree = ""; }; + 7A3E3FB0D781D456B0A7EAA3E2F963F5 /* YYMemoryCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYMemoryCache.h; path = YYCache/YYMemoryCache.h; sourceTree = ""; }; + 7BE10DD8B0A5B83FCC0B288E5B0A18E3 /* YYCache-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "YYCache-dummy.m"; sourceTree = ""; }; + 7CDE30A80DC94398F86F48D99C4F77B0 /* DoricHLayoutNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricHLayoutNode.m; sourceTree = ""; }; + 7EA8656B3E5CB824E7FFE065291CE2A2 /* UIButton+YYWebImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIButton+YYWebImage.m"; path = "YYWebImage/Categories/UIButton+YYWebImage.m"; sourceTree = ""; }; + 7F0CB0F446B24D888852B2364F36277F /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerFileResponse.h; path = GCDWebServer/Responses/GCDWebServerFileResponse.h; sourceTree = ""; }; + 8052FAE778A6A09F2260E4E85B371F57 /* Pods-ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ExampleTests.release.xcconfig"; sourceTree = ""; }; + 80C87054F1E43F5CF294CF98FD364DCB /* _YYWebImageSetter.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = _YYWebImageSetter.h; path = YYWebImage/Categories/_YYWebImageSetter.h; sourceTree = ""; }; + 831A1D9F56EC0EBE6FF18C4182F5A398 /* DoricExtensions.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricExtensions.h; sourceTree = ""; }; + 8471854325653CC0587DFBA14F0E5881 /* DoricViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DoricViewController.m; path = Pod/Classes/DoricViewController.m; sourceTree = ""; }; + 85A01882ED06DFEA2E0CE78BCDB204A7 /* libSocketRocket.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSocketRocket.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 87E7F5288DD80B4FDE8896A6D2782093 /* Pods-Example-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Example-resources.sh"; sourceTree = ""; }; + 897E4027B05E43D194A2F5003AD48C9F /* UIImage+YYWebImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+YYWebImage.m"; path = "YYWebImage/Categories/UIImage+YYWebImage.m"; sourceTree = ""; }; + 8A44D4CFEB27EB7F87F52864C6B32A80 /* Pods-ExampleTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ExampleTests-dummy.m"; sourceTree = ""; }; + 8A7242990DCCE6D3F50B2C608F8C7DAA /* MKAnnotationView+YYWebImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "MKAnnotationView+YYWebImage.m"; path = "YYWebImage/Categories/MKAnnotationView+YYWebImage.m"; sourceTree = ""; }; + 8AE526EFF2B40DF374482ED45DBF4112 /* GCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerURLEncodedFormRequest.h; path = GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h; sourceTree = ""; }; + 8BBDF9C8B9FB6CF5A8749EB21092432B /* DoricStoragePlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricStoragePlugin.h; sourceTree = ""; }; + 8CA54A1EB9EB8E6A1B4706A0833E6005 /* DoricJSCoreExecutor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricJSCoreExecutor.m; sourceTree = ""; }; + 8D3C44C69746FC60AE1C36AAC8CA1214 /* DoricListItemNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricListItemNode.h; sourceTree = ""; }; + 8DBE82481F986094549AFA32F59088B6 /* YYCache-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "YYCache-prefix.pch"; sourceTree = ""; }; + 8F4D6D38881BB9FAF4450841BD1BF9CD /* Pods-ExampleUITests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ExampleUITests-acknowledgements.markdown"; sourceTree = ""; }; + 91DA09580FAA07E0536E1A1A2629D357 /* DoricLayouts.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricLayouts.h; sourceTree = ""; }; + 926E73E6AFC56158E380B54AB31357C8 /* NSString+JsonString.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "NSString+JsonString.m"; sourceTree = ""; }; + 957FAE9188CA957E24FA4ED47EA9622F /* GCDWebServerRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerRequest.m; path = GCDWebServer/Core/GCDWebServerRequest.m; sourceTree = ""; }; + 9727EA66A9F093D8FA1308F11FDB18A9 /* DoricListItemNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricListItemNode.m; sourceTree = ""; }; + 97A29E9FC4A082D04395F34455FE48DC /* YYWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYWebImage.h; path = YYWebImage/YYWebImage.h; sourceTree = ""; }; + 98512CF1DCD76DF334D0FDB005736214 /* GCDWebServer.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GCDWebServer.xcconfig; sourceTree = ""; }; + 99A078A2150409AF2197032DC49D9154 /* GCDWebServerFunctions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerFunctions.m; path = GCDWebServer/Core/GCDWebServerFunctions.m; sourceTree = ""; }; + 9A08665774EAAEF8A1660FA1D111197E /* DoricSwipeRefreshLayout.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricSwipeRefreshLayout.m; sourceTree = ""; }; + 9CF6301AAD07F98FE89F30E7B5A07161 /* Pods-ExampleTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ExampleTests-acknowledgements.plist"; sourceTree = ""; }; + 9D58E98F171A9E38028D0DF093EB0330 /* DoricPromise.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricPromise.h; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9E8B1A44958B7CD262D3DBC144361926 /* Doric-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Doric-dummy.m"; sourceTree = ""; }; + 9E91879C3619D14D5D1DDA6497E1BA4D /* YYCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYCache.m; path = YYCache/YYCache.m; sourceTree = ""; }; + 9E9713FBFF7F5E5044E6D47719303B01 /* DoricMainBundleJSLoader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricMainBundleJSLoader.m; sourceTree = ""; }; + 9F7AD6923D5CCBFE31219597055CDCD1 /* DoricFlowLayoutItemNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricFlowLayoutItemNode.h; sourceTree = ""; }; + A0267CAB68267A442DFACDDC267F8889 /* DoricListNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricListNode.h; sourceTree = ""; }; + A03175B6FDC3D438CB6057D11FC1174B /* YYAnimatedImageView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYAnimatedImageView.m; path = YYImage/YYAnimatedImageView.m; sourceTree = ""; }; + A12EA2D6D6F629978F16C0E458152B6E /* Pods-ExampleTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ExampleTests-acknowledgements.markdown"; sourceTree = ""; }; + A2A8B0BD64E3EE6FD74E776143CEABBD /* SocketRocket.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = SocketRocket.h; path = SocketRocket/SocketRocket.h; sourceTree = ""; }; + A335B4D91BE640B6C3D4AD680B89F387 /* DoricAsyncResult.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricAsyncResult.m; sourceTree = ""; }; + A461D3539C89936737BC3759F6AA0407 /* YYCache.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = YYCache.xcconfig; sourceTree = ""; }; + A6E6D86C860C651FF21E3259F3848538 /* DoricPopoverPlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricPopoverPlugin.m; sourceTree = ""; }; + A7F3FF1FB691A3120E58F78CD10E6144 /* DoricJSLoaderManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricJSLoaderManager.m; sourceTree = ""; }; + ACBEDCADBB031E9C2FD75BB370970B85 /* YYMemoryCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYMemoryCache.m; path = YYCache/YYMemoryCache.m; sourceTree = ""; }; + B02FB78D23DCB6E556DB28E6788FBC32 /* YYImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYImage.h; path = YYImage/YYImage.h; sourceTree = ""; }; + B07CF2A8DE9FFCA72BFC4892C4B07622 /* DoricNavigatorPlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricNavigatorPlugin.m; sourceTree = ""; }; + B103E58B040A9B8A692C37CDA18D30DB /* DoricVLayoutNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricVLayoutNode.m; sourceTree = ""; }; + B222B94B13B3C219D4E51EDF297508AE /* DoricContextManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DoricContextManager.m; path = Pod/Classes/DoricContextManager.m; sourceTree = ""; }; + B379C9E71A25B03E5E18907D983826E8 /* GCDWebServer-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GCDWebServer-prefix.pch"; sourceTree = ""; }; + B54C4523BBF2D713FE5A0DC2448FFFB1 /* SocketRocket.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SocketRocket.xcconfig; sourceTree = ""; }; + B68C1052A3B51DBCF7D960F898AAFA95 /* libGCDWebServer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libGCDWebServer.a; sourceTree = BUILT_PRODUCTS_DIR; }; + B732C93FEF68A791A00E07F7A99C1918 /* DoricLoaderProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricLoaderProtocol.h; sourceTree = ""; }; + B99A3A34AEE3672CEEDE0A236C330C5B /* CALayer+YYWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "CALayer+YYWebImage.h"; path = "YYWebImage/Categories/CALayer+YYWebImage.h"; sourceTree = ""; }; + B9F783173337956660EADDB6852F3988 /* DoricJSEngine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricJSEngine.h; sourceTree = ""; }; + BCF14F6215D2D31E2C3EE1590B48EA6B /* YYImageCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYImageCache.m; path = YYWebImage/YYImageCache.m; sourceTree = ""; }; + BE821F4AD3FE9FD0AE2CDE4A0CB0046C /* DoricExtensions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricExtensions.m; sourceTree = ""; }; + C13120C8E5BD1D524867899DCDD16443 /* YYWebImageManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYWebImageManager.m; path = YYWebImage/YYWebImageManager.m; sourceTree = ""; }; + C232398D4E3A40F2337F8A40D7F8D751 /* libPods-ExampleUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ExampleUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + C2EA63FA904BF51E753C7AF5830F1939 /* Doric.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; path = Doric.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + C7DFC32B543A9AC293FA82E57A2EEF45 /* DoricStackNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricStackNode.m; sourceTree = ""; }; + C942D362AA56A8F277A85F2072CBF7CC /* SocketRocket-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SocketRocket-prefix.pch"; sourceTree = ""; }; + CA9CDEF595948D04DA1741FF71C81D9D /* GCDWebServerFileRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerFileRequest.m; path = GCDWebServer/Requests/GCDWebServerFileRequest.m; sourceTree = ""; }; + CB646B53BBE623587150E6AC6766F116 /* DoricRootNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricRootNode.m; sourceTree = ""; }; + CB6504BD07E3990E6F058E4073421ADA /* libDoric.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libDoric.a; sourceTree = BUILT_PRODUCTS_DIR; }; + CC27308520C53C314813857696C87F5A /* DoricScrollerNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricScrollerNode.m; sourceTree = ""; }; + CC658E0105F8AB9C2152B76052BC481C /* DoricJSExecutorProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricJSExecutorProtocol.h; sourceTree = ""; }; + CE2C4FAB25A4BE2CDB1080B7F5E022BA /* DoricTextNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricTextNode.h; sourceTree = ""; }; + CF0E2A75C9ED58C64D9679A0FD5B5736 /* Doric.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = Doric.h; path = Pod/Classes/Doric.h; sourceTree = ""; }; + D10710C759F21524183FD049995772B9 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerConnection.h; path = GCDWebServer/Core/GCDWebServerConnection.h; sourceTree = ""; }; + D1D1F23C13AA8F4A60395689C2A2972F /* UIImageView+YYWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImageView+YYWebImage.h"; path = "YYWebImage/Categories/UIImageView+YYWebImage.h"; sourceTree = ""; }; + D61A91E711568804C04D530ECA84B347 /* UIView+Doric.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "UIView+Doric.h"; sourceTree = ""; }; + D67FDB6FFB2698EC2F221337BFD02417 /* DoricRootNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricRootNode.h; sourceTree = ""; }; + D69ACC0B5B7679176EAE83C369DE3C68 /* DoricJSRemoteExecutor.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricJSRemoteExecutor.m; sourceTree = ""; }; + D6D5146022097BDD38DF3E944B756D91 /* DoricViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DoricViewController.h; path = Pod/Classes/DoricViewController.h; sourceTree = ""; }; + D6FB06D861EC9C7B3A5806FD0936D4EE /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + D9C1B314EECDF555386CE99356E5E8CA /* YYWebImageManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYWebImageManager.h; path = YYWebImage/YYWebImageManager.h; sourceTree = ""; }; + D9F3BD70EDB58FADFA2469045295B9F0 /* DoricNativePlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricNativePlugin.h; sourceTree = ""; }; + DA02943572CEA445F2F8653C90453DB7 /* DoricStackNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricStackNode.h; sourceTree = ""; }; + DB1B8FA21DCFCF77D0E911125BB4D6C4 /* DoricPanel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DoricPanel.h; path = Pod/Classes/DoricPanel.h; sourceTree = ""; }; + DE98B1DF2576CD010FCE9BEDE09839B3 /* DoricImageNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricImageNode.m; sourceTree = ""; }; + DF75BDF041CCE69AB4472C081D5BB413 /* DoricAsyncResult.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricAsyncResult.h; sourceTree = ""; }; + DF8E338C24524A360CD70AE8C5C4BD9B /* DoricNetworkPlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricNetworkPlugin.h; sourceTree = ""; }; + DFFAD17E7567EE856A59F246D50A9B0B /* Pods-Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Example-acknowledgements.plist"; sourceTree = ""; }; + E06ED02D51FF564980F5EE60C2D0BF3C /* DoricSuperNode.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricSuperNode.h; sourceTree = ""; }; + E210810B4D8E485B38EED5F4E4F37D5D /* YYImage-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "YYImage-prefix.pch"; sourceTree = ""; }; + E22AAD1FD657A163541F19331362EB37 /* GCDWebServerStreamedResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerStreamedResponse.m; path = GCDWebServer/Responses/GCDWebServerStreamedResponse.m; sourceTree = ""; }; + E340F908A05867E06E975FDCDD32315C /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Example.debug.xcconfig"; sourceTree = ""; }; + E5B68A2C8EC1D3C756ACAC5C6885BFB7 /* Doric-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Doric-prefix.pch"; sourceTree = ""; }; + E5BAB81F1BAD8C5801EF364BA119C61E /* GCDWebServerDataRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerDataRequest.m; path = GCDWebServer/Requests/GCDWebServerDataRequest.m; sourceTree = ""; }; + E633C6EE0A00DFDEC7C940219332CB2B /* DoricDriver.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DoricDriver.h; path = Pod/Classes/DoricDriver.h; sourceTree = ""; }; + E81407FCC09BBCDBEE1EC5A7CEC70FB8 /* DoricNavigatorPlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricNavigatorPlugin.h; sourceTree = ""; }; + E86208B42727852B3E99D5974D3C200D /* DoricJSCoreExecutor.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricJSCoreExecutor.h; sourceTree = ""; }; + E954660781F9D33A67ED91C8301AEF7D /* DoricJSLoaderManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricJSLoaderManager.h; sourceTree = ""; }; + EA15D96F597B03524D56ACE6456BA273 /* DoricNavBarDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricNavBarDelegate.h; sourceTree = ""; }; + EB0D8BB22CA3C0215357241370EAEAC6 /* YYKVStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYKVStorage.m; path = YYCache/YYKVStorage.m; sourceTree = ""; }; + EB2E14EF40F3D133983D89FB98A23289 /* DoricSliderNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricSliderNode.m; sourceTree = ""; }; + EBD7F2437166A99BA4B48F7B4A0D0A3D /* DoricTextNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricTextNode.m; sourceTree = ""; }; + EC1EF6312171A3F3BBC065A16A025DCD /* GCDWebServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServer.h; path = GCDWebServer/Core/GCDWebServer.h; sourceTree = ""; }; + EEE4F0A156BA817D331B8F2B08D52C94 /* YYImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYImage.m; path = YYImage/YYImage.m; sourceTree = ""; }; + EFC7A426556D925CB8F88FFEF8A86603 /* DoricUtil.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricUtil.m; sourceTree = ""; }; + F0A42D3B9F17159C2E8EF209BF368966 /* YYDiskCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYDiskCache.m; path = YYCache/YYDiskCache.m; sourceTree = ""; }; + F15350531BD5482A0A2999F0125F07C0 /* YYImageCoder.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYImageCoder.h; path = YYImage/YYImageCoder.h; sourceTree = ""; }; + F1833FF9D06D7E34248B2169098D6C2B /* DoricAnimatePlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricAnimatePlugin.m; sourceTree = ""; }; + F1CDF99B5A1018AEF83D898999D07779 /* DoricHttpJSLoader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricHttpJSLoader.m; sourceTree = ""; }; + F431AB7C271DD9A3026FB8E02A680F7F /* DoricAnimatePlugin.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = DoricAnimatePlugin.h; sourceTree = ""; }; + F4CDB6C6F9D82E80CAAB690123B906B1 /* YYWebImageOperation.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYWebImageOperation.h; path = YYWebImage/YYWebImageOperation.h; sourceTree = ""; }; + F510C5557CFB6D0554B63EC63DD93696 /* DoricWSClient.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricWSClient.m; sourceTree = ""; }; + F8C78BC8A578353725FDC61B593A1215 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GCDWebServerDataResponse.m; path = GCDWebServer/Responses/GCDWebServerDataResponse.m; sourceTree = ""; }; + FAA54994F6C4CB7A1FB7BE3D563728F1 /* GCDWebServerResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GCDWebServerResponse.h; path = GCDWebServer/Core/GCDWebServerResponse.h; sourceTree = ""; }; + FC17BE12FE8C2FFCCB253BF7E707FA2B /* DoricModalPlugin.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricModalPlugin.m; sourceTree = ""; }; + FCAB9544BCE7D9EA4E5C5DAF6765F84A /* _YYWebImageSetter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = _YYWebImageSetter.m; path = YYWebImage/Categories/_YYWebImageSetter.m; sourceTree = ""; }; + FDCFEBA818269E535D32E1DD7BB8346B /* SocketRocket-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SocketRocket-dummy.m"; sourceTree = ""; }; + FE05C6D16D438BF63DA4C5FBD51D65BD /* DoricSlideItemNode.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = DoricSlideItemNode.m; sourceTree = ""; }; + FE99AB24E829DD35491764E2DED6ED75 /* Pods-ExampleUITests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ExampleUITests-dummy.m"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0A7300EEDB760E5BBD8CDCDA0E756D9F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0BAD36B7F232A56FB4EDD23DD8325A04 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5D0C362902E19DD7E17835D63591AF07 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6921CB3F3F90A4D3854D879F38B0D619 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7C1CF9A03EB6116AF3ED3831DDA4F75E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9FD779CC3664055065BDD25DBF915088 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C5F5F9AE8591441292295FD9101B9D1C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2ACF8B68A7F91BDA7A718418ABC1827 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EAFB18F53A88C2BC75B17D6832DE0248 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F60109FF999AE00268C7F05690539DAC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0386913182A397CC85877DE4A1878241 /* Support Files */ = { + isa = PBXGroup; + children = ( + 5BD2FB5A84E2481DF889A8718306023F /* YYImage.xcconfig */, + 184188E0BB292926A6800D9F590C1F9E /* YYImage-dummy.m */, + E210810B4D8E485B38EED5F4E4F37D5D /* YYImage-prefix.pch */, + ); + name = "Support Files"; + path = "../Target Support Files/YYImage"; + sourceTree = ""; + }; + 09F33CEB8B7A742B7A434F08266D84ED /* Development Pods */ = { + isa = PBXGroup; + children = ( + 226218E9A30501452870107458681AC4 /* Doric */, + ); + name = "Development Pods"; + sourceTree = ""; + }; + 0A14775E9B55EA90BE6735786B7FFAF2 /* Dev */ = { + isa = PBXGroup; + children = ( + 74700F4F68E283F40E2DE79D53B9523A /* DoricWSClient.h */, + F510C5557CFB6D0554B63EC63DD93696 /* DoricWSClient.m */, + ); + name = Dev; + path = Pod/Classes/Dev; + sourceTree = ""; + }; + 0B488D7903D5D1E34D3CE3728AF78695 /* Core */ = { + isa = PBXGroup; + children = ( + 50ED2806A9B5F444A52A22655766AF40 /* YYAnimatedImageView.h */, + A03175B6FDC3D438CB6057D11FC1174B /* YYAnimatedImageView.m */, + 6F0D56623E9801EA024276066A9F82DF /* YYFrameImage.h */, + 23D1B9EB936178579F8FE3397E1E520B /* YYFrameImage.m */, + B02FB78D23DCB6E556DB28E6788FBC32 /* YYImage.h */, + EEE4F0A156BA817D331B8F2B08D52C94 /* YYImage.m */, + F15350531BD5482A0A2999F0125F07C0 /* YYImageCoder.h */, + 3B11F16372DCBEBC7EB6991994CAD36D /* YYImageCoder.m */, + 3B7668033E6256C31E99870A20817F18 /* YYSpriteSheetImage.h */, + 59C5E7984250BADCE0464A5A7AE3A4AC /* YYSpriteSheetImage.m */, + ); + name = Core; + sourceTree = ""; + }; + 1C55F02B8E232DDC975A0E3CD7268CE8 /* Extension */ = { + isa = PBXGroup; + children = ( + 2EE7CA83B5FDFD3AB422E44AC3D0BA54 /* DoricBridgeExtension.h */, + 393ABDC63D186E9C4B3FE6BF2702A7F0 /* DoricBridgeExtension.m */, + ); + name = Extension; + path = Pod/Classes/Extension; + sourceTree = ""; + }; + 226218E9A30501452870107458681AC4 /* Doric */ = { + isa = PBXGroup; + children = ( + CF0E2A75C9ED58C64D9679A0FD5B5736 /* Doric.h */, + 1E2F0A74FC8C6533C7F11B5FB037D850 /* DoricContext.h */, + 3F3007DD50A74B00DA2B7C46EC8884AE /* DoricContext.m */, + 2545576A3AA348E9CF7FC785DF90BCDE /* DoricContextHolder.h */, + 60848AB93046FA9180CA87F3E762625D /* DoricContextHolder.m */, + 345221108007185F555BBEFC3B40A397 /* DoricContextManager.h */, + B222B94B13B3C219D4E51EDF297508AE /* DoricContextManager.m */, + E633C6EE0A00DFDEC7C940219332CB2B /* DoricDriver.h */, + 37FC112ED3BAB91DC102CE3A608F44D8 /* DoricDriver.m */, + DB1B8FA21DCFCF77D0E911125BB4D6C4 /* DoricPanel.h */, + 30D26C72C541C3C71C71985CFFAA9FBB /* DoricPanel.m */, + 6B9E73D207FF6B336BA1D24BB2F45D7A /* DoricRegistry.h */, + 1C5F8B8A7C7449D22667D8BE1ECCF225 /* DoricRegistry.m */, + D6D5146022097BDD38DF3E944B756D91 /* DoricViewController.h */, + 8471854325653CC0587DFBA14F0E5881 /* DoricViewController.m */, + 0A14775E9B55EA90BE6735786B7FFAF2 /* Dev */, + CEB63A3DB606D32E81FE0BF32E22B691 /* Engine */, + 1C55F02B8E232DDC975A0E3CD7268CE8 /* Extension */, + FBBA8662101AD2449CFD6D1B2909D93B /* Loader */, + 6DAA03EB441845E7844C7ED3562638E7 /* NavBar */, + A174DEC53558BFC58735FBEA8B1E67D9 /* Navigator */, + AE8C97521FE70C203BFC519DADBCE470 /* Plugin */, + 3A4239C711A8862FB15F804092CAEAFA /* Pod */, + 88E32938FD9A92756470F86FC898A97D /* Refresh */, + 54FFF520BCDD899850E6914B4EA794AE /* Resources */, + 651D7D9E623C960D185643DB16D68855 /* Shader */, + EE3A181D5BC943077162DE2E2CE96413 /* Support Files */, + 4D0DEF2EB17B2D289E09324CAF48C685 /* Util */, + ); + name = Doric; + path = ../..; + sourceTree = ""; + }; + 30A8DE6E1E0EBAA1604E04E7381435B0 /* GCDWebServer */ = { + isa = PBXGroup; + children = ( + A4640F0A0B089BFA16FDDE1F844F91B5 /* Core */, + 5BFDA60E04E3DC81BF2B82A41345F11E /* Support Files */, + ); + path = GCDWebServer; + sourceTree = ""; + }; + 3A4239C711A8862FB15F804092CAEAFA /* Pod */ = { + isa = PBXGroup; + children = ( + C2EA63FA904BF51E753C7AF5830F1939 /* Doric.podspec */, + D6FB06D861EC9C7B3A5806FD0936D4EE /* LICENSE */, + ); + name = Pod; + sourceTree = ""; + }; + 4D0DEF2EB17B2D289E09324CAF48C685 /* Util */ = { + isa = PBXGroup; + children = ( + DF75BDF041CCE69AB4472C081D5BB413 /* DoricAsyncResult.h */, + A335B4D91BE640B6C3D4AD680B89F387 /* DoricAsyncResult.m */, + 194FF25327094949F8678C52FEF82DB0 /* DoricConstant.h */, + 6DCCA68E1E5ABBC70D3D07B2CC8F256F /* DoricConstant.m */, + 831A1D9F56EC0EBE6FF18C4182F5A398 /* DoricExtensions.h */, + BE821F4AD3FE9FD0AE2CDE4A0CB0046C /* DoricExtensions.m */, + 23763066D1E210BB7DA34D895A030912 /* DoricJSRemoteArgType.h */, + 796A5DCDCA570E615FD646C000E98BB7 /* DoricJSRemoteArgType.m */, + 74F34A3EFBF17EEBFC54EF2F762160E9 /* DoricUtil.h */, + EFC7A426556D925CB8F88FFEF8A86603 /* DoricUtil.m */, + C2A35DA02CDF3FAC82D9C0EF02892D33 /* Category */, + ); + name = Util; + path = Pod/Classes/Util; + sourceTree = ""; + }; + 54FFF520BCDD899850E6914B4EA794AE /* Resources */ = { + isa = PBXGroup; + children = ( + 0C6AA7C8B725DA881A77BA01E3C34442 /* bundle */, + ); + name = Resources; + sourceTree = ""; + }; + 5B0041E43FEDB21EA4FBC49D23C4CC27 /* WebP */ = { + isa = PBXGroup; + children = ( + 9CC9E6178AAA9C3954F30FD6C2E4B391 /* Frameworks */, + ); + name = WebP; + sourceTree = ""; + }; + 5BFDA60E04E3DC81BF2B82A41345F11E /* Support Files */ = { + isa = PBXGroup; + children = ( + 98512CF1DCD76DF334D0FDB005736214 /* GCDWebServer.xcconfig */, + 2E45811603BF8F9506F73F073CF69DED /* GCDWebServer-dummy.m */, + B379C9E71A25B03E5E18907D983826E8 /* GCDWebServer-prefix.pch */, + ); + name = "Support Files"; + path = "../Target Support Files/GCDWebServer"; + sourceTree = ""; + }; + 6379C9731FB12BD21F5DBD5FAFB59689 /* YYCache */ = { + isa = PBXGroup; + children = ( + 0EBC58D82F141C654BB539D797418A5B /* YYCache.h */, + 9E91879C3619D14D5D1DDA6497E1BA4D /* YYCache.m */, + 297A5C040632A2268999EFE516D8BDA8 /* YYDiskCache.h */, + F0A42D3B9F17159C2E8EF209BF368966 /* YYDiskCache.m */, + 27C5EC2FB50F0055581B04397C8FCE53 /* YYKVStorage.h */, + EB0D8BB22CA3C0215357241370EAEAC6 /* YYKVStorage.m */, + 7A3E3FB0D781D456B0A7EAA3E2F963F5 /* YYMemoryCache.h */, + ACBEDCADBB031E9C2FD75BB370970B85 /* YYMemoryCache.m */, + 8E519A0CE076D0FEC65EACEDE46DA682 /* Support Files */, + ); + path = YYCache; + sourceTree = ""; + }; + 651D7D9E623C960D185643DB16D68855 /* Shader */ = { + isa = PBXGroup; + children = ( + 9F7AD6923D5CCBFE31219597055CDCD1 /* DoricFlowLayoutItemNode.h */, + 2090560BF70593C57CA6023743148E11 /* DoricFlowLayoutItemNode.m */, + 4C15572E97B2FF255C7711CA6F2CAF5D /* DoricFlowLayoutNode.h */, + 5160B449E0CB6266AA04E0124E1C9177 /* DoricFlowLayoutNode.m */, + 6EF7D823AD5D2ECD06C2F348E4C6A5EA /* DoricGroupNode.h */, + 1ADD883025D3EFB87B7F07F9193AD2C5 /* DoricGroupNode.m */, + 0933426FA6EC94016EDA5FED7229FA53 /* DoricHLayoutNode.h */, + 7CDE30A80DC94398F86F48D99C4F77B0 /* DoricHLayoutNode.m */, + 74873EAB3FB9D4C0483B89BAF77BA343 /* DoricImageNode.h */, + DE98B1DF2576CD010FCE9BEDE09839B3 /* DoricImageNode.m */, + 91DA09580FAA07E0536E1A1A2629D357 /* DoricLayouts.h */, + 3E3E64E2684AC22559A4B60F71F9D093 /* DoricLayouts.m */, + 8D3C44C69746FC60AE1C36AAC8CA1214 /* DoricListItemNode.h */, + 9727EA66A9F093D8FA1308F11FDB18A9 /* DoricListItemNode.m */, + A0267CAB68267A442DFACDDC267F8889 /* DoricListNode.h */, + 2DF3AB02E400F4E40C79084448D1A559 /* DoricListNode.m */, + D67FDB6FFB2698EC2F221337BFD02417 /* DoricRootNode.h */, + CB646B53BBE623587150E6AC6766F116 /* DoricRootNode.m */, + 5C30CC1ECCC7B3952D3947A068E1C965 /* DoricScrollerNode.h */, + CC27308520C53C314813857696C87F5A /* DoricScrollerNode.m */, + 0A0D31EB38392CD7A552D20173BFE8F8 /* DoricSlideItemNode.h */, + FE05C6D16D438BF63DA4C5FBD51D65BD /* DoricSlideItemNode.m */, + 4B8591B854D2597F78D60308854D9926 /* DoricSliderNode.h */, + EB2E14EF40F3D133983D89FB98A23289 /* DoricSliderNode.m */, + DA02943572CEA445F2F8653C90453DB7 /* DoricStackNode.h */, + C7DFC32B543A9AC293FA82E57A2EEF45 /* DoricStackNode.m */, + E06ED02D51FF564980F5EE60C2D0BF3C /* DoricSuperNode.h */, + 2AFA360A449171381B173D42448150B3 /* DoricSuperNode.m */, + CE2C4FAB25A4BE2CDB1080B7F5E022BA /* DoricTextNode.h */, + EBD7F2437166A99BA4B48F7B4A0D0A3D /* DoricTextNode.m */, + 010065ADFC9A1AAAE9671B3153E1721C /* DoricViewNode.h */, + 2C739B7C9969E47087480B3F81EA1E14 /* DoricViewNode.m */, + 00BABCE2F6DD5317484E2B251ABB7D47 /* DoricVLayoutNode.h */, + B103E58B040A9B8A692C37CDA18D30DB /* DoricVLayoutNode.m */, + D61A91E711568804C04D530ECA84B347 /* UIView+Doric.h */, + 480534A816C4B0024FA369A3C407F59A /* UIView+Doric.m */, + ); + name = Shader; + path = Pod/Classes/Shader; + sourceTree = ""; + }; + 6DAA03EB441845E7844C7ED3562638E7 /* NavBar */ = { + isa = PBXGroup; + children = ( + EA15D96F597B03524D56ACE6456BA273 /* DoricNavBarDelegate.h */, + ); + name = NavBar; + path = Pod/Classes/NavBar; + sourceTree = ""; + }; + 71AEF72537B62D028A21E00FBEC2111B /* Pods-Example */ = { + isa = PBXGroup; + children = ( + 4426D67B66065EF97BCB0FBC9E35BEE6 /* Pods-Example-acknowledgements.markdown */, + DFFAD17E7567EE856A59F246D50A9B0B /* Pods-Example-acknowledgements.plist */, + 674AA61D65F8AA0A85296F972EB84840 /* Pods-Example-dummy.m */, + 87E7F5288DD80B4FDE8896A6D2782093 /* Pods-Example-resources.sh */, + E340F908A05867E06E975FDCDD32315C /* Pods-Example.debug.xcconfig */, + 57728CFE03A4D88821681C3D55A1FA85 /* Pods-Example.release.xcconfig */, + ); + name = "Pods-Example"; + path = "Target Support Files/Pods-Example"; + sourceTree = ""; + }; + 71D7AAC4D13F3A060E368F44B7510778 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 71AEF72537B62D028A21E00FBEC2111B /* Pods-Example */, + F4EB4F3E7D139A61CACFFAFCA3EF3B30 /* Pods-ExampleTests */, + 87BF90E86730F73BF5CB57472930C8FD /* Pods-ExampleUITests */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 832BEB4FEBC3730E23FC8795E1CA6AA6 /* YYImage */ = { + isa = PBXGroup; + children = ( + 0B488D7903D5D1E34D3CE3728AF78695 /* Core */, + 0386913182A397CC85877DE4A1878241 /* Support Files */, + 5B0041E43FEDB21EA4FBC49D23C4CC27 /* WebP */, + ); + path = YYImage; + sourceTree = ""; + }; + 87BF90E86730F73BF5CB57472930C8FD /* Pods-ExampleUITests */ = { + isa = PBXGroup; + children = ( + 8F4D6D38881BB9FAF4450841BD1BF9CD /* Pods-ExampleUITests-acknowledgements.markdown */, + 1DC97F881F42239F4D5EE57FE5266B1F /* Pods-ExampleUITests-acknowledgements.plist */, + FE99AB24E829DD35491764E2DED6ED75 /* Pods-ExampleUITests-dummy.m */, + 786AB58F4C2451575BBF7722ACDE9BEB /* Pods-ExampleUITests.debug.xcconfig */, + 50CE420621B9D9ED01E06A8EDBD3E990 /* Pods-ExampleUITests.release.xcconfig */, + ); + name = "Pods-ExampleUITests"; + path = "Target Support Files/Pods-ExampleUITests"; + sourceTree = ""; + }; + 88E32938FD9A92756470F86FC898A97D /* Refresh */ = { + isa = PBXGroup; + children = ( + 26D78EFF40478832FD5657A597DE1AF7 /* DoricRefreshableNode.h */, + 001CAE6E5551B7443D52904059305768 /* DoricRefreshableNode.m */, + 43EDD5936F96DC2FA156A398FD5B439A /* DoricSwipeRefreshLayout.h */, + 9A08665774EAAEF8A1660FA1D111197E /* DoricSwipeRefreshLayout.m */, + ); + name = Refresh; + path = Pod/Classes/Refresh; + sourceTree = ""; + }; + 8E519A0CE076D0FEC65EACEDE46DA682 /* Support Files */ = { + isa = PBXGroup; + children = ( + A461D3539C89936737BC3759F6AA0407 /* YYCache.xcconfig */, + 7BE10DD8B0A5B83FCC0B288E5B0A18E3 /* YYCache-dummy.m */, + 8DBE82481F986094549AFA32F59088B6 /* YYCache-prefix.pch */, + ); + name = "Support Files"; + path = "../Target Support Files/YYCache"; + sourceTree = ""; + }; + 90C0B47BBD1F2F5B6A4EA44F309FD09B /* Support Files */ = { + isa = PBXGroup; + children = ( + 4A6BBD5D599973673610A24AF01EF5D7 /* YYWebImage.xcconfig */, + 6358ABB187AFEBA11B8651FCEAA62B65 /* YYWebImage-dummy.m */, + 67CCE9A47A1F1E452AF6E94DFA6F93F7 /* YYWebImage-prefix.pch */, + ); + name = "Support Files"; + path = "../Target Support Files/YYWebImage"; + sourceTree = ""; + }; + 9CC9E6178AAA9C3954F30FD6C2E4B391 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6E2E83CCA4E2E6955BC3C11025F963AA /* WebP.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9ED971B78F2FD589A1C4064717FB44A4 /* Support Files */ = { + isa = PBXGroup; + children = ( + B54C4523BBF2D713FE5A0DC2448FFFB1 /* SocketRocket.xcconfig */, + FDCFEBA818269E535D32E1DD7BB8346B /* SocketRocket-dummy.m */, + C942D362AA56A8F277A85F2072CBF7CC /* SocketRocket-prefix.pch */, + ); + name = "Support Files"; + path = "../Target Support Files/SocketRocket"; + sourceTree = ""; + }; + A174DEC53558BFC58735FBEA8B1E67D9 /* Navigator */ = { + isa = PBXGroup; + children = ( + 728D29C57333CFD144D9FEBA48C43D24 /* DoricNavigatorDelegate.h */, + ); + name = Navigator; + path = Pod/Classes/Navigator; + sourceTree = ""; + }; + A4640F0A0B089BFA16FDDE1F844F91B5 /* Core */ = { + isa = PBXGroup; + children = ( + EC1EF6312171A3F3BBC065A16A025DCD /* GCDWebServer.h */, + 7112332F6BD7610F1B869696C2154AC5 /* GCDWebServer.m */, + D10710C759F21524183FD049995772B9 /* GCDWebServerConnection.h */, + 1E538FAF2C02B15BB10C7FD3F0F7A1E7 /* GCDWebServerConnection.m */, + 6B3784257BDE2761D43DC51427849586 /* GCDWebServerDataRequest.h */, + E5BAB81F1BAD8C5801EF364BA119C61E /* GCDWebServerDataRequest.m */, + 1ED20AF454A53D1112EA705702F141B8 /* GCDWebServerDataResponse.h */, + F8C78BC8A578353725FDC61B593A1215 /* GCDWebServerDataResponse.m */, + 02EAAD3467FB5D76D3EBDC713104BA9C /* GCDWebServerErrorResponse.h */, + 19A3AC359451033E9EE30FF4A5524918 /* GCDWebServerErrorResponse.m */, + 0AC63A48456BCE52364F8F75F11114A6 /* GCDWebServerFileRequest.h */, + CA9CDEF595948D04DA1741FF71C81D9D /* GCDWebServerFileRequest.m */, + 7F0CB0F446B24D888852B2364F36277F /* GCDWebServerFileResponse.h */, + 75453DA51095496D971EC3387AAA68AB /* GCDWebServerFileResponse.m */, + 1D40E088948B09DDDA870482BF145738 /* GCDWebServerFunctions.h */, + 99A078A2150409AF2197032DC49D9154 /* GCDWebServerFunctions.m */, + 4D2AD00F375FC11C8A1A006F89F22DA9 /* GCDWebServerHTTPStatusCodes.h */, + 188F9C29A193D759B8D1D8F7329F12D3 /* GCDWebServerMultiPartFormRequest.h */, + 74D5C5C6BF3910CB033C8FF9A117A6BF /* GCDWebServerMultiPartFormRequest.m */, + 06403BCF3EAD8E815A6E853490C3AD68 /* GCDWebServerPrivate.h */, + 156A26BD50C08C5F6154CCD8E2EF92BC /* GCDWebServerRequest.h */, + 957FAE9188CA957E24FA4ED47EA9622F /* GCDWebServerRequest.m */, + FAA54994F6C4CB7A1FB7BE3D563728F1 /* GCDWebServerResponse.h */, + 250467AAE5F772A373866D1B28EC4209 /* GCDWebServerResponse.m */, + 12BD78ED93E0CF7366A357A05AE3631F /* GCDWebServerStreamedResponse.h */, + E22AAD1FD657A163541F19331362EB37 /* GCDWebServerStreamedResponse.m */, + 8AE526EFF2B40DF374482ED45DBF4112 /* GCDWebServerURLEncodedFormRequest.h */, + 0F249CF0C4C6FE10EDDE26089800C1DA /* GCDWebServerURLEncodedFormRequest.m */, + ); + name = Core; + sourceTree = ""; + }; + AE8C97521FE70C203BFC519DADBCE470 /* Plugin */ = { + isa = PBXGroup; + children = ( + F431AB7C271DD9A3026FB8E02A680F7F /* DoricAnimatePlugin.h */, + F1833FF9D06D7E34248B2169098D6C2B /* DoricAnimatePlugin.m */, + 4299E4F7E5CCB5540DF92919DFFEDF04 /* DoricModalPlugin.h */, + FC17BE12FE8C2FFCCB253BF7E707FA2B /* DoricModalPlugin.m */, + D9F3BD70EDB58FADFA2469045295B9F0 /* DoricNativePlugin.h */, + 16C752E94DC71B16E717B961DFA88C27 /* DoricNativePlugin.m */, + 5AAD7381E4B6574C4526FBC79BA20CDE /* DoricNavBarPlugin.h */, + 49A782176196E9E7E6302765A505C4DF /* DoricNavBarPlugin.m */, + E81407FCC09BBCDBEE1EC5A7CEC70FB8 /* DoricNavigatorPlugin.h */, + B07CF2A8DE9FFCA72BFC4892C4B07622 /* DoricNavigatorPlugin.m */, + DF8E338C24524A360CD70AE8C5C4BD9B /* DoricNetworkPlugin.h */, + 744CAADE898A5F9DBB0C6C22012B3D6D /* DoricNetworkPlugin.m */, + 6B5F2AD14C3EB39CB7A069965B83AF84 /* DoricPopoverPlugin.h */, + A6E6D86C860C651FF21E3259F3848538 /* DoricPopoverPlugin.m */, + 9D58E98F171A9E38028D0DF093EB0330 /* DoricPromise.h */, + 0743ED4C66205A494631BAF9B34017CB /* DoricPromise.m */, + 52E7BB0575F9CA51BC44B597B2E61004 /* DoricShaderPlugin.h */, + 3D189235FEF49CB09F61BBF1E61F027C /* DoricShaderPlugin.m */, + 8BBDF9C8B9FB6CF5A8749EB21092432B /* DoricStoragePlugin.h */, + 71EE75F7A7546F0B0B4CAAABC3A1A221 /* DoricStoragePlugin.m */, + ); + name = Plugin; + path = Pod/Classes/Plugin; + sourceTree = ""; + }; + C2A35DA02CDF3FAC82D9C0EF02892D33 /* Category */ = { + isa = PBXGroup; + children = ( + 4048EC8080915DFB5BB7BDBA40D63859 /* NSString+JsonString.h */, + 926E73E6AFC56158E380B54AB31357C8 /* NSString+JsonString.m */, + ); + path = Category; + sourceTree = ""; + }; + C5DD576F1F37833FBDF2EBC49CA8A2FA /* Pods */ = { + isa = PBXGroup; + children = ( + 30A8DE6E1E0EBAA1604E04E7381435B0 /* GCDWebServer */, + FCB7AC0056F85EBE133A94D945422814 /* SocketRocket */, + 6379C9731FB12BD21F5DBD5FAFB59689 /* YYCache */, + 832BEB4FEBC3730E23FC8795E1CA6AA6 /* YYImage */, + E2482C626B72B053A909FF1D41D9B49C /* YYWebImage */, + ); + name = Pods; + sourceTree = ""; + }; + CEB63A3DB606D32E81FE0BF32E22B691 /* Engine */ = { + isa = PBXGroup; + children = ( + E86208B42727852B3E99D5974D3C200D /* DoricJSCoreExecutor.h */, + 8CA54A1EB9EB8E6A1B4706A0833E6005 /* DoricJSCoreExecutor.m */, + B9F783173337956660EADDB6852F3988 /* DoricJSEngine.h */, + 47A38F2A154C0A098F399ACF6A6BEB0A /* DoricJSEngine.m */, + CC658E0105F8AB9C2152B76052BC481C /* DoricJSExecutorProtocol.h */, + 62B1BB46C6104B4699E35A59FBDCE1B3 /* DoricJSRemoteExecutor.h */, + D69ACC0B5B7679176EAE83C369DE3C68 /* DoricJSRemoteExecutor.m */, + ); + name = Engine; + path = Pod/Classes/Engine; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + 09F33CEB8B7A742B7A434F08266D84ED /* Development Pods */, + D89477F20FB1DE18A04690586D7808C4 /* Frameworks */, + C5DD576F1F37833FBDF2EBC49CA8A2FA /* Pods */, + F84D7922A6B05CDCB57606E11D08173B /* Products */, + 71D7AAC4D13F3A060E368F44B7510778 /* Targets Support Files */, + ); + sourceTree = ""; + }; + D89477F20FB1DE18A04690586D7808C4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + E2482C626B72B053A909FF1D41D9B49C /* YYWebImage */ = { + isa = PBXGroup; + children = ( + 80C87054F1E43F5CF294CF98FD364DCB /* _YYWebImageSetter.h */, + FCAB9544BCE7D9EA4E5C5DAF6765F84A /* _YYWebImageSetter.m */, + B99A3A34AEE3672CEEDE0A236C330C5B /* CALayer+YYWebImage.h */, + 2BD06F78C15864BFD45D5B8A25C3998C /* CALayer+YYWebImage.m */, + 42D503ACEA2717C15EB4DF8CF7A201FE /* MKAnnotationView+YYWebImage.h */, + 8A7242990DCCE6D3F50B2C608F8C7DAA /* MKAnnotationView+YYWebImage.m */, + 0D1FEE803DDD7EDF75D50BC2463E64D7 /* UIButton+YYWebImage.h */, + 7EA8656B3E5CB824E7FFE065291CE2A2 /* UIButton+YYWebImage.m */, + 242BBB3556D614E9BAC17FD3A6A6165B /* UIImage+YYWebImage.h */, + 897E4027B05E43D194A2F5003AD48C9F /* UIImage+YYWebImage.m */, + D1D1F23C13AA8F4A60395689C2A2972F /* UIImageView+YYWebImage.h */, + 4E8A03456D6FC2891C95938036545262 /* UIImageView+YYWebImage.m */, + 24541E60AC8A9C984313EC52106ADB35 /* YYImageCache.h */, + BCF14F6215D2D31E2C3EE1590B48EA6B /* YYImageCache.m */, + 97A29E9FC4A082D04395F34455FE48DC /* YYWebImage.h */, + D9C1B314EECDF555386CE99356E5E8CA /* YYWebImageManager.h */, + C13120C8E5BD1D524867899DCDD16443 /* YYWebImageManager.m */, + F4CDB6C6F9D82E80CAAB690123B906B1 /* YYWebImageOperation.h */, + 37E4D0D28BB5E740A02CFA1CC47F2944 /* YYWebImageOperation.m */, + 90C0B47BBD1F2F5B6A4EA44F309FD09B /* Support Files */, + ); + path = YYWebImage; + sourceTree = ""; + }; + EE3A181D5BC943077162DE2E2CE96413 /* Support Files */ = { + isa = PBXGroup; + children = ( + 677BC4F0CCD6EB2C9FBCB3B375E7349F /* Doric.xcconfig */, + 9E8B1A44958B7CD262D3DBC144361926 /* Doric-dummy.m */, + E5B68A2C8EC1D3C756ACAC5C6885BFB7 /* Doric-prefix.pch */, + 23426BADD33356B8F68F1DA4CA9D59F2 /* ResourceBundle-Doric-Doric-Info.plist */, + ); + name = "Support Files"; + path = "Example/Pods/Target Support Files/Doric"; + sourceTree = ""; + }; + F4EB4F3E7D139A61CACFFAFCA3EF3B30 /* Pods-ExampleTests */ = { + isa = PBXGroup; + children = ( + A12EA2D6D6F629978F16C0E458152B6E /* Pods-ExampleTests-acknowledgements.markdown */, + 9CF6301AAD07F98FE89F30E7B5A07161 /* Pods-ExampleTests-acknowledgements.plist */, + 8A44D4CFEB27EB7F87F52864C6B32A80 /* Pods-ExampleTests-dummy.m */, + 35D3ADD1FF0C9A33D86DB110C181048B /* Pods-ExampleTests.debug.xcconfig */, + 8052FAE778A6A09F2260E4E85B371F57 /* Pods-ExampleTests.release.xcconfig */, + ); + name = "Pods-ExampleTests"; + path = "Target Support Files/Pods-ExampleTests"; + sourceTree = ""; + }; + F84D7922A6B05CDCB57606E11D08173B /* Products */ = { + isa = PBXGroup; + children = ( + 1E19479558E0A9431B2B5F5A39136939 /* Doric.bundle */, + CB6504BD07E3990E6F058E4073421ADA /* libDoric.a */, + B68C1052A3B51DBCF7D960F898AAFA95 /* libGCDWebServer.a */, + 1F667CC0E19EAF34E5A4119E2121F585 /* libPods-Example.a */, + 630CABE8F692FCDBFD1B122A2D0CE388 /* libPods-ExampleTests.a */, + C232398D4E3A40F2337F8A40D7F8D751 /* libPods-ExampleUITests.a */, + 85A01882ED06DFEA2E0CE78BCDB204A7 /* libSocketRocket.a */, + 48ACF38225AF5129416A1F090F6D3286 /* libYYCache.a */, + 0C6F4829D47D1826F47CDC4264F9F6A8 /* libYYImage.a */, + 4FC1978206616CE319648B302B05AA8A /* libYYWebImage.a */, + ); + name = Products; + sourceTree = ""; + }; + FBBA8662101AD2449CFD6D1B2909D93B /* Loader */ = { + isa = PBXGroup; + children = ( + 2EE15792329EC630A175A3896C245D90 /* DoricHttpJSLoader.h */, + F1CDF99B5A1018AEF83D898999D07779 /* DoricHttpJSLoader.m */, + E954660781F9D33A67ED91C8301AEF7D /* DoricJSLoaderManager.h */, + A7F3FF1FB691A3120E58F78CD10E6144 /* DoricJSLoaderManager.m */, + B732C93FEF68A791A00E07F7A99C1918 /* DoricLoaderProtocol.h */, + 5BA6819C98E9CC361D6FA297291BA04C /* DoricMainBundleJSLoader.h */, + 9E9713FBFF7F5E5044E6D47719303B01 /* DoricMainBundleJSLoader.m */, + ); + name = Loader; + path = Pod/Classes/Loader; + sourceTree = ""; + }; + FCB7AC0056F85EBE133A94D945422814 /* SocketRocket */ = { + isa = PBXGroup; + children = ( + A2A8B0BD64E3EE6FD74E776143CEABBD /* SocketRocket.h */, + 0FA454CFC822DF97658EA00D249150DB /* SRWebSocket.h */, + 4180A5D36AD0F103338812693D8B57A6 /* SRWebSocket.m */, + 9ED971B78F2FD589A1C4064717FB44A4 /* Support Files */, + ); + path = SocketRocket; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 19324E32064972C0D8A1FA2DD7D41A2A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D840F5A06B9893F618D61BB4983DF373 /* YYCache.h in Headers */, + 0CC1B3B497098C4159B8A348C85EC4B3 /* YYDiskCache.h in Headers */, + 525B0D4969B3DC70F910745701B6FFDE /* YYKVStorage.h in Headers */, + E95755F0A09A57B158365C41F25AE7A7 /* YYMemoryCache.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1F9F42084FA5328A65D06A10A3195627 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 25862B2DD200FF89C0B7099FBBFF9647 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 290062944EC0FB69A647836CB1FDE3A0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C11CBD0FC0CBA29F1F2DAC1AC37AF364 /* _YYWebImageSetter.h in Headers */, + 35E55CC007A4866A1F5E3ADE81679130 /* CALayer+YYWebImage.h in Headers */, + 01225045F0F04131A2DCCB419C5A43AF /* MKAnnotationView+YYWebImage.h in Headers */, + 071B7DACEB0E33381A899175AD7719CA /* UIButton+YYWebImage.h in Headers */, + A57D92BA915B59A21168F7BBF9D965A6 /* UIImage+YYWebImage.h in Headers */, + BB23FABE2628260AFFF63CD70FBFD25F /* UIImageView+YYWebImage.h in Headers */, + E6F8BC0728AC2154EF471B332F30ECB2 /* YYImageCache.h in Headers */, + BEFD64DBD549FDA265DA763D50F83662 /* YYWebImage.h in Headers */, + 38B0845A4B83CE0959531E40A36AADC4 /* YYWebImageManager.h in Headers */, + 046D4C719884BEE359F3E4056B3ADC91 /* YYWebImageOperation.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 847F9972480A4EB0E406EB0318D243C5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BDB5E4397AAD710F4446BA3952A2060 /* Doric.h in Headers */, + D4A9AB151E41873EBA01F779AC2DADF4 /* DoricAnimatePlugin.h in Headers */, + 46869F78B152AA301623F3FE82F6C0E3 /* DoricAsyncResult.h in Headers */, + BE2A19E5C02AD94897CCDA7324827BCA /* DoricBridgeExtension.h in Headers */, + 191E9735AC97FC3CEE416088EC325F62 /* DoricConstant.h in Headers */, + 45B6A13373ABB67043AC103DC249C1B3 /* DoricContext.h in Headers */, + CE2B11F33301B8F7CEAB17059F0DF2EA /* DoricContextHolder.h in Headers */, + 7C89E8BE51394D7BAEA8EC62422F805D /* DoricContextManager.h in Headers */, + ED616E6493400CB7638DE29CCCD7ABF6 /* DoricDriver.h in Headers */, + 7DAB96BECDDC0A6A989C3B33D4F0E08F /* DoricExtensions.h in Headers */, + BC8DFE1DD3D2B39277EEFA6D70CCF302 /* DoricFlowLayoutItemNode.h in Headers */, + 528525C1C62D4C0D269D0ADEC868C28F /* DoricFlowLayoutNode.h in Headers */, + 6E5E67F3816F41DDADE88F7461B1DC68 /* DoricGroupNode.h in Headers */, + AAA8E5B65C1E2F2FF885712766554A58 /* DoricHLayoutNode.h in Headers */, + A8FC102FD1822315CE435B5E7E0E9D14 /* DoricHttpJSLoader.h in Headers */, + 450A3D0C5E5889C279AC63532D0409AD /* DoricImageNode.h in Headers */, + 431B1130829E710EA3C4334BB232430A /* DoricJSCoreExecutor.h in Headers */, + D67AD5E402E954CDE1C16B744E39BDB1 /* DoricJSEngine.h in Headers */, + CEBDF3FD9ABF5D2B3C326B21F58D4640 /* DoricJSExecutorProtocol.h in Headers */, + 690D114CE33D424D91E65BCC5AEB0666 /* DoricJSLoaderManager.h in Headers */, + ECF57F8EB1CD164D28E1FA2D172F2A1A /* DoricJSRemoteArgType.h in Headers */, + 4EA7628A9D84272317EB18D602CB6101 /* DoricJSRemoteExecutor.h in Headers */, + 33FA845C063B814C8EEEF56B48CD41F8 /* DoricLayouts.h in Headers */, + 695907AB3DC305929A1E5CB534DD6788 /* DoricListItemNode.h in Headers */, + 6CB9C3842889740D1A1FED2D7A4E839D /* DoricListNode.h in Headers */, + 415078A044EA3AF3C8099845471A1FC9 /* DoricLoaderProtocol.h in Headers */, + 414869E5655141E747BB94588B59075B /* DoricMainBundleJSLoader.h in Headers */, + 1253265B1B37678C169BF6A10A260F9E /* DoricModalPlugin.h in Headers */, + 08AF19B42AB684E88852CD6CB8A3AA75 /* DoricNativePlugin.h in Headers */, + 9BB07ECB1EFF8E745E9185C4E2C27366 /* DoricNavBarDelegate.h in Headers */, + 5117ED4BE4507C48781C5C3DDF810D31 /* DoricNavBarPlugin.h in Headers */, + 5EB1DD47EC3AC8BA4BF3D04B510FCDC5 /* DoricNavigatorDelegate.h in Headers */, + 4E6CF5CCD8D1EC5E9A646749383DEC66 /* DoricNavigatorPlugin.h in Headers */, + CA21DC6FA6E5CB45B41924611ADC6C9A /* DoricNetworkPlugin.h in Headers */, + 7A24C07A15E0F108E4737F07A7027B15 /* DoricPanel.h in Headers */, + 56E0DDB21CAA1DB9B2209F2F10C5CDFC /* DoricPopoverPlugin.h in Headers */, + BFF89C93F769127B541C6B48460ED91D /* DoricPromise.h in Headers */, + 46C7437D30623CA97CA6E34487731AB9 /* DoricRefreshableNode.h in Headers */, + 1D68CC8FCA5AD66ACB98AC5A90B8D6BC /* DoricRegistry.h in Headers */, + 4D1F2333AFCA191C99238E2F7555D6C0 /* DoricRootNode.h in Headers */, + 517E021259A0510131206568BD7128CC /* DoricScrollerNode.h in Headers */, + 246C99114EE2CC85724B2F425B51D787 /* DoricShaderPlugin.h in Headers */, + 0D2A7AA3E2BA05FEB6CC5463F9BEFF15 /* DoricSlideItemNode.h in Headers */, + AC49DC5F6394CD3B4222E74C0FCC6A27 /* DoricSliderNode.h in Headers */, + FD1DCE441D6702175FC35DE58D019CAA /* DoricStackNode.h in Headers */, + 46C1F76C0AD5370A5F5C10425F0F2BAE /* DoricStoragePlugin.h in Headers */, + 83FE02E47F9826D81EA6A3848189C301 /* DoricSuperNode.h in Headers */, + 74DD6E1DC8F5E8EAD77AB903DB2CF145 /* DoricSwipeRefreshLayout.h in Headers */, + 29F425B1A1A60C85C87E2EA6A84CCBF1 /* DoricTextNode.h in Headers */, + 144143A659E0FEC033EB44B5C1707BCA /* DoricUtil.h in Headers */, + 2285FC52EAE17821471935D517C7DACB /* DoricViewController.h in Headers */, + 1D72D6B46AD74A46178DB3DA00B792E3 /* DoricViewNode.h in Headers */, + B6B56C166B186B33A5A87C791A284F36 /* DoricVLayoutNode.h in Headers */, + DDD55545A6FA8C0944DA1A34B564F876 /* DoricWSClient.h in Headers */, + 8304203ACC6B6C1F74F891D156D76CFC /* NSString+JsonString.h in Headers */, + C830AF2D550187C31E5AC8A46AA4E76F /* UIView+Doric.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 88C7134D809509122DF5C2AF0D66DED5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 96A0EDF8241D65DBEB01DDEA2551B6AB /* YYAnimatedImageView.h in Headers */, + 08C8A6242B5F8A6D3A5817FF9416A518 /* YYFrameImage.h in Headers */, + E5C994E9D29FEFF8CFA6A824D99EAB05 /* YYImage.h in Headers */, + 22B9F8152B9E980E1FEB00B0CA6B27BB /* YYImageCoder.h in Headers */, + 3721F80C3FDE8A2B101A119D78201031 /* YYSpriteSheetImage.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 93E92C78C7F3DDC0BA185907CCD45FD0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5CD6C1E489C8975D293C7C8EBE642359 /* SocketRocket.h in Headers */, + C83B012BD9B3B5F541AB506122EF966C /* SRWebSocket.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E516289B0110056A2A7A1CE2E2F230C0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F0DD942BF93F6B6E560D77691E37FD4B /* GCDWebServer.h in Headers */, + 8BF99A86864A6A5A96D92FDB6E0D4FDB /* GCDWebServerConnection.h in Headers */, + 8189756A6783D7BE61E08FE409BFD871 /* GCDWebServerDataRequest.h in Headers */, + 2D97BA46A9241177D41D943032EB6386 /* GCDWebServerDataResponse.h in Headers */, + 8890A8A88FA2ED811319B234B388321C /* GCDWebServerErrorResponse.h in Headers */, + D1014DBF2654D76D637022D52CC87885 /* GCDWebServerFileRequest.h in Headers */, + 715F955E5C63DB36EB84F6A043C89F51 /* GCDWebServerFileResponse.h in Headers */, + FD404E60462D931E6041A96D39DC024A /* GCDWebServerFunctions.h in Headers */, + 660C17830ED9DAF3CD14FEFEC16572D1 /* GCDWebServerHTTPStatusCodes.h in Headers */, + B303B392A2EB6D99E98BE556D969CE6C /* GCDWebServerMultiPartFormRequest.h in Headers */, + 59186150B3A10FED46BB81E923BC3803 /* GCDWebServerPrivate.h in Headers */, + 8203F1FE8748893B7FB0F9330E74E40E /* GCDWebServerRequest.h in Headers */, + 3F9991E0D126667F253AFD5ADE153D75 /* GCDWebServerResponse.h in Headers */, + B73DE88797B200F90C3123B9582631EB /* GCDWebServerStreamedResponse.h in Headers */, + CCB8E3339DFB68E7605BE381F977AEF9 /* GCDWebServerURLEncodedFormRequest.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FC44F694DF43568E1C9D46D4F8AA2AC7 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 0AEE99A309977BD12A049FF48AF9BA4B /* Pods-Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 254DEE34BA0C554E2254F342226EF50C /* Build configuration list for PBXNativeTarget "Pods-Example" */; + buildPhases = ( + 25862B2DD200FF89C0B7099FBBFF9647 /* Headers */, + EAB8A5B3F6754F82F8E07B6AD8B240CA /* Sources */, + D2ACF8B68A7F91BDA7A718418ABC1827 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + DE060ACD35D08B8BCE8C6907E1C205D2 /* PBXTargetDependency */, + 4D79DA13C03170CE9C21D80EF46AF849 /* PBXTargetDependency */, + DF228FFC5206B8E9FD01E9397FEE2A49 /* PBXTargetDependency */, + 35A5957DD49EAD482E1E36143F1DCDFE /* PBXTargetDependency */, + 36F2559A041FD20B19A9EEDDB7D016A3 /* PBXTargetDependency */, + 9D85CAFF928F7B198848A244D5964248 /* PBXTargetDependency */, + ); + name = "Pods-Example"; + productName = "Pods-Example"; + productReference = 1F667CC0E19EAF34E5A4119E2121F585 /* libPods-Example.a */; + productType = "com.apple.product-type.library.static"; + }; + 1948D0B63D2CF6A48E18B0B292BC6091 /* SocketRocket */ = { + isa = PBXNativeTarget; + buildConfigurationList = 019A61CAE0868AC0BE60D448F7684ECF /* Build configuration list for PBXNativeTarget "SocketRocket" */; + buildPhases = ( + 93E92C78C7F3DDC0BA185907CCD45FD0 /* Headers */, + 0B989C5F5B94740CEDB10A7E2E4F8728 /* Sources */, + 9FD779CC3664055065BDD25DBF915088 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SocketRocket; + productName = SocketRocket; + productReference = 85A01882ED06DFEA2E0CE78BCDB204A7 /* libSocketRocket.a */; + productType = "com.apple.product-type.library.static"; + }; + 3467588C9AC1920A96217C14EB70DA40 /* Doric */ = { + isa = PBXNativeTarget; + buildConfigurationList = 491A184E17170C12C18549F5C1D24A74 /* Build configuration list for PBXNativeTarget "Doric" */; + buildPhases = ( + 847F9972480A4EB0E406EB0318D243C5 /* Headers */, + FFC3F703D00FC68E571251C277A9D946 /* Sources */, + F60109FF999AE00268C7F05690539DAC /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 125839921EFB069382195EB61B591B65 /* PBXTargetDependency */, + 9BB0DA7DF4375D94FAF7099B4E18F8F6 /* PBXTargetDependency */, + A3D49195A496AA0826AB0216CB28D68E /* PBXTargetDependency */, + A73ADCD9DC1236D61BA4D6F0EB9068EC /* PBXTargetDependency */, + 10B5260627C632981E228E766CDB9218 /* PBXTargetDependency */, + 45A4C317249E4EFC8B146BAC54205744 /* PBXTargetDependency */, + ); + name = Doric; + productName = Doric; + productReference = CB6504BD07E3990E6F058E4073421ADA /* libDoric.a */; + productType = "com.apple.product-type.library.static"; + }; + 4432A314DF5F14FFF8809E319054F4AF /* Pods-ExampleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = B2548596E2544D6246F7A1567F0FED35 /* Build configuration list for PBXNativeTarget "Pods-ExampleTests" */; + buildPhases = ( + FC44F694DF43568E1C9D46D4F8AA2AC7 /* Headers */, + 0A6945A94C114AD03543703E96A0F3E1 /* Sources */, + 0BAD36B7F232A56FB4EDD23DD8325A04 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 7C13BF224FA9F8FB97D246BD39E099C3 /* PBXTargetDependency */, + ); + name = "Pods-ExampleTests"; + productName = "Pods-ExampleTests"; + productReference = 630CABE8F692FCDBFD1B122A2D0CE388 /* libPods-ExampleTests.a */; + productType = "com.apple.product-type.library.static"; + }; + 488E8C830C5A5694EE916251B9ADAADA /* YYWebImage */ = { + isa = PBXNativeTarget; + buildConfigurationList = FA9151EF6282C13D4B730B3273163572 /* Build configuration list for PBXNativeTarget "YYWebImage" */; + buildPhases = ( + 290062944EC0FB69A647836CB1FDE3A0 /* Headers */, + 4ABD9C04B104B92E7521A6BE88BF41B3 /* Sources */, + 5D0C362902E19DD7E17835D63591AF07 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 7CD09EAB9716E55B66A35A67313C16CE /* PBXTargetDependency */, + 0065ADB05F8959B485170F431CAD76AF /* PBXTargetDependency */, + ); + name = YYWebImage; + productName = YYWebImage; + productReference = 4FC1978206616CE319648B302B05AA8A /* libYYWebImage.a */; + productType = "com.apple.product-type.library.static"; + }; + 5305059FAA2A7B3246F1874D047D8000 /* Doric-Doric */ = { + isa = PBXNativeTarget; + buildConfigurationList = CF28C89A17B973DCEF0CC17EC429C924 /* Build configuration list for PBXNativeTarget "Doric-Doric" */; + buildPhases = ( + 9B8AF771B107648B420DF8828CCAD48D /* Sources */, + EAFB18F53A88C2BC75B17D6832DE0248 /* Frameworks */, + 2BFAA43ADD2BB8B0D98C009C813767F0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Doric-Doric"; + productName = "Doric-Doric"; + productReference = 1E19479558E0A9431B2B5F5A39136939 /* Doric.bundle */; + productType = "com.apple.product-type.bundle"; + }; + 822E44240F2922DAB12018A6B649BD19 /* YYImage */ = { + isa = PBXNativeTarget; + buildConfigurationList = F0862E6186C771182316C9FF20CC569A /* Build configuration list for PBXNativeTarget "YYImage" */; + buildPhases = ( + 88C7134D809509122DF5C2AF0D66DED5 /* Headers */, + 40AD5C10BEA0D14F1E71A34E9E6A8C45 /* Sources */, + 0A7300EEDB760E5BBD8CDCDA0E756D9F /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = YYImage; + productName = YYImage; + productReference = 0C6F4829D47D1826F47CDC4264F9F6A8 /* libYYImage.a */; + productType = "com.apple.product-type.library.static"; + }; + 8844A49AE64365ECBB6F0B228922E3D4 /* Pods-ExampleUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FF1E8FCEC0A05CD6042539B49FE84977 /* Build configuration list for PBXNativeTarget "Pods-ExampleUITests" */; + buildPhases = ( + 1F9F42084FA5328A65D06A10A3195627 /* Headers */, + 9A3583B42723658D87E4868694121B3A /* Sources */, + C5F5F9AE8591441292295FD9101B9D1C /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 747006C456DDDC210FA7F2BA6E3DA5A7 /* PBXTargetDependency */, + ); + name = "Pods-ExampleUITests"; + productName = "Pods-ExampleUITests"; + productReference = C232398D4E3A40F2337F8A40D7F8D751 /* libPods-ExampleUITests.a */; + productType = "com.apple.product-type.library.static"; + }; + AD904F32069787EFB2DFFE05EB82F5BD /* GCDWebServer */ = { + isa = PBXNativeTarget; + buildConfigurationList = D55AAFCDDBA2946ECB4E7DBA650E8B60 /* Build configuration list for PBXNativeTarget "GCDWebServer" */; + buildPhases = ( + E516289B0110056A2A7A1CE2E2F230C0 /* Headers */, + 0EE7488E17DB09046CB68A662F259E9A /* Sources */, + 7C1CF9A03EB6116AF3ED3831DDA4F75E /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GCDWebServer; + productName = GCDWebServer; + productReference = B68C1052A3B51DBCF7D960F898AAFA95 /* libGCDWebServer.a */; + productType = "com.apple.product-type.library.static"; + }; + CFDAE6EB02F58B9A37CADCF439AE6082 /* YYCache */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9D52FD8B42EC011F0CE3772AA41A7531 /* Build configuration list for PBXNativeTarget "YYCache" */; + buildPhases = ( + 19324E32064972C0D8A1FA2DD7D41A2A /* Headers */, + 5B0FD70E7DCF189E26B11864D6AB44FB /* Sources */, + 6921CB3F3F90A4D3854D879F38B0D619 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = YYCache; + productName = YYCache; + productReference = 48ACF38225AF5129416A1F090F6D3286 /* libYYCache.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1100; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 10.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + productRefGroup = F84D7922A6B05CDCB57606E11D08173B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3467588C9AC1920A96217C14EB70DA40 /* Doric */, + 5305059FAA2A7B3246F1874D047D8000 /* Doric-Doric */, + AD904F32069787EFB2DFFE05EB82F5BD /* GCDWebServer */, + 0AEE99A309977BD12A049FF48AF9BA4B /* Pods-Example */, + 4432A314DF5F14FFF8809E319054F4AF /* Pods-ExampleTests */, + 8844A49AE64365ECBB6F0B228922E3D4 /* Pods-ExampleUITests */, + 1948D0B63D2CF6A48E18B0B292BC6091 /* SocketRocket */, + CFDAE6EB02F58B9A37CADCF439AE6082 /* YYCache */, + 822E44240F2922DAB12018A6B649BD19 /* YYImage */, + 488E8C830C5A5694EE916251B9ADAADA /* YYWebImage */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2BFAA43ADD2BB8B0D98C009C813767F0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 469A1604CBB39942455BBDCC31C15BA3 /* bundle in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0A6945A94C114AD03543703E96A0F3E1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49CFE195ADFA55E91789E15D0C87C01D /* Pods-ExampleTests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0B989C5F5B94740CEDB10A7E2E4F8728 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 785D30ACE0CA96360935E2B84255B603 /* SocketRocket-dummy.m in Sources */, + 742D298906BD7E18FDE81D0690CE6707 /* SRWebSocket.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0EE7488E17DB09046CB68A662F259E9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 02F04EB7E8D481AF83AF79996BD87792 /* GCDWebServer-dummy.m in Sources */, + 35AA6FEA17185BA5249C86BF97CBBAB5 /* GCDWebServer.m in Sources */, + 6E3B68A0E99E0B47F320EF2331E19B7A /* GCDWebServerConnection.m in Sources */, + 6DEB097B36E0E54C1AB014D6F03C022D /* GCDWebServerDataRequest.m in Sources */, + 41E53FC8F7F5F7443EA81735B185621F /* GCDWebServerDataResponse.m in Sources */, + AB84AE28B27CF582EF19D36BDC43F98B /* GCDWebServerErrorResponse.m in Sources */, + D9B4599A181CC306A7435AD154685666 /* GCDWebServerFileRequest.m in Sources */, + DE1EEF389F8CB34534EAF7F8E4E6A7B7 /* GCDWebServerFileResponse.m in Sources */, + 6C945AC43FC9252239BD4EFA591834C4 /* GCDWebServerFunctions.m in Sources */, + 5374F9AB9AA0600E3CCA16E92C8896DF /* GCDWebServerMultiPartFormRequest.m in Sources */, + 456B4EDB3C253A44406F77BC05A674DF /* GCDWebServerRequest.m in Sources */, + D8F9C2440532E05025C22711C00FEDF4 /* GCDWebServerResponse.m in Sources */, + 39B36A38566CBE323E76AE77CF7F14C8 /* GCDWebServerStreamedResponse.m in Sources */, + 54EF62B95670DDDF59F337561BA72092 /* GCDWebServerURLEncodedFormRequest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 40AD5C10BEA0D14F1E71A34E9E6A8C45 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AE50A6FC10341E2B72B7DAF028954805 /* YYAnimatedImageView.m in Sources */, + 11B5C2686382CB0A191CBB1065B398B1 /* YYFrameImage.m in Sources */, + 3683FDAA91B21FEF6656962C0B18E61B /* YYImage-dummy.m in Sources */, + 5C6582CABB0B8973C08D1654AA69B835 /* YYImage.m in Sources */, + A5F7BC027290310D20EDBA4AA2217461 /* YYImageCoder.m in Sources */, + 2ADB39CC8762C3F34068C0AE84DDCFF4 /* YYSpriteSheetImage.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4ABD9C04B104B92E7521A6BE88BF41B3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B87B726F4418B01AF502A9E9C6119098 /* _YYWebImageSetter.m in Sources */, + 0B59A1F5BD1F123DB97060971DCF1D99 /* CALayer+YYWebImage.m in Sources */, + C907F8799CB0E289D53A5D7BE38EE6FF /* MKAnnotationView+YYWebImage.m in Sources */, + 27CAB568DB504CAB9E87A5949ACBF92A /* UIButton+YYWebImage.m in Sources */, + 1DC222879921DD8E01BDCA6348F0FAEF /* UIImage+YYWebImage.m in Sources */, + B325F28821AA9ED7BB4684C3E6952965 /* UIImageView+YYWebImage.m in Sources */, + FE345403F7CCECEE9CBF80C4C2969636 /* YYImageCache.m in Sources */, + 7134381ADFB49E13A492C8842DD9136E /* YYWebImage-dummy.m in Sources */, + BE80E6672A724B9C99DFDB16291E7920 /* YYWebImageManager.m in Sources */, + B99C4ED46C0396496F882C7254E186C5 /* YYWebImageOperation.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5B0FD70E7DCF189E26B11864D6AB44FB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1BA55E660DBDCCE258A0E2136464ABD7 /* YYCache-dummy.m in Sources */, + 289FEAACBCE2F0DF88ABB6978CFA4AC9 /* YYCache.m in Sources */, + CC7DF4168FA66F94C7FAC4722D625DE7 /* YYDiskCache.m in Sources */, + 90CA1D7B5E55DD73D1E54DF69ABAF037 /* YYKVStorage.m in Sources */, + 2F5FF6C6FBEE2F4E3BF522CBA1D5ABDC /* YYMemoryCache.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A3583B42723658D87E4868694121B3A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BECF1ACCAAAEBA8D91A27C0A8202E95 /* Pods-ExampleUITests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B8AF771B107648B420DF8828CCAD48D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EAB8A5B3F6754F82F8E07B6AD8B240CA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 642A88A3A60378104204D90AF865DCE7 /* Pods-Example-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FFC3F703D00FC68E571251C277A9D946 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 15A100A4553D6DAA81BBF7C892BADCA4 /* Doric-dummy.m in Sources */, + B6DBD08C47B1B79A1BFB12885900AD59 /* DoricAnimatePlugin.m in Sources */, + 3F95372A5D8DB12F879C15F58389A702 /* DoricAsyncResult.m in Sources */, + 03082882CBBA1667A93B7D61BC55B4B4 /* DoricBridgeExtension.m in Sources */, + 9456535481F6736CF42CC6C0CA0F05CC /* DoricConstant.m in Sources */, + 601AA4A9A51A6295EA0C587E314B4878 /* DoricContext.m in Sources */, + 454BDBC91B37775CE9FEAED5EB98A318 /* DoricContextHolder.m in Sources */, + FA22D26FA65988469855488530673176 /* DoricContextManager.m in Sources */, + 628C00DE711BDAF2D01F87D4E3F507C9 /* DoricDriver.m in Sources */, + 148E041F6FE689C65ECC968D22391F6B /* DoricExtensions.m in Sources */, + 43E035EA4D7778C3A5750C4AA067D517 /* DoricFlowLayoutItemNode.m in Sources */, + 08EF09EB19754EE509818C5790D31DA9 /* DoricFlowLayoutNode.m in Sources */, + 3925A9BD5798E09BE65375ED85D5A080 /* DoricGroupNode.m in Sources */, + 325A01C30B880AEE4052C5FFABC08513 /* DoricHLayoutNode.m in Sources */, + DECC4D2D1BE5BC5F10509E4C42C1A898 /* DoricHttpJSLoader.m in Sources */, + 4D1906A236B1E3163D1F273CA7504B1B /* DoricImageNode.m in Sources */, + 8D1B144A742F49683BE2828D810E4F3E /* DoricJSCoreExecutor.m in Sources */, + 5DDE21F723A5F11642AE78829F3E48E2 /* DoricJSEngine.m in Sources */, + 58EAEE34AF06823DB5089E796271876E /* DoricJSLoaderManager.m in Sources */, + 5A6F8B4FA356338E0C4F7E7BF66DF665 /* DoricJSRemoteArgType.m in Sources */, + 53D32F9170417C4D434CE02C29D28D6D /* DoricJSRemoteExecutor.m in Sources */, + 34ADBE5772A6192B7B304B0004D12A75 /* DoricLayouts.m in Sources */, + D23D009449BDA013320B83E96FD34F0C /* DoricListItemNode.m in Sources */, + A595FE2D135AA39316E3E3BC29297BA1 /* DoricListNode.m in Sources */, + 4152104C6EDDADD63E2A0FD4346C704A /* DoricMainBundleJSLoader.m in Sources */, + C02DE514B2104B7F5BF56CA865D60E1D /* DoricModalPlugin.m in Sources */, + 85FCA81F31A3D7F20A3B8573E4EC0CCD /* DoricNativePlugin.m in Sources */, + FF61AAFF955979FB5139A45AAA356476 /* DoricNavBarPlugin.m in Sources */, + 9E438C93DCD92B560E17734920ECA41E /* DoricNavigatorPlugin.m in Sources */, + BCCA8B4ECCE73780AF092C376700FFAA /* DoricNetworkPlugin.m in Sources */, + 64A7E44B95ADC998CAC69B4173F28F5D /* DoricPanel.m in Sources */, + DA5C200F877DDC6B376E80FC59DB5810 /* DoricPopoverPlugin.m in Sources */, + 7913064CD98682308E4DB915BF9035C2 /* DoricPromise.m in Sources */, + A603D2CA37138F4E0D2DB37C781A06BF /* DoricRefreshableNode.m in Sources */, + ECE9EB5004C807E82D576D1E4AFA13D2 /* DoricRegistry.m in Sources */, + 11C9CB703C2C92A588EA39CCFE5BCBC2 /* DoricRootNode.m in Sources */, + 235867C4157F363474F02CFECB820F45 /* DoricScrollerNode.m in Sources */, + 1DA05AE266C79E2CC17F3F527E34F7CC /* DoricShaderPlugin.m in Sources */, + E661D35C67269E4A6F71760F5E2A2432 /* DoricSlideItemNode.m in Sources */, + D69495C5934065FB2F245BA3A332DE86 /* DoricSliderNode.m in Sources */, + DD862728E67ABA06851177E86348C67D /* DoricStackNode.m in Sources */, + 2BF2F43EB0EDA27FBF764B004723F8F9 /* DoricStoragePlugin.m in Sources */, + F966178401EEC590EF0A26C79481E4D1 /* DoricSuperNode.m in Sources */, + 00612BA0FDFFF760AA31897B256552D3 /* DoricSwipeRefreshLayout.m in Sources */, + F29D4EC239C9F0D6705C9E1CC22576F9 /* DoricTextNode.m in Sources */, + FABB9B51C2FFDED22D686E59516612D0 /* DoricUtil.m in Sources */, + 89F0597FC8000460FB5CC2E7FC6319F9 /* DoricViewController.m in Sources */, + C3BC5A74FD1503DFA41D5A4E5EC3B672 /* DoricViewNode.m in Sources */, + EAC6F94876E051A213D3D556453486CC /* DoricVLayoutNode.m in Sources */, + A44016F8566693840BB920EA21DB7500 /* DoricWSClient.m in Sources */, + 07EB9BB703FC850650DC41BEA946EA62 /* NSString+JsonString.m in Sources */, + FBBC942DFE11B502C5C608D9BABBE130 /* UIView+Doric.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0065ADB05F8959B485170F431CAD76AF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYImage; + target = 822E44240F2922DAB12018A6B649BD19 /* YYImage */; + targetProxy = DD8E4C66891B230FFF1D4817E48DAB1A /* PBXContainerItemProxy */; + }; + 10B5260627C632981E228E766CDB9218 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYImage; + target = 822E44240F2922DAB12018A6B649BD19 /* YYImage */; + targetProxy = BE67A29C8EE3211F6E4264C92C4C5784 /* PBXContainerItemProxy */; + }; + 125839921EFB069382195EB61B591B65 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Doric-Doric"; + target = 5305059FAA2A7B3246F1874D047D8000 /* Doric-Doric */; + targetProxy = 8BCCEE3CE39180DCABAD08A2A88ABED2 /* PBXContainerItemProxy */; + }; + 35A5957DD49EAD482E1E36143F1DCDFE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYCache; + target = CFDAE6EB02F58B9A37CADCF439AE6082 /* YYCache */; + targetProxy = 77CF2158783C6995303A85B86AFD0AB9 /* PBXContainerItemProxy */; + }; + 36F2559A041FD20B19A9EEDDB7D016A3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYImage; + target = 822E44240F2922DAB12018A6B649BD19 /* YYImage */; + targetProxy = 8479FE8F8C2EC600F1603EF1BFD6CC05 /* PBXContainerItemProxy */; + }; + 45A4C317249E4EFC8B146BAC54205744 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYWebImage; + target = 488E8C830C5A5694EE916251B9ADAADA /* YYWebImage */; + targetProxy = 5EF22E1C74B909EA29E090C50719BBD9 /* PBXContainerItemProxy */; + }; + 4D79DA13C03170CE9C21D80EF46AF849 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GCDWebServer; + target = AD904F32069787EFB2DFFE05EB82F5BD /* GCDWebServer */; + targetProxy = A79DF282FD90B4C36A64A1AF9B145EDB /* PBXContainerItemProxy */; + }; + 747006C456DDDC210FA7F2BA6E3DA5A7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Pods-Example"; + target = 0AEE99A309977BD12A049FF48AF9BA4B /* Pods-Example */; + targetProxy = 253670C8605F5FFDE1D6606BA634381B /* PBXContainerItemProxy */; + }; + 7C13BF224FA9F8FB97D246BD39E099C3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Pods-Example"; + target = 0AEE99A309977BD12A049FF48AF9BA4B /* Pods-Example */; + targetProxy = 751D1FF2822550D322644F951A7BDA9F /* PBXContainerItemProxy */; + }; + 7CD09EAB9716E55B66A35A67313C16CE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYCache; + target = CFDAE6EB02F58B9A37CADCF439AE6082 /* YYCache */; + targetProxy = 856CD558A6A4C84FD434D6D2A50A352C /* PBXContainerItemProxy */; + }; + 9BB0DA7DF4375D94FAF7099B4E18F8F6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GCDWebServer; + target = AD904F32069787EFB2DFFE05EB82F5BD /* GCDWebServer */; + targetProxy = CBBE8CF6CC81DB32DA2DDF836FC41947 /* PBXContainerItemProxy */; + }; + 9D85CAFF928F7B198848A244D5964248 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYWebImage; + target = 488E8C830C5A5694EE916251B9ADAADA /* YYWebImage */; + targetProxy = 5D80C63145B83493E429D6C7B05394BB /* PBXContainerItemProxy */; + }; + A3D49195A496AA0826AB0216CB28D68E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SocketRocket; + target = 1948D0B63D2CF6A48E18B0B292BC6091 /* SocketRocket */; + targetProxy = 5F800553AF429EC9C1F3C6E5C826D04E /* PBXContainerItemProxy */; + }; + A73ADCD9DC1236D61BA4D6F0EB9068EC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = YYCache; + target = CFDAE6EB02F58B9A37CADCF439AE6082 /* YYCache */; + targetProxy = F5D4CA413DB8AE6664EA56196D8C0C4A /* PBXContainerItemProxy */; + }; + DE060ACD35D08B8BCE8C6907E1C205D2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Doric; + target = 3467588C9AC1920A96217C14EB70DA40 /* Doric */; + targetProxy = D478AD36DA51D0362FE11615C051F15B /* PBXContainerItemProxy */; + }; + DF228FFC5206B8E9FD01E9397FEE2A49 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SocketRocket; + target = 1948D0B63D2CF6A48E18B0B292BC6091 /* SocketRocket */; + targetProxy = 2313449E8824345CBDB9ED4D3E3BCF32 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 00994F306AF3B768484D4D5A96FF3E9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 35D3ADD1FF0C9A33D86DB110C181048B /* Pods-ExampleTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MACH_O_TYPE = staticlib; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1867EE0ECC0D95F72F3C21F56270C932 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 98512CF1DCD76DF334D0FDB005736214 /* GCDWebServer.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/GCDWebServer/GCDWebServer-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = GCDWebServer; + PRODUCT_NAME = GCDWebServer; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1BEEA832EFCEC6C7D6306AE959E5F736 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5BD2FB5A84E2481DF889A8718306023F /* YYImage.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/YYImage/YYImage-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = YYImage; + PRODUCT_NAME = YYImage; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1F74D9D2F08B3F941DB2B37B659E834B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 677BC4F0CCD6EB2C9FBCB3B375E7349F /* Doric.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Doric"; + INFOPLIST_FILE = "Target Support Files/Doric/ResourceBundle-Doric-Doric-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + PRODUCT_NAME = Doric; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; + 2DF4EFAC0C2BD7ECA51C3801727A9E30 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 677BC4F0CCD6EB2C9FBCB3B375E7349F /* Doric.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/Doric/Doric-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = Doric; + PRODUCT_NAME = Doric; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4F49B03DE53237020A9A6B697CBB8CCB /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 57728CFE03A4D88821681C3D55A1FA85 /* Pods-Example.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MACH_O_TYPE = staticlib; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6410F571BA8C1AEC6A19403F1B61B0B5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 6643C31EFA970B7EAFC68BCADA3E7242 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4A6BBD5D599973673610A24AF01EF5D7 /* YYWebImage.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/YYWebImage/YYWebImage-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = YYWebImage; + PRODUCT_NAME = YYWebImage; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 747661AF61F4387FF6885B69B54F271E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8052FAE778A6A09F2260E4E85B371F57 /* Pods-ExampleTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MACH_O_TYPE = staticlib; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 76441C7336CE1037FF46D29DF3FC2228 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A461D3539C89936737BC3759F6AA0407 /* YYCache.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/YYCache/YYCache-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = YYCache; + PRODUCT_NAME = YYCache; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 770D93103C8F5744BE0B815D6D8A9F6F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4A6BBD5D599973673610A24AF01EF5D7 /* YYWebImage.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/YYWebImage/YYWebImage-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = YYWebImage; + PRODUCT_NAME = YYWebImage; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 9AD4A9FAB7BCA211A8C0FC98A7683348 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E340F908A05867E06E975FDCDD32315C /* Pods-Example.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MACH_O_TYPE = staticlib; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A27713CB74E99AF202AADC23200A32F4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + A4DB471E05BD01419CF7CE60F6404243 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A461D3539C89936737BC3759F6AA0407 /* YYCache.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/YYCache/YYCache-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = YYCache; + PRODUCT_NAME = YYCache; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + A7D7E74228D53C61289938CCEC18058C /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B54C4523BBF2D713FE5A0DC2448FFFB1 /* SocketRocket.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/SocketRocket/SocketRocket-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = SocketRocket; + PRODUCT_NAME = SocketRocket; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + B2E6C9F349C68F27DFF73ACF12F14761 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 677BC4F0CCD6EB2C9FBCB3B375E7349F /* Doric.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Doric"; + INFOPLIST_FILE = "Target Support Files/Doric/ResourceBundle-Doric-Doric-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + PRODUCT_NAME = Doric; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + BAA29EEE9BBE1E8D82BFADC3AD1BBEF9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 786AB58F4C2451575BBF7722ACDE9BEB /* Pods-ExampleUITests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MACH_O_TYPE = staticlib; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C7051B457F5811ED7939D90711AB1C77 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 677BC4F0CCD6EB2C9FBCB3B375E7349F /* Doric.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/Doric/Doric-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = Doric; + PRODUCT_NAME = Doric; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D5CF2A2E3D8139B7310231DB736E4364 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 50CE420621B9D9ED01E06A8EDBD3E990 /* Pods-ExampleUITests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MACH_O_TYPE = staticlib; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + E07894811891A3E671C1D3F25C73EA31 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 98512CF1DCD76DF334D0FDB005736214 /* GCDWebServer.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/GCDWebServer/GCDWebServer-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = GCDWebServer; + PRODUCT_NAME = GCDWebServer; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + E55F3035644B5631EE29D961E11FC0AC /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5BD2FB5A84E2481DF889A8718306023F /* YYImage.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/YYImage/YYImage-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = YYImage; + PRODUCT_NAME = YYImage; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + F005A2CA09B6A27BEC0220D81C4724F7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B54C4523BBF2D713FE5A0DC2448FFFB1 /* SocketRocket.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + GCC_PREFIX_HEADER = "Target Support Files/SocketRocket/SocketRocket-prefix.pch"; + IPHONEOS_DEPLOYMENT_TARGET = 6.0; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PRIVATE_HEADERS_FOLDER_PATH = ""; + PRODUCT_MODULE_NAME = SocketRocket; + PRODUCT_NAME = SocketRocket; + PUBLIC_HEADERS_FOLDER_PATH = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 019A61CAE0868AC0BE60D448F7684ECF /* Build configuration list for PBXNativeTarget "SocketRocket" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A7D7E74228D53C61289938CCEC18058C /* Debug */, + F005A2CA09B6A27BEC0220D81C4724F7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 254DEE34BA0C554E2254F342226EF50C /* Build configuration list for PBXNativeTarget "Pods-Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AD4A9FAB7BCA211A8C0FC98A7683348 /* Debug */, + 4F49B03DE53237020A9A6B697CBB8CCB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6410F571BA8C1AEC6A19403F1B61B0B5 /* Debug */, + A27713CB74E99AF202AADC23200A32F4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 491A184E17170C12C18549F5C1D24A74 /* Build configuration list for PBXNativeTarget "Doric" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C7051B457F5811ED7939D90711AB1C77 /* Debug */, + 2DF4EFAC0C2BD7ECA51C3801727A9E30 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9D52FD8B42EC011F0CE3772AA41A7531 /* Build configuration list for PBXNativeTarget "YYCache" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A4DB471E05BD01419CF7CE60F6404243 /* Debug */, + 76441C7336CE1037FF46D29DF3FC2228 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B2548596E2544D6246F7A1567F0FED35 /* Build configuration list for PBXNativeTarget "Pods-ExampleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00994F306AF3B768484D4D5A96FF3E9A /* Debug */, + 747661AF61F4387FF6885B69B54F271E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CF28C89A17B973DCEF0CC17EC429C924 /* Build configuration list for PBXNativeTarget "Doric-Doric" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B2E6C9F349C68F27DFF73ACF12F14761 /* Debug */, + 1F74D9D2F08B3F941DB2B37B659E834B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D55AAFCDDBA2946ECB4E7DBA650E8B60 /* Build configuration list for PBXNativeTarget "GCDWebServer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1867EE0ECC0D95F72F3C21F56270C932 /* Debug */, + E07894811891A3E671C1D3F25C73EA31 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F0862E6186C771182316C9FF20CC569A /* Build configuration list for PBXNativeTarget "YYImage" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1BEEA832EFCEC6C7D6306AE959E5F736 /* Debug */, + E55F3035644B5631EE29D961E11FC0AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FA9151EF6282C13D4B730B3273163572 /* Build configuration list for PBXNativeTarget "YYWebImage" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 770D93103C8F5744BE0B815D6D8A9F6F /* Debug */, + 6643C31EFA970B7EAFC68BCADA3E7242 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FF1E8FCEC0A05CD6042539B49FE84977 /* Build configuration list for PBXNativeTarget "Pods-ExampleUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BAA29EEE9BBE1E8D82BFADC3AD1BBEF9 /* Debug */, + D5CF2A2E3D8139B7310231DB736E4364 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Doric-Doric.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Doric-Doric.xcscheme new file mode 100644 index 00000000..9445d3e9 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Doric-Doric.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Doric.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Doric.xcscheme new file mode 100644 index 00000000..62701502 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Doric.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/GCDWebServer.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/GCDWebServer.xcscheme new file mode 100644 index 00000000..65cdf11a --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/GCDWebServer.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-Example.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-Example.xcscheme new file mode 100644 index 00000000..583ee4b5 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-Example.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-ExampleTests.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-ExampleTests.xcscheme new file mode 100644 index 00000000..e3b35c09 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-ExampleTests.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-ExampleUITests.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-ExampleUITests.xcscheme new file mode 100644 index 00000000..4a616e3a --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/Pods-ExampleUITests.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/SocketRocket.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/SocketRocket.xcscheme new file mode 100644 index 00000000..771b095d --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/SocketRocket.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYCache.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYCache.xcscheme new file mode 100644 index 00000000..e4162776 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYCache.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYImage.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYImage.xcscheme new file mode 100644 index 00000000..320a58cf --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYImage.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYWebImage.xcscheme b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYWebImage.xcscheme new file mode 100644 index 00000000..a5ae6514 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/YYWebImage.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..b31ba0e7 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/xcuserdata/pengfei.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,81 @@ + + + + + SchemeUserState + + Doric-Doric.xcscheme + + isShown + + orderHint + 0 + + Doric.xcscheme + + isShown + + orderHint + 1 + + GCDWebServer.xcscheme + + isShown + + orderHint + 2 + + Pods-Example.xcscheme + + isShown + + orderHint + 3 + + Pods-ExampleTests.xcscheme + + isShown + + orderHint + 4 + + Pods-ExampleUITests.xcscheme + + isShown + + orderHint + 5 + + SocketRocket.xcscheme + + isShown + + orderHint + 6 + + YYCache.xcscheme + + isShown + + orderHint + 7 + + YYImage.xcscheme + + isShown + + orderHint + 8 + + YYWebImage.xcscheme + + isShown + + orderHint + 9 + + + SuppressBuildableAutocreation + + + diff --git a/Example/Pods/SocketRocket/LICENSE b/Example/Pods/SocketRocket/LICENSE new file mode 100644 index 00000000..c01a79c3 --- /dev/null +++ b/Example/Pods/SocketRocket/LICENSE @@ -0,0 +1,15 @@ + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Example/Pods/SocketRocket/README.rst b/Example/Pods/SocketRocket/README.rst new file mode 100644 index 00000000..033c9dd0 --- /dev/null +++ b/Example/Pods/SocketRocket/README.rst @@ -0,0 +1,246 @@ +SocketRocket Objective-C WebSocket Client (beta) +================================================ +A conforming WebSocket (`RFC 6455 `_) +client library. + +`Test results for SocketRocket here `_. +You can compare to what `modern browsers look like here +`_. + +SocketRocket currently conforms to all ~300 of `Autobahn +`_'s fuzzing tests (aside from +two UTF-8 ones where it is merely *non-strict*. tests 6.4.2 and 6.4.4) + +Features/Design +--------------- +- TLS (wss) support. It uses CFStream so we get this for *free* +- Uses NSStream/CFNetworking. Earlier implementations used ``dispatch_io``, + however, this proved to be make TLS nearly impossible. Also I wanted this to + work in iOS 4.x. (SocketRocket only supports 5.0 and above now) +- Uses ARC. It uses the 4.0 compatible subset (no weak refs). +- Seems to perform quite well +- Parallel architecture. Most of the work is done in background worker queues. +- Delegate-based. Had older versions that could use blocks too, but I felt it + didn't blend well with retain cycles and just objective C in general. + +Changes +------- + +v0.3.1-beta2 - 2013-01-12 +````````````````````````` + +- Stability fix for ``closeWithCode:reason:`` (Thanks @michaelpetrov!) +- Actually clean up the NSStreams and remove them from their runloops +- ``_SRRunLoopThread``'s ``main`` wasn't correctly wrapped with + ``@autoreleasepool`` + +v0.3.1-beta1 - 2013-01-12 +````````````````````````` + +- Cleaned up GCD so OS_OBJECT_USE_OBJC_RETAIN_RELEASE is optional +- Removed deprecated ``dispatch_get_current_queue`` in favor of ``dispatch_queue_set_specific`` and ``dispatch_get_specific`` +- Dropping support for iOS 4.0 (it may still work) + + +Installing (iOS) +---------------- +There's a few options. Choose one, or just figure it out + +- You can copy all the files in the SocketRocket group into your app. +- Include SocketRocket as a subproject and use libSocketRocket + + If you do this, you must add -ObjC to your "other linker flags" option + +- For OS X you will have to repackage make a .framework target. I will take + contributions. Message me if you are interested. + + +Depending on how you configure your project you may need to ``#import`` either +```` or ``"SRWebSocket.h"`` + +Framework Dependencies +`````````````````````` +Your .app must be linked against the following frameworks/dylibs + +- libicucore.dylib +- CFNetwork.framework +- Security.framework +- Foundation.framework + +Installing (OS X) +----------------- +SocketRocket now has (64-bit only) OS X support. ``SocketRocket.framework`` +inside Xcode project is for OS X only. It should be identical in function aside +from the unicode validation. ICU isn't shipped with OS X which is what the +original implementation used for unicode validation. The workaround is much +more rudimentary and less robust. + +1. Add SocketRocket.xcodeproj as either a subproject of your app or in your workspace. +2. Add ``SocketRocket.framework`` to the link libraries +3. If you don't have a "copy files" step for ``Framework``, create one +4. Add ``SocketRocket.framework`` to the "copy files" step. + +API +--- +The classes + +``SRWebSocket`` +``````````````` +The Web Socket. + +.. note:: ``SRWebSocket`` will retain itself between ``-(void)open`` and when it + closes, errors, or fails. This is similar to how ``NSURLConnection`` behaves. + (unlike ``NSURLConnection``, ``SRWebSocket`` won't retain the delegate) + +What you need to know + +.. code-block:: objective-c + + @interface SRWebSocket : NSObject + + // Make it with this + - (id)initWithURLRequest:(NSURLRequest *)request; + + // Set this before opening + @property (nonatomic, assign) id delegate; + + - (void)open; + + // Close it with this + - (void)close; + + // Send a UTF8 String or Data + - (void)send:(id)data; + + @end + +``SRWebSocketDelegate`` +``````````````````````` +You implement this + +.. code-block:: objective-c + + @protocol SRWebSocketDelegate + + - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; + + @optional + + - (void)webSocketDidOpen:(SRWebSocket *)webSocket; + - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; + - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; + + @end + +Known Issues/Server Todo's +-------------------------- +- Needs auth delegates (like in NSURLConnection) +- Move the streams off the main runloop (most of the work is backgrounded uses + GCD, but I just haven't gotten around to moving it off the main loop since I + converted it from dispatch_io) +- Re-implement server. I removed an existing implementation as well because it + wasn't being used and I wasn't super happy with the interface. Will revisit + this. +- Separate framer and client logic. This will make it nicer when having a + server. + +Testing +------- +Included are setup scripts for the python testing environment. It comes +packaged with vitualenv so all the dependencies are installed in userland. + +To run the short test from the command line, run:: + + make test + +To run all the tests, run:: + + make test_all + +The short tests don't include the performance tests. (the test harness is +actually the bottleneck, not SocketRocket). + +The first time this is run, it may take a while to install the dependencies. It +will be smooth sailing after that. After the test runs the makefile will open +the results page in your browser. If nothing comes up, you failed. Working on +making this interface a bit nicer. + +To run from the app, choose the ``SocketRocket`` target and run the test action +(``cmd+u``). It runs the same thing, but makes it easier to debug. There is +some serious pre/post hooks in the Test action. You can edit it to customize +behavior. + +.. note:: Xcode only up to version 4.4 is currently supported for the test + harness + +TestChat Demo Application +------------------------- +SocketRocket includes a demo app, TestChat. It will "chat" with a listening +websocket on port 9900. + +It's a simple project. Uses storyboard. Storyboard is sweet. + + +TestChat Server +``````````````` +We've included a small server for the chat app. It has a simple function. +It will take a message and broadcast it to all other connected clients. + +We have to get some dependencies. We also want to reuse the virtualenv we made +when we ran the tests. If you haven't run the tests yet, go into the +SocketRocket root directory and type:: + + make test + +This will set up your `virtualenv `_. +Now, in your terminal:: + + source .env/bin/activate + pip install git+https://github.com/tornadoweb/tornado.git + +In the same terminal session, start the chatroom server:: + + python TestChatServer/py/chatroom.py + +There's also a Go implementation (with the latest weekly) where you can:: + + cd TestChatServer/go + go run chatroom.go + +Chatting +```````` +Now, start TestChat.app (just run the target in the Xcode project). If you had +it started already you can hit the refresh button to reconnect. It should say +"Connected!" on top. + +To talk with the app, open up your browser to `http://localhost:9000 `_ and +start chatting. + + +WebSocket Server Implementation Recommendations +----------------------------------------------- +SocketRocket has been used with the following libraries: + +- `Tornado `_ +- Go's `WebSocket package `_ or Gorilla's `version `_ +- `Autobahn `_ (using its fuzzing + client) + +The Tornado one is dirt simple and works like a charm. (`IPython notebook +`_ uses it +too). It's much easier to configure handlers and routes than in +Autobahn/twisted. + +As far as Go's goes, it works in my limited testing. I much prefer go's +concurrency model as well. Try it! You may like it. +It could use some more control over things such as pings, etc., but I +am sure it will come in time. + +Autobahn is a great test suite. The Python server code is good, and conforms +well (obviously). However for me, twisted would be a deal-breaker for writing +something new. I find it a bit too complex and heavy for a simple service. If +you are already using twisted though, Autobahn is probably for you. + +Contributing +------------ +We’re glad you’re interested in SocketRocket, and we’d love to see where you take it. Please read our `contributing guidelines `_ prior to submitting a Pull Request. \ No newline at end of file diff --git a/Example/Pods/SocketRocket/SocketRocket/SRWebSocket.h b/Example/Pods/SocketRocket/SocketRocket/SRWebSocket.h new file mode 100644 index 00000000..ca3a2c0f --- /dev/null +++ b/Example/Pods/SocketRocket/SocketRocket/SRWebSocket.h @@ -0,0 +1,154 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +typedef NS_ENUM(NSInteger, SRReadyState) { + SR_CONNECTING = 0, + SR_OPEN = 1, + SR_CLOSING = 2, + SR_CLOSED = 3, +}; + +typedef enum SRStatusCode : NSInteger { + // 0–999: Reserved and not used. + SRStatusCodeNormal = 1000, + SRStatusCodeGoingAway = 1001, + SRStatusCodeProtocolError = 1002, + SRStatusCodeUnhandledType = 1003, + // 1004 reserved. + SRStatusNoStatusReceived = 1005, + SRStatusCodeAbnormal = 1006, + SRStatusCodeInvalidUTF8 = 1007, + SRStatusCodePolicyViolated = 1008, + SRStatusCodeMessageTooBig = 1009, + SRStatusCodeMissingExtension = 1010, + SRStatusCodeInternalError = 1011, + SRStatusCodeServiceRestart = 1012, + SRStatusCodeTryAgainLater = 1013, + // 1014: Reserved for future use by the WebSocket standard. + SRStatusCodeTLSHandshake = 1015, + // 1016–1999: Reserved for future use by the WebSocket standard. + // 2000–2999: Reserved for use by WebSocket extensions. + // 3000–3999: Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve. + // 4000–4999: Available for use by applications. +} SRStatusCode; + +@class SRWebSocket; + +extern NSString *const SRWebSocketErrorDomain; +extern NSString *const SRHTTPResponseErrorKey; + +#pragma mark - SRWebSocketDelegate + +@protocol SRWebSocketDelegate; + +#pragma mark - SRWebSocket + +@interface SRWebSocket : NSObject + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, readonly) SRReadyState readyState; +@property (nonatomic, readonly, retain) NSURL *url; + + +@property (nonatomic, readonly) CFHTTPMessageRef receivedHTTPHeaders; + +// Optional array of cookies (NSHTTPCookie objects) to apply to the connections +@property (nonatomic, readwrite) NSArray * requestCookies; + +// This returns the negotiated protocol. +// It will be nil until after the handshake completes. +@property (nonatomic, readonly, copy) NSString *protocol; + +// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates; +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +- (id)initWithURLRequest:(NSURLRequest *)request; + +// Some helper constructors. +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates; +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +- (id)initWithURL:(NSURL *)url; + +// Delegate queue will be dispatch_main_queue by default. +// You cannot set both OperationQueue and dispatch_queue. +- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; + +// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes. +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; + +// SRWebSockets are intended for one-time-use only. Open should be called once and only once. +- (void)open; + +- (void)close; +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; + +// Send a UTF8 String or Data. +- (void)send:(id)data; + +// Send Data (can be nil) in a ping message. +- (void)sendPing:(NSData *)data; + +@end + +#pragma mark - SRWebSocketDelegate + +@protocol SRWebSocketDelegate + +// message will either be an NSString if the server is using text +// or NSData if the server is using binary. +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; + +@optional + +- (void)webSocketDidOpen:(SRWebSocket *)webSocket; +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; +- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload; + +// Return YES to convert messages sent as Text to an NSString. Return NO to skip NSData -> NSString conversion for Text messages. Defaults to YES. +- (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket; + +@end + +#pragma mark - NSURLRequest (SRCertificateAdditions) + +@interface NSURLRequest (SRCertificateAdditions) + +@property (nonatomic, retain, readonly) NSArray *SR_SSLPinnedCertificates; + +@end + +#pragma mark - NSMutableURLRequest (SRCertificateAdditions) + +@interface NSMutableURLRequest (SRCertificateAdditions) + +@property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates; + +@end + +#pragma mark - NSRunLoop (SRWebSocket) + +@interface NSRunLoop (SRWebSocket) + ++ (NSRunLoop *)SR_networkRunLoop; + +@end diff --git a/Example/Pods/SocketRocket/SocketRocket/SRWebSocket.m b/Example/Pods/SocketRocket/SocketRocket/SRWebSocket.m new file mode 100644 index 00000000..b078dd24 --- /dev/null +++ b/Example/Pods/SocketRocket/SocketRocket/SRWebSocket.m @@ -0,0 +1,1916 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import "SRWebSocket.h" + +#if TARGET_OS_IPHONE +#define HAS_ICU +#endif + +#ifdef HAS_ICU +#import +#endif + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import +#import + +#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE +#define sr_dispatch_retain(x) +#define sr_dispatch_release(x) +#define maybe_bridge(x) ((__bridge void *) x) +#else +#define sr_dispatch_retain(x) dispatch_retain(x) +#define sr_dispatch_release(x) dispatch_release(x) +#define maybe_bridge(x) (x) +#endif + +#if !__has_feature(objc_arc) +#error SocketRocket must be compiled with ARC enabled +#endif + + +typedef enum { + SROpCodeTextFrame = 0x1, + SROpCodeBinaryFrame = 0x2, + // 3-7 reserved. + SROpCodeConnectionClose = 0x8, + SROpCodePing = 0x9, + SROpCodePong = 0xA, + // B-F reserved. +} SROpCode; + +typedef struct { + BOOL fin; +// BOOL rsv1; +// BOOL rsv2; +// BOOL rsv3; + uint8_t opcode; + BOOL masked; + uint64_t payload_length; +} frame_header; + +static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +static inline int32_t validate_dispatch_data_partial_string(NSData *data); +static inline void SRFastLog(NSString *format, ...); + +@interface NSData (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSString (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSURL (SRWebSocket) + +// The origin isn't really applicable for a native application. +// So instead, just map ws -> http and wss -> https. +- (NSString *)SR_origin; + +@end + + +@interface _SRRunLoopThread : NSThread + +@property (nonatomic, readonly) NSRunLoop *runLoop; + +@end + + +static NSString *newSHA1String(const char *bytes, size_t length) { + uint8_t md[CC_SHA1_DIGEST_LENGTH]; + + assert(length >= 0); + assert(length <= UINT32_MAX); + CC_SHA1(bytes, (CC_LONG)length, md); + + NSData *data = [NSData dataWithBytes:md length:CC_SHA1_DIGEST_LENGTH]; + + if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { + return [data base64EncodedStringWithOptions:0]; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return [data base64Encoding]; +#pragma clang diagnostic pop +} + +@implementation NSData (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.bytes, self.length); +} + +@end + + +@implementation NSString (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.UTF8String, self.length); +} + +@end + +NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; +NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; + +// Returns number of bytes consumed. Returning 0 means you didn't match. +// Sends bytes to callback handler; +typedef size_t (^stream_scanner)(NSData *collected_data); + +typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data); + +@interface SRIOConsumer : NSObject { + stream_scanner _scanner; + data_callback _handler; + size_t _bytesNeeded; + BOOL _readToCurrentFrame; + BOOL _unmaskBytes; +} +@property (nonatomic, copy, readonly) stream_scanner consumer; +@property (nonatomic, copy, readonly) data_callback handler; +@property (nonatomic, assign) size_t bytesNeeded; +@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; +@property (nonatomic, assign, readonly) BOOL unmaskBytes; + +@end + +// This class is not thread-safe, and is expected to always be run on the same queue. +@interface SRIOConsumerPool : NSObject + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)returnConsumer:(SRIOConsumer *)consumer; + +@end + +@interface SRWebSocket () + +@property (nonatomic) SRReadyState readyState; + +@property (nonatomic) NSOperationQueue *delegateOperationQueue; +@property (nonatomic) dispatch_queue_t delegateDispatchQueue; + +// Specifies whether SSL trust chain should NOT be evaluated. +// By default this flag is set to NO, meaning only secure SSL connections are allowed. +// For DEBUG builds this flag is ignored, and SSL connections are allowed regardless +// of the certificate trust configuration +@property (nonatomic, readwrite) BOOL allowsUntrustedSSLCertificates; + +@end + + +@implementation SRWebSocket { + NSInteger _webSocketVersion; + + NSOperationQueue *_delegateOperationQueue; + dispatch_queue_t _delegateDispatchQueue; + + dispatch_queue_t _workQueue; + NSMutableArray *_consumers; + + NSInputStream *_inputStream; + NSOutputStream *_outputStream; + + NSMutableData *_readBuffer; + NSUInteger _readBufferOffset; + + NSMutableData *_outputBuffer; + NSUInteger _outputBufferOffset; + + uint8_t _currentFrameOpcode; + size_t _currentFrameCount; + size_t _readOpCount; + uint32_t _currentStringScanPosition; + NSMutableData *_currentFrameData; + + NSString *_closeReason; + + NSString *_secKey; + NSString *_basicAuthorizationString; + + BOOL _pinnedCertFound; + + uint8_t _currentReadMaskKey[4]; + size_t _currentReadMaskOffset; + + BOOL _consumerStopped; + + BOOL _closeWhenFinishedWriting; + BOOL _failed; + + BOOL _secure; + NSURLRequest *_urlRequest; + + BOOL _sentClose; + BOOL _didFail; + BOOL _cleanupScheduled; + int _closeCode; + + BOOL _isPumping; + + NSMutableSet *_scheduledRunloops; + + // We use this to retain ourselves. + __strong SRWebSocket *_selfRetain; + + NSArray *_requestedProtocols; + SRIOConsumerPool *_consumerPool; +} + +@synthesize delegate = _delegate; +@synthesize url = _url; +@synthesize readyState = _readyState; +@synthesize protocol = _protocol; + +static __strong NSData *CRLFCRLF; + ++ (void)initialize; +{ + CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; +} + +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates; +{ + self = [super init]; + if (self) { + assert(request.URL); + _url = request.URL; + _urlRequest = request; + _allowsUntrustedSSLCertificates = allowsUntrustedSSLCertificates; + + _requestedProtocols = [protocols copy]; + + [self _SR_commonInit]; + } + + return self; +} + +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +{ + return [self initWithURLRequest:request protocols:protocols allowsUntrustedSSLCertificates:NO]; +} + +- (id)initWithURLRequest:(NSURLRequest *)request; +{ + return [self initWithURLRequest:request protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url; +{ + return [self initWithURL:url protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +{ + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + return [self initWithURLRequest:request protocols:protocols]; +} + +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates; +{ + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + return [self initWithURLRequest:request protocols:protocols allowsUntrustedSSLCertificates:allowsUntrustedSSLCertificates]; +} + +- (void)_SR_commonInit; +{ + NSString *scheme = _url.scheme.lowercaseString; + assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); + + if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { + _secure = YES; + } + + _readyState = SR_CONNECTING; + _consumerStopped = YES; + _webSocketVersion = 13; + + _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + + // Going to set a specific on the queue so we can validate we're on the work queue + dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL); + + _delegateDispatchQueue = dispatch_get_main_queue(); + sr_dispatch_retain(_delegateDispatchQueue); + + _readBuffer = [[NSMutableData alloc] init]; + _outputBuffer = [[NSMutableData alloc] init]; + + _currentFrameData = [[NSMutableData alloc] init]; + + _consumers = [[NSMutableArray alloc] init]; + + _consumerPool = [[SRIOConsumerPool alloc] init]; + + _scheduledRunloops = [[NSMutableSet alloc] init]; + + [self _initializeStreams]; + + // default handlers +} + +- (void)assertOnWorkQueue; +{ + assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue)); +} + +- (void)dealloc +{ + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + [_inputStream close]; + [_outputStream close]; + + if (_workQueue) { + sr_dispatch_release(_workQueue); + _workQueue = NULL; + } + + if (_receivedHTTPHeaders) { + CFRelease(_receivedHTTPHeaders); + _receivedHTTPHeaders = NULL; + } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + _delegateDispatchQueue = NULL; + } +} + +#ifndef NDEBUG + +- (void)setReadyState:(SRReadyState)aReadyState; +{ + assert(aReadyState > _readyState); + _readyState = aReadyState; +} + +#endif + +- (void)open; +{ + assert(_url); + NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once"); + + _selfRetain = self; + + if (_urlRequest.timeoutInterval > 0) + { + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, _urlRequest.timeoutInterval * NSEC_PER_SEC); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + if (self.readyState == SR_CONNECTING) + [self _failWithError:[NSError errorWithDomain:@"com.squareup.SocketRocket" code:504 userInfo:@{NSLocalizedDescriptionKey: @"Timeout Connecting to Server"}]]; + }); + } + + [self openConnection]; +} + +// Calls block on delegate queue +- (void)_performDelegateBlock:(dispatch_block_t)block; +{ + if (_delegateOperationQueue) { + [_delegateOperationQueue addOperationWithBlock:block]; + } else { + assert(_delegateDispatchQueue); + dispatch_async(_delegateDispatchQueue, block); + } +} + +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; +{ + if (queue) { + sr_dispatch_retain(queue); + } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + } + + _delegateDispatchQueue = queue; +} + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +{ + NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); + + if (acceptHeader == nil) { + return NO; + } + + NSString *concattedString = [_secKey stringByAppendingString:SRWebSocketAppendToSecKeyString]; + NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; + + return [acceptHeader isEqualToString:expectedAccept]; +} + +- (void)_HTTPHeadersDidFinish; +{ + NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); + + if (responseCode >= 400) { + SRFastLog(@"Request failed with response code %d", responseCode); + [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode], SRHTTPResponseErrorKey:@(responseCode)}]]; + return; + } + + if(![self _checkHandshake:_receivedHTTPHeaders]) { + [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); + if (negotiatedProtocol) { + // Make sure we requested the protocol + if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { + [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _protocol = negotiatedProtocol; + } + + self.readyState = SR_OPEN; + + if (!_didFail) { + [self _readFrameNew]; + } + + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { + [self.delegate webSocketDidOpen:self]; + }; + }]; +} + + +- (void)_readHTTPHeader; +{ + if (_receivedHTTPHeaders == NULL) { + _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); + } + + [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) { + CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + + if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { + SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); + [self _HTTPHeadersDidFinish]; + } else { + [self _readHTTPHeader]; + } + }]; +} + +- (void)didConnect; +{ + SRFastLog(@"Connected"); + CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); + + // Set host first so it defaults + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); + + NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; + SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); + + if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { + _secKey = [keyBytes base64EncodedStringWithOptions:0]; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + _secKey = [keyBytes base64Encoding]; +#pragma clang diagnostic pop + } + + assert([_secKey length] == 24); + + // Apply cookies if any have been provided + NSDictionary * cookies = [NSHTTPCookie requestHeaderFieldsWithCookies:[self requestCookies]]; + for (NSString * cookieKey in cookies) { + NSString * cookieValue = [cookies objectForKey:cookieKey]; + if ([cookieKey length] && [cookieValue length]) { + CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)cookieKey, (__bridge CFStringRef)cookieValue); + } + } + + // set header for http basic auth + if (_url.user.length && _url.password.length) { + NSData *userAndPassword = [[NSString stringWithFormat:@"%@:%@", _url.user, _url.password] dataUsingEncoding:NSUTF8StringEncoding]; + NSString *userAndPasswordBase64Encoded; + if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) { + userAndPasswordBase64Encoded = [userAndPassword base64EncodedStringWithOptions:0]; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + userAndPasswordBase64Encoded = [userAndPassword base64Encoding]; +#pragma clang diagnostic pop + } + _basicAuthorizationString = [NSString stringWithFormat:@"Basic %@", userAndPasswordBase64Encoded]; + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Authorization"), (__bridge CFStringRef)_basicAuthorizationString); + } + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin); + + if (_requestedProtocols) { + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); + } + + [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + + NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); + + CFRelease(request); + + [self _writeData:message]; + [self _readHTTPHeader]; +} + +- (void)_initializeStreams; +{ + assert(_url.port.unsignedIntValue <= UINT32_MAX); + uint32_t port = _url.port.unsignedIntValue; + if (port == 0) { + if (!_secure) { + port = 80; + } else { + port = 443; + } + } + NSString *host = _url.host; + + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); + + _outputStream = CFBridgingRelease(writeStream); + _inputStream = CFBridgingRelease(readStream); + + _inputStream.delegate = self; + _outputStream.delegate = self; +} + +- (void)_updateSecureStreamOptions; +{ + if (_secure) { + NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; + + [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // If we're using pinned certs, don't validate the certificate chain + if ([_urlRequest SR_SSLPinnedCertificates].count) { + [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + } + +#if DEBUG + self.allowsUntrustedSSLCertificates = YES; +#endif + + if (self.allowsUntrustedSSLCertificates) { + [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + SRFastLog(@"Allowing connection to any root cert"); + } + + [_outputStream setProperty:SSLOptions + forKey:(__bridge id)kCFStreamPropertySSLSettings]; + } + + _inputStream.delegate = self; + _outputStream.delegate = self; + + [self setupNetworkServiceType:_urlRequest.networkServiceType]; +} + +- (void)setupNetworkServiceType:(NSURLRequestNetworkServiceType)requestNetworkServiceType +{ + NSString *networkServiceType; + switch (requestNetworkServiceType) { + case NSURLNetworkServiceTypeDefault: + break; + case NSURLNetworkServiceTypeVoIP: { + networkServiceType = NSStreamNetworkServiceTypeVoIP; +#if TARGET_OS_IPHONE && __IPHONE_9_0 + if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_8_3) { + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ + NSLog(@"SocketRocket: %@ - this service type is deprecated in favor of using PushKit for VoIP control", networkServiceType); + }); + } +#endif + break; + } + case NSURLNetworkServiceTypeVideo: + networkServiceType = NSStreamNetworkServiceTypeVideo; + break; + case NSURLNetworkServiceTypeBackground: + networkServiceType = NSStreamNetworkServiceTypeBackground; + break; + case NSURLNetworkServiceTypeVoice: + networkServiceType = NSStreamNetworkServiceTypeVoice; + break; + } + + if (networkServiceType != nil) { + [_inputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType]; + [_outputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType]; + } +} + +- (void)openConnection; +{ + [self _updateSecureStreamOptions]; + + if (!_scheduledRunloops.count) { + [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + } + + + [_outputStream open]; + [_inputStream open]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; + [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops addObject:@[aRunLoop, mode]]; +} + +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; + [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops removeObject:@[aRunLoop, mode]]; +} + +- (void)close; +{ + [self closeWithCode:SRStatusCodeNormal reason:nil]; +} + +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; +{ + assert(code); + dispatch_async(_workQueue, ^{ + if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { + return; + } + + BOOL wasConnecting = self.readyState == SR_CONNECTING; + + self.readyState = SR_CLOSING; + + SRFastLog(@"Closing with code %d reason %@", code, reason); + + if (wasConnecting) { + [self closeConnection]; + return; + } + + size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; + NSData *payload = mutablePayload; + + ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); + + if (reason) { + NSRange remainingRange = {0}; + + NSUInteger usedLength = 0; + + BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; + #pragma unused (success) + + assert(success); + assert(remainingRange.length == 0); + + if (usedLength != maxMsgSize) { + payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; + } + } + + + [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; + }); +} + +- (void)_closeWithProtocolError:(NSString *)message; +{ + // Need to shunt this on the _callbackQueue first to see if they received any messages + [self _performDelegateBlock:^{ + [self closeWithCode:SRStatusCodeProtocolError reason:message]; + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); + }]; +} + +- (void)_failWithError:(NSError *)error; +{ + dispatch_async(_workQueue, ^{ + if (self.readyState != SR_CLOSED) { + _failed = YES; + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { + [self.delegate webSocket:self didFailWithError:error]; + } + }]; + + self.readyState = SR_CLOSED; + + SRFastLog(@"Failing with error %@", error.localizedDescription); + + [self closeConnection]; + [self _scheduleCleanup]; + } + }); +} + +- (void)_writeData:(NSData *)data; +{ + [self assertOnWorkQueue]; + + if (_closeWhenFinishedWriting) { + return; + } + [_outputBuffer appendData:data]; + [self _pumpWriting]; +} + +- (void)send:(id)data; +{ + NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); + // TODO: maybe not copy this for performance + data = [data copy]; + dispatch_async(_workQueue, ^{ + if ([data isKindOfClass:[NSString class]]) { + [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; + } else if ([data isKindOfClass:[NSData class]]) { + [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; + } else if (data == nil) { + [self _sendFrameWithOpcode:SROpCodeTextFrame data:data]; + } else { + assert(NO); + } + }); +} + +- (void)sendPing:(NSData *)data; +{ + NSAssert(self.readyState == SR_OPEN, @"Invalid State: Cannot call send: until connection is open"); + // TODO: maybe not copy this for performance + data = [data copy] ?: [NSData data]; // It's okay for a ping to be empty + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodePing data:data]; + }); +} + +- (void)handlePing:(NSData *)pingData; +{ + // Need to pingpong this off _callbackQueue first to make sure messages happen in order + [self _performDelegateBlock:^{ + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodePong data:pingData]; + }); + }]; +} + +- (void)handlePong:(NSData *)pongData; +{ + SRFastLog(@"Received pong"); + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didReceivePong:)]) { + [self.delegate webSocket:self didReceivePong:pongData]; + } + }]; +} + +- (void)_handleMessage:(id)message +{ + SRFastLog(@"Received message"); + [self _performDelegateBlock:^{ + [self.delegate webSocket:self didReceiveMessage:message]; + }]; +} + + +static inline BOOL closeCodeIsValid(int closeCode) { + if (closeCode < 1000) { + return NO; + } + + if (closeCode >= 1000 && closeCode <= 1011) { + if (closeCode == 1004 || + closeCode == 1005 || + closeCode == 1006) { + return NO; + } + return YES; + } + + if (closeCode >= 3000 && closeCode <= 3999) { + return YES; + } + + if (closeCode >= 4000 && closeCode <= 4999) { + return YES; + } + + return NO; +} + +// Note from RFC: +// +// If there is a body, the first two +// bytes of the body MUST be a 2-byte unsigned integer (in network byte +// order) representing a status code with value /code/ defined in +// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 +// encoded data with value /reason/, the interpretation of which is not +// defined by this specification. + +- (void)handleCloseWithData:(NSData *)data; +{ + size_t dataSize = data.length; + __block uint16_t closeCode = 0; + + SRFastLog(@"Received close frame"); + + if (dataSize == 1) { + // TODO handle error + [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; + return; + } else if (dataSize >= 2) { + [data getBytes:&closeCode length:sizeof(closeCode)]; + _closeCode = EndianU16_BtoN(closeCode); + if (!closeCodeIsValid(_closeCode)) { + [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; + return; + } + if (dataSize > 2) { + _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; + if (!_closeReason) { + [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; + return; + } + } + } else { + _closeCode = SRStatusNoStatusReceived; + } + + [self assertOnWorkQueue]; + + if (self.readyState == SR_OPEN) { + [self closeWithCode:1000 reason:nil]; + } + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); +} + +- (void)closeConnection; +{ + [self assertOnWorkQueue]; + SRFastLog(@"Trying to disconnect"); + _closeWhenFinishedWriting = YES; + [self _pumpWriting]; +} + +- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; +{ + // Check that the current data is valid UTF8 + + BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose); + if (!isControlFrame) { + [self _readFrameNew]; + } else { + dispatch_async(_workQueue, ^{ + [self _readFrameContinue]; + }); + } + + //frameData will be copied before passing to handlers + //otherwise there can be misbehaviours when value at the pointer is changed + switch (opcode) { + case SROpCodeTextFrame: { + if ([self.delegate respondsToSelector:@selector(webSocketShouldConvertTextFrameToString:)] && ![self.delegate webSocketShouldConvertTextFrameToString:self]) { + [self _handleMessage:[frameData copy]]; + } else { + NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; + if (str == nil && frameData) { + [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); + return; + } + [self _handleMessage:str]; + } + break; + } + case SROpCodeBinaryFrame: + [self _handleMessage:[frameData copy]]; + break; + case SROpCodeConnectionClose: + [self handleCloseWithData:[frameData copy]]; + break; + case SROpCodePing: + [self handlePing:[frameData copy]]; + break; + case SROpCodePong: + [self handlePong:[frameData copy]]; + break; + default: + [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]]; + // TODO: Handle invalid opcode + break; + } +} + +- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; +{ + assert(frame_header.opcode != 0); + + if (self.readyState == SR_CLOSED) { + return; + } + + + BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.opcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose); + + if (isControlFrame && !frame_header.fin) { + [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; + return; + } + + if (isControlFrame && frame_header.payload_length >= 126) { + [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; + return; + } + + if (!isControlFrame) { + _currentFrameOpcode = frame_header.opcode; + _currentFrameCount += 1; + } + + if (frame_header.payload_length == 0) { + if (isControlFrame) { + [self _handleFrameWithData:curData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + } + } else { + assert(frame_header.payload_length <= SIZE_T_MAX); + [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) { + if (isControlFrame) { + [self _handleFrameWithData:newData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + + } + } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; + } +} + +/* From RFC: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + */ + +static const uint8_t SRFinMask = 0x80; +static const uint8_t SROpCodeMask = 0x0F; +static const uint8_t SRRsvMask = 0x70; +static const uint8_t SRMaskMask = 0x80; +static const uint8_t SRPayloadLenMask = 0x7F; + + +- (void)_readFrameContinue; +{ + assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); + + [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) { + __block frame_header header = {0}; + + const uint8_t *headerBuffer = data.bytes; + assert(data.length >= 2); + + if (headerBuffer[0] & SRRsvMask) { + [self _closeWithProtocolError:@"Server used RSV bits"]; + return; + } + + uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]); + + BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose); + + if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) { + [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; + return; + } + + if (receivedOpcode == 0 && self->_currentFrameCount == 0) { + [self _closeWithProtocolError:@"cannot continue a message"]; + return; + } + + header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode; + + header.fin = !!(SRFinMask & headerBuffer[0]); + + + header.masked = !!(SRMaskMask & headerBuffer[1]); + header.payload_length = SRPayloadLenMask & headerBuffer[1]; + + headerBuffer = NULL; + + if (header.masked) { + [self _closeWithProtocolError:@"Client must receive unmasked data"]; + } + + size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; + + if (header.payload_length == 126) { + extra_bytes_needed += sizeof(uint16_t); + } else if (header.payload_length == 127) { + extra_bytes_needed += sizeof(uint64_t); + } + + if (extra_bytes_needed == 0) { + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } else { + [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) { + size_t mapped_size = data.length; + #pragma unused (mapped_size) + const void *mapped_buffer = data.bytes; + size_t offset = 0; + + if (header.payload_length == 126) { + assert(mapped_size >= sizeof(uint16_t)); + uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); + header.payload_length = newLen; + offset += sizeof(uint16_t); + } else if (header.payload_length == 127) { + assert(mapped_size >= sizeof(uint64_t)); + header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); + offset += sizeof(uint64_t); + } else { + assert(header.payload_length < 126 && header.payload_length >= 0); + } + + if (header.masked) { + assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); + memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey)); + } + + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } readToCurrentFrame:NO unmaskBytes:NO]; + } + } readToCurrentFrame:NO unmaskBytes:NO]; +} + +- (void)_readFrameNew; +{ + dispatch_async(_workQueue, ^{ + [_currentFrameData setLength:0]; + + _currentFrameOpcode = 0; + _currentFrameCount = 0; + _readOpCount = 0; + _currentStringScanPosition = 0; + + [self _readFrameContinue]; + }); +} + +- (void)_pumpWriting; +{ + [self assertOnWorkQueue]; + + NSUInteger dataLength = _outputBuffer.length; + if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { + NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; + if (bytesWritten == -1) { + [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _outputBufferOffset += bytesWritten; + + if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { + _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; + _outputBufferOffset = 0; + } + } + + if (_closeWhenFinishedWriting && + _outputBuffer.length - _outputBufferOffset == 0 && + (_inputStream.streamStatus != NSStreamStatusNotOpen && + _inputStream.streamStatus != NSStreamStatusClosed) && + !_sentClose) { + _sentClose = YES; + + @synchronized(self) { + [_outputStream close]; + [_inputStream close]; + + + for (NSArray *runLoop in [_scheduledRunloops copy]) { + [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; + } + } + + if (!_failed) { + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; + } + }]; + } + + [self _scheduleCleanup]; + } +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +{ + [self assertOnWorkQueue]; + [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; +} + +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + [self assertOnWorkQueue]; + assert(dataLength); + + [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; + [self _pumpScanner]; +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +{ + [self assertOnWorkQueue]; + [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; + [self _pumpScanner]; +} + + +- (void)_scheduleCleanup +{ + @synchronized(self) { + if (_cleanupScheduled) { + return; + } + + _cleanupScheduled = YES; + + // Cleanup NSStream delegate's in the same RunLoop used by the streams themselves: + // This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc + NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO]; + [[NSRunLoop SR_networkRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; + } +} + +- (void)_cleanupSelfReference:(NSTimer *)timer +{ + @synchronized(self) { + // Nuke NSStream delegate's + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + // Remove the streams, right now, from the networkRunLoop + [_inputStream close]; + [_outputStream close]; + } + + // Cleanup selfRetain in the same GCD queue as usual + dispatch_async(_workQueue, ^{ + _selfRetain = nil; + }); +} + + +static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; + +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; +{ + [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; +} + +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +{ + // TODO optimize so this can continue from where we last searched + stream_scanner consumer = ^size_t(NSData *data) { + __block size_t found_size = 0; + __block size_t match_count = 0; + + size_t size = data.length; + const unsigned char *buffer = data.bytes; + for (size_t i = 0; i < size; i++ ) { + if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { + match_count += 1; + if (match_count == length) { + found_size = i + 1; + break; + } + } else { + match_count = 0; + } + } + return found_size; + }; + [self _addConsumerWithScanner:consumer callback:dataHandler]; +} + + +// Returns true if did work +- (BOOL)_innerPumpScanner { + + BOOL didWork = NO; + + if (self.readyState >= SR_CLOSED) { + return didWork; + } + + if (!_consumers.count) { + return didWork; + } + + size_t curSize = _readBuffer.length - _readBufferOffset; + if (!curSize) { + return didWork; + } + + SRIOConsumer *consumer = [_consumers objectAtIndex:0]; + + size_t bytesNeeded = consumer.bytesNeeded; + + size_t foundSize = 0; + if (consumer.consumer) { + NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; + foundSize = consumer.consumer(tempView); + } else { + assert(consumer.bytesNeeded); + if (curSize >= bytesNeeded) { + foundSize = bytesNeeded; + } else if (consumer.readToCurrentFrame) { + foundSize = curSize; + } + } + + NSData *slice = nil; + if (consumer.readToCurrentFrame || foundSize) { + NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); + slice = [_readBuffer subdataWithRange:sliceRange]; + + _readBufferOffset += foundSize; + + if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { + _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; + } + + if (consumer.unmaskBytes) { + NSMutableData *mutableSlice = [slice mutableCopy]; + + NSUInteger len = mutableSlice.length; + uint8_t *bytes = mutableSlice.mutableBytes; + + for (NSUInteger i = 0; i < len; i++) { + bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; + _currentReadMaskOffset += 1; + } + + slice = mutableSlice; + } + + if (consumer.readToCurrentFrame) { + [_currentFrameData appendData:slice]; + + _readOpCount += 1; + + if (_currentFrameOpcode == SROpCodeTextFrame) { + // Validate UTF8 stuff. + size_t currentDataSize = _currentFrameData.length; + if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) { + // TODO: Optimize the crap out of this. Don't really have to copy all the data each time + + size_t scanSize = currentDataSize - _currentStringScanPosition; + + NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; + int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); + + if (valid_utf8_size == -1) { + [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self closeConnection]; + }); + return didWork; + } else { + _currentStringScanPosition += valid_utf8_size; + } + } + + } + + consumer.bytesNeeded -= foundSize; + + if (consumer.bytesNeeded == 0) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, nil); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } else if (foundSize) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, slice); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } + return didWork; +} + +-(void)_pumpScanner; +{ + [self assertOnWorkQueue]; + + if (!_isPumping) { + _isPumping = YES; + } else { + return; + } + + while ([self _innerPumpScanner]) { + + } + + _isPumping = NO; +} + +//#define NOMASK + +static const size_t SRFrameHeaderOverhead = 32; + +- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; +{ + [self assertOnWorkQueue]; + + if (nil == data) { + return; + } + + NSAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"NSString or NSData"); + + size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; + + NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; + if (!frame) { + [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; + return; + } + uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; + + // set fin + frame_buffer[0] = SRFinMask | opcode; + + BOOL useMask = YES; +#ifdef NOMASK + useMask = NO; +#endif + + if (useMask) { + // set the mask and header + frame_buffer[1] |= SRMaskMask; + } + + size_t frame_buffer_size = 2; + + const uint8_t *unmasked_payload = NULL; + if ([data isKindOfClass:[NSData class]]) { + unmasked_payload = (uint8_t *)[data bytes]; + } else if ([data isKindOfClass:[NSString class]]) { + unmasked_payload = (const uint8_t *)[data UTF8String]; + } else { + return; + } + + if (payloadLength < 126) { + frame_buffer[1] |= payloadLength; + } else if (payloadLength <= UINT16_MAX) { + frame_buffer[1] |= 126; + *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); + frame_buffer_size += sizeof(uint16_t); + } else { + frame_buffer[1] |= 127; + *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); + frame_buffer_size += sizeof(uint64_t); + } + + if (!useMask) { + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i]; + frame_buffer_size += 1; + } + } else { + uint8_t *mask_key = frame_buffer + frame_buffer_size; + SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); + frame_buffer_size += sizeof(uint32_t); + + // TODO: could probably optimize this with SIMD + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; + frame_buffer_size += 1; + } + } + + assert(frame_buffer_size <= [frame length]); + frame.length = frame_buffer_size; + + [self _writeData:frame]; +} + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; +{ + __weak typeof(self) weakSelf = self; + + if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + + NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates]; + if (sslCerts) { + SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; + if (secTrust) { + NSInteger numCerts = SecTrustGetCertificateCount(secTrust); + for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); + NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); + + for (id ref in sslCerts) { + SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; + NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); + + if ([trustedCertData isEqualToData:certData]) { + _pinnedCertFound = YES; + break; + } + } + } + } + + if (!_pinnedCertFound) { + dispatch_async(_workQueue, ^{ + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"Invalid server cert" }; + [weakSelf _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:userInfo]]; + }); + return; + } else if (aStream == _outputStream) { + dispatch_async(_workQueue, ^{ + [self didConnect]; + }); + } + } + } + + dispatch_async(_workQueue, ^{ + [weakSelf safeHandleEvent:eventCode stream:aStream]; + }); +} + +- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream +{ + switch (eventCode) { + case NSStreamEventOpenCompleted: { + SRFastLog(@"NSStreamEventOpenCompleted %@", aStream); + if (self.readyState >= SR_CLOSING) { + return; + } + assert(_readBuffer); + + // didConnect fires after certificate verification if we're using pinned certificates. + BOOL usingPinnedCerts = [[_urlRequest SR_SSLPinnedCertificates] count] > 0; + if ((!_secure || !usingPinnedCerts) && self.readyState == SR_CONNECTING && aStream == _inputStream) { + [self didConnect]; + } + [self _pumpWriting]; + [self _pumpScanner]; + break; + } + + case NSStreamEventErrorOccurred: { + SRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); + /// TODO specify error better! + [self _failWithError:aStream.streamError]; + _readBufferOffset = 0; + [_readBuffer setLength:0]; + break; + + } + + case NSStreamEventEndEncountered: { + [self _pumpScanner]; + SRFastLog(@"NSStreamEventEndEncountered %@", aStream); + if (aStream.streamError) { + [self _failWithError:aStream.streamError]; + } else { + dispatch_async(_workQueue, ^{ + if (self.readyState != SR_CLOSED) { + self.readyState = SR_CLOSED; + [self _scheduleCleanup]; + } + + if (!_sentClose && !_failed) { + _sentClose = YES; + // If we get closed in this state it's probably not clean because we should be sending this when we send messages + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:SRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO]; + } + }]; + } + }); + } + + break; + } + + case NSStreamEventHasBytesAvailable: { + SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); + const int bufferSize = 2048; + uint8_t buffer[bufferSize]; + + while (_inputStream.hasBytesAvailable) { + NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize]; + + if (bytes_read > 0) { + [_readBuffer appendBytes:buffer length:bytes_read]; + } else if (bytes_read < 0) { + [self _failWithError:_inputStream.streamError]; + } + + if (bytes_read != bufferSize) { + break; + } + }; + [self _pumpScanner]; + break; + } + + case NSStreamEventHasSpaceAvailable: { + SRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); + [self _pumpWriting]; + break; + } + + default: + SRFastLog(@"(default) %@", aStream); + break; + } +} + +@end + + +@implementation SRIOConsumer + +@synthesize bytesNeeded = _bytesNeeded; +@synthesize consumer = _scanner; +@synthesize handler = _handler; +@synthesize readToCurrentFrame = _readToCurrentFrame; +@synthesize unmaskBytes = _unmaskBytes; + +- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + _scanner = [scanner copy]; + _handler = [handler copy]; + _bytesNeeded = bytesNeeded; + _readToCurrentFrame = readToCurrentFrame; + _unmaskBytes = unmaskBytes; + assert(_scanner || _bytesNeeded); +} + + +@end + + +@implementation SRIOConsumerPool { + NSUInteger _poolSize; + NSMutableArray *_bufferedConsumers; +} + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; +{ + self = [super init]; + if (self) { + _poolSize = poolSize; + _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; + } + return self; +} + +- (id)init +{ + return [self initWithBufferCapacity:8]; +} + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + SRIOConsumer *consumer = nil; + if (_bufferedConsumers.count) { + consumer = [_bufferedConsumers lastObject]; + [_bufferedConsumers removeLastObject]; + } else { + consumer = [[SRIOConsumer alloc] init]; + } + + [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; + + return consumer; +} + +- (void)returnConsumer:(SRIOConsumer *)consumer; +{ + if (_bufferedConsumers.count < _poolSize) { + [_bufferedConsumers addObject:consumer]; + } +} + +@end + + +@implementation NSURLRequest (SRCertificateAdditions) + +- (NSArray *)SR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSMutableURLRequest (SRCertificateAdditions) + +- (NSArray *)SR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; +} + +- (void)setSR_SSLPinnedCertificates:(NSArray *)SR_SSLPinnedCertificates; +{ + [NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSURL (SRWebSocket) + +- (NSString *)SR_origin; +{ + NSString *scheme = [self.scheme lowercaseString]; + + if ([scheme isEqualToString:@"wss"]) { + scheme = @"https"; + } else if ([scheme isEqualToString:@"ws"]) { + scheme = @"http"; + } + + BOOL portIsDefault = !self.port || + ([scheme isEqualToString:@"http"] && self.port.integerValue == 80) || + ([scheme isEqualToString:@"https"] && self.port.integerValue == 443); + + if (!portIsDefault) { + return [NSString stringWithFormat:@"%@://%@:%@", scheme, self.host, self.port]; + } else { + return [NSString stringWithFormat:@"%@://%@", scheme, self.host]; + } +} + +@end + +//#define SR_ENABLE_LOG + +static inline void SRFastLog(NSString *format, ...) { +#ifdef SR_ENABLE_LOG + __block va_list arg_list; + va_start (arg_list, format); + + NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; + + va_end(arg_list); + + NSLog(@"[SR] %@", formattedString); +#endif +} + + +#ifdef HAS_ICU + +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + if ([data length] > INT32_MAX) { + // INT32_MAX is the limit so long as this Framework is using 32 bit ints everywhere. + return -1; + } + + int32_t size = (int32_t)[data length]; + + const void * contents = [data bytes]; + const uint8_t *str = (const uint8_t *)contents; + + UChar32 codepoint = 1; + int32_t offset = 0; + int32_t lastOffset = 0; + while(offset < size && codepoint > 0) { + lastOffset = offset; + U8_NEXT(str, offset, size, codepoint); + } + + if (codepoint == -1) { + // Check to see if the last byte is valid or whether it was just continuing + if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) { + + size = -1; + } else { + uint8_t leadByte = str[lastOffset]; + U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); + + for (int i = lastOffset + 1; i < offset; i++) { + if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { + size = -1; + } + } + + if (size != -1) { + size = lastOffset; + } + } + } + + if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { + size = -1; + } + + return size; +} + +#else + +// This is a hack, and probably not optimal +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + static const int maxCodepointSize = 3; + + for (int i = 0; i < maxCodepointSize; i++) { + NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; + if (str) { + return (int32_t)data.length - i; + } + } + + return -1; +} + +#endif + +static _SRRunLoopThread *networkThread = nil; +static NSRunLoop *networkRunLoop = nil; + +@implementation NSRunLoop (SRWebSocket) + ++ (NSRunLoop *)SR_networkRunLoop { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + networkThread = [[_SRRunLoopThread alloc] init]; + networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; + [networkThread start]; + networkRunLoop = networkThread.runLoop; + }); + + return networkRunLoop; +} + +@end + + +@implementation _SRRunLoopThread { + dispatch_group_t _waitGroup; +} + +@synthesize runLoop = _runLoop; + +- (void)dealloc +{ + sr_dispatch_release(_waitGroup); +} + +- (id)init +{ + self = [super init]; + if (self) { + _waitGroup = dispatch_group_create(); + dispatch_group_enter(_waitGroup); + } + return self; +} + +- (void)main; +{ + @autoreleasepool { + _runLoop = [NSRunLoop currentRunLoop]; + dispatch_group_leave(_waitGroup); + + // Add an empty run loop source to prevent runloop from spinning. + CFRunLoopSourceContext sourceCtx = { + .version = 0, + .info = NULL, + .retain = NULL, + .release = NULL, + .copyDescription = NULL, + .equal = NULL, + .hash = NULL, + .schedule = NULL, + .cancel = NULL, + .perform = NULL + }; + CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + CFRelease(source); + + while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { + + } + assert(NO); + } +} + +- (NSRunLoop *)runLoop; +{ + dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); + return _runLoop; +} + +@end diff --git a/Example/Pods/SocketRocket/SocketRocket/SocketRocket.h b/Example/Pods/SocketRocket/SocketRocket/SocketRocket.h new file mode 100644 index 00000000..2e986aef --- /dev/null +++ b/Example/Pods/SocketRocket/SocketRocket/SocketRocket.h @@ -0,0 +1,17 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import diff --git a/Example/Pods/Target Support Files/Doric/Doric-dummy.m b/Example/Pods/Target Support Files/Doric/Doric-dummy.m new file mode 100644 index 00000000..f56119cb --- /dev/null +++ b/Example/Pods/Target Support Files/Doric/Doric-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Doric : NSObject +@end +@implementation PodsDummy_Doric +@end diff --git a/Example/Pods/Target Support Files/Doric/Doric-prefix.pch b/Example/Pods/Target Support Files/Doric/Doric-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Example/Pods/Target Support Files/Doric/Doric-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/Doric/Doric.xcconfig b/Example/Pods/Target Support Files/Doric/Doric.xcconfig new file mode 100644 index 00000000..bc7e48a9 --- /dev/null +++ b/Example/Pods/Target Support Files/Doric/Doric.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Doric +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Doric" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Doric" "${PODS_ROOT}/Headers/Public/GCDWebServer" "${PODS_ROOT}/Headers/Public/SocketRocket" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/Example/Pods/Target Support Files/Doric/ResourceBundle-Doric-Doric-Info.plist b/Example/Pods/Target Support Files/Doric/ResourceBundle-Doric-Doric-Info.plist new file mode 100644 index 00000000..99a40ffb --- /dev/null +++ b/Example/Pods/Target Support Files/Doric/ResourceBundle-Doric-Doric-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + BNDL + CFBundleShortVersionString + 0.1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + NSPrincipalClass + + + diff --git a/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer-dummy.m b/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer-dummy.m new file mode 100644 index 00000000..4cb3a838 --- /dev/null +++ b/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_GCDWebServer : NSObject +@end +@implementation PodsDummy_GCDWebServer +@end diff --git a/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer-prefix.pch b/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer.xcconfig b/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer.xcconfig new file mode 100644 index 00000000..d9f7a185 --- /dev/null +++ b/Example/Pods/Target Support Files/GCDWebServer/GCDWebServer.xcconfig @@ -0,0 +1,9 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GCDWebServer +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/GCDWebServer" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/GCDWebServer" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GCDWebServer +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown new file mode 100644 index 00000000..44e2fda7 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown @@ -0,0 +1,333 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## Doric + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2019] [Doric.Pub] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +## GCDWebServer + +Copyright (c) 2012-2014, Pierre-Olivier Latour +All rights reserved. + +Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + +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 PIERRE-OLIVIER LATOUR 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. + + +## SocketRocket + + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + +## YYCache + +The MIT License (MIT) + +Copyright (c) 2015 ibireme + +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. + + + +## YYImage + +The MIT License (MIT) + +Copyright (c) 2015 ibireme + +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. + + + +## YYWebImage + +The MIT License (MIT) + +Copyright (c) 2015 ibireme + +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. + + +Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist new file mode 100644 index 00000000..0fd00c41 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist @@ -0,0 +1,395 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2019] [Doric.Pub] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + License + Apache-2.0 + Title + Doric + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2012-2014, Pierre-Olivier Latour +All rights reserved. + +Redistribution and use 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. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + +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 PIERRE-OLIVIER LATOUR 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. + + License + BSD + Title + GCDWebServer + Type + PSGroupSpecifier + + + FooterText + + Copyright 2012 Square Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + License + Apache License, Version 2.0 + Title + SocketRocket + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +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. + + + License + MIT + Title + YYCache + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +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. + + + License + MIT + Title + YYImage + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 ibireme <ibireme@gmail.com> + +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. + + + License + MIT + Title + YYWebImage + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m new file mode 100644 index 00000000..6ee3f900 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_Example : NSObject +@end +@implementation PodsDummy_Pods_Example +@end diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Debug-input-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Debug-input-files.xcfilelist new file mode 100644 index 00000000..0c5bb6d7 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Debug-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-resources.sh +${PODS_CONFIGURATION_BUILD_DIR}/Doric/Doric.bundle \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Debug-output-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Debug-output-files.xcfilelist new file mode 100644 index 00000000..761773b6 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Debug-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Doric.bundle \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Release-input-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Release-input-files.xcfilelist new file mode 100644 index 00000000..0c5bb6d7 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Release-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-resources.sh +${PODS_CONFIGURATION_BUILD_DIR}/Doric/Doric.bundle \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Release-output-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Release-output-files.xcfilelist new file mode 100644 index 00000000..761773b6 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources-Release-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Doric.bundle \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources.sh b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources.sh new file mode 100755 index 00000000..5d7b52f9 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-resources.sh @@ -0,0 +1,129 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then + # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy + # resources to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + +RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt +> "$RESOURCES_TO_COPY" + +XCASSET_FILES=() + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +case "${TARGETED_DEVICE_FAMILY:-}" in + 1,2) + TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" + ;; + 1) + TARGET_DEVICE_ARGS="--target-device iphone" + ;; + 2) + TARGET_DEVICE_ARGS="--target-device ipad" + ;; + 3) + TARGET_DEVICE_ARGS="--target-device tv" + ;; + 4) + TARGET_DEVICE_ARGS="--target-device watch" + ;; + *) + TARGET_DEVICE_ARGS="--target-device mac" + ;; +esac + +install_resource() +{ + if [[ "$1" = /* ]] ; then + RESOURCE_PATH="$1" + else + RESOURCE_PATH="${PODS_ROOT}/$1" + fi + if [[ ! -e "$RESOURCE_PATH" ]] ; then + cat << EOM +error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. +EOM + exit 1 + fi + case $RESOURCE_PATH in + *.storyboard) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.xib) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.framework) + echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + ;; + *.xcdatamodel) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" + ;; + *.xcdatamodeld) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" + ;; + *.xcmappingmodel) + echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true + xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" + ;; + *.xcassets) + ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" + XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") + ;; + *) + echo "$RESOURCE_PATH" || true + echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" + ;; + esac +} +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Doric/Doric.bundle" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Doric/Doric.bundle" +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then + mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi +rm -f "$RESOURCES_TO_COPY" + +if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] +then + # Find all other xcassets (this unfortunately includes those of path pods and other targets). + OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "${PODS_ROOT}*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + else + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" + fi +fi diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig b/Example/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig new file mode 100644 index 00000000..58f4c5d2 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig @@ -0,0 +1,9 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Doric" "${PODS_ROOT}/Headers/Public/GCDWebServer" "${PODS_ROOT}/Headers/Public/SocketRocket" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Doric" "${PODS_CONFIGURATION_BUILD_DIR}/GCDWebServer" "${PODS_CONFIGURATION_BUILD_DIR}/SocketRocket" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" +OTHER_LDFLAGS = $(inherited) -ObjC -l"Doric" -l"GCDWebServer" -l"SocketRocket" -l"YYCache" -l"YYImage" -l"YYWebImage" -l"icucore" -l"sqlite3" -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "Security" -framework "UIKit" -framework "WebP" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig b/Example/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig new file mode 100644 index 00000000..58f4c5d2 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig @@ -0,0 +1,9 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Doric" "${PODS_ROOT}/Headers/Public/GCDWebServer" "${PODS_ROOT}/Headers/Public/SocketRocket" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Doric" "${PODS_CONFIGURATION_BUILD_DIR}/GCDWebServer" "${PODS_CONFIGURATION_BUILD_DIR}/SocketRocket" "${PODS_CONFIGURATION_BUILD_DIR}/YYCache" "${PODS_CONFIGURATION_BUILD_DIR}/YYImage" "${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage" +OTHER_LDFLAGS = $(inherited) -ObjC -l"Doric" -l"GCDWebServer" -l"SocketRocket" -l"YYCache" -l"YYImage" -l"YYWebImage" -l"icucore" -l"sqlite3" -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "Security" -framework "UIKit" -framework "WebP" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-acknowledgements.markdown new file mode 100644 index 00000000..102af753 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-acknowledgements.markdown @@ -0,0 +1,3 @@ +# Acknowledgements +This application makes use of the following third party libraries: +Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-acknowledgements.plist new file mode 100644 index 00000000..7acbad1e --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-acknowledgements.plist @@ -0,0 +1,29 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-dummy.m b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-dummy.m new file mode 100644 index 00000000..c0024d52 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_ExampleTests : NSObject +@end +@implementation PodsDummy_Pods_ExampleTests +@end diff --git a/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.debug.xcconfig b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.debug.xcconfig new file mode 100644 index 00000000..a0f96f90 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.debug.xcconfig @@ -0,0 +1,8 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Doric" "${PODS_ROOT}/Headers/Public/GCDWebServer" "${PODS_ROOT}/Headers/Public/SocketRocket" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +OTHER_LDFLAGS = $(inherited) -l"icucore" -l"sqlite3" -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "Security" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.release.xcconfig b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.release.xcconfig new file mode 100644 index 00000000..a0f96f90 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.release.xcconfig @@ -0,0 +1,8 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Doric" "${PODS_ROOT}/Headers/Public/GCDWebServer" "${PODS_ROOT}/Headers/Public/SocketRocket" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +OTHER_LDFLAGS = $(inherited) -l"icucore" -l"sqlite3" -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "Security" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-acknowledgements.markdown new file mode 100644 index 00000000..102af753 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-acknowledgements.markdown @@ -0,0 +1,3 @@ +# Acknowledgements +This application makes use of the following third party libraries: +Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-acknowledgements.plist new file mode 100644 index 00000000..7acbad1e --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-acknowledgements.plist @@ -0,0 +1,29 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-dummy.m b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-dummy.m new file mode 100644 index 00000000..623ac2be --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_ExampleUITests : NSObject +@end +@implementation PodsDummy_Pods_ExampleUITests +@end diff --git a/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.debug.xcconfig b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.debug.xcconfig new file mode 100644 index 00000000..a0f96f90 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.debug.xcconfig @@ -0,0 +1,8 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Doric" "${PODS_ROOT}/Headers/Public/GCDWebServer" "${PODS_ROOT}/Headers/Public/SocketRocket" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +OTHER_LDFLAGS = $(inherited) -l"icucore" -l"sqlite3" -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "Security" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.release.xcconfig b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.release.xcconfig new file mode 100644 index 00000000..a0f96f90 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-ExampleUITests/Pods-ExampleUITests.release.xcconfig @@ -0,0 +1,8 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Doric" "${PODS_ROOT}/Headers/Public/GCDWebServer" "${PODS_ROOT}/Headers/Public/SocketRocket" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +OTHER_LDFLAGS = $(inherited) -l"icucore" -l"sqlite3" -l"z" -framework "Accelerate" -framework "AssetsLibrary" -framework "CFNetwork" -framework "CoreFoundation" -framework "ImageIO" -framework "MobileCoreServices" -framework "QuartzCore" -framework "Security" -framework "UIKit" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods diff --git a/Example/Pods/Target Support Files/SocketRocket/SocketRocket-dummy.m b/Example/Pods/Target Support Files/SocketRocket/SocketRocket-dummy.m new file mode 100644 index 00000000..1e9d2c9d --- /dev/null +++ b/Example/Pods/Target Support Files/SocketRocket/SocketRocket-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_SocketRocket : NSObject +@end +@implementation PodsDummy_SocketRocket +@end diff --git a/Example/Pods/Target Support Files/SocketRocket/SocketRocket-prefix.pch b/Example/Pods/Target Support Files/SocketRocket/SocketRocket-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Example/Pods/Target Support Files/SocketRocket/SocketRocket-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/SocketRocket/SocketRocket.xcconfig b/Example/Pods/Target Support Files/SocketRocket/SocketRocket.xcconfig new file mode 100644 index 00000000..3c8102bb --- /dev/null +++ b/Example/Pods/Target Support Files/SocketRocket/SocketRocket.xcconfig @@ -0,0 +1,9 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SocketRocket +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/SocketRocket" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/SocketRocket" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/SocketRocket +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/Example/Pods/Target Support Files/YYCache/YYCache-dummy.m b/Example/Pods/Target Support Files/YYCache/YYCache-dummy.m new file mode 100644 index 00000000..a17e4601 --- /dev/null +++ b/Example/Pods/Target Support Files/YYCache/YYCache-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_YYCache : NSObject +@end +@implementation PodsDummy_YYCache +@end diff --git a/Example/Pods/Target Support Files/YYCache/YYCache-prefix.pch b/Example/Pods/Target Support Files/YYCache/YYCache-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Example/Pods/Target Support Files/YYCache/YYCache-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/YYCache/YYCache.xcconfig b/Example/Pods/Target Support Files/YYCache/YYCache.xcconfig new file mode 100644 index 00000000..b917a4c3 --- /dev/null +++ b/Example/Pods/Target Support Files/YYCache/YYCache.xcconfig @@ -0,0 +1,9 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYCache +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/YYCache" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/YYCache" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYCache +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/Example/Pods/Target Support Files/YYImage/YYImage-dummy.m b/Example/Pods/Target Support Files/YYImage/YYImage-dummy.m new file mode 100644 index 00000000..3940c89c --- /dev/null +++ b/Example/Pods/Target Support Files/YYImage/YYImage-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_YYImage : NSObject +@end +@implementation PodsDummy_YYImage +@end diff --git a/Example/Pods/Target Support Files/YYImage/YYImage-prefix.pch b/Example/Pods/Target Support Files/YYImage/YYImage-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Example/Pods/Target Support Files/YYImage/YYImage-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/YYImage/YYImage.xcconfig b/Example/Pods/Target Support Files/YYImage/YYImage.xcconfig new file mode 100644 index 00000000..29bb3329 --- /dev/null +++ b/Example/Pods/Target Support Files/YYImage/YYImage.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYImage +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/YYImage" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/YYImage" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYImage +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/Example/Pods/Target Support Files/YYWebImage/YYWebImage-dummy.m b/Example/Pods/Target Support Files/YYWebImage/YYWebImage-dummy.m new file mode 100644 index 00000000..85670cfd --- /dev/null +++ b/Example/Pods/Target Support Files/YYWebImage/YYWebImage-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_YYWebImage : NSObject +@end +@implementation PodsDummy_YYWebImage +@end diff --git a/Example/Pods/Target Support Files/YYWebImage/YYWebImage-prefix.pch b/Example/Pods/Target Support Files/YYWebImage/YYWebImage-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Example/Pods/Target Support Files/YYWebImage/YYWebImage-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/YYWebImage/YYWebImage.xcconfig b/Example/Pods/Target Support Files/YYWebImage/YYWebImage.xcconfig new file mode 100644 index 00000000..3c793229 --- /dev/null +++ b/Example/Pods/Target Support Files/YYWebImage/YYWebImage.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYWebImage +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/YYImage/Vendor" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/YYWebImage" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/YYCache" "${PODS_ROOT}/Headers/Public/YYImage" "${PODS_ROOT}/Headers/Public/YYWebImage" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYWebImage +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES diff --git a/Example/Pods/YYCache/LICENSE b/Example/Pods/YYCache/LICENSE new file mode 100644 index 00000000..46be20bd --- /dev/null +++ b/Example/Pods/YYCache/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 ibireme + +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/Example/Pods/YYCache/README.md b/Example/Pods/YYCache/README.md new file mode 100755 index 00000000..46623b5c --- /dev/null +++ b/Example/Pods/YYCache/README.md @@ -0,0 +1,171 @@ +YYCache +============== + +[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/ibireme/YYCache/master/LICENSE)  +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)  +[![CocoaPods](http://img.shields.io/cocoapods/v/YYCache.svg?style=flat)](http://cocoapods.org/?q= YYCache)  +[![CocoaPods](http://img.shields.io/cocoapods/p/YYCache.svg?style=flat)](http://cocoapods.org/?q= YYCache)  +[![Support](https://img.shields.io/badge/support-iOS%206%2B%20-blue.svg?style=flat)](https://www.apple.com/nl/ios/)  +[![Build Status](https://travis-ci.org/ibireme/YYCache.svg?branch=master)](https://travis-ci.org/ibireme/YYCache) + +High performance cache framework for iOS.
+(It's a component of [YYKit](https://github.com/ibireme/YYKit)) + +Performance +============== + +![Memory cache benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_memory.png +) + +![Disk benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_disk.png +) + +You may [download](http://www.sqlite.org/download.html) and compile the latest version of sqlite and ignore the libsqlite3.dylib in iOS system to get higher performance. + +See `Benchmark/CacheBenchmark.xcodeproj` for more benchmark case. + + +Features +============== +- **LRU**: Objects can be evicted with least-recently-used algorithm. +- **Limitation**: Cache limitation can be controlled with count, cost, age and free space. +- **Compatibility**: The API is similar to `NSCache`, all methods are thread-safe. +- **Memory Cache** + - **Release Control**: Objects can be released synchronously/asynchronously on main thread or background thread. + - **Automatically Clear**: It can be configured to automatically evict objects when receive memory warning or app enter background. +- **Disk Cache** + - **Customization**: It supports custom archive and unarchive method to store object which does not adopt NSCoding. + - **Storage Type Control**: It can automatically decide the storage type (sqlite / file) for each object to get + better performance. + + +Installation +============== + +### CocoaPods + +1. Add `pod 'YYCache'` to your Podfile. +2. Run `pod install` or `pod update`. +3. Import \. + + +### Carthage + +1. Add `github "ibireme/YYCache"` to your Cartfile. +2. Run `carthage update --platform ios` and add the framework to your project. +3. Import \. + + +### Manually + +1. Download all the files in the YYCache subdirectory. +2. Add the source files to your Xcode project. +3. Link with required frameworks: + * UIKit + * CoreFoundation + * QuartzCore + * sqlite3 +4. Import `YYCache.h`. + + +Documentation +============== +Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYCache/).
+You can also install documentation locally using [appledoc](https://github.com/tomaz/appledoc). + + +Requirements +============== +This library requires `iOS 6.0+` and `Xcode 7.0+`. + + +License +============== +YYCache is provided under the MIT license. See LICENSE file for details. + + +

+--- +中文介绍 +============== +高性能 iOS 缓存框架。
+(该项目是 [YYKit](https://github.com/ibireme/YYKit) 组件之一) + +性能 +============== + +iPhone 6 上,内存缓存每秒响应次数 (越高越好): +![Memory cache benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_memory.png +) + +iPhone 6 上,磁盘缓存每秒响应次数 (越高越好): +![Disk benchmark result](https://raw.github.com/ibireme/YYCache/master/Benchmark/Result_disk.png +) + +推荐到 SQLite 官网[下载](http://www.sqlite.org/download.html)和编译最新的 SQLite,以替换 iOS 自带的 libsqlite3.dylib,以获得最高 1.5~3 倍的性能提升。 + +更多测试代码和用例见 `Benchmark/CacheBenchmark.xcodeproj`。 + + +特性 +============== +- **LRU**: 缓存支持 LRU (least-recently-used) 淘汰算法。 +- **缓存控制**: 支持多种缓存控制方法:总数量、总大小、存活时间、空闲空间。 +- **兼容性**: API 基本和 `NSCache` 保持一致, 所有方法都是线程安全的。 +- **内存缓存** + - **对象释放控制**: 对象的释放(release) 可以配置为同步或异步进行,可以配置在主线程或后台线程进行。 + - **自动清空**: 当收到内存警告或 App 进入后台时,缓存可以配置为自动清空。 +- **磁盘缓存** + - **可定制性**: 磁盘缓存支持自定义的归档解档方法,以支持那些没有实现 NSCoding 协议的对象。 + - **存储类型控制**: 磁盘缓存支持对每个对象的存储类型 (SQLite/文件) 进行自动或手动控制,以获得更高的存取性能。 + + +安装 +============== + +### CocoaPods + +1. 在 Podfile 中添加 `pod 'YYCache'`。 +2. 执行 `pod install` 或 `pod update`。 +3. 导入 \。 + + +### Carthage + +1. 在 Cartfile 中添加 `github "ibireme/YYCache"`。 +2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。 +3. 导入 \。 + + +### 手动安装 + +1. 下载 YYCache 文件夹内的所有内容。 +2. 将 YYCache 内的源文件添加(拖放)到你的工程。 +3. 链接以下的 frameworks: + * UIKit + * CoreFoundation + * QuartzCore + * sqlite3 +4. 导入 `YYCache.h`。 + + +文档 +============== +你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYCache/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。 + + +系统要求 +============== +该项目最低支持 `iOS 6.0` 和 `Xcode 7.0`。 + + +许可证 +============== +YYCache 使用 MIT 许可证,详情见 LICENSE 文件。 + + +相关链接 +============== +[YYCache 设计思路与技术细节](http://blog.ibireme.com/2015/10/26/yycache/) + + diff --git a/Example/Pods/YYCache/YYCache/YYCache.h b/Example/Pods/YYCache/YYCache/YYCache.h new file mode 100644 index 00000000..f042ed77 --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYCache.h @@ -0,0 +1,206 @@ +// +// YYCache.h +// YYCache +// +// Created by ibireme on 15/2/13. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +FOUNDATION_EXPORT double YYCacheVersionNumber; +FOUNDATION_EXPORT const unsigned char YYCacheVersionString[]; +#import +#import +#import +#elif __has_include() +#import +#import +#import +#else +#import "YYMemoryCache.h" +#import "YYDiskCache.h" +#import "YYKVStorage.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + + +/** + `YYCache` is a thread safe key-value cache. + + It use `YYMemoryCache` to store objects in a small and fast memory cache, + and use `YYDiskCache` to persisting objects to a large and slow disk cache. + See `YYMemoryCache` and `YYDiskCache` for more information. + */ +@interface YYCache : NSObject + +/** The name of the cache, readonly. */ +@property (copy, readonly) NSString *name; + +/** The underlying memory cache. see `YYMemoryCache` for more information.*/ +@property (strong, readonly) YYMemoryCache *memoryCache; + +/** The underlying disk cache. see `YYDiskCache` for more information.*/ +@property (strong, readonly) YYDiskCache *diskCache; + +/** + Create a new instance with the specified name. + Multiple instances with the same name will make the cache unstable. + + @param name The name of the cache. It will create a dictionary with the name in + the app's caches dictionary for disk cache. Once initialized you should not + read and write to this directory. + @result A new cache object, or nil if an error occurs. + */ +- (nullable instancetype)initWithName:(NSString *)name; + +/** + Create a new instance with the specified path. + Multiple instances with the same name will make the cache unstable. + + @param path Full path of a directory in which the cache will write data. + Once initialized you should not read and write to this directory. + @result A new cache object, or nil if an error occurs. + */ +- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER; + +/** + Convenience Initializers + Create a new instance with the specified name. + Multiple instances with the same name will make the cache unstable. + + @param name The name of the cache. It will create a dictionary with the name in + the app's caches dictionary for disk cache. Once initialized you should not + read and write to this directory. + @result A new cache object, or nil if an error occurs. + */ ++ (nullable instancetype)cacheWithName:(NSString *)name; + +/** + Convenience Initializers + Create a new instance with the specified path. + Multiple instances with the same name will make the cache unstable. + + @param path Full path of a directory in which the cache will write data. + Once initialized you should not read and write to this directory. + @result A new cache object, or nil if an error occurs. + */ ++ (nullable instancetype)cacheWithPath:(NSString *)path; + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +#pragma mark - Access Methods +///============================================================================= +/// @name Access Methods +///============================================================================= + +/** + Returns a boolean value that indicates whether a given key is in cache. + This method may blocks the calling thread until file read finished. + + @param key A string identifying the value. If nil, just return NO. + @return Whether the key is in cache. + */ +- (BOOL)containsObjectForKey:(NSString *)key; + +/** + Returns a boolean value with the block that indicates whether a given key is in cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param key A string identifying the value. If nil, just return NO. + @param block A block which will be invoked in background queue when finished. + */ +- (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block; + +/** + Returns the value associated with a given key. + This method may blocks the calling thread until file read finished. + + @param key A string identifying the value. If nil, just return nil. + @return The value associated with key, or nil if no value is associated with key. + */ +- (nullable id)objectForKey:(NSString *)key; + +/** + Returns the value associated with a given key. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param key A string identifying the value. If nil, just return nil. + @param block A block which will be invoked in background queue when finished. + */ +- (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id object))block; + +/** + Sets the value of the specified key in the cache. + This method may blocks the calling thread until file write finished. + + @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`. + @param key The key with which to associate the value. If nil, this method has no effect. + */ +- (void)setObject:(nullable id)object forKey:(NSString *)key; + +/** + Sets the value of the specified key in the cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`. + @param block A block which will be invoked in background queue when finished. + */ +- (void)setObject:(nullable id)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block; + +/** + Removes the value of the specified key in the cache. + This method may blocks the calling thread until file delete finished. + + @param key The key identifying the value to be removed. If nil, this method has no effect. + */ +- (void)removeObjectForKey:(NSString *)key; + +/** + Removes the value of the specified key in the cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param key The key identifying the value to be removed. If nil, this method has no effect. + @param block A block which will be invoked in background queue when finished. + */ +- (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block; + +/** + Empties the cache. + This method may blocks the calling thread until file delete finished. + */ +- (void)removeAllObjects; + +/** + Empties the cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param block A block which will be invoked in background queue when finished. + */ +- (void)removeAllObjectsWithBlock:(void(^)(void))block; + +/** + Empties the cache with block. + This method returns immediately and executes the clear operation with block in background. + + @warning You should not send message to this instance in these blocks. + @param progress This block will be invoked during removing, pass nil to ignore. + @param end This block will be invoked at the end, pass nil to ignore. + */ +- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress + endBlock:(nullable void(^)(BOOL error))end; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYCache/YYCache/YYCache.m b/Example/Pods/YYCache/YYCache/YYCache.m new file mode 100644 index 00000000..e6cdcba3 --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYCache.m @@ -0,0 +1,139 @@ +// +// YYCache.m +// YYCache +// +// Created by ibireme on 15/2/13. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYCache.h" +#import "YYMemoryCache.h" +#import "YYDiskCache.h" + +@implementation YYCache + +- (instancetype) init { + NSLog(@"Use \"initWithName\" or \"initWithPath\" to create YYCache instance."); + return [self initWithPath:@""]; +} + +- (instancetype)initWithName:(NSString *)name { + if (name.length == 0) return nil; + NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; + NSString *path = [cacheFolder stringByAppendingPathComponent:name]; + return [self initWithPath:path]; +} + +- (instancetype)initWithPath:(NSString *)path { + if (path.length == 0) return nil; + YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path]; + if (!diskCache) return nil; + NSString *name = [path lastPathComponent]; + YYMemoryCache *memoryCache = [YYMemoryCache new]; + memoryCache.name = name; + + self = [super init]; + _name = name; + _diskCache = diskCache; + _memoryCache = memoryCache; + return self; +} + ++ (instancetype)cacheWithName:(NSString *)name { + return [[self alloc] initWithName:name]; +} + ++ (instancetype)cacheWithPath:(NSString *)path { + return [[self alloc] initWithPath:path]; +} + +- (BOOL)containsObjectForKey:(NSString *)key { + return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key]; +} + +- (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block { + if (!block) return; + + if ([_memoryCache containsObjectForKey:key]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + block(key, YES); + }); + } else { + [_diskCache containsObjectForKey:key withBlock:block]; + } +} + +- (id)objectForKey:(NSString *)key { + id object = [_memoryCache objectForKey:key]; + if (!object) { + object = [_diskCache objectForKey:key]; + if (object) { + [_memoryCache setObject:object forKey:key]; + } + } + return object; +} + +- (void)objectForKey:(NSString *)key withBlock:(void (^)(NSString *key, id object))block { + if (!block) return; + id object = [_memoryCache objectForKey:key]; + if (object) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + block(key, object); + }); + } else { + [_diskCache objectForKey:key withBlock:^(NSString *key, id object) { + if (object && ![_memoryCache objectForKey:key]) { + [_memoryCache setObject:object forKey:key]; + } + block(key, object); + }]; + } +} + +- (void)setObject:(id)object forKey:(NSString *)key { + [_memoryCache setObject:object forKey:key]; + [_diskCache setObject:object forKey:key]; +} + +- (void)setObject:(id)object forKey:(NSString *)key withBlock:(void (^)(void))block { + [_memoryCache setObject:object forKey:key]; + [_diskCache setObject:object forKey:key withBlock:block]; +} + +- (void)removeObjectForKey:(NSString *)key { + [_memoryCache removeObjectForKey:key]; + [_diskCache removeObjectForKey:key]; +} + +- (void)removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block { + [_memoryCache removeObjectForKey:key]; + [_diskCache removeObjectForKey:key withBlock:block]; +} + +- (void)removeAllObjects { + [_memoryCache removeAllObjects]; + [_diskCache removeAllObjects]; +} + +- (void)removeAllObjectsWithBlock:(void(^)(void))block { + [_memoryCache removeAllObjects]; + [_diskCache removeAllObjectsWithBlock:block]; +} + +- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress + endBlock:(void(^)(BOOL error))end { + [_memoryCache removeAllObjects]; + [_diskCache removeAllObjectsWithProgressBlock:progress endBlock:end]; + +} + +- (NSString *)description { + if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name]; + else return [NSString stringWithFormat:@"<%@: %p>", self.class, self]; +} + +@end diff --git a/Example/Pods/YYCache/YYCache/YYDiskCache.h b/Example/Pods/YYCache/YYCache/YYDiskCache.h new file mode 100644 index 00000000..dd193e43 --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYDiskCache.h @@ -0,0 +1,412 @@ +// +// YYDiskCache.h +// YYCache +// +// Created by ibireme on 15/2/11. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + YYDiskCache is a thread-safe cache that stores key-value pairs backed by SQLite + and file system (similar to NSURLCache's disk cache). + + YYDiskCache has these features: + + * It use LRU (least-recently-used) to remove objects. + * It can be controlled by cost, count, and age. + * It can be configured to automatically evict objects when there's no free disk space. + * It can automatically decide the storage type (sqlite/file) for each object to get + better performance. + + You may compile the latest version of sqlite and ignore the libsqlite3.dylib in + iOS system to get 2x~4x speed up. + */ +@interface YYDiskCache : NSObject + +#pragma mark - Attribute +///============================================================================= +/// @name Attribute +///============================================================================= + +/** The name of the cache. Default is nil. */ +@property (nullable, copy) NSString *name; + +/** The path of the cache (read-only). */ +@property (readonly) NSString *path; + +/** + If the object's data size (in bytes) is larger than this value, then object will + be stored as a file, otherwise the object will be stored in sqlite. + + 0 means all objects will be stored as separated files, NSUIntegerMax means all + objects will be stored in sqlite. + + The default value is 20480 (20KB). + */ +@property (readonly) NSUInteger inlineThreshold; + +/** + If this block is not nil, then the block will be used to archive object instead + of NSKeyedArchiver. You can use this block to support the objects which do not + conform to the `NSCoding` protocol. + + The default value is nil. + */ +@property (nullable, copy) NSData *(^customArchiveBlock)(id object); + +/** + If this block is not nil, then the block will be used to unarchive object instead + of NSKeyedUnarchiver. You can use this block to support the objects which do not + conform to the `NSCoding` protocol. + + The default value is nil. + */ +@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data); + +/** + When an object needs to be saved as a file, this block will be invoked to generate + a file name for a specified key. If the block is nil, the cache use md5(key) as + default file name. + + The default value is nil. + */ +@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key); + + + +#pragma mark - Limit +///============================================================================= +/// @name Limit +///============================================================================= + +/** + The maximum number of objects the cache should hold. + + @discussion The default value is NSUIntegerMax, which means no limit. + This is not a strict limit — if the cache goes over the limit, some objects in the + cache could be evicted later in background queue. + */ +@property NSUInteger countLimit; + +/** + The maximum total cost that the cache can hold before it starts evicting objects. + + @discussion The default value is NSUIntegerMax, which means no limit. + This is not a strict limit — if the cache goes over the limit, some objects in the + cache could be evicted later in background queue. + */ +@property NSUInteger costLimit; + +/** + The maximum expiry time of objects in cache. + + @discussion The default value is DBL_MAX, which means no limit. + This is not a strict limit — if an object goes over the limit, the objects could + be evicted later in background queue. + */ +@property NSTimeInterval ageLimit; + +/** + The minimum free disk space (in bytes) which the cache should kept. + + @discussion The default value is 0, which means no limit. + If the free disk space is lower than this value, the cache will remove objects + to free some disk space. This is not a strict limit—if the free disk space goes + over the limit, the objects could be evicted later in background queue. + */ +@property NSUInteger freeDiskSpaceLimit; + +/** + The auto trim check time interval in seconds. Default is 60 (1 minute). + + @discussion The cache holds an internal timer to check whether the cache reaches + its limits, and if the limit is reached, it begins to evict objects. + */ +@property NSTimeInterval autoTrimInterval; + +/** + Set `YES` to enable error logs for debug. + */ +@property BOOL errorLogsEnabled; + +#pragma mark - Initializer +///============================================================================= +/// @name Initializer +///============================================================================= +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +/** + Create a new cache based on the specified path. + + @param path Full path of a directory in which the cache will write data. + Once initialized you should not read and write to this directory. + + @return A new cache object, or nil if an error occurs. + + @warning If the cache instance for the specified path already exists in memory, + this method will return it directly, instead of creating a new instance. + */ +- (nullable instancetype)initWithPath:(NSString *)path; + +/** + The designated initializer. + + @param path Full path of a directory in which the cache will write data. + Once initialized you should not read and write to this directory. + + @param threshold The data store inline threshold in bytes. If the object's data + size (in bytes) is larger than this value, then object will be stored as a + file, otherwise the object will be stored in sqlite. 0 means all objects will + be stored as separated files, NSUIntegerMax means all objects will be stored + in sqlite. If you don't know your object's size, 20480 is a good choice. + After first initialized you should not change this value of the specified path. + + @return A new cache object, or nil if an error occurs. + + @warning If the cache instance for the specified path already exists in memory, + this method will return it directly, instead of creating a new instance. + */ +- (nullable instancetype)initWithPath:(NSString *)path + inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER; + + +#pragma mark - Access Methods +///============================================================================= +/// @name Access Methods +///============================================================================= + +/** + Returns a boolean value that indicates whether a given key is in cache. + This method may blocks the calling thread until file read finished. + + @param key A string identifying the value. If nil, just return NO. + @return Whether the key is in cache. + */ +- (BOOL)containsObjectForKey:(NSString *)key; + +/** + Returns a boolean value with the block that indicates whether a given key is in cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param key A string identifying the value. If nil, just return NO. + @param block A block which will be invoked in background queue when finished. + */ +- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block; + +/** + Returns the value associated with a given key. + This method may blocks the calling thread until file read finished. + + @param key A string identifying the value. If nil, just return nil. + @return The value associated with key, or nil if no value is associated with key. + */ +- (nullable id)objectForKey:(NSString *)key; + +/** + Returns the value associated with a given key. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param key A string identifying the value. If nil, just return nil. + @param block A block which will be invoked in background queue when finished. + */ +- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id _Nullable object))block; + +/** + Sets the value of the specified key in the cache. + This method may blocks the calling thread until file write finished. + + @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`. + @param key The key with which to associate the value. If nil, this method has no effect. + */ +- (void)setObject:(nullable id)object forKey:(NSString *)key; + +/** + Sets the value of the specified key in the cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`. + @param block A block which will be invoked in background queue when finished. + */ +- (void)setObject:(nullable id)object forKey:(NSString *)key withBlock:(void(^)(void))block; + +/** + Removes the value of the specified key in the cache. + This method may blocks the calling thread until file delete finished. + + @param key The key identifying the value to be removed. If nil, this method has no effect. + */ +- (void)removeObjectForKey:(NSString *)key; + +/** + Removes the value of the specified key in the cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param key The key identifying the value to be removed. If nil, this method has no effect. + @param block A block which will be invoked in background queue when finished. + */ +- (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block; + +/** + Empties the cache. + This method may blocks the calling thread until file delete finished. + */ +- (void)removeAllObjects; + +/** + Empties the cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param block A block which will be invoked in background queue when finished. + */ +- (void)removeAllObjectsWithBlock:(void(^)(void))block; + +/** + Empties the cache with block. + This method returns immediately and executes the clear operation with block in background. + + @warning You should not send message to this instance in these blocks. + @param progress This block will be invoked during removing, pass nil to ignore. + @param end This block will be invoked at the end, pass nil to ignore. + */ +- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress + endBlock:(nullable void(^)(BOOL error))end; + + +/** + Returns the number of objects in this cache. + This method may blocks the calling thread until file read finished. + + @return The total objects count. + */ +- (NSInteger)totalCount; + +/** + Get the number of objects in this cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param block A block which will be invoked in background queue when finished. + */ +- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block; + +/** + Returns the total cost (in bytes) of objects in this cache. + This method may blocks the calling thread until file read finished. + + @return The total objects cost in bytes. + */ +- (NSInteger)totalCost; + +/** + Get the total cost (in bytes) of objects in this cache. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param block A block which will be invoked in background queue when finished. + */ +- (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block; + + +#pragma mark - Trim +///============================================================================= +/// @name Trim +///============================================================================= + +/** + Removes objects from the cache use LRU, until the `totalCount` is below the specified value. + This method may blocks the calling thread until operation finished. + + @param count The total count allowed to remain after the cache has been trimmed. + */ +- (void)trimToCount:(NSUInteger)count; + +/** + Removes objects from the cache use LRU, until the `totalCount` is below the specified value. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param count The total count allowed to remain after the cache has been trimmed. + @param block A block which will be invoked in background queue when finished. + */ +- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block; + +/** + Removes objects from the cache use LRU, until the `totalCost` is below the specified value. + This method may blocks the calling thread until operation finished. + + @param cost The total cost allowed to remain after the cache has been trimmed. + */ +- (void)trimToCost:(NSUInteger)cost; + +/** + Removes objects from the cache use LRU, until the `totalCost` is below the specified value. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param cost The total cost allowed to remain after the cache has been trimmed. + @param block A block which will be invoked in background queue when finished. + */ +- (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block; + +/** + Removes objects from the cache use LRU, until all expiry objects removed by the specified value. + This method may blocks the calling thread until operation finished. + + @param age The maximum age of the object. + */ +- (void)trimToAge:(NSTimeInterval)age; + +/** + Removes objects from the cache use LRU, until all expiry objects removed by the specified value. + This method returns immediately and invoke the passed block in background queue + when the operation finished. + + @param age The maximum age of the object. + @param block A block which will be invoked in background queue when finished. + */ +- (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block; + + +#pragma mark - Extended Data +///============================================================================= +/// @name Extended Data +///============================================================================= + +/** + Get extended data from an object. + + @discussion See 'setExtendedData:toObject:' for more information. + + @param object An object. + @return The extended data. + */ ++ (nullable NSData *)getExtendedDataFromObject:(id)object; + +/** + Set extended data to an object. + + @discussion You can set any extended data to an object before you save the object + to disk cache. The extended data will also be saved with this object. You can get + the extended data later with "getExtendedDataFromObject:". + + @param extendedData The extended data (pass nil to remove). + @param object The object. + */ ++ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYCache/YYCache/YYDiskCache.m b/Example/Pods/YYCache/YYCache/YYDiskCache.m new file mode 100644 index 00000000..735cc5df --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYDiskCache.m @@ -0,0 +1,458 @@ +// +// YYDiskCache.m +// YYCache +// +// Created by ibireme on 15/2/11. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYDiskCache.h" +#import "YYKVStorage.h" +#import +#import +#import +#import + +#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER) +#define Unlock() dispatch_semaphore_signal(self->_lock) + +static const int extended_data_key; + +/// Free disk space in bytes. +static int64_t _YYDiskSpaceFree() { + NSError *error = nil; + NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error]; + if (error) return -1; + int64_t space = [[attrs objectForKey:NSFileSystemFreeSize] longLongValue]; + if (space < 0) space = -1; + return space; +} + +/// String's md5 hash. +static NSString *_YYNSStringMD5(NSString *string) { + if (!string) return nil; + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + unsigned char result[CC_MD5_DIGEST_LENGTH]; + CC_MD5(data.bytes, (CC_LONG)data.length, result); + return [NSString stringWithFormat: + @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + result[0], result[1], result[2], result[3], + result[4], result[5], result[6], result[7], + result[8], result[9], result[10], result[11], + result[12], result[13], result[14], result[15] + ]; +} + +/// weak reference for all instances +static NSMapTable *_globalInstances; +static dispatch_semaphore_t _globalInstancesLock; + +static void _YYDiskCacheInitGlobal() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _globalInstancesLock = dispatch_semaphore_create(1); + _globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0]; + }); +} + +static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) { + if (path.length == 0) return nil; + _YYDiskCacheInitGlobal(); + dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); + id cache = [_globalInstances objectForKey:path]; + dispatch_semaphore_signal(_globalInstancesLock); + return cache; +} + +static void _YYDiskCacheSetGlobal(YYDiskCache *cache) { + if (cache.path.length == 0) return; + _YYDiskCacheInitGlobal(); + dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); + [_globalInstances setObject:cache forKey:cache.path]; + dispatch_semaphore_signal(_globalInstancesLock); +} + + + +@implementation YYDiskCache { + YYKVStorage *_kv; + dispatch_semaphore_t _lock; + dispatch_queue_t _queue; +} + +- (void)_trimRecursively { + __weak typeof(self) _self = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ + __strong typeof(_self) self = _self; + if (!self) return; + [self _trimInBackground]; + [self _trimRecursively]; + }); +} + +- (void)_trimInBackground { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + if (!self) return; + Lock(); + [self _trimToCost:self.costLimit]; + [self _trimToCount:self.countLimit]; + [self _trimToAge:self.ageLimit]; + [self _trimToFreeDiskSpace:self.freeDiskSpaceLimit]; + Unlock(); + }); +} + +- (void)_trimToCost:(NSUInteger)costLimit { + if (costLimit >= INT_MAX) return; + [_kv removeItemsToFitSize:(int)costLimit]; + +} + +- (void)_trimToCount:(NSUInteger)countLimit { + if (countLimit >= INT_MAX) return; + [_kv removeItemsToFitCount:(int)countLimit]; +} + +- (void)_trimToAge:(NSTimeInterval)ageLimit { + if (ageLimit <= 0) { + [_kv removeAllItems]; + return; + } + long timestamp = time(NULL); + if (timestamp <= ageLimit) return; + long age = timestamp - ageLimit; + if (age >= INT_MAX) return; + [_kv removeItemsEarlierThanTime:(int)age]; +} + +- (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace { + if (targetFreeDiskSpace == 0) return; + int64_t totalBytes = [_kv getItemsSize]; + if (totalBytes <= 0) return; + int64_t diskFreeBytes = _YYDiskSpaceFree(); + if (diskFreeBytes < 0) return; + int64_t needTrimBytes = targetFreeDiskSpace - diskFreeBytes; + if (needTrimBytes <= 0) return; + int64_t costLimit = totalBytes - needTrimBytes; + if (costLimit < 0) costLimit = 0; + [self _trimToCost:(int)costLimit]; +} + +- (NSString *)_filenameForKey:(NSString *)key { + NSString *filename = nil; + if (_customFileNameBlock) filename = _customFileNameBlock(key); + if (!filename) filename = _YYNSStringMD5(key); + return filename; +} + +- (void)_appWillBeTerminated { + Lock(); + _kv = nil; + Unlock(); +} + +#pragma mark - public + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; +} + +- (instancetype)init { + @throw [NSException exceptionWithName:@"YYDiskCache init error" reason:@"YYDiskCache must be initialized with a path. Use 'initWithPath:' or 'initWithPath:inlineThreshold:' instead." userInfo:nil]; + return [self initWithPath:@"" inlineThreshold:0]; +} + +- (instancetype)initWithPath:(NSString *)path { + return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB +} + +- (instancetype)initWithPath:(NSString *)path + inlineThreshold:(NSUInteger)threshold { + self = [super init]; + if (!self) return nil; + + YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path); + if (globalCache) return globalCache; + + YYKVStorageType type; + if (threshold == 0) { + type = YYKVStorageTypeFile; + } else if (threshold == NSUIntegerMax) { + type = YYKVStorageTypeSQLite; + } else { + type = YYKVStorageTypeMixed; + } + + YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type]; + if (!kv) return nil; + + _kv = kv; + _path = path; + _lock = dispatch_semaphore_create(1); + _queue = dispatch_queue_create("com.ibireme.cache.disk", DISPATCH_QUEUE_CONCURRENT); + _inlineThreshold = threshold; + _countLimit = NSUIntegerMax; + _costLimit = NSUIntegerMax; + _ageLimit = DBL_MAX; + _freeDiskSpaceLimit = 0; + _autoTrimInterval = 60; + + [self _trimRecursively]; + _YYDiskCacheSetGlobal(self); + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil]; + return self; +} + +- (BOOL)containsObjectForKey:(NSString *)key { + if (!key) return NO; + Lock(); + BOOL contains = [_kv itemExistsForKey:key]; + Unlock(); + return contains; +} + +- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block { + if (!block) return; + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + BOOL contains = [self containsObjectForKey:key]; + block(key, contains); + }); +} + +- (id)objectForKey:(NSString *)key { + if (!key) return nil; + Lock(); + YYKVStorageItem *item = [_kv getItemForKey:key]; + Unlock(); + if (!item.value) return nil; + + id object = nil; + if (_customUnarchiveBlock) { + object = _customUnarchiveBlock(item.value); + } else { + @try { + object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value]; + } + @catch (NSException *exception) { + // nothing to do... + } + } + if (object && item.extendedData) { + [YYDiskCache setExtendedData:item.extendedData toObject:object]; + } + return object; +} + +- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id object))block { + if (!block) return; + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + id object = [self objectForKey:key]; + block(key, object); + }); +} + +- (void)setObject:(id)object forKey:(NSString *)key { + if (!key) return; + if (!object) { + [self removeObjectForKey:key]; + return; + } + + NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object]; + NSData *value = nil; + if (_customArchiveBlock) { + value = _customArchiveBlock(object); + } else { + @try { + value = [NSKeyedArchiver archivedDataWithRootObject:object]; + } + @catch (NSException *exception) { + // nothing to do... + } + } + if (!value) return; + NSString *filename = nil; + if (_kv.type != YYKVStorageTypeSQLite) { + if (value.length > _inlineThreshold) { + filename = [self _filenameForKey:key]; + } + } + + Lock(); + [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData]; + Unlock(); +} + +- (void)setObject:(id)object forKey:(NSString *)key withBlock:(void(^)(void))block { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + [self setObject:object forKey:key]; + if (block) block(); + }); +} + +- (void)removeObjectForKey:(NSString *)key { + if (!key) return; + Lock(); + [_kv removeItemForKey:key]; + Unlock(); +} + +- (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + [self removeObjectForKey:key]; + if (block) block(key); + }); +} + +- (void)removeAllObjects { + Lock(); + [_kv removeAllItems]; + Unlock(); +} + +- (void)removeAllObjectsWithBlock:(void(^)(void))block { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + [self removeAllObjects]; + if (block) block(); + }); +} + +- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress + endBlock:(void(^)(BOOL error))end { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + if (!self) { + if (end) end(YES); + return; + } + Lock(); + [_kv removeAllItemsWithProgressBlock:progress endBlock:end]; + Unlock(); + }); +} + +- (NSInteger)totalCount { + Lock(); + int count = [_kv getItemsCount]; + Unlock(); + return count; +} + +- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block { + if (!block) return; + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + NSInteger totalCount = [self totalCount]; + block(totalCount); + }); +} + +- (NSInteger)totalCost { + Lock(); + int count = [_kv getItemsSize]; + Unlock(); + return count; +} + +- (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block { + if (!block) return; + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + NSInteger totalCost = [self totalCost]; + block(totalCost); + }); +} + +- (void)trimToCount:(NSUInteger)count { + Lock(); + [self _trimToCount:count]; + Unlock(); +} + +- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + [self trimToCount:count]; + if (block) block(); + }); +} + +- (void)trimToCost:(NSUInteger)cost { + Lock(); + [self _trimToCost:cost]; + Unlock(); +} + +- (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + [self trimToCost:cost]; + if (block) block(); + }); +} + +- (void)trimToAge:(NSTimeInterval)age { + Lock(); + [self _trimToAge:age]; + Unlock(); +} + +- (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block { + __weak typeof(self) _self = self; + dispatch_async(_queue, ^{ + __strong typeof(_self) self = _self; + [self trimToAge:age]; + if (block) block(); + }); +} + ++ (NSData *)getExtendedDataFromObject:(id)object { + if (!object) return nil; + return (NSData *)objc_getAssociatedObject(object, &extended_data_key); +} + ++ (void)setExtendedData:(NSData *)extendedData toObject:(id)object { + if (!object) return; + objc_setAssociatedObject(object, &extended_data_key, extendedData, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSString *)description { + if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@:%@)", self.class, self, _name, _path]; + else return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _path]; +} + +- (BOOL)errorLogsEnabled { + Lock(); + BOOL enabled = _kv.errorLogsEnabled; + Unlock(); + return enabled; +} + +- (void)setErrorLogsEnabled:(BOOL)errorLogsEnabled { + Lock(); + _kv.errorLogsEnabled = errorLogsEnabled; + Unlock(); +} + +@end diff --git a/Example/Pods/YYCache/YYCache/YYKVStorage.h b/Example/Pods/YYCache/YYCache/YYKVStorage.h new file mode 100644 index 00000000..939a6e4c --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYKVStorage.h @@ -0,0 +1,325 @@ +// +// YYKVStorage.h +// YYCache +// +// Created by ibireme on 15/4/22. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + YYKVStorageItem is used by `YYKVStorage` to store key-value pair and meta data. + Typically, you should not use this class directly. + */ +@interface YYKVStorageItem : NSObject +@property (nonatomic, strong) NSString *key; ///< key +@property (nonatomic, strong) NSData *value; ///< value +@property (nullable, nonatomic, strong) NSString *filename; ///< filename (nil if inline) +@property (nonatomic) int size; ///< value's size in bytes +@property (nonatomic) int modTime; ///< modification unix timestamp +@property (nonatomic) int accessTime; ///< last access unix timestamp +@property (nullable, nonatomic, strong) NSData *extendedData; ///< extended data (nil if no extended data) +@end + +/** + Storage type, indicated where the `YYKVStorageItem.value` stored. + + @discussion Typically, write data to sqlite is faster than extern file, but + reading performance is dependent on data size. In my test (on iPhone 6 64G), + read data from extern file is faster than from sqlite when the data is larger + than 20KB. + + * If you want to store large number of small datas (such as contacts cache), + use YYKVStorageTypeSQLite to get better performance. + * If you want to store large files (such as image cache), + use YYKVStorageTypeFile to get better performance. + * You can use YYKVStorageTypeMixed and choice your storage type for each item. + + See for more information. + */ +typedef NS_ENUM(NSUInteger, YYKVStorageType) { + + /// The `value` is stored as a file in file system. + YYKVStorageTypeFile = 0, + + /// The `value` is stored in sqlite with blob type. + YYKVStorageTypeSQLite = 1, + + /// The `value` is stored in file system or sqlite based on your choice. + YYKVStorageTypeMixed = 2, +}; + + + +/** + YYKVStorage is a key-value storage based on sqlite and file system. + Typically, you should not use this class directly. + + @discussion The designated initializer for YYKVStorage is `initWithPath:type:`. + After initialized, a directory is created based on the `path` to hold key-value data. + Once initialized you should not read or write this directory without the instance. + + You may compile the latest version of sqlite and ignore the libsqlite3.dylib in + iOS system to get 2x~4x speed up. + + @warning The instance of this class is *NOT* thread safe, you need to make sure + that there's only one thread to access the instance at the same time. If you really + need to process large amounts of data in multi-thread, you should split the data + to multiple KVStorage instance (sharding). + */ +@interface YYKVStorage : NSObject + +#pragma mark - Attribute +///============================================================================= +/// @name Attribute +///============================================================================= + +@property (nonatomic, readonly) NSString *path; ///< The path of this storage. +@property (nonatomic, readonly) YYKVStorageType type; ///< The type of this storage. +@property (nonatomic) BOOL errorLogsEnabled; ///< Set `YES` to enable error logs for debug. + +#pragma mark - Initializer +///============================================================================= +/// @name Initializer +///============================================================================= +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +/** + The designated initializer. + + @param path Full path of a directory in which the storage will write data. If + the directory is not exists, it will try to create one, otherwise it will + read the data in this directory. + @param type The storage type. After first initialized you should not change the + type of the specified path. + @return A new storage object, or nil if an error occurs. + @warning Multiple instances with the same path will make the storage unstable. + */ +- (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type NS_DESIGNATED_INITIALIZER; + + +#pragma mark - Save Items +///============================================================================= +/// @name Save Items +///============================================================================= + +/** + Save an item or update the item with 'key' if it already exists. + + @discussion This method will save the item.key, item.value, item.filename and + item.extendedData to disk or sqlite, other properties will be ignored. item.key + and item.value should not be empty (nil or zero length). + + If the `type` is YYKVStorageTypeFile, then the item.filename should not be empty. + If the `type` is YYKVStorageTypeSQLite, then the item.filename will be ignored. + It the `type` is YYKVStorageTypeMixed, then the item.value will be saved to file + system if the item.filename is not empty, otherwise it will be saved to sqlite. + + @param item An item. + @return Whether succeed. + */ +- (BOOL)saveItem:(YYKVStorageItem *)item; + +/** + Save an item or update the item with 'key' if it already exists. + + @discussion This method will save the key-value pair to sqlite. If the `type` is + YYKVStorageTypeFile, then this method will failed. + + @param key The key, should not be empty (nil or zero length). + @param value The key, should not be empty (nil or zero length). + @return Whether succeed. + */ +- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value; + +/** + Save an item or update the item with 'key' if it already exists. + + @discussion + If the `type` is YYKVStorageTypeFile, then the `filename` should not be empty. + If the `type` is YYKVStorageTypeSQLite, then the `filename` will be ignored. + It the `type` is YYKVStorageTypeMixed, then the `value` will be saved to file + system if the `filename` is not empty, otherwise it will be saved to sqlite. + + @param key The key, should not be empty (nil or zero length). + @param value The key, should not be empty (nil or zero length). + @param filename The filename. + @param extendedData The extended data for this item (pass nil to ignore it). + + @return Whether succeed. + */ +- (BOOL)saveItemWithKey:(NSString *)key + value:(NSData *)value + filename:(nullable NSString *)filename + extendedData:(nullable NSData *)extendedData; + +#pragma mark - Remove Items +///============================================================================= +/// @name Remove Items +///============================================================================= + +/** + Remove an item with 'key'. + + @param key The item's key. + @return Whether succeed. + */ +- (BOOL)removeItemForKey:(NSString *)key; + +/** + Remove items with an array of keys. + + @param keys An array of specified keys. + + @return Whether succeed. + */ +- (BOOL)removeItemForKeys:(NSArray *)keys; + +/** + Remove all items which `value` is larger than a specified size. + + @param size The maximum size in bytes. + @return Whether succeed. + */ +- (BOOL)removeItemsLargerThanSize:(int)size; + +/** + Remove all items which last access time is earlier than a specified timestamp. + + @param time The specified unix timestamp. + @return Whether succeed. + */ +- (BOOL)removeItemsEarlierThanTime:(int)time; + +/** + Remove items to make the total size not larger than a specified size. + The least recently used (LRU) items will be removed first. + + @param maxSize The specified size in bytes. + @return Whether succeed. + */ +- (BOOL)removeItemsToFitSize:(int)maxSize; + +/** + Remove items to make the total count not larger than a specified count. + The least recently used (LRU) items will be removed first. + + @param maxCount The specified item count. + @return Whether succeed. + */ +- (BOOL)removeItemsToFitCount:(int)maxCount; + +/** + Remove all items in background queue. + + @discussion This method will remove the files and sqlite database to a trash + folder, and then clear the folder in background queue. So this method is much + faster than `removeAllItemsWithProgressBlock:endBlock:`. + + @return Whether succeed. + */ +- (BOOL)removeAllItems; + +/** + Remove all items. + + @warning You should not send message to this instance in these blocks. + @param progress This block will be invoked during removing, pass nil to ignore. + @param end This block will be invoked at the end, pass nil to ignore. + */ +- (void)removeAllItemsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress + endBlock:(nullable void(^)(BOOL error))end; + + +#pragma mark - Get Items +///============================================================================= +/// @name Get Items +///============================================================================= + +/** + Get item with a specified key. + + @param key A specified key. + @return Item for the key, or nil if not exists / error occurs. + */ +- (nullable YYKVStorageItem *)getItemForKey:(NSString *)key; + +/** + Get item information with a specified key. + The `value` in this item will be ignored. + + @param key A specified key. + @return Item information for the key, or nil if not exists / error occurs. + */ +- (nullable YYKVStorageItem *)getItemInfoForKey:(NSString *)key; + +/** + Get item value with a specified key. + + @param key A specified key. + @return Item's value, or nil if not exists / error occurs. + */ +- (nullable NSData *)getItemValueForKey:(NSString *)key; + +/** + Get items with an array of keys. + + @param keys An array of specified keys. + @return An array of `YYKVStorageItem`, or nil if not exists / error occurs. + */ +- (nullable NSArray *)getItemForKeys:(NSArray *)keys; + +/** + Get item infomartions with an array of keys. + The `value` in items will be ignored. + + @param keys An array of specified keys. + @return An array of `YYKVStorageItem`, or nil if not exists / error occurs. + */ +- (nullable NSArray *)getItemInfoForKeys:(NSArray *)keys; + +/** + Get items value with an array of keys. + + @param keys An array of specified keys. + @return A dictionary which key is 'key' and value is 'value', or nil if not + exists / error occurs. + */ +- (nullable NSDictionary *)getItemValueForKeys:(NSArray *)keys; + +#pragma mark - Get Storage Status +///============================================================================= +/// @name Get Storage Status +///============================================================================= + +/** + Whether an item exists for a specified key. + + @param key A specified key. + + @return `YES` if there's an item exists for the key, `NO` if not exists or an error occurs. + */ +- (BOOL)itemExistsForKey:(NSString *)key; + +/** + Get total item count. + @return Total item count, -1 when an error occurs. + */ +- (int)getItemsCount; + +/** + Get item value's total size in bytes. + @return Total size in bytes, -1 when an error occurs. + */ +- (int)getItemsSize; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYCache/YYCache/YYKVStorage.m b/Example/Pods/YYCache/YYCache/YYKVStorage.m new file mode 100644 index 00000000..501dfdde --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYKVStorage.m @@ -0,0 +1,1069 @@ +// +// YYKVStorage.m +// YYCache +// +// Created by ibireme on 15/4/22. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYKVStorage.h" +#import +#import + +#if __has_include() +#import +#else +#import "sqlite3.h" +#endif + + +static const NSUInteger kMaxErrorRetryCount = 8; +static const NSTimeInterval kMinRetryTimeInterval = 2.0; +static const int kPathLengthMax = PATH_MAX - 64; +static NSString *const kDBFileName = @"manifest.sqlite"; +static NSString *const kDBShmFileName = @"manifest.sqlite-shm"; +static NSString *const kDBWalFileName = @"manifest.sqlite-wal"; +static NSString *const kDataDirectoryName = @"data"; +static NSString *const kTrashDirectoryName = @"trash"; + + +/* + File: + /path/ + /manifest.sqlite + /manifest.sqlite-shm + /manifest.sqlite-wal + /data/ + /e10adc3949ba59abbe56e057f20f883e + /e10adc3949ba59abbe56e057f20f883e + /trash/ + /unused_file_or_folder + + SQL: + create table if not exists manifest ( + key text, + filename text, + size integer, + inline_data blob, + modification_time integer, + last_access_time integer, + extended_data blob, + primary key(key) + ); + create index if not exists last_access_time_idx on manifest(last_access_time); + */ + +/// Returns nil in App Extension. +static UIApplication *_YYSharedApplication() { + static BOOL isAppExtension = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class cls = NSClassFromString(@"UIApplication"); + if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES; + if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES; + }); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)]; +#pragma clang diagnostic pop +} + + +@implementation YYKVStorageItem +@end + +@implementation YYKVStorage { + dispatch_queue_t _trashQueue; + + NSString *_path; + NSString *_dbPath; + NSString *_dataPath; + NSString *_trashPath; + + sqlite3 *_db; + CFMutableDictionaryRef _dbStmtCache; + NSTimeInterval _dbLastOpenErrorTime; + NSUInteger _dbOpenErrorCount; +} + + +#pragma mark - db + +- (BOOL)_dbOpen { + if (_db) return YES; + + int result = sqlite3_open(_dbPath.UTF8String, &_db); + if (result == SQLITE_OK) { + CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks; + CFDictionaryValueCallBacks valueCallbacks = {0}; + _dbStmtCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &keyCallbacks, &valueCallbacks); + _dbLastOpenErrorTime = 0; + _dbOpenErrorCount = 0; + return YES; + } else { + _db = NULL; + if (_dbStmtCache) CFRelease(_dbStmtCache); + _dbStmtCache = NULL; + _dbLastOpenErrorTime = CACurrentMediaTime(); + _dbOpenErrorCount++; + + if (_errorLogsEnabled) { + NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result); + } + return NO; + } +} + +- (BOOL)_dbClose { + if (!_db) return YES; + + int result = 0; + BOOL retry = NO; + BOOL stmtFinalized = NO; + + if (_dbStmtCache) CFRelease(_dbStmtCache); + _dbStmtCache = NULL; + + do { + retry = NO; + result = sqlite3_close(_db); + if (result == SQLITE_BUSY || result == SQLITE_LOCKED) { + if (!stmtFinalized) { + stmtFinalized = YES; + sqlite3_stmt *stmt; + while ((stmt = sqlite3_next_stmt(_db, nil)) != 0) { + sqlite3_finalize(stmt); + retry = YES; + } + } + } else if (result != SQLITE_OK) { + if (_errorLogsEnabled) { + NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result); + } + } + } while (retry); + _db = NULL; + return YES; +} + +- (BOOL)_dbCheck { + if (!_db) { + if (_dbOpenErrorCount < kMaxErrorRetryCount && + CACurrentMediaTime() - _dbLastOpenErrorTime > kMinRetryTimeInterval) { + return [self _dbOpen] && [self _dbInitialize]; + } else { + return NO; + } + } + return YES; +} + +- (BOOL)_dbInitialize { + NSString *sql = @"pragma journal_mode = wal; pragma synchronous = normal; create table if not exists manifest (key text, filename text, size integer, inline_data blob, modification_time integer, last_access_time integer, extended_data blob, primary key(key)); create index if not exists last_access_time_idx on manifest(last_access_time);"; + return [self _dbExecute:sql]; +} + +- (void)_dbCheckpoint { + if (![self _dbCheck]) return; + // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file. + sqlite3_wal_checkpoint(_db, NULL); +} + +- (BOOL)_dbExecute:(NSString *)sql { + if (sql.length == 0) return NO; + if (![self _dbCheck]) return NO; + + char *error = NULL; + int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &error); + if (error) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite exec error (%d): %s", __FUNCTION__, __LINE__, result, error); + sqlite3_free(error); + } + + return result == SQLITE_OK; +} + +- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql { + if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL; + sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql)); + if (!stmt) { + int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL); + if (result != SQLITE_OK) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NULL; + } + CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt); + } else { + sqlite3_reset(stmt); + } + return stmt; +} + +- (NSString *)_dbJoinedKeys:(NSArray *)keys { + NSMutableString *string = [NSMutableString new]; + for (NSUInteger i = 0,max = keys.count; i < max; i++) { + [string appendString:@"?"]; + if (i + 1 != max) { + [string appendString:@","]; + } + } + return string; +} + +- (void)_dbBindJoinedKeys:(NSArray *)keys stmt:(sqlite3_stmt *)stmt fromIndex:(int)index{ + for (int i = 0, max = (int)keys.count; i < max; i++) { + NSString *key = keys[i]; + sqlite3_bind_text(stmt, index + i, key.UTF8String, -1, NULL); + } +} + +- (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData { + NSString *sql = @"insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return NO; + + int timestamp = (int)time(NULL); + sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL); + sqlite3_bind_text(stmt, 2, fileName.UTF8String, -1, NULL); + sqlite3_bind_int(stmt, 3, (int)value.length); + if (fileName.length == 0) { + sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0); + } else { + sqlite3_bind_blob(stmt, 4, NULL, 0, 0); + } + sqlite3_bind_int(stmt, 5, timestamp); + sqlite3_bind_int(stmt, 6, timestamp); + sqlite3_bind_blob(stmt, 7, extendedData.bytes, (int)extendedData.length, 0); + + int result = sqlite3_step(stmt); + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite insert error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + return YES; +} + +- (BOOL)_dbUpdateAccessTimeWithKey:(NSString *)key { + NSString *sql = @"update manifest set last_access_time = ?1 where key = ?2;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return NO; + sqlite3_bind_int(stmt, 1, (int)time(NULL)); + sqlite3_bind_text(stmt, 2, key.UTF8String, -1, NULL); + int result = sqlite3_step(stmt); + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite update error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + return YES; +} + +- (BOOL)_dbUpdateAccessTimeWithKeys:(NSArray *)keys { + if (![self _dbCheck]) return NO; + int t = (int)time(NULL); + NSString *sql = [NSString stringWithFormat:@"update manifest set last_access_time = %d where key in (%@);", t, [self _dbJoinedKeys:keys]]; + + sqlite3_stmt *stmt = NULL; + int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL); + if (result != SQLITE_OK) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + + [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1]; + result = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite update error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + return YES; +} + +- (BOOL)_dbDeleteItemWithKey:(NSString *)key { + NSString *sql = @"delete from manifest where key = ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return NO; + sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL); + + int result = sqlite3_step(stmt); + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d db delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + return YES; +} + +- (BOOL)_dbDeleteItemWithKeys:(NSArray *)keys { + if (![self _dbCheck]) return NO; + NSString *sql = [NSString stringWithFormat:@"delete from manifest where key in (%@);", [self _dbJoinedKeys:keys]]; + sqlite3_stmt *stmt = NULL; + int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL); + if (result != SQLITE_OK) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + + [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1]; + result = sqlite3_step(stmt); + sqlite3_finalize(stmt); + if (result == SQLITE_ERROR) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + return YES; +} + +- (BOOL)_dbDeleteItemsWithSizeLargerThan:(int)size { + NSString *sql = @"delete from manifest where size > ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return NO; + sqlite3_bind_int(stmt, 1, size); + int result = sqlite3_step(stmt); + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + return YES; +} + +- (BOOL)_dbDeleteItemsWithTimeEarlierThan:(int)time { + NSString *sql = @"delete from manifest where last_access_time < ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return NO; + sqlite3_bind_int(stmt, 1, time); + int result = sqlite3_step(stmt); + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return NO; + } + return YES; +} + +- (YYKVStorageItem *)_dbGetItemFromStmt:(sqlite3_stmt *)stmt excludeInlineData:(BOOL)excludeInlineData { + int i = 0; + char *key = (char *)sqlite3_column_text(stmt, i++); + char *filename = (char *)sqlite3_column_text(stmt, i++); + int size = sqlite3_column_int(stmt, i++); + const void *inline_data = excludeInlineData ? NULL : sqlite3_column_blob(stmt, i); + int inline_data_bytes = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, i++); + int modification_time = sqlite3_column_int(stmt, i++); + int last_access_time = sqlite3_column_int(stmt, i++); + const void *extended_data = sqlite3_column_blob(stmt, i); + int extended_data_bytes = sqlite3_column_bytes(stmt, i++); + + YYKVStorageItem *item = [YYKVStorageItem new]; + if (key) item.key = [NSString stringWithUTF8String:key]; + if (filename && *filename != 0) item.filename = [NSString stringWithUTF8String:filename]; + item.size = size; + if (inline_data_bytes > 0 && inline_data) item.value = [NSData dataWithBytes:inline_data length:inline_data_bytes]; + item.modTime = modification_time; + item.accessTime = last_access_time; + if (extended_data_bytes > 0 && extended_data) item.extendedData = [NSData dataWithBytes:extended_data length:extended_data_bytes]; + return item; +} + +- (YYKVStorageItem *)_dbGetItemWithKey:(NSString *)key excludeInlineData:(BOOL)excludeInlineData { + NSString *sql = excludeInlineData ? @"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key = ?1;" : @"select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key = ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return nil; + sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL); + + YYKVStorageItem *item = nil; + int result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData]; + } else { + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + } + } + return item; +} + +- (NSMutableArray *)_dbGetItemWithKeys:(NSArray *)keys excludeInlineData:(BOOL)excludeInlineData { + if (![self _dbCheck]) return nil; + NSString *sql; + if (excludeInlineData) { + sql = [NSString stringWithFormat:@"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (%@);", [self _dbJoinedKeys:keys]]; + } else { + sql = [NSString stringWithFormat:@"select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key in (%@)", [self _dbJoinedKeys:keys]]; + } + + sqlite3_stmt *stmt = NULL; + int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL); + if (result != SQLITE_OK) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return nil; + } + + [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1]; + NSMutableArray *items = [NSMutableArray new]; + do { + result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + YYKVStorageItem *item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData]; + if (item) [items addObject:item]; + } else if (result == SQLITE_DONE) { + break; + } else { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + items = nil; + break; + } + } while (1); + sqlite3_finalize(stmt); + return items; +} + +- (NSData *)_dbGetValueWithKey:(NSString *)key { + NSString *sql = @"select inline_data from manifest where key = ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return nil; + sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL); + + int result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + const void *inline_data = sqlite3_column_blob(stmt, 0); + int inline_data_bytes = sqlite3_column_bytes(stmt, 0); + if (!inline_data || inline_data_bytes <= 0) return nil; + return [NSData dataWithBytes:inline_data length:inline_data_bytes]; + } else { + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + } + return nil; + } +} + +- (NSString *)_dbGetFilenameWithKey:(NSString *)key { + NSString *sql = @"select filename from manifest where key = ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return nil; + sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL); + int result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + char *filename = (char *)sqlite3_column_text(stmt, 0); + if (filename && *filename != 0) { + return [NSString stringWithUTF8String:filename]; + } + } else { + if (result != SQLITE_DONE) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + } + } + return nil; +} + +- (NSMutableArray *)_dbGetFilenameWithKeys:(NSArray *)keys { + if (![self _dbCheck]) return nil; + NSString *sql = [NSString stringWithFormat:@"select filename from manifest where key in (%@);", [self _dbJoinedKeys:keys]]; + sqlite3_stmt *stmt = NULL; + int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL); + if (result != SQLITE_OK) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return nil; + } + + [self _dbBindJoinedKeys:keys stmt:stmt fromIndex:1]; + NSMutableArray *filenames = [NSMutableArray new]; + do { + result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + char *filename = (char *)sqlite3_column_text(stmt, 0); + if (filename && *filename != 0) { + NSString *name = [NSString stringWithUTF8String:filename]; + if (name) [filenames addObject:name]; + } + } else if (result == SQLITE_DONE) { + break; + } else { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + filenames = nil; + break; + } + } while (1); + sqlite3_finalize(stmt); + return filenames; +} + +- (NSMutableArray *)_dbGetFilenamesWithSizeLargerThan:(int)size { + NSString *sql = @"select filename from manifest where size > ?1 and filename is not null;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return nil; + sqlite3_bind_int(stmt, 1, size); + + NSMutableArray *filenames = [NSMutableArray new]; + do { + int result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + char *filename = (char *)sqlite3_column_text(stmt, 0); + if (filename && *filename != 0) { + NSString *name = [NSString stringWithUTF8String:filename]; + if (name) [filenames addObject:name]; + } + } else if (result == SQLITE_DONE) { + break; + } else { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + filenames = nil; + break; + } + } while (1); + return filenames; +} + +- (NSMutableArray *)_dbGetFilenamesWithTimeEarlierThan:(int)time { + NSString *sql = @"select filename from manifest where last_access_time < ?1 and filename is not null;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return nil; + sqlite3_bind_int(stmt, 1, time); + + NSMutableArray *filenames = [NSMutableArray new]; + do { + int result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + char *filename = (char *)sqlite3_column_text(stmt, 0); + if (filename && *filename != 0) { + NSString *name = [NSString stringWithUTF8String:filename]; + if (name) [filenames addObject:name]; + } + } else if (result == SQLITE_DONE) { + break; + } else { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + filenames = nil; + break; + } + } while (1); + return filenames; +} + +- (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int)count { + NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return nil; + sqlite3_bind_int(stmt, 1, count); + + NSMutableArray *items = [NSMutableArray new]; + do { + int result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + char *key = (char *)sqlite3_column_text(stmt, 0); + char *filename = (char *)sqlite3_column_text(stmt, 1); + int size = sqlite3_column_int(stmt, 2); + NSString *keyStr = key ? [NSString stringWithUTF8String:key] : nil; + if (keyStr) { + YYKVStorageItem *item = [YYKVStorageItem new]; + item.key = key ? [NSString stringWithUTF8String:key] : nil; + item.filename = filename ? [NSString stringWithUTF8String:filename] : nil; + item.size = size; + [items addObject:item]; + } + } else if (result == SQLITE_DONE) { + break; + } else { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + items = nil; + break; + } + } while (1); + return items; +} + +- (int)_dbGetItemCountWithKey:(NSString *)key { + NSString *sql = @"select count(key) from manifest where key = ?1;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return -1; + sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL); + int result = sqlite3_step(stmt); + if (result != SQLITE_ROW) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return -1; + } + return sqlite3_column_int(stmt, 0); +} + +- (int)_dbGetTotalItemSize { + NSString *sql = @"select sum(size) from manifest;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return -1; + int result = sqlite3_step(stmt); + if (result != SQLITE_ROW) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return -1; + } + return sqlite3_column_int(stmt, 0); +} + +- (int)_dbGetTotalItemCount { + NSString *sql = @"select count(*) from manifest;"; + sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; + if (!stmt) return -1; + int result = sqlite3_step(stmt); + if (result != SQLITE_ROW) { + if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); + return -1; + } + return sqlite3_column_int(stmt, 0); +} + + +#pragma mark - file + +- (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data { + NSString *path = [_dataPath stringByAppendingPathComponent:filename]; + return [data writeToFile:path atomically:NO]; +} + +- (NSData *)_fileReadWithName:(NSString *)filename { + NSString *path = [_dataPath stringByAppendingPathComponent:filename]; + NSData *data = [NSData dataWithContentsOfFile:path]; + return data; +} + +- (BOOL)_fileDeleteWithName:(NSString *)filename { + NSString *path = [_dataPath stringByAppendingPathComponent:filename]; + return [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; +} + +- (BOOL)_fileMoveAllToTrash { + CFUUIDRef uuidRef = CFUUIDCreate(NULL); + CFStringRef uuid = CFUUIDCreateString(NULL, uuidRef); + CFRelease(uuidRef); + NSString *tmpPath = [_trashPath stringByAppendingPathComponent:(__bridge NSString *)(uuid)]; + BOOL suc = [[NSFileManager defaultManager] moveItemAtPath:_dataPath toPath:tmpPath error:nil]; + if (suc) { + suc = [[NSFileManager defaultManager] createDirectoryAtPath:_dataPath withIntermediateDirectories:YES attributes:nil error:NULL]; + } + CFRelease(uuid); + return suc; +} + +- (void)_fileEmptyTrashInBackground { + NSString *trashPath = _trashPath; + dispatch_queue_t queue = _trashQueue; + dispatch_async(queue, ^{ + NSFileManager *manager = [NSFileManager new]; + NSArray *directoryContents = [manager contentsOfDirectoryAtPath:trashPath error:NULL]; + for (NSString *path in directoryContents) { + NSString *fullPath = [trashPath stringByAppendingPathComponent:path]; + [manager removeItemAtPath:fullPath error:NULL]; + } + }); +} + + +#pragma mark - private + +/** + Delete all files and empty in background. + Make sure the db is closed. + */ +- (void)_reset { + [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBFileName] error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBShmFileName] error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:[_path stringByAppendingPathComponent:kDBWalFileName] error:nil]; + [self _fileMoveAllToTrash]; + [self _fileEmptyTrashInBackground]; +} + +#pragma mark - public + +- (instancetype)init { + @throw [NSException exceptionWithName:@"YYKVStorage init error" reason:@"Please use the designated initializer and pass the 'path' and 'type'." userInfo:nil]; + return [self initWithPath:@"" type:YYKVStorageTypeFile]; +} + +- (instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type { + if (path.length == 0 || path.length > kPathLengthMax) { + NSLog(@"YYKVStorage init error: invalid path: [%@].", path); + return nil; + } + if (type > YYKVStorageTypeMixed) { + NSLog(@"YYKVStorage init error: invalid type: %lu.", (unsigned long)type); + return nil; + } + + self = [super init]; + _path = path.copy; + _type = type; + _dataPath = [path stringByAppendingPathComponent:kDataDirectoryName]; + _trashPath = [path stringByAppendingPathComponent:kTrashDirectoryName]; + _trashQueue = dispatch_queue_create("com.ibireme.cache.disk.trash", DISPATCH_QUEUE_SERIAL); + _dbPath = [path stringByAppendingPathComponent:kDBFileName]; + _errorLogsEnabled = YES; + NSError *error = nil; + if (![[NSFileManager defaultManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:&error] || + ![[NSFileManager defaultManager] createDirectoryAtPath:[path stringByAppendingPathComponent:kDataDirectoryName] + withIntermediateDirectories:YES + attributes:nil + error:&error] || + ![[NSFileManager defaultManager] createDirectoryAtPath:[path stringByAppendingPathComponent:kTrashDirectoryName] + withIntermediateDirectories:YES + attributes:nil + error:&error]) { + NSLog(@"YYKVStorage init error:%@", error); + return nil; + } + + if (![self _dbOpen] || ![self _dbInitialize]) { + // db file may broken... + [self _dbClose]; + [self _reset]; // rebuild + if (![self _dbOpen] || ![self _dbInitialize]) { + [self _dbClose]; + NSLog(@"YYKVStorage init error: fail to open sqlite db."); + return nil; + } + } + [self _fileEmptyTrashInBackground]; // empty the trash if failed at last time + return self; +} + +- (void)dealloc { + UIBackgroundTaskIdentifier taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{}]; + [self _dbClose]; + if (taskID != UIBackgroundTaskInvalid) { + [_YYSharedApplication() endBackgroundTask:taskID]; + } +} + +- (BOOL)saveItem:(YYKVStorageItem *)item { + return [self saveItemWithKey:item.key value:item.value filename:item.filename extendedData:item.extendedData]; +} + +- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value { + return [self saveItemWithKey:key value:value filename:nil extendedData:nil]; +} + +- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData { + if (key.length == 0 || value.length == 0) return NO; + if (_type == YYKVStorageTypeFile && filename.length == 0) { + return NO; + } + + if (filename.length) { + if (![self _fileWriteWithName:filename data:value]) { + return NO; + } + if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) { + [self _fileDeleteWithName:filename]; + return NO; + } + return YES; + } else { + if (_type != YYKVStorageTypeSQLite) { + NSString *filename = [self _dbGetFilenameWithKey:key]; + if (filename) { + [self _fileDeleteWithName:filename]; + } + } + return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData]; + } +} + +- (BOOL)removeItemForKey:(NSString *)key { + if (key.length == 0) return NO; + switch (_type) { + case YYKVStorageTypeSQLite: { + return [self _dbDeleteItemWithKey:key]; + } break; + case YYKVStorageTypeFile: + case YYKVStorageTypeMixed: { + NSString *filename = [self _dbGetFilenameWithKey:key]; + if (filename) { + [self _fileDeleteWithName:filename]; + } + return [self _dbDeleteItemWithKey:key]; + } break; + default: return NO; + } +} + +- (BOOL)removeItemForKeys:(NSArray *)keys { + if (keys.count == 0) return NO; + switch (_type) { + case YYKVStorageTypeSQLite: { + return [self _dbDeleteItemWithKeys:keys]; + } break; + case YYKVStorageTypeFile: + case YYKVStorageTypeMixed: { + NSArray *filenames = [self _dbGetFilenameWithKeys:keys]; + for (NSString *filename in filenames) { + [self _fileDeleteWithName:filename]; + } + return [self _dbDeleteItemWithKeys:keys]; + } break; + default: return NO; + } +} + +- (BOOL)removeItemsLargerThanSize:(int)size { + if (size == INT_MAX) return YES; + if (size <= 0) return [self removeAllItems]; + + switch (_type) { + case YYKVStorageTypeSQLite: { + if ([self _dbDeleteItemsWithSizeLargerThan:size]) { + [self _dbCheckpoint]; + return YES; + } + } break; + case YYKVStorageTypeFile: + case YYKVStorageTypeMixed: { + NSArray *filenames = [self _dbGetFilenamesWithSizeLargerThan:size]; + for (NSString *name in filenames) { + [self _fileDeleteWithName:name]; + } + if ([self _dbDeleteItemsWithSizeLargerThan:size]) { + [self _dbCheckpoint]; + return YES; + } + } break; + } + return NO; +} + +- (BOOL)removeItemsEarlierThanTime:(int)time { + if (time <= 0) return YES; + if (time == INT_MAX) return [self removeAllItems]; + + switch (_type) { + case YYKVStorageTypeSQLite: { + if ([self _dbDeleteItemsWithTimeEarlierThan:time]) { + [self _dbCheckpoint]; + return YES; + } + } break; + case YYKVStorageTypeFile: + case YYKVStorageTypeMixed: { + NSArray *filenames = [self _dbGetFilenamesWithTimeEarlierThan:time]; + for (NSString *name in filenames) { + [self _fileDeleteWithName:name]; + } + if ([self _dbDeleteItemsWithTimeEarlierThan:time]) { + [self _dbCheckpoint]; + return YES; + } + } break; + } + return NO; +} + +- (BOOL)removeItemsToFitSize:(int)maxSize { + if (maxSize == INT_MAX) return YES; + if (maxSize <= 0) return [self removeAllItems]; + + int total = [self _dbGetTotalItemSize]; + if (total < 0) return NO; + if (total <= maxSize) return YES; + + NSArray *items = nil; + BOOL suc = NO; + do { + int perCount = 16; + items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; + for (YYKVStorageItem *item in items) { + if (total > maxSize) { + if (item.filename) { + [self _fileDeleteWithName:item.filename]; + } + suc = [self _dbDeleteItemWithKey:item.key]; + total -= item.size; + } else { + break; + } + if (!suc) break; + } + } while (total > maxSize && items.count > 0 && suc); + if (suc) [self _dbCheckpoint]; + return suc; +} + +- (BOOL)removeItemsToFitCount:(int)maxCount { + if (maxCount == INT_MAX) return YES; + if (maxCount <= 0) return [self removeAllItems]; + + int total = [self _dbGetTotalItemCount]; + if (total < 0) return NO; + if (total <= maxCount) return YES; + + NSArray *items = nil; + BOOL suc = NO; + do { + int perCount = 16; + items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; + for (YYKVStorageItem *item in items) { + if (total > maxCount) { + if (item.filename) { + [self _fileDeleteWithName:item.filename]; + } + suc = [self _dbDeleteItemWithKey:item.key]; + total--; + } else { + break; + } + if (!suc) break; + } + } while (total > maxCount && items.count > 0 && suc); + if (suc) [self _dbCheckpoint]; + return suc; +} + +- (BOOL)removeAllItems { + if (![self _dbClose]) return NO; + [self _reset]; + if (![self _dbOpen]) return NO; + if (![self _dbInitialize]) return NO; + return YES; +} + +- (void)removeAllItemsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress + endBlock:(void(^)(BOOL error))end { + + int total = [self _dbGetTotalItemCount]; + if (total <= 0) { + if (end) end(total < 0); + } else { + int left = total; + int perCount = 32; + NSArray *items = nil; + BOOL suc = NO; + do { + items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; + for (YYKVStorageItem *item in items) { + if (left > 0) { + if (item.filename) { + [self _fileDeleteWithName:item.filename]; + } + suc = [self _dbDeleteItemWithKey:item.key]; + left--; + } else { + break; + } + if (!suc) break; + } + if (progress) progress(total - left, total); + } while (left > 0 && items.count > 0 && suc); + if (suc) [self _dbCheckpoint]; + if (end) end(!suc); + } +} + +- (YYKVStorageItem *)getItemForKey:(NSString *)key { + if (key.length == 0) return nil; + YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO]; + if (item) { + [self _dbUpdateAccessTimeWithKey:key]; + if (item.filename) { + item.value = [self _fileReadWithName:item.filename]; + if (!item.value) { + [self _dbDeleteItemWithKey:key]; + item = nil; + } + } + } + return item; +} + +- (YYKVStorageItem *)getItemInfoForKey:(NSString *)key { + if (key.length == 0) return nil; + YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:YES]; + return item; +} + +- (NSData *)getItemValueForKey:(NSString *)key { + if (key.length == 0) return nil; + NSData *value = nil; + switch (_type) { + case YYKVStorageTypeFile: { + NSString *filename = [self _dbGetFilenameWithKey:key]; + if (filename) { + value = [self _fileReadWithName:filename]; + if (!value) { + [self _dbDeleteItemWithKey:key]; + value = nil; + } + } + } break; + case YYKVStorageTypeSQLite: { + value = [self _dbGetValueWithKey:key]; + } break; + case YYKVStorageTypeMixed: { + NSString *filename = [self _dbGetFilenameWithKey:key]; + if (filename) { + value = [self _fileReadWithName:filename]; + if (!value) { + [self _dbDeleteItemWithKey:key]; + value = nil; + } + } else { + value = [self _dbGetValueWithKey:key]; + } + } break; + } + if (value) { + [self _dbUpdateAccessTimeWithKey:key]; + } + return value; +} + +- (NSArray *)getItemForKeys:(NSArray *)keys { + if (keys.count == 0) return nil; + NSMutableArray *items = [self _dbGetItemWithKeys:keys excludeInlineData:NO]; + if (_type != YYKVStorageTypeSQLite) { + for (NSInteger i = 0, max = items.count; i < max; i++) { + YYKVStorageItem *item = items[i]; + if (item.filename) { + item.value = [self _fileReadWithName:item.filename]; + if (!item.value) { + if (item.key) [self _dbDeleteItemWithKey:item.key]; + [items removeObjectAtIndex:i]; + i--; + max--; + } + } + } + } + if (items.count > 0) { + [self _dbUpdateAccessTimeWithKeys:keys]; + } + return items.count ? items : nil; +} + +- (NSArray *)getItemInfoForKeys:(NSArray *)keys { + if (keys.count == 0) return nil; + return [self _dbGetItemWithKeys:keys excludeInlineData:YES]; +} + +- (NSDictionary *)getItemValueForKeys:(NSArray *)keys { + NSMutableArray *items = (NSMutableArray *)[self getItemForKeys:keys]; + NSMutableDictionary *kv = [NSMutableDictionary new]; + for (YYKVStorageItem *item in items) { + if (item.key && item.value) { + [kv setObject:item.value forKey:item.key]; + } + } + return kv.count ? kv : nil; +} + +- (BOOL)itemExistsForKey:(NSString *)key { + if (key.length == 0) return NO; + return [self _dbGetItemCountWithKey:key] > 0; +} + +- (int)getItemsCount { + return [self _dbGetTotalItemCount]; +} + +- (int)getItemsSize { + return [self _dbGetTotalItemSize]; +} + +@end diff --git a/Example/Pods/YYCache/YYCache/YYMemoryCache.h b/Example/Pods/YYCache/YYCache/YYMemoryCache.h new file mode 100644 index 00000000..ded84402 --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYMemoryCache.h @@ -0,0 +1,213 @@ +// +// YYMemoryCache.h +// YYCache +// +// Created by ibireme on 15/2/7. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + YYMemoryCache is a fast in-memory cache that stores key-value pairs. + In contrast to NSDictionary, keys are retained and not copied. + The API and performance is similar to `NSCache`, all methods are thread-safe. + + YYMemoryCache objects differ from NSCache in a few ways: + + * It uses LRU (least-recently-used) to remove objects; NSCache's eviction method + is non-deterministic. + * It can be controlled by cost, count and age; NSCache's limits are imprecise. + * It can be configured to automatically evict objects when receive memory + warning or app enter background. + + The time of `Access Methods` in YYMemoryCache is typically in constant time (O(1)). + */ +@interface YYMemoryCache : NSObject + +#pragma mark - Attribute +///============================================================================= +/// @name Attribute +///============================================================================= + +/** The name of the cache. Default is nil. */ +@property (nullable, copy) NSString *name; + +/** The number of objects in the cache (read-only) */ +@property (readonly) NSUInteger totalCount; + +/** The total cost of objects in the cache (read-only). */ +@property (readonly) NSUInteger totalCost; + + +#pragma mark - Limit +///============================================================================= +/// @name Limit +///============================================================================= + +/** + The maximum number of objects the cache should hold. + + @discussion The default value is NSUIntegerMax, which means no limit. + This is not a strict limit—if the cache goes over the limit, some objects in the + cache could be evicted later in backgound thread. + */ +@property NSUInteger countLimit; + +/** + The maximum total cost that the cache can hold before it starts evicting objects. + + @discussion The default value is NSUIntegerMax, which means no limit. + This is not a strict limit—if the cache goes over the limit, some objects in the + cache could be evicted later in backgound thread. + */ +@property NSUInteger costLimit; + +/** + The maximum expiry time of objects in cache. + + @discussion The default value is DBL_MAX, which means no limit. + This is not a strict limit—if an object goes over the limit, the object could + be evicted later in backgound thread. + */ +@property NSTimeInterval ageLimit; + +/** + The auto trim check time interval in seconds. Default is 5.0. + + @discussion The cache holds an internal timer to check whether the cache reaches + its limits, and if the limit is reached, it begins to evict objects. + */ +@property NSTimeInterval autoTrimInterval; + +/** + If `YES`, the cache will remove all objects when the app receives a memory warning. + The default value is `YES`. + */ +@property BOOL shouldRemoveAllObjectsOnMemoryWarning; + +/** + If `YES`, The cache will remove all objects when the app enter background. + The default value is `YES`. + */ +@property BOOL shouldRemoveAllObjectsWhenEnteringBackground; + +/** + A block to be executed when the app receives a memory warning. + The default value is nil. + */ +@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache); + +/** + A block to be executed when the app enter background. + The default value is nil. + */ +@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache); + +/** + If `YES`, the key-value pair will be released on main thread, otherwise on + background thread. Default is NO. + + @discussion You may set this value to `YES` if the key-value object contains + the instance which should be released in main thread (such as UIView/CALayer). + */ +@property BOOL releaseOnMainThread; + +/** + If `YES`, the key-value pair will be released asynchronously to avoid blocking + the access methods, otherwise it will be released in the access method + (such as removeObjectForKey:). Default is YES. + */ +@property BOOL releaseAsynchronously; + + +#pragma mark - Access Methods +///============================================================================= +/// @name Access Methods +///============================================================================= + +/** + Returns a Boolean value that indicates whether a given key is in cache. + + @param key An object identifying the value. If nil, just return `NO`. + @return Whether the key is in cache. + */ +- (BOOL)containsObjectForKey:(id)key; + +/** + Returns the value associated with a given key. + + @param key An object identifying the value. If nil, just return nil. + @return The value associated with key, or nil if no value is associated with key. + */ +- (nullable id)objectForKey:(id)key; + +/** + Sets the value of the specified key in the cache (0 cost). + + @param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`. + @param key The key with which to associate the value. If nil, this method has no effect. + @discussion Unlike an NSMutableDictionary object, a cache does not copy the key + objects that are put into it. + */ +- (void)setObject:(nullable id)object forKey:(id)key; + +/** + Sets the value of the specified key in the cache, and associates the key-value + pair with the specified cost. + + @param object The object to store in the cache. If nil, it calls `removeObjectForKey`. + @param key The key with which to associate the value. If nil, this method has no effect. + @param cost The cost with which to associate the key-value pair. + @discussion Unlike an NSMutableDictionary object, a cache does not copy the key + objects that are put into it. + */ +- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost; + +/** + Removes the value of the specified key in the cache. + + @param key The key identifying the value to be removed. If nil, this method has no effect. + */ +- (void)removeObjectForKey:(id)key; + +/** + Empties the cache immediately. + */ +- (void)removeAllObjects; + + +#pragma mark - Trim +///============================================================================= +/// @name Trim +///============================================================================= + +/** + Removes objects from the cache with LRU, until the `totalCount` is below or equal to + the specified value. + @param count The total count allowed to remain after the cache has been trimmed. + */ +- (void)trimToCount:(NSUInteger)count; + +/** + Removes objects from the cache with LRU, until the `totalCost` is or equal to + the specified value. + @param cost The total cost allowed to remain after the cache has been trimmed. + */ +- (void)trimToCost:(NSUInteger)cost; + +/** + Removes objects from the cache with LRU, until all expiry objects removed by the + specified value. + @param age The maximum age (in seconds) of objects. + */ +- (void)trimToAge:(NSTimeInterval)age; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYCache/YYCache/YYMemoryCache.m b/Example/Pods/YYCache/YYCache/YYMemoryCache.m new file mode 100644 index 00000000..9042d9b1 --- /dev/null +++ b/Example/Pods/YYCache/YYCache/YYMemoryCache.m @@ -0,0 +1,505 @@ +// +// YYMemoryCache.m +// YYCache +// +// Created by ibireme on 15/2/7. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYMemoryCache.h" +#import +#import +#import +#import + + +static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() { + return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); +} + +/** + A node in linked map. + Typically, you should not use this class directly. + */ +@interface _YYLinkedMapNode : NSObject { + @package + __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic + __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic + id _key; + id _value; + NSUInteger _cost; + NSTimeInterval _time; +} +@end + +@implementation _YYLinkedMapNode +@end + + +/** + A linked map used by YYMemoryCache. + It's not thread-safe and does not validate the parameters. + + Typically, you should not use this class directly. + */ +@interface _YYLinkedMap : NSObject { + @package + CFMutableDictionaryRef _dic; // do not set object directly + NSUInteger _totalCost; + NSUInteger _totalCount; + _YYLinkedMapNode *_head; // MRU, do not change it directly + _YYLinkedMapNode *_tail; // LRU, do not change it directly + BOOL _releaseOnMainThread; + BOOL _releaseAsynchronously; +} + +/// Insert a node at head and update the total cost. +/// Node and node.key should not be nil. +- (void)insertNodeAtHead:(_YYLinkedMapNode *)node; + +/// Bring a inner node to header. +/// Node should already inside the dic. +- (void)bringNodeToHead:(_YYLinkedMapNode *)node; + +/// Remove a inner node and update the total cost. +/// Node should already inside the dic. +- (void)removeNode:(_YYLinkedMapNode *)node; + +/// Remove tail node if exist. +- (_YYLinkedMapNode *)removeTailNode; + +/// Remove all node in background queue. +- (void)removeAll; + +@end + +@implementation _YYLinkedMap + +- (instancetype)init { + self = [super init]; + _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + _releaseOnMainThread = NO; + _releaseAsynchronously = YES; + return self; +} + +- (void)dealloc { + CFRelease(_dic); +} + +- (void)insertNodeAtHead:(_YYLinkedMapNode *)node { + CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node)); + _totalCost += node->_cost; + _totalCount++; + if (_head) { + node->_next = _head; + _head->_prev = node; + _head = node; + } else { + _head = _tail = node; + } +} + +- (void)bringNodeToHead:(_YYLinkedMapNode *)node { + if (_head == node) return; + + if (_tail == node) { + _tail = node->_prev; + _tail->_next = nil; + } else { + node->_next->_prev = node->_prev; + node->_prev->_next = node->_next; + } + node->_next = _head; + node->_prev = nil; + _head->_prev = node; + _head = node; +} + +- (void)removeNode:(_YYLinkedMapNode *)node { + CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key)); + _totalCost -= node->_cost; + _totalCount--; + if (node->_next) node->_next->_prev = node->_prev; + if (node->_prev) node->_prev->_next = node->_next; + if (_head == node) _head = node->_next; + if (_tail == node) _tail = node->_prev; +} + +- (_YYLinkedMapNode *)removeTailNode { + if (!_tail) return nil; + _YYLinkedMapNode *tail = _tail; + CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key)); + _totalCost -= _tail->_cost; + _totalCount--; + if (_head == _tail) { + _head = _tail = nil; + } else { + _tail = _tail->_prev; + _tail->_next = nil; + } + return tail; +} + +- (void)removeAll { + _totalCost = 0; + _totalCount = 0; + _head = nil; + _tail = nil; + if (CFDictionaryGetCount(_dic) > 0) { + CFMutableDictionaryRef holder = _dic; + _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + if (_releaseAsynchronously) { + dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); + dispatch_async(queue, ^{ + CFRelease(holder); // hold and release in specified queue + }); + } else if (_releaseOnMainThread && !pthread_main_np()) { + dispatch_async(dispatch_get_main_queue(), ^{ + CFRelease(holder); // hold and release in specified queue + }); + } else { + CFRelease(holder); + } + } +} + +@end + + + +@implementation YYMemoryCache { + pthread_mutex_t _lock; + _YYLinkedMap *_lru; + dispatch_queue_t _queue; +} + +- (void)_trimRecursively { + __weak typeof(self) _self = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ + __strong typeof(_self) self = _self; + if (!self) return; + [self _trimInBackground]; + [self _trimRecursively]; + }); +} + +- (void)_trimInBackground { + dispatch_async(_queue, ^{ + [self _trimToCost:self->_costLimit]; + [self _trimToCount:self->_countLimit]; + [self _trimToAge:self->_ageLimit]; + }); +} + +- (void)_trimToCost:(NSUInteger)costLimit { + BOOL finish = NO; + pthread_mutex_lock(&_lock); + if (costLimit == 0) { + [_lru removeAll]; + finish = YES; + } else if (_lru->_totalCost <= costLimit) { + finish = YES; + } + pthread_mutex_unlock(&_lock); + if (finish) return; + + NSMutableArray *holder = [NSMutableArray new]; + while (!finish) { + if (pthread_mutex_trylock(&_lock) == 0) { + if (_lru->_totalCost > costLimit) { + _YYLinkedMapNode *node = [_lru removeTailNode]; + if (node) [holder addObject:node]; + } else { + finish = YES; + } + pthread_mutex_unlock(&_lock); + } else { + usleep(10 * 1000); //10 ms + } + } + if (holder.count) { + dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); + dispatch_async(queue, ^{ + [holder count]; // release in queue + }); + } +} + +- (void)_trimToCount:(NSUInteger)countLimit { + BOOL finish = NO; + pthread_mutex_lock(&_lock); + if (countLimit == 0) { + [_lru removeAll]; + finish = YES; + } else if (_lru->_totalCount <= countLimit) { + finish = YES; + } + pthread_mutex_unlock(&_lock); + if (finish) return; + + NSMutableArray *holder = [NSMutableArray new]; + while (!finish) { + if (pthread_mutex_trylock(&_lock) == 0) { + if (_lru->_totalCount > countLimit) { + _YYLinkedMapNode *node = [_lru removeTailNode]; + if (node) [holder addObject:node]; + } else { + finish = YES; + } + pthread_mutex_unlock(&_lock); + } else { + usleep(10 * 1000); //10 ms + } + } + if (holder.count) { + dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); + dispatch_async(queue, ^{ + [holder count]; // release in queue + }); + } +} + +- (void)_trimToAge:(NSTimeInterval)ageLimit { + BOOL finish = NO; + NSTimeInterval now = CACurrentMediaTime(); + pthread_mutex_lock(&_lock); + if (ageLimit <= 0) { + [_lru removeAll]; + finish = YES; + } else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) { + finish = YES; + } + pthread_mutex_unlock(&_lock); + if (finish) return; + + NSMutableArray *holder = [NSMutableArray new]; + while (!finish) { + if (pthread_mutex_trylock(&_lock) == 0) { + if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) { + _YYLinkedMapNode *node = [_lru removeTailNode]; + if (node) [holder addObject:node]; + } else { + finish = YES; + } + pthread_mutex_unlock(&_lock); + } else { + usleep(10 * 1000); //10 ms + } + } + if (holder.count) { + dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); + dispatch_async(queue, ^{ + [holder count]; // release in queue + }); + } +} + +- (void)_appDidReceiveMemoryWarningNotification { + if (self.didReceiveMemoryWarningBlock) { + self.didReceiveMemoryWarningBlock(self); + } + if (self.shouldRemoveAllObjectsOnMemoryWarning) { + [self removeAllObjects]; + } +} + +- (void)_appDidEnterBackgroundNotification { + if (self.didEnterBackgroundBlock) { + self.didEnterBackgroundBlock(self); + } + if (self.shouldRemoveAllObjectsWhenEnteringBackground) { + [self removeAllObjects]; + } +} + +#pragma mark - public + +- (instancetype)init { + self = super.init; + pthread_mutex_init(&_lock, NULL); + _lru = [_YYLinkedMap new]; + _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL); + + _countLimit = NSUIntegerMax; + _costLimit = NSUIntegerMax; + _ageLimit = DBL_MAX; + _autoTrimInterval = 5.0; + _shouldRemoveAllObjectsOnMemoryWarning = YES; + _shouldRemoveAllObjectsWhenEnteringBackground = YES; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil]; + + [self _trimRecursively]; + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [_lru removeAll]; + pthread_mutex_destroy(&_lock); +} + +- (NSUInteger)totalCount { + pthread_mutex_lock(&_lock); + NSUInteger count = _lru->_totalCount; + pthread_mutex_unlock(&_lock); + return count; +} + +- (NSUInteger)totalCost { + pthread_mutex_lock(&_lock); + NSUInteger totalCost = _lru->_totalCost; + pthread_mutex_unlock(&_lock); + return totalCost; +} + +- (BOOL)releaseOnMainThread { + pthread_mutex_lock(&_lock); + BOOL releaseOnMainThread = _lru->_releaseOnMainThread; + pthread_mutex_unlock(&_lock); + return releaseOnMainThread; +} + +- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread { + pthread_mutex_lock(&_lock); + _lru->_releaseOnMainThread = releaseOnMainThread; + pthread_mutex_unlock(&_lock); +} + +- (BOOL)releaseAsynchronously { + pthread_mutex_lock(&_lock); + BOOL releaseAsynchronously = _lru->_releaseAsynchronously; + pthread_mutex_unlock(&_lock); + return releaseAsynchronously; +} + +- (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously { + pthread_mutex_lock(&_lock); + _lru->_releaseAsynchronously = releaseAsynchronously; + pthread_mutex_unlock(&_lock); +} + +- (BOOL)containsObjectForKey:(id)key { + if (!key) return NO; + pthread_mutex_lock(&_lock); + BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key)); + pthread_mutex_unlock(&_lock); + return contains; +} + +- (id)objectForKey:(id)key { + if (!key) return nil; + pthread_mutex_lock(&_lock); + _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key)); + if (node) { + node->_time = CACurrentMediaTime(); + [_lru bringNodeToHead:node]; + } + pthread_mutex_unlock(&_lock); + return node ? node->_value : nil; +} + +- (void)setObject:(id)object forKey:(id)key { + [self setObject:object forKey:key withCost:0]; +} + +- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost { + if (!key) return; + if (!object) { + [self removeObjectForKey:key]; + return; + } + pthread_mutex_lock(&_lock); + _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key)); + NSTimeInterval now = CACurrentMediaTime(); + if (node) { + _lru->_totalCost -= node->_cost; + _lru->_totalCost += cost; + node->_cost = cost; + node->_time = now; + node->_value = object; + [_lru bringNodeToHead:node]; + } else { + node = [_YYLinkedMapNode new]; + node->_cost = cost; + node->_time = now; + node->_key = key; + node->_value = object; + [_lru insertNodeAtHead:node]; + } + if (_lru->_totalCost > _costLimit) { + dispatch_async(_queue, ^{ + [self trimToCost:_costLimit]; + }); + } + if (_lru->_totalCount > _countLimit) { + _YYLinkedMapNode *node = [_lru removeTailNode]; + if (_lru->_releaseAsynchronously) { + dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); + dispatch_async(queue, ^{ + [node class]; //hold and release in queue + }); + } else if (_lru->_releaseOnMainThread && !pthread_main_np()) { + dispatch_async(dispatch_get_main_queue(), ^{ + [node class]; //hold and release in queue + }); + } + } + pthread_mutex_unlock(&_lock); +} + +- (void)removeObjectForKey:(id)key { + if (!key) return; + pthread_mutex_lock(&_lock); + _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key)); + if (node) { + [_lru removeNode:node]; + if (_lru->_releaseAsynchronously) { + dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); + dispatch_async(queue, ^{ + [node class]; //hold and release in queue + }); + } else if (_lru->_releaseOnMainThread && !pthread_main_np()) { + dispatch_async(dispatch_get_main_queue(), ^{ + [node class]; //hold and release in queue + }); + } + } + pthread_mutex_unlock(&_lock); +} + +- (void)removeAllObjects { + pthread_mutex_lock(&_lock); + [_lru removeAll]; + pthread_mutex_unlock(&_lock); +} + +- (void)trimToCount:(NSUInteger)count { + if (count == 0) { + [self removeAllObjects]; + return; + } + [self _trimToCount:count]; +} + +- (void)trimToCost:(NSUInteger)cost { + [self _trimToCost:cost]; +} + +- (void)trimToAge:(NSTimeInterval)age { + [self _trimToAge:age]; +} + +- (NSString *)description { + if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name]; + else return [NSString stringWithFormat:@"<%@: %p>", self.class, self]; +} + +@end diff --git a/Example/Pods/YYImage/LICENSE b/Example/Pods/YYImage/LICENSE new file mode 100644 index 00000000..46be20bd --- /dev/null +++ b/Example/Pods/YYImage/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 ibireme + +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/Example/Pods/YYImage/README.md b/Example/Pods/YYImage/README.md new file mode 100755 index 00000000..7051d7f8 --- /dev/null +++ b/Example/Pods/YYImage/README.md @@ -0,0 +1,384 @@ +YYImage +============== +[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/ibireme/YYImage/master/LICENSE)  +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)  +[![CocoaPods](http://img.shields.io/cocoapods/v/YYImage.svg?style=flat)](http://cocoapods.org/?q= YYImage)  +[![CocoaPods](http://img.shields.io/cocoapods/p/YYImage.svg?style=flat)](http://cocoapods.org/?q= YYImage)  +[![Support](https://img.shields.io/badge/support-iOS%206%2B%20-blue.svg?style=flat)](https://www.apple.com/nl/ios/)  +[![Build Status](https://travis-ci.org/ibireme/YYImage.svg?branch=master)](https://travis-ci.org/ibireme/YYImage) + +Image framework for iOS to display/encode/decode animated WebP, APNG, GIF, and more.
+(It's a component of [YYKit](https://github.com/ibireme/YYKit)) + +![niconiconi~](https://raw.github.com/ibireme/YYImage/master/Demo/YYImageDemo/niconiconi@2x.gif +) + +Features +============== +- Display/encode/decode animated image with these types:
    WebP, APNG, GIF. +- Display/encode/decode still image with these types:
    WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS. +- Baseline/progressive/interlaced image decode with these types:
    PNG, GIF, JPEG, BMP. +- Display frame based image animation and sprite sheet animation. +- Dynamic memory buffer for lower memory usage. +- Fully compatible with UIImage and UIImageView class. +- Extendable protocol for custom image animation. +- Fully documented. + +Usage +============== + +###Display animated image + + // File: ani@3x.gif + UIImage *image = [YYImage imageNamed:@"ani.gif"]; + UIImageView *imageView = [[YYAnimatedImageView alloc] initWithImage:image]; + [self.view addSubView:imageView]; + + +###Display frame animation + + // Files: frame1.png, frame2.png, frame3.png + NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"]; + NSArray *times = @[@0.1, @0.2, @0.1]; + UIImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES]; + UIImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image]; + [self.view addSubView:imageView]; + +###Display sprite sheet animation + + // 8 * 12 sprites in a single sheet image + UIImage *spriteSheet = [UIImage imageNamed:@"sprite-sheet"]; + NSMutableArray *contentRects = [NSMutableArray new]; + NSMutableArray *durations = [NSMutableArray new]; + for (int j = 0; j < 12; j++) { + for (int i = 0; i < 8; i++) { + CGRect rect; + rect.size = CGSizeMake(img.size.width / 8, img.size.height / 12); + rect.origin.x = img.size.width / 8 * i; + rect.origin.y = img.size.height / 12 * j; + [contentRects addObject:[NSValue valueWithCGRect:rect]]; + [durations addObject:@(1 / 60.0)]; + } + } + YYSpriteSheetImage *sprite; + sprite = [[YYSpriteSheetImage alloc] initWithSpriteSheetImage:img + contentRects:contentRects + frameDurations:durations + loopCount:0]; + YYAnimatedImageView *imageView = [YYAnimatedImageView new]; + imageView.size = CGSizeMake(img.size.width / 8, img.size.height / 12); + imageView.image = sprite; + [self.view addSubView:imageView]; + +###Animation control + + YYAnimatedImageView *imageView = ...; + // pause: + [imageView stopAnimating]; + // play: + [imageView startAnimating]; + // set frame index: + imageView.currentAnimatedImageIndex = 12; + // get current status + image.currentIsPlayingAnimation; + +###Image decoder + + // Decode single frame: + NSData *data = [NSData dataWithContentsOfFile:@"/tmp/image.webp"]; + YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0]; + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + + // Progressive: + NSMutableData *data = [NSMutableData new]; + YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0]; + while(newDataArrived) { + [data appendData:newData]; + [decoder updateData:data final:NO]; + if (decoder.frameCount > 0) { + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + // progressive display... + } + } + [decoder updateData:data final:YES]; + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + // final display... + +###Image encoder + + // Encode still image: + YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG]; + jpegEncoder.quality = 0.9; + [jpegEncoder addImage:image duration:0]; + NSData jpegData = [jpegEncoder encode]; + + // Encode animated image: + YYImageEncoder *webpEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeWebP]; + webpEncoder.loopCount = 5; + [webpEncoder addImage:image0 duration:0.1]; + [webpEncoder addImage:image1 duration:0.15]; + [webpEncoder addImage:image2 duration:0.2]; + NSData webpData = [webpEncoder encode]; + +###Image type detection + + // Get image type from image data + YYImageType type = YYImageDetectType(data); + if (type == YYImageTypePNG) ... + + +Installation +============== + +### CocoaPods + +1. Update cocoapods to the latest version. +2. Add `pod 'YYImage'` to your Podfile. +3. Run `pod install` or `pod update`. +4. Import \. +5. Notice: it doesn't include WebP subspec by default, if you want to support WebP format, you may add `pod 'YYImage/WebP'` to your Podfile. + +### Carthage + +1. Add `github "ibireme/YYImage"` to your Cartfile. +2. Run `carthage update --platform ios` and add the framework to your project. +3. Import \. +4. Notice: carthage framework doesn't include WebP component, if you want to support WebP format, use CocoaPods or install manually. + +### Manually + +1. Download all the files in the YYImage subdirectory. +2. Add the source files to your Xcode project. +3. Link with required frameworks: + * UIKit + * CoreFoundation + * QuartzCore + * AssetsLibrary + * ImageIO + * Accelerate + * MobileCoreServices + * libz +4. Import `YYImage.h`. +5. Notice: if you want to support WebP format, you may add `Vendor/WebP.framework`(static library) to your Xcode project. + +FAQ +============== +_Q: Why I can't display WebP image?_ + +A: Make sure you added the `WebP.framework` in your project. You may call `YYImageWebPAvailable()` to check whether the WebP subspec is installed correctly. + +_Q: Why I can't play APNG animation?_ + +A: You should disable the `Compress PNG Files` and `Remove Text Metadata From PNG Files` in your project's build settings. Or you can rename your APNG file's extension name with `apng`. + +Documentation +============== +Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYImage/).
+You can also install documentation locally using [appledoc](https://github.com/tomaz/appledoc). + + + +Requirements +============== +This library requires `iOS 6.0+` and `Xcode 7.0+`. + + +License +============== +YYImage is provided under the MIT license. See LICENSE file for details. + + +

+--- +中文介绍 +============== +YYImage: 功能强大的 iOS 图像框架。
+(该项目是 [YYKit](https://github.com/ibireme/YYKit) 组件之一) + +![niconiconi~](https://raw.github.com/ibireme/YYImage/master/Demo/YYImageDemo/niconiconi@2x.gif +) + +特性 +============== +- 支持以下类型动画图像的播放/编码/解码:
+     WebP, APNG, GIF。 +- 支持以下类型静态图像的显示/编码/解码:
+     WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS。 +- 支持以下类型图片的渐进式/逐行扫描/隔行扫描解码:
+     PNG, GIF, JPEG, BMP。 +- 支持多张图片构成的帧动画播放,支持单张图片的 sprite sheet 动画。 +- 高效的动态内存缓存管理,以保证高性能低内存的动画播放。 +- 完全兼容 UIImage 和 UIImageView,使用方便。 +- 保留可扩展的接口,以支持自定义动画。 +- 每个类和方法都有完善的文档注释。 + + +用法 +============== + +###显示动画类型的图片 + + // 文件: ani@3x.gif + UIImage *image = [YYImage imageNamed:@"ani.gif"]; + UIImageView *imageView = [[YYAnimatedImageView alloc] initWithImage:image]; + [self.view addSubView:imageView]; + + +###播放帧动画 + + // 文件: frame1.png, frame2.png, frame3.png + NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"]; + NSArray *times = @[@0.1, @0.2, @0.1]; + UIImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES]; + UIImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image]; + [self.view addSubView:imageView]; + +###播放 sprite sheet 动画 + + // 8 * 12 sprites in a single sheet image + UIImage *spriteSheet = [UIImage imageNamed:@"sprite-sheet"]; + NSMutableArray *contentRects = [NSMutableArray new]; + NSMutableArray *durations = [NSMutableArray new]; + for (int j = 0; j < 12; j++) { + for (int i = 0; i < 8; i++) { + CGRect rect; + rect.size = CGSizeMake(img.size.width / 8, img.size.height / 12); + rect.origin.x = img.size.width / 8 * i; + rect.origin.y = img.size.height / 12 * j; + [contentRects addObject:[NSValue valueWithCGRect:rect]]; + [durations addObject:@(1 / 60.0)]; + } + } + YYSpriteSheetImage *sprite; + sprite = [[YYSpriteSheetImage alloc] initWithSpriteSheetImage:img + contentRects:contentRects + frameDurations:durations + loopCount:0]; + YYAnimatedImageView *imageView = [YYAnimatedImageView new]; + imageView.size = CGSizeMake(img.size.width / 8, img.size.height / 12); + imageView.image = sprite; + [self.view addSubView:imageView]; + +###动画播放控制 + + YYAnimatedImageView *imageView = ...; + // 暂停: + [imageView stopAnimating]; + // 播放: + [imageView startAnimating]; + // 设置播放进度: + imageView.currentAnimatedImageIndex = 12; + // 获取播放状态: + image.currentIsPlayingAnimation; + //上面两个属性都支持 KVO。 + +###图片解码 + + // 解码单帧图片: + NSData *data = [NSData dataWithContentsOfFile:@"/tmp/image.webp"]; + YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0]; + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + + // 渐进式图片解码 (可用于图片下载显示): + NSMutableData *data = [NSMutableData new]; + YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0]; + while(newDataArrived) { + [data appendData:newData]; + [decoder updateData:data final:NO]; + if (decoder.frameCount > 0) { + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + // progressive display... + } + } + [decoder updateData:data final:YES]; + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + // final display... + +###图片编码 + + // 编码静态图 (支持各种常见图片格式): + YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG]; + jpegEncoder.quality = 0.9; + [jpegEncoder addImage:image duration:0]; + NSData jpegData = [jpegEncoder encode]; + + // 编码动态图 (支持 GIF/APNG/WebP): + YYImageEncoder *webpEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeWebP]; + webpEncoder.loopCount = 5; + [webpEncoder addImage:image0 duration:0.1]; + [webpEncoder addImage:image1 duration:0.15]; + [webpEncoder addImage:image2 duration:0.2]; + NSData webpData = [webpEncoder encode]; + +###图片类型探测 + + // 获取图片类型 + YYImageType type = YYImageDetectType(data); + if (type == YYImageTypePNG) ... + + +安装 +============== + +### CocoaPods + +1. 将 cocoapods 更新至最新版本. +2. 在 Podfile 中添加 `pod 'YYImage'`。 +3. 执行 `pod install` 或 `pod update`。 +4. 导入 \。 +5. 注意:pod 配置并没有包含 WebP 组件, 如果你需要支持 WebP,可以在 Podfile 中添加 `pod 'YYImage/WebP'`。 + +### Carthage + +1. 在 Cartfile 中添加 `github "ibireme/YYImage"`。 +2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。 +3. 导入 \。 +4. 注意:carthage framework 并没有包含 WebP 组件。如果你需要支持 WebP,可以用 CocoaPods 安装,或者手动安装。 + +### 手动安装 + +1. 下载 YYImage 文件夹内的所有内容。 +2. 将 YYImage 内的源文件添加(拖放)到你的工程。 +3. 链接以下 frameworks: + * UIKit + * CoreFoundation + * QuartzCore + * AssetsLibrary + * ImageIO + * Accelerate + * MobileCoreServices + * libz +4. 导入 `YYImage.h`。 +5. 注意:如果你需要支持 WebP,可以将 `Vendor/WebP.framework`(静态库) 加入你的工程。 + +常见问题 +============== +_Q: 为什么我不能显示 WebP 图片?_ + +A: 确保 `WebP.framework` 已经被添加到你的工程内了。你可以调用 `YYImageWebPAvailable()` 来检查一下 WebP 组件是否被正确安装。 + +_Q: 为什么我不能播放 APNG 动画?_ + +A: 你应该禁用 Build Settings 中的 `Compress PNG Files` 和 `Remove Text Metadata From PNG Files`. 或者你也可以把 APNG 文件的扩展名改为`apng`. + +文档 +============== +你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYImage/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。 + + +系统要求 +============== +该项目最低支持 `iOS 6.0` 和 `Xcode 7.0`。 + + +许可证 +============== +YYImage 使用 MIT 许可证,详情见 LICENSE 文件。 + + +相关链接 +============== +[移动端图片格式调研](http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/)
+ +[iOS 处理图片的一些小 Tip](http://blog.ibireme.com/2015/11/02/ios_image_tips/) + diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/config.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/config.h new file mode 100644 index 00000000..70b1fbd4 --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/config.h @@ -0,0 +1,145 @@ +/* src/webp/config.h. Generated from config.h.in by configure. */ +/* src/webp/config.h.in. Generated from configure.ac by autoheader. */ + +/* Define if building universal (internal helper macro) */ +/* #undef AC_APPLE_UNIVERSAL_BUILD */ + +/* Set to 1 if __builtin_bswap16 is available */ +#define HAVE_BUILTIN_BSWAP16 1 + +/* Set to 1 if __builtin_bswap32 is available */ +#define HAVE_BUILTIN_BSWAP32 1 + +/* Set to 1 if __builtin_bswap64 is available */ +#define HAVE_BUILTIN_BSWAP64 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GLUT_GLUT_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GL_GLUT_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OPENGL_GLUT_H */ + +/* Have PTHREAD_PRIO_INHERIT. */ +#define HAVE_PTHREAD_PRIO_INHERIT 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SHLWAPI_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_WINCODEC_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "libwebp" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "https://bugs.chromium.org/p/webp" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libwebp" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libwebp 0.5.0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libwebp" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "http://developers.google.com/speed/webp" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.5.0" + +/* Define to necessary symbol if this constant uses a non-standard name on + your system. */ +/* #undef PTHREAD_CREATE_JOINABLE */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "0.5.0" + +/* Enable experimental code */ +/* #undef WEBP_EXPERIMENTAL_FEATURES */ + +/* Define to 1 to force aligned memory operations */ +/* #undef WEBP_FORCE_ALIGNED */ + +/* Set to 1 if AVX2 is supported */ +/* #undef WEBP_HAVE_AVX2 */ + +/* Set to 1 if GIF library is installed */ +/* #undef WEBP_HAVE_GIF */ + +/* Set to 1 if OpenGL is supported */ +/* #undef WEBP_HAVE_GL */ + +/* Set to 1 if JPEG library is installed */ +/* #undef WEBP_HAVE_JPEG */ + +/* Set to 1 if PNG library is installed */ +/* #undef WEBP_HAVE_PNG */ + +/* Set to 1 if SSE2 is supported */ +/* #undef WEBP_HAVE_SSE2 */ + +/* Set to 1 if SSE4.1 is supported */ +/* #undef WEBP_HAVE_SSE41 */ + +/* Set to 1 if TIFF library is installed */ +/* #undef WEBP_HAVE_TIFF */ + +/* Undefine this to disable thread support. */ +#define WEBP_USE_THREAD 1 + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#if defined AC_APPLE_UNIVERSAL_BUILD +# if defined __BIG_ENDIAN__ +# define WORDS_BIGENDIAN 1 +# endif +#else +# ifndef WORDS_BIGENDIAN +/* # undef WORDS_BIGENDIAN */ +# endif +#endif diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/decode.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/decode.h new file mode 100644 index 00000000..143e4fbe --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/decode.h @@ -0,0 +1,488 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Main decoding functions for WebP images. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_DECODE_H_ +#define WEBP_WEBP_DECODE_H_ + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_DECODER_ABI_VERSION 0x0208 // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum VP8StatusCode VP8StatusCode; +// typedef enum WEBP_CSP_MODE WEBP_CSP_MODE; +typedef struct WebPRGBABuffer WebPRGBABuffer; +typedef struct WebPYUVABuffer WebPYUVABuffer; +typedef struct WebPDecBuffer WebPDecBuffer; +typedef struct WebPIDecoder WebPIDecoder; +typedef struct WebPBitstreamFeatures WebPBitstreamFeatures; +typedef struct WebPDecoderOptions WebPDecoderOptions; +typedef struct WebPDecoderConfig WebPDecoderConfig; + +// Return the decoder's version number, packed in hexadecimal using 8bits for +// each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN(int) WebPGetDecoderVersion(void); + +// Retrieve basic header information: width, height. +// This function will also validate the header and return 0 in +// case of formatting error. +// Pointers 'width' and 'height' can be passed NULL if deemed irrelevant. +WEBP_EXTERN(int) WebPGetInfo(const uint8_t* data, size_t data_size, + int* width, int* height); + +// Decodes WebP images pointed to by 'data' and returns RGBA samples, along +// with the dimensions in *width and *height. The ordering of samples in +// memory is R, G, B, A, R, G, B, A... in scan order (endian-independent). +// The returned pointer should be deleted calling WebPFree(). +// Returns NULL in case of error. +WEBP_EXTERN(uint8_t*) WebPDecodeRGBA(const uint8_t* data, size_t data_size, + int* width, int* height); + +// Same as WebPDecodeRGBA, but returning A, R, G, B, A, R, G, B... ordered data. +WEBP_EXTERN(uint8_t*) WebPDecodeARGB(const uint8_t* data, size_t data_size, + int* width, int* height); + +// Same as WebPDecodeRGBA, but returning B, G, R, A, B, G, R, A... ordered data. +WEBP_EXTERN(uint8_t*) WebPDecodeBGRA(const uint8_t* data, size_t data_size, + int* width, int* height); + +// Same as WebPDecodeRGBA, but returning R, G, B, R, G, B... ordered data. +// If the bitstream contains transparency, it is ignored. +WEBP_EXTERN(uint8_t*) WebPDecodeRGB(const uint8_t* data, size_t data_size, + int* width, int* height); + +// Same as WebPDecodeRGB, but returning B, G, R, B, G, R... ordered data. +WEBP_EXTERN(uint8_t*) WebPDecodeBGR(const uint8_t* data, size_t data_size, + int* width, int* height); + + +// Decode WebP images pointed to by 'data' to Y'UV format(*). The pointer +// returned is the Y samples buffer. Upon return, *u and *v will point to +// the U and V chroma data. These U and V buffers need NOT be passed to +// WebPFree(), unlike the returned Y luma one. The dimension of the U and V +// planes are both (*width + 1) / 2 and (*height + 1)/ 2. +// Upon return, the Y buffer has a stride returned as '*stride', while U and V +// have a common stride returned as '*uv_stride'. +// Return NULL in case of error. +// (*) Also named Y'CbCr. See: http://en.wikipedia.org/wiki/YCbCr +WEBP_EXTERN(uint8_t*) WebPDecodeYUV(const uint8_t* data, size_t data_size, + int* width, int* height, + uint8_t** u, uint8_t** v, + int* stride, int* uv_stride); + +// Releases memory returned by the WebPDecode*() functions above. +WEBP_EXTERN(void) WebPFree(void* ptr); + +// These five functions are variants of the above ones, that decode the image +// directly into a pre-allocated buffer 'output_buffer'. The maximum storage +// available in this buffer is indicated by 'output_buffer_size'. If this +// storage is not sufficient (or an error occurred), NULL is returned. +// Otherwise, output_buffer is returned, for convenience. +// The parameter 'output_stride' specifies the distance (in bytes) +// between scanlines. Hence, output_buffer_size is expected to be at least +// output_stride x picture-height. +WEBP_EXTERN(uint8_t*) WebPDecodeRGBAInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_EXTERN(uint8_t*) WebPDecodeARGBInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_EXTERN(uint8_t*) WebPDecodeBGRAInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// RGB and BGR variants. Here too the transparency information, if present, +// will be dropped and ignored. +WEBP_EXTERN(uint8_t*) WebPDecodeRGBInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_EXTERN(uint8_t*) WebPDecodeBGRInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// WebPDecodeYUVInto() is a variant of WebPDecodeYUV() that operates directly +// into pre-allocated luma/chroma plane buffers. This function requires the +// strides to be passed: one for the luma plane and one for each of the +// chroma ones. The size of each plane buffer is passed as 'luma_size', +// 'u_size' and 'v_size' respectively. +// Pointer to the luma plane ('*luma') is returned or NULL if an error occurred +// during decoding (or because some buffers were found to be too small). +WEBP_EXTERN(uint8_t*) WebPDecodeYUVInto( + const uint8_t* data, size_t data_size, + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride); + +//------------------------------------------------------------------------------ +// Output colorspaces and buffer + +// Colorspaces +// Note: the naming describes the byte-ordering of packed samples in memory. +// For instance, MODE_BGRA relates to samples ordered as B,G,R,A,B,G,R,A,... +// Non-capital names (e.g.:MODE_Argb) relates to pre-multiplied RGB channels. +// RGBA-4444 and RGB-565 colorspaces are represented by following byte-order: +// RGBA-4444: [r3 r2 r1 r0 g3 g2 g1 g0], [b3 b2 b1 b0 a3 a2 a1 a0], ... +// RGB-565: [r4 r3 r2 r1 r0 g5 g4 g3], [g2 g1 g0 b4 b3 b2 b1 b0], ... +// In the case WEBP_SWAP_16BITS_CSP is defined, the bytes are swapped for +// these two modes: +// RGBA-4444: [b3 b2 b1 b0 a3 a2 a1 a0], [r3 r2 r1 r0 g3 g2 g1 g0], ... +// RGB-565: [g2 g1 g0 b4 b3 b2 b1 b0], [r4 r3 r2 r1 r0 g5 g4 g3], ... + +typedef enum WEBP_CSP_MODE { + MODE_RGB = 0, MODE_RGBA = 1, + MODE_BGR = 2, MODE_BGRA = 3, + MODE_ARGB = 4, MODE_RGBA_4444 = 5, + MODE_RGB_565 = 6, + // RGB-premultiplied transparent modes (alpha value is preserved) + MODE_rgbA = 7, + MODE_bgrA = 8, + MODE_Argb = 9, + MODE_rgbA_4444 = 10, + // YUV modes must come after RGB ones. + MODE_YUV = 11, MODE_YUVA = 12, // yuv 4:2:0 + MODE_LAST = 13 +} WEBP_CSP_MODE; + +// Some useful macros: +static WEBP_INLINE int WebPIsPremultipliedMode(WEBP_CSP_MODE mode) { + return (mode == MODE_rgbA || mode == MODE_bgrA || mode == MODE_Argb || + mode == MODE_rgbA_4444); +} + +static WEBP_INLINE int WebPIsAlphaMode(WEBP_CSP_MODE mode) { + return (mode == MODE_RGBA || mode == MODE_BGRA || mode == MODE_ARGB || + mode == MODE_RGBA_4444 || mode == MODE_YUVA || + WebPIsPremultipliedMode(mode)); +} + +static WEBP_INLINE int WebPIsRGBMode(WEBP_CSP_MODE mode) { + return (mode < MODE_YUV); +} + +//------------------------------------------------------------------------------ +// WebPDecBuffer: Generic structure for describing the output sample buffer. + +struct WebPRGBABuffer { // view as RGBA + uint8_t* rgba; // pointer to RGBA samples + int stride; // stride in bytes from one scanline to the next. + size_t size; // total size of the *rgba buffer. +}; + +struct WebPYUVABuffer { // view as YUVA + uint8_t* y, *u, *v, *a; // pointer to luma, chroma U/V, alpha samples + int y_stride; // luma stride + int u_stride, v_stride; // chroma strides + int a_stride; // alpha stride + size_t y_size; // luma plane size + size_t u_size, v_size; // chroma planes size + size_t a_size; // alpha-plane size +}; + +// Output buffer +struct WebPDecBuffer { + WEBP_CSP_MODE colorspace; // Colorspace. + int width, height; // Dimensions. + int is_external_memory; // If true, 'internal_memory' pointer is not used. + union { + WebPRGBABuffer RGBA; + WebPYUVABuffer YUVA; + } u; // Nameless union of buffer parameters. + uint32_t pad[4]; // padding for later use + + uint8_t* private_memory; // Internally allocated memory (only when + // is_external_memory is false). Should not be used + // externally, but accessed via the buffer union. +}; + +// Internal, version-checked, entry point +WEBP_EXTERN(int) WebPInitDecBufferInternal(WebPDecBuffer*, int); + +// Initialize the structure as empty. Must be called before any other use. +// Returns false in case of version mismatch +static WEBP_INLINE int WebPInitDecBuffer(WebPDecBuffer* buffer) { + return WebPInitDecBufferInternal(buffer, WEBP_DECODER_ABI_VERSION); +} + +// Free any memory associated with the buffer. Must always be called last. +// Note: doesn't free the 'buffer' structure itself. +WEBP_EXTERN(void) WebPFreeDecBuffer(WebPDecBuffer* buffer); + +//------------------------------------------------------------------------------ +// Enumeration of the status codes + +typedef enum VP8StatusCode { + VP8_STATUS_OK = 0, + VP8_STATUS_OUT_OF_MEMORY, + VP8_STATUS_INVALID_PARAM, + VP8_STATUS_BITSTREAM_ERROR, + VP8_STATUS_UNSUPPORTED_FEATURE, + VP8_STATUS_SUSPENDED, + VP8_STATUS_USER_ABORT, + VP8_STATUS_NOT_ENOUGH_DATA +} VP8StatusCode; + +//------------------------------------------------------------------------------ +// Incremental decoding +// +// This API allows streamlined decoding of partial data. +// Picture can be incrementally decoded as data become available thanks to the +// WebPIDecoder object. This object can be left in a SUSPENDED state if the +// picture is only partially decoded, pending additional input. +// Code example: +// +// WebPInitDecBuffer(&buffer); +// buffer.colorspace = mode; +// ... +// WebPIDecoder* idec = WebPINewDecoder(&buffer); +// while (has_more_data) { +// // ... (get additional data) +// status = WebPIAppend(idec, new_data, new_data_size); +// if (status != VP8_STATUS_SUSPENDED || +// break; +// } +// +// // The above call decodes the current available buffer. +// // Part of the image can now be refreshed by calling to +// // WebPIDecGetRGB()/WebPIDecGetYUVA() etc. +// } +// WebPIDelete(idec); + +// Creates a new incremental decoder with the supplied buffer parameter. +// This output_buffer can be passed NULL, in which case a default output buffer +// is used (with MODE_RGB). Otherwise, an internal reference to 'output_buffer' +// is kept, which means that the lifespan of 'output_buffer' must be larger than +// that of the returned WebPIDecoder object. +// The supplied 'output_buffer' content MUST NOT be changed between calls to +// WebPIAppend() or WebPIUpdate() unless 'output_buffer.is_external_memory' is +// set to 1. In such a case, it is allowed to modify the pointers, size and +// stride of output_buffer.u.RGBA or output_buffer.u.YUVA, provided they remain +// within valid bounds. +// All other fields of WebPDecBuffer MUST remain constant between calls. +// Returns NULL if the allocation failed. +WEBP_EXTERN(WebPIDecoder*) WebPINewDecoder(WebPDecBuffer* output_buffer); + +// This function allocates and initializes an incremental-decoder object, which +// will output the RGB/A samples specified by 'csp' into a preallocated +// buffer 'output_buffer'. The size of this buffer is at least +// 'output_buffer_size' and the stride (distance in bytes between two scanlines) +// is specified by 'output_stride'. +// Additionally, output_buffer can be passed NULL in which case the output +// buffer will be allocated automatically when the decoding starts. The +// colorspace 'csp' is taken into account for allocating this buffer. All other +// parameters are ignored. +// Returns NULL if the allocation failed, or if some parameters are invalid. +WEBP_EXTERN(WebPIDecoder*) WebPINewRGB( + WEBP_CSP_MODE csp, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// This function allocates and initializes an incremental-decoder object, which +// will output the raw luma/chroma samples into a preallocated planes if +// supplied. The luma plane is specified by its pointer 'luma', its size +// 'luma_size' and its stride 'luma_stride'. Similarly, the chroma-u plane +// is specified by the 'u', 'u_size' and 'u_stride' parameters, and the chroma-v +// plane by 'v' and 'v_size'. And same for the alpha-plane. The 'a' pointer +// can be pass NULL in case one is not interested in the transparency plane. +// Conversely, 'luma' can be passed NULL if no preallocated planes are supplied. +// In this case, the output buffer will be automatically allocated (using +// MODE_YUVA) when decoding starts. All parameters are then ignored. +// Returns NULL if the allocation failed or if a parameter is invalid. +WEBP_EXTERN(WebPIDecoder*) WebPINewYUVA( + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride, + uint8_t* a, size_t a_size, int a_stride); + +// Deprecated version of the above, without the alpha plane. +// Kept for backward compatibility. +WEBP_EXTERN(WebPIDecoder*) WebPINewYUV( + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride); + +// Deletes the WebPIDecoder object and associated memory. Must always be called +// if WebPINewDecoder, WebPINewRGB or WebPINewYUV succeeded. +WEBP_EXTERN(void) WebPIDelete(WebPIDecoder* idec); + +// Copies and decodes the next available data. Returns VP8_STATUS_OK when +// the image is successfully decoded. Returns VP8_STATUS_SUSPENDED when more +// data is expected. Returns error in other cases. +WEBP_EXTERN(VP8StatusCode) WebPIAppend( + WebPIDecoder* idec, const uint8_t* data, size_t data_size); + +// A variant of the above function to be used when data buffer contains +// partial data from the beginning. In this case data buffer is not copied +// to the internal memory. +// Note that the value of the 'data' pointer can change between calls to +// WebPIUpdate, for instance when the data buffer is resized to fit larger data. +WEBP_EXTERN(VP8StatusCode) WebPIUpdate( + WebPIDecoder* idec, const uint8_t* data, size_t data_size); + +// Returns the RGB/A image decoded so far. Returns NULL if output params +// are not initialized yet. The RGB/A output type corresponds to the colorspace +// specified during call to WebPINewDecoder() or WebPINewRGB(). +// *last_y is the index of last decoded row in raster scan order. Some pointers +// (*last_y, *width etc.) can be NULL if corresponding information is not +// needed. +WEBP_EXTERN(uint8_t*) WebPIDecGetRGB( + const WebPIDecoder* idec, int* last_y, + int* width, int* height, int* stride); + +// Same as above function to get a YUVA image. Returns pointer to the luma +// plane or NULL in case of error. If there is no alpha information +// the alpha pointer '*a' will be returned NULL. +WEBP_EXTERN(uint8_t*) WebPIDecGetYUVA( + const WebPIDecoder* idec, int* last_y, + uint8_t** u, uint8_t** v, uint8_t** a, + int* width, int* height, int* stride, int* uv_stride, int* a_stride); + +// Deprecated alpha-less version of WebPIDecGetYUVA(): it will ignore the +// alpha information (if present). Kept for backward compatibility. +static WEBP_INLINE uint8_t* WebPIDecGetYUV( + const WebPIDecoder* idec, int* last_y, uint8_t** u, uint8_t** v, + int* width, int* height, int* stride, int* uv_stride) { + return WebPIDecGetYUVA(idec, last_y, u, v, NULL, width, height, + stride, uv_stride, NULL); +} + +// Generic call to retrieve information about the displayable area. +// If non NULL, the left/right/width/height pointers are filled with the visible +// rectangular area so far. +// Returns NULL in case the incremental decoder object is in an invalid state. +// Otherwise returns the pointer to the internal representation. This structure +// is read-only, tied to WebPIDecoder's lifespan and should not be modified. +WEBP_EXTERN(const WebPDecBuffer*) WebPIDecodedArea( + const WebPIDecoder* idec, int* left, int* top, int* width, int* height); + +//------------------------------------------------------------------------------ +// Advanced decoding parametrization +// +// Code sample for using the advanced decoding API +/* + // A) Init a configuration object + WebPDecoderConfig config; + CHECK(WebPInitDecoderConfig(&config)); + + // B) optional: retrieve the bitstream's features. + CHECK(WebPGetFeatures(data, data_size, &config.input) == VP8_STATUS_OK); + + // C) Adjust 'config', if needed + config.no_fancy_upsampling = 1; + config.output.colorspace = MODE_BGRA; + // etc. + + // Note that you can also make config.output point to an externally + // supplied memory buffer, provided it's big enough to store the decoded + // picture. Otherwise, config.output will just be used to allocate memory + // and store the decoded picture. + + // D) Decode! + CHECK(WebPDecode(data, data_size, &config) == VP8_STATUS_OK); + + // E) Decoded image is now in config.output (and config.output.u.RGBA) + + // F) Reclaim memory allocated in config's object. It's safe to call + // this function even if the memory is external and wasn't allocated + // by WebPDecode(). + WebPFreeDecBuffer(&config.output); +*/ + +// Features gathered from the bitstream +struct WebPBitstreamFeatures { + int width; // Width in pixels, as read from the bitstream. + int height; // Height in pixels, as read from the bitstream. + int has_alpha; // True if the bitstream contains an alpha channel. + int has_animation; // True if the bitstream is an animation. + int format; // 0 = undefined (/mixed), 1 = lossy, 2 = lossless + + uint32_t pad[5]; // padding for later use +}; + +// Internal, version-checked, entry point +WEBP_EXTERN(VP8StatusCode) WebPGetFeaturesInternal( + const uint8_t*, size_t, WebPBitstreamFeatures*, int); + +// Retrieve features from the bitstream. The *features structure is filled +// with information gathered from the bitstream. +// Returns VP8_STATUS_OK when the features are successfully retrieved. Returns +// VP8_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the +// features from headers. Returns error in other cases. +static WEBP_INLINE VP8StatusCode WebPGetFeatures( + const uint8_t* data, size_t data_size, + WebPBitstreamFeatures* features) { + return WebPGetFeaturesInternal(data, data_size, features, + WEBP_DECODER_ABI_VERSION); +} + +// Decoding options +struct WebPDecoderOptions { + int bypass_filtering; // if true, skip the in-loop filtering + int no_fancy_upsampling; // if true, use faster pointwise upsampler + int use_cropping; // if true, cropping is applied _first_ + int crop_left, crop_top; // top-left position for cropping. + // Will be snapped to even values. + int crop_width, crop_height; // dimension of the cropping area + int use_scaling; // if true, scaling is applied _afterward_ + int scaled_width, scaled_height; // final resolution + int use_threads; // if true, use multi-threaded decoding + int dithering_strength; // dithering strength (0=Off, 100=full) + int flip; // flip output vertically + int alpha_dithering_strength; // alpha dithering strength in [0..100] + + uint32_t pad[5]; // padding for later use +}; + +// Main object storing the configuration for advanced decoding. +struct WebPDecoderConfig { + WebPBitstreamFeatures input; // Immutable bitstream features (optional) + WebPDecBuffer output; // Output buffer (can point to external mem) + WebPDecoderOptions options; // Decoding options +}; + +// Internal, version-checked, entry point +WEBP_EXTERN(int) WebPInitDecoderConfigInternal(WebPDecoderConfig*, int); + +// Initialize the configuration as empty. This function must always be +// called first, unless WebPGetFeatures() is to be called. +// Returns false in case of mismatched version. +static WEBP_INLINE int WebPInitDecoderConfig(WebPDecoderConfig* config) { + return WebPInitDecoderConfigInternal(config, WEBP_DECODER_ABI_VERSION); +} + +// Instantiate a new incremental decoder object with the requested +// configuration. The bitstream can be passed using 'data' and 'data_size' +// parameter, in which case the features will be parsed and stored into +// config->input. Otherwise, 'data' can be NULL and no parsing will occur. +// Note that 'config' can be NULL too, in which case a default configuration +// is used. +// The return WebPIDecoder object must always be deleted calling WebPIDelete(). +// Returns NULL in case of error (and config->status will then reflect +// the error condition). +WEBP_EXTERN(WebPIDecoder*) WebPIDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config); + +// Non-incremental version. This version decodes the full data at once, taking +// 'config' into account. Returns decoding status (which should be VP8_STATUS_OK +// if the decoding was successful). +WEBP_EXTERN(VP8StatusCode) WebPDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_WEBP_DECODE_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/demux.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/demux.h new file mode 100644 index 00000000..454f6914 --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/demux.h @@ -0,0 +1,358 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Demux API. +// Enables extraction of image and extended format data from WebP files. + +// Code Example: Demuxing WebP data to extract all the frames, ICC profile +// and EXIF/XMP metadata. +/* + WebPDemuxer* demux = WebPDemux(&webp_data); + + uint32_t width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); + uint32_t height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); + // ... (Get information about the features present in the WebP file). + uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); + + // ... (Iterate over all frames). + WebPIterator iter; + if (WebPDemuxGetFrame(demux, 1, &iter)) { + do { + // ... (Consume 'iter'; e.g. Decode 'iter.fragment' with WebPDecode(), + // ... and get other frame properties like width, height, offsets etc. + // ... see 'struct WebPIterator' below for more info). + } while (WebPDemuxNextFrame(&iter)); + WebPDemuxReleaseIterator(&iter); + } + + // ... (Extract metadata). + WebPChunkIterator chunk_iter; + if (flags & ICCP_FLAG) WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter); + // ... (Consume the ICC profile in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + if (flags & EXIF_FLAG) WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter); + // ... (Consume the EXIF metadata in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + if (flags & XMP_FLAG) WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter); + // ... (Consume the XMP metadata in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + WebPDemuxDelete(demux); +*/ + +#ifndef WEBP_WEBP_DEMUX_H_ +#define WEBP_WEBP_DEMUX_H_ + +#include "./decode.h" // for WEBP_CSP_MODE +#include "./mux_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_DEMUX_ABI_VERSION 0x0107 // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPDemuxState WebPDemuxState; +// typedef enum WebPFormatFeature WebPFormatFeature; +typedef struct WebPDemuxer WebPDemuxer; +typedef struct WebPIterator WebPIterator; +typedef struct WebPChunkIterator WebPChunkIterator; +typedef struct WebPAnimInfo WebPAnimInfo; +typedef struct WebPAnimDecoderOptions WebPAnimDecoderOptions; + +//------------------------------------------------------------------------------ + +// Returns the version number of the demux library, packed in hexadecimal using +// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN(int) WebPGetDemuxVersion(void); + +//------------------------------------------------------------------------------ +// Life of a Demux object + +typedef enum WebPDemuxState { + WEBP_DEMUX_PARSE_ERROR = -1, // An error occurred while parsing. + WEBP_DEMUX_PARSING_HEADER = 0, // Not enough data to parse full header. + WEBP_DEMUX_PARSED_HEADER = 1, // Header parsing complete, + // data may be available. + WEBP_DEMUX_DONE = 2 // Entire file has been parsed. +} WebPDemuxState; + +// Internal, version-checked, entry point +WEBP_EXTERN(WebPDemuxer*) WebPDemuxInternal( + const WebPData*, int, WebPDemuxState*, int); + +// Parses the full WebP file given by 'data'. For single images the WebP file +// header alone or the file header and the chunk header may be absent. +// Returns a WebPDemuxer object on successful parse, NULL otherwise. +static WEBP_INLINE WebPDemuxer* WebPDemux(const WebPData* data) { + return WebPDemuxInternal(data, 0, NULL, WEBP_DEMUX_ABI_VERSION); +} + +// Parses the possibly incomplete WebP file given by 'data'. +// If 'state' is non-NULL it will be set to indicate the status of the demuxer. +// Returns NULL in case of error or if there isn't enough data to start parsing; +// and a WebPDemuxer object on successful parse. +// Note that WebPDemuxer keeps internal pointers to 'data' memory segment. +// If this data is volatile, the demuxer object should be deleted (by calling +// WebPDemuxDelete()) and WebPDemuxPartial() called again on the new data. +// This is usually an inexpensive operation. +static WEBP_INLINE WebPDemuxer* WebPDemuxPartial( + const WebPData* data, WebPDemuxState* state) { + return WebPDemuxInternal(data, 1, state, WEBP_DEMUX_ABI_VERSION); +} + +// Frees memory associated with 'dmux'. +WEBP_EXTERN(void) WebPDemuxDelete(WebPDemuxer* dmux); + +//------------------------------------------------------------------------------ +// Data/information extraction. + +typedef enum WebPFormatFeature { + WEBP_FF_FORMAT_FLAGS, // Extended format flags present in the 'VP8X' chunk. + WEBP_FF_CANVAS_WIDTH, + WEBP_FF_CANVAS_HEIGHT, + WEBP_FF_LOOP_COUNT, + WEBP_FF_BACKGROUND_COLOR, + WEBP_FF_FRAME_COUNT // Number of frames present in the demux object. + // In case of a partial demux, this is the number of + // frames seen so far, with the last frame possibly + // being partial. +} WebPFormatFeature; + +// Get the 'feature' value from the 'dmux'. +// NOTE: values are only valid if WebPDemux() was used or WebPDemuxPartial() +// returned a state > WEBP_DEMUX_PARSING_HEADER. +WEBP_EXTERN(uint32_t) WebPDemuxGetI( + const WebPDemuxer* dmux, WebPFormatFeature feature); + +//------------------------------------------------------------------------------ +// Frame iteration. + +struct WebPIterator { + int frame_num; + int num_frames; // equivalent to WEBP_FF_FRAME_COUNT. + int x_offset, y_offset; // offset relative to the canvas. + int width, height; // dimensions of this frame. + int duration; // display duration in milliseconds. + WebPMuxAnimDispose dispose_method; // dispose method for the frame. + int complete; // true if 'fragment' contains a full frame. partial images + // may still be decoded with the WebP incremental decoder. + WebPData fragment; // The frame given by 'frame_num'. Note for historical + // reasons this is called a fragment. + int has_alpha; // True if the frame contains transparency. + WebPMuxAnimBlend blend_method; // Blend operation for the frame. + + uint32_t pad[2]; // padding for later use. + void* private_; // for internal use only. +}; + +// Retrieves frame 'frame_number' from 'dmux'. +// 'iter->fragment' points to the frame on return from this function. +// Setting 'frame_number' equal to 0 will return the last frame of the image. +// Returns false if 'dmux' is NULL or frame 'frame_number' is not present. +// Call WebPDemuxReleaseIterator() when use of the iterator is complete. +// NOTE: 'dmux' must persist for the lifetime of 'iter'. +WEBP_EXTERN(int) WebPDemuxGetFrame( + const WebPDemuxer* dmux, int frame_number, WebPIterator* iter); + +// Sets 'iter->fragment' to point to the next ('iter->frame_num' + 1) or +// previous ('iter->frame_num' - 1) frame. These functions do not loop. +// Returns true on success, false otherwise. +WEBP_EXTERN(int) WebPDemuxNextFrame(WebPIterator* iter); +WEBP_EXTERN(int) WebPDemuxPrevFrame(WebPIterator* iter); + +// Releases any memory associated with 'iter'. +// Must be called before any subsequent calls to WebPDemuxGetChunk() on the same +// iter. Also, must be called before destroying the associated WebPDemuxer with +// WebPDemuxDelete(). +WEBP_EXTERN(void) WebPDemuxReleaseIterator(WebPIterator* iter); + +//------------------------------------------------------------------------------ +// Chunk iteration. + +struct WebPChunkIterator { + // The current and total number of chunks with the fourcc given to + // WebPDemuxGetChunk(). + int chunk_num; + int num_chunks; + WebPData chunk; // The payload of the chunk. + + uint32_t pad[6]; // padding for later use + void* private_; +}; + +// Retrieves the 'chunk_number' instance of the chunk with id 'fourcc' from +// 'dmux'. +// 'fourcc' is a character array containing the fourcc of the chunk to return, +// e.g., "ICCP", "XMP ", "EXIF", etc. +// Setting 'chunk_number' equal to 0 will return the last chunk in a set. +// Returns true if the chunk is found, false otherwise. Image related chunk +// payloads are accessed through WebPDemuxGetFrame() and related functions. +// Call WebPDemuxReleaseChunkIterator() when use of the iterator is complete. +// NOTE: 'dmux' must persist for the lifetime of the iterator. +WEBP_EXTERN(int) WebPDemuxGetChunk(const WebPDemuxer* dmux, + const char fourcc[4], int chunk_number, + WebPChunkIterator* iter); + +// Sets 'iter->chunk' to point to the next ('iter->chunk_num' + 1) or previous +// ('iter->chunk_num' - 1) chunk. These functions do not loop. +// Returns true on success, false otherwise. +WEBP_EXTERN(int) WebPDemuxNextChunk(WebPChunkIterator* iter); +WEBP_EXTERN(int) WebPDemuxPrevChunk(WebPChunkIterator* iter); + +// Releases any memory associated with 'iter'. +// Must be called before destroying the associated WebPDemuxer with +// WebPDemuxDelete(). +WEBP_EXTERN(void) WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter); + +//------------------------------------------------------------------------------ +// WebPAnimDecoder API +// +// This API allows decoding (possibly) animated WebP images. +// +// Code Example: +/* + WebPAnimDecoderOptions dec_options; + WebPAnimDecoderOptionsInit(&dec_options); + // Tune 'dec_options' as needed. + WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options); + WebPAnimInfo anim_info; + WebPAnimDecoderGetInfo(dec, &anim_info); + for (uint32_t i = 0; i < anim_info.loop_count; ++i) { + while (WebPAnimDecoderHasMoreFrames(dec)) { + uint8_t* buf; + int timestamp; + WebPAnimDecoderGetNext(dec, &buf, ×tamp); + // ... (Render 'buf' based on 'timestamp'). + // ... (Do NOT free 'buf', as it is owned by 'dec'). + } + WebPAnimDecoderReset(dec); + } + const WebPDemuxer* demuxer = WebPAnimDecoderGetDemuxer(dec); + // ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data). + WebPAnimDecoderDelete(dec); +*/ + +typedef struct WebPAnimDecoder WebPAnimDecoder; // Main opaque object. + +// Global options. +struct WebPAnimDecoderOptions { + // Output colorspace. Only the following modes are supported: + // MODE_RGBA, MODE_BGRA, MODE_rgbA and MODE_bgrA. + WEBP_CSP_MODE color_mode; + int use_threads; // If true, use multi-threaded decoding. + uint32_t padding[7]; // Padding for later use. +}; + +// Internal, version-checked, entry point. +WEBP_EXTERN(int) WebPAnimDecoderOptionsInitInternal( + WebPAnimDecoderOptions*, int); + +// Should always be called, to initialize a fresh WebPAnimDecoderOptions +// structure before modification. Returns false in case of version mismatch. +// WebPAnimDecoderOptionsInit() must have succeeded before using the +// 'dec_options' object. +static WEBP_INLINE int WebPAnimDecoderOptionsInit( + WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderOptionsInitInternal(dec_options, + WEBP_DEMUX_ABI_VERSION); +} + +// Internal, version-checked, entry point. +WEBP_EXTERN(WebPAnimDecoder*) WebPAnimDecoderNewInternal( + const WebPData*, const WebPAnimDecoderOptions*, int); + +// Creates and initializes a WebPAnimDecoder object. +// Parameters: +// webp_data - (in) WebP bitstream. This should remain unchanged during the +// lifetime of the output WebPAnimDecoder object. +// dec_options - (in) decoding options. Can be passed NULL to choose +// reasonable defaults (in particular, color mode MODE_RGBA +// will be picked). +// Returns: +// A pointer to the newly created WebPAnimDecoder object, or NULL in case of +// parsing error, invalid option or memory error. +static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew( + const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderNewInternal(webp_data, dec_options, + WEBP_DEMUX_ABI_VERSION); +} + +// Global information about the animation.. +struct WebPAnimInfo { + uint32_t canvas_width; + uint32_t canvas_height; + uint32_t loop_count; + uint32_t bgcolor; + uint32_t frame_count; + uint32_t pad[4]; // padding for later use +}; + +// Get global information about the animation. +// Parameters: +// dec - (in) decoder instance to get information from. +// info - (out) global information fetched from the animation. +// Returns: +// True on success. +WEBP_EXTERN(int) WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, + WebPAnimInfo* info); + +// Fetch the next frame from 'dec' based on options supplied to +// WebPAnimDecoderNew(). This will be a fully reconstructed canvas of size +// 'canvas_width * 4 * canvas_height', and not just the frame sub-rectangle. The +// returned buffer 'buf' is valid only until the next call to +// WebPAnimDecoderGetNext(), WebPAnimDecoderReset() or WebPAnimDecoderDelete(). +// Parameters: +// dec - (in/out) decoder instance from which the next frame is to be fetched. +// buf - (out) decoded frame. +// timestamp - (out) timestamp of the frame in milliseconds. +// Returns: +// False if any of the arguments are NULL, or if there is a parsing or +// decoding error, or if there are no more frames. Otherwise, returns true. +WEBP_EXTERN(int) WebPAnimDecoderGetNext(WebPAnimDecoder* dec, + uint8_t** buf, int* timestamp); + +// Check if there are more frames left to decode. +// Parameters: +// dec - (in) decoder instance to be checked. +// Returns: +// True if 'dec' is not NULL and some frames are yet to be decoded. +// Otherwise, returns false. +WEBP_EXTERN(int) WebPAnimDecoderHasMoreFrames(const WebPAnimDecoder* dec); + +// Resets the WebPAnimDecoder object, so that next call to +// WebPAnimDecoderGetNext() will restart decoding from 1st frame. This would be +// helpful when all frames need to be decoded multiple times (e.g. +// info.loop_count times) without destroying and recreating the 'dec' object. +// Parameters: +// dec - (in/out) decoder instance to be reset +WEBP_EXTERN(void) WebPAnimDecoderReset(WebPAnimDecoder* dec); + +// Grab the internal demuxer object. +// Getting the demuxer object can be useful if one wants to use operations only +// available through demuxer; e.g. to get XMP/EXIF/ICC metadata. The returned +// demuxer object is owned by 'dec' and is valid only until the next call to +// WebPAnimDecoderDelete(). +// +// Parameters: +// dec - (in) decoder instance from which the demuxer object is to be fetched. +WEBP_EXTERN(const WebPDemuxer*) WebPAnimDecoderGetDemuxer( + const WebPAnimDecoder* dec); + +// Deletes the WebPAnimDecoder object. +// Parameters: +// dec - (in/out) decoder instance to be deleted +WEBP_EXTERN(void) WebPAnimDecoderDelete(WebPAnimDecoder* dec); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_WEBP_DEMUX_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/encode.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/encode.h new file mode 100644 index 00000000..c382ea76 --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/encode.h @@ -0,0 +1,527 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebP encoder: main interface +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_ENCODE_H_ +#define WEBP_WEBP_ENCODE_H_ + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_ENCODER_ABI_VERSION 0x0209 // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPImageHint WebPImageHint; +// typedef enum WebPEncCSP WebPEncCSP; +// typedef enum WebPPreset WebPPreset; +// typedef enum WebPEncodingError WebPEncodingError; +typedef struct WebPConfig WebPConfig; +typedef struct WebPPicture WebPPicture; // main structure for I/O +typedef struct WebPAuxStats WebPAuxStats; +typedef struct WebPMemoryWriter WebPMemoryWriter; + +// Return the encoder's version number, packed in hexadecimal using 8bits for +// each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN(int) WebPGetEncoderVersion(void); + +//------------------------------------------------------------------------------ +// One-stop-shop call! No questions asked: + +// Returns the size of the compressed data (pointed to by *output), or 0 if +// an error occurred. The compressed data must be released by the caller +// using the call 'WebPFree(*output)'. +// These functions compress using the lossy format, and the quality_factor +// can go from 0 (smaller output, lower quality) to 100 (best quality, +// larger output). +WEBP_EXTERN(size_t) WebPEncodeRGB(const uint8_t* rgb, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN(size_t) WebPEncodeBGR(const uint8_t* bgr, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN(size_t) WebPEncodeRGBA(const uint8_t* rgba, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN(size_t) WebPEncodeBGRA(const uint8_t* bgra, + int width, int height, int stride, + float quality_factor, uint8_t** output); + +// These functions are the equivalent of the above, but compressing in a +// lossless manner. Files are usually larger than lossy format, but will +// not suffer any compression loss. +WEBP_EXTERN(size_t) WebPEncodeLosslessRGB(const uint8_t* rgb, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN(size_t) WebPEncodeLosslessBGR(const uint8_t* bgr, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN(size_t) WebPEncodeLosslessRGBA(const uint8_t* rgba, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN(size_t) WebPEncodeLosslessBGRA(const uint8_t* bgra, + int width, int height, int stride, + uint8_t** output); + +// Releases memory returned by the WebPEncode*() functions above. +WEBP_EXTERN(void) WebPFree(void* ptr); + +//------------------------------------------------------------------------------ +// Coding parameters + +// Image characteristics hint for the underlying encoder. +typedef enum WebPImageHint { + WEBP_HINT_DEFAULT = 0, // default preset. + WEBP_HINT_PICTURE, // digital picture, like portrait, inner shot + WEBP_HINT_PHOTO, // outdoor photograph, with natural lighting + WEBP_HINT_GRAPH, // Discrete tone image (graph, map-tile etc). + WEBP_HINT_LAST +} WebPImageHint; + +// Compression parameters. +struct WebPConfig { + int lossless; // Lossless encoding (0=lossy(default), 1=lossless). + float quality; // between 0 (smallest file) and 100 (biggest) + int method; // quality/speed trade-off (0=fast, 6=slower-better) + + WebPImageHint image_hint; // Hint for image type (lossless only for now). + + // Parameters related to lossy compression only: + int target_size; // if non-zero, set the desired target size in bytes. + // Takes precedence over the 'compression' parameter. + float target_PSNR; // if non-zero, specifies the minimal distortion to + // try to achieve. Takes precedence over target_size. + int segments; // maximum number of segments to use, in [1..4] + int sns_strength; // Spatial Noise Shaping. 0=off, 100=maximum. + int filter_strength; // range: [0 = off .. 100 = strongest] + int filter_sharpness; // range: [0 = off .. 7 = least sharp] + int filter_type; // filtering type: 0 = simple, 1 = strong (only used + // if filter_strength > 0 or autofilter > 0) + int autofilter; // Auto adjust filter's strength [0 = off, 1 = on] + int alpha_compression; // Algorithm for encoding the alpha plane (0 = none, + // 1 = compressed with WebP lossless). Default is 1. + int alpha_filtering; // Predictive filtering method for alpha plane. + // 0: none, 1: fast, 2: best. Default if 1. + int alpha_quality; // Between 0 (smallest size) and 100 (lossless). + // Default is 100. + int pass; // number of entropy-analysis passes (in [1..10]). + + int show_compressed; // if true, export the compressed picture back. + // In-loop filtering is not applied. + int preprocessing; // preprocessing filter: + // 0=none, 1=segment-smooth, 2=pseudo-random dithering + int partitions; // log2(number of token partitions) in [0..3]. Default + // is set to 0 for easier progressive decoding. + int partition_limit; // quality degradation allowed to fit the 512k limit + // on prediction modes coding (0: no degradation, + // 100: maximum possible degradation). + int emulate_jpeg_size; // If true, compression parameters will be remapped + // to better match the expected output size from + // JPEG compression. Generally, the output size will + // be similar but the degradation will be lower. + int thread_level; // If non-zero, try and use multi-threaded encoding. + int low_memory; // If set, reduce memory usage (but increase CPU use). + + int near_lossless; // Near lossless encoding [0 = off(default) .. 100]. + // This feature is experimental. + int exact; // if non-zero, preserve the exact RGB values under + // transparent area. Otherwise, discard this invisible + // RGB information for better compression. The default + // value is 0. + +#ifdef WEBP_EXPERIMENTAL_FEATURES + int delta_palettization; + uint32_t pad[2]; // padding for later use +#else + uint32_t pad[3]; // padding for later use +#endif // WEBP_EXPERIMENTAL_FEATURES +}; + +// Enumerate some predefined settings for WebPConfig, depending on the type +// of source picture. These presets are used when calling WebPConfigPreset(). +typedef enum WebPPreset { + WEBP_PRESET_DEFAULT = 0, // default preset. + WEBP_PRESET_PICTURE, // digital picture, like portrait, inner shot + WEBP_PRESET_PHOTO, // outdoor photograph, with natural lighting + WEBP_PRESET_DRAWING, // hand or line drawing, with high-contrast details + WEBP_PRESET_ICON, // small-sized colorful images + WEBP_PRESET_TEXT // text-like +} WebPPreset; + +// Internal, version-checked, entry point +WEBP_EXTERN(int) WebPConfigInitInternal(WebPConfig*, WebPPreset, float, int); + +// Should always be called, to initialize a fresh WebPConfig structure before +// modification. Returns false in case of version mismatch. WebPConfigInit() +// must have succeeded before using the 'config' object. +// Note that the default values are lossless=0 and quality=75. +static WEBP_INLINE int WebPConfigInit(WebPConfig* config) { + return WebPConfigInitInternal(config, WEBP_PRESET_DEFAULT, 75.f, + WEBP_ENCODER_ABI_VERSION); +} + +// This function will initialize the configuration according to a predefined +// set of parameters (referred to by 'preset') and a given quality factor. +// This function can be called as a replacement to WebPConfigInit(). Will +// return false in case of error. +static WEBP_INLINE int WebPConfigPreset(WebPConfig* config, + WebPPreset preset, float quality) { + return WebPConfigInitInternal(config, preset, quality, + WEBP_ENCODER_ABI_VERSION); +} + +// Activate the lossless compression mode with the desired efficiency level +// between 0 (fastest, lowest compression) and 9 (slower, best compression). +// A good default level is '6', providing a fair tradeoff between compression +// speed and final compressed size. +// This function will overwrite several fields from config: 'method', 'quality' +// and 'lossless'. Returns false in case of parameter error. +WEBP_EXTERN(int) WebPConfigLosslessPreset(WebPConfig* config, int level); + +// Returns true if 'config' is non-NULL and all configuration parameters are +// within their valid ranges. +WEBP_EXTERN(int) WebPValidateConfig(const WebPConfig* config); + +//------------------------------------------------------------------------------ +// Input / Output +// Structure for storing auxiliary statistics (mostly for lossy encoding). + +struct WebPAuxStats { + int coded_size; // final size + + float PSNR[5]; // peak-signal-to-noise ratio for Y/U/V/All/Alpha + int block_count[3]; // number of intra4/intra16/skipped macroblocks + int header_bytes[2]; // approximate number of bytes spent for header + // and mode-partition #0 + int residual_bytes[3][4]; // approximate number of bytes spent for + // DC/AC/uv coefficients for each (0..3) segments. + int segment_size[4]; // number of macroblocks in each segments + int segment_quant[4]; // quantizer values for each segments + int segment_level[4]; // filtering strength for each segments [0..63] + + int alpha_data_size; // size of the transparency data + int layer_data_size; // size of the enhancement layer data + + // lossless encoder statistics + uint32_t lossless_features; // bit0:predictor bit1:cross-color transform + // bit2:subtract-green bit3:color indexing + int histogram_bits; // number of precision bits of histogram + int transform_bits; // precision bits for transform + int cache_bits; // number of bits for color cache lookup + int palette_size; // number of color in palette, if used + int lossless_size; // final lossless size + int lossless_hdr_size; // lossless header (transform, huffman etc) size + int lossless_data_size; // lossless image data size + + uint32_t pad[2]; // padding for later use +}; + +// Signature for output function. Should return true if writing was successful. +// data/data_size is the segment of data to write, and 'picture' is for +// reference (and so one can make use of picture->custom_ptr). +typedef int (*WebPWriterFunction)(const uint8_t* data, size_t data_size, + const WebPPicture* picture); + +// WebPMemoryWrite: a special WebPWriterFunction that writes to memory using +// the following WebPMemoryWriter object (to be set as a custom_ptr). +struct WebPMemoryWriter { + uint8_t* mem; // final buffer (of size 'max_size', larger than 'size'). + size_t size; // final size + size_t max_size; // total capacity + uint32_t pad[1]; // padding for later use +}; + +// The following must be called first before any use. +WEBP_EXTERN(void) WebPMemoryWriterInit(WebPMemoryWriter* writer); + +// The following must be called to deallocate writer->mem memory. The 'writer' +// object itself is not deallocated. +WEBP_EXTERN(void) WebPMemoryWriterClear(WebPMemoryWriter* writer); +// The custom writer to be used with WebPMemoryWriter as custom_ptr. Upon +// completion, writer.mem and writer.size will hold the coded data. +// writer.mem must be freed by calling WebPMemoryWriterClear. +WEBP_EXTERN(int) WebPMemoryWrite(const uint8_t* data, size_t data_size, + const WebPPicture* picture); + +// Progress hook, called from time to time to report progress. It can return +// false to request an abort of the encoding process, or true otherwise if +// everything is OK. +typedef int (*WebPProgressHook)(int percent, const WebPPicture* picture); + +// Color spaces. +typedef enum WebPEncCSP { + // chroma sampling + WEBP_YUV420 = 0, // 4:2:0 + WEBP_YUV420A = 4, // alpha channel variant + WEBP_CSP_UV_MASK = 3, // bit-mask to get the UV sampling factors + WEBP_CSP_ALPHA_BIT = 4 // bit that is set if alpha is present +} WebPEncCSP; + +// Encoding error conditions. +typedef enum WebPEncodingError { + VP8_ENC_OK = 0, + VP8_ENC_ERROR_OUT_OF_MEMORY, // memory error allocating objects + VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY, // memory error while flushing bits + VP8_ENC_ERROR_NULL_PARAMETER, // a pointer parameter is NULL + VP8_ENC_ERROR_INVALID_CONFIGURATION, // configuration is invalid + VP8_ENC_ERROR_BAD_DIMENSION, // picture has invalid width/height + VP8_ENC_ERROR_PARTITION0_OVERFLOW, // partition is bigger than 512k + VP8_ENC_ERROR_PARTITION_OVERFLOW, // partition is bigger than 16M + VP8_ENC_ERROR_BAD_WRITE, // error while flushing bytes + VP8_ENC_ERROR_FILE_TOO_BIG, // file is bigger than 4G + VP8_ENC_ERROR_USER_ABORT, // abort request by user + VP8_ENC_ERROR_LAST // list terminator. always last. +} WebPEncodingError; + +// maximum width/height allowed (inclusive), in pixels +#define WEBP_MAX_DIMENSION 16383 + +// Main exchange structure (input samples, output bytes, statistics) +struct WebPPicture { + // INPUT + ////////////// + // Main flag for encoder selecting between ARGB or YUV input. + // It is recommended to use ARGB input (*argb, argb_stride) for lossless + // compression, and YUV input (*y, *u, *v, etc.) for lossy compression + // since these are the respective native colorspace for these formats. + int use_argb; + + // YUV input (mostly used for input to lossy compression) + WebPEncCSP colorspace; // colorspace: should be YUV420 for now (=Y'CbCr). + int width, height; // dimensions (less or equal to WEBP_MAX_DIMENSION) + uint8_t *y, *u, *v; // pointers to luma/chroma planes. + int y_stride, uv_stride; // luma/chroma strides. + uint8_t* a; // pointer to the alpha plane + int a_stride; // stride of the alpha plane + uint32_t pad1[2]; // padding for later use + + // ARGB input (mostly used for input to lossless compression) + uint32_t* argb; // Pointer to argb (32 bit) plane. + int argb_stride; // This is stride in pixels units, not bytes. + uint32_t pad2[3]; // padding for later use + + // OUTPUT + /////////////// + // Byte-emission hook, to store compressed bytes as they are ready. + WebPWriterFunction writer; // can be NULL + void* custom_ptr; // can be used by the writer. + + // map for extra information (only for lossy compression mode) + int extra_info_type; // 1: intra type, 2: segment, 3: quant + // 4: intra-16 prediction mode, + // 5: chroma prediction mode, + // 6: bit cost, 7: distortion + uint8_t* extra_info; // if not NULL, points to an array of size + // ((width + 15) / 16) * ((height + 15) / 16) that + // will be filled with a macroblock map, depending + // on extra_info_type. + + // STATS AND REPORTS + /////////////////////////// + // Pointer to side statistics (updated only if not NULL) + WebPAuxStats* stats; + + // Error code for the latest error encountered during encoding + WebPEncodingError error_code; + + // If not NULL, report progress during encoding. + WebPProgressHook progress_hook; + + void* user_data; // this field is free to be set to any value and + // used during callbacks (like progress-report e.g.). + + uint32_t pad3[3]; // padding for later use + + // Unused for now + uint8_t *pad4, *pad5; + uint32_t pad6[8]; // padding for later use + + // PRIVATE FIELDS + //////////////////// + void* memory_; // row chunk of memory for yuva planes + void* memory_argb_; // and for argb too. + void* pad7[2]; // padding for later use +}; + +// Internal, version-checked, entry point +WEBP_EXTERN(int) WebPPictureInitInternal(WebPPicture*, int); + +// Should always be called, to initialize the structure. Returns false in case +// of version mismatch. WebPPictureInit() must have succeeded before using the +// 'picture' object. +// Note that, by default, use_argb is false and colorspace is WEBP_YUV420. +static WEBP_INLINE int WebPPictureInit(WebPPicture* picture) { + return WebPPictureInitInternal(picture, WEBP_ENCODER_ABI_VERSION); +} + +//------------------------------------------------------------------------------ +// WebPPicture utils + +// Convenience allocation / deallocation based on picture->width/height: +// Allocate y/u/v buffers as per colorspace/width/height specification. +// Note! This function will free the previous buffer if needed. +// Returns false in case of memory error. +WEBP_EXTERN(int) WebPPictureAlloc(WebPPicture* picture); + +// Release the memory allocated by WebPPictureAlloc() or WebPPictureImport*(). +// Note that this function does _not_ free the memory used by the 'picture' +// object itself. +// Besides memory (which is reclaimed) all other fields of 'picture' are +// preserved. +WEBP_EXTERN(void) WebPPictureFree(WebPPicture* picture); + +// Copy the pixels of *src into *dst, using WebPPictureAlloc. Upon return, *dst +// will fully own the copied pixels (this is not a view). The 'dst' picture need +// not be initialized as its content is overwritten. +// Returns false in case of memory allocation error. +WEBP_EXTERN(int) WebPPictureCopy(const WebPPicture* src, WebPPicture* dst); + +// Compute PSNR, SSIM or LSIM distortion metric between two pictures. Results +// are in dB, stored in result[] in the Y/U/V/Alpha/All or B/G/R/A/All order. +// Returns false in case of error (src and ref don't have same dimension, ...) +// Warning: this function is rather CPU-intensive. +WEBP_EXTERN(int) WebPPictureDistortion( + const WebPPicture* src, const WebPPicture* ref, + int metric_type, // 0 = PSNR, 1 = SSIM, 2 = LSIM + float result[5]); + +// self-crops a picture to the rectangle defined by top/left/width/height. +// Returns false in case of memory allocation error, or if the rectangle is +// outside of the source picture. +// The rectangle for the view is defined by the top-left corner pixel +// coordinates (left, top) as well as its width and height. This rectangle +// must be fully be comprised inside the 'src' source picture. If the source +// picture uses the YUV420 colorspace, the top and left coordinates will be +// snapped to even values. +WEBP_EXTERN(int) WebPPictureCrop(WebPPicture* picture, + int left, int top, int width, int height); + +// Extracts a view from 'src' picture into 'dst'. The rectangle for the view +// is defined by the top-left corner pixel coordinates (left, top) as well +// as its width and height. This rectangle must be fully be comprised inside +// the 'src' source picture. If the source picture uses the YUV420 colorspace, +// the top and left coordinates will be snapped to even values. +// Picture 'src' must out-live 'dst' picture. Self-extraction of view is allowed +// ('src' equal to 'dst') as a mean of fast-cropping (but note that doing so, +// the original dimension will be lost). Picture 'dst' need not be initialized +// with WebPPictureInit() if it is different from 'src', since its content will +// be overwritten. +// Returns false in case of memory allocation error or invalid parameters. +WEBP_EXTERN(int) WebPPictureView(const WebPPicture* src, + int left, int top, int width, int height, + WebPPicture* dst); + +// Returns true if the 'picture' is actually a view and therefore does +// not own the memory for pixels. +WEBP_EXTERN(int) WebPPictureIsView(const WebPPicture* picture); + +// Rescale a picture to new dimension width x height. +// If either 'width' or 'height' (but not both) is 0 the corresponding +// dimension will be calculated preserving the aspect ratio. +// No gamma correction is applied. +// Returns false in case of error (invalid parameter or insufficient memory). +WEBP_EXTERN(int) WebPPictureRescale(WebPPicture* pic, int width, int height); + +// Colorspace conversion function to import RGB samples. +// Previous buffer will be free'd, if any. +// *rgb buffer should have a size of at least height * rgb_stride. +// Returns false in case of memory error. +WEBP_EXTERN(int) WebPPictureImportRGB( + WebPPicture* picture, const uint8_t* rgb, int rgb_stride); +// Same, but for RGBA buffer. +WEBP_EXTERN(int) WebPPictureImportRGBA( + WebPPicture* picture, const uint8_t* rgba, int rgba_stride); +// Same, but for RGBA buffer. Imports the RGB direct from the 32-bit format +// input buffer ignoring the alpha channel. Avoids needing to copy the data +// to a temporary 24-bit RGB buffer to import the RGB only. +WEBP_EXTERN(int) WebPPictureImportRGBX( + WebPPicture* picture, const uint8_t* rgbx, int rgbx_stride); + +// Variants of the above, but taking BGR(A|X) input. +WEBP_EXTERN(int) WebPPictureImportBGR( + WebPPicture* picture, const uint8_t* bgr, int bgr_stride); +WEBP_EXTERN(int) WebPPictureImportBGRA( + WebPPicture* picture, const uint8_t* bgra, int bgra_stride); +WEBP_EXTERN(int) WebPPictureImportBGRX( + WebPPicture* picture, const uint8_t* bgrx, int bgrx_stride); + +// Converts picture->argb data to the YUV420A format. The 'colorspace' +// parameter is deprecated and should be equal to WEBP_YUV420. +// Upon return, picture->use_argb is set to false. The presence of real +// non-opaque transparent values is detected, and 'colorspace' will be +// adjusted accordingly. Note that this method is lossy. +// Returns false in case of error. +WEBP_EXTERN(int) WebPPictureARGBToYUVA(WebPPicture* picture, + WebPEncCSP /*colorspace = WEBP_YUV420*/); + +// Same as WebPPictureARGBToYUVA(), but the conversion is done using +// pseudo-random dithering with a strength 'dithering' between +// 0.0 (no dithering) and 1.0 (maximum dithering). This is useful +// for photographic picture. +WEBP_EXTERN(int) WebPPictureARGBToYUVADithered( + WebPPicture* picture, WebPEncCSP colorspace, float dithering); + +// Performs 'smart' RGBA->YUVA420 downsampling and colorspace conversion. +// Downsampling is handled with extra care in case of color clipping. This +// method is roughly 2x slower than WebPPictureARGBToYUVA() but produces better +// YUV representation. +// Returns false in case of error. +WEBP_EXTERN(int) WebPPictureSmartARGBToYUVA(WebPPicture* picture); + +// Converts picture->yuv to picture->argb and sets picture->use_argb to true. +// The input format must be YUV_420 or YUV_420A. +// Note that the use of this method is discouraged if one has access to the +// raw ARGB samples, since using YUV420 is comparatively lossy. Also, the +// conversion from YUV420 to ARGB incurs a small loss too. +// Returns false in case of error. +WEBP_EXTERN(int) WebPPictureYUVAToARGB(WebPPicture* picture); + +// Helper function: given a width x height plane of RGBA or YUV(A) samples +// clean-up the YUV or RGB samples under fully transparent area, to help +// compressibility (no guarantee, though). +WEBP_EXTERN(void) WebPCleanupTransparentArea(WebPPicture* picture); + +// Scan the picture 'picture' for the presence of non fully opaque alpha values. +// Returns true in such case. Otherwise returns false (indicating that the +// alpha plane can be ignored altogether e.g.). +WEBP_EXTERN(int) WebPPictureHasTransparency(const WebPPicture* picture); + +// Remove the transparency information (if present) by blending the color with +// the background color 'background_rgb' (specified as 24bit RGB triplet). +// After this call, all alpha values are reset to 0xff. +WEBP_EXTERN(void) WebPBlendAlpha(WebPPicture* pic, uint32_t background_rgb); + +//------------------------------------------------------------------------------ +// Main call + +// Main encoding call, after config and picture have been initialized. +// 'picture' must be less than 16384x16384 in dimension (cf WEBP_MAX_DIMENSION), +// and the 'config' object must be a valid one. +// Returns false in case of error, true otherwise. +// In case of error, picture->error_code is updated accordingly. +// 'picture' can hold the source samples in both YUV(A) or ARGB input, depending +// on the value of 'picture->use_argb'. It is highly recommended to use +// the former for lossy encoding, and the latter for lossless encoding +// (when config.lossless is true). Automatic conversion from one format to +// another is provided but they both incur some loss. +WEBP_EXTERN(int) WebPEncode(const WebPConfig* config, WebPPicture* picture); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_WEBP_ENCODE_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/extras.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/extras.h new file mode 100644 index 00000000..1c24be2e --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/extras.h @@ -0,0 +1,51 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// + +#ifndef WEBP_WEBP_EXTRAS_H_ +#define WEBP_WEBP_EXTRAS_H_ + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./encode.h" + +#define WEBP_EXTRAS_ABI_VERSION 0x0000 // MAJOR(8b) + MINOR(8b) + +//------------------------------------------------------------------------------ + +// Returns the version number of the extras library, packed in hexadecimal using +// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN(int) WebPGetExtrasVersion(void); + +//------------------------------------------------------------------------------ +// Ad-hoc colorspace importers. + +// Import luma sample (gray scale image) into 'picture'. The 'picture' +// width and height must be set prior to calling this function. +WEBP_EXTERN(int) WebPImportGray(const uint8_t* gray, WebPPicture* picture); + +// Import rgb sample in RGB565 packed format into 'picture'. The 'picture' +// width and height must be set prior to calling this function. +WEBP_EXTERN(int) WebPImportRGB565(const uint8_t* rgb565, WebPPicture* pic); + +// Import rgb sample in RGB4444 packed format into 'picture'. The 'picture' +// width and height must be set prior to calling this function. +WEBP_EXTERN(int) WebPImportRGB4444(const uint8_t* rgb4444, WebPPicture* pic); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_WEBP_EXTRAS_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/format_constants.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/format_constants.h new file mode 100644 index 00000000..b6e78a64 --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/format_constants.h @@ -0,0 +1,88 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Internal header for constants related to WebP file format. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_WEBP_FORMAT_CONSTANTS_H_ +#define WEBP_WEBP_FORMAT_CONSTANTS_H_ + +// Create fourcc of the chunk from the chunk tag characters. +#define MKFOURCC(a, b, c, d) ((a) | (b) << 8 | (c) << 16 | (uint32_t)(d) << 24) + +// VP8 related constants. +#define VP8_SIGNATURE 0x9d012a // Signature in VP8 data. +#define VP8_MAX_PARTITION0_SIZE (1 << 19) // max size of mode partition +#define VP8_MAX_PARTITION_SIZE (1 << 24) // max size for token partition +#define VP8_FRAME_HEADER_SIZE 10 // Size of the frame header within VP8 data. + +// VP8L related constants. +#define VP8L_SIGNATURE_SIZE 1 // VP8L signature size. +#define VP8L_MAGIC_BYTE 0x2f // VP8L signature byte. +#define VP8L_IMAGE_SIZE_BITS 14 // Number of bits used to store + // width and height. +#define VP8L_VERSION_BITS 3 // 3 bits reserved for version. +#define VP8L_VERSION 0 // version 0 +#define VP8L_FRAME_HEADER_SIZE 5 // Size of the VP8L frame header. + +#define MAX_PALETTE_SIZE 256 +#define MAX_CACHE_BITS 11 +#define HUFFMAN_CODES_PER_META_CODE 5 +#define ARGB_BLACK 0xff000000 + +#define DEFAULT_CODE_LENGTH 8 +#define MAX_ALLOWED_CODE_LENGTH 15 + +#define NUM_LITERAL_CODES 256 +#define NUM_LENGTH_CODES 24 +#define NUM_DISTANCE_CODES 40 +#define CODE_LENGTH_CODES 19 + +#define MIN_HUFFMAN_BITS 2 // min number of Huffman bits +#define MAX_HUFFMAN_BITS 9 // max number of Huffman bits + +#define TRANSFORM_PRESENT 1 // The bit to be written when next data + // to be read is a transform. +#define NUM_TRANSFORMS 4 // Maximum number of allowed transform + // in a bitstream. +typedef enum { + PREDICTOR_TRANSFORM = 0, + CROSS_COLOR_TRANSFORM = 1, + SUBTRACT_GREEN = 2, + COLOR_INDEXING_TRANSFORM = 3 +} VP8LImageTransformType; + +// Alpha related constants. +#define ALPHA_HEADER_LEN 1 +#define ALPHA_NO_COMPRESSION 0 +#define ALPHA_LOSSLESS_COMPRESSION 1 +#define ALPHA_PREPROCESSED_LEVELS 1 + +// Mux related constants. +#define TAG_SIZE 4 // Size of a chunk tag (e.g. "VP8L"). +#define CHUNK_SIZE_BYTES 4 // Size needed to store chunk's size. +#define CHUNK_HEADER_SIZE 8 // Size of a chunk header. +#define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP"). +#define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk. +#define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk. +#define FRGM_CHUNK_SIZE 6 // Size of a FRGM chunk. +#define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk. + +#define MAX_CANVAS_SIZE (1 << 24) // 24-bit max for VP8X width/height. +#define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height. +#define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count +#define MAX_DURATION (1 << 24) // maximum duration +#define MAX_POSITION_OFFSET (1 << 24) // maximum frame/fragment x/y offset + +// Maximum chunk payload is such that adding the header and padding won't +// overflow a uint32_t. +#define MAX_CHUNK_PAYLOAD (~0U - CHUNK_HEADER_SIZE - 1) + +#endif /* WEBP_WEBP_FORMAT_CONSTANTS_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/mux.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/mux.h new file mode 100644 index 00000000..b72658c7 --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/mux.h @@ -0,0 +1,530 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// RIFF container manipulation and encoding for WebP images. +// +// Authors: Urvang (urvang@google.com) +// Vikas (vikasa@google.com) + +#ifndef WEBP_WEBP_MUX_H_ +#define WEBP_WEBP_MUX_H_ + +#include "./mux_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_MUX_ABI_VERSION 0x0106 // MAJOR(8b) + MINOR(8b) + +//------------------------------------------------------------------------------ +// Mux API +// +// This API allows manipulation of WebP container images containing features +// like color profile, metadata, animation and fragmented images. +// +// Code Example#1: Create a WebPMux object with image data, color profile and +// XMP metadata. +/* + int copy_data = 0; + WebPMux* mux = WebPMuxNew(); + // ... (Prepare image data). + WebPMuxSetImage(mux, &image, copy_data); + // ... (Prepare ICCP color profile data). + WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); + // ... (Prepare XMP metadata). + WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data); + // Get data from mux in WebP RIFF format. + WebPMuxAssemble(mux, &output_data); + WebPMuxDelete(mux); + // ... (Consume output_data; e.g. write output_data.bytes to file). + WebPDataClear(&output_data); +*/ + +// Code Example#2: Get image and color profile data from a WebP file. +/* + int copy_data = 0; + // ... (Read data from file). + WebPMux* mux = WebPMuxCreate(&data, copy_data); + WebPMuxGetFrame(mux, 1, &image); + // ... (Consume image; e.g. call WebPDecode() to decode the data). + WebPMuxGetChunk(mux, "ICCP", &icc_profile); + // ... (Consume icc_data). + WebPMuxDelete(mux); + free(data); +*/ + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPMuxError WebPMuxError; +// typedef enum WebPChunkId WebPChunkId; +typedef struct WebPMux WebPMux; // main opaque object. +typedef struct WebPMuxFrameInfo WebPMuxFrameInfo; +typedef struct WebPMuxAnimParams WebPMuxAnimParams; +typedef struct WebPAnimEncoderOptions WebPAnimEncoderOptions; + +// Error codes +typedef enum WebPMuxError { + WEBP_MUX_OK = 1, + WEBP_MUX_NOT_FOUND = 0, + WEBP_MUX_INVALID_ARGUMENT = -1, + WEBP_MUX_BAD_DATA = -2, + WEBP_MUX_MEMORY_ERROR = -3, + WEBP_MUX_NOT_ENOUGH_DATA = -4 +} WebPMuxError; + +// IDs for different types of chunks. +typedef enum WebPChunkId { + WEBP_CHUNK_VP8X, // VP8X + WEBP_CHUNK_ICCP, // ICCP + WEBP_CHUNK_ANIM, // ANIM + WEBP_CHUNK_ANMF, // ANMF + WEBP_CHUNK_FRGM, // FRGM + WEBP_CHUNK_ALPHA, // ALPH + WEBP_CHUNK_IMAGE, // VP8/VP8L + WEBP_CHUNK_EXIF, // EXIF + WEBP_CHUNK_XMP, // XMP + WEBP_CHUNK_UNKNOWN, // Other chunks. + WEBP_CHUNK_NIL +} WebPChunkId; + +//------------------------------------------------------------------------------ + +// Returns the version number of the mux library, packed in hexadecimal using +// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN(int) WebPGetMuxVersion(void); + +//------------------------------------------------------------------------------ +// Life of a Mux object + +// Internal, version-checked, entry point +WEBP_EXTERN(WebPMux*) WebPNewInternal(int); + +// Creates an empty mux object. +// Returns: +// A pointer to the newly created empty mux object. +// Or NULL in case of memory error. +static WEBP_INLINE WebPMux* WebPMuxNew(void) { + return WebPNewInternal(WEBP_MUX_ABI_VERSION); +} + +// Deletes the mux object. +// Parameters: +// mux - (in/out) object to be deleted +WEBP_EXTERN(void) WebPMuxDelete(WebPMux* mux); + +//------------------------------------------------------------------------------ +// Mux creation. + +// Internal, version-checked, entry point +WEBP_EXTERN(WebPMux*) WebPMuxCreateInternal(const WebPData*, int, int); + +// Creates a mux object from raw data given in WebP RIFF format. +// Parameters: +// bitstream - (in) the bitstream data in WebP RIFF format +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. +// Returns: +// A pointer to the mux object created from given data - on success. +// NULL - In case of invalid data or memory error. +static WEBP_INLINE WebPMux* WebPMuxCreate(const WebPData* bitstream, + int copy_data) { + return WebPMuxCreateInternal(bitstream, copy_data, WEBP_MUX_ABI_VERSION); +} + +//------------------------------------------------------------------------------ +// Non-image chunks. + +// Note: Only non-image related chunks should be managed through chunk APIs. +// (Image related chunks are: "ANMF", "FRGM", "VP8 ", "VP8L" and "ALPH"). +// To add, get and delete images, use WebPMuxSetImage(), WebPMuxPushFrame(), +// WebPMuxGetFrame() and WebPMuxDeleteFrame(). + +// Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object. +// Any existing chunk(s) with the same id will be removed. +// Parameters: +// mux - (in/out) object to which the chunk is to be added +// fourcc - (in) a character array containing the fourcc of the given chunk; +// e.g., "ICCP", "XMP ", "EXIF" etc. +// chunk_data - (in) the chunk data to be added +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL +// or if fourcc corresponds to an image chunk. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxSetChunk( + WebPMux* mux, const char fourcc[4], const WebPData* chunk_data, + int copy_data); + +// Gets a reference to the data of the chunk with id 'fourcc' in the mux object. +// The caller should NOT free the returned data. +// Parameters: +// mux - (in) object from which the chunk data is to be fetched +// fourcc - (in) a character array containing the fourcc of the chunk; +// e.g., "ICCP", "XMP ", "EXIF" etc. +// chunk_data - (out) returned chunk data +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL +// or if fourcc corresponds to an image chunk. +// WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given id. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxGetChunk( + const WebPMux* mux, const char fourcc[4], WebPData* chunk_data); + +// Deletes the chunk with the given 'fourcc' from the mux object. +// Parameters: +// mux - (in/out) object from which the chunk is to be deleted +// fourcc - (in) a character array containing the fourcc of the chunk; +// e.g., "ICCP", "XMP ", "EXIF" etc. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or fourcc is NULL +// or if fourcc corresponds to an image chunk. +// WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given fourcc. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxDeleteChunk( + WebPMux* mux, const char fourcc[4]); + +//------------------------------------------------------------------------------ +// Images. + +// Encapsulates data about a single frame/fragment. +struct WebPMuxFrameInfo { + WebPData bitstream; // image data: can be a raw VP8/VP8L bitstream + // or a single-image WebP file. + int x_offset; // x-offset of the frame. + int y_offset; // y-offset of the frame. + int duration; // duration of the frame (in milliseconds). + + WebPChunkId id; // frame type: should be one of WEBP_CHUNK_ANMF, + // WEBP_CHUNK_FRGM or WEBP_CHUNK_IMAGE + WebPMuxAnimDispose dispose_method; // Disposal method for the frame. + WebPMuxAnimBlend blend_method; // Blend operation for the frame. + uint32_t pad[1]; // padding for later use +}; + +// Sets the (non-animated and non-fragmented) image in the mux object. +// Note: Any existing images (including frames/fragments) will be removed. +// Parameters: +// mux - (in/out) object in which the image is to be set +// bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image +// WebP file (non-animated and non-fragmented) +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxSetImage( + WebPMux* mux, const WebPData* bitstream, int copy_data); + +// Adds a frame at the end of the mux object. +// Notes: (1) frame.id should be one of WEBP_CHUNK_ANMF or WEBP_CHUNK_FRGM +// (2) For setting a non-animated non-fragmented image, use +// WebPMuxSetImage() instead. +// (3) Type of frame being pushed must be same as the frames in mux. +// (4) As WebP only supports even offsets, any odd offset will be snapped +// to an even location using: offset &= ~1 +// Parameters: +// mux - (in/out) object to which the frame is to be added +// frame - (in) frame data. +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL +// or if content of 'frame' is invalid. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxPushFrame( + WebPMux* mux, const WebPMuxFrameInfo* frame, int copy_data); + +// Gets the nth frame from the mux object. +// The content of 'frame->bitstream' is allocated using malloc(), and NOT +// owned by the 'mux' object. It MUST be deallocated by the caller by calling +// WebPDataClear(). +// nth=0 has a special meaning - last position. +// Parameters: +// mux - (in) object from which the info is to be fetched +// nth - (in) index of the frame in the mux object +// frame - (out) data of the returned frame +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL. +// WEBP_MUX_NOT_FOUND - if there are less than nth frames in the mux object. +// WEBP_MUX_BAD_DATA - if nth frame chunk in mux is invalid. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxGetFrame( + const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame); + +// Deletes a frame from the mux object. +// nth=0 has a special meaning - last position. +// Parameters: +// mux - (in/out) object from which a frame is to be deleted +// nth - (in) The position from which the frame is to be deleted +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL. +// WEBP_MUX_NOT_FOUND - If there are less than nth frames in the mux object +// before deletion. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth); + +//------------------------------------------------------------------------------ +// Animation. + +// Animation parameters. +struct WebPMuxAnimParams { + uint32_t bgcolor; // Background color of the canvas stored (in MSB order) as: + // Bits 00 to 07: Alpha. + // Bits 08 to 15: Red. + // Bits 16 to 23: Green. + // Bits 24 to 31: Blue. + int loop_count; // Number of times to repeat the animation [0 = infinite]. +}; + +// Sets the animation parameters in the mux object. Any existing ANIM chunks +// will be removed. +// Parameters: +// mux - (in/out) object in which ANIM chunk is to be set/added +// params - (in) animation parameters. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxSetAnimationParams( + WebPMux* mux, const WebPMuxAnimParams* params); + +// Gets the animation parameters from the mux object. +// Parameters: +// mux - (in) object from which the animation parameters to be fetched +// params - (out) animation parameters extracted from the ANIM chunk +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL. +// WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxGetAnimationParams( + const WebPMux* mux, WebPMuxAnimParams* params); + +//------------------------------------------------------------------------------ +// Misc Utilities. + +// Sets the canvas size for the mux object. The width and height can be +// specified explicitly or left as zero (0, 0). +// * When width and height are specified explicitly, then this frame bound is +// enforced during subsequent calls to WebPMuxAssemble() and an error is +// reported if any animated frame does not completely fit within the canvas. +// * When unspecified (0, 0), the constructed canvas will get the frame bounds +// from the bounding-box over all frames after calling WebPMuxAssemble(). +// Parameters: +// mux - (in) object to which the canvas size is to be set +// width - (in) canvas width +// height - (in) canvas height +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL; or +// width or height are invalid or out of bounds +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxSetCanvasSize(WebPMux* mux, + int width, int height); + +// Gets the canvas size from the mux object. +// Note: This method assumes that the VP8X chunk, if present, is up-to-date. +// That is, the mux object hasn't been modified since the last call to +// WebPMuxAssemble() or WebPMuxCreate(). +// Parameters: +// mux - (in) object from which the canvas size is to be fetched +// width - (out) canvas width +// height - (out) canvas height +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, width or height is NULL. +// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxGetCanvasSize(const WebPMux* mux, + int* width, int* height); + +// Gets the feature flags from the mux object. +// Note: This method assumes that the VP8X chunk, if present, is up-to-date. +// That is, the mux object hasn't been modified since the last call to +// WebPMuxAssemble() or WebPMuxCreate(). +// Parameters: +// mux - (in) object from which the features are to be fetched +// flags - (out) the flags specifying which features are present in the +// mux object. This will be an OR of various flag values. +// Enum 'WebPFeatureFlags' can be used to test individual flag values. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or flags is NULL. +// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxGetFeatures(const WebPMux* mux, + uint32_t* flags); + +// Gets number of chunks with the given 'id' in the mux object. +// Parameters: +// mux - (in) object from which the info is to be fetched +// id - (in) chunk id specifying the type of chunk +// num_elements - (out) number of chunks with the given chunk id +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, or num_elements is NULL. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxNumChunks(const WebPMux* mux, + WebPChunkId id, int* num_elements); + +// Assembles all chunks in WebP RIFF format and returns in 'assembled_data'. +// This function also validates the mux object. +// Note: The content of 'assembled_data' will be ignored and overwritten. +// Also, the content of 'assembled_data' is allocated using malloc(), and NOT +// owned by the 'mux' object. It MUST be deallocated by the caller by calling +// WebPDataClear(). It's always safe to call WebPDataClear() upon return, +// even in case of error. +// Parameters: +// mux - (in/out) object whose chunks are to be assembled +// assembled_data - (out) assembled WebP data +// Returns: +// WEBP_MUX_BAD_DATA - if mux object is invalid. +// WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN(WebPMuxError) WebPMuxAssemble(WebPMux* mux, + WebPData* assembled_data); + +//------------------------------------------------------------------------------ +// WebPAnimEncoder API +// +// This API allows encoding (possibly) animated WebP images. +// +// Code Example: +/* + WebPAnimEncoderOptions enc_options; + WebPAnimEncoderOptionsInit(&enc_options); + // Tune 'enc_options' as needed. + WebPAnimEncoder* enc = WebPAnimEncoderNew(width, height, &enc_options); + while() { + WebPConfig config; + WebPConfigInit(&config); + // Tune 'config' as needed. + WebPAnimEncoderAdd(enc, frame, timestamp_ms, &config); + } + WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL); + WebPAnimEncoderAssemble(enc, webp_data); + WebPAnimEncoderDelete(enc); + // Write the 'webp_data' to a file, or re-mux it further. +*/ + +typedef struct WebPAnimEncoder WebPAnimEncoder; // Main opaque object. + +// Forward declarations. Defined in encode.h. +struct WebPPicture; +struct WebPConfig; + +// Global options. +struct WebPAnimEncoderOptions { + WebPMuxAnimParams anim_params; // Animation parameters. + int minimize_size; // If true, minimize the output size (slow). Implicitly + // disables key-frame insertion. + int kmin; + int kmax; // Minimum and maximum distance between consecutive key + // frames in the output. The library may insert some key + // frames as needed to satisfy this criteria. + // Note that these conditions should hold: kmax > kmin + // and kmin >= kmax / 2 + 1. Also, if kmin == 0, then + // key-frame insertion is disabled; and if kmax == 0, + // then all frames will be key-frames. + int allow_mixed; // If true, use mixed compression mode; may choose + // either lossy and lossless for each frame. + int verbose; // If true, print info and warning messages to stderr. + + uint32_t padding[4]; // Padding for later use. +}; + +// Internal, version-checked, entry point. +WEBP_EXTERN(int) WebPAnimEncoderOptionsInitInternal( + WebPAnimEncoderOptions*, int); + +// Should always be called, to initialize a fresh WebPAnimEncoderOptions +// structure before modification. Returns false in case of version mismatch. +// WebPAnimEncoderOptionsInit() must have succeeded before using the +// 'enc_options' object. +static WEBP_INLINE int WebPAnimEncoderOptionsInit( + WebPAnimEncoderOptions* enc_options) { + return WebPAnimEncoderOptionsInitInternal(enc_options, WEBP_MUX_ABI_VERSION); +} + +// Internal, version-checked, entry point. +WEBP_EXTERN(WebPAnimEncoder*) WebPAnimEncoderNewInternal( + int, int, const WebPAnimEncoderOptions*, int); + +// Creates and initializes a WebPAnimEncoder object. +// Parameters: +// width/height - (in) canvas width and height of the animation. +// enc_options - (in) encoding options; can be passed NULL to pick +// reasonable defaults. +// Returns: +// A pointer to the newly created WebPAnimEncoder object. +// Or NULL in case of memory error. +static WEBP_INLINE WebPAnimEncoder* WebPAnimEncoderNew( + int width, int height, const WebPAnimEncoderOptions* enc_options) { + return WebPAnimEncoderNewInternal(width, height, enc_options, + WEBP_MUX_ABI_VERSION); +} + +// Optimize the given frame for WebP, encode it and add it to the +// WebPAnimEncoder object. +// The last call to 'WebPAnimEncoderAdd' should be with frame = NULL, which +// indicates that no more frames are to be added. This call is also used to +// determine the duration of the last frame. +// Parameters: +// enc - (in/out) object to which the frame is to be added. +// frame - (in/out) frame data in ARGB or YUV(A) format. If it is in YUV(A) +// format, it will be converted to ARGB, which incurs a small loss. +// timestamp_ms - (in) timestamp of this frame in milliseconds. +// Duration of a frame would be calculated as +// "timestamp of next frame - timestamp of this frame". +// Hence, timestamps should be in non-decreasing order. +// config - (in) encoding options; can be passed NULL to pick +// reasonable defaults. +// Returns: +// On error, returns false and frame->error_code is set appropriately. +// Otherwise, returns true. +WEBP_EXTERN(int) WebPAnimEncoderAdd( + WebPAnimEncoder* enc, struct WebPPicture* frame, int timestamp_ms, + const struct WebPConfig* config); + +// Assemble all frames added so far into a WebP bitstream. +// This call should be preceded by a call to 'WebPAnimEncoderAdd' with +// frame = NULL; if not, the duration of the last frame will be internally +// estimated. +// Parameters: +// enc - (in/out) object from which the frames are to be assembled. +// webp_data - (out) generated WebP bitstream. +// Returns: +// True on success. +WEBP_EXTERN(int) WebPAnimEncoderAssemble(WebPAnimEncoder* enc, + WebPData* webp_data); + +// Get error string corresponding to the most recent call using 'enc'. The +// returned string is owned by 'enc' and is valid only until the next call to +// WebPAnimEncoderAdd() or WebPAnimEncoderAssemble() or WebPAnimEncoderDelete(). +// Parameters: +// enc - (in/out) object from which the error string is to be fetched. +// Returns: +// NULL if 'enc' is NULL. Otherwise, returns the error string if the last call +// to 'enc' had an error, or an empty string if the last call was a success. +WEBP_EXTERN(const char*) WebPAnimEncoderGetError(WebPAnimEncoder* enc); + +// Deletes the WebPAnimEncoder object. +// Parameters: +// enc - (in/out) object to be deleted +WEBP_EXTERN(void) WebPAnimEncoderDelete(WebPAnimEncoder* enc); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_WEBP_MUX_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/mux_types.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/mux_types.h new file mode 100644 index 00000000..c94043a3 --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/mux_types.h @@ -0,0 +1,97 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Data-types common to the mux and demux libraries. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_WEBP_MUX_TYPES_H_ +#define WEBP_WEBP_MUX_TYPES_H_ + +#include // free() +#include // memset() +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPFeatureFlags WebPFeatureFlags; +// typedef enum WebPMuxAnimDispose WebPMuxAnimDispose; +// typedef enum WebPMuxAnimBlend WebPMuxAnimBlend; +typedef struct WebPData WebPData; + +// VP8X Feature Flags. +typedef enum WebPFeatureFlags { + FRAGMENTS_FLAG = 0x00000001, + ANIMATION_FLAG = 0x00000002, + XMP_FLAG = 0x00000004, + EXIF_FLAG = 0x00000008, + ALPHA_FLAG = 0x00000010, + ICCP_FLAG = 0x00000020 +} WebPFeatureFlags; + +// Dispose method (animation only). Indicates how the area used by the current +// frame is to be treated before rendering the next frame on the canvas. +typedef enum WebPMuxAnimDispose { + WEBP_MUX_DISPOSE_NONE, // Do not dispose. + WEBP_MUX_DISPOSE_BACKGROUND // Dispose to background color. +} WebPMuxAnimDispose; + +// Blend operation (animation only). Indicates how transparent pixels of the +// current frame are blended with those of the previous canvas. +typedef enum WebPMuxAnimBlend { + WEBP_MUX_BLEND, // Blend. + WEBP_MUX_NO_BLEND // Do not blend. +} WebPMuxAnimBlend; + +// Data type used to describe 'raw' data, e.g., chunk data +// (ICC profile, metadata) and WebP compressed image data. +struct WebPData { + const uint8_t* bytes; + size_t size; +}; + +// Initializes the contents of the 'webp_data' object with default values. +static WEBP_INLINE void WebPDataInit(WebPData* webp_data) { + if (webp_data != NULL) { + memset(webp_data, 0, sizeof(*webp_data)); + } +} + +// Clears the contents of the 'webp_data' object by calling free(). Does not +// deallocate the object itself. +static WEBP_INLINE void WebPDataClear(WebPData* webp_data) { + if (webp_data != NULL) { + free((void*)webp_data->bytes); + WebPDataInit(webp_data); + } +} + +// Allocates necessary storage for 'dst' and copies the contents of 'src'. +// Returns true on success. +static WEBP_INLINE int WebPDataCopy(const WebPData* src, WebPData* dst) { + if (src == NULL || dst == NULL) return 0; + WebPDataInit(dst); + if (src->bytes != NULL && src->size != 0) { + dst->bytes = (uint8_t*)malloc(src->size); + if (dst->bytes == NULL) return 0; + memcpy((void*)dst->bytes, src->bytes, src->size); + dst->size = src->size; + } + return 1; +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* WEBP_WEBP_MUX_TYPES_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/Headers/types.h b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/types.h new file mode 100644 index 00000000..98fff35a --- /dev/null +++ b/Example/Pods/YYImage/Vendor/WebP.framework/Headers/types.h @@ -0,0 +1,52 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Common types +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_TYPES_H_ +#define WEBP_WEBP_TYPES_H_ + +#include // for size_t + +#ifndef _MSC_VER +#include +#if defined(__cplusplus) || !defined(__STRICT_ANSI__) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +#define WEBP_INLINE inline +#else +#define WEBP_INLINE +#endif +#else +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef unsigned long long int uint64_t; +typedef long long int int64_t; +#define WEBP_INLINE __forceinline +#endif /* _MSC_VER */ + +#ifndef WEBP_EXTERN +// This explicitly marks library functions and allows for changing the +// signature for e.g., Windows DLL builds. +# if defined(__GNUC__) && __GNUC__ >= 4 +# define WEBP_EXTERN(type) extern __attribute__ ((visibility ("default"))) type +# else +# define WEBP_EXTERN(type) extern type +# endif /* __GNUC__ >= 4 */ +#endif /* WEBP_EXTERN */ + +// Macro to check ABI compatibility (same major revision number) +#define WEBP_ABI_IS_INCOMPATIBLE(a, b) (((a) >> 8) != ((b) >> 8)) + +#endif /* WEBP_WEBP_TYPES_H_ */ diff --git a/Example/Pods/YYImage/Vendor/WebP.framework/WebP b/Example/Pods/YYImage/Vendor/WebP.framework/WebP new file mode 100644 index 00000000..5d88df8b Binary files /dev/null and b/Example/Pods/YYImage/Vendor/WebP.framework/WebP differ diff --git a/Example/Pods/YYImage/YYImage/YYAnimatedImageView.h b/Example/Pods/YYImage/YYImage/YYAnimatedImageView.h new file mode 100644 index 00000000..e70a6543 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYAnimatedImageView.h @@ -0,0 +1,125 @@ +// +// YYAnimatedImageView.h +// YYImage +// +// Created by ibireme on 14/10/19. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + An image view for displaying animated image. + + @discussion It is a fully compatible `UIImageView` subclass. + If the `image` or `highlightedImage` property adopt to the `YYAnimatedImage` protocol, + then it can be used to play the multi-frame animation. The animation can also be + controlled with the UIImageView methods `-startAnimating`, `-stopAnimating` and `-isAnimating`. + + This view request the frame data just in time. When the device has enough free memory, + this view may cache some or all future frames in an inner buffer for lower CPU cost. + Buffer size is dynamically adjusted based on the current state of the device memory. + + Sample Code: + + // ani@3x.gif + YYImage *image = [YYImage imageNamed:@"ani"]; + YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image]; + [view addSubView:imageView]; + */ +@interface YYAnimatedImageView : UIImageView + +/** + If the image has more than one frame, set this value to `YES` will automatically + play/stop the animation when the view become visible/invisible. + + The default value is `YES`. + */ +@property (nonatomic) BOOL autoPlayAnimatedImage; + +/** + Index of the currently displayed frame (index from 0). + + Set a new value to this property will cause to display the new frame immediately. + If the new value is invalid, this method has no effect. + + You can add an observer to this property to observe the playing status. + */ +@property (nonatomic) NSUInteger currentAnimatedImageIndex; + +/** + Whether the image view is playing animation currently. + + You can add an observer to this property to observe the playing status. + */ +@property (nonatomic, readonly) BOOL currentIsPlayingAnimation; + +/** + The animation timer's runloop mode, default is `NSRunLoopCommonModes`. + + Set this property to `NSDefaultRunLoopMode` will make the animation pause during + UIScrollView scrolling. + */ +@property (nonatomic, copy) NSString *runloopMode; + +/** + The max size (in bytes) for inner frame buffer size, default is 0 (dynamically). + + When the device has enough free memory, this view will request and decode some or + all future frame image into an inner buffer. If this property's value is 0, then + the max buffer size will be dynamically adjusted based on the current state of + the device free memory. Otherwise, the buffer size will be limited by this value. + + When receive memory warning or app enter background, the buffer will be released + immediately, and may grow back at the right time. + */ +@property (nonatomic) NSUInteger maxBufferSize; + +@end + + + +/** + The YYAnimatedImage protocol declares the required methods for animated image + display with YYAnimatedImageView. + + Subclass a UIImage and implement this protocol, so that instances of that class + can be set to YYAnimatedImageView.image or YYAnimatedImageView.highlightedImage + to display animation. + + See `YYImage` and `YYFrameImage` for example. + */ +@protocol YYAnimatedImage +@required +/// Total animated frame count. +/// It the frame count is less than 1, then the methods below will be ignored. +- (NSUInteger)animatedImageFrameCount; + +/// Animation loop count, 0 means infinite looping. +- (NSUInteger)animatedImageLoopCount; + +/// Bytes per frame (in memory). It may used to optimize memory buffer size. +- (NSUInteger)animatedImageBytesPerFrame; + +/// Returns the frame image from a specified index. +/// This method may be called on background thread. +/// @param index Frame index (zero based). +- (nullable UIImage *)animatedImageFrameAtIndex:(NSUInteger)index; + +/// Returns the frames's duration from a specified index. +/// @param index Frame index (zero based). +- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index; + +@optional +/// A rectangle in image coordinates defining the subrectangle of the image that +/// will be displayed. The rectangle should not outside the image's bounds. +/// It may used to display sprite animation with a single image (sprite sheet). +- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index; +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYImage/YYImage/YYAnimatedImageView.m b/Example/Pods/YYImage/YYImage/YYAnimatedImageView.m new file mode 100644 index 00000000..5f2bcb42 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYAnimatedImageView.m @@ -0,0 +1,672 @@ +// +// YYAnimatedImageView.m +// YYImage +// +// Created by ibireme on 14/10/19. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYAnimatedImageView.h" +#import "YYImageCoder.h" +#import +#import + + +#define BUFFER_SIZE (10 * 1024 * 1024) // 10MB (minimum memory buffer size) + +#define LOCK(...) dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER); \ +__VA_ARGS__; \ +dispatch_semaphore_signal(self->_lock); + +#define LOCK_VIEW(...) dispatch_semaphore_wait(view->_lock, DISPATCH_TIME_FOREVER); \ +__VA_ARGS__; \ +dispatch_semaphore_signal(view->_lock); + + +static int64_t _YYDeviceMemoryTotal() { + int64_t mem = [[NSProcessInfo processInfo] physicalMemory]; + if (mem < -1) mem = -1; + return mem; +} + +static int64_t _YYDeviceMemoryFree() { + mach_port_t host_port = mach_host_self(); + mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t); + vm_size_t page_size; + vm_statistics_data_t vm_stat; + kern_return_t kern; + + kern = host_page_size(host_port, &page_size); + if (kern != KERN_SUCCESS) return -1; + kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); + if (kern != KERN_SUCCESS) return -1; + return vm_stat.free_count * page_size; +} + +/** + A proxy used to hold a weak object. + It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink. + */ +@interface _YYImageWeakProxy : NSProxy +@property (nonatomic, weak, readonly) id target; +- (instancetype)initWithTarget:(id)target; ++ (instancetype)proxyWithTarget:(id)target; +@end + +@implementation _YYImageWeakProxy +- (instancetype)initWithTarget:(id)target { + _target = target; + return self; +} ++ (instancetype)proxyWithTarget:(id)target { + return [[_YYImageWeakProxy alloc] initWithTarget:target]; +} +- (id)forwardingTargetForSelector:(SEL)selector { + return _target; +} +- (void)forwardInvocation:(NSInvocation *)invocation { + void *null = NULL; + [invocation setReturnValue:&null]; +} +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + return [NSObject instanceMethodSignatureForSelector:@selector(init)]; +} +- (BOOL)respondsToSelector:(SEL)aSelector { + return [_target respondsToSelector:aSelector]; +} +- (BOOL)isEqual:(id)object { + return [_target isEqual:object]; +} +- (NSUInteger)hash { + return [_target hash]; +} +- (Class)superclass { + return [_target superclass]; +} +- (Class)class { + return [_target class]; +} +- (BOOL)isKindOfClass:(Class)aClass { + return [_target isKindOfClass:aClass]; +} +- (BOOL)isMemberOfClass:(Class)aClass { + return [_target isMemberOfClass:aClass]; +} +- (BOOL)conformsToProtocol:(Protocol *)aProtocol { + return [_target conformsToProtocol:aProtocol]; +} +- (BOOL)isProxy { + return YES; +} +- (NSString *)description { + return [_target description]; +} +- (NSString *)debugDescription { + return [_target debugDescription]; +} +@end + + + + +typedef NS_ENUM(NSUInteger, YYAnimatedImageType) { + YYAnimatedImageTypeNone = 0, + YYAnimatedImageTypeImage, + YYAnimatedImageTypeHighlightedImage, + YYAnimatedImageTypeImages, + YYAnimatedImageTypeHighlightedImages, +}; + +@interface YYAnimatedImageView() { + @package + UIImage *_curAnimatedImage; + + dispatch_once_t _onceToken; + dispatch_semaphore_t _lock; ///< lock for _buffer + NSOperationQueue *_requestQueue; ///< image request queue, serial + + CADisplayLink *_link; ///< ticker for change frame + NSTimeInterval _time; ///< time after last frame + + UIImage *_curFrame; ///< current frame to display + NSUInteger _curIndex; ///< current frame index (from 0) + NSUInteger _totalFrameCount; ///< total frame count + + BOOL _loopEnd; ///< whether the loop is end. + NSUInteger _curLoop; ///< current loop count (from 0) + NSUInteger _totalLoop; ///< total loop count, 0 means infinity + + NSMutableDictionary *_buffer; ///< frame buffer + BOOL _bufferMiss; ///< whether miss frame on last opportunity + NSUInteger _maxBufferCount; ///< maximum buffer count + NSInteger _incrBufferCount; ///< current allowed buffer count (will increase by step) + + CGRect _curContentsRect; + BOOL _curImageHasContentsRect; ///< image has implementated "animatedImageContentsRectAtIndex:" +} +@property (nonatomic, readwrite) BOOL currentIsPlayingAnimation; +- (void)calcMaxBufferCount; +@end + +/// An operation for image fetch +@interface _YYAnimatedImageViewFetchOperation : NSOperation +@property (nonatomic, weak) YYAnimatedImageView *view; +@property (nonatomic, assign) NSUInteger nextIndex; +@property (nonatomic, strong) UIImage *curImage; +@end + +@implementation _YYAnimatedImageViewFetchOperation +- (void)main { + __strong YYAnimatedImageView *view = _view; + if (!view) return; + if ([self isCancelled]) return; + view->_incrBufferCount++; + if (view->_incrBufferCount == 0) [view calcMaxBufferCount]; + if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) { + view->_incrBufferCount = view->_maxBufferCount; + } + NSUInteger idx = _nextIndex; + NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount; + NSUInteger total = view->_totalFrameCount; + view = nil; + + for (int i = 0; i < max; i++, idx++) { + @autoreleasepool { + if (idx >= total) idx = 0; + if ([self isCancelled]) break; + __strong YYAnimatedImageView *view = _view; + if (!view) break; + LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil)); + + if (miss) { + UIImage *img = [_curImage animatedImageFrameAtIndex:idx]; + img = img.yy_imageByDecoded; + if ([self isCancelled]) break; + LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]); + view = nil; + } + } + } +} +@end + +@implementation YYAnimatedImageView + +- (instancetype)init { + self = [super init]; + _runloopMode = NSRunLoopCommonModes; + _autoPlayAnimatedImage = YES; + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + _runloopMode = NSRunLoopCommonModes; + _autoPlayAnimatedImage = YES; + return self; +} + +- (instancetype)initWithImage:(UIImage *)image { + self = [super init]; + _runloopMode = NSRunLoopCommonModes; + _autoPlayAnimatedImage = YES; + self.frame = (CGRect) {CGPointZero, image.size }; + self.image = image; + return self; +} + +- (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage { + self = [super init]; + _runloopMode = NSRunLoopCommonModes; + _autoPlayAnimatedImage = YES; + CGSize size = image ? image.size : highlightedImage.size; + self.frame = (CGRect) {CGPointZero, size }; + self.image = image; + self.highlightedImage = highlightedImage; + return self; +} + +// init the animated params. +- (void)resetAnimated { + dispatch_once(&_onceToken, ^{ + _lock = dispatch_semaphore_create(1); + _buffer = [NSMutableDictionary new]; + _requestQueue = [[NSOperationQueue alloc] init]; + _requestQueue.maxConcurrentOperationCount = 1; + _link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)]; + if (_runloopMode) { + [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode]; + } + _link.paused = YES; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + }); + + [_requestQueue cancelAllOperations]; + LOCK( + if (_buffer.count) { + NSMutableDictionary *holder = _buffer; + _buffer = [NSMutableDictionary new]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ + // Capture the dictionary to global queue, + // release these images in background to avoid blocking UI thread. + [holder class]; + }); + } + ); + _link.paused = YES; + _time = 0; + if (_curIndex != 0) { + [self willChangeValueForKey:@"currentAnimatedImageIndex"]; + _curIndex = 0; + [self didChangeValueForKey:@"currentAnimatedImageIndex"]; + } + _curAnimatedImage = nil; + _curFrame = nil; + _curLoop = 0; + _totalLoop = 0; + _totalFrameCount = 1; + _loopEnd = NO; + _bufferMiss = NO; + _incrBufferCount = 0; +} + +- (void)setImage:(UIImage *)image { + if (self.image == image) return; + [self setImage:image withType:YYAnimatedImageTypeImage]; +} + +- (void)setHighlightedImage:(UIImage *)highlightedImage { + if (self.highlightedImage == highlightedImage) return; + [self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage]; +} + +- (void)setAnimationImages:(NSArray *)animationImages { + if (self.animationImages == animationImages) return; + [self setImage:animationImages withType:YYAnimatedImageTypeImages]; +} + +- (void)setHighlightedAnimationImages:(NSArray *)highlightedAnimationImages { + if (self.highlightedAnimationImages == highlightedAnimationImages) return; + [self setImage:highlightedAnimationImages withType:YYAnimatedImageTypeHighlightedImages]; +} + +- (void)setHighlighted:(BOOL)highlighted { + [super setHighlighted:highlighted]; + if (_link) [self resetAnimated]; + [self imageChanged]; +} + +- (id)imageForType:(YYAnimatedImageType)type { + switch (type) { + case YYAnimatedImageTypeNone: return nil; + case YYAnimatedImageTypeImage: return self.image; + case YYAnimatedImageTypeHighlightedImage: return self.highlightedImage; + case YYAnimatedImageTypeImages: return self.animationImages; + case YYAnimatedImageTypeHighlightedImages: return self.highlightedAnimationImages; + } + return nil; +} + +- (YYAnimatedImageType)currentImageType { + YYAnimatedImageType curType = YYAnimatedImageTypeNone; + if (self.highlighted) { + if (self.highlightedAnimationImages.count) curType = YYAnimatedImageTypeHighlightedImages; + else if (self.highlightedImage) curType = YYAnimatedImageTypeHighlightedImage; + } + if (curType == YYAnimatedImageTypeNone) { + if (self.animationImages.count) curType = YYAnimatedImageTypeImages; + else if (self.image) curType = YYAnimatedImageTypeImage; + } + return curType; +} + +- (void)setImage:(id)image withType:(YYAnimatedImageType)type { + [self stopAnimating]; + if (_link) [self resetAnimated]; + _curFrame = nil; + switch (type) { + case YYAnimatedImageTypeNone: break; + case YYAnimatedImageTypeImage: super.image = image; break; + case YYAnimatedImageTypeHighlightedImage: super.highlightedImage = image; break; + case YYAnimatedImageTypeImages: super.animationImages = image; break; + case YYAnimatedImageTypeHighlightedImages: super.highlightedAnimationImages = image; break; + } + [self imageChanged]; +} + +- (void)imageChanged { + YYAnimatedImageType newType = [self currentImageType]; + id newVisibleImage = [self imageForType:newType]; + NSUInteger newImageFrameCount = 0; + BOOL hasContentsRect = NO; + if ([newVisibleImage isKindOfClass:[UIImage class]] && + [newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)]) { + newImageFrameCount = ((UIImage *) newVisibleImage).animatedImageFrameCount; + if (newImageFrameCount > 1) { + hasContentsRect = [((UIImage *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)]; + } + } + if (!hasContentsRect && _curImageHasContentsRect) { + if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) { + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + self.layer.contentsRect = CGRectMake(0, 0, 1, 1); + [CATransaction commit]; + } + } + _curImageHasContentsRect = hasContentsRect; + if (hasContentsRect) { + CGRect rect = [((UIImage *) newVisibleImage) animatedImageContentsRectAtIndex:0]; + [self setContentsRect:rect forImage:newVisibleImage]; + } + + if (newImageFrameCount > 1) { + [self resetAnimated]; + _curAnimatedImage = newVisibleImage; + _curFrame = newVisibleImage; + _totalLoop = _curAnimatedImage.animatedImageLoopCount; + _totalFrameCount = _curAnimatedImage.animatedImageFrameCount; + [self calcMaxBufferCount]; + } + [self setNeedsDisplay]; + [self didMoved]; +} + +// dynamically adjust buffer size for current memory. +- (void)calcMaxBufferCount { + int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame; + if (bytes == 0) bytes = 1024; + + int64_t total = _YYDeviceMemoryTotal(); + int64_t free = _YYDeviceMemoryFree(); + int64_t max = MIN(total * 0.2, free * 0.6); + max = MAX(max, BUFFER_SIZE); + if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max; + double maxBufferCount = (double)max / (double)bytes; + if (maxBufferCount < 1) maxBufferCount = 1; + else if (maxBufferCount > 512) maxBufferCount = 512; + _maxBufferCount = maxBufferCount; +} + +- (void)dealloc { + [_requestQueue cancelAllOperations]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [_link invalidate]; +} + +- (BOOL)isAnimating { + return self.currentIsPlayingAnimation; +} + +- (void)stopAnimating { + [super stopAnimating]; + [_requestQueue cancelAllOperations]; + _link.paused = YES; + self.currentIsPlayingAnimation = NO; +} + +- (void)startAnimating { + YYAnimatedImageType type = [self currentImageType]; + if (type == YYAnimatedImageTypeImages || type == YYAnimatedImageTypeHighlightedImages) { + NSArray *images = [self imageForType:type]; + if (images.count > 0) { + [super startAnimating]; + self.currentIsPlayingAnimation = YES; + } + } else { + if (_curAnimatedImage && _link.paused) { + _curLoop = 0; + _loopEnd = NO; + _link.paused = NO; + self.currentIsPlayingAnimation = YES; + } + } +} + +- (void)didReceiveMemoryWarning:(NSNotification *)notification { + [_requestQueue cancelAllOperations]; + [_requestQueue addOperationWithBlock: ^{ + _incrBufferCount = -60 - (int)(arc4random() % 120); // about 1~3 seconds to grow back.. + NSNumber *next = @((_curIndex + 1) % _totalFrameCount); + LOCK( + NSArray * keys = _buffer.allKeys; + for (NSNumber * key in keys) { + if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation + [_buffer removeObjectForKey:key]; + } + } + )//LOCK + }]; +} + +- (void)didEnterBackground:(NSNotification *)notification { + [_requestQueue cancelAllOperations]; + NSNumber *next = @((_curIndex + 1) % _totalFrameCount); + LOCK( + NSArray * keys = _buffer.allKeys; + for (NSNumber * key in keys) { + if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation + [_buffer removeObjectForKey:key]; + } + } + )//LOCK +} + +- (void)step:(CADisplayLink *)link { + UIImage *image = _curAnimatedImage; + NSMutableDictionary *buffer = _buffer; + UIImage *bufferedImage = nil; + NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount; + BOOL bufferIsFull = NO; + + if (!image) return; + if (_loopEnd) { // view will keep in last frame + [self stopAnimating]; + return; + } + + NSTimeInterval delay = 0; + if (!_bufferMiss) { + _time += link.duration; + delay = [image animatedImageDurationAtIndex:_curIndex]; + if (_time < delay) return; + _time -= delay; + if (nextIndex == 0) { + _curLoop++; + if (_curLoop >= _totalLoop && _totalLoop != 0) { + _loopEnd = YES; + [self stopAnimating]; + [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep + return; // stop at last frame + } + } + delay = [image animatedImageDurationAtIndex:nextIndex]; + if (_time > delay) _time = delay; // do not jump over frame + } + LOCK( + bufferedImage = buffer[@(nextIndex)]; + if (bufferedImage) { + if ((int)_incrBufferCount < _totalFrameCount) { + [buffer removeObjectForKey:@(nextIndex)]; + } + [self willChangeValueForKey:@"currentAnimatedImageIndex"]; + _curIndex = nextIndex; + [self didChangeValueForKey:@"currentAnimatedImageIndex"]; + _curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage; + if (_curImageHasContentsRect) { + _curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex]; + [self setContentsRect:_curContentsRect forImage:_curFrame]; + } + nextIndex = (_curIndex + 1) % _totalFrameCount; + _bufferMiss = NO; + if (buffer.count == _totalFrameCount) { + bufferIsFull = YES; + } + } else { + _bufferMiss = YES; + } + )//LOCK + + if (!_bufferMiss) { + [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep + } + + if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity + _YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new]; + operation.view = self; + operation.nextIndex = nextIndex; + operation.curImage = image; + [_requestQueue addOperation:operation]; + } +} + +- (void)displayLayer:(CALayer *)layer { + if (_curFrame) { + layer.contents = (__bridge id)_curFrame.CGImage; + } +} + +- (void)setContentsRect:(CGRect)rect forImage:(UIImage *)image{ + CGRect layerRect = CGRectMake(0, 0, 1, 1); + if (image) { + CGSize imageSize = image.size; + if (imageSize.width > 0.01 && imageSize.height > 0.01) { + layerRect.origin.x = rect.origin.x / imageSize.width; + layerRect.origin.y = rect.origin.y / imageSize.height; + layerRect.size.width = rect.size.width / imageSize.width; + layerRect.size.height = rect.size.height / imageSize.height; + layerRect = CGRectIntersection(layerRect, CGRectMake(0, 0, 1, 1)); + if (CGRectIsNull(layerRect) || CGRectIsEmpty(layerRect)) { + layerRect = CGRectMake(0, 0, 1, 1); + } + } + } + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + self.layer.contentsRect = layerRect; + [CATransaction commit]; +} + +- (void)didMoved { + if (self.autoPlayAnimatedImage) { + if(self.superview && self.window) { + [self startAnimating]; + } else { + [self stopAnimating]; + } + } +} + +- (void)didMoveToWindow { + [super didMoveToWindow]; + [self didMoved]; +} + +- (void)didMoveToSuperview { + [super didMoveToSuperview]; + [self didMoved]; +} + +- (void)setCurrentAnimatedImageIndex:(NSUInteger)currentAnimatedImageIndex { + if (!_curAnimatedImage) return; + if (currentAnimatedImageIndex >= _curAnimatedImage.animatedImageFrameCount) return; + if (_curIndex == currentAnimatedImageIndex) return; + + void (^block)() = ^{ + LOCK( + [_requestQueue cancelAllOperations]; + [_buffer removeAllObjects]; + [self willChangeValueForKey:@"currentAnimatedImageIndex"]; + _curIndex = currentAnimatedImageIndex; + [self didChangeValueForKey:@"currentAnimatedImageIndex"]; + _curFrame = [_curAnimatedImage animatedImageFrameAtIndex:_curIndex]; + if (_curImageHasContentsRect) { + _curContentsRect = [_curAnimatedImage animatedImageContentsRectAtIndex:_curIndex]; + } + _time = 0; + _loopEnd = NO; + _bufferMiss = NO; + [self.layer setNeedsDisplay]; + )//LOCK + }; + + if (pthread_main_np()) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), block); + } +} + +- (NSUInteger)currentAnimatedImageIndex { + return _curIndex; +} + +- (void)setRunloopMode:(NSString *)runloopMode { + if ([_runloopMode isEqual:runloopMode]) return; + if (_link) { + if (_runloopMode) { + [_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode]; + } + if (runloopMode.length) { + [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:runloopMode]; + } + } + _runloopMode = runloopMode.copy; +} + +#pragma mark - Override NSObject(NSKeyValueObservingCustomization) + ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { + if ([key isEqualToString:@"currentAnimatedImageIndex"]) { + return NO; + } + return [super automaticallyNotifiesObserversForKey:key]; +} + +#pragma mark - NSCoding + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + _runloopMode = [aDecoder decodeObjectForKey:@"runloopMode"]; + if (_runloopMode.length == 0) _runloopMode = NSRunLoopCommonModes; + if ([aDecoder containsValueForKey:@"autoPlayAnimatedImage"]) { + _autoPlayAnimatedImage = [aDecoder decodeBoolForKey:@"autoPlayAnimatedImage"]; + } else { + _autoPlayAnimatedImage = YES; + } + + UIImage *image = [aDecoder decodeObjectForKey:@"YYAnimatedImage"]; + UIImage *highlightedImage = [aDecoder decodeObjectForKey:@"YYHighlightedAnimatedImage"]; + if (image) { + self.image = image; + [self setImage:image withType:YYAnimatedImageTypeImage]; + } + if (highlightedImage) { + self.highlightedImage = highlightedImage; + [self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:_runloopMode forKey:@"runloopMode"]; + [aCoder encodeBool:_autoPlayAnimatedImage forKey:@"autoPlayAnimatedImage"]; + + BOOL ani, multi; + ani = [self.image conformsToProtocol:@protocol(YYAnimatedImage)]; + multi = (ani && ((UIImage *)self.image).animatedImageFrameCount > 1); + if (multi) [aCoder encodeObject:self.image forKey:@"YYAnimatedImage"]; + + ani = [self.highlightedImage conformsToProtocol:@protocol(YYAnimatedImage)]; + multi = (ani && ((UIImage *)self.highlightedImage).animatedImageFrameCount > 1); + if (multi) [aCoder encodeObject:self.highlightedImage forKey:@"YYHighlightedAnimatedImage"]; +} + +@end diff --git a/Example/Pods/YYImage/YYImage/YYFrameImage.h b/Example/Pods/YYImage/YYImage/YYFrameImage.h new file mode 100644 index 00000000..5795cc51 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYFrameImage.h @@ -0,0 +1,109 @@ +// +// YYFrameImage.h +// YYImage +// +// Created by ibireme on 14/12/9. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#elif __has_include() +#import +#else +#import "YYAnimatedImageView.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + An image to display frame-based animation. + + @discussion It is a fully compatible `UIImage` subclass. + It only support system image format such as png and jpeg. + The animation can be played by YYAnimatedImageView. + + Sample Code: + + NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"]; + NSArray *times = @[@0.1, @0.2, @0.1]; + YYFrameImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES]; + YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image]; + [view addSubView:imageView]; + */ +@interface YYFrameImage : UIImage + +/** + Create a frame animated image from files. + + @param paths An array of NSString objects, contains the full or + partial path to each image file. + e.g. @[@"/ani/1.png",@"/ani/2.png",@"/ani/3.png"] + + @param oneFrameDuration The duration (in seconds) per frame. + + @param loopCount The animation loop count, 0 means infinite. + + @return An initialized YYFrameImage object, or nil when an error occurs. + */ +- (nullable instancetype)initWithImagePaths:(NSArray *)paths + oneFrameDuration:(NSTimeInterval)oneFrameDuration + loopCount:(NSUInteger)loopCount; + +/** + Create a frame animated image from files. + + @param paths An array of NSString objects, contains the full or + partial path to each image file. + e.g. @[@"/ani/frame1.png",@"/ani/frame2.png",@"/ani/frame3.png"] + + @param frameDurations An array of NSNumber objects, contains the duration (in seconds) per frame. + e.g. @[@0.1, @0.2, @0.3]; + + @param loopCount The animation loop count, 0 means infinite. + + @return An initialized YYFrameImage object, or nil when an error occurs. + */ +- (nullable instancetype)initWithImagePaths:(NSArray *)paths + frameDurations:(NSArray *)frameDurations + loopCount:(NSUInteger)loopCount; + +/** + Create a frame animated image from an array of data. + + @param dataArray An array of NSData objects. + + @param oneFrameDuration The duration (in seconds) per frame. + + @param loopCount The animation loop count, 0 means infinite. + + @return An initialized YYFrameImage object, or nil when an error occurs. + */ +- (nullable instancetype)initWithImageDataArray:(NSArray *)dataArray + oneFrameDuration:(NSTimeInterval)oneFrameDuration + loopCount:(NSUInteger)loopCount; + +/** + Create a frame animated image from an array of data. + + @param dataArray An array of NSData objects. + + @param frameDurations An array of NSNumber objects, contains the duration (in seconds) per frame. + e.g. @[@0.1, @0.2, @0.3]; + + @param loopCount The animation loop count, 0 means infinite. + + @return An initialized YYFrameImage object, or nil when an error occurs. + */ +- (nullable instancetype)initWithImageDataArray:(NSArray *)dataArray + frameDurations:(NSArray *)frameDurations + loopCount:(NSUInteger)loopCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYImage/YYImage/YYFrameImage.m b/Example/Pods/YYImage/YYImage/YYFrameImage.m new file mode 100644 index 00000000..93efe887 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYFrameImage.m @@ -0,0 +1,150 @@ +// +// YYFrameImage.m +// YYImage +// +// Created by ibireme on 14/12/9. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYFrameImage.h" +#import "YYImageCoder.h" + + +/** + Return the path scale. + + e.g. + + + + + + + + +
Path Scale
"icon.png" 1
"icon@2x.png" 2
"icon@2.5x.png" 2.5
"icon@2x" 1
"icon@2x..png" 1
"icon@2x.png/" 1
+ */ +static CGFloat _NSStringPathScale(NSString *string) { + if (string.length == 0 || [string hasSuffix:@"/"]) return 1; + NSString *name = string.stringByDeletingPathExtension; + __block CGFloat scale = 1; + + NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + if (result.range.location >= 3) { + scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue; + } + }]; + + return scale; +} + + + +@implementation YYFrameImage { + NSUInteger _loopCount; + NSUInteger _oneFrameBytes; + NSArray *_imagePaths; + NSArray *_imageDatas; + NSArray *_frameDurations; +} + +- (instancetype)initWithImagePaths:(NSArray *)paths oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount { + NSMutableArray *durations = [NSMutableArray new]; + for (int i = 0, max = (int)paths.count; i < max; i++) { + [durations addObject:@(oneFrameDuration)]; + } + return [self initWithImagePaths:paths frameDurations:durations loopCount:loopCount]; +} + +- (instancetype)initWithImagePaths:(NSArray *)paths frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount { + if (paths.count == 0) return nil; + if (paths.count != frameDurations.count) return nil; + + NSString *firstPath = paths[0]; + NSData *firstData = [NSData dataWithContentsOfFile:firstPath]; + CGFloat scale = _NSStringPathScale(firstPath); + UIImage *firstCG = [[[UIImage alloc] initWithData:firstData] yy_imageByDecoded]; + self = [self initWithCGImage:firstCG.CGImage scale:scale orientation:UIImageOrientationUp]; + if (!self) return nil; + long frameByte = CGImageGetBytesPerRow(firstCG.CGImage) * CGImageGetHeight(firstCG.CGImage); + _oneFrameBytes = (NSUInteger)frameByte; + _imagePaths = paths.copy; + _frameDurations = frameDurations.copy; + _loopCount = loopCount; + + return self; +} + +- (instancetype)initWithImageDataArray:(NSArray *)dataArray oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount { + NSMutableArray *durations = [NSMutableArray new]; + for (int i = 0, max = (int)dataArray.count; i < max; i++) { + [durations addObject:@(oneFrameDuration)]; + } + return [self initWithImageDataArray:dataArray frameDurations:durations loopCount:loopCount]; +} + +- (instancetype)initWithImageDataArray:(NSArray *)dataArray frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount { + if (dataArray.count == 0) return nil; + if (dataArray.count != frameDurations.count) return nil; + + NSData *firstData = dataArray[0]; + CGFloat scale = [UIScreen mainScreen].scale; + UIImage *firstCG = [[[UIImage alloc] initWithData:firstData] yy_imageByDecoded]; + self = [self initWithCGImage:firstCG.CGImage scale:scale orientation:UIImageOrientationUp]; + if (!self) return nil; + long frameByte = CGImageGetBytesPerRow(firstCG.CGImage) * CGImageGetHeight(firstCG.CGImage); + _oneFrameBytes = (NSUInteger)frameByte; + _imageDatas = dataArray.copy; + _frameDurations = frameDurations.copy; + _loopCount = loopCount; + + return self; +} + +#pragma mark - YYAnimtedImage + +- (NSUInteger)animatedImageFrameCount { + if (_imagePaths) { + return _imagePaths.count; + } else if (_imageDatas) { + return _imageDatas.count; + } else { + return 1; + } +} + +- (NSUInteger)animatedImageLoopCount { + return _loopCount; +} + +- (NSUInteger)animatedImageBytesPerFrame { + return _oneFrameBytes; +} + +- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { + if (_imagePaths) { + if (index >= _imagePaths.count) return nil; + NSString *path = _imagePaths[index]; + CGFloat scale = _NSStringPathScale(path); + NSData *data = [NSData dataWithContentsOfFile:path]; + return [[UIImage imageWithData:data scale:scale] yy_imageByDecoded]; + } else if (_imageDatas) { + if (index >= _imageDatas.count) return nil; + NSData *data = _imageDatas[index]; + return [[UIImage imageWithData:data scale:[UIScreen mainScreen].scale] yy_imageByDecoded]; + } else { + return index == 0 ? self : nil; + } +} + +- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index { + if (index >= _frameDurations.count) return 0; + NSNumber *num = _frameDurations[index]; + return [num doubleValue]; +} + +@end diff --git a/Example/Pods/YYImage/YYImage/YYImage.h b/Example/Pods/YYImage/YYImage/YYImage.h new file mode 100644 index 00000000..03610260 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYImage.h @@ -0,0 +1,92 @@ +// +// YYImage.h +// YYImage +// +// Created by ibireme on 14/10/20. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +FOUNDATION_EXPORT double YYImageVersionNumber; +FOUNDATION_EXPORT const unsigned char YYImageVersionString[]; +#import +#import +#import +#import +#elif __has_include() +#import +#import +#import +#import +#else +#import "YYFrameImage.h" +#import "YYSpriteSheetImage.h" +#import "YYImageCoder.h" +#import "YYAnimatedImageView.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + + +/** + A YYImage object is a high-level way to display animated image data. + + @discussion It is a fully compatible `UIImage` subclass. It extends the UIImage + to support animated WebP, APNG and GIF format image data decoding. It also + support NSCoding protocol to archive and unarchive multi-frame image data. + + If the image is created from multi-frame image data, and you want to play the + animation, try replace UIImageView with `YYAnimatedImageView`. + + Sample Code: + + // animation@3x.webp + YYImage *image = [YYImage imageNamed:@"animation.webp"]; + YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image]; + [view addSubView:imageView]; + + */ +@interface YYImage : UIImage + ++ (nullable YYImage *)imageNamed:(NSString *)name; // no cache! ++ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path; ++ (nullable YYImage *)imageWithData:(NSData *)data; ++ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale; + +/** + If the image is created from data or file, then the value indicates the data type. + */ +@property (nonatomic, readonly) YYImageType animatedImageType; + +/** + If the image is created from animated image data (multi-frame GIF/APNG/WebP), + this property stores the original image data. + */ +@property (nullable, nonatomic, readonly) NSData *animatedImageData; + +/** + The total memory usage (in bytes) if all frame images was loaded into memory. + The value is 0 if the image is not created from a multi-frame image data. + */ +@property (nonatomic, readonly) NSUInteger animatedImageMemorySize; + +/** + Preload all frame image to memory. + + @discussion Set this property to `YES` will block the calling thread to decode + all animation frame image to memory, set to `NO` will release the preloaded frames. + If the image is shared by lots of image views (such as emoticon), preload all + frames will reduce the CPU cost. + + See `animatedImageMemorySize` for memory cost. + */ +@property (nonatomic) BOOL preloadAllAnimatedImageFrames; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYImage/YYImage/YYImage.m b/Example/Pods/YYImage/YYImage/YYImage.m new file mode 100644 index 00000000..aaea9398 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYImage.m @@ -0,0 +1,254 @@ +// +// YYImage.m +// YYImage +// +// Created by ibireme on 14/10/20. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYImage.h" + +/** + An array of NSNumber objects, shows the best order for path scale search. + e.g. iPhone3GS:@[@1,@2,@3] iPhone5:@[@2,@3,@1] iPhone6 Plus:@[@3,@2,@1] + */ +static NSArray *_NSBundlePreferredScales() { + static NSArray *scales; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + CGFloat screenScale = [UIScreen mainScreen].scale; + if (screenScale <= 1) { + scales = @[@1,@2,@3]; + } else if (screenScale <= 2) { + scales = @[@2,@3,@1]; + } else { + scales = @[@3,@2,@1]; + } + }); + return scales; +} + +/** + Add scale modifier to the file name (without path extension), + From @"name" to @"name@2x". + + e.g. + + + + + + + +
Before After(scale:2)
"icon" "icon@2x"
"icon " "icon @2x"
"icon.top" "icon.top@2x"
"/p/name" "/p/name@2x"
"/path/" "/path/"
+ + @param scale Resource scale. + @return String by add scale modifier, or just return if it's not end with file name. + */ +static NSString *_NSStringByAppendingNameScale(NSString *string, CGFloat scale) { + if (!string) return nil; + if (fabs(scale - 1) <= __FLT_EPSILON__ || string.length == 0 || [string hasSuffix:@"/"]) return string.copy; + return [string stringByAppendingFormat:@"@%@x", @(scale)]; +} + +/** + Return the path scale. + + e.g. + + + + + + + + +
Path Scale
"icon.png" 1
"icon@2x.png" 2
"icon@2.5x.png" 2.5
"icon@2x" 1
"icon@2x..png" 1
"icon@2x.png/" 1
+ */ +static CGFloat _NSStringPathScale(NSString *string) { + if (string.length == 0 || [string hasSuffix:@"/"]) return 1; + NSString *name = string.stringByDeletingPathExtension; + __block CGFloat scale = 1; + + NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil]; + [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { + if (result.range.location >= 3) { + scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue; + } + }]; + + return scale; +} + + +@implementation YYImage { + YYImageDecoder *_decoder; + NSArray *_preloadedFrames; + dispatch_semaphore_t _preloadedLock; + NSUInteger _bytesPerFrame; +} + ++ (YYImage *)imageNamed:(NSString *)name { + if (name.length == 0) return nil; + if ([name hasSuffix:@"/"]) return nil; + + NSString *res = name.stringByDeletingPathExtension; + NSString *ext = name.pathExtension; + NSString *path = nil; + CGFloat scale = 1; + + // If no extension, guess by system supported (same as UIImage). + NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; + NSArray *scales = _NSBundlePreferredScales(); + for (int s = 0; s < scales.count; s++) { + scale = ((NSNumber *)scales[s]).floatValue; + NSString *scaledName = _NSStringByAppendingNameScale(res, scale); + for (NSString *e in exts) { + path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e]; + if (path) break; + } + if (path) break; + } + if (path.length == 0) return nil; + + NSData *data = [NSData dataWithContentsOfFile:path]; + if (data.length == 0) return nil; + + return [[self alloc] initWithData:data scale:scale]; +} + ++ (YYImage *)imageWithContentsOfFile:(NSString *)path { + return [[self alloc] initWithContentsOfFile:path]; +} + ++ (YYImage *)imageWithData:(NSData *)data { + return [[self alloc] initWithData:data]; +} + ++ (YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale { + return [[self alloc] initWithData:data scale:scale]; +} + +- (instancetype)initWithContentsOfFile:(NSString *)path { + NSData *data = [NSData dataWithContentsOfFile:path]; + return [self initWithData:data scale:_NSStringPathScale(path)]; +} + +- (instancetype)initWithData:(NSData *)data { + return [self initWithData:data scale:1]; +} + +- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale { + if (data.length == 0) return nil; + if (scale <= 0) scale = [UIScreen mainScreen].scale; + _preloadedLock = dispatch_semaphore_create(1); + @autoreleasepool { + YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale]; + YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES]; + UIImage *image = frame.image; + if (!image) return nil; + self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation]; + if (!self) return nil; + _animatedImageType = decoder.type; + if (decoder.frameCount > 1) { + _decoder = decoder; + _bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage); + _animatedImageMemorySize = _bytesPerFrame * decoder.frameCount; + } + self.yy_isDecodedForDisplay = YES; + } + return self; +} + +- (NSData *)animatedImageData { + return _decoder.data; +} + +- (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames { + if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) { + if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) { + NSMutableArray *frames = [NSMutableArray new]; + for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) { + UIImage *img = [self animatedImageFrameAtIndex:i]; + if (img) { + [frames addObject:img]; + } else { + [frames addObject:[NSNull null]]; + } + } + dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER); + _preloadedFrames = frames; + dispatch_semaphore_signal(_preloadedLock); + } else { + dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER); + _preloadedFrames = nil; + dispatch_semaphore_signal(_preloadedLock); + } + } +} + +#pragma mark - protocol NSCoding + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + NSNumber *scale = [aDecoder decodeObjectForKey:@"YYImageScale"]; + NSData *data = [aDecoder decodeObjectForKey:@"YYImageData"]; + if (data.length) { + self = [self initWithData:data scale:scale.doubleValue]; + } else { + self = [super initWithCoder:aDecoder]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + if (_decoder.data.length) { + [aCoder encodeObject:@(self.scale) forKey:@"YYImageScale"]; + [aCoder encodeObject:_decoder.data forKey:@"YYImageData"]; + } else { + [super encodeWithCoder:aCoder]; // Apple use UIImagePNGRepresentation() to encode UIImage. + } +} + +#pragma mark - protocol YYAnimatedImage + +- (NSUInteger)animatedImageFrameCount { + return _decoder.frameCount; +} + +- (NSUInteger)animatedImageLoopCount { + return _decoder.loopCount; +} + +- (NSUInteger)animatedImageBytesPerFrame { + return _bytesPerFrame; +} + +- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { + if (index >= _decoder.frameCount) return nil; + dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER); + UIImage *image = _preloadedFrames[index]; + dispatch_semaphore_signal(_preloadedLock); + if (image) return image == (id)[NSNull null] ? nil : image; + return [_decoder frameAtIndex:index decodeForDisplay:YES].image; +} + +- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index { + NSTimeInterval duration = [_decoder frameDurationAtIndex:index]; + + /* + http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp + Many annoying ads specify a 0 duration to make an image flash as quickly as + possible. We follow Safari and Firefox's behavior and use a duration of 100 ms + for any frames that specify a duration of <= 10 ms. + See and for more information. + + See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser. + */ + if (duration < 0.011f) return 0.100f; + return duration; +} + +@end diff --git a/Example/Pods/YYImage/YYImage/YYImageCoder.h b/Example/Pods/YYImage/YYImage/YYImageCoder.h new file mode 100644 index 00000000..61a931c4 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYImageCoder.h @@ -0,0 +1,505 @@ +// +// YYImageCoder.h +// YYImage +// +// Created by ibireme on 15/5/13. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Image file type. + */ +typedef NS_ENUM(NSUInteger, YYImageType) { + YYImageTypeUnknown = 0, ///< unknown + YYImageTypeJPEG, ///< jpeg, jpg + YYImageTypeJPEG2000, ///< jp2 + YYImageTypeTIFF, ///< tiff, tif + YYImageTypeBMP, ///< bmp + YYImageTypeICO, ///< ico + YYImageTypeICNS, ///< icns + YYImageTypeGIF, ///< gif + YYImageTypePNG, ///< png + YYImageTypeWebP, ///< webp + YYImageTypeOther, ///< other image format +}; + + +/** + Dispose method specifies how the area used by the current frame is to be treated + before rendering the next frame on the canvas. + */ +typedef NS_ENUM(NSUInteger, YYImageDisposeMethod) { + + /** + No disposal is done on this frame before rendering the next; the contents + of the canvas are left as is. + */ + YYImageDisposeNone = 0, + + /** + The frame's region of the canvas is to be cleared to fully transparent black + before rendering the next frame. + */ + YYImageDisposeBackground, + + /** + The frame's region of the canvas is to be reverted to the previous contents + before rendering the next frame. + */ + YYImageDisposePrevious, +}; + +/** + Blend operation specifies how transparent pixels of the current frame are + blended with those of the previous canvas. + */ +typedef NS_ENUM(NSUInteger, YYImageBlendOperation) { + + /** + All color components of the frame, including alpha, overwrite the current + contents of the frame's canvas region. + */ + YYImageBlendNone = 0, + + /** + The frame should be composited onto the output buffer based on its alpha. + */ + YYImageBlendOver, +}; + +/** + An image frame object. + */ +@interface YYImageFrame : NSObject +@property (nonatomic) NSUInteger index; ///< Frame index (zero based) +@property (nonatomic) NSUInteger width; ///< Frame width +@property (nonatomic) NSUInteger height; ///< Frame height +@property (nonatomic) NSUInteger offsetX; ///< Frame origin.x in canvas (left-bottom based) +@property (nonatomic) NSUInteger offsetY; ///< Frame origin.y in canvas (left-bottom based) +@property (nonatomic) NSTimeInterval duration; ///< Frame duration in seconds +@property (nonatomic) YYImageDisposeMethod dispose; ///< Frame dispose method. +@property (nonatomic) YYImageBlendOperation blend; ///< Frame blend operation. +@property (nullable, nonatomic, strong) UIImage *image; ///< The image. ++ (instancetype)frameWithImage:(UIImage *)image; +@end + + +#pragma mark - Decoder + +/** + An image decoder to decode image data. + + @discussion This class supports decoding animated WebP, APNG, GIF and system + image format such as PNG, JPG, JP2, BMP, TIFF, PIC, ICNS and ICO. It can be used + to decode complete image data, or to decode incremental image data during image + download. This class is thread-safe. + + Example: + + // Decode single image: + NSData *data = [NSData dataWithContentOfFile:@"/tmp/image.webp"]; + YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0]; + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + + // Decode image during download: + NSMutableData *data = [NSMutableData new]; + YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0]; + while(newDataArrived) { + [data appendData:newData]; + [decoder updateData:data final:NO]; + if (decoder.frameCount > 0) { + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + // progressive display... + } + } + [decoder updateData:data final:YES]; + UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image; + // final display... + + */ +@interface YYImageDecoder : NSObject + +@property (nullable, nonatomic, readonly) NSData *data; ///< Image data. +@property (nonatomic, readonly) YYImageType type; ///< Image data type. +@property (nonatomic, readonly) CGFloat scale; ///< Image scale. +@property (nonatomic, readonly) NSUInteger frameCount; ///< Image frame count. +@property (nonatomic, readonly) NSUInteger loopCount; ///< Image loop count, 0 means infinite. +@property (nonatomic, readonly) NSUInteger width; ///< Image canvas width. +@property (nonatomic, readonly) NSUInteger height; ///< Image canvas height. +@property (nonatomic, readonly, getter=isFinalized) BOOL finalized; + +/** + Creates an image decoder. + + @param scale Image's scale. + @return An image decoder. + */ +- (instancetype)initWithScale:(CGFloat)scale NS_DESIGNATED_INITIALIZER; + +/** + Updates the incremental image with new data. + + @discussion You can use this method to decode progressive/interlaced/baseline + image when you do not have the complete image data. The `data` was retained by + decoder, you should not modify the data in other thread during decoding. + + @param data The data to add to the image decoder. Each time you call this + function, the 'data' parameter must contain all of the image file data + accumulated so far. + + @param final A value that specifies whether the data is the final set. + Pass YES if it is, NO otherwise. When the data is already finalized, you can + not update the data anymore. + + @return Whether succeed. + */ +- (BOOL)updateData:(nullable NSData *)data final:(BOOL)final; + +/** + Convenience method to create a decoder with specified data. + @param data Image data. + @param scale Image's scale. + @return A new decoder, or nil if an error occurs. + */ ++ (nullable instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale; + +/** + Decodes and returns a frame from a specified index. + @param index Frame image index (zero-based). + @param decodeForDisplay Whether decode the image to memory bitmap for display. + If NO, it will try to returns the original frame data without blend. + @return A new frame with image, or nil if an error occurs. + */ +- (nullable YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay; + +/** + Returns the frame duration from a specified index. + @param index Frame image (zero-based). + @return Duration in seconds. + */ +- (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index; + +/** + Returns the frame's properties. See "CGImageProperties.h" in ImageIO.framework + for more information. + + @param index Frame image index (zero-based). + @return The ImageIO frame property. + */ +- (nullable NSDictionary *)framePropertiesAtIndex:(NSUInteger)index; + +/** + Returns the image's properties. See "CGImageProperties.h" in ImageIO.framework + for more information. + */ +- (nullable NSDictionary *)imageProperties; + +@end + + + +#pragma mark - Encoder + +/** + An image encoder to encode image to data. + + @discussion It supports encoding single frame image with the type defined in YYImageType. + It also supports encoding multi-frame image with GIF, APNG and WebP. + + Example: + + YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG]; + jpegEncoder.quality = 0.9; + [jpegEncoder addImage:image duration:0]; + NSData jpegData = [jpegEncoder encode]; + + YYImageEncoder *gifEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeGIF]; + gifEncoder.loopCount = 5; + [gifEncoder addImage:image0 duration:0.1]; + [gifEncoder addImage:image1 duration:0.15]; + [gifEncoder addImage:image2 duration:0.2]; + NSData gifData = [gifEncoder encode]; + + @warning It just pack the images together when encoding multi-frame image. If you + want to reduce the image file size, try imagemagick/ffmpeg for GIF and WebP, + and apngasm for APNG. + */ +@interface YYImageEncoder : NSObject + +@property (nonatomic, readonly) YYImageType type; ///< Image type. +@property (nonatomic) NSUInteger loopCount; ///< Loop count, 0 means infinit, only available for GIF/APNG/WebP. +@property (nonatomic) BOOL lossless; ///< Lossless, only available for WebP. +@property (nonatomic) CGFloat quality; ///< Compress quality, 0.0~1.0, only available for JPG/JP2/WebP. + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +/** + Create an image encoder with a specified type. + @param type Image type. + @return A new encoder, or nil if an error occurs. + */ +- (nullable instancetype)initWithType:(YYImageType)type NS_DESIGNATED_INITIALIZER; + +/** + Add an image to encoder. + @param image Image. + @param duration Image duration for animation. Pass 0 to ignore this parameter. + */ +- (void)addImage:(UIImage *)image duration:(NSTimeInterval)duration; + +/** + Add an image with image data to encoder. + @param data Image data. + @param duration Image duration for animation. Pass 0 to ignore this parameter. + */ +- (void)addImageWithData:(NSData *)data duration:(NSTimeInterval)duration; + +/** + Add an image from a file path to encoder. + @param image Image file path. + @param duration Image duration for animation. Pass 0 to ignore this parameter. + */ +- (void)addImageWithFile:(NSString *)path duration:(NSTimeInterval)duration; + +/** + Encodes the image and returns the image data. + @return The image data, or nil if an error occurs. + */ +- (nullable NSData *)encode; + +/** + Encodes the image to a file. + @param path The file path (overwrite if exist). + @return Whether succeed. + */ +- (BOOL)encodeToFile:(NSString *)path; + +/** + Convenience method to encode single frame image. + @param image The image. + @param type The destination image type. + @param quality Image quality, 0.0~1.0. + @return The image data, or nil if an error occurs. + */ ++ (nullable NSData *)encodeImage:(UIImage *)image type:(YYImageType)type quality:(CGFloat)quality; + +/** + Convenience method to encode image from a decoder. + @param decoder The image decoder. + @param type The destination image type; + @param quality Image quality, 0.0~1.0. + @return The image data, or nil if an error occurs. + */ ++ (nullable NSData *)encodeImageWithDecoder:(YYImageDecoder *)decoder type:(YYImageType)type quality:(CGFloat)quality; + +@end + + +#pragma mark - UIImage + +@interface UIImage (YYImageCoder) + +/** + Decompress this image to bitmap, so when the image is displayed on screen, + the main thread won't be blocked by additional decode. If the image has already + been decoded or unable to decode, it just returns itself. + + @return an image decoded, or just return itself if no needed. + @see yy_isDecodedForDisplay + */ +- (instancetype)yy_imageByDecoded; + +/** + Wherher the image can be display on screen without additional decoding. + @warning It just a hint for your code, change it has no other effect. + */ +@property (nonatomic) BOOL yy_isDecodedForDisplay; + +/** + Saves this image to iOS Photos Album. + + @discussion This method attempts to save the original data to album if the + image is created from an animated GIF/APNG, otherwise, it will save the image + as JPEG or PNG (based on the alpha information). + + @param completionBlock The block invoked (in main thread) after the save operation completes. + assetURL: An URL that identifies the saved image file. If the image is not saved, assetURL is nil. + error: If the image is not saved, an error object that describes the reason for failure, otherwise nil. + */ +- (void)yy_saveToAlbumWithCompletionBlock:(nullable void(^)(NSURL * _Nullable assetURL, NSError * _Nullable error))completionBlock; + +/** + Return a 'best' data representation for this image. + + @discussion The convertion based on these rule: + 1. If the image is created from an animated GIF/APNG/WebP, it returns the original data. + 2. It returns PNG or JPEG(0.9) representation based on the alpha information. + + @return Image data, or nil if an error occurs. + */ +- (nullable NSData *)yy_imageDataRepresentation; + +@end + + + +#pragma mark - Helper + +/// Detect a data's image type by reading the data's header 16 bytes (very fast). +CG_EXTERN YYImageType YYImageDetectType(CFDataRef data); + +/// Convert YYImageType to UTI (such as kUTTypeJPEG). +CG_EXTERN CFStringRef _Nullable YYImageTypeToUTType(YYImageType type); + +/// Convert UTI (such as kUTTypeJPEG) to YYImageType. +CG_EXTERN YYImageType YYImageTypeFromUTType(CFStringRef uti); + +/// Get image type's file extension (such as @"jpg"). +CG_EXTERN NSString *_Nullable YYImageTypeGetExtension(YYImageType type); + + + +/// Returns the shared DeviceRGB color space. +CG_EXTERN CGColorSpaceRef YYCGColorSpaceGetDeviceRGB(); + +/// Returns the shared DeviceGray color space. +CG_EXTERN CGColorSpaceRef YYCGColorSpaceGetDeviceGray(); + +/// Returns whether a color space is DeviceRGB. +CG_EXTERN BOOL YYCGColorSpaceIsDeviceRGB(CGColorSpaceRef space); + +/// Returns whether a color space is DeviceGray. +CG_EXTERN BOOL YYCGColorSpaceIsDeviceGray(CGColorSpaceRef space); + + + +/// Convert EXIF orientation value to UIImageOrientation. +CG_EXTERN UIImageOrientation YYUIImageOrientationFromEXIFValue(NSInteger value); + +/// Convert UIImageOrientation to EXIF orientation value. +CG_EXTERN NSInteger YYUIImageOrientationToEXIFValue(UIImageOrientation orientation); + + + +/** + Create a decoded image. + + @discussion If the source image is created from a compressed image data (such as + PNG or JPEG), you can use this method to decode the image. After decoded, you can + access the decoded bytes with CGImageGetDataProvider() and CGDataProviderCopyData() + without additional decode process. If the image has already decoded, this method + just copy the decoded bytes to the new image. + + @param imageRef The source image. + @param decodeForDisplay If YES, this method will decode the image and convert + it to BGRA8888 (premultiplied) or BGRX8888 format for CALayer display. + + @return A decoded image, or NULL if an error occurs. + */ +CG_EXTERN CGImageRef _Nullable YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay); + +/** + Create an image copy with an orientation. + + @param imageRef Source image + @param orientation Image orientation which will applied to the image. + @param destBitmapInfo Destimation image bitmap, only support 32bit format (such as ARGB8888). + @return A new image, or NULL if an error occurs. + */ +CG_EXTERN CGImageRef _Nullable YYCGImageCreateCopyWithOrientation(CGImageRef imageRef, + UIImageOrientation orientation, + CGBitmapInfo destBitmapInfo); + +/** + Create an image copy with CGAffineTransform. + + @param imageRef Source image. + @param transform Transform applied to image (left-bottom based coordinate system). + @param destSize Destination image size + @param destBitmapInfo Destimation image bitmap, only support 32bit format (such as ARGB8888). + @return A new image, or NULL if an error occurs. + */ +CG_EXTERN CGImageRef _Nullable YYCGImageCreateAffineTransformCopy(CGImageRef imageRef, + CGAffineTransform transform, + CGSize destSize, + CGBitmapInfo destBitmapInfo); + +/** + Encode an image to data with CGImageDestination. + + @param imageRef The image. + @param type The image destination data type. + @param quality The quality (0.0~1.0) + @return A new image data, or nil if an error occurs. + */ +CG_EXTERN CFDataRef _Nullable YYCGImageCreateEncodedData(CGImageRef imageRef, YYImageType type, CGFloat quality); + + +/** + Whether WebP is available in YYImage. + */ +CG_EXTERN BOOL YYImageWebPAvailable(); + +/** + Get a webp image frame count; + + @param webpData WebP data. + @return Image frame count, or 0 if an error occurs. + */ +CG_EXTERN NSUInteger YYImageGetWebPFrameCount(CFDataRef webpData); + +/** + Decode an image from WebP data, returns NULL if an error occurs. + + @param webpData The WebP data. + @param decodeForDisplay If YES, this method will decode the image and convert it + to BGRA8888 (premultiplied) format for CALayer display. + @param useThreads YES to enable multi-thread decode. + (speed up, but cost more CPU) + @param bypassFiltering YES to skip the in-loop filtering. + (speed up, but may lose some smooth) + @param noFancyUpsampling YES to use faster pointwise upsampler. + (speed down, and may lose some details). + @return The decoded image, or NULL if an error occurs. + */ +CG_EXTERN CGImageRef _Nullable YYCGImageCreateWithWebPData(CFDataRef webpData, + BOOL decodeForDisplay, + BOOL useThreads, + BOOL bypassFiltering, + BOOL noFancyUpsampling); + +typedef NS_ENUM(NSUInteger, YYImagePreset) { + YYImagePresetDefault = 0, ///< default preset. + YYImagePresetPicture, ///< digital picture, like portrait, inner shot + YYImagePresetPhoto, ///< outdoor photograph, with natural lighting + YYImagePresetDrawing, ///< hand or line drawing, with high-contrast details + YYImagePresetIcon, ///< small-sized colorful images + YYImagePresetText ///< text-like +}; + +/** + Encode a CGImage to WebP data + + @param imageRef image + @param lossless YES=lossless (similar to PNG), NO=lossy (similar to JPEG) + @param quality 0.0~1.0 (0=smallest file, 1.0=biggest file) + For lossless image, try the value near 1.0; for lossy, try the value near 0.8. + @param compressLevel 0~6 (0=fast, 6=slower-better). Default is 4. + @param preset Preset for different image type, default is YYImagePresetDefault. + @return WebP data, or nil if an error occurs. + */ +CG_EXTERN CFDataRef _Nullable YYCGImageCreateEncodedWebPData(CGImageRef imageRef, + BOOL lossless, + CGFloat quality, + int compressLevel, + YYImagePreset preset); + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYImage/YYImage/YYImageCoder.m b/Example/Pods/YYImage/YYImage/YYImageCoder.m new file mode 100644 index 00000000..66cf46ef --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYImageCoder.m @@ -0,0 +1,2870 @@ +// +// YYImageCoder.m +// YYImage +// +// Created by ibireme on 15/5/13. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYImageCoder.h" +#import "YYImage.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import + + + +#ifndef YYIMAGE_WEBP_ENABLED +#if __has_include() && __has_include() && \ + __has_include() && __has_include() +#define YYIMAGE_WEBP_ENABLED 1 +#import +#import +#import +#import +#elif __has_include("webp/decode.h") && __has_include("webp/encode.h") && \ + __has_include("webp/demux.h") && __has_include("webp/mux.h") +#define YYIMAGE_WEBP_ENABLED 1 +#import "webp/decode.h" +#import "webp/encode.h" +#import "webp/demux.h" +#import "webp/mux.h" +#else +#define YYIMAGE_WEBP_ENABLED 0 +#endif +#endif + + + + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Utility (for little endian platform) + +#define YY_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1))) +#define YY_TWO_CC(c1,c2) ((uint16_t)(((c2) << 8) | (c1))) + +static inline uint16_t yy_swap_endian_uint16(uint16_t value) { + return + (uint16_t) ((value & 0x00FF) << 8) | + (uint16_t) ((value & 0xFF00) >> 8) ; +} + +static inline uint32_t yy_swap_endian_uint32(uint32_t value) { + return + (uint32_t)((value & 0x000000FFU) << 24) | + (uint32_t)((value & 0x0000FF00U) << 8) | + (uint32_t)((value & 0x00FF0000U) >> 8) | + (uint32_t)((value & 0xFF000000U) >> 24) ; +} + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - APNG + +/* + PNG spec: http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html + APNG spec: https://wiki.mozilla.org/APNG_Specification + + =============================================================================== + PNG format: + header (8): 89 50 4e 47 0d 0a 1a 0a + chunk, chunk, chunk, ... + + =============================================================================== + chunk format: + length (4): uint32_t big endian + fourcc (4): chunk type code + data (length): data + crc32 (4): uint32_t big endian crc32(fourcc + data) + + =============================================================================== + PNG chunk define: + + IHDR (Image Header) required, must appear first, 13 bytes + width (4) pixel count, should not be zero + height (4) pixel count, should not be zero + bit depth (1) expected: 1, 2, 4, 8, 16 + color type (1) 1<<0 (palette used), 1<<1 (color used), 1<<2 (alpha channel used) + compression method (1) 0 (deflate/inflate) + filter method (1) 0 (adaptive filtering with five basic filter types) + interlace method (1) 0 (no interlace) or 1 (Adam7 interlace) + + IDAT (Image Data) required, must appear consecutively if there's multiple 'IDAT' chunk + + IEND (End) required, must appear last, 0 bytes + + =============================================================================== + APNG chunk define: + + acTL (Animation Control) required, must appear before 'IDAT', 8 bytes + num frames (4) number of frames + num plays (4) number of times to loop, 0 indicates infinite looping + + fcTL (Frame Control) required, must appear before the 'IDAT' or 'fdAT' chunks of the frame to which it applies, 26 bytes + sequence number (4) sequence number of the animation chunk, starting from 0 + width (4) width of the following frame + height (4) height of the following frame + x offset (4) x position at which to render the following frame + y offset (4) y position at which to render the following frame + delay num (2) frame delay fraction numerator + delay den (2) frame delay fraction denominator + dispose op (1) type of frame area disposal to be done after rendering this frame (0:none, 1:background 2:previous) + blend op (1) type of frame area rendering for this frame (0:source, 1:over) + + fdAT (Frame Data) required + sequence number (4) sequence number of the animation chunk + frame data (x) frame data for this frame (same as 'IDAT') + + =============================================================================== + `dispose_op` specifies how the output buffer should be changed at the end of the delay + (before rendering the next frame). + + * NONE: no disposal is done on this frame before rendering the next; the contents + of the output buffer are left as is. + * BACKGROUND: the frame's region of the output buffer is to be cleared to fully + transparent black before rendering the next frame. + * PREVIOUS: the frame's region of the output buffer is to be reverted to the previous + contents before rendering the next frame. + + `blend_op` specifies whether the frame is to be alpha blended into the current output buffer + content, or whether it should completely replace its region in the output buffer. + + * SOURCE: all color components of the frame, including alpha, overwrite the current contents + of the frame's output buffer region. + * OVER: the frame should be composited onto the output buffer based on its alpha, + using a simple OVER operation as described in the "Alpha Channel Processing" section + of the PNG specification + */ + +typedef enum { + YY_PNG_ALPHA_TYPE_PALEETE = 1 << 0, + YY_PNG_ALPHA_TYPE_COLOR = 1 << 1, + YY_PNG_ALPHA_TYPE_ALPHA = 1 << 2, +} yy_png_alpha_type; + +typedef enum { + YY_PNG_DISPOSE_OP_NONE = 0, + YY_PNG_DISPOSE_OP_BACKGROUND = 1, + YY_PNG_DISPOSE_OP_PREVIOUS = 2, +} yy_png_dispose_op; + +typedef enum { + YY_PNG_BLEND_OP_SOURCE = 0, + YY_PNG_BLEND_OP_OVER = 1, +} yy_png_blend_op; + +typedef struct { + uint32_t width; ///< pixel count, should not be zero + uint32_t height; ///< pixel count, should not be zero + uint8_t bit_depth; ///< expected: 1, 2, 4, 8, 16 + uint8_t color_type; ///< see yy_png_alpha_type + uint8_t compression_method; ///< 0 (deflate/inflate) + uint8_t filter_method; ///< 0 (adaptive filtering with five basic filter types) + uint8_t interlace_method; ///< 0 (no interlace) or 1 (Adam7 interlace) +} yy_png_chunk_IHDR; + +typedef struct { + uint32_t sequence_number; ///< sequence number of the animation chunk, starting from 0 + uint32_t width; ///< width of the following frame + uint32_t height; ///< height of the following frame + uint32_t x_offset; ///< x position at which to render the following frame + uint32_t y_offset; ///< y position at which to render the following frame + uint16_t delay_num; ///< frame delay fraction numerator + uint16_t delay_den; ///< frame delay fraction denominator + uint8_t dispose_op; ///< see yy_png_dispose_op + uint8_t blend_op; ///< see yy_png_blend_op +} yy_png_chunk_fcTL; + +typedef struct { + uint32_t offset; ///< chunk offset in PNG data + uint32_t fourcc; ///< chunk fourcc + uint32_t length; ///< chunk data length + uint32_t crc32; ///< chunk crc32 +} yy_png_chunk_info; + +typedef struct { + uint32_t chunk_index; ///< the first `fdAT`/`IDAT` chunk index + uint32_t chunk_num; ///< the `fdAT`/`IDAT` chunk count + uint32_t chunk_size; ///< the `fdAT`/`IDAT` chunk bytes + yy_png_chunk_fcTL frame_control; +} yy_png_frame_info; + +typedef struct { + yy_png_chunk_IHDR header; ///< png header + yy_png_chunk_info *chunks; ///< chunks + uint32_t chunk_num; ///< count of chunks + + yy_png_frame_info *apng_frames; ///< frame info, NULL if not apng + uint32_t apng_frame_num; ///< 0 if not apng + uint32_t apng_loop_num; ///< 0 indicates infinite looping + + uint32_t *apng_shared_chunk_indexs; ///< shared chunk index + uint32_t apng_shared_chunk_num; ///< shared chunk count + uint32_t apng_shared_chunk_size; ///< shared chunk bytes + uint32_t apng_shared_insert_index; ///< shared chunk insert index + bool apng_first_frame_is_cover; ///< the first frame is same as png (cover) +} yy_png_info; + +static void yy_png_chunk_IHDR_read(yy_png_chunk_IHDR *IHDR, const uint8_t *data) { + IHDR->width = yy_swap_endian_uint32(*((uint32_t *)(data))); + IHDR->height = yy_swap_endian_uint32(*((uint32_t *)(data + 4))); + IHDR->bit_depth = data[8]; + IHDR->color_type = data[9]; + IHDR->compression_method = data[10]; + IHDR->filter_method = data[11]; + IHDR->interlace_method = data[12]; +} + +static void yy_png_chunk_IHDR_write(yy_png_chunk_IHDR *IHDR, uint8_t *data) { + *((uint32_t *)(data)) = yy_swap_endian_uint32(IHDR->width); + *((uint32_t *)(data + 4)) = yy_swap_endian_uint32(IHDR->height); + data[8] = IHDR->bit_depth; + data[9] = IHDR->color_type; + data[10] = IHDR->compression_method; + data[11] = IHDR->filter_method; + data[12] = IHDR->interlace_method; +} + +static void yy_png_chunk_fcTL_read(yy_png_chunk_fcTL *fcTL, const uint8_t *data) { + fcTL->sequence_number = yy_swap_endian_uint32(*((uint32_t *)(data))); + fcTL->width = yy_swap_endian_uint32(*((uint32_t *)(data + 4))); + fcTL->height = yy_swap_endian_uint32(*((uint32_t *)(data + 8))); + fcTL->x_offset = yy_swap_endian_uint32(*((uint32_t *)(data + 12))); + fcTL->y_offset = yy_swap_endian_uint32(*((uint32_t *)(data + 16))); + fcTL->delay_num = yy_swap_endian_uint16(*((uint16_t *)(data + 20))); + fcTL->delay_den = yy_swap_endian_uint16(*((uint16_t *)(data + 22))); + fcTL->dispose_op = data[24]; + fcTL->blend_op = data[25]; +} + +static void yy_png_chunk_fcTL_write(yy_png_chunk_fcTL *fcTL, uint8_t *data) { + *((uint32_t *)(data)) = yy_swap_endian_uint32(fcTL->sequence_number); + *((uint32_t *)(data + 4)) = yy_swap_endian_uint32(fcTL->width); + *((uint32_t *)(data + 8)) = yy_swap_endian_uint32(fcTL->height); + *((uint32_t *)(data + 12)) = yy_swap_endian_uint32(fcTL->x_offset); + *((uint32_t *)(data + 16)) = yy_swap_endian_uint32(fcTL->y_offset); + *((uint16_t *)(data + 20)) = yy_swap_endian_uint16(fcTL->delay_num); + *((uint16_t *)(data + 22)) = yy_swap_endian_uint16(fcTL->delay_den); + data[24] = fcTL->dispose_op; + data[25] = fcTL->blend_op; +} + +// convert double value to fraction +static void yy_png_delay_to_fraction(double duration, uint16_t *num, uint16_t *den) { + if (duration >= 0xFF) { + *num = 0xFF; + *den = 1; + } else if (duration <= 1.0 / (double)0xFF) { + *num = 1; + *den = 0xFF; + } else { + // Use continued fraction to calculate the num and den. + long MAX = 10; + double eps = (0.5 / (double)0xFF); + long p[MAX], q[MAX], a[MAX], i, numl = 0, denl = 0; + // The first two convergents are 0/1 and 1/0 + p[0] = 0; q[0] = 1; + p[1] = 1; q[1] = 0; + // The rest of the convergents (and continued fraction) + for (i = 2; i < MAX; i++) { + a[i] = lrint(floor(duration)); + p[i] = a[i] * p[i - 1] + p[i - 2]; + q[i] = a[i] * q[i - 1] + q[i - 2]; + if (p[i] <= 0xFF && q[i] <= 0xFF) { // uint16_t + numl = p[i]; + denl = q[i]; + } else break; + if (fabs(duration - a[i]) < eps) break; + duration = 1.0 / (duration - a[i]); + } + + if (numl != 0 && denl != 0) { + *num = numl; + *den = denl; + } else { + *num = 1; + *den = 100; + } + } +} + +// convert fraction to double value +static double yy_png_delay_to_seconds(uint16_t num, uint16_t den) { + if (den == 0) { + return num / 100.0; + } else { + return (double)num / (double)den; + } +} + +static bool yy_png_validate_animation_chunk_order(yy_png_chunk_info *chunks, /* input */ + uint32_t chunk_num, /* input */ + uint32_t *first_idat_index, /* output */ + bool *first_frame_is_cover /* output */) { + /* + PNG at least contains 3 chunks: IHDR, IDAT, IEND. + `IHDR` must appear first. + `IDAT` must appear consecutively. + `IEND` must appear end. + + APNG must contains one `acTL` and at least one 'fcTL' and `fdAT`. + `fdAT` must appear consecutively. + `fcTL` must appear before `IDAT` or `fdAT`. + */ + if (chunk_num <= 2) return false; + if (chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R')) return false; + if ((chunks + chunk_num - 1)->fourcc != YY_FOUR_CC('I', 'E', 'N', 'D')) return false; + + uint32_t prev_fourcc = 0; + uint32_t IHDR_num = 0; + uint32_t IDAT_num = 0; + uint32_t acTL_num = 0; + uint32_t fcTL_num = 0; + uint32_t first_IDAT = 0; + bool first_frame_cover = false; + for (uint32_t i = 0; i < chunk_num; i++) { + yy_png_chunk_info *chunk = chunks + i; + switch (chunk->fourcc) { + case YY_FOUR_CC('I', 'H', 'D', 'R'): { // png header + if (i != 0) return false; + if (IHDR_num > 0) return false; + IHDR_num++; + } break; + case YY_FOUR_CC('I', 'D', 'A', 'T'): { // png data + if (prev_fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) { + if (IDAT_num == 0) + first_IDAT = i; + else + return false; + } + IDAT_num++; + } break; + case YY_FOUR_CC('a', 'c', 'T', 'L'): { // apng control + if (acTL_num > 0) return false; + acTL_num++; + } break; + case YY_FOUR_CC('f', 'c', 'T', 'L'): { // apng frame control + if (i + 1 == chunk_num) return false; + if ((chunk + 1)->fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') && + (chunk + 1)->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) { + return false; + } + if (fcTL_num == 0) { + if ((chunk + 1)->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) { + first_frame_cover = true; + } + } + fcTL_num++; + } break; + case YY_FOUR_CC('f', 'd', 'A', 'T'): { // apng data + if (prev_fourcc != YY_FOUR_CC('f', 'd', 'A', 'T') && prev_fourcc != YY_FOUR_CC('f', 'c', 'T', 'L')) { + return false; + } + } break; + } + prev_fourcc = chunk->fourcc; + } + if (IHDR_num != 1) return false; + if (IDAT_num == 0) return false; + if (acTL_num != 1) return false; + if (fcTL_num < acTL_num) return false; + *first_idat_index = first_IDAT; + *first_frame_is_cover = first_frame_cover; + return true; +} + +static void yy_png_info_release(yy_png_info *info) { + if (info) { + if (info->chunks) free(info->chunks); + if (info->apng_frames) free(info->apng_frames); + if (info->apng_shared_chunk_indexs) free(info->apng_shared_chunk_indexs); + free(info); + } +} + +/** + Create a png info from a png file. See struct png_info for more information. + + @param data png/apng file data. + @param length the data's length in bytes. + @return A png info object, you may call yy_png_info_release() to release it. + Returns NULL if an error occurs. + */ +static yy_png_info *yy_png_info_create(const uint8_t *data, uint32_t length) { + if (length < 32) return NULL; + if (*((uint32_t *)data) != YY_FOUR_CC(0x89, 0x50, 0x4E, 0x47)) return NULL; + if (*((uint32_t *)(data + 4)) != YY_FOUR_CC(0x0D, 0x0A, 0x1A, 0x0A)) return NULL; + + uint32_t chunk_realloc_num = 16; + yy_png_chunk_info *chunks = malloc(sizeof(yy_png_chunk_info) * chunk_realloc_num); + if (!chunks) return NULL; + + // parse png chunks + uint32_t offset = 8; + uint32_t chunk_num = 0; + uint32_t chunk_capacity = chunk_realloc_num; + uint32_t apng_loop_num = 0; + int32_t apng_sequence_index = -1; + int32_t apng_frame_index = 0; + int32_t apng_frame_number = -1; + bool apng_chunk_error = false; + do { + if (chunk_num >= chunk_capacity) { + yy_png_chunk_info *new_chunks = realloc(chunks, sizeof(yy_png_chunk_info) * (chunk_capacity + chunk_realloc_num)); + if (!new_chunks) { + free(chunks); + return NULL; + } + chunks = new_chunks; + chunk_capacity += chunk_realloc_num; + } + yy_png_chunk_info *chunk = chunks + chunk_num; + const uint8_t *chunk_data = data + offset; + chunk->offset = offset; + chunk->length = yy_swap_endian_uint32(*((uint32_t *)chunk_data)); + if ((uint64_t)chunk->offset + (uint64_t)chunk->length + 12 > length) { + free(chunks); + return NULL; + } + + chunk->fourcc = *((uint32_t *)(chunk_data + 4)); + if ((uint64_t)chunk->offset + 4 + chunk->length + 4 > (uint64_t)length) break; + chunk->crc32 = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8 + chunk->length))); + chunk_num++; + offset += 12 + chunk->length; + + switch (chunk->fourcc) { + case YY_FOUR_CC('a', 'c', 'T', 'L') : { + if (chunk->length == 8) { + apng_frame_number = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8))); + apng_loop_num = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 12))); + } else { + apng_chunk_error = true; + } + } break; + case YY_FOUR_CC('f', 'c', 'T', 'L') : + case YY_FOUR_CC('f', 'd', 'A', 'T') : { + if (chunk->fourcc == YY_FOUR_CC('f', 'c', 'T', 'L')) { + if (chunk->length != 26) { + apng_chunk_error = true; + } else { + apng_frame_index++; + } + } + if (chunk->length > 4) { + uint32_t sequence = yy_swap_endian_uint32(*((uint32_t *)(chunk_data + 8))); + if (apng_sequence_index + 1 == sequence) { + apng_sequence_index++; + } else { + apng_chunk_error = true; + } + } else { + apng_chunk_error = true; + } + } break; + case YY_FOUR_CC('I', 'E', 'N', 'D') : { + offset = length; // end, break do-while loop + } break; + } + } while (offset + 12 <= length); + + if (chunk_num < 3 || + chunks->fourcc != YY_FOUR_CC('I', 'H', 'D', 'R') || + chunks->length != 13) { + free(chunks); + return NULL; + } + + // png info + yy_png_info *info = calloc(1, sizeof(yy_png_info)); + if (!info) { + free(chunks); + return NULL; + } + info->chunks = chunks; + info->chunk_num = chunk_num; + yy_png_chunk_IHDR_read(&info->header, data + chunks->offset + 8); + + // apng info + if (!apng_chunk_error && apng_frame_number == apng_frame_index && apng_frame_number >= 1) { + bool first_frame_is_cover = false; + uint32_t first_IDAT_index = 0; + if (!yy_png_validate_animation_chunk_order(info->chunks, info->chunk_num, &first_IDAT_index, &first_frame_is_cover)) { + return info; // ignore apng chunk + } + + info->apng_loop_num = apng_loop_num; + info->apng_frame_num = apng_frame_number; + info->apng_first_frame_is_cover = first_frame_is_cover; + info->apng_shared_insert_index = first_IDAT_index; + info->apng_frames = calloc(apng_frame_number, sizeof(yy_png_frame_info)); + if (!info->apng_frames) { + yy_png_info_release(info); + return NULL; + } + info->apng_shared_chunk_indexs = calloc(info->chunk_num, sizeof(uint32_t)); + if (!info->apng_shared_chunk_indexs) { + yy_png_info_release(info); + return NULL; + } + + int32_t frame_index = -1; + uint32_t *shared_chunk_index = info->apng_shared_chunk_indexs; + for (int32_t i = 0; i < info->chunk_num; i++) { + yy_png_chunk_info *chunk = info->chunks + i; + switch (chunk->fourcc) { + case YY_FOUR_CC('I', 'D', 'A', 'T'): { + if (info->apng_shared_insert_index == 0) { + info->apng_shared_insert_index = i; + } + if (first_frame_is_cover) { + yy_png_frame_info *frame = info->apng_frames + frame_index; + frame->chunk_num++; + frame->chunk_size += chunk->length + 12; + } + } break; + case YY_FOUR_CC('a', 'c', 'T', 'L'): { + } break; + case YY_FOUR_CC('f', 'c', 'T', 'L'): { + frame_index++; + yy_png_frame_info *frame = info->apng_frames + frame_index; + frame->chunk_index = i + 1; + yy_png_chunk_fcTL_read(&frame->frame_control, data + chunk->offset + 8); + } break; + case YY_FOUR_CC('f', 'd', 'A', 'T'): { + yy_png_frame_info *frame = info->apng_frames + frame_index; + frame->chunk_num++; + frame->chunk_size += chunk->length + 12; + } break; + default: { + *shared_chunk_index = i; + shared_chunk_index++; + info->apng_shared_chunk_size += chunk->length + 12; + info->apng_shared_chunk_num++; + } break; + } + } + } + return info; +} + +/** + Copy a png frame data from an apng file. + + @param data apng file data + @param info png info + @param index frame index (zero-based) + @param size output, the size of the frame data + @return A frame data (single-frame png file), call free() to release the data. + Returns NULL if an error occurs. + */ +static uint8_t *yy_png_copy_frame_data_at_index(const uint8_t *data, + const yy_png_info *info, + const uint32_t index, + uint32_t *size) { + if (index >= info->apng_frame_num) return NULL; + + yy_png_frame_info *frame_info = info->apng_frames + index; + uint32_t frame_remux_size = 8 /* PNG Header */ + info->apng_shared_chunk_size + frame_info->chunk_size; + if (!(info->apng_first_frame_is_cover && index == 0)) { + frame_remux_size -= frame_info->chunk_num * 4; // remove fdAT sequence number + } + uint8_t *frame_data = malloc(frame_remux_size); + if (!frame_data) return NULL; + *size = frame_remux_size; + + uint32_t data_offset = 0; + bool inserted = false; + memcpy(frame_data, data, 8); // PNG File Header + data_offset += 8; + for (uint32_t i = 0; i < info->apng_shared_chunk_num; i++) { + uint32_t shared_chunk_index = info->apng_shared_chunk_indexs[i]; + yy_png_chunk_info *shared_chunk_info = info->chunks + shared_chunk_index; + + if (shared_chunk_index >= info->apng_shared_insert_index && !inserted) { // replace IDAT with fdAT + inserted = true; + for (uint32_t c = 0; c < frame_info->chunk_num; c++) { + yy_png_chunk_info *insert_chunk_info = info->chunks + frame_info->chunk_index + c; + if (insert_chunk_info->fourcc == YY_FOUR_CC('f', 'd', 'A', 'T')) { + *((uint32_t *)(frame_data + data_offset)) = yy_swap_endian_uint32(insert_chunk_info->length - 4); + *((uint32_t *)(frame_data + data_offset + 4)) = YY_FOUR_CC('I', 'D', 'A', 'T'); + memcpy(frame_data + data_offset + 8, data + insert_chunk_info->offset + 12, insert_chunk_info->length - 4); + uint32_t crc = (uint32_t)crc32(0, frame_data + data_offset + 4, insert_chunk_info->length); + *((uint32_t *)(frame_data + data_offset + insert_chunk_info->length + 4)) = yy_swap_endian_uint32(crc); + data_offset += insert_chunk_info->length + 8; + } else { // IDAT + memcpy(frame_data + data_offset, data + insert_chunk_info->offset, insert_chunk_info->length + 12); + data_offset += insert_chunk_info->length + 12; + } + } + } + + if (shared_chunk_info->fourcc == YY_FOUR_CC('I', 'H', 'D', 'R')) { + uint8_t tmp[25] = {0}; + memcpy(tmp, data + shared_chunk_info->offset, 25); + yy_png_chunk_IHDR IHDR = info->header; + IHDR.width = frame_info->frame_control.width; + IHDR.height = frame_info->frame_control.height; + yy_png_chunk_IHDR_write(&IHDR, tmp + 8); + *((uint32_t *)(tmp + 21)) = yy_swap_endian_uint32((uint32_t)crc32(0, tmp + 4, 17)); + memcpy(frame_data + data_offset, tmp, 25); + data_offset += 25; + } else { + memcpy(frame_data + data_offset, data + shared_chunk_info->offset, shared_chunk_info->length + 12); + data_offset += shared_chunk_info->length + 12; + } + } + return frame_data; +} + + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Helper + +/// Returns byte-aligned size. +static inline size_t YYImageByteAlign(size_t size, size_t alignment) { + return ((size + (alignment - 1)) / alignment) * alignment; +} + +/// Convert degree to radians +static inline CGFloat YYImageDegreesToRadians(CGFloat degrees) { + return degrees * M_PI / 180; +} + +CGColorSpaceRef YYCGColorSpaceGetDeviceRGB() { + static CGColorSpaceRef space; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + space = CGColorSpaceCreateDeviceRGB(); + }); + return space; +} + +CGColorSpaceRef YYCGColorSpaceGetDeviceGray() { + static CGColorSpaceRef space; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + space = CGColorSpaceCreateDeviceGray(); + }); + return space; +} + +BOOL YYCGColorSpaceIsDeviceRGB(CGColorSpaceRef space) { + return space && CFEqual(space, YYCGColorSpaceGetDeviceRGB()); +} + +BOOL YYCGColorSpaceIsDeviceGray(CGColorSpaceRef space) { + return space && CFEqual(space, YYCGColorSpaceGetDeviceGray()); +} + +/** + A callback used in CGDataProviderCreateWithData() to release data. + + Example: + + void *data = malloc(size); + CGDataProviderRef provider = CGDataProviderCreateWithData(data, data, size, YYCGDataProviderReleaseDataCallback); + */ +static void YYCGDataProviderReleaseDataCallback(void *info, const void *data, size_t size) { + if (info) free(info); +} + +/** + Decode an image to bitmap buffer with the specified format. + + @param srcImage Source image. + @param dest Destination buffer. It should be zero before call this method. + If decode succeed, you should release the dest->data using free(). + @param destFormat Destination bitmap format. + + @return Whether succeed. + + @warning This method support iOS7.0 and later. If call it on iOS6, it just returns NO. + CG_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0) + */ +static BOOL YYCGImageDecodeToBitmapBufferWithAnyFormat(CGImageRef srcImage, vImage_Buffer *dest, vImage_CGImageFormat *destFormat) { + if (!srcImage || (((long)vImageConvert_AnyToAny) + 1 == 1) || !destFormat || !dest) return NO; + size_t width = CGImageGetWidth(srcImage); + size_t height = CGImageGetHeight(srcImage); + if (width == 0 || height == 0) return NO; + dest->data = NULL; + + vImage_Error error = kvImageNoError; + CFDataRef srcData = NULL; + vImageConverterRef convertor = NULL; + vImage_CGImageFormat srcFormat = {0}; + srcFormat.bitsPerComponent = (uint32_t)CGImageGetBitsPerComponent(srcImage); + srcFormat.bitsPerPixel = (uint32_t)CGImageGetBitsPerPixel(srcImage); + srcFormat.colorSpace = CGImageGetColorSpace(srcImage); + srcFormat.bitmapInfo = CGImageGetBitmapInfo(srcImage) | CGImageGetAlphaInfo(srcImage); + + convertor = vImageConverter_CreateWithCGImageFormat(&srcFormat, destFormat, NULL, kvImageNoFlags, NULL); + if (!convertor) goto fail; + + CGDataProviderRef srcProvider = CGImageGetDataProvider(srcImage); + srcData = srcProvider ? CGDataProviderCopyData(srcProvider) : NULL; // decode + size_t srcLength = srcData ? CFDataGetLength(srcData) : 0; + const void *srcBytes = srcData ? CFDataGetBytePtr(srcData) : NULL; + if (srcLength == 0 || !srcBytes) goto fail; + + vImage_Buffer src = {0}; + src.data = (void *)srcBytes; + src.width = width; + src.height = height; + src.rowBytes = CGImageGetBytesPerRow(srcImage); + + error = vImageBuffer_Init(dest, height, width, 32, kvImageNoFlags); + if (error != kvImageNoError) goto fail; + + error = vImageConvert_AnyToAny(convertor, &src, dest, NULL, kvImageNoFlags); // convert + if (error != kvImageNoError) goto fail; + + CFRelease(convertor); + CFRelease(srcData); + return YES; + +fail: + if (convertor) CFRelease(convertor); + if (srcData) CFRelease(srcData); + if (dest->data) free(dest->data); + dest->data = NULL; + return NO; +} + +/** + Decode an image to bitmap buffer with the 32bit format (such as ARGB8888). + + @param srcImage Source image. + @param dest Destination buffer. It should be zero before call this method. + If decode succeed, you should release the dest->data using free(). + @param bitmapInfo Destination bitmap format. + + @return Whether succeed. + */ +static BOOL YYCGImageDecodeToBitmapBufferWith32BitFormat(CGImageRef srcImage, vImage_Buffer *dest, CGBitmapInfo bitmapInfo) { + if (!srcImage || !dest) return NO; + size_t width = CGImageGetWidth(srcImage); + size_t height = CGImageGetHeight(srcImage); + if (width == 0 || height == 0) return NO; + + BOOL hasAlpha = NO; + BOOL alphaFirst = NO; + BOOL alphaPremultiplied = NO; + BOOL byteOrderNormal = NO; + + switch (bitmapInfo & kCGBitmapAlphaInfoMask) { + case kCGImageAlphaPremultipliedLast: { + hasAlpha = YES; + alphaPremultiplied = YES; + } break; + case kCGImageAlphaPremultipliedFirst: { + hasAlpha = YES; + alphaPremultiplied = YES; + alphaFirst = YES; + } break; + case kCGImageAlphaLast: { + hasAlpha = YES; + } break; + case kCGImageAlphaFirst: { + hasAlpha = YES; + alphaFirst = YES; + } break; + case kCGImageAlphaNoneSkipLast: { + } break; + case kCGImageAlphaNoneSkipFirst: { + alphaFirst = YES; + } break; + default: { + return NO; + } break; + } + + switch (bitmapInfo & kCGBitmapByteOrderMask) { + case kCGBitmapByteOrderDefault: { + byteOrderNormal = YES; + } break; + case kCGBitmapByteOrder32Little: { + } break; + case kCGBitmapByteOrder32Big: { + byteOrderNormal = YES; + } break; + default: { + return NO; + } break; + } + + /* + Try convert with vImageConvert_AnyToAny() (avaliable since iOS 7.0). + If fail, try decode with CGContextDrawImage(). + CGBitmapContext use a premultiplied alpha format, unpremultiply may lose precision. + */ + vImage_CGImageFormat destFormat = {0}; + destFormat.bitsPerComponent = 8; + destFormat.bitsPerPixel = 32; + destFormat.colorSpace = YYCGColorSpaceGetDeviceRGB(); + destFormat.bitmapInfo = bitmapInfo; + dest->data = NULL; + if (YYCGImageDecodeToBitmapBufferWithAnyFormat(srcImage, dest, &destFormat)) return YES; + + CGBitmapInfo contextBitmapInfo = bitmapInfo & kCGBitmapByteOrderMask; + if (!hasAlpha || alphaPremultiplied) { + contextBitmapInfo |= (bitmapInfo & kCGBitmapAlphaInfoMask); + } else { + contextBitmapInfo |= alphaFirst ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaPremultipliedLast; + } + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), contextBitmapInfo); + if (!context) goto fail; + + CGContextDrawImage(context, CGRectMake(0, 0, width, height), srcImage); // decode and convert + size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context); + size_t length = height * bytesPerRow; + void *data = CGBitmapContextGetData(context); + if (length == 0 || !data) goto fail; + + dest->data = malloc(length); + dest->width = width; + dest->height = height; + dest->rowBytes = bytesPerRow; + if (!dest->data) goto fail; + + if (hasAlpha && !alphaPremultiplied) { + vImage_Buffer tmpSrc = {0}; + tmpSrc.data = data; + tmpSrc.width = width; + tmpSrc.height = height; + tmpSrc.rowBytes = bytesPerRow; + vImage_Error error; + if (alphaFirst && byteOrderNormal) { + error = vImageUnpremultiplyData_ARGB8888(&tmpSrc, dest, kvImageNoFlags); + } else { + error = vImageUnpremultiplyData_RGBA8888(&tmpSrc, dest, kvImageNoFlags); + } + if (error != kvImageNoError) goto fail; + } else { + memcpy(dest->data, data, length); + } + + CFRelease(context); + return YES; + +fail: + if (context) CFRelease(context); + if (dest->data) free(dest->data); + dest->data = NULL; + return NO; + return NO; +} + +CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) { + if (!imageRef) return NULL; + size_t width = CGImageGetWidth(imageRef); + size_t height = CGImageGetHeight(imageRef); + if (width == 0 || height == 0) return NULL; + + if (decodeForDisplay) { //decode with redraw (may lose some precision) + CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask; + BOOL hasAlpha = NO; + if (alphaInfo == kCGImageAlphaPremultipliedLast || + alphaInfo == kCGImageAlphaPremultipliedFirst || + alphaInfo == kCGImageAlphaLast || + alphaInfo == kCGImageAlphaFirst) { + hasAlpha = YES; + } + // BGRA8888 (premultiplied) or BGRX8888 + // same as UIGraphicsBeginImageContext() and -[UIView drawRect:] + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; + bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo); + if (!context) return NULL; + CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode + CGImageRef newImage = CGBitmapContextCreateImage(context); + CFRelease(context); + return newImage; + + } else { + CGColorSpaceRef space = CGImageGetColorSpace(imageRef); + size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef); + size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef); + size_t bytesPerRow = CGImageGetBytesPerRow(imageRef); + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); + if (bytesPerRow == 0 || width == 0 || height == 0) return NULL; + + CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef); + if (!dataProvider) return NULL; + CFDataRef data = CGDataProviderCopyData(dataProvider); // decode + if (!data) return NULL; + + CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(data); + CFRelease(data); + if (!newProvider) return NULL; + + CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, newProvider, NULL, false, kCGRenderingIntentDefault); + CFRelease(newProvider); + return newImage; + } +} + +CGImageRef YYCGImageCreateAffineTransformCopy(CGImageRef imageRef, CGAffineTransform transform, CGSize destSize, CGBitmapInfo destBitmapInfo) { + if (!imageRef) return NULL; + size_t srcWidth = CGImageGetWidth(imageRef); + size_t srcHeight = CGImageGetHeight(imageRef); + size_t destWidth = round(destSize.width); + size_t destHeight = round(destSize.height); + if (srcWidth == 0 || srcHeight == 0 || destWidth == 0 || destHeight == 0) return NULL; + + CGDataProviderRef tmpProvider = NULL, destProvider = NULL; + CGImageRef tmpImage = NULL, destImage = NULL; + vImage_Buffer src = {0}, tmp = {0}, dest = {0}; + if(!YYCGImageDecodeToBitmapBufferWith32BitFormat(imageRef, &src, kCGImageAlphaFirst | kCGBitmapByteOrderDefault)) return NULL; + + size_t destBytesPerRow = YYImageByteAlign(destWidth * 4, 32); + tmp.data = malloc(destHeight * destBytesPerRow); + if (!tmp.data) goto fail; + + tmp.width = destWidth; + tmp.height = destHeight; + tmp.rowBytes = destBytesPerRow; + vImage_CGAffineTransform vTransform = *((vImage_CGAffineTransform *)&transform); + uint8_t backColor[4] = {0}; + vImage_Error error = vImageAffineWarpCG_ARGB8888(&src, &tmp, NULL, &vTransform, backColor, kvImageBackgroundColorFill); + if (error != kvImageNoError) goto fail; + free(src.data); + src.data = NULL; + + tmpProvider = CGDataProviderCreateWithData(tmp.data, tmp.data, destHeight * destBytesPerRow, YYCGDataProviderReleaseDataCallback); + if (!tmpProvider) goto fail; + tmp.data = NULL; // hold by provider + tmpImage = CGImageCreate(destWidth, destHeight, 8, 32, destBytesPerRow, YYCGColorSpaceGetDeviceRGB(), kCGImageAlphaFirst | kCGBitmapByteOrderDefault, tmpProvider, NULL, false, kCGRenderingIntentDefault); + if (!tmpImage) goto fail; + CFRelease(tmpProvider); + tmpProvider = NULL; + + if ((destBitmapInfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaFirst && + (destBitmapInfo & kCGBitmapByteOrderMask) != kCGBitmapByteOrder32Little) { + return tmpImage; + } + + if (!YYCGImageDecodeToBitmapBufferWith32BitFormat(tmpImage, &dest, destBitmapInfo)) goto fail; + CFRelease(tmpImage); + tmpImage = NULL; + + destProvider = CGDataProviderCreateWithData(dest.data, dest.data, destHeight * destBytesPerRow, YYCGDataProviderReleaseDataCallback); + if (!destProvider) goto fail; + dest.data = NULL; // hold by provider + destImage = CGImageCreate(destWidth, destHeight, 8, 32, destBytesPerRow, YYCGColorSpaceGetDeviceRGB(), destBitmapInfo, destProvider, NULL, false, kCGRenderingIntentDefault); + if (!destImage) goto fail; + CFRelease(destProvider); + destProvider = NULL; + + return destImage; + +fail: + if (src.data) free(src.data); + if (tmp.data) free(tmp.data); + if (dest.data) free(dest.data); + if (tmpProvider) CFRelease(tmpProvider); + if (tmpImage) CFRelease(tmpImage); + if (destProvider) CFRelease(destProvider); + return NULL; +} + +UIImageOrientation YYUIImageOrientationFromEXIFValue(NSInteger value) { + switch (value) { + case kCGImagePropertyOrientationUp: return UIImageOrientationUp; + case kCGImagePropertyOrientationDown: return UIImageOrientationDown; + case kCGImagePropertyOrientationLeft: return UIImageOrientationLeft; + case kCGImagePropertyOrientationRight: return UIImageOrientationRight; + case kCGImagePropertyOrientationUpMirrored: return UIImageOrientationUpMirrored; + case kCGImagePropertyOrientationDownMirrored: return UIImageOrientationDownMirrored; + case kCGImagePropertyOrientationLeftMirrored: return UIImageOrientationLeftMirrored; + case kCGImagePropertyOrientationRightMirrored: return UIImageOrientationRightMirrored; + default: return UIImageOrientationUp; + } +} + +NSInteger YYUIImageOrientationToEXIFValue(UIImageOrientation orientation) { + switch (orientation) { + case UIImageOrientationUp: return kCGImagePropertyOrientationUp; + case UIImageOrientationDown: return kCGImagePropertyOrientationDown; + case UIImageOrientationLeft: return kCGImagePropertyOrientationLeft; + case UIImageOrientationRight: return kCGImagePropertyOrientationRight; + case UIImageOrientationUpMirrored: return kCGImagePropertyOrientationUpMirrored; + case UIImageOrientationDownMirrored: return kCGImagePropertyOrientationDownMirrored; + case UIImageOrientationLeftMirrored: return kCGImagePropertyOrientationLeftMirrored; + case UIImageOrientationRightMirrored: return kCGImagePropertyOrientationRightMirrored; + default: return kCGImagePropertyOrientationUp; + } +} + +CGImageRef YYCGImageCreateCopyWithOrientation(CGImageRef imageRef, UIImageOrientation orientation, CGBitmapInfo destBitmapInfo) { + if (!imageRef) return NULL; + if (orientation == UIImageOrientationUp) return (CGImageRef)CFRetain(imageRef); + + size_t width = CGImageGetWidth(imageRef); + size_t height = CGImageGetHeight(imageRef); + + CGAffineTransform transform = CGAffineTransformIdentity; + BOOL swapWidthAndHeight = NO; + switch (orientation) { + case UIImageOrientationDown: { + transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(180)); + transform = CGAffineTransformTranslate(transform, -(CGFloat)width, -(CGFloat)height); + } break; + case UIImageOrientationLeft: { + transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(90)); + transform = CGAffineTransformTranslate(transform, -(CGFloat)0, -(CGFloat)height); + swapWidthAndHeight = YES; + } break; + case UIImageOrientationRight: { + transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(-90)); + transform = CGAffineTransformTranslate(transform, -(CGFloat)width, (CGFloat)0); + swapWidthAndHeight = YES; + } break; + case UIImageOrientationUpMirrored: { + transform = CGAffineTransformTranslate(transform, (CGFloat)width, 0); + transform = CGAffineTransformScale(transform, -1, 1); + } break; + case UIImageOrientationDownMirrored: { + transform = CGAffineTransformTranslate(transform, 0, (CGFloat)height); + transform = CGAffineTransformScale(transform, 1, -1); + } break; + case UIImageOrientationLeftMirrored: { + transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(-90)); + transform = CGAffineTransformScale(transform, 1, -1); + transform = CGAffineTransformTranslate(transform, -(CGFloat)width, -(CGFloat)height); + swapWidthAndHeight = YES; + } break; + case UIImageOrientationRightMirrored: { + transform = CGAffineTransformMakeRotation(YYImageDegreesToRadians(90)); + transform = CGAffineTransformScale(transform, 1, -1); + swapWidthAndHeight = YES; + } break; + default: break; + } + if (CGAffineTransformIsIdentity(transform)) return (CGImageRef)CFRetain(imageRef); + + CGSize destSize = {width, height}; + if (swapWidthAndHeight) { + destSize.width = height; + destSize.height = width; + } + + return YYCGImageCreateAffineTransformCopy(imageRef, transform, destSize, destBitmapInfo); +} + +YYImageType YYImageDetectType(CFDataRef data) { + if (!data) return YYImageTypeUnknown; + uint64_t length = CFDataGetLength(data); + if (length < 16) return YYImageTypeUnknown; + + const char *bytes = (char *)CFDataGetBytePtr(data); + + uint32_t magic4 = *((uint32_t *)bytes); + switch (magic4) { + case YY_FOUR_CC(0x4D, 0x4D, 0x00, 0x2A): { // big endian TIFF + return YYImageTypeTIFF; + } break; + + case YY_FOUR_CC(0x49, 0x49, 0x2A, 0x00): { // little endian TIFF + return YYImageTypeTIFF; + } break; + + case YY_FOUR_CC(0x00, 0x00, 0x01, 0x00): { // ICO + return YYImageTypeICO; + } break; + + case YY_FOUR_CC(0x00, 0x00, 0x02, 0x00): { // CUR + return YYImageTypeICO; + } break; + + case YY_FOUR_CC('i', 'c', 'n', 's'): { // ICNS + return YYImageTypeICNS; + } break; + + case YY_FOUR_CC('G', 'I', 'F', '8'): { // GIF + return YYImageTypeGIF; + } break; + + case YY_FOUR_CC(0x89, 'P', 'N', 'G'): { // PNG + uint32_t tmp = *((uint32_t *)(bytes + 4)); + if (tmp == YY_FOUR_CC('\r', '\n', 0x1A, '\n')) { + return YYImageTypePNG; + } + } break; + + case YY_FOUR_CC('R', 'I', 'F', 'F'): { // WebP + uint32_t tmp = *((uint32_t *)(bytes + 8)); + if (tmp == YY_FOUR_CC('W', 'E', 'B', 'P')) { + return YYImageTypeWebP; + } + } break; + /* + case YY_FOUR_CC('B', 'P', 'G', 0xFB): { // BPG + return YYImageTypeBPG; + } break; + */ + } + + uint16_t magic2 = *((uint16_t *)bytes); + switch (magic2) { + case YY_TWO_CC('B', 'A'): + case YY_TWO_CC('B', 'M'): + case YY_TWO_CC('I', 'C'): + case YY_TWO_CC('P', 'I'): + case YY_TWO_CC('C', 'I'): + case YY_TWO_CC('C', 'P'): { // BMP + return YYImageTypeBMP; + } + case YY_TWO_CC(0xFF, 0x4F): { // JPEG2000 + return YYImageTypeJPEG2000; + } + } + + // JPG FF D8 FF + if (memcmp(bytes,"\377\330\377",3) == 0) return YYImageTypeJPEG; + + // JP2 + if (memcmp(bytes + 4, "\152\120\040\040\015", 5) == 0) return YYImageTypeJPEG2000; + + return YYImageTypeUnknown; +} + +CFStringRef YYImageTypeToUTType(YYImageType type) { + switch (type) { + case YYImageTypeJPEG: return kUTTypeJPEG; + case YYImageTypeJPEG2000: return kUTTypeJPEG2000; + case YYImageTypeTIFF: return kUTTypeTIFF; + case YYImageTypeBMP: return kUTTypeBMP; + case YYImageTypeICO: return kUTTypeICO; + case YYImageTypeICNS: return kUTTypeAppleICNS; + case YYImageTypeGIF: return kUTTypeGIF; + case YYImageTypePNG: return kUTTypePNG; + default: return NULL; + } +} + +YYImageType YYImageTypeFromUTType(CFStringRef uti) { + static NSDictionary *dic; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dic = @{(id)kUTTypeJPEG : @(YYImageTypeJPEG), + (id)kUTTypeJPEG2000 : @(YYImageTypeJPEG2000), + (id)kUTTypeTIFF : @(YYImageTypeTIFF), + (id)kUTTypeBMP : @(YYImageTypeBMP), + (id)kUTTypeICO : @(YYImageTypeICO), + (id)kUTTypeAppleICNS : @(YYImageTypeICNS), + (id)kUTTypeGIF : @(YYImageTypeGIF), + (id)kUTTypePNG : @(YYImageTypePNG)}; + }); + if (!uti) return YYImageTypeUnknown; + NSNumber *num = dic[(__bridge __strong id)(uti)]; + return num.unsignedIntegerValue; +} + +NSString *YYImageTypeGetExtension(YYImageType type) { + switch (type) { + case YYImageTypeJPEG: return @"jpg"; + case YYImageTypeJPEG2000: return @"jp2"; + case YYImageTypeTIFF: return @"tiff"; + case YYImageTypeBMP: return @"bmp"; + case YYImageTypeICO: return @"ico"; + case YYImageTypeICNS: return @"icns"; + case YYImageTypeGIF: return @"gif"; + case YYImageTypePNG: return @"png"; + case YYImageTypeWebP: return @"webp"; + default: return nil; + } +} + +CFDataRef YYCGImageCreateEncodedData(CGImageRef imageRef, YYImageType type, CGFloat quality) { + if (!imageRef) return nil; + quality = quality < 0 ? 0 : quality > 1 ? 1 : quality; + + if (type == YYImageTypeWebP) { +#if YYIMAGE_WEBP_ENABLED + if (quality == 1) { + return YYCGImageCreateEncodedWebPData(imageRef, YES, quality, 4, YYImagePresetDefault); + } else { + return YYCGImageCreateEncodedWebPData(imageRef, NO, quality, 4, YYImagePresetDefault); + } +#else + return NULL; +#endif + } + + CFStringRef uti = YYImageTypeToUTType(type); + if (!uti) return nil; + + CFMutableDataRef data = CFDataCreateMutable(CFAllocatorGetDefault(), 0); + if (!data) return NULL; + CGImageDestinationRef dest = CGImageDestinationCreateWithData(data, uti, 1, NULL); + if (!dest) { + CFRelease(data); + return NULL; + } + NSDictionary *options = @{(id)kCGImageDestinationLossyCompressionQuality : @(quality) }; + CGImageDestinationAddImage(dest, imageRef, (CFDictionaryRef)options); + if (!CGImageDestinationFinalize(dest)) { + CFRelease(data); + CFRelease(dest); + return nil; + } + CFRelease(dest); + + if (CFDataGetLength(data) == 0) { + CFRelease(data); + return NULL; + } + return data; +} + +#if YYIMAGE_WEBP_ENABLED + +BOOL YYImageWebPAvailable() { + return YES; +} + +CFDataRef YYCGImageCreateEncodedWebPData(CGImageRef imageRef, BOOL lossless, CGFloat quality, int compressLevel, YYImagePreset preset) { + if (!imageRef) return nil; + size_t width = CGImageGetWidth(imageRef); + size_t height = CGImageGetHeight(imageRef); + if (width == 0 || width > WEBP_MAX_DIMENSION) return nil; + if (height == 0 || height > WEBP_MAX_DIMENSION) return nil; + + vImage_Buffer buffer = {0}; + if(!YYCGImageDecodeToBitmapBufferWith32BitFormat(imageRef, &buffer, kCGImageAlphaLast | kCGBitmapByteOrderDefault)) return nil; + + WebPConfig config = {0}; + WebPPicture picture = {0}; + WebPMemoryWriter writer = {0}; + CFDataRef webpData = NULL; + BOOL pictureNeedFree = NO; + + quality = quality < 0 ? 0 : quality > 1 ? 1 : quality; + preset = preset > YYImagePresetText ? YYImagePresetDefault : preset; + compressLevel = compressLevel < 0 ? 0 : compressLevel > 6 ? 6 : compressLevel; + if (!WebPConfigPreset(&config, (WebPPreset)preset, quality)) goto fail; + + config.quality = round(quality * 100.0); + config.lossless = lossless; + config.method = compressLevel; + switch ((WebPPreset)preset) { + case WEBP_PRESET_DEFAULT: { + config.image_hint = WEBP_HINT_DEFAULT; + } break; + case WEBP_PRESET_PICTURE: { + config.image_hint = WEBP_HINT_PICTURE; + } break; + case WEBP_PRESET_PHOTO: { + config.image_hint = WEBP_HINT_PHOTO; + } break; + case WEBP_PRESET_DRAWING: + case WEBP_PRESET_ICON: + case WEBP_PRESET_TEXT: { + config.image_hint = WEBP_HINT_GRAPH; + } break; + } + if (!WebPValidateConfig(&config)) goto fail; + + if (!WebPPictureInit(&picture)) goto fail; + pictureNeedFree = YES; + picture.width = (int)buffer.width; + picture.height = (int)buffer.height; + picture.use_argb = lossless; + if(!WebPPictureImportRGBA(&picture, buffer.data, (int)buffer.rowBytes)) goto fail; + + WebPMemoryWriterInit(&writer); + picture.writer = WebPMemoryWrite; + picture.custom_ptr = &writer; + if(!WebPEncode(&config, &picture)) goto fail; + + webpData = CFDataCreate(CFAllocatorGetDefault(), writer.mem, writer.size); + free(writer.mem); + WebPPictureFree(&picture); + free(buffer.data); + return webpData; + +fail: + if (buffer.data) free(buffer.data); + if (pictureNeedFree) WebPPictureFree(&picture); + return nil; +} + +NSUInteger YYImageGetWebPFrameCount(CFDataRef webpData) { + if (!webpData || CFDataGetLength(webpData) == 0) return 0; + + WebPData data = {CFDataGetBytePtr(webpData), CFDataGetLength(webpData)}; + WebPDemuxer *demuxer = WebPDemux(&data); + if (!demuxer) return 0; + NSUInteger webpFrameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT); + WebPDemuxDelete(demuxer); + return webpFrameCount; +} + +CGImageRef YYCGImageCreateWithWebPData(CFDataRef webpData, + BOOL decodeForDisplay, + BOOL useThreads, + BOOL bypassFiltering, + BOOL noFancyUpsampling) { + /* + Call WebPDecode() on a multi-frame webp data will get an error (VP8_STATUS_UNSUPPORTED_FEATURE). + Use WebPDemuxer to unpack it first. + */ + WebPData data = {0}; + WebPDemuxer *demuxer = NULL; + + int frameCount = 0, canvasWidth = 0, canvasHeight = 0; + WebPIterator iter = {0}; + BOOL iterInited = NO; + const uint8_t *payload = NULL; + size_t payloadSize = 0; + WebPDecoderConfig config = {0}; + + BOOL hasAlpha = NO; + size_t bitsPerComponent = 0, bitsPerPixel = 0, bytesPerRow = 0, destLength = 0; + CGBitmapInfo bitmapInfo = 0; + WEBP_CSP_MODE colorspace = 0; + void *destBytes = NULL; + CGDataProviderRef provider = NULL; + CGImageRef imageRef = NULL; + + if (!webpData || CFDataGetLength(webpData) == 0) return NULL; + data.bytes = CFDataGetBytePtr(webpData); + data.size = CFDataGetLength(webpData); + demuxer = WebPDemux(&data); + if (!demuxer) goto fail; + + frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT); + if (frameCount == 0) { + goto fail; + + } else if (frameCount == 1) { // single-frame + payload = data.bytes; + payloadSize = data.size; + if (!WebPInitDecoderConfig(&config)) goto fail; + if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) goto fail; + canvasWidth = config.input.width; + canvasHeight = config.input.height; + + } else { // multi-frame + canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); + canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); + if (canvasWidth < 1 || canvasHeight < 1) goto fail; + + if (!WebPDemuxGetFrame(demuxer, 1, &iter)) goto fail; + iterInited = YES; + + if (iter.width > canvasWidth || iter.height > canvasHeight) goto fail; + payload = iter.fragment.bytes; + payloadSize = iter.fragment.size; + + if (!WebPInitDecoderConfig(&config)) goto fail; + if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) goto fail; + } + if (payload == NULL || payloadSize == 0) goto fail; + + hasAlpha = config.input.has_alpha; + bitsPerComponent = 8; + bitsPerPixel = 32; + bytesPerRow = YYImageByteAlign(bitsPerPixel / 8 * canvasWidth, 32); + destLength = bytesPerRow * canvasHeight; + if (decodeForDisplay) { + bitmapInfo = kCGBitmapByteOrder32Host; + bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; + colorspace = MODE_bgrA; // small endian + } else { + bitmapInfo = kCGBitmapByteOrderDefault; + bitmapInfo |= hasAlpha ? kCGImageAlphaLast : kCGImageAlphaNoneSkipLast; + colorspace = MODE_RGBA; + } + destBytes = calloc(1, destLength); + if (!destBytes) goto fail; + + config.options.use_threads = useThreads; //speed up 23% + config.options.bypass_filtering = bypassFiltering; //speed up 11%, cause some banding + config.options.no_fancy_upsampling = noFancyUpsampling; //speed down 16%, lose some details + config.output.colorspace = colorspace; + config.output.is_external_memory = 1; + config.output.u.RGBA.rgba = destBytes; + config.output.u.RGBA.stride = (int)bytesPerRow; + config.output.u.RGBA.size = destLength; + + VP8StatusCode result = WebPDecode(payload, payloadSize, &config); + if ((result != VP8_STATUS_OK) && (result != VP8_STATUS_NOT_ENOUGH_DATA)) goto fail; + + if (iter.x_offset != 0 || iter.y_offset != 0) { + void *tmp = calloc(1, destLength); + if (tmp) { + vImage_Buffer src = {destBytes, canvasHeight, canvasWidth, bytesPerRow}; + vImage_Buffer dest = {tmp, canvasHeight, canvasWidth, bytesPerRow}; + vImage_CGAffineTransform transform = {1, 0, 0, 1, iter.x_offset, -iter.y_offset}; + uint8_t backColor[4] = {0}; + vImageAffineWarpCG_ARGB8888(&src, &dest, NULL, &transform, backColor, kvImageBackgroundColorFill); + memcpy(destBytes, tmp, destLength); + free(tmp); + } + } + + provider = CGDataProviderCreateWithData(destBytes, destBytes, destLength, YYCGDataProviderReleaseDataCallback); + if (!provider) goto fail; + destBytes = NULL; // hold by provider + + imageRef = CGImageCreate(canvasWidth, canvasHeight, bitsPerComponent, bitsPerPixel, bytesPerRow, YYCGColorSpaceGetDeviceRGB(), bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault); + + CFRelease(provider); + if (iterInited) WebPDemuxReleaseIterator(&iter); + WebPDemuxDelete(demuxer); + + return imageRef; + +fail: + if (destBytes) free(destBytes); + if (provider) CFRelease(provider); + if (iterInited) WebPDemuxReleaseIterator(&iter); + if (demuxer) WebPDemuxDelete(demuxer); + return NULL; +} + +#else + +BOOL YYImageWebPAvailable() { + return NO; +} + +CFDataRef YYCGImageCreateEncodedWebPData(CGImageRef imageRef, BOOL lossless, CGFloat quality, int compressLevel, YYImagePreset preset) { + NSLog(@"WebP decoder is disabled"); + return NULL; +} + +NSUInteger YYImageGetWebPFrameCount(CFDataRef webpData) { + NSLog(@"WebP decoder is disabled"); + return 0; +} + +CGImageRef YYCGImageCreateWithWebPData(CFDataRef webpData, + BOOL decodeForDisplay, + BOOL useThreads, + BOOL bypassFiltering, + BOOL noFancyUpsampling) { + NSLog(@"WebP decoder is disabled"); + return NULL; +} + +#endif + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Decoder + +@implementation YYImageFrame ++ (instancetype)frameWithImage:(UIImage *)image { + YYImageFrame *frame = [self new]; + frame.image = image; + return frame; +} +- (id)copyWithZone:(NSZone *)zone { + YYImageFrame *frame = [self.class new]; + frame.index = _index; + frame.width = _width; + frame.height = _height; + frame.offsetX = _offsetX; + frame.offsetY = _offsetY; + frame.duration = _duration; + frame.dispose = _dispose; + frame.blend = _blend; + frame.image = _image.copy; + return frame; +} +@end + +// Internal frame object. +@interface _YYImageDecoderFrame : YYImageFrame +@property (nonatomic, assign) BOOL hasAlpha; ///< Whether frame has alpha. +@property (nonatomic, assign) BOOL isFullSize; ///< Whether frame fill the canvas. +@property (nonatomic, assign) NSUInteger blendFromIndex; ///< Blend from frame index to current frame. +@end + +@implementation _YYImageDecoderFrame +- (id)copyWithZone:(NSZone *)zone { + _YYImageDecoderFrame *frame = [super copyWithZone:zone]; + frame.hasAlpha = _hasAlpha; + frame.isFullSize = _isFullSize; + frame.blendFromIndex = _blendFromIndex; + return frame; +} +@end + + +@implementation YYImageDecoder { + pthread_mutex_t _lock; // recursive lock + + BOOL _sourceTypeDetected; + CGImageSourceRef _source; + yy_png_info *_apngSource; +#if YYIMAGE_WEBP_ENABLED + WebPDemuxer *_webpSource; +#endif + + UIImageOrientation _orientation; + dispatch_semaphore_t _framesLock; + NSArray *_frames; ///< Array, without image + BOOL _needBlend; + NSUInteger _blendFrameIndex; + CGContextRef _blendCanvas; +} + +- (void)dealloc { + if (_source) CFRelease(_source); + if (_apngSource) yy_png_info_release(_apngSource); +#if YYIMAGE_WEBP_ENABLED + if (_webpSource) WebPDemuxDelete(_webpSource); +#endif + if (_blendCanvas) CFRelease(_blendCanvas); + pthread_mutex_destroy(&_lock); +} + ++ (instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale { + if (!data) return nil; + YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:scale]; + [decoder updateData:data final:YES]; + if (decoder.frameCount == 0) return nil; + return decoder; +} + +- (instancetype)init { + return [self initWithScale:[UIScreen mainScreen].scale]; +} + +- (instancetype)initWithScale:(CGFloat)scale { + self = [super init]; + if (scale <= 0) scale = 1; + _scale = scale; + _framesLock = dispatch_semaphore_create(1); + + pthread_mutexattr_t attr; + pthread_mutexattr_init (&attr); + pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init (&_lock, &attr); + pthread_mutexattr_destroy (&attr); + + return self; +} + +- (BOOL)updateData:(NSData *)data final:(BOOL)final { + BOOL result = NO; + pthread_mutex_lock(&_lock); + result = [self _updateData:data final:final]; + pthread_mutex_unlock(&_lock); + return result; +} + +- (YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay { + YYImageFrame *result = nil; + pthread_mutex_lock(&_lock); + result = [self _frameAtIndex:index decodeForDisplay:decodeForDisplay]; + pthread_mutex_unlock(&_lock); + return result; +} + +- (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index { + NSTimeInterval result = 0; + dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER); + if (index < _frames.count) { + result = ((_YYImageDecoderFrame *)_frames[index]).duration; + } + dispatch_semaphore_signal(_framesLock); + return result; +} + +- (NSDictionary *)framePropertiesAtIndex:(NSUInteger)index { + NSDictionary *result = nil; + pthread_mutex_lock(&_lock); + result = [self _framePropertiesAtIndex:index]; + pthread_mutex_unlock(&_lock); + return result; +} + +- (NSDictionary *)imageProperties { + NSDictionary *result = nil; + pthread_mutex_lock(&_lock); + result = [self _imageProperties]; + pthread_mutex_unlock(&_lock); + return result; +} + +#pragma private (wrap) + +- (BOOL)_updateData:(NSData *)data final:(BOOL)final { + if (_finalized) return NO; + if (data.length < _data.length) return NO; + _finalized = final; + _data = data; + + YYImageType type = YYImageDetectType((__bridge CFDataRef)data); + if (_sourceTypeDetected) { + if (_type != type) { + return NO; + } else { + [self _updateSource]; + } + } else { + if (_data.length > 16) { + _type = type; + _sourceTypeDetected = YES; + [self _updateSource]; + } + } + return YES; +} + +- (YYImageFrame *)_frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay { + if (index >= _frames.count) return 0; + _YYImageDecoderFrame *frame = [(_YYImageDecoderFrame *)_frames[index] copy]; + BOOL decoded = NO; + BOOL extendToCanvas = NO; + if (_type != YYImageTypeICO && decodeForDisplay) { // ICO contains multi-size frame and should not extend to canvas. + extendToCanvas = YES; + } + + if (!_needBlend) { + CGImageRef imageRef = [self _newUnblendedImageAtIndex:index extendToCanvas:extendToCanvas decoded:&decoded]; + if (!imageRef) return nil; + if (decodeForDisplay && !decoded) { + CGImageRef imageRefDecoded = YYCGImageCreateDecodedCopy(imageRef, YES); + if (imageRefDecoded) { + CFRelease(imageRef); + imageRef = imageRefDecoded; + decoded = YES; + } + } + UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation]; + CFRelease(imageRef); + if (!image) return nil; + image.yy_isDecodedForDisplay = decoded; + frame.image = image; + return frame; + } + + // blend + if (![self _createBlendContextIfNeeded]) return nil; + CGImageRef imageRef = NULL; + + if (_blendFrameIndex + 1 == frame.index) { + imageRef = [self _newBlendedImageWithFrame:frame]; + _blendFrameIndex = index; + } else { // should draw canvas from previous frame + _blendFrameIndex = NSNotFound; + CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height)); + + if (frame.blendFromIndex == frame.index) { + CGImageRef unblendedImage = [self _newUnblendedImageAtIndex:index extendToCanvas:NO decoded:NULL]; + if (unblendedImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendedImage); + CFRelease(unblendedImage); + } + imageRef = CGBitmapContextCreateImage(_blendCanvas); + if (frame.dispose == YYImageDisposeBackground) { + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + } + _blendFrameIndex = index; + } else { // canvas is not ready + for (uint32_t i = (uint32_t)frame.blendFromIndex; i <= (uint32_t)frame.index; i++) { + if (i == frame.index) { + if (!imageRef) imageRef = [self _newBlendedImageWithFrame:frame]; + } else { + [self _blendImageWithFrame:_frames[i]]; + } + } + _blendFrameIndex = index; + } + } + + if (!imageRef) return nil; + UIImage *image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:_orientation]; + CFRelease(imageRef); + if (!image) return nil; + + image.yy_isDecodedForDisplay = YES; + frame.image = image; + if (extendToCanvas) { + frame.width = _width; + frame.height = _height; + frame.offsetX = 0; + frame.offsetY = 0; + frame.dispose = YYImageDisposeNone; + frame.blend = YYImageBlendNone; + } + return frame; +} + +- (NSDictionary *)_framePropertiesAtIndex:(NSUInteger)index { + if (index >= _frames.count) return nil; + if (!_source) return nil; + CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_source, index, NULL); + if (!properties) return nil; + return CFBridgingRelease(properties); +} + +- (NSDictionary *)_imageProperties { + if (!_source) return nil; + CFDictionaryRef properties = CGImageSourceCopyProperties(_source, NULL); + if (!properties) return nil; + return CFBridgingRelease(properties); +} + +#pragma private + +- (void)_updateSource { + switch (_type) { + case YYImageTypeWebP: { + [self _updateSourceWebP]; + } break; + + case YYImageTypePNG: { + [self _updateSourceAPNG]; + } break; + + default: { + [self _updateSourceImageIO]; + } break; + } +} + +- (void)_updateSourceWebP { +#if YYIMAGE_WEBP_ENABLED + _width = 0; + _height = 0; + _loopCount = 0; + if (_webpSource) WebPDemuxDelete(_webpSource); + _webpSource = NULL; + dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER); + _frames = nil; + dispatch_semaphore_signal(_framesLock); + + /* + https://developers.google.com/speed/webp/docs/api + The documentation said we can use WebPIDecoder to decode webp progressively, + but currently it can only returns an empty image (not same as progressive jpegs), + so we don't use progressive decoding. + + When using WebPDecode() to decode multi-frame webp, we will get the error + "VP8_STATUS_UNSUPPORTED_FEATURE", so we first use WebPDemuxer to unpack it. + */ + + WebPData webPData = {0}; + webPData.bytes = _data.bytes; + webPData.size = _data.length; + WebPDemuxer *demuxer = WebPDemux(&webPData); + if (!demuxer) return; + + uint32_t webpFrameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT); + uint32_t webpLoopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT); + uint32_t canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); + uint32_t canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); + if (webpFrameCount == 0 || canvasWidth < 1 || canvasHeight < 1) { + WebPDemuxDelete(demuxer); + return; + } + + NSMutableArray *frames = [NSMutableArray new]; + BOOL needBlend = NO; + uint32_t iterIndex = 0; + uint32_t lastBlendIndex = 0; + WebPIterator iter = {0}; + if (WebPDemuxGetFrame(demuxer, 1, &iter)) { // one-based index... + do { + _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new]; + [frames addObject:frame]; + if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { + frame.dispose = YYImageDisposeBackground; + } + if (iter.blend_method == WEBP_MUX_BLEND) { + frame.blend = YYImageBlendOver; + } + + int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); + int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); + frame.index = iterIndex; + frame.duration = iter.duration / 1000.0; + frame.width = iter.width; + frame.height = iter.height; + frame.hasAlpha = iter.has_alpha; + frame.blend = iter.blend_method == WEBP_MUX_BLEND; + frame.offsetX = iter.x_offset; + frame.offsetY = canvasHeight - iter.y_offset - iter.height; + + BOOL sizeEqualsToCanvas = (iter.width == canvasWidth && iter.height == canvasHeight); + BOOL offsetIsZero = (iter.x_offset == 0 && iter.y_offset == 0); + frame.isFullSize = (sizeEqualsToCanvas && offsetIsZero); + + if ((!frame.blend || !frame.hasAlpha) && frame.isFullSize) { + frame.blendFromIndex = lastBlendIndex = iterIndex; + } else { + if (frame.dispose && frame.isFullSize) { + frame.blendFromIndex = lastBlendIndex; + lastBlendIndex = iterIndex + 1; + } else { + frame.blendFromIndex = lastBlendIndex; + } + } + if (frame.index != frame.blendFromIndex) needBlend = YES; + iterIndex++; + } while (WebPDemuxNextFrame(&iter)); + WebPDemuxReleaseIterator(&iter); + } + if (frames.count != webpFrameCount) { + WebPDemuxDelete(demuxer); + return; + } + + _width = canvasWidth; + _height = canvasHeight; + _frameCount = frames.count; + _loopCount = webpLoopCount; + _needBlend = needBlend; + _webpSource = demuxer; + dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER); + _frames = frames; + dispatch_semaphore_signal(_framesLock); +#else + static const char *func = __FUNCTION__; + static const int line = __LINE__; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"[%s: %d] WebP is not available, check the documentation to see how to install WebP component: https://github.com/ibireme/YYImage#installation", func, line); + }); +#endif +} + +- (void)_updateSourceAPNG { + /* + APNG extends PNG format to support animation, it was supported by ImageIO + since iOS 8. + + We use a custom APNG decoder to make APNG available in old system, so we + ignore the ImageIO's APNG frame info. Typically the custom decoder is a bit + faster than ImageIO. + */ + + yy_png_info_release(_apngSource); + _apngSource = nil; + + [self _updateSourceImageIO]; // decode first frame + if (_frameCount == 0) return; // png decode failed + if (!_finalized) return; // ignore multi-frame before finalized + + yy_png_info *apng = yy_png_info_create(_data.bytes, (uint32_t)_data.length); + if (!apng) return; // apng decode failed + if (apng->apng_frame_num == 0 || + (apng->apng_frame_num == 1 && apng->apng_first_frame_is_cover)) { + yy_png_info_release(apng); + return; // no animation + } + if (_source) { // apng decode succeed, no longer need image souce + CFRelease(_source); + _source = NULL; + } + + uint32_t canvasWidth = apng->header.width; + uint32_t canvasHeight = apng->header.height; + NSMutableArray *frames = [NSMutableArray new]; + BOOL needBlend = NO; + uint32_t lastBlendIndex = 0; + for (uint32_t i = 0; i < apng->apng_frame_num; i++) { + _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new]; + [frames addObject:frame]; + + yy_png_frame_info *fi = apng->apng_frames + i; + frame.index = i; + frame.duration = yy_png_delay_to_seconds(fi->frame_control.delay_num, fi->frame_control.delay_den); + frame.hasAlpha = YES; + frame.width = fi->frame_control.width; + frame.height = fi->frame_control.height; + frame.offsetX = fi->frame_control.x_offset; + frame.offsetY = canvasHeight - fi->frame_control.y_offset - fi->frame_control.height; + + BOOL sizeEqualsToCanvas = (frame.width == canvasWidth && frame.height == canvasHeight); + BOOL offsetIsZero = (fi->frame_control.x_offset == 0 && fi->frame_control.y_offset == 0); + frame.isFullSize = (sizeEqualsToCanvas && offsetIsZero); + + switch (fi->frame_control.dispose_op) { + case YY_PNG_DISPOSE_OP_BACKGROUND: { + frame.dispose = YYImageDisposeBackground; + } break; + case YY_PNG_DISPOSE_OP_PREVIOUS: { + frame.dispose = YYImageDisposePrevious; + } break; + default: { + frame.dispose = YYImageDisposeNone; + } break; + } + switch (fi->frame_control.blend_op) { + case YY_PNG_BLEND_OP_OVER: { + frame.blend = YYImageBlendOver; + } break; + + default: { + frame.blend = YYImageBlendNone; + } break; + } + + if (frame.blend == YYImageBlendNone && frame.isFullSize) { + frame.blendFromIndex = i; + if (frame.dispose != YYImageDisposePrevious) lastBlendIndex = i; + } else { + if (frame.dispose == YYImageDisposeBackground && frame.isFullSize) { + frame.blendFromIndex = lastBlendIndex; + lastBlendIndex = i + 1; + } else { + frame.blendFromIndex = lastBlendIndex; + } + } + if (frame.index != frame.blendFromIndex) needBlend = YES; + } + + _width = canvasWidth; + _height = canvasHeight; + _frameCount = frames.count; + _loopCount = apng->apng_loop_num; + _needBlend = needBlend; + _apngSource = apng; + dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER); + _frames = frames; + dispatch_semaphore_signal(_framesLock); +} + +- (void)_updateSourceImageIO { + _width = 0; + _height = 0; + _orientation = UIImageOrientationUp; + _loopCount = 0; + dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER); + _frames = nil; + dispatch_semaphore_signal(_framesLock); + + if (!_source) { + if (_finalized) { + _source = CGImageSourceCreateWithData((__bridge CFDataRef)_data, NULL); + } else { + _source = CGImageSourceCreateIncremental(NULL); + if (_source) CGImageSourceUpdateData(_source, (__bridge CFDataRef)_data, false); + } + } else { + CGImageSourceUpdateData(_source, (__bridge CFDataRef)_data, _finalized); + } + if (!_source) return; + + _frameCount = CGImageSourceGetCount(_source); + if (_frameCount == 0) return; + + if (!_finalized) { // ignore multi-frame before finalized + _frameCount = 1; + } else { + if (_type == YYImageTypePNG) { // use custom apng decoder and ignore multi-frame + _frameCount = 1; + } + if (_type == YYImageTypeGIF) { // get gif loop count + CFDictionaryRef properties = CGImageSourceCopyProperties(_source, NULL); + if (properties) { + CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary); + if (gif) { + CFTypeRef loop = CFDictionaryGetValue(gif, kCGImagePropertyGIFLoopCount); + if (loop) CFNumberGetValue(loop, kCFNumberNSIntegerType, &_loopCount); + } + CFRelease(properties); + } + } + } + + /* + ICO, GIF, APNG may contains multi-frame. + */ + NSMutableArray *frames = [NSMutableArray new]; + for (NSUInteger i = 0; i < _frameCount; i++) { + _YYImageDecoderFrame *frame = [_YYImageDecoderFrame new]; + frame.index = i; + frame.blendFromIndex = i; + frame.hasAlpha = YES; + frame.isFullSize = YES; + [frames addObject:frame]; + + CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_source, i, NULL); + if (properties) { + NSTimeInterval duration = 0; + NSInteger orientationValue = 0, width = 0, height = 0; + CFTypeRef value = NULL; + + value = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth); + if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &width); + value = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight); + if (value) CFNumberGetValue(value, kCFNumberNSIntegerType, &height); + if (_type == YYImageTypeGIF) { + CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary); + if (gif) { + // Use the unclamped frame delay if it exists. + value = CFDictionaryGetValue(gif, kCGImagePropertyGIFUnclampedDelayTime); + if (!value) { + // Fall back to the clamped frame delay if the unclamped frame delay does not exist. + value = CFDictionaryGetValue(gif, kCGImagePropertyGIFDelayTime); + } + if (value) CFNumberGetValue(value, kCFNumberDoubleType, &duration); + } + } + + frame.width = width; + frame.height = height; + frame.duration = duration; + + if (i == 0 && _width + _height == 0) { // init first frame + _width = width; + _height = height; + value = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); + if (value) { + CFNumberGetValue(value, kCFNumberNSIntegerType, &orientationValue); + _orientation = YYUIImageOrientationFromEXIFValue(orientationValue); + } + } + CFRelease(properties); + } + } + dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER); + _frames = frames; + dispatch_semaphore_signal(_framesLock); +} + +- (CGImageRef)_newUnblendedImageAtIndex:(NSUInteger)index + extendToCanvas:(BOOL)extendToCanvas + decoded:(BOOL *)decoded CF_RETURNS_RETAINED { + + if (!_finalized && index > 0) return NULL; + if (_frames.count <= index) return NULL; + _YYImageDecoderFrame *frame = _frames[index]; + + if (_source) { + CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_source, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)}); + if (imageRef && extendToCanvas) { + size_t width = CGImageGetWidth(imageRef); + size_t height = CGImageGetHeight(imageRef); + if (width == _width && height == _height) { + CGImageRef imageRefExtended = YYCGImageCreateDecodedCopy(imageRef, YES); + if (imageRefExtended) { + CFRelease(imageRef); + imageRef = imageRefExtended; + if (decoded) *decoded = YES; + } + } else { + CGContextRef context = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); + if (context) { + CGContextDrawImage(context, CGRectMake(0, _height - height, width, height), imageRef); + CGImageRef imageRefExtended = CGBitmapContextCreateImage(context); + CFRelease(context); + if (imageRefExtended) { + CFRelease(imageRef); + imageRef = imageRefExtended; + if (decoded) *decoded = YES; + } + } + } + } + return imageRef; + } + + if (_apngSource) { + uint32_t size = 0; + uint8_t *bytes = yy_png_copy_frame_data_at_index(_data.bytes, _apngSource, (uint32_t)index, &size); + if (!bytes) return NULL; + CGDataProviderRef provider = CGDataProviderCreateWithData(bytes, bytes, size, YYCGDataProviderReleaseDataCallback); + if (!provider) { + free(bytes); + return NULL; + } + bytes = NULL; // hold by provider + + CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL); + if (!source) { + CFRelease(provider); + return NULL; + } + CFRelease(provider); + + if(CGImageSourceGetCount(source) < 1) { + CFRelease(source); + return NULL; + } + + CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(YES)}); + CFRelease(source); + if (!imageRef) return NULL; + if (extendToCanvas) { + CGContextRef context = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); //bgrA + if (context) { + CGContextDrawImage(context, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), imageRef); + CFRelease(imageRef); + imageRef = CGBitmapContextCreateImage(context); + CFRelease(context); + if (decoded) *decoded = YES; + } + } + return imageRef; + } + +#if YYIMAGE_WEBP_ENABLED + if (_webpSource) { + WebPIterator iter; + if (!WebPDemuxGetFrame(_webpSource, (int)(index + 1), &iter)) return NULL; // demux webp frame data + // frame numbers are one-based in webp -----------^ + + int frameWidth = iter.width; + int frameHeight = iter.height; + if (frameWidth < 1 || frameHeight < 1) return NULL; + + int width = extendToCanvas ? (int)_width : frameWidth; + int height = extendToCanvas ? (int)_height : frameHeight; + if (width > _width || height > _height) return NULL; + + const uint8_t *payload = iter.fragment.bytes; + size_t payloadSize = iter.fragment.size; + + WebPDecoderConfig config; + if (!WebPInitDecoderConfig(&config)) { + WebPDemuxReleaseIterator(&iter); + return NULL; + } + if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) { + WebPDemuxReleaseIterator(&iter); + return NULL; + } + + size_t bitsPerComponent = 8; + size_t bitsPerPixel = 32; + size_t bytesPerRow = YYImageByteAlign(bitsPerPixel / 8 * width, 32); + size_t length = bytesPerRow * height; + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; //bgrA + + void *pixels = calloc(1, length); + if (!pixels) { + WebPDemuxReleaseIterator(&iter); + return NULL; + } + + config.output.colorspace = MODE_bgrA; + config.output.is_external_memory = 1; + config.output.u.RGBA.rgba = pixels; + config.output.u.RGBA.stride = (int)bytesPerRow; + config.output.u.RGBA.size = length; + VP8StatusCode result = WebPDecode(payload, payloadSize, &config); // decode + if ((result != VP8_STATUS_OK) && (result != VP8_STATUS_NOT_ENOUGH_DATA)) { + WebPDemuxReleaseIterator(&iter); + free(pixels); + return NULL; + } + WebPDemuxReleaseIterator(&iter); + + if (extendToCanvas && (iter.x_offset != 0 || iter.y_offset != 0)) { + void *tmp = calloc(1, length); + if (tmp) { + vImage_Buffer src = {pixels, height, width, bytesPerRow}; + vImage_Buffer dest = {tmp, height, width, bytesPerRow}; + vImage_CGAffineTransform transform = {1, 0, 0, 1, iter.x_offset, -iter.y_offset}; + uint8_t backColor[4] = {0}; + vImage_Error error = vImageAffineWarpCG_ARGB8888(&src, &dest, NULL, &transform, backColor, kvImageBackgroundColorFill); + if (error == kvImageNoError) { + memcpy(pixels, tmp, length); + } + free(tmp); + } + } + + CGDataProviderRef provider = CGDataProviderCreateWithData(pixels, pixels, length, YYCGDataProviderReleaseDataCallback); + if (!provider) { + free(pixels); + return NULL; + } + pixels = NULL; // hold by provider + + CGImageRef image = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, YYCGColorSpaceGetDeviceRGB(), bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault); + CFRelease(provider); + if (decoded) *decoded = YES; + return image; + } +#endif + + return NULL; +} + +- (BOOL)_createBlendContextIfNeeded { + if (!_blendCanvas) { + _blendFrameIndex = NSNotFound; + _blendCanvas = CGBitmapContextCreate(NULL, _width, _height, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); + } + BOOL suc = _blendCanvas != NULL; + return suc; +} + +- (void)_blendImageWithFrame:(_YYImageDecoderFrame *)frame { + if (frame.dispose == YYImageDisposePrevious) { + // nothing + } else if (frame.dispose == YYImageDisposeBackground) { + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + } else { // no dispose + if (frame.blend == YYImageBlendOver) { + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + } else { + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + } + } +} + +- (CGImageRef)_newBlendedImageWithFrame:(_YYImageDecoderFrame *)frame CF_RETURNS_RETAINED{ + CGImageRef imageRef = NULL; + if (frame.dispose == YYImageDisposePrevious) { + if (frame.blend == YYImageBlendOver) { + CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas); + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + imageRef = CGBitmapContextCreateImage(_blendCanvas); + CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height)); + if (previousImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage); + CFRelease(previousImage); + } + } else { + CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas); + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + imageRef = CGBitmapContextCreateImage(_blendCanvas); + CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height)); + if (previousImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage); + CFRelease(previousImage); + } + } + } else if (frame.dispose == YYImageDisposeBackground) { + if (frame.blend == YYImageBlendOver) { + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + imageRef = CGBitmapContextCreateImage(_blendCanvas); + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + } else { + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + imageRef = CGBitmapContextCreateImage(_blendCanvas); + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + } + } else { // no dispose + if (frame.blend == YYImageBlendOver) { + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + imageRef = CGBitmapContextCreateImage(_blendCanvas); + } else { + CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL]; + if (unblendImage) { + CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height)); + CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage); + CFRelease(unblendImage); + } + imageRef = CGBitmapContextCreateImage(_blendCanvas); + } + } + return imageRef; +} + +@end + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Encoder + +@implementation YYImageEncoder { + NSMutableArray *_images; + NSMutableArray *_durations; +} + +- (instancetype)init { + @throw [NSException exceptionWithName:@"YYImageEncoder init error" reason:@"YYImageEncoder must be initialized with a type. Use 'initWithType:' instead." userInfo:nil]; + return [self initWithType:YYImageTypeUnknown]; +} + +- (instancetype)initWithType:(YYImageType)type { + if (type == YYImageTypeUnknown || type >= YYImageTypeOther) { + NSLog(@"[%s: %d] Unsupported image type:%d",__FUNCTION__, __LINE__, (int)type); + return nil; + } + +#if !YYIMAGE_WEBP_ENABLED + if (type == YYImageTypeWebP) { + NSLog(@"[%s: %d] WebP is not available, check the documentation to see how to install WebP component: https://github.com/ibireme/YYImage#installation", __FUNCTION__, __LINE__); + return nil; + } +#endif + + self = [super init]; + if (!self) return nil; + _type = type; + _images = [NSMutableArray new]; + _durations = [NSMutableArray new]; + + switch (type) { + case YYImageTypeJPEG: + case YYImageTypeJPEG2000: { + _quality = 0.9; + } break; + case YYImageTypeTIFF: + case YYImageTypeBMP: + case YYImageTypeGIF: + case YYImageTypeICO: + case YYImageTypeICNS: + case YYImageTypePNG: { + _quality = 1; + _lossless = YES; + } break; + case YYImageTypeWebP: { + _quality = 0.8; + } break; + default: + break; + } + + return self; +} + +- (void)setQuality:(CGFloat)quality { + _quality = quality < 0 ? 0 : quality > 1 ? 1 : quality; +} + +- (void)addImage:(UIImage *)image duration:(NSTimeInterval)duration { + if (!image.CGImage) return; + duration = duration < 0 ? 0 : duration; + [_images addObject:image]; + [_durations addObject:@(duration)]; +} + +- (void)addImageWithData:(NSData *)data duration:(NSTimeInterval)duration { + if (data.length == 0) return; + duration = duration < 0 ? 0 : duration; + [_images addObject:data]; + [_durations addObject:@(duration)]; +} + +- (void)addImageWithFile:(NSString *)path duration:(NSTimeInterval)duration { + if (path.length == 0) return; + duration = duration < 0 ? 0 : duration; + NSURL *url = [NSURL URLWithString:path]; + if (!url) return; + [_images addObject:url]; + [_durations addObject:@(duration)]; +} + +- (BOOL)_imageIOAvaliable { + switch (_type) { + case YYImageTypeJPEG: + case YYImageTypeJPEG2000: + case YYImageTypeTIFF: + case YYImageTypeBMP: + case YYImageTypeICO: + case YYImageTypeICNS: + case YYImageTypeGIF: { + return _images.count > 0; + } break; + case YYImageTypePNG: { + return _images.count == 1; + } break; + case YYImageTypeWebP: { + return NO; + } break; + default: return NO; + } +} + +- (CGImageDestinationRef)_newImageDestination:(id)dest imageCount:(NSUInteger)count CF_RETURNS_RETAINED { + if (!dest) return nil; + CGImageDestinationRef destination = NULL; + if ([dest isKindOfClass:[NSString class]]) { + NSURL *url = [[NSURL alloc] initFileURLWithPath:dest]; + if (url) { + destination = CGImageDestinationCreateWithURL((CFURLRef)url, YYImageTypeToUTType(_type), count, NULL); + } + } else if ([dest isKindOfClass:[NSMutableData class]]) { + destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest, YYImageTypeToUTType(_type), count, NULL); + } + return destination; +} + +- (void)_encodeImageWithDestination:(CGImageDestinationRef)destination imageCount:(NSUInteger)count { + if (_type == YYImageTypeGIF) { + NSDictionary *gifProperty = @{(__bridge id)kCGImagePropertyGIFDictionary: + @{(__bridge id)kCGImagePropertyGIFLoopCount: @(_loopCount)}}; + CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)gifProperty); + } + + for (int i = 0; i < count; i++) { + @autoreleasepool { + id imageSrc = _images[i]; + NSDictionary *frameProperty = NULL; + if (_type == YYImageTypeGIF && count > 1) { + frameProperty = @{(NSString *)kCGImagePropertyGIFDictionary : @{(NSString *) kCGImagePropertyGIFDelayTime:_durations[i]}}; + } else { + frameProperty = @{(id)kCGImageDestinationLossyCompressionQuality : @(_quality)}; + } + + if ([imageSrc isKindOfClass:[UIImage class]]) { + UIImage *image = imageSrc; + if (image.imageOrientation != UIImageOrientationUp && image.CGImage) { + CGBitmapInfo info = CGImageGetBitmapInfo(image.CGImage) | CGImageGetAlphaInfo(image.CGImage); + CGImageRef rotated = YYCGImageCreateCopyWithOrientation(image.CGImage, image.imageOrientation, info); + if (rotated) { + image = [UIImage imageWithCGImage:rotated]; + CFRelease(rotated); + } + } + if (image.CGImage) CGImageDestinationAddImage(destination, ((UIImage *)imageSrc).CGImage, (CFDictionaryRef)frameProperty); + } else if ([imageSrc isKindOfClass:[NSURL class]]) { + CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageSrc, NULL); + if (source) { + CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef)frameProperty); + CFRelease(source); + } + } else if ([imageSrc isKindOfClass:[NSData class]]) { + CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageSrc, NULL); + if (source) { + CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef)frameProperty); + CFRelease(source); + } + } + } + } +} + +- (CGImageRef)_newCGImageFromIndex:(NSUInteger)index decoded:(BOOL)decoded CF_RETURNS_RETAINED { + UIImage *image = nil; + id imageSrc= _images[index]; + if ([imageSrc isKindOfClass:[UIImage class]]) { + image = imageSrc; + } else if ([imageSrc isKindOfClass:[NSURL class]]) { + image = [UIImage imageWithContentsOfFile:((NSURL *)imageSrc).absoluteString]; + } else if ([imageSrc isKindOfClass:[NSData class]]) { + image = [UIImage imageWithData:imageSrc]; + } + if (!image) return NULL; + CGImageRef imageRef = image.CGImage; + if (!imageRef) return NULL; + if (image.imageOrientation != UIImageOrientationUp) { + return YYCGImageCreateCopyWithOrientation(imageRef, image.imageOrientation, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); + } + if (decoded) { + return YYCGImageCreateDecodedCopy(imageRef, YES); + } + return (CGImageRef)CFRetain(imageRef); +} + +- (NSData *)_encodeWithImageIO { + NSMutableData *data = [NSMutableData new]; + NSUInteger count = _type == YYImageTypeGIF ? _images.count : 1; + CGImageDestinationRef destination = [self _newImageDestination:data imageCount:count]; + BOOL suc = NO; + if (destination) { + [self _encodeImageWithDestination:destination imageCount:count]; + suc = CGImageDestinationFinalize(destination); + CFRelease(destination); + } + if (suc && data.length > 0) { + return data; + } else { + return nil; + } +} + +- (BOOL)_encodeWithImageIO:(NSString *)path { + NSUInteger count = _type == YYImageTypeGIF ? _images.count : 1; + CGImageDestinationRef destination = [self _newImageDestination:path imageCount:count]; + BOOL suc = NO; + if (destination) { + [self _encodeImageWithDestination:destination imageCount:count]; + suc = CGImageDestinationFinalize(destination); + CFRelease(destination); + } + return suc; +} + +- (NSData *)_encodeAPNG { + // encode APNG (ImageIO doesn't support APNG encoding, so we use a custom encoder) + NSMutableArray *pngDatas = [NSMutableArray new]; + NSMutableArray *pngSizes = [NSMutableArray new]; + NSUInteger canvasWidth = 0, canvasHeight = 0; + for (int i = 0; i < _images.count; i++) { + CGImageRef decoded = [self _newCGImageFromIndex:i decoded:YES]; + if (!decoded) return nil; + CGSize size = CGSizeMake(CGImageGetWidth(decoded), CGImageGetHeight(decoded)); + [pngSizes addObject:[NSValue valueWithCGSize:size]]; + if (canvasWidth < size.width) canvasWidth = size.width; + if (canvasHeight < size.height) canvasHeight = size.height; + CFDataRef frameData = YYCGImageCreateEncodedData(decoded, YYImageTypePNG, 1); + CFRelease(decoded); + if (!frameData) return nil; + [pngDatas addObject:(__bridge id)(frameData)]; + CFRelease(frameData); + if (size.width < 1 || size.height < 1) return nil; + } + CGSize firstFrameSize = [(NSValue *)[pngSizes firstObject] CGSizeValue]; + if (firstFrameSize.width < canvasWidth || firstFrameSize.height < canvasHeight) { + CGImageRef decoded = [self _newCGImageFromIndex:0 decoded:YES]; + if (!decoded) return nil; + CGContextRef context = CGBitmapContextCreate(NULL, canvasWidth, canvasHeight, 8, + 0, YYCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); + if (!context) { + CFRelease(decoded); + return nil; + } + CGContextDrawImage(context, CGRectMake(0, canvasHeight - firstFrameSize.height, firstFrameSize.width, firstFrameSize.height), decoded); + CFRelease(decoded); + CGImageRef extendedImage = CGBitmapContextCreateImage(context); + CFRelease(context); + if (!extendedImage) return nil; + CFDataRef frameData = YYCGImageCreateEncodedData(extendedImage, YYImageTypePNG, 1); + if (!frameData) { + CFRelease(extendedImage); + return nil; + } + pngDatas[0] = (__bridge id)(frameData); + CFRelease(frameData); + } + + NSData *firstFrameData = pngDatas[0]; + yy_png_info *info = yy_png_info_create(firstFrameData.bytes, (uint32_t)firstFrameData.length); + if (!info) return nil; + NSMutableData *result = [NSMutableData new]; + BOOL insertBefore = NO, insertAfter = NO; + uint32_t apngSequenceIndex = 0; + + uint32_t png_header[2]; + png_header[0] = YY_FOUR_CC(0x89, 0x50, 0x4E, 0x47); + png_header[1] = YY_FOUR_CC(0x0D, 0x0A, 0x1A, 0x0A); + + [result appendBytes:png_header length:8]; + + for (int i = 0; i < info->chunk_num; i++) { + yy_png_chunk_info *chunk = info->chunks + i; + + if (!insertBefore && chunk->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) { + insertBefore = YES; + // insert acTL (APNG Control) + uint32_t acTL[5] = {0}; + acTL[0] = yy_swap_endian_uint32(8); //length + acTL[1] = YY_FOUR_CC('a', 'c', 'T', 'L'); // fourcc + acTL[2] = yy_swap_endian_uint32((uint32_t)pngDatas.count); // num frames + acTL[3] = yy_swap_endian_uint32((uint32_t)_loopCount); // num plays + acTL[4] = yy_swap_endian_uint32((uint32_t)crc32(0, (const Bytef *)(acTL + 1), 12)); //crc32 + [result appendBytes:acTL length:20]; + + // insert fcTL (first frame control) + yy_png_chunk_fcTL chunk_fcTL = {0}; + chunk_fcTL.sequence_number = apngSequenceIndex; + chunk_fcTL.width = (uint32_t)firstFrameSize.width; + chunk_fcTL.height = (uint32_t)firstFrameSize.height; + yy_png_delay_to_fraction([(NSNumber *)_durations[0] doubleValue], &chunk_fcTL.delay_num, &chunk_fcTL.delay_den); + chunk_fcTL.delay_num = chunk_fcTL.delay_num; + chunk_fcTL.delay_den = chunk_fcTL.delay_den; + chunk_fcTL.dispose_op = YY_PNG_DISPOSE_OP_BACKGROUND; + chunk_fcTL.blend_op = YY_PNG_BLEND_OP_SOURCE; + + uint8_t fcTL[38] = {0}; + *((uint32_t *)fcTL) = yy_swap_endian_uint32(26); //length + *((uint32_t *)(fcTL + 4)) = YY_FOUR_CC('f', 'c', 'T', 'L'); // fourcc + yy_png_chunk_fcTL_write(&chunk_fcTL, fcTL + 8); + *((uint32_t *)(fcTL + 34)) = yy_swap_endian_uint32((uint32_t)crc32(0, (const Bytef *)(fcTL + 4), 30)); + [result appendBytes:fcTL length:38]; + + apngSequenceIndex++; + } + + if (!insertAfter && insertBefore && chunk->fourcc != YY_FOUR_CC('I', 'D', 'A', 'T')) { + insertAfter = YES; + // insert fcTL and fdAT (APNG frame control and data) + + for (int i = 1; i < pngDatas.count; i++) { + NSData *frameData = pngDatas[i]; + yy_png_info *frame = yy_png_info_create(frameData.bytes, (uint32_t)frameData.length); + if (!frame) { + yy_png_info_release(info); + return nil; + } + + // insert fcTL (first frame control) + yy_png_chunk_fcTL chunk_fcTL = {0}; + chunk_fcTL.sequence_number = apngSequenceIndex; + chunk_fcTL.width = frame->header.width; + chunk_fcTL.height = frame->header.height; + yy_png_delay_to_fraction([(NSNumber *)_durations[i] doubleValue], &chunk_fcTL.delay_num, &chunk_fcTL.delay_den); + chunk_fcTL.delay_num = chunk_fcTL.delay_num; + chunk_fcTL.delay_den = chunk_fcTL.delay_den; + chunk_fcTL.dispose_op = YY_PNG_DISPOSE_OP_BACKGROUND; + chunk_fcTL.blend_op = YY_PNG_BLEND_OP_SOURCE; + + uint8_t fcTL[38] = {0}; + *((uint32_t *)fcTL) = yy_swap_endian_uint32(26); //length + *((uint32_t *)(fcTL + 4)) = YY_FOUR_CC('f', 'c', 'T', 'L'); // fourcc + yy_png_chunk_fcTL_write(&chunk_fcTL, fcTL + 8); + *((uint32_t *)(fcTL + 34)) = yy_swap_endian_uint32((uint32_t)crc32(0, (const Bytef *)(fcTL + 4), 30)); + [result appendBytes:fcTL length:38]; + + apngSequenceIndex++; + + // insert fdAT (frame data) + for (int d = 0; d < frame->chunk_num; d++) { + yy_png_chunk_info *dchunk = frame->chunks + d; + if (dchunk->fourcc == YY_FOUR_CC('I', 'D', 'A', 'T')) { + uint32_t length = yy_swap_endian_uint32(dchunk->length + 4); + [result appendBytes:&length length:4]; //length + uint32_t fourcc = YY_FOUR_CC('f', 'd', 'A', 'T'); + [result appendBytes:&fourcc length:4]; //fourcc + uint32_t sq = yy_swap_endian_uint32(apngSequenceIndex); + [result appendBytes:&sq length:4]; //data (sq) + [result appendBytes:(((uint8_t *)frameData.bytes) + dchunk->offset + 8) length:dchunk->length]; //data + uint8_t *bytes = ((uint8_t *)result.bytes) + result.length - dchunk->length - 8; + uint32_t crc = yy_swap_endian_uint32((uint32_t)crc32(0, bytes, dchunk->length + 8)); + [result appendBytes:&crc length:4]; //crc + + apngSequenceIndex++; + } + } + yy_png_info_release(frame); + } + } + + [result appendBytes:((uint8_t *)firstFrameData.bytes) + chunk->offset length:chunk->length + 12]; + } + yy_png_info_release(info); + return result; +} + +- (NSData *)_encodeWebP { +#if YYIMAGE_WEBP_ENABLED + // encode webp + NSMutableArray *webpDatas = [NSMutableArray new]; + for (NSUInteger i = 0; i < _images.count; i++) { + CGImageRef image = [self _newCGImageFromIndex:i decoded:NO]; + if (!image) return nil; + CFDataRef frameData = YYCGImageCreateEncodedWebPData(image, _lossless, _quality, 4, YYImagePresetDefault); + CFRelease(image); + if (!frameData) return nil; + [webpDatas addObject:(__bridge id)frameData]; + CFRelease(frameData); + } + if (webpDatas.count == 1) { + return webpDatas.firstObject; + } else { + // multi-frame webp + WebPMux *mux = WebPMuxNew(); + if (!mux) return nil; + for (NSUInteger i = 0; i < _images.count; i++) { + NSData *data = webpDatas[i]; + NSNumber *duration = _durations[i]; + WebPMuxFrameInfo frame = {0}; + frame.bitstream.bytes = data.bytes; + frame.bitstream.size = data.length; + frame.duration = (int)(duration.floatValue * 1000.0); + frame.id = WEBP_CHUNK_ANMF; + frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND; + frame.blend_method = WEBP_MUX_NO_BLEND; + if (WebPMuxPushFrame(mux, &frame, 0) != WEBP_MUX_OK) { + WebPMuxDelete(mux); + return nil; + } + } + + WebPMuxAnimParams params = {(uint32_t)0, (int)_loopCount}; + if (WebPMuxSetAnimationParams(mux, ¶ms) != WEBP_MUX_OK) { + WebPMuxDelete(mux); + return nil; + } + + WebPData output_data; + WebPMuxError error = WebPMuxAssemble(mux, &output_data); + WebPMuxDelete(mux); + if (error != WEBP_MUX_OK) { + return nil; + } + NSData *result = [NSData dataWithBytes:output_data.bytes length:output_data.size]; + WebPDataClear(&output_data); + return result.length ? result : nil; + } +#else + return nil; +#endif +} +- (NSData *)encode { + if (_images.count == 0) return nil; + + if ([self _imageIOAvaliable]) return [self _encodeWithImageIO]; + if (_type == YYImageTypePNG) return [self _encodeAPNG]; + if (_type == YYImageTypeWebP) return [self _encodeWebP]; + return nil; +} + +- (BOOL)encodeToFile:(NSString *)path { + if (_images.count == 0 || path.length == 0) return NO; + + if ([self _imageIOAvaliable]) return [self _encodeWithImageIO:path]; + NSData *data = [self encode]; + if (!data) return NO; + return [data writeToFile:path atomically:YES]; +} + ++ (NSData *)encodeImage:(UIImage *)image type:(YYImageType)type quality:(CGFloat)quality { + YYImageEncoder *encoder = [[YYImageEncoder alloc] initWithType:type]; + encoder.quality = quality; + [encoder addImage:image duration:0]; + return [encoder encode]; +} + ++ (NSData *)encodeImageWithDecoder:(YYImageDecoder *)decoder type:(YYImageType)type quality:(CGFloat)quality { + if (!decoder || decoder.frameCount == 0) return nil; + YYImageEncoder *encoder = [[YYImageEncoder alloc] initWithType:type]; + encoder.quality = quality; + for (int i = 0; i < decoder.frameCount; i++) { + UIImage *frame = [decoder frameAtIndex:i decodeForDisplay:YES].image; + [encoder addImageWithData:UIImagePNGRepresentation(frame) duration:[decoder frameDurationAtIndex:i]]; + } + return encoder.encode; +} + +@end + + +@implementation UIImage (YYImageCoder) + +- (instancetype)yy_imageByDecoded { + if (self.yy_isDecodedForDisplay) return self; + CGImageRef imageRef = self.CGImage; + if (!imageRef) return self; + CGImageRef newImageRef = YYCGImageCreateDecodedCopy(imageRef, YES); + if (!newImageRef) return self; + UIImage *newImage = [[self.class alloc] initWithCGImage:newImageRef scale:self.scale orientation:self.imageOrientation]; + CGImageRelease(newImageRef); + if (!newImage) newImage = self; // decode failed, return self. + newImage.yy_isDecodedForDisplay = YES; + return newImage; +} + +- (BOOL)yy_isDecodedForDisplay { + if (self.images.count > 1 || [self isKindOfClass:[YYSpriteSheetImage class]]) return YES; + NSNumber *num = objc_getAssociatedObject(self, @selector(yy_isDecodedForDisplay)); + return [num boolValue]; +} + +- (void)setYy_isDecodedForDisplay:(BOOL)isDecodedForDisplay { + objc_setAssociatedObject(self, @selector(yy_isDecodedForDisplay), @(isDecodedForDisplay), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (void)yy_saveToAlbumWithCompletionBlock:(void(^)(NSURL *assetURL, NSError *error))completionBlock { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSData *data = [self _yy_dataRepresentationForSystem:YES]; + ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; + [library writeImageDataToSavedPhotosAlbum:data metadata:nil completionBlock:^(NSURL *assetURL, NSError *error){ + if (!completionBlock) return; + if (pthread_main_np()) { + completionBlock(assetURL, error); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + completionBlock(assetURL, error); + }); + } + }]; + }); +} + +- (NSData *)yy_imageDataRepresentation { + return [self _yy_dataRepresentationForSystem:NO]; +} + +/// @param forSystem YES: used for system album (PNG/JPEG/GIF), NO: used for YYImage (PNG/JPEG/GIF/WebP) +- (NSData *)_yy_dataRepresentationForSystem:(BOOL)forSystem { + NSData *data = nil; + if ([self isKindOfClass:[YYImage class]]) { + YYImage *image = (id)self; + if (image.animatedImageData) { + if (forSystem) { // system only support GIF and PNG + if (image.animatedImageType == YYImageTypeGIF || + image.animatedImageType == YYImageTypePNG) { + data = image.animatedImageData; + } + } else { + data = image.animatedImageData; + } + } + } + if (!data) { + CGImageRef imageRef = self.CGImage ? (CGImageRef)CFRetain(self.CGImage) : nil; + if (imageRef) { + CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); + CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask; + BOOL hasAlpha = NO; + if (alphaInfo == kCGImageAlphaPremultipliedLast || + alphaInfo == kCGImageAlphaPremultipliedFirst || + alphaInfo == kCGImageAlphaLast || + alphaInfo == kCGImageAlphaFirst) { + hasAlpha = YES; + } + if (self.imageOrientation != UIImageOrientationUp) { + CGImageRef rotated = YYCGImageCreateCopyWithOrientation(imageRef, self.imageOrientation, bitmapInfo | alphaInfo); + if (rotated) { + CFRelease(imageRef); + imageRef = rotated; + } + } + @autoreleasepool { + UIImage *newImage = [UIImage imageWithCGImage:imageRef]; + if (newImage) { + if (hasAlpha) { + data = UIImagePNGRepresentation([UIImage imageWithCGImage:imageRef]); + } else { + data = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.9); // same as Apple's example + } + } + } + CFRelease(imageRef); + } + } + if (!data) { + data = UIImagePNGRepresentation(self); + } + return data; +} + +@end diff --git a/Example/Pods/YYImage/YYImage/YYSpriteSheetImage.h b/Example/Pods/YYImage/YYImage/YYSpriteSheetImage.h new file mode 100644 index 00000000..403bbf57 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYSpriteSheetImage.h @@ -0,0 +1,104 @@ +// +// YYSpriteImage.h +// YYImage +// +// Created by ibireme on 15/4/21. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#elif __has_include() +#import +#else +#import "YYAnimatedImageView.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + An image to display sprite sheet animation. + + @discussion It is a fully compatible `UIImage` subclass. + The animation can be played by YYAnimatedImageView. + + Sample Code: + + // 8 * 12 sprites in a single sheet image + UIImage *spriteSheet = [UIImage imageNamed:@"sprite-sheet"]; + NSMutableArray *contentRects = [NSMutableArray new]; + NSMutableArray *durations = [NSMutableArray new]; + for (int j = 0; j < 12; j++) { + for (int i = 0; i < 8; i++) { + CGRect rect; + rect.size = CGSizeMake(img.size.width / 8, img.size.height / 12); + rect.origin.x = img.size.width / 8 * i; + rect.origin.y = img.size.height / 12 * j; + [contentRects addObject:[NSValue valueWithCGRect:rect]]; + [durations addObject:@(1 / 60.0)]; + } + } + YYSpriteSheetImage *sprite; + sprite = [[YYSpriteSheetImage alloc] initWithSpriteSheetImage:img + contentRects:contentRects + frameDurations:durations + loopCount:0]; + YYAnimatedImageView *imgView = [YYAnimatedImageView new]; + imgView.size = CGSizeMake(img.size.width / 8, img.size.height / 12); + imgView.image = sprite; + + + + @discussion It can also be used to display single frame in sprite sheet image. + Sample Code: + + YYSpriteSheetImage *sheet = ...; + UIImageView *imageView = ...; + imageView.image = sheet; + imageView.layer.contentsRect = [sheet contentsRectForCALayerAtIndex:6]; + + */ +@interface YYSpriteSheetImage : UIImage + +/** + Creates and returns an image object. + + @param image The sprite sheet image (contains all frames). + + @param contentRects The sprite sheet image frame rects in the image coordinates. + The rectangle should not outside the image's bounds. The objects in this array + should be created with [NSValue valueWithCGRect:]. + + @param frameDurations The sprite sheet image frame's durations in seconds. + The objects in this array should be NSNumber. + + @param loopCount Animation loop count, 0 means infinite looping. + + @return An image object, or nil if an error occurs. + */ +- (nullable instancetype)initWithSpriteSheetImage:(UIImage *)image + contentRects:(NSArray *)contentRects + frameDurations:(NSArray *)frameDurations + loopCount:(NSUInteger)loopCount; + +@property (nonatomic, readonly) NSArray *contentRects; +@property (nonatomic, readonly) NSArray *frameDurations; +@property (nonatomic, readonly) NSUInteger loopCount; + +/** + Get the contents rect for CALayer. + See "contentsRect" property in CALayer for more information. + + @param index Index of frame. + @return Contents Rect. + */ +- (CGRect)contentsRectForCALayerAtIndex:(NSUInteger)index; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYImage/YYImage/YYSpriteSheetImage.m b/Example/Pods/YYImage/YYImage/YYSpriteSheetImage.m new file mode 100644 index 00000000..f5a0d778 --- /dev/null +++ b/Example/Pods/YYImage/YYImage/YYSpriteSheetImage.m @@ -0,0 +1,80 @@ +// +// YYSpriteImage.m +// YYImage +// +// Created by ibireme on 15/4/21. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYSpriteSheetImage.h" + +@implementation YYSpriteSheetImage + +- (instancetype)initWithSpriteSheetImage:(UIImage *)image + contentRects:(NSArray *)contentRects + frameDurations:(NSArray *)frameDurations + loopCount:(NSUInteger)loopCount { + if (!image.CGImage) return nil; + if (contentRects.count < 1 || frameDurations.count < 1) return nil; + if (contentRects.count != frameDurations.count) return nil; + + self = [super initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation]; + if (!self) return nil; + + _contentRects = contentRects.copy; + _frameDurations = frameDurations.copy; + _loopCount = loopCount; + return self; +} + +- (CGRect)contentsRectForCALayerAtIndex:(NSUInteger)index { + CGRect layerRect = CGRectMake(0, 0, 1, 1); + if (index >= _contentRects.count) return layerRect; + + CGSize imageSize = self.size; + CGRect rect = [self animatedImageContentsRectAtIndex:index]; + if (imageSize.width > 0.01 && imageSize.height > 0.01) { + layerRect.origin.x = rect.origin.x / imageSize.width; + layerRect.origin.y = rect.origin.y / imageSize.height; + layerRect.size.width = rect.size.width / imageSize.width; + layerRect.size.height = rect.size.height / imageSize.height; + layerRect = CGRectIntersection(layerRect, CGRectMake(0, 0, 1, 1)); + if (CGRectIsNull(layerRect) || CGRectIsEmpty(layerRect)) { + layerRect = CGRectMake(0, 0, 1, 1); + } + } + return layerRect; +} + +#pragma mark @protocol YYAnimatedImage + +- (NSUInteger)animatedImageFrameCount { + return _contentRects.count; +} + +- (NSUInteger)animatedImageLoopCount { + return _loopCount; +} + +- (NSUInteger)animatedImageBytesPerFrame { + return 0; +} + +- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index { + return self; +} + +- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index { + if (index >= _frameDurations.count) return 0; + return ((NSNumber *)_frameDurations[index]).doubleValue; +} + +- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index { + if (index >= _contentRects.count) return CGRectZero; + return ((NSValue *)_contentRects[index]).CGRectValue; +} + +@end diff --git a/Example/Pods/YYWebImage/LICENSE b/Example/Pods/YYWebImage/LICENSE new file mode 100644 index 00000000..46be20bd --- /dev/null +++ b/Example/Pods/YYWebImage/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 ibireme + +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/Example/Pods/YYWebImage/README.md b/Example/Pods/YYWebImage/README.md new file mode 100755 index 00000000..ce346785 --- /dev/null +++ b/Example/Pods/YYWebImage/README.md @@ -0,0 +1,308 @@ +YYWebImage +============== +[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/ibireme/YYWebImage/master/LICENSE)  +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)  +[![CocoaPods](http://img.shields.io/cocoapods/v/YYWebImage.svg?style=flat)](http://cocoapods.org/?q= YYWebImage)  +[![CocoaPods](http://img.shields.io/cocoapods/p/YYWebImage.svg?style=flat)](http://cocoapods.org/?q= YYWebImage)  +[![Support](https://img.shields.io/badge/support-iOS%206%2B%20-blue.svg?style=flat)](https://www.apple.com/nl/ios/)  +[![Build Status](https://travis-ci.org/ibireme/YYWebImage.svg?branch=master)](https://travis-ci.org/ibireme/YYWebImage) + +![ProgressiveBlur~](https://raw.github.com/ibireme/YYWebImage/master/Demo/Demo.gif +) + +YYWebImage is an asynchronous image loading framework (a component of [YYKit](https://github.com/ibireme/YYKit)). + +It was created as an improved replacement for SDWebImage, PINRemoteImage and FLAnimatedImage. + +It use [YYCache](https://github.com/ibireme/YYCache) to support memory and disk cache, and [YYImage](https://github.com/ibireme/YYImage) to support WebP/APNG/GIF image decode.
+See these project for more information. + + +Features +============== +- Asynchronous image load from remote or local URL. +- Animated WebP, APNG, GIF support (dynamic buffer, lower memory usage). +- Baseline/progressive/interlaced image decode support. +- Image loading category for UIImageView, UIButton, MKAnnotationView and CALayer. +- Image effect: blur, round corner, resize, color tint, crop, rotate and more. +- High performance memory and disk image cache. +- High performance image loader to avoid main thread blocked. +- Fully documented. + +Usage +============== + +###Load image from URL + + // load from remote url + imageView.yy_imageURL = [NSURL URLWithString:@"http://github.com/logo.png"]; + + // load from local url + imageView.yy_imageURL = [NSURL fileURLWithPath:@"/tmp/logo.png"]; + + +###Load animated image + + // just replace `UIImageView` with `YYAnimatedImageView` + UIImageView *imageView = [YYAnimatedImageView new]; + imageView.yy_imageURL = [NSURL URLWithString:@"http://github.com/ani.webp"]; + + +###Load image progressively + + // progressive + [imageView yy_setImageWithURL:url options:YYWebImageOptionProgressive]; + + // progressive with blur and fade animation (see the demo at the top of this page) + [imageView yy_setImageWithURL:url options:YYWebImageOptionProgressiveBlur | YYWebImageOptionSetImageWithFadeAnimation]; + + +###Load and process image + + // 1. download image from remote + // 2. get download progress + // 3. resize image and add round corner + // 4. set image with a fade animation + + [imageView yy_setImageWithURL:url + placeholder:nil + options:YYWebImageOptionSetImageWithFadeAnimation + progress:^(NSInteger receivedSize, NSInteger expectedSize) { + progress = (float)receivedSize / expectedSize; + } + transform:^UIImage *(UIImage *image, NSURL *url) { + image = [image yy_imageByResizeToSize:CGSizeMake(100, 100) contentMode:UIViewContentModeCenter]; + return [image yy_imageByRoundCornerRadius:10]; + } + completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + if (from == YYWebImageFromDiskCache) { + NSLog(@"load from disk cache"); + } + }]; + +###Image Cache + YYImageCache *cache = [YYWebImageManager sharedManager].cache; + + // get cache capacity + cache.memoryCache.totalCost; + cache.memoryCache.totalCount; + cache.diskCache.totalCost; + cache.diskCache.totalCount; + + // clear cache + [cache.memoryCache removeAllObjects]; + [cache.diskCache removeAllObjects]; + + // clear disk cache with progress + [cache.diskCache removeAllObjectsWithProgressBlock:^(int removedCount, int totalCount) { + // progress + } endBlock:^(BOOL error) { + // end + }]; + +Installation +============== + +### CocoaPods + +1. Update cocoapods to the latest version. +2. Add `pod 'YYWebImage'` to your Podfile. +3. Run `pod install` or `pod update`. +4. Import \. +5. Notice: it doesn't include WebP subspec by default, if you want to support WebP format, you may add `pod 'YYImage/WebP'` to your Podfile. You may call `YYImageWebPAvailable()` to check whether the WebP subspec is installed correctly. + +### Carthage + +1. Add `github "ibireme/YYWebImage"` to your Cartfile. +2. Run `carthage update --platform ios` and add the framework to your project. +3. Import \. +4. Notice: carthage framework doesn't include webp component, if you want to support WebP format, use CocoaPods or install manually. You may call `YYImageWebPAvailable()` to check whether the WebP library is installed correctly. + +### Manually + +1. Download all the files in the YYWebImage subdirectory. +2. Add the source files to your Xcode project. +3. Link with required frameworks: + * UIKit + * CoreFoundation + * QuartzCore + * AssetsLibrary + * ImageIO + * Accelerate + * MobileCoreServices + * sqlite3 + * libz +4. Import `YYWebImage.h`. +5. Notice: if you want to support WebP format, you may add `Vendor/WebP.framework`(static library) to your Xcode project. + + +Documentation +============== +Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYWebImage/).
+You can also install documentation locally using [appledoc](https://github.com/tomaz/appledoc). + + +Requirements +============== +This library requires `iOS 6.0+` and `Xcode 7.0+`. + + +License +============== +YYWebImage is provided under the MIT license. See LICENSE file for details. + + +

+--- +中文介绍 +============== +![ProgressiveBlur~](https://raw.github.com/ibireme/YYWebImage/master/Demo/Demo.gif +) + +YYWebImage 是一个异步图片加载框架 ([YYKit](https://github.com/ibireme/YYKit) 组件之一). + +其设计目的是试图替代 SDWebImage、PINRemoteImage、FLAnimatedImage 等开源框架,它支持这些开源框架的大部分功能,同时增加了大量新特性、并且有不小的性能提升。 + +它底层用 [YYCache](https://github.com/ibireme/YYCache) 实现了内存和磁盘缓存, 用 [YYImage](https://github.com/ibireme/YYImage) 实现了 WebP/APNG/GIF 动图的解码和播放。
+你可以查看这些项目以获得更多信息。 + + +特性 +============== +- 异步的图片加载,支持 HTTP 和本地文件。 +- 支持 GIF、APNG、WebP 动画(动态缓存,低内存占用)。 +- 支持逐行扫描、隔行扫描、渐进式图像加载。 +- UIImageView、UIButton、MKAnnotationView、CALayer 的 Category 方法支持。 +- 常见图片处理:模糊、圆角、大小调整、裁切、旋转、色调等。 +- 高性能的内存和磁盘缓存。 +- 高性能的图片设置方式,以避免主线程阻塞。 +- 每个类和方法都有完善的文档注释。 + +用法 +============== + +###从 URL 加载图片 + + // 加载网络图片 + imageView.yy_imageURL = [NSURL URLWithString:@"http://github.com/logo.png"]; + + // 加载本地图片 + imageView.yy_imageURL = [NSURL fileURLWithPath:@"/tmp/logo.png"]; + + +###加载动图 + + // 只需要把 `UIImageView` 替换为 `YYAnimatedImageView` 即可。 + UIImageView *imageView = [YYAnimatedImageView new]; + imageView.yy_imageURL = [NSURL URLWithString:@"http://github.com/ani.webp"]; + + +###渐进式图片加载 + + // 渐进式:边下载边显示 + [imageView yy_setImageWithURL:url options:YYWebImageOptionProgressive]; + + // 渐进式加载,增加模糊效果和渐变动画 (见本页最上方的GIF演示) + [imageView yy_setImageWithURL:url options:YYWebImageOptionProgressiveBlur | YYWebImageOptionSetImageWithFadeAnimation]; + + +###加载、处理图片 + + // 1. 下载图片 + // 2. 获得图片下载进度 + // 3. 调整图片大小、加圆角 + // 4. 显示图片时增加一个淡入动画,以获得更好的用户体验 + + [imageView yy_setImageWithURL:url + placeholder:nil + options:YYWebImageOptionSetImageWithFadeAnimation + progress:^(NSInteger receivedSize, NSInteger expectedSize) { + progress = (float)receivedSize / expectedSize; + } + transform:^UIImage *(UIImage *image, NSURL *url) { + image = [image yy_imageByResizeToSize:CGSizeMake(100, 100) contentMode:UIViewContentModeCenter]; + return [image yy_imageByRoundCornerRadius:10]; + } + completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + if (from == YYWebImageFromDiskCache) { + NSLog(@"load from disk cache"); + } + }]; + + +###图片缓存 + YYImageCache *cache = [YYWebImageManager sharedManager].cache; + + // 获取缓存大小 + cache.memoryCache.totalCost; + cache.memoryCache.totalCount; + cache.diskCache.totalCost; + cache.diskCache.totalCount; + + // 清空缓存 + [cache.memoryCache removeAllObjects]; + [cache.diskCache removeAllObjects]; + + // 清空磁盘缓存,带进度回调 + [cache.diskCache removeAllObjectsWithProgressBlock:^(int removedCount, int totalCount) { + // progress + } endBlock:^(BOOL error) { + // end + }]; + +安装 +============== + +### CocoaPods + +1. 将 cocoapods 更新至最新版本. +2. 在 Podfile 中添加 `pod 'YYWebImage'`。 +3. 执行 `pod install` 或 `pod update`。 +4. 导入 \。 +5. 注意:pod 配置并没有包含 WebP 组件, 如果你需要支持 WebP,可以在 Podfile 中添加 `pod 'YYImage/WebP'`。你可以调用 `YYImageWebPAvailable()` 来检查一下 WebP 组件是否被正确安装。 + +### Carthage + +1. 在 Cartfile 中添加 `github "ibireme/YYWebImage"`。 +2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。 +3. 导入 \。 +4. 注意: carthage framework 并没有包含 webp 组件。如果你需要支持 WebP,可以用 CocoaPods 安装,或者手动安装。 + +### 手动安装 + +1. 下载 YYWebImage 文件夹内的所有内容。 +2. 将 YYWebImage 内的源文件添加(拖放)到你的工程。 +3. 链接以下 frameworks: + * UIKit + * CoreFoundation + * QuartzCore + * AssetsLibrary + * ImageIO + * Accelerate + * MobileCoreServices + * sqlite3 + * libz +4. 导入 `YYWebImage.h`。 +5. 注意:如果你需要支持 WebP,可以将 `Vendor/WebP.framework`(静态库) 加入你的工程。你可以调用 `YYImageWebPAvailable()` 来检查一下 WebP 组件是否被正确安装。 + + +文档 +============== +你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYWebImage/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。 + + +系统要求 +============== +该项目最低支持 `iOS 6.0` 和 `Xcode 7.0`。 + + +许可证 +============== +YYWebImage 使用 MIT 许可证,详情见 LICENSE 文件。 + +相关链接 +============== +[移动端图片格式调研](http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/)
+ +[iOS 处理图片的一些小 Tip](http://blog.ibireme.com/2015/11/02/ios_image_tips/) + diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.h b/Example/Pods/YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.h new file mode 100644 index 00000000..722edd88 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.h @@ -0,0 +1,112 @@ +// +// CALayer+YYWebImage.h +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +#if __has_include() +#import +#else +#import "YYWebImageManager.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + Web image methods for CALayer. + It will set image to layer.contents. + */ +@interface CALayer (YYWebImage) + +#pragma mark - image + +/** + Current image URL. + + @discussion Set a new value to this property will cancel the previous request + operation and create a new request operation to fetch image. Set nil to clear + the image and image URL. + */ +@property (nullable, nonatomic, strong) NSURL *yy_imageURL; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param options The options to use when request the image. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder he image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param manager The manager to create image request operation. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(nullable YYWebImageManager *)manager + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Cancel the current image request. + */ +- (void)yy_cancelCurrentImageRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.m b/Example/Pods/YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.m new file mode 100644 index 00000000..8309a0cf --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/CALayer+YYWebImage.m @@ -0,0 +1,189 @@ +// +// CALayer+YYWebImage.m +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "CALayer+YYWebImage.h" +#import "YYWebImageOperation.h" +#import "_YYWebImageSetter.h" +#import + +// Dummy class for category +@interface CALayer_YYWebImage : NSObject @end +@implementation CALayer_YYWebImage @end + + +static int _YYWebImageSetterKey; + +@implementation CALayer (YYWebImage) + +- (NSURL *)yy_imageURL { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + return setter.imageURL; +} + +- (void)setYy_imageURL:(NSURL *)imageURL { + [self yy_setImageWithURL:imageURL + placeholder:nil + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options { + [self yy_setImageWithURL:imageURL + placeholder:nil + options:options + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:nil + transform:nil + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:progress + transform:transform + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL]; + manager = manager ? manager : [YYWebImageManager sharedManager]; + + + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + if (!setter) { + setter = [_YYWebImageSetter new]; + objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + int32_t sentinel = [setter cancelWithNewURL:imageURL]; + + _yy_dispatch_sync_on_main_queue(^{ + if ((options & YYWebImageOptionSetImageWithFadeAnimation) && + !(options & YYWebImageOptionAvoidSetImage)) { + [self removeAnimationForKey:_YYWebImageFadeAnimationKey]; + } + + if (!imageURL) { + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.contents = (id)placeholder.CGImage; + } + return; + } + + // get the image from memory as quickly as possible + UIImage *imageFromMemory = nil; + if (manager.cache && + !(options & YYWebImageOptionUseNSURLCache) && + !(options & YYWebImageOptionRefreshImageCache)) { + imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory]; + } + if (imageFromMemory) { + if (!(options & YYWebImageOptionAvoidSetImage)) { + self.contents = (id)imageFromMemory.CGImage; + } + if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil); + return; + } + + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.contents = (id)placeholder.CGImage; + } + + __weak typeof(self) _self = self; + dispatch_async([_YYWebImageSetter setterQueue], ^{ + YYWebImageProgressBlock _progress = nil; + if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) { + dispatch_async(dispatch_get_main_queue(), ^{ + progress(receivedSize, expectedSize); + }); + }; + + __block int32_t newSentinel = 0; + __block __weak typeof(setter) weakSetter = nil; + YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + __strong typeof(_self) self = _self; + BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage); + BOOL showFade = (options & YYWebImageOptionSetImageWithFadeAnimation); + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel; + if (setImage && self && !sentinelChanged) { + if (showFade) { + CATransition *transition = [CATransition animation]; + transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime; + transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + transition.type = kCATransitionFade; + [self addAnimation:transition forKey:_YYWebImageFadeAnimationKey]; + } + self.contents = (id)image.CGImage; + } + if (completion) { + if (sentinelChanged) { + completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil); + } else { + completion(image, url, from, stage, error); + } + } + }); + }; + + newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion]; + weakSetter = setter; + }); + + + }); +} + +- (void)yy_cancelCurrentImageRequest { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + if (setter) [setter cancel]; +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.h b/Example/Pods/YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.h new file mode 100644 index 00000000..b9bce0b7 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.h @@ -0,0 +1,109 @@ +// +// MKAnnotationView+YYWebImage.h +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +#if __has_include() +#import +#else +#import "YYWebImageManager.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + Web image methods for MKAnnotationView. + */ +@interface MKAnnotationView (YYWebImage) + +/** + Current image URL. + + @discussion Set a new value to this property will cancel the previous request + operation and create a new request operation to fetch image. Set nil to clear + the image and image URL. + */ +@property (nullable, nonatomic, strong) NSURL *yy_imageURL; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param options The options to use when request the image. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder he image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param manager The manager to create image request operation. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(nullable YYWebImageManager *)manager + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Cancel the current image request. + */ +- (void)yy_cancelCurrentImageRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.m b/Example/Pods/YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.m new file mode 100644 index 00000000..d9afff48 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/MKAnnotationView+YYWebImage.m @@ -0,0 +1,187 @@ +// +// MKAnnotationView+YYWebImage.m +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "MKAnnotationView+YYWebImage.h" +#import "YYWebImageOperation.h" +#import "_YYWebImageSetter.h" +#import + +// Dummy class for category +@interface MKAnnotationView_YYWebImage : NSObject @end +@implementation MKAnnotationView_YYWebImage @end + + +static int _YYWebImageSetterKey; + +@implementation MKAnnotationView (YYWebImage) + +- (NSURL *)yy_imageURL { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + return setter.imageURL; +} + +- (void)setYy_imageURL:(NSURL *)imageURL { + [self yy_setImageWithURL:imageURL + placeholder:nil + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options { + [self yy_setImageWithURL:imageURL + placeholder:nil + options:options + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:nil + transform:nil + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:progress + transform:transform + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL]; + manager = manager ? manager : [YYWebImageManager sharedManager]; + + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + if (!setter) { + setter = [_YYWebImageSetter new]; + objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + int32_t sentinel = [setter cancelWithNewURL:imageURL]; + + _yy_dispatch_sync_on_main_queue(^{ + if ((options & YYWebImageOptionSetImageWithFadeAnimation) && + !(options & YYWebImageOptionAvoidSetImage)) { + if (!self.highlighted) { + [self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey]; + } + } + if (!imageURL) { + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.image = placeholder; + } + return; + } + + // get the image from memory as quickly as possible + UIImage *imageFromMemory = nil; + if (manager.cache && + !(options & YYWebImageOptionUseNSURLCache) && + !(options & YYWebImageOptionRefreshImageCache)) { + imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory]; + } + if (imageFromMemory) { + if (!(options & YYWebImageOptionAvoidSetImage)) { + self.image = imageFromMemory; + } + if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil); + return; + } + + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.image = placeholder; + } + + __weak typeof(self) _self = self; + dispatch_async([_YYWebImageSetter setterQueue], ^{ + YYWebImageProgressBlock _progress = nil; + if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) { + dispatch_async(dispatch_get_main_queue(), ^{ + progress(receivedSize, expectedSize); + }); + }; + + __block int32_t newSentinel = 0; + __block __weak typeof(setter) weakSetter = nil; + YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + __strong typeof(_self) self = _self; + BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage); + BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted); + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel; + if (setImage && self && !sentinelChanged) { + if (showFade) { + CATransition *transition = [CATransition animation]; + transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime; + transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + transition.type = kCATransitionFade; + [self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey]; + } + self.image = image; + } + if (completion) { + if (sentinelChanged) { + completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil); + } else { + completion(image, url, from, stage, error); + } + } + }); + }; + + newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion]; + weakSetter = setter; + }); + }); +} + +- (void)yy_cancelCurrentImageRequest { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + if (setter) [setter cancel]; +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.h b/Example/Pods/YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.h new file mode 100644 index 00000000..7ad6effa --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.h @@ -0,0 +1,213 @@ +// +// UIButton+YYWebImage.h +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#else +#import "YYWebImageManager.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + Web image methods for UIButton. + */ +@interface UIButton (YYWebImage) + +#pragma mark - image + +/** + Current image URL for the specified state. + @return The image URL, or nil. + */ +- (nullable NSURL *)yy_imageURLForState:(UIControlState)state; + +/** + Set the button's image with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder; + +/** + Set the button's image with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param options The options to use when request the image. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + options:(YYWebImageOptions)options; + +/** + Set the button's image with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the button's image with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the button's image with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param manager The manager to create image request operation. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(nullable YYWebImageManager *)manager + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Cancel the current image request for a specified state. + @param state The state that uses the specified image. + */ +- (void)yy_cancelImageRequestForState:(UIControlState)state; + + + +#pragma mark - background image + +/** + Current backgroundImage URL for the specified state. + @return The image URL, or nil. + */ +- (nullable NSURL *)yy_backgroundImageURLForState:(UIControlState)state; + +/** + Set the button's backgroundImage with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + */ +- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder; + +/** + Set the button's backgroundImage with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param options The options to use when request the image. + */ +- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + options:(YYWebImageOptions)options; + +/** + Set the button's backgroundImage with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the button's backgroundImage with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the button's backgroundImage with a specified URL for the specified state. + + @param imageURL The image url (remote or local file path). + @param state The state that uses the specified image. + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param manager The manager to create image request operation. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setBackgroundImageWithURL:(nullable NSURL *)imageURL + forState:(UIControlState)state + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(nullable YYWebImageManager *)manager + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Cancel the current backgroundImage request for a specified state. + @param state The state that uses the specified image. + */ +- (void)yy_cancelBackgroundImageRequestForState:(UIControlState)state; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.m b/Example/Pods/YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.m new file mode 100644 index 00000000..8cc21d59 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/UIButton+YYWebImage.m @@ -0,0 +1,439 @@ +// +// UIButton+YYWebImage.m +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "UIButton+YYWebImage.h" +#import "YYWebImageOperation.h" +#import "_YYWebImageSetter.h" +#import + +// Dummy class for category +@interface UIButton_YYWebImage : NSObject @end +@implementation UIButton_YYWebImage @end + +static inline NSNumber *UIControlStateSingle(UIControlState state) { + if (state & UIControlStateHighlighted) return @(UIControlStateHighlighted); + if (state & UIControlStateDisabled) return @(UIControlStateDisabled); + if (state & UIControlStateSelected) return @(UIControlStateSelected); + return @(UIControlStateNormal); +} + +static inline NSArray *UIControlStateMulti(UIControlState state) { + NSMutableArray *array = [NSMutableArray new]; + if (state & UIControlStateHighlighted) [array addObject:@(UIControlStateHighlighted)]; + if (state & UIControlStateDisabled) [array addObject:@(UIControlStateDisabled)]; + if (state & UIControlStateSelected) [array addObject:@(UIControlStateSelected)]; + if ((state & 0xFF) == 0) [array addObject:@(UIControlStateNormal)]; + return array; +} + +static int _YYWebImageSetterKey; +static int _YYWebImageBackgroundSetterKey; + + +@interface _YYWebImageSetterDicForButton : NSObject +- (_YYWebImageSetter *)setterForState:(NSNumber *)state; +- (_YYWebImageSetter *)lazySetterForState:(NSNumber *)state; +@end + +@implementation _YYWebImageSetterDicForButton { + NSMutableDictionary *_dic; + dispatch_semaphore_t _lock; +} +- (instancetype)init { + self = [super init]; + _lock = dispatch_semaphore_create(1); + _dic = [NSMutableDictionary new]; + return self; +} +- (_YYWebImageSetter *)setterForState:(NSNumber *)state { + dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + _YYWebImageSetter *setter = _dic[state]; + dispatch_semaphore_signal(_lock); + return setter; + +} +- (_YYWebImageSetter *)lazySetterForState:(NSNumber *)state { + dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + _YYWebImageSetter *setter = _dic[state]; + if (!setter) { + setter = [_YYWebImageSetter new]; + _dic[state] = setter; + } + dispatch_semaphore_signal(_lock); + return setter; +} +@end + + +@implementation UIButton (YYWebImage) + +#pragma mark - image + +- (void)_yy_setImageWithURL:(NSURL *)imageURL + forSingleState:(NSNumber *)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL]; + manager = manager ? manager : [YYWebImageManager sharedManager]; + + _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + if (!dic) { + dic = [_YYWebImageSetterDicForButton new]; + objc_setAssociatedObject(self, &_YYWebImageSetterKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + _YYWebImageSetter *setter = [dic lazySetterForState:state]; + int32_t sentinel = [setter cancelWithNewURL:imageURL]; + + _yy_dispatch_sync_on_main_queue(^{ + if (!imageURL) { + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + [self setImage:placeholder forState:state.integerValue]; + } + return; + } + + // get the image from memory as quickly as possible + UIImage *imageFromMemory = nil; + if (manager.cache && + !(options & YYWebImageOptionUseNSURLCache) && + !(options & YYWebImageOptionRefreshImageCache)) { + imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory]; + } + if (imageFromMemory) { + if (!(options & YYWebImageOptionAvoidSetImage)) { + [self setImage:imageFromMemory forState:state.integerValue]; + } + if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil); + return; + } + + + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + [self setImage:placeholder forState:state.integerValue]; + } + + __weak typeof(self) _self = self; + dispatch_async([_YYWebImageSetter setterQueue], ^{ + YYWebImageProgressBlock _progress = nil; + if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) { + dispatch_async(dispatch_get_main_queue(), ^{ + progress(receivedSize, expectedSize); + }); + }; + + __block int32_t newSentinel = 0; + __block __weak typeof(setter) weakSetter = nil; + YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + __strong typeof(_self) self = _self; + BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage); + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel; + if (setImage && self && !sentinelChanged) { + [self setImage:image forState:state.integerValue]; + } + if (completion) { + if (sentinelChanged) { + completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil); + } else { + completion(image, url, from, stage, error); + } + } + }); + }; + + newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion]; + weakSetter = setter; + }); + }); +} + +- (void)_yy_cancelImageRequestForSingleState:(NSNumber *)state { + _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + _YYWebImageSetter *setter = [dic setterForState:state]; + if (setter) [setter cancel]; +} + +- (NSURL *)yy_imageURLForState:(UIControlState)state { + _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + _YYWebImageSetter *setter = [dic setterForState:UIControlStateSingle(state)]; + return setter.imageURL; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder { + [self yy_setImageWithURL:imageURL + forState:state + placeholder:placeholder + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + options:(YYWebImageOptions)options { + [self yy_setImageWithURL:imageURL + forState:state + placeholder:nil + options:options + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + forState:state + placeholder:placeholder + options:options + manager:nil + progress:nil + transform:nil + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + forState:state + placeholder:placeholder + options:options + manager:nil + progress:progress + transform:transform + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + for (NSNumber *num in UIControlStateMulti(state)) { + [self _yy_setImageWithURL:imageURL + forSingleState:num + placeholder:placeholder + options:options + manager:manager + progress:progress + transform:transform + completion:completion]; + } +} + +- (void)yy_cancelImageRequestForState:(UIControlState)state { + for (NSNumber *num in UIControlStateMulti(state)) { + [self _yy_cancelImageRequestForSingleState:num]; + } +} + + +#pragma mark - background image + +- (void)_yy_setBackgroundImageWithURL:(NSURL *)imageURL + forSingleState:(NSNumber *)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL]; + manager = manager ? manager : [YYWebImageManager sharedManager]; + + _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey); + if (!dic) { + dic = [_YYWebImageSetterDicForButton new]; + objc_setAssociatedObject(self, &_YYWebImageBackgroundSetterKey, dic, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + _YYWebImageSetter *setter = [dic lazySetterForState:state]; + int32_t sentinel = [setter cancelWithNewURL:imageURL]; + + _yy_dispatch_sync_on_main_queue(^{ + if (!imageURL) { + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + [self setBackgroundImage:placeholder forState:state.integerValue]; + } + return; + } + + // get the image from memory as quickly as possible + UIImage *imageFromMemory = nil; + if (manager.cache && + !(options & YYWebImageOptionUseNSURLCache) && + !(options & YYWebImageOptionRefreshImageCache)) { + imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory]; + } + if (imageFromMemory) { + if (!(options & YYWebImageOptionAvoidSetImage)) { + [self setBackgroundImage:imageFromMemory forState:state.integerValue]; + } + if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil); + return; + } + + + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + [self setBackgroundImage:placeholder forState:state.integerValue]; + } + + __weak typeof(self) _self = self; + dispatch_async([_YYWebImageSetter setterQueue], ^{ + YYWebImageProgressBlock _progress = nil; + if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) { + dispatch_async(dispatch_get_main_queue(), ^{ + progress(receivedSize, expectedSize); + }); + }; + + __block int32_t newSentinel = 0; + __block __weak typeof(setter) weakSetter = nil; + YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + __strong typeof(_self) self = _self; + BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage); + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel; + if (setImage && self && !sentinelChanged) { + [self setBackgroundImage:image forState:state.integerValue]; + } + if (completion) { + if (sentinelChanged) { + completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil); + } else { + completion(image, url, from, stage, error); + } + } + }); + }; + + newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion]; + weakSetter = setter; + }); + }); +} + +- (void)_yy_cancelBackgroundImageRequestForSingleState:(NSNumber *)state { + _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey); + _YYWebImageSetter *setter = [dic setterForState:state]; + if (setter) [setter cancel]; +} + +- (NSURL *)yy_backgroundImageURLForState:(UIControlState)state { + _YYWebImageSetterDicForButton *dic = objc_getAssociatedObject(self, &_YYWebImageBackgroundSetterKey); + _YYWebImageSetter *setter = [dic setterForState:UIControlStateSingle(state)]; + return setter.imageURL; +} + +- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder { + [self yy_setBackgroundImageWithURL:imageURL + forState:state + placeholder:placeholder + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + options:(YYWebImageOptions)options { + [self yy_setBackgroundImageWithURL:imageURL + forState:state + placeholder:nil + options:options + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(YYWebImageCompletionBlock)completion { + [self yy_setBackgroundImageWithURL:imageURL + forState:state + placeholder:placeholder + options:options + manager:nil + progress:nil + transform:nil + completion:completion]; +} + +- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + [self yy_setBackgroundImageWithURL:imageURL + forState:state + placeholder:placeholder + options:options + manager:nil + progress:progress + transform:transform + completion:completion]; +} + +- (void)yy_setBackgroundImageWithURL:(NSURL *)imageURL + forState:(UIControlState)state + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + for (NSNumber *num in UIControlStateMulti(state)) { + [self _yy_setBackgroundImageWithURL:imageURL + forSingleState:num + placeholder:placeholder + options:options + manager:manager + progress:progress + transform:transform + completion:completion]; + } +} + +- (void)yy_cancelBackgroundImageRequestForState:(UIControlState)state { + for (NSNumber *num in UIControlStateMulti(state)) { + [self _yy_cancelBackgroundImageRequestForSingleState:num]; + } +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.h b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.h new file mode 100644 index 00000000..26f2af15 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.h @@ -0,0 +1,316 @@ +// +// UIImage+YYWebImage.h +// YYWebImage +// +// Created by ibireme on 13/4/4. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Provide some commen method for `UIImage`. + Image process is based on CoreGraphic and vImage. + */ +@interface UIImage (YYWebImage) + +#pragma mark - Create image +///============================================================================= +/// @name Create image +///============================================================================= + +/** + Create an animated image with GIF data. After created, you can access + the images via property '.images'. If the data is not animated gif, this + function is same as [UIImage imageWithData:data scale:scale]; + + @discussion It has a better display performance, but costs more memory + (width * height * frames Bytes). It only suited to display small + gif such as animated emoticon. If you want to display large gif, + see `YYImage`. + + @param data GIF data. + + @param scale The scale factor + + @return A new image created from GIF, or nil when an error occurs. + */ ++ (nullable UIImage *)yy_imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale; + +/** + Create and return a 1x1 point size image with the given color. + + @param color The color. + */ ++ (nullable UIImage *)yy_imageWithColor:(UIColor *)color; + +/** + Create and return a pure color image with the given color and size. + + @param color The color. + @param size New image's type. + */ ++ (nullable UIImage *)yy_imageWithColor:(UIColor *)color size:(CGSize)size; + +/** + Create and return an image with custom draw code. + + @param size The image size. + @param drawBlock The draw block. + + @return The new image. + */ ++ (nullable UIImage *)yy_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock; + +#pragma mark - Image Info +///============================================================================= +/// @name Image Info +///============================================================================= + +/** + Whether this image has alpha channel. + */ +- (BOOL)yy_hasAlphaChannel; + + +#pragma mark - Modify Image +///============================================================================= +/// @name Modify Image +///============================================================================= + +/** + Draws the entire image in the specified rectangle, content changed with + the contentMode. + + @discussion This method draws the entire image in the current graphics context, + respecting the image's orientation setting. In the default coordinate system, + images are situated down and to the right of the origin of the specified + rectangle. This method respects any transforms applied to the current graphics + context, however. + + @param rect The rectangle in which to draw the image. + + @param contentMode Draw content mode + + @param clips A Boolean value that determines whether content are confined to the rect. + */ +- (void)yy_drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips; + +/** + Returns a new image which is scaled from this image. + The image will be stretched as needed. + + @param size The new size to be scaled, values should be positive. + + @return The new image with the given size. + */ +- (nullable UIImage *)yy_imageByResizeToSize:(CGSize)size; + +/** + Returns a new image which is scaled from this image. + The image content will be changed with thencontentMode. + + @param size The new size to be scaled, values should be positive. + + @param contentMode The content mode for image content. + + @return The new image with the given size. + */ +- (nullable UIImage *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode; + +/** + Returns a new image which is cropped from this image. + + @param rect Image's inner rect. + + @return The new image, or nil if an error occurs. + */ +- (nullable UIImage *)yy_imageByCropToRect:(CGRect)rect; + +/** + Returns a new image which is edge inset from this image. + + @param insets Inset (positive) for each of the edges, values can be negative to 'outset'. + + @param color Extend edge's fill color, nil means clear color. + + @return The new image, or nil if an error occurs. + */ +- (nullable UIImage *)yy_imageByInsetEdge:(UIEdgeInsets)insets withColor:(nullable UIColor *)color; + +/** + Rounds a new image with a given corner size. + + @param radius The radius of each corner oval. Values larger than half the + rectangle's width or height are clamped appropriately to half + the width or height. + */ +- (nullable UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius; + +/** + Rounds a new image with a given corner size. + + @param radius The radius of each corner oval. Values larger than half the + rectangle's width or height are clamped appropriately to + half the width or height. + + @param borderWidth The inset border line width. Values larger than half the rectangle's + width or height are clamped appropriately to half the width + or height. + + @param borderColor The border stroke color. nil means clear color. + */ +- (nullable UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius + borderWidth:(CGFloat)borderWidth + borderColor:(nullable UIColor *)borderColor; + +/** + Rounds a new image with a given corner size. + + @param radius The radius of each corner oval. Values larger than half the + rectangle's width or height are clamped appropriately to + half the width or height. + + @param corners A bitmask value that identifies the corners that you want + rounded. You can use this parameter to round only a subset + of the corners of the rectangle. + + @param borderWidth The inset border line width. Values larger than half the rectangle's + width or height are clamped appropriately to half the width + or height. + + @param borderColor The border stroke color. nil means clear color. + + @param borderLineJoin The border line join. + */ +- (nullable UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius + corners:(UIRectCorner)corners + borderWidth:(CGFloat)borderWidth + borderColor:(nullable UIColor *)borderColor + borderLineJoin:(CGLineJoin)borderLineJoin; + +/** + Returns a new rotated image (relative to the center). + + @param radians Rotated radians in counterclockwise.⟲ + + @param fitSize YES: new image's size is extend to fit all content. + NO: image's size will not change, content may be clipped. + */ +- (nullable UIImage *)yy_imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize; + +/** + Returns a new image rotated counterclockwise by a quarter‑turn (90°). ⤺ + The width and height will be exchanged. + */ +- (nullable UIImage *)yy_imageByRotateLeft90; + +/** + Returns a new image rotated clockwise by a quarter‑turn (90°). ⤼ + The width and height will be exchanged. + */ +- (nullable UIImage *)yy_imageByRotateRight90; + +/** + Returns a new image rotated 180° . ↻ + */ +- (nullable UIImage *)yy_imageByRotate180; + +/** + Returns a vertically flipped image. ⥯ + */ +- (nullable UIImage *)yy_imageByFlipVertical; + +/** + Returns a horizontally flipped image. ⇋ + */ +- (nullable UIImage *)yy_imageByFlipHorizontal; + + +#pragma mark - Image Effect +///============================================================================= +/// @name Image Effect +///============================================================================= + +/** + Tint the image in alpha channel with the given color. + + @param color The color. + */ +- (nullable UIImage *)yy_imageByTintColor:(UIColor *)color; + +/** + Returns a grayscaled image. + */ +- (nullable UIImage *)yy_imageByGrayscale; + +/** + Applies a blur effect to this image. Suitable for blur any content. + */ +- (nullable UIImage *)yy_imageByBlurSoft; + +/** + Applies a blur effect to this image. Suitable for blur any content except pure white. + (same as iOS Control Panel) + */ +- (nullable UIImage *)yy_imageByBlurLight; + +/** + Applies a blur effect to this image. Suitable for displaying black text. + (same as iOS Navigation Bar White) + */ +- (nullable UIImage *)yy_imageByBlurExtraLight; + +/** + Applies a blur effect to this image. Suitable for displaying white text. + (same as iOS Notification Center) + */ +- (nullable UIImage *)yy_imageByBlurDark; + +/** + Applies a blur and tint color to this image. + + @param tintColor The tint color. + */ +- (nullable UIImage *)yy_imageByBlurWithTint:(UIColor *)tintColor; + +/** + Applies a blur, tint color, and saturation adjustment to this image, + optionally within the area specified by @a maskImage. + + @param blurRadius The radius of the blur in points, 0 means no blur effect. + + @param tintColor An optional UIColor object that is uniformly blended with + the result of the blur and saturation operations. The + alpha channel of this color determines how strong the + tint is. nil means no tint. + + @param tintBlendMode The @a tintColor blend mode. Default is kCGBlendModeNormal (0). + + @param saturation A value of 1.0 produces no change in the resulting image. + Values less than 1.0 will desaturation the resulting image + while values greater than 1.0 will have the opposite effect. + 0 means gray scale. + + @param maskImage If specified, @a inputImage is only modified in the area(s) + defined by this mask. This must be an image mask or it + must meet the requirements of the mask parameter of + CGContextClipToMask. + + @return image with effect, or nil if an error occurs (e.g. no + enough memory). + */ +- (nullable UIImage *)yy_imageByBlurRadius:(CGFloat)blurRadius + tintColor:(nullable UIColor *)tintColor + tintMode:(CGBlendMode)tintBlendMode + saturation:(CGFloat)saturation + maskImage:(nullable UIImage *)maskImage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.m b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.m new file mode 100644 index 00000000..2cb0496c --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImage+YYWebImage.m @@ -0,0 +1,745 @@ +// +// UIImage+YYWebImage.m +// YYWebImage +// +// Created by ibireme on 13/4/4. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "UIImage+YYWebImage.h" +#import +#import +#import + +// Dummy class for category +@interface UIImage_YYWebImage : NSObject @end +@implementation UIImage_YYWebImage @end + + + +/// Convert degrees to radians. +static inline CGFloat _DegreesToRadians(CGFloat degrees) { + return degrees * M_PI / 180; +} + + +/** + Resize rect to fit the size using a given contentMode. + + @param rect The draw rect + @param size The content size + @param mode The content mode + @return A resized rect for the given content mode. + @discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill. + */ +static CGRect _YYCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) { + rect = CGRectStandardize(rect); + size.width = size.width < 0 ? -size.width : size.width; + size.height = size.height < 0 ? -size.height : size.height; + CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); + switch (mode) { + case UIViewContentModeScaleAspectFit: + case UIViewContentModeScaleAspectFill: { + if (rect.size.width < 0.01 || rect.size.height < 0.01 || + size.width < 0.01 || size.height < 0.01) { + rect.origin = center; + rect.size = CGSizeZero; + } else { + CGFloat scale; + if (mode == UIViewContentModeScaleAspectFit) { + if (size.width / size.height < rect.size.width / rect.size.height) { + scale = rect.size.height / size.height; + } else { + scale = rect.size.width / size.width; + } + } else { + if (size.width / size.height < rect.size.width / rect.size.height) { + scale = rect.size.width / size.width; + } else { + scale = rect.size.height / size.height; + } + } + size.width *= scale; + size.height *= scale; + rect.size = size; + rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); + } + } break; + case UIViewContentModeCenter: { + rect.size = size; + rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); + } break; + case UIViewContentModeTop: { + rect.origin.x = center.x - size.width * 0.5; + rect.size = size; + } break; + case UIViewContentModeBottom: { + rect.origin.x = center.x - size.width * 0.5; + rect.origin.y += rect.size.height - size.height; + rect.size = size; + } break; + case UIViewContentModeLeft: { + rect.origin.y = center.y - size.height * 0.5; + rect.size = size; + } break; + case UIViewContentModeRight: { + rect.origin.y = center.y - size.height * 0.5; + rect.origin.x += rect.size.width - size.width; + rect.size = size; + } break; + case UIViewContentModeTopLeft: { + rect.size = size; + } break; + case UIViewContentModeTopRight: { + rect.origin.x += rect.size.width - size.width; + rect.size = size; + } break; + case UIViewContentModeBottomLeft: { + rect.origin.y += rect.size.height - size.height; + rect.size = size; + } break; + case UIViewContentModeBottomRight: { + rect.origin.x += rect.size.width - size.width; + rect.origin.y += rect.size.height - size.height; + rect.size = size; + } break; + case UIViewContentModeScaleToFill: + case UIViewContentModeRedraw: + default: { + rect = rect; + } + } + return rect; +} + + + + +static NSTimeInterval _yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index) { + NSTimeInterval delay = 0; + CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(source, index, NULL); + if (dic) { + CFDictionaryRef dicGIF = CFDictionaryGetValue(dic, kCGImagePropertyGIFDictionary); + if (dicGIF) { + NSNumber *num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFUnclampedDelayTime); + if (num.doubleValue <= __FLT_EPSILON__) { + num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFDelayTime); + } + delay = num.doubleValue; + } + CFRelease(dic); + } + + // http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility + if (delay < 0.02) delay = 0.1; + return delay; +} + + + +@implementation UIImage (YYWebImage) + ++ (UIImage *)yy_imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale { + CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)(data), NULL); + if (!source) return nil; + + size_t count = CGImageSourceGetCount(source); + if (count <= 1) { + CFRelease(source); + return [self.class imageWithData:data scale:scale]; + } + + NSUInteger frames[count]; + double oneFrameTime = 1 / 50.0; // 50 fps + NSTimeInterval totalTime = 0; + NSUInteger totalFrame = 0; + NSUInteger gcdFrame = 0; + for (size_t i = 0; i < count; i++) { + NSTimeInterval delay = _yy_CGImageSourceGetGIFFrameDelayAtIndex(source, i); + totalTime += delay; + NSInteger frame = lrint(delay / oneFrameTime); + if (frame < 1) frame = 1; + frames[i] = frame; + totalFrame += frames[i]; + if (i == 0) gcdFrame = frames[i]; + else { + NSUInteger frame = frames[i], tmp; + if (frame < gcdFrame) { + tmp = frame; frame = gcdFrame; gcdFrame = tmp; + } + while (true) { + tmp = frame % gcdFrame; + if (tmp == 0) break; + frame = gcdFrame; + gcdFrame = tmp; + } + } + } + NSMutableArray *array = [NSMutableArray new]; + for (size_t i = 0; i < count; i++) { + CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL); + if (!imageRef) { + CFRelease(source); + return nil; + } + size_t width = CGImageGetWidth(imageRef); + size_t height = CGImageGetHeight(imageRef); + if (width == 0 || height == 0) { + CFRelease(source); + CFRelease(imageRef); + return nil; + } + + CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask; + BOOL hasAlpha = NO; + if (alphaInfo == kCGImageAlphaPremultipliedLast || + alphaInfo == kCGImageAlphaPremultipliedFirst || + alphaInfo == kCGImageAlphaLast || + alphaInfo == kCGImageAlphaFirst) { + hasAlpha = YES; + } + // BGRA8888 (premultiplied) or BGRX8888 + // same as UIGraphicsBeginImageContext() and -[UIView drawRect:] + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; + bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo); + CGColorSpaceRelease(space); + if (!context) { + CFRelease(source); + CFRelease(imageRef); + return nil; + } + CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode + CGImageRef decoded = CGBitmapContextCreateImage(context); + CFRelease(context); + if (!decoded) { + CFRelease(source); + CFRelease(imageRef); + return nil; + } + UIImage *image = [UIImage imageWithCGImage:decoded scale:scale orientation:UIImageOrientationUp]; + CGImageRelease(imageRef); + CGImageRelease(decoded); + if (!image) { + CFRelease(source); + return nil; + } + for (size_t j = 0, max = frames[i] / gcdFrame; j < max; j++) { + [array addObject:image]; + } + } + CFRelease(source); + UIImage *image = [self.class animatedImageWithImages:array duration:totalTime]; + return image; +} + ++ (UIImage *)yy_imageWithColor:(UIColor *)color { + return [self yy_imageWithColor:color size:CGSizeMake(1, 1)]; +} + ++ (UIImage *)yy_imageWithColor:(UIColor *)color size:(CGSize)size { + if (!color || size.width <= 0 || size.height <= 0) return nil; + CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height); + UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetFillColorWithColor(context, color.CGColor); + CGContextFillRect(context, rect); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + ++ (UIImage *)yy_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock { + if (!drawBlock) return nil; + UIGraphicsBeginImageContextWithOptions(size, NO, 0); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (!context) return nil; + drawBlock(context); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (BOOL)yy_hasAlphaChannel { + if (self.CGImage == NULL) return NO; + CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask; + return (alpha == kCGImageAlphaFirst || + alpha == kCGImageAlphaLast || + alpha == kCGImageAlphaPremultipliedFirst || + alpha == kCGImageAlphaPremultipliedLast); +} + +- (void)yy_drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{ + CGRect drawRect = _YYCGRectFitWithContentMode(rect, self.size, contentMode); + if (drawRect.size.width == 0 || drawRect.size.height == 0) return; + if (clips) { + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context) { + CGContextSaveGState(context); + CGContextAddRect(context, rect); + CGContextClip(context); + [self drawInRect:drawRect]; + CGContextRestoreGState(context); + } + } else { + [self drawInRect:drawRect]; + } +} + +- (UIImage *)yy_imageByResizeToSize:(CGSize)size { + if (size.width <= 0 || size.height <= 0) return nil; + UIGraphicsBeginImageContextWithOptions(size, NO, self.scale); + [self drawInRect:CGRectMake(0, 0, size.width, size.height)]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (UIImage *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode { + if (size.width <= 0 || size.height <= 0) return nil; + UIGraphicsBeginImageContextWithOptions(size, NO, self.scale); + [self yy_drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (UIImage *)yy_imageByCropToRect:(CGRect)rect { + rect.origin.x *= self.scale; + rect.origin.y *= self.scale; + rect.size.width *= self.scale; + rect.size.height *= self.scale; + if (rect.size.width <= 0 || rect.size.height <= 0) return nil; + CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect); + UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; + CGImageRelease(imageRef); + return image; +} + +- (UIImage *)yy_imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color { + CGSize size = self.size; + size.width -= insets.left + insets.right; + size.height -= insets.top + insets.bottom; + if (size.width <= 0 || size.height <= 0) return nil; + CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height); + UIGraphicsBeginImageContextWithOptions(size, NO, self.scale); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (color) { + CGContextSetFillColorWithColor(context, color.CGColor); + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height)); + CGPathAddRect(path, NULL, rect); + CGContextAddPath(context, path); + CGContextEOFillPath(context); + CGPathRelease(path); + } + [self drawInRect:rect]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius { + return [self yy_imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil]; +} + +- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius + borderWidth:(CGFloat)borderWidth + borderColor:(UIColor *)borderColor { + return [self yy_imageByRoundCornerRadius:radius corners:UIRectCornerAllCorners borderWidth:borderWidth borderColor:borderColor borderLineJoin:kCGLineJoinMiter]; +} + +- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius + corners:(UIRectCorner)corners + borderWidth:(CGFloat)borderWidth + borderColor:(UIColor *)borderColor + borderLineJoin:(CGLineJoin)borderLineJoin { + + if (corners != UIRectCornerAllCorners) { + UIRectCorner tmp = 0; + if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft; + if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight; + if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft; + if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight; + corners = tmp; + } + + UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); + CGContextScaleCTM(context, 1, -1); + CGContextTranslateCTM(context, 0, -rect.size.height); + + CGFloat minSize = MIN(self.size.width, self.size.height); + if (borderWidth < minSize / 2) { + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)]; + [path closePath]; + + CGContextSaveGState(context); + [path addClip]; + CGContextDrawImage(context, rect, self.CGImage); + CGContextRestoreGState(context); + } + + if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) { + CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale; + CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset); + CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)]; + [path closePath]; + + path.lineWidth = borderWidth; + path.lineJoinStyle = borderLineJoin; + [borderColor setStroke]; + [path stroke]; + } + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (UIImage *)yy_imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize { + size_t width = (size_t)CGImageGetWidth(self.CGImage); + size_t height = (size_t)CGImageGetHeight(self.CGImage); + CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height), + fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, + (size_t)newRect.size.width, + (size_t)newRect.size.height, + 8, + (size_t)newRect.size.width * 4, + colorSpace, + kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(colorSpace); + if (!context) return nil; + + CGContextSetShouldAntialias(context, true); + CGContextSetAllowsAntialiasing(context, true); + CGContextSetInterpolationQuality(context, kCGInterpolationHigh); + + CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5)); + CGContextRotateCTM(context, radians); + + CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage); + CGImageRef imgRef = CGBitmapContextCreateImage(context); + UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation]; + CGImageRelease(imgRef); + CGContextRelease(context); + return img; +} + +- (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical { + if (!self.CGImage) return nil; + size_t width = (size_t)CGImageGetWidth(self.CGImage); + size_t height = (size_t)CGImageGetHeight(self.CGImage); + size_t bytesPerRow = width * 4; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); + CGColorSpaceRelease(colorSpace); + if (!context) return nil; + + CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage); + UInt8 *data = (UInt8 *)CGBitmapContextGetData(context); + if (!data) { + CGContextRelease(context); + return nil; + } + vImage_Buffer src = { data, height, width, bytesPerRow }; + vImage_Buffer dest = { data, height, width, bytesPerRow }; + if (vertical) { + vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill); + } + if (horizontal) { + vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill); + } + CGImageRef imgRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation]; + CGImageRelease(imgRef); + return img; +} + +- (UIImage *)yy_imageByRotateLeft90 { + return [self yy_imageByRotate:_DegreesToRadians(90) fitSize:YES]; +} + +- (UIImage *)yy_imageByRotateRight90 { + return [self yy_imageByRotate:_DegreesToRadians(-90) fitSize:YES]; +} + +- (UIImage *)yy_imageByRotate180 { + return [self _yy_flipHorizontal:YES vertical:YES]; +} + +- (UIImage *)yy_imageByFlipVertical { + return [self _yy_flipHorizontal:NO vertical:YES]; +} + +- (UIImage *)yy_imageByFlipHorizontal { + return [self _yy_flipHorizontal:YES vertical:NO]; +} + +- (UIImage *)yy_imageByTintColor:(UIColor *)color { + UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale); + CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); + [color set]; + UIRectFill(rect); + [self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1]; + UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return newImage; +} + +- (UIImage *)yy_imageByGrayscale { + return [self yy_imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil]; +} + +- (UIImage *)yy_imageByBlurSoft { + return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil]; +} + +- (UIImage *)yy_imageByBlurLight { + return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil]; +} + +- (UIImage *)yy_imageByBlurExtraLight { + return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil]; +} + +- (UIImage *)yy_imageByBlurDark { + return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil]; +} + +- (UIImage *)yy_imageByBlurWithTint:(UIColor *)tintColor { + const CGFloat EffectColorAlpha = 0.6; + UIColor *effectColor = tintColor; + size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor); + if (componentCount == 2) { + CGFloat b; + if ([tintColor getWhite:&b alpha:NULL]) { + effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha]; + } + } else { + CGFloat r, g, b; + if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) { + effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha]; + } + } + return [self yy_imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil]; +} + +- (UIImage *)yy_imageByBlurRadius:(CGFloat)blurRadius + tintColor:(UIColor *)tintColor + tintMode:(CGBlendMode)tintBlendMode + saturation:(CGFloat)saturation + maskImage:(UIImage *)maskImage { + if (self.size.width < 1 || self.size.height < 1) { + NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self); + return nil; + } + if (!self.CGImage) { + NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self); + return nil; + } + if (maskImage && !maskImage.CGImage) { + NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage); + return nil; + } + + // iOS7 and above can use new func. + BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0; + BOOL hasBlur = blurRadius > __FLT_EPSILON__; + BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__; + + CGSize size = self.size; + CGRect rect = { CGPointZero, size }; + CGFloat scale = self.scale; + CGImageRef imageRef = self.CGImage; + BOOL opaque = NO; + + if (!hasBlur && !hasSaturation) { + return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque]; + } + + vImage_Buffer effect = { 0 }, scratch = { 0 }; + vImage_Buffer *input = NULL, *output = NULL; + + vImage_CGImageFormat format = { + .bitsPerComponent = 8, + .bitsPerPixel = 32, + .colorSpace = NULL, + .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer. + .version = 0, + .decode = NULL, + .renderingIntent = kCGRenderingIntentDefault + }; + + if (hasNewFunc) { + vImage_Error err; + err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole); + if (err != kvImageNoError) { + NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self); + return nil; + } + err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags); + if (err != kvImageNoError) { + NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self); + return nil; + } + } else { + UIGraphicsBeginImageContextWithOptions(size, opaque, scale); + CGContextRef effectCtx = UIGraphicsGetCurrentContext(); + CGContextScaleCTM(effectCtx, 1.0, -1.0); + CGContextTranslateCTM(effectCtx, 0, -size.height); + CGContextDrawImage(effectCtx, rect, imageRef); + effect.data = CGBitmapContextGetData(effectCtx); + effect.width = CGBitmapContextGetWidth(effectCtx); + effect.height = CGBitmapContextGetHeight(effectCtx); + effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx); + + UIGraphicsBeginImageContextWithOptions(size, opaque, scale); + CGContextRef scratchCtx = UIGraphicsGetCurrentContext(); + scratch.data = CGBitmapContextGetData(scratchCtx); + scratch.width = CGBitmapContextGetWidth(scratchCtx); + scratch.height = CGBitmapContextGetHeight(scratchCtx); + scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx); + } + + input = &effect; + output = &scratch; + + if (hasBlur) { + // A description of how to compute the box kernel width from the Gaussian + // radius (aka standard deviation) appears in the SVG spec: + // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement + // + // For larger values of 's' (s >= 2.0), an approximation can be used: Three + // successive box-blurs build a piece-wise quadratic convolution kernel, which + // approximates the Gaussian kernel to within roughly 3%. + // + // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) + // + // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. + // + CGFloat inputRadius = blurRadius * scale; + if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0; + uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2); + radius |= 1; // force radius to be odd so that the three box-blur methodology works. + int iterations; + if (blurRadius * scale < 0.5) iterations = 1; + else if (blurRadius * scale < 1.5) iterations = 2; + else iterations = 3; + NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend); + void *temp = malloc(tempSize); + for (int i = 0; i < iterations; i++) { + vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend); + // swap + vImage_Buffer *swap_tmp = input; + input = output; + output = swap_tmp; + } + free(temp); + } + + + if (hasSaturation) { + // These values appear in the W3C Filter Effects spec: + // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent + CGFloat s = saturation; + CGFloat matrixFloat[] = { + 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, + 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, + 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, + 0, 0, 0, 1, + }; + const int32_t divisor = 256; + NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]); + int16_t matrix[matrixSize]; + for (NSUInteger i = 0; i < matrixSize; ++i) { + matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor); + } + vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags); + // swap + vImage_Buffer *swap_tmp = input; + input = output; + output = swap_tmp; + } + + UIImage *outputImage = nil; + if (hasNewFunc) { + CGImageRef effectCGImage = NULL; + effectCGImage = vImageCreateCGImageFromBuffer(input, &format, &_yy_cleanupBuffer, NULL, kvImageNoAllocate, NULL); + if (effectCGImage == NULL) { + effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL); + free(input->data); + } + free(output->data); + outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque]; + CGImageRelease(effectCGImage); + } else { + CGImageRef effectCGImage; + UIImage *effectImage; + if (input != &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + if (input == &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + effectCGImage = effectImage.CGImage; + outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque]; + } + return outputImage; +} + +// Helper function to handle deferred cleanup of a buffer. +static void _yy_cleanupBuffer(void *userData, void *buf_data) { + free(buf_data); +} + +// Helper function to add tint and mask. +- (UIImage *)_yy_mergeImageRef:(CGImageRef)effectCGImage + tintColor:(UIColor *)tintColor + tintBlendMode:(CGBlendMode)tintBlendMode + maskImage:(UIImage *)maskImage + opaque:(BOOL)opaque { + BOOL hasTint = tintColor != nil && CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__; + BOOL hasMask = maskImage != nil; + CGSize size = self.size; + CGRect rect = { CGPointZero, size }; + CGFloat scale = self.scale; + + if (!hasTint && !hasMask) { + return [UIImage imageWithCGImage:effectCGImage]; + } + + UIGraphicsBeginImageContextWithOptions(size, opaque, scale); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextScaleCTM(context, 1.0, -1.0); + CGContextTranslateCTM(context, 0, -size.height); + if (hasMask) { + CGContextDrawImage(context, rect, self.CGImage); + CGContextSaveGState(context); + CGContextClipToMask(context, rect, maskImage.CGImage); + } + CGContextDrawImage(context, rect, effectCGImage); + if (hasTint) { + CGContextSaveGState(context); + CGContextSetBlendMode(context, tintBlendMode); + CGContextSetFillColorWithColor(context, tintColor.CGColor); + CGContextFillRect(context, rect); + CGContextRestoreGState(context); + } + if (hasMask) { + CGContextRestoreGState(context); + } + UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return outputImage; +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.h b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.h new file mode 100644 index 00000000..6a662335 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.h @@ -0,0 +1,193 @@ +// +// UIImageView+YYWebImage.h +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#else +#import "YYWebImageManager.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + Web image methods for UIImageView. + */ +@interface UIImageView (YYWebImage) + +#pragma mark - image + +/** + Current image URL. + + @discussion Set a new value to this property will cancel the previous request + operation and create a new request operation to fetch image. Set nil to clear + the image and image URL. + */ +@property (nullable, nonatomic, strong) NSURL *yy_imageURL; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param options The options to use when request the image. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `image` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder he image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param manager The manager to create image request operation. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(nullable YYWebImageManager *)manager + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Cancel the current image request. + */ +- (void)yy_cancelCurrentImageRequest; + + + +#pragma mark - highlight image + +/** + Current highlighted image URL. + + @discussion Set a new value to this property will cancel the previous request + operation and create a new request operation to fetch image. Set nil to clear + the highlighted image and image URL. + */ +@property (nullable, nonatomic, strong) NSURL *yy_highlightedImageURL; + +/** + Set the view's `highlightedImage` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + */ +- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder; + +/** + Set the view's `highlightedImage` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param options The options to use when request the image. + */ +- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL options:(YYWebImageOptions)options; + +/** + Set the view's `highlightedImage` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `highlightedImage` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Set the view's `highlightedImage` with a specified URL. + + @param imageURL The image url (remote or local file path). + @param placeholder The image to be set initially, until the image request finishes. + @param options The options to use when request the image. + @param manager The manager to create image request operation. + @param progress The block invoked (on main thread) during image request. + @param transform The block invoked (on background thread) to do additional image process. + @param completion The block invoked (on main thread) when image request completed. + */ +- (void)yy_setHighlightedImageWithURL:(nullable NSURL *)imageURL + placeholder:(nullable UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(nullable YYWebImageManager *)manager + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + Cancel the current highlighed image request. + */ +- (void)yy_cancelCurrentHighlightedImageRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.m b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.m new file mode 100644 index 00000000..1719a12f --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/UIImageView+YYWebImage.m @@ -0,0 +1,355 @@ +// +// UIImageView+YYWebImage.m +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "UIImageView+YYWebImage.h" +#import "YYWebImageOperation.h" +#import "_YYWebImageSetter.h" +#import + +// Dummy class for category +@interface UIImageView_YYWebImage : NSObject @end +@implementation UIImageView_YYWebImage @end + +static int _YYWebImageSetterKey; +static int _YYWebImageHighlightedSetterKey; + + +@implementation UIImageView (YYWebImage) + +#pragma mark - image + +- (NSURL *)yy_imageURL { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + return setter.imageURL; +} + +- (void)setYy_imageURL:(NSURL *)imageURL { + [self yy_setImageWithURL:imageURL + placeholder:nil + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options { + [self yy_setImageWithURL:imageURL + placeholder:nil + options:options + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:nil + transform:nil + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + [self yy_setImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:progress + transform:transform + completion:completion]; +} + +- (void)yy_setImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL]; + manager = manager ? manager : [YYWebImageManager sharedManager]; + + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + if (!setter) { + setter = [_YYWebImageSetter new]; + objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + int32_t sentinel = [setter cancelWithNewURL:imageURL]; + + _yy_dispatch_sync_on_main_queue(^{ + if ((options & YYWebImageOptionSetImageWithFadeAnimation) && + !(options & YYWebImageOptionAvoidSetImage)) { + if (!self.highlighted) { + [self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey]; + } + } + + if (!imageURL) { + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.image = placeholder; + } + return; + } + + // get the image from memory as quickly as possible + UIImage *imageFromMemory = nil; + if (manager.cache && + !(options & YYWebImageOptionUseNSURLCache) && + !(options & YYWebImageOptionRefreshImageCache)) { + imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory]; + } + if (imageFromMemory) { + if (!(options & YYWebImageOptionAvoidSetImage)) { + self.image = imageFromMemory; + } + if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil); + return; + } + + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.image = placeholder; + } + + __weak typeof(self) _self = self; + dispatch_async([_YYWebImageSetter setterQueue], ^{ + YYWebImageProgressBlock _progress = nil; + if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) { + dispatch_async(dispatch_get_main_queue(), ^{ + progress(receivedSize, expectedSize); + }); + }; + + __block int32_t newSentinel = 0; + __block __weak typeof(setter) weakSetter = nil; + YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + __strong typeof(_self) self = _self; + BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage); + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel; + if (setImage && self && !sentinelChanged) { + BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted); + if (showFade) { + CATransition *transition = [CATransition animation]; + transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime; + transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + transition.type = kCATransitionFade; + [self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey]; + } + self.image = image; + } + if (completion) { + if (sentinelChanged) { + completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil); + } else { + completion(image, url, from, stage, error); + } + } + }); + }; + + newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion]; + weakSetter = setter; + }); + }); +} + +- (void)yy_cancelCurrentImageRequest { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey); + if (setter) [setter cancel]; +} + + +#pragma mark - highlighted image + +- (NSURL *)yy_highlightedImageURL { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageHighlightedSetterKey); + return setter.imageURL; +} + +- (void)setYy_highlightedImageURL:(NSURL *)imageURL { + [self yy_setHighlightedImageWithURL:imageURL + placeholder:nil + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL placeholder:(UIImage *)placeholder { + [self yy_setHighlightedImageWithURL:imageURL + placeholder:placeholder + options:kNilOptions + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL options:(YYWebImageOptions)options { + [self yy_setHighlightedImageWithURL:imageURL + placeholder:nil + options:options + manager:nil + progress:nil + transform:nil + completion:nil]; +} + +- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + completion:(YYWebImageCompletionBlock)completion { + [self yy_setHighlightedImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:nil + transform:nil + completion:completion]; +} + +- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + [self yy_setHighlightedImageWithURL:imageURL + placeholder:placeholder + options:options + manager:nil + progress:progress + transform:nil + completion:completion]; +} + +- (void)yy_setHighlightedImageWithURL:(NSURL *)imageURL + placeholder:(UIImage *)placeholder + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL]; + manager = manager ? manager : [YYWebImageManager sharedManager]; + + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageHighlightedSetterKey); + if (!setter) { + setter = [_YYWebImageSetter new]; + objc_setAssociatedObject(self, &_YYWebImageHighlightedSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + int32_t sentinel = [setter cancelWithNewURL:imageURL]; + + _yy_dispatch_sync_on_main_queue(^{ + if ((options & YYWebImageOptionSetImageWithFadeAnimation) && + !(options & YYWebImageOptionAvoidSetImage)) { + if (self.highlighted) { + [self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey]; + } + } + if (!imageURL) { + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.highlightedImage = placeholder; + } + return; + } + + // get the image from memory as quickly as possible + UIImage *imageFromMemory = nil; + if (manager.cache && + !(options & YYWebImageOptionUseNSURLCache) && + !(options & YYWebImageOptionRefreshImageCache)) { + imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory]; + } + if (imageFromMemory) { + if (!(options & YYWebImageOptionAvoidSetImage)) { + self.highlightedImage = imageFromMemory; + } + if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil); + return; + } + + if (!(options & YYWebImageOptionIgnorePlaceHolder)) { + self.highlightedImage = placeholder; + } + + __weak typeof(self) _self = self; + dispatch_async([_YYWebImageSetter setterQueue], ^{ + YYWebImageProgressBlock _progress = nil; + if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) { + dispatch_async(dispatch_get_main_queue(), ^{ + progress(receivedSize, expectedSize); + }); + }; + + __block int32_t newSentinel = 0; + __block __weak typeof(setter) weakSetter = nil; + YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + __strong typeof(_self) self = _self; + BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage); + BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && self.highlighted); + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel; + if (setImage && self && !sentinelChanged) { + if (showFade) { + CATransition *transition = [CATransition animation]; + transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime; + transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + transition.type = kCATransitionFade; + [self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey]; + } + self.highlightedImage = image; + } + if (completion) { + if (sentinelChanged) { + completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil); + } else { + completion(image, url, from, stage, error); + } + } + }); + }; + + newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion]; + weakSetter = setter; + }); + }); +} + +- (void)yy_cancelCurrentHighlightedImageRequest { + _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageHighlightedSetterKey); + if (setter) [setter cancel]; +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/_YYWebImageSetter.h b/Example/Pods/YYWebImage/YYWebImage/Categories/_YYWebImageSetter.h new file mode 100644 index 00000000..cf5a1186 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/_YYWebImageSetter.h @@ -0,0 +1,67 @@ +// +// _YYWebImageSetter.h +// YYWebImage +// +// Created by ibireme on 15/7/15. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// +#import +#import + +#if __has_include() +#import +#else +#import "YYWebImageManager.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + Submits a block for execution on a main queue and waits until the block completes. + */ +static inline void _yy_dispatch_sync_on_main_queue(void (^block)()) { + if (pthread_main_np()) { + block(); + } else { + dispatch_sync(dispatch_get_main_queue(), block); + } +} + +extern NSString *const _YYWebImageFadeAnimationKey; +extern const NSTimeInterval _YYWebImageFadeTime; +extern const NSTimeInterval _YYWebImageProgressiveFadeTime; + +/** + Private class used by web image categories. + Typically, you should not use this class directly. + */ +@interface _YYWebImageSetter : NSObject +/// Current image url. +@property (nullable, nonatomic, readonly) NSURL *imageURL; +/// Current sentinel. +@property (nonatomic, readonly) int32_t sentinel; + +/// Create new operation for web image and return a sentinel value. +- (int32_t)setOperationWithSentinel:(int32_t)sentinel + url:(nullable NSURL *)imageURL + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/// Cancel and return a sentinel value. The imageURL will be set to nil. +- (int32_t)cancel; + +/// Cancel and return a sentinel value. The imageURL will be set to new value. +- (int32_t)cancelWithNewURL:(nullable NSURL *)imageURL; + +/// A queue to set operation. ++ (dispatch_queue_t)setterQueue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/Categories/_YYWebImageSetter.m b/Example/Pods/YYWebImage/YYWebImage/Categories/_YYWebImageSetter.m new file mode 100644 index 00000000..ec3c99b6 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/Categories/_YYWebImageSetter.m @@ -0,0 +1,103 @@ +// +// _YYWebImageSetter.m +// YYWebImage +// +// Created by ibireme on 15/7/15. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "_YYWebImageSetter.h" +#import "YYWebImageOperation.h" +#import + +NSString *const _YYWebImageFadeAnimationKey = @"YYWebImageFade"; +const NSTimeInterval _YYWebImageFadeTime = 0.2; +const NSTimeInterval _YYWebImageProgressiveFadeTime = 0.4; + + +@implementation _YYWebImageSetter { + dispatch_semaphore_t _lock; + NSURL *_imageURL; + NSOperation *_operation; + int32_t _sentinel; +} + +- (instancetype)init { + self = [super init]; + _lock = dispatch_semaphore_create(1); + return self; +} + +- (NSURL *)imageURL { + dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + NSURL *imageURL = _imageURL; + dispatch_semaphore_signal(_lock); + return imageURL; +} + +- (void)dealloc { + OSAtomicIncrement32(&_sentinel); + [_operation cancel]; +} + +- (int32_t)setOperationWithSentinel:(int32_t)sentinel + url:(NSURL *)imageURL + options:(YYWebImageOptions)options + manager:(YYWebImageManager *)manager + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + if (sentinel != _sentinel) { + if (completion) completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageCancelled, nil); + return _sentinel; + } + + NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion]; + if (!operation && completion) { + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"YYWebImageOperation create failed." }; + completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageFinished, [NSError errorWithDomain:@"com.ibireme.webimage" code:-1 userInfo:userInfo]); + } + + dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + if (sentinel == _sentinel) { + if (_operation) [_operation cancel]; + _operation = operation; + sentinel = OSAtomicIncrement32(&_sentinel); + } else { + [operation cancel]; + } + dispatch_semaphore_signal(_lock); + return sentinel; +} + +- (int32_t)cancel { + return [self cancelWithNewURL:nil]; +} + +- (int32_t)cancelWithNewURL:(NSURL *)imageURL { + int32_t sentinel; + dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + if (_operation) { + [_operation cancel]; + _operation = nil; + } + _imageURL = imageURL; + sentinel = OSAtomicIncrement32(&_sentinel); + dispatch_semaphore_signal(_lock); + return sentinel; +} + ++ (dispatch_queue_t)setterQueue { + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("com.ibireme.webimage.setter", DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); + }); + return queue; +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/YYImageCache.h b/Example/Pods/YYWebImage/YYWebImage/YYImageCache.h new file mode 100644 index 00000000..ad382883 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/YYImageCache.h @@ -0,0 +1,228 @@ +// +// YYImageCache.h +// YYWebImage +// +// Created by ibireme on 15/2/15. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +@class YYMemoryCache, YYDiskCache; + +NS_ASSUME_NONNULL_BEGIN + +/// Image cache type +typedef NS_OPTIONS(NSUInteger, YYImageCacheType) { + /// No value. + YYImageCacheTypeNone = 0, + + /// Get/store image with memory cache. + YYImageCacheTypeMemory = 1 << 0, + + /// Get/store image with disk cache. + YYImageCacheTypeDisk = 1 << 1, + + /// Get/store image with both memory cache and disk cache. + YYImageCacheTypeAll = YYImageCacheTypeMemory | YYImageCacheTypeDisk, +}; + + +/** + YYImageCache is a cache that stores UIImage and image data based on memory cache and disk cache. + + @discussion The disk cache will try to protect the original image data: + + * If the original image is still image, it will be saved as png/jpeg file based on alpha information. + * If the original image is animated gif, apng or webp, it will be saved as original format. + * If the original image's scale is not 1, the scale value will be saved as extended data. + + Although UIImage can be serialized with NSCoding protocol, but it's not a good idea: + Apple actually use UIImagePNGRepresentation() to encode all kind of image, it may + lose the original multi-frame data. The result is packed to plist file and cannot + view with photo viewer directly. If the image has no alpha channel, using JPEG + instead of PNG can save more disk size and encoding/decoding time. + */ +@interface YYImageCache : NSObject + +#pragma mark - Attribute +///============================================================================= +/// @name Attribute +///============================================================================= + +/** The name of the cache. Default is nil. */ +@property (nullable, copy) NSString *name; + +/** The underlying memory cache. see `YYMemoryCache` for more information.*/ +@property (strong, readonly) YYMemoryCache *memoryCache; + +/** The underlying disk cache. see `YYDiskCache` for more information.*/ +@property (strong, readonly) YYDiskCache *diskCache; + +/** + Whether decode animated image when fetch image from disk cache. Default is YES. + + @discussion When fetch image from disk cache, it will use 'YYImage' to decode + animated image such as WebP/APNG/GIF. Set to 'NO' to ignore animated image. + */ +@property BOOL allowAnimatedImage; + +/** + Whether decode the image to memory bitmap. Default is YES. + + @discussion If the value is YES, then the image will be decoded to memory bitmap + for better display performance, but may cost more memory. + */ +@property BOOL decodeForDisplay; + + +#pragma mark - Initializer +///============================================================================= +/// @name Initializer +///============================================================================= +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +/** + Returns global shared image cache instance. + @return The singleton YYImageCache instance. + */ ++ (instancetype)sharedCache; + +/** + The designated initializer. Multiple instances with the same path will make the + cache unstable. + + @param path Full path of a directory in which the cache will write data. + Once initialized you should not read and write to this directory. + @result A new cache object, or nil if an error occurs. + */ +- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER; + + +#pragma mark - Access Methods +///============================================================================= +/// @name Access Methods +///============================================================================= + +/** + Sets the image with the specified key in the cache (both memory and disk). + This method returns immediately and executes the store operation in background. + + @param image The image to be stored in the cache. If nil, this method has no effect. + @param key The key with which to associate the image. If nil, this method has no effect. + */ +- (void)setImage:(UIImage *)image forKey:(NSString *)key; + +/** + Sets the image with the specified key in the cache. + This method returns immediately and executes the store operation in background. + + @discussion If the `type` contain `YYImageCacheTypeMemory`, then the `image` will + be stored in the memory cache; `imageData` will be used instead if `image` is nil. + If the `type` contain `YYImageCacheTypeDisk`, then the `imageData` will + be stored in the disk cache; `image` will be used instead if `imageData` is nil. + + @param image The image to be stored in the cache. + @param imageData The image data to be stored in the cache. + @param key The key with which to associate the image. If nil, this method has no effect. + @param type The cache type to store image. + */ +- (void)setImage:(nullable UIImage *)image + imageData:(nullable NSData *)imageData + forKey:(NSString *)key + withType:(YYImageCacheType)type; + +/** + Removes the image of the specified key in the cache (both memory and disk). + This method returns immediately and executes the remove operation in background. + + @param key The key identifying the image to be removed. If nil, this method has no effect. + */ +- (void)removeImageForKey:(NSString *)key; + +/** + Removes the image of the specified key in the cache. + This method returns immediately and executes the remove operation in background. + + @param key The key identifying the image to be removed. If nil, this method has no effect. + @param type The cache type to remove image. + */ +- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type; + +/** + Returns a Boolean value that indicates whether a given key is in cache. + If the image is not in memory, this method may blocks the calling thread until + file read finished. + + @param key A string identifying the image. If nil, just return NO. + @return Whether the image is in cache. + */ +- (BOOL)containsImageForKey:(NSString *)key; + +/** + Returns a Boolean value that indicates whether a given key is in cache. + If the image is not in memory and the `type` contains `YYImageCacheTypeDisk`, + this method may blocks the calling thread until file read finished. + + @param key A string identifying the image. If nil, just return NO. + @param type The cache type. + @return Whether the image is in cache. + */ +- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type; + +/** + Returns the image associated with a given key. + If the image is not in memory, this method may blocks the calling thread until + file read finished. + + @param key A string identifying the image. If nil, just return nil. + @return The image associated with key, or nil if no image is associated with key. + */ +- (nullable UIImage *)getImageForKey:(NSString *)key; + +/** + Returns the image associated with a given key. + If the image is not in memory and the `type` contains `YYImageCacheTypeDisk`, + this method may blocks the calling thread until file read finished. + + @param key A string identifying the image. If nil, just return nil. + @return The image associated with key, or nil if no image is associated with key. + */ +- (nullable UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type; + +/** + Asynchronously get the image associated with a given key. + + @param key A string identifying the image. If nil, just return nil. + @param type The cache type. + @param block A completion block which will be called on main thread. + */ +- (void)getImageForKey:(NSString *)key + withType:(YYImageCacheType)type + withBlock:(void(^)(UIImage * _Nullable image, YYImageCacheType type))block; + +/** + Returns the image data associated with a given key. + This method may blocks the calling thread until file read finished. + + @param key A string identifying the image. If nil, just return nil. + @return The image data associated with key, or nil if no image is associated with key. + */ +- (nullable NSData *)getImageDataForKey:(NSString *)key; + +/** + Asynchronously get the image data associated with a given key. + + @param key A string identifying the image. If nil, just return nil. + @param block A completion block which will be called on main thread. + */ +- (void)getImageDataForKey:(NSString *)key + withBlock:(void(^)(NSData * _Nullable imageData))block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/YYImageCache.m b/Example/Pods/YYWebImage/YYWebImage/YYImageCache.m new file mode 100644 index 00000000..65b2779f --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/YYImageCache.m @@ -0,0 +1,253 @@ +// +// YYImageCache.m +// YYWebImage +// +// Created by ibireme on 15/2/15. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYImageCache.h" +#import "YYImage.h" +#import "UIImage+YYWebImage.h" + +#if __has_include() +#import +#else +#import "YYImage.h" +#endif + +#if __has_include() +#import +#else +#import "YYCache.h" +#endif + + + +static inline dispatch_queue_t YYImageCacheIOQueue() { + return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); +} + +static inline dispatch_queue_t YYImageCacheDecodeQueue() { + return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); +} + + +@interface YYImageCache () +- (NSUInteger)imageCost:(UIImage *)image; +- (UIImage *)imageFromData:(NSData *)data; +@end + + +@implementation YYImageCache + +- (NSUInteger)imageCost:(UIImage *)image { + CGImageRef cgImage = image.CGImage; + if (!cgImage) return 1; + CGFloat height = CGImageGetHeight(cgImage); + size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); + NSUInteger cost = bytesPerRow * height; + if (cost == 0) cost = 1; + return cost; +} + +- (UIImage *)imageFromData:(NSData *)data { + NSData *scaleData = [YYDiskCache getExtendedDataFromObject:data]; + CGFloat scale = 0; + if (scaleData) { + scale = ((NSNumber *)[NSKeyedUnarchiver unarchiveObjectWithData:scaleData]).doubleValue; + } + if (scale <= 0) scale = [UIScreen mainScreen].scale; + UIImage *image; + if (_allowAnimatedImage) { + image = [[YYImage alloc] initWithData:data scale:scale]; + if (_decodeForDisplay) image = [image yy_imageByDecoded]; + } else { + YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale]; + image = [decoder frameAtIndex:0 decodeForDisplay:_decodeForDisplay].image; + } + return image; +} + +#pragma mark Public + ++ (instancetype)sharedCache { + static YYImageCache *cache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, + NSUserDomainMask, YES) firstObject]; + cachePath = [cachePath stringByAppendingPathComponent:@"com.ibireme.yykit"]; + cachePath = [cachePath stringByAppendingPathComponent:@"images"]; + cache = [[self alloc] initWithPath:cachePath]; + }); + return cache; +} + +- (instancetype)init { + @throw [NSException exceptionWithName:@"YYImageCache init error" reason:@"YYImageCache must be initialized with a path. Use 'initWithPath:' instead." userInfo:nil]; + return [self initWithPath:@""]; +} + +- (instancetype)initWithPath:(NSString *)path { + YYMemoryCache *memoryCache = [YYMemoryCache new]; + memoryCache.shouldRemoveAllObjectsOnMemoryWarning = YES; + memoryCache.shouldRemoveAllObjectsWhenEnteringBackground = YES; + memoryCache.countLimit = NSUIntegerMax; + memoryCache.costLimit = NSUIntegerMax; + memoryCache.ageLimit = 12 * 60 * 60; + + YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path]; + diskCache.customArchiveBlock = ^(id object) { return (NSData *)object; }; + diskCache.customUnarchiveBlock = ^(NSData *data) { return (id)data; }; + if (!memoryCache || !diskCache) return nil; + + self = [super init]; + _memoryCache = memoryCache; + _diskCache = diskCache; + _allowAnimatedImage = YES; + _decodeForDisplay = YES; + return self; +} + +- (void)setImage:(UIImage *)image forKey:(NSString *)key { + [self setImage:image imageData:nil forKey:key withType:YYImageCacheTypeAll]; +} + +- (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type { + if (!key || (image == nil && imageData.length == 0)) return; + + __weak typeof(self) _self = self; + if (type & YYImageCacheTypeMemory) { // add to memory cache + if (image) { + if (image.yy_isDecodedForDisplay) { + [_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]]; + } else { + dispatch_async(YYImageCacheDecodeQueue(), ^{ + __strong typeof(_self) self = _self; + if (!self) return; + [self.memoryCache setObject:[image yy_imageByDecoded] forKey:key withCost:[self imageCost:image]]; + }); + } + } else if (imageData) { + dispatch_async(YYImageCacheDecodeQueue(), ^{ + __strong typeof(_self) self = _self; + if (!self) return; + UIImage *newImage = [self imageFromData:imageData]; + [self.memoryCache setObject:newImage forKey:key withCost:[self imageCost:newImage]]; + }); + } + } + if (type & YYImageCacheTypeDisk) { // add to disk cache + if (imageData) { + if (image) { + [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:imageData]; + } + [_diskCache setObject:imageData forKey:key]; + } else if (image) { + dispatch_async(YYImageCacheIOQueue(), ^{ + __strong typeof(_self) self = _self; + if (!self) return; + NSData *data = [image yy_imageDataRepresentation]; + [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data]; + [self.diskCache setObject:data forKey:key]; + }); + } + } +} + +- (void)removeImageForKey:(NSString *)key { + [self removeImageForKey:key withType:YYImageCacheTypeAll]; +} + +- (void)removeImageForKey:(NSString *)key withType:(YYImageCacheType)type { + if (type & YYImageCacheTypeMemory) [_memoryCache removeObjectForKey:key]; + if (type & YYImageCacheTypeDisk) [_diskCache removeObjectForKey:key]; +} + +- (BOOL)containsImageForKey:(NSString *)key { + return [self containsImageForKey:key withType:YYImageCacheTypeAll]; +} + +- (BOOL)containsImageForKey:(NSString *)key withType:(YYImageCacheType)type { + if (type & YYImageCacheTypeMemory) { + if ([_memoryCache containsObjectForKey:key]) return YES; + } + if (type & YYImageCacheTypeDisk) { + if ([_diskCache containsObjectForKey:key]) return YES; + } + return NO; +} + +- (UIImage *)getImageForKey:(NSString *)key { + return [self getImageForKey:key withType:YYImageCacheTypeAll]; +} + +- (UIImage *)getImageForKey:(NSString *)key withType:(YYImageCacheType)type { + if (!key) return nil; + if (type & YYImageCacheTypeMemory) { + UIImage *image = [_memoryCache objectForKey:key]; + if (image) return image; + } + if (type & YYImageCacheTypeDisk) { + NSData *data = (id)[_diskCache objectForKey:key]; + UIImage *image = [self imageFromData:data]; + if (image && (type & YYImageCacheTypeMemory)) { + [_memoryCache setObject:image forKey:key withCost:[self imageCost:image]]; + } + return image; + } + return nil; +} + +- (void)getImageForKey:(NSString *)key withType:(YYImageCacheType)type withBlock:(void (^)(UIImage *image, YYImageCacheType type))block { + if (!block) return; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + UIImage *image = nil; + + if (type & YYImageCacheTypeMemory) { + image = [_memoryCache objectForKey:key]; + if (image) { + dispatch_async(dispatch_get_main_queue(), ^{ + block(image, YYImageCacheTypeMemory); + }); + return; + } + } + + if (type & YYImageCacheTypeDisk) { + NSData *data = (id)[_diskCache objectForKey:key]; + image = [self imageFromData:data]; + if (image) { + [_memoryCache setObject:image forKey:key]; + dispatch_async(dispatch_get_main_queue(), ^{ + block(image, YYImageCacheTypeDisk); + }); + return; + } + } + + dispatch_async(dispatch_get_main_queue(), ^{ + block(nil, YYImageCacheTypeNone); + }); + }); +} + +- (NSData *)getImageDataForKey:(NSString *)key { + return (id)[_diskCache objectForKey:key]; +} + +- (void)getImageDataForKey:(NSString *)key withBlock:(void (^)(NSData *imageData))block { + if (!block) return; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSData *data = (id)[_diskCache objectForKey:key]; + dispatch_async(dispatch_get_main_queue(), ^{ + block(data); + }); + }); +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/YYWebImage.h b/Example/Pods/YYWebImage/YYWebImage/YYWebImage.h new file mode 100644 index 00000000..407d52c0 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/YYWebImage.h @@ -0,0 +1,65 @@ +// +// YYWebImage.h +// YYWebImage +// +// Created by ibireme on 15/2/23. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +FOUNDATION_EXPORT double YYWebImageVersionNumber; +FOUNDATION_EXPORT const unsigned char YYWebImageVersionString[]; +#import +#import +#import +#import +#import +#import +#import +#import +#else +#import "YYImageCache.h" +#import "YYWebImageOperation.h" +#import "YYWebImageManager.h" +#import "UIImage+YYWebImage.h" +#import "UIImageView+YYWebImage.h" +#import "UIButton+YYWebImage.h" +#import "CALayer+YYWebImage.h" +#import "MKAnnotationView+YYWebImage.h" +#endif + +#if __has_include() +#import +#elif __has_include() +#import +#import +#import +#import +#import +#else +#import "YYImage.h" +#import "YYFrameImage.h" +#import "YYSpriteSheetImage.h" +#import "YYImageCoder.h" +#import "YYAnimatedImageView.h" +#endif + +#if __has_include() +#import +#elif __has_include() +#import +#import +#import +#import +#else +#import "YYCache.h" +#import "YYMemoryCache.h" +#import "YYDiskCache.h" +#import "YYKVStorage.h" +#endif + diff --git a/Example/Pods/YYWebImage/YYWebImage/YYWebImageManager.h b/Example/Pods/YYWebImage/YYWebImage/YYWebImageManager.h new file mode 100644 index 00000000..6947fafd --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/YYWebImageManager.h @@ -0,0 +1,312 @@ +// +// YYWebImageManager.h +// YYWebImage +// +// Created by ibireme on 15/2/19. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#else +#import "YYImageCache.h" +#endif + +@class YYWebImageOperation; + +NS_ASSUME_NONNULL_BEGIN + +/// The options to control image operation. +typedef NS_OPTIONS(NSUInteger, YYWebImageOptions) { + + /// Show network activity on status bar when download image. + YYWebImageOptionShowNetworkActivity = 1 << 0, + + /// Display progressive/interlaced/baseline image during download (same as web browser). + YYWebImageOptionProgressive = 1 << 1, + + /// Display blurred progressive JPEG or interlaced PNG image during download. + /// This will ignore baseline image for better user experience. + YYWebImageOptionProgressiveBlur = 1 << 2, + + /// Use NSURLCache instead of YYImageCache. + YYWebImageOptionUseNSURLCache = 1 << 3, + + /// Allows untrusted SSL ceriticates. + YYWebImageOptionAllowInvalidSSLCertificates = 1 << 4, + + /// Allows background task to download image when app is in background. + YYWebImageOptionAllowBackgroundTask = 1 << 5, + + /// Handles cookies stored in NSHTTPCookieStore. + YYWebImageOptionHandleCookies = 1 << 6, + + /// Load the image from remote and refresh the image cache. + YYWebImageOptionRefreshImageCache = 1 << 7, + + /// Do not load image from/to disk cache. + YYWebImageOptionIgnoreDiskCache = 1 << 8, + + /// Do not change the view's image before set a new URL to it. + YYWebImageOptionIgnorePlaceHolder = 1 << 9, + + /// Ignore image decoding. + /// This may used for image downloading without display. + YYWebImageOptionIgnoreImageDecoding = 1 << 10, + + /// Ignore multi-frame image decoding. + /// This will handle the GIF/APNG/WebP/ICO image as single frame image. + YYWebImageOptionIgnoreAnimatedImage = 1 << 11, + + /// Set the image to view with a fade animation. + /// This will add a "fade" animation on image view's layer for better user experience. + YYWebImageOptionSetImageWithFadeAnimation = 1 << 12, + + /// Do not set the image to the view when image fetch complete. + /// You may set the image manually. + YYWebImageOptionAvoidSetImage = 1 << 13, + + /// This flag will add the URL to a blacklist (in memory) when the URL fail to be downloaded, + /// so the library won't keep trying. + YYWebImageOptionIgnoreFailedURL = 1 << 14, +}; + +/// Indicated where the image came from. +typedef NS_ENUM(NSUInteger, YYWebImageFromType) { + + /// No value. + YYWebImageFromNone = 0, + + /// Fetched from memory cache immediately. + /// If you called "setImageWithURL:..." and the image is already in memory, + /// then you will get this value at the same call. + YYWebImageFromMemoryCacheFast, + + /// Fetched from memory cache. + YYWebImageFromMemoryCache, + + /// Fetched from disk cache. + YYWebImageFromDiskCache, + + /// Fetched from remote (web or file path). + YYWebImageFromRemote, +}; + +/// Indicated image fetch complete stage. +typedef NS_ENUM(NSInteger, YYWebImageStage) { + + /// Incomplete, progressive image. + YYWebImageStageProgress = -1, + + /// Cancelled. + YYWebImageStageCancelled = 0, + + /// Finished (succeed or failed). + YYWebImageStageFinished = 1, +}; + + +/** + The block invoked in remote image fetch progress. + + @param receivedSize Current received size in bytes. + @param expectedSize Expected total size in bytes (-1 means unknown). + */ +typedef void(^YYWebImageProgressBlock)(NSInteger receivedSize, NSInteger expectedSize); + +/** + The block invoked before remote image fetch finished to do additional image process. + + @discussion This block will be invoked before `YYWebImageCompletionBlock` to give + you a chance to do additional image process (such as resize or crop). If there's + no need to transform the image, just return the `image` parameter. + + @example You can clip the image, blur it and add rounded corners with these code: + ^(UIImage *image, NSURL *url) { + // Maybe you need to create an @autoreleasepool to limit memory cost. + image = [image yy_imageByResizeToSize:CGSizeMake(100, 100) contentMode:UIViewContentModeScaleAspectFill]; + image = [image yy_imageByBlurRadius:20 tintColor:nil tintMode:kCGBlendModeNormal saturation:1.2 maskImage:nil]; + image = [image yy_imageByRoundCornerRadius:5]; + return image; + } + + @param image The image fetched from url. + @param url The image url (remote or local file path). + @return The transformed image. + */ +typedef UIImage * _Nullable (^YYWebImageTransformBlock)(UIImage *image, NSURL *url); + +/** + The block invoked when image fetch finished or cancelled. + + @param image The image. + @param url The image url (remote or local file path). + @param from Where the image came from. + @param error Error during image fetching. + @param finished If the operation is cancelled, this value is NO, otherwise YES. + */ +typedef void (^YYWebImageCompletionBlock)(UIImage * _Nullable image, + NSURL *url, + YYWebImageFromType from, + YYWebImageStage stage, + NSError * _Nullable error); + + + + +/** + A manager to create and manage web image operation. + */ +@interface YYWebImageManager : NSObject + +/** + Returns global YYWebImageManager instance. + + @return YYWebImageManager shared instance. + */ ++ (instancetype)sharedManager; + +/** + Creates a manager with an image cache and operation queue. + + @param cache Image cache used by manager (pass nil to avoid image cache). + @param queue The operation queue on which image operations are scheduled and run + (pass nil to make the new operation start immediately without queue). + @return A new manager. + */ +- (instancetype)initWithCache:(nullable YYImageCache *)cache + queue:(nullable NSOperationQueue *)queue NS_DESIGNATED_INITIALIZER; + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +/** + Creates and returns a new image operation, the operation will start immediately. + + @param url The image url (remote or local file path). + @param options The options to control image operation. + @param progress Progress block which will be invoked on background thread (pass nil to avoid). + @param transform Transform block which will be invoked on background thread (pass nil to avoid). + @param completion Completion block which will be invoked on background thread (pass nil to avoid). + @return A new image operation. + */ +- (nullable YYWebImageOperation *)requestImageWithURL:(NSURL *)url + options:(YYWebImageOptions)options + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion; + +/** + The image cache used by image operation. + You can set it to nil to avoid image cache. + */ +@property (nullable, nonatomic, strong) YYImageCache *cache; + +/** + The operation queue on which image operations are scheduled and run. + You can set it to nil to make the new operation start immediately without queue. + + You can use this queue to control maximum number of concurrent operations, to obtain + the status of the current operations, or to cancel all operations in this manager. + */ +@property (nullable, nonatomic, strong) NSOperationQueue *queue; + +/** + The shared transform block to process image. Default is nil. + + When called `requestImageWithURL:options:progress:transform:completion` and + the `transform` is nil, this block will be used. + */ +@property (nullable, nonatomic, copy) YYWebImageTransformBlock sharedTransformBlock; + +/** + The image request timeout interval in seconds. Default is 15. + */ +@property (nonatomic) NSTimeInterval timeout; + +/** + The username used by NSURLCredential, default is nil. + */ +@property (nullable, nonatomic, copy) NSString *username; + +/** + The password used by NSURLCredential, default is nil. + */ +@property (nullable, nonatomic, copy) NSString *password; + +/** + The image HTTP request header. Default is "Accept:image/webp,image/\*;q=0.8". + */ +@property (nullable, nonatomic, copy) NSDictionary *headers; + +/** + A block which will be invoked for each image HTTP request to do additional + HTTP header process. Default is nil. + + Use this block to add or remove HTTP header field for a specified URL. + */ +@property (nullable, nonatomic, copy) NSDictionary *(^headersFilter)(NSURL *url, NSDictionary * _Nullable header); + +/** + A block which will be invoked for each image operation. Default is nil. + + Use this block to provide a custom image cache key for a specified URL. + */ +@property (nullable, nonatomic, copy) NSString *(^cacheKeyFilter)(NSURL *url); + +/** + Returns the HTTP headers for a specified URL. + + @param url A specified URL. + @return HTTP headers. + */ +- (nullable NSDictionary *)headersForURL:(NSURL *)url; + +/** + Returns the cache key for a specified URL. + + @param url A specified URL + @return Cache key used in YYImageCache. + */ +- (NSString *)cacheKeyForURL:(NSURL *)url; + + +/** + Increments the number of active network requests. + If this number was zero before incrementing, this will start animating the + status bar network activity indicator. + + This method is thread safe. + + This method has no effect in App Extension. + */ ++ (void)incrementNetworkActivityCount; + +/** + Decrements the number of active network requests. + If this number becomes zero after decrementing, this will stop animating the + status bar network activity indicator. + + This method is thread safe. + + This method has no effect in App Extension. + */ ++ (void)decrementNetworkActivityCount; + +/** + Get current number of active network requests. + + This method is thread safe. + + This method has no effect in App Extension. + */ ++ (NSInteger)currentNetworkActivityCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/YYWebImageManager.m b/Example/Pods/YYWebImage/YYWebImage/YYWebImageManager.m new file mode 100644 index 00000000..784fd8be --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/YYWebImageManager.m @@ -0,0 +1,184 @@ +// +// YYWebImageManager.m +// YYWebImage +// +// Created by ibireme on 15/2/19. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYWebImageManager.h" +#import "YYImageCache.h" +#import "YYWebImageOperation.h" +#import "YYImageCoder.h" +#import + +#define kNetworkIndicatorDelay (1/30.0) + + +/// Returns nil in App Extension. +static UIApplication *_YYSharedApplication() { + static BOOL isAppExtension = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class cls = NSClassFromString(@"UIApplication"); + if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES; + if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES; + }); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)]; +#pragma clang diagnostic pop +} + + +@interface _YYWebImageApplicationNetworkIndicatorInfo : NSObject +@property (nonatomic, assign) NSInteger count; +@property (nonatomic, strong) NSTimer *timer; +@end +@implementation _YYWebImageApplicationNetworkIndicatorInfo +@end + +@implementation YYWebImageManager + ++ (instancetype)sharedManager { + static YYWebImageManager *manager; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + YYImageCache *cache = [YYImageCache sharedCache]; + NSOperationQueue *queue = [NSOperationQueue new]; + if ([queue respondsToSelector:@selector(setQualityOfService:)]) { + queue.qualityOfService = NSQualityOfServiceBackground; + } + manager = [[self alloc] initWithCache:cache queue:queue]; + }); + return manager; +} + +- (instancetype)init { + @throw [NSException exceptionWithName:@"YYWebImageManager init error" reason:@"Use the designated initializer to init." userInfo:nil]; + return [self initWithCache:nil queue:nil]; +} + +- (instancetype)initWithCache:(YYImageCache *)cache queue:(NSOperationQueue *)queue{ + self = [super init]; + if (!self) return nil; + _cache = cache; + _queue = queue; + _timeout = 15.0; + if (YYImageWebPAvailable()) { + _headers = @{ @"Accept" : @"image/webp,image/*;q=0.8" }; + } else { + _headers = @{ @"Accept" : @"image/*;q=0.8" }; + } + return self; +} + +- (YYWebImageOperation *)requestImageWithURL:(NSURL *)url + options:(YYWebImageOptions)options + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.timeoutInterval = _timeout; + request.HTTPShouldHandleCookies = (options & YYWebImageOptionHandleCookies) != 0; + request.allHTTPHeaderFields = [self headersForURL:url]; + request.HTTPShouldUsePipelining = YES; + request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ? + NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData; + + YYWebImageOperation *operation = [[YYWebImageOperation alloc] initWithRequest:request + options:options + cache:_cache + cacheKey:[self cacheKeyForURL:url] + progress:progress + transform:transform ? transform : _sharedTransformBlock + completion:completion]; + + if (_username && _password) { + operation.credential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceForSession]; + } + if (operation) { + NSOperationQueue *queue = _queue; + if (queue) { + [queue addOperation:operation]; + } else { + [operation start]; + } + } + return operation; +} + +- (NSDictionary *)headersForURL:(NSURL *)url { + if (!url) return nil; + return _headersFilter ? _headersFilter(url, _headers) : _headers; +} + +- (NSString *)cacheKeyForURL:(NSURL *)url { + if (!url) return nil; + return _cacheKeyFilter ? _cacheKeyFilter(url) : url.absoluteString; +} + + + +#pragma mark Network Indicator + ++ (_YYWebImageApplicationNetworkIndicatorInfo *)_networkIndicatorInfo { + return objc_getAssociatedObject(self, @selector(_networkIndicatorInfo)); +} + ++ (void)_setNetworkIndicatorInfo:(_YYWebImageApplicationNetworkIndicatorInfo *)info { + objc_setAssociatedObject(self, @selector(_networkIndicatorInfo), info, OBJC_ASSOCIATION_RETAIN); +} + ++ (void)_delaySetActivity:(NSTimer *)timer { + UIApplication *app = _YYSharedApplication(); + if (!app) return; + + NSNumber *visiable = timer.userInfo; + if (app.networkActivityIndicatorVisible != visiable.boolValue) { + [app setNetworkActivityIndicatorVisible:visiable.boolValue]; + } + [timer invalidate]; +} + ++ (void)_changeNetworkActivityCount:(NSInteger)delta { + if (!_YYSharedApplication()) return; + + void (^block)() = ^{ + _YYWebImageApplicationNetworkIndicatorInfo *info = [self _networkIndicatorInfo]; + if (!info) { + info = [_YYWebImageApplicationNetworkIndicatorInfo new]; + [self _setNetworkIndicatorInfo:info]; + } + NSInteger count = info.count; + count += delta; + info.count = count; + [info.timer invalidate]; + info.timer = [NSTimer timerWithTimeInterval:kNetworkIndicatorDelay target:self selector:@selector(_delaySetActivity:) userInfo:@(info.count > 0) repeats:NO]; + [[NSRunLoop mainRunLoop] addTimer:info.timer forMode:NSRunLoopCommonModes]; + }; + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), block); + } +} + ++ (void)incrementNetworkActivityCount { + [self _changeNetworkActivityCount:1]; +} + ++ (void)decrementNetworkActivityCount { + [self _changeNetworkActivityCount:-1]; +} + ++ (NSInteger)currentNetworkActivityCount { + _YYWebImageApplicationNetworkIndicatorInfo *info = [self _networkIndicatorInfo]; + return info.count; +} + +@end diff --git a/Example/Pods/YYWebImage/YYWebImage/YYWebImageOperation.h b/Example/Pods/YYWebImage/YYWebImage/YYWebImageOperation.h new file mode 100644 index 00000000..d15eb3b3 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/YYWebImageOperation.h @@ -0,0 +1,97 @@ +// +// YYWebImageOperation.h +// YYWebImage +// +// Created by ibireme on 15/2/15. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#import +#else +#import "YYImageCache.h" +#import "YYWebImageManager.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + The YYWebImageOperation class is an NSOperation subclass used to fetch image + from URL request. + + @discussion It's an asynchronous operation. You typically execute it by adding + it to an operation queue, or calls 'start' to execute it manually. When the + operation is started, it will: + + 1. Get the image from the cache, if exist, return it with `completion` block. + 2. Start an URL connection to fetch image from the request, invoke the `progress` + to notify request progress (and invoke `completion` block to return the + progressive image if enabled by progressive option). + 3. Process the image by invoke the `transform` block. + 4. Put the image to cache and return it with `completion` block. + + */ +@interface YYWebImageOperation : NSOperation + +@property (nonatomic, strong, readonly) NSURLRequest *request; ///< The image URL request. +@property (nullable, nonatomic, strong, readonly) NSURLResponse *response; ///< The response for request. +@property (nullable, nonatomic, strong, readonly) YYImageCache *cache; ///< The image cache. +@property (nonatomic, strong, readonly) NSString *cacheKey; ///< The image cache key. +@property (nonatomic, readonly) YYWebImageOptions options; ///< The operation's option. + +/** + Whether the URL connection should consult the credential storage for authenticating + the connection. Default is YES. + + @discussion This is the value that is returned in the `NSURLConnectionDelegate` + method `-connectionShouldUseCredentialStorage:`. + */ +@property (nonatomic) BOOL shouldUseCredentialStorage; + +/** + The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`. + + @discussion This will be overridden by any shared credentials that exist for the + username or password of the request URL, if present. + */ +@property (nullable, nonatomic, strong) NSURLCredential *credential; + +/** + Creates and returns a new operation. + + You should call `start` to execute this operation, or you can add the operation + to an operation queue. + + @param request The Image request. This value should not be nil. + @param options A mask to specify options to use for this operation. + @param cache An image cache. Pass nil to avoid image cache. + @param cacheKey An image cache key. Pass nil to avoid image cache. + @param progress A block invoked in image fetch progress. + The block will be invoked in background thread. Pass nil to avoid it. + @param transform A block invoked before image fetch finished to do additional image process. + The block will be invoked in background thread. Pass nil to avoid it. + @param completion A block invoked when image fetch finished or cancelled. + The block will be invoked in background thread. Pass nil to avoid it. + + @return The image request opeartion, or nil if an error occurs. + */ +- (instancetype)initWithRequest:(NSURLRequest *)request + options:(YYWebImageOptions)options + cache:(nullable YYImageCache *)cache + cacheKey:(nullable NSString *)cacheKey + progress:(nullable YYWebImageProgressBlock)progress + transform:(nullable YYWebImageTransformBlock)transform + completion:(nullable YYWebImageCompletionBlock)completion NS_DESIGNATED_INITIALIZER; + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/YYWebImage/YYWebImage/YYWebImageOperation.m b/Example/Pods/YYWebImage/YYWebImage/YYWebImageOperation.m new file mode 100644 index 00000000..118dd6d2 --- /dev/null +++ b/Example/Pods/YYWebImage/YYWebImage/YYWebImageOperation.m @@ -0,0 +1,868 @@ +// +// YYWebImageOperation.m +// YYWebImage +// +// Created by ibireme on 15/2/15. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import "YYWebImageOperation.h" +#import "UIImage+YYWebImage.h" +#import +#import + +#if __has_include() +#import +#else +#import "YYImage.h" +#endif + + +#define MIN_PROGRESSIVE_TIME_INTERVAL 0.2 +#define MIN_PROGRESSIVE_BLUR_TIME_INTERVAL 0.4 + + +/// Returns nil in App Extension. +static UIApplication *_YYSharedApplication() { + static BOOL isAppExtension = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class cls = NSClassFromString(@"UIApplication"); + if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES; + if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES; + }); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)]; +#pragma clang diagnostic pop +} + +/// Returns YES if the right-bottom pixel is filled. +static BOOL YYCGImageLastPixelFilled(CGImageRef image) { + if (!image) return NO; + size_t width = CGImageGetWidth(image); + size_t height = CGImageGetHeight(image); + if (width == 0 || height == 0) return NO; + CGContextRef ctx = CGBitmapContextCreate(NULL, 1, 1, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault); + if (!ctx) return NO; + CGContextDrawImage(ctx, CGRectMake( -(int)width + 1, 0, width, height), image); + uint8_t *bytes = CGBitmapContextGetData(ctx); + BOOL isAlpha = bytes && bytes[0] == 0; + CFRelease(ctx); + return !isAlpha; +} + +/// Returns JPEG SOS (Start Of Scan) Marker +static NSData *JPEGSOSMarker() { + // "Start Of Scan" Marker + static NSData *marker = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + uint8_t bytes[2] = {0xFF, 0xDA}; + marker = [NSData dataWithBytes:bytes length:2]; + }); + return marker; +} + + +static NSMutableSet *URLBlacklist; +static dispatch_semaphore_t URLBlacklistLock; + +static void URLBlacklistInit() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + URLBlacklist = [NSMutableSet new]; + URLBlacklistLock = dispatch_semaphore_create(1); + }); +} + +static BOOL URLBlackListContains(NSURL *url) { + if (!url || url == (id)[NSNull null]) return NO; + URLBlacklistInit(); + dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER); + BOOL contains = [URLBlacklist containsObject:url]; + dispatch_semaphore_signal(URLBlacklistLock); + return contains; +} + +static void URLInBlackListAdd(NSURL *url) { + if (!url || url == (id)[NSNull null]) return; + URLBlacklistInit(); + dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER); + [URLBlacklist addObject:url]; + dispatch_semaphore_signal(URLBlacklistLock); +} + + +/// A proxy used to hold a weak object. +@interface _YYWebImageWeakProxy : NSProxy +@property (nonatomic, weak, readonly) id target; +- (instancetype)initWithTarget:(id)target; ++ (instancetype)proxyWithTarget:(id)target; +@end + +@implementation _YYWebImageWeakProxy +- (instancetype)initWithTarget:(id)target { + _target = target; + return self; +} ++ (instancetype)proxyWithTarget:(id)target { + return [[_YYWebImageWeakProxy alloc] initWithTarget:target]; +} +- (id)forwardingTargetForSelector:(SEL)selector { + return _target; +} +- (void)forwardInvocation:(NSInvocation *)invocation { + void *null = NULL; + [invocation setReturnValue:&null]; +} +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + return [NSObject instanceMethodSignatureForSelector:@selector(init)]; +} +- (BOOL)respondsToSelector:(SEL)aSelector { + return [_target respondsToSelector:aSelector]; +} +- (BOOL)isEqual:(id)object { + return [_target isEqual:object]; +} +- (NSUInteger)hash { + return [_target hash]; +} +- (Class)superclass { + return [_target superclass]; +} +- (Class)class { + return [_target class]; +} +- (BOOL)isKindOfClass:(Class)aClass { + return [_target isKindOfClass:aClass]; +} +- (BOOL)isMemberOfClass:(Class)aClass { + return [_target isMemberOfClass:aClass]; +} +- (BOOL)conformsToProtocol:(Protocol *)aProtocol { + return [_target conformsToProtocol:aProtocol]; +} +- (BOOL)isProxy { + return YES; +} +- (NSString *)description { + return [_target description]; +} +- (NSString *)debugDescription { + return [_target debugDescription]; +} +@end + + +@interface YYWebImageOperation() +@property (readwrite, getter=isExecuting) BOOL executing; +@property (readwrite, getter=isFinished) BOOL finished; +@property (readwrite, getter=isCancelled) BOOL cancelled; +@property (readwrite, getter=isStarted) BOOL started; +@property (nonatomic, strong) NSRecursiveLock *lock; +@property (nonatomic, strong) NSURLConnection *connection; +@property (nonatomic, strong) NSMutableData *data; +@property (nonatomic, assign) NSInteger expectedSize; +@property (nonatomic, assign) UIBackgroundTaskIdentifier taskID; + +@property (nonatomic, assign) NSTimeInterval lastProgressiveDecodeTimestamp; +@property (nonatomic, strong) YYImageDecoder *progressiveDecoder; +@property (nonatomic, assign) BOOL progressiveIgnored; +@property (nonatomic, assign) BOOL progressiveDetected; +@property (nonatomic, assign) NSUInteger progressiveScanedLength; +@property (nonatomic, assign) NSUInteger progressiveDisplayCount; + +@property (nonatomic, copy) YYWebImageProgressBlock progress; +@property (nonatomic, copy) YYWebImageTransformBlock transform; +@property (nonatomic, copy) YYWebImageCompletionBlock completion; +@end + + +@implementation YYWebImageOperation +@synthesize executing = _executing; +@synthesize finished = _finished; +@synthesize cancelled = _cancelled; + +/// Network thread entry point. ++ (void)_networkThreadMain:(id)object { + @autoreleasepool { + [[NSThread currentThread] setName:@"com.ibireme.webimage.request"]; + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; + [runLoop run]; + } +} + +/// Global image request network thread, used by NSURLConnection delegate. ++ (NSThread *)_networkThread { + static NSThread *thread = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil]; + if ([thread respondsToSelector:@selector(setQualityOfService:)]) { + thread.qualityOfService = NSQualityOfServiceBackground; + } + [thread start]; + }); + return thread; +} + +/// Global image queue, used for image reading and decoding. ++ (dispatch_queue_t)_imageQueue { + #define MAX_QUEUE_COUNT 16 + static int queueCount; + static dispatch_queue_t queues[MAX_QUEUE_COUNT]; + static dispatch_once_t onceToken; + static int32_t counter = 0; + dispatch_once(&onceToken, ^{ + queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount; + queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount; + if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) { + for (NSUInteger i = 0; i < queueCount; i++) { + dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); + queues[i] = dispatch_queue_create("com.ibireme.image.decode", attr); + } + } else { + for (NSUInteger i = 0; i < queueCount; i++) { + queues[i] = dispatch_queue_create("com.ibireme.image.decode", DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); + } + } + }); + int32_t cur = OSAtomicIncrement32(&counter); + if (cur < 0) cur = -cur; + return queues[(cur) % queueCount]; + #undef MAX_QUEUE_COUNT +} + +- (instancetype)init { + @throw [NSException exceptionWithName:@"YYWebImageOperation init error" reason:@"YYWebImageOperation must be initialized with a request. Use the designated initializer to init." userInfo:nil]; + return [self initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] options:0 cache:nil cacheKey:nil progress:nil transform:nil completion:nil]; +} + +- (instancetype)initWithRequest:(NSURLRequest *)request + options:(YYWebImageOptions)options + cache:(YYImageCache *)cache + cacheKey:(NSString *)cacheKey + progress:(YYWebImageProgressBlock)progress + transform:(YYWebImageTransformBlock)transform + completion:(YYWebImageCompletionBlock)completion { + self = [super init]; + if (!self) return nil; + if (!request) return nil; + _request = request; + _options = options; + _cache = cache; + _cacheKey = cacheKey ? cacheKey : request.URL.absoluteString; + _shouldUseCredentialStorage = YES; + _progress = progress; + _transform = transform; + _completion = completion; + _executing = NO; + _finished = NO; + _cancelled = NO; + _taskID = UIBackgroundTaskInvalid; + _lock = [NSRecursiveLock new]; + return self; +} + +- (void)dealloc { + [_lock lock]; + if (_taskID != UIBackgroundTaskInvalid) { + [_YYSharedApplication() endBackgroundTask:_taskID]; + _taskID = UIBackgroundTaskInvalid; + } + if ([self isExecuting]) { + self.cancelled = YES; + self.finished = YES; + if (_connection) { + [_connection cancel]; + if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { + [YYWebImageManager decrementNetworkActivityCount]; + } + } + if (_completion) { + @autoreleasepool { + _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil); + } + } + } + [_lock unlock]; +} + +- (void)_endBackgroundTask { + [_lock lock]; + if (_taskID != UIBackgroundTaskInvalid) { + [_YYSharedApplication() endBackgroundTask:_taskID]; + _taskID = UIBackgroundTaskInvalid; + } + [_lock unlock]; +} + +#pragma mark - Runs in operation thread + +- (void)_finish { + self.executing = NO; + self.finished = YES; + [self _endBackgroundTask]; +} + +// runs on network thread +- (void)_startOperation { + if ([self isCancelled]) return; + @autoreleasepool { + // get image from cache + if (_cache && + !(_options & YYWebImageOptionUseNSURLCache) && + !(_options & YYWebImageOptionRefreshImageCache)) { + UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory]; + if (image) { + [_lock lock]; + if (![self isCancelled]) { + if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil); + } + [self _finish]; + [_lock unlock]; + return; + } + if (!(_options & YYWebImageOptionIgnoreDiskCache)) { + __weak typeof(self) _self = self; + dispatch_async([self.class _imageQueue], ^{ + __strong typeof(_self) self = _self; + if (!self || [self isCancelled]) return; + UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk]; + if (image) { + [self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory]; + [self performSelector:@selector(_didReceiveImageFromDiskCache:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO]; + } else { + [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO]; + } + }); + return; + } + } + } + [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO]; +} + +// runs on network thread +- (void)_startRequest:(id)object { + if ([self isCancelled]) return; + @autoreleasepool { + if ((_options & YYWebImageOptionIgnoreFailedURL) && URLBlackListContains(_request.URL)) { + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }]; + [_lock lock]; + if (![self isCancelled]) { + if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error); + } + [self _finish]; + [_lock unlock]; + return; + } + + if (_request.URL.isFileURL) { + NSArray *keys = @[NSURLFileSizeKey]; + NSDictionary *attr = [_request.URL resourceValuesForKeys:keys error:nil]; + NSNumber *fileSize = attr[NSURLFileSizeKey]; + _expectedSize = fileSize ? fileSize.unsignedIntegerValue : -1; + } + + // request image from web + [_lock lock]; + if (![self isCancelled]) { + _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[_YYWebImageWeakProxy proxyWithTarget:self]]; + if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { + [YYWebImageManager incrementNetworkActivityCount]; + } + } + [_lock unlock]; + } +} + +// runs on network thread, called from outer "cancel" +- (void)_cancelOperation { + @autoreleasepool { + if (_connection) { + if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { + [YYWebImageManager decrementNetworkActivityCount]; + } + } + [_connection cancel]; + _connection = nil; + if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil); + [self _endBackgroundTask]; + } +} + + +// runs on network thread +- (void)_didReceiveImageFromDiskCache:(UIImage *)image { + @autoreleasepool { + [_lock lock]; + if (![self isCancelled]) { + if (image) { + if (_completion) _completion(image, _request.URL, YYWebImageFromDiskCache, YYWebImageStageFinished, nil); + [self _finish]; + } else { + [self _startRequest:nil]; + } + } + [_lock unlock]; + } +} + +- (void)_didReceiveImageFromWeb:(UIImage *)image { + @autoreleasepool { + [_lock lock]; + if (![self isCancelled]) { + if (_cache) { + if (image || (_options & YYWebImageOptionRefreshImageCache)) { + NSData *data = _data; + dispatch_async([YYWebImageOperation _imageQueue], ^{ + [_cache setImage:image imageData:data forKey:_cacheKey withType:YYImageCacheTypeAll]; + }); + } + } + _data = nil; + NSError *error = nil; + if (!image) { + error = [NSError errorWithDomain:@"com.ibireme.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }]; + if (_options & YYWebImageOptionIgnoreFailedURL) { + if (URLBlackListContains(_request.URL)) { + error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }]; + } else { + URLInBlackListAdd(_request.URL); + } + } + } + if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error); + [self _finish]; + } + [_lock unlock]; + } +} + +#pragma mark - NSURLConnectionDelegate runs in operation thread + +- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection { + return _shouldUseCredentialStorage; +} + +- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + @autoreleasepool { + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + if (!(_options & YYWebImageOptionAllowInvalidSSLCertificates) && + [challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) { + [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; + } else { + NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + } + } else { + if ([challenge previousFailureCount] == 0) { + if (_credential) { + [[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } + } +} + +- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { + if (!cachedResponse) return cachedResponse; + if (_options & YYWebImageOptionUseNSURLCache) { + return cachedResponse; + } else { + // ignore NSURLCache + return nil; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + @autoreleasepool { + NSError *error = nil; + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse *httpResponse = (id) response; + NSInteger statusCode = httpResponse.statusCode; + if (statusCode >= 400 || statusCode == 304) { + error = [NSError errorWithDomain:NSURLErrorDomain code:statusCode userInfo:nil]; + } + } + if (error) { + [_connection cancel]; + [self connection:_connection didFailWithError:error]; + } else { + if (response.expectedContentLength) { + _expectedSize = (NSInteger)response.expectedContentLength; + if (_expectedSize < 0) _expectedSize = -1; + } + _data = [NSMutableData dataWithCapacity:_expectedSize > 0 ? _expectedSize : 0]; + if (_progress) { + [_lock lock]; + if (![self isCancelled]) _progress(0, _expectedSize); + [_lock unlock]; + } + } + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + @autoreleasepool { + [_lock lock]; + BOOL canceled = [self isCancelled]; + [_lock unlock]; + if (canceled) return; + + if (data) [_data appendData:data]; + if (_progress) { + [_lock lock]; + if (![self isCancelled]) { + _progress(_data.length, _expectedSize); + } + [_lock unlock]; + } + + /*--------------------------- progressive ----------------------------*/ + BOOL progressive = (_options & YYWebImageOptionProgressive) > 0; + BOOL progressiveBlur = (_options & YYWebImageOptionProgressiveBlur) > 0; + if (!_completion || !(progressive || progressiveBlur)) return; + if (data.length <= 16) return; + if (_expectedSize > 0 && data.length >= _expectedSize * 0.99) return; + if (_progressiveIgnored) return; + + NSTimeInterval min = progressiveBlur ? MIN_PROGRESSIVE_BLUR_TIME_INTERVAL : MIN_PROGRESSIVE_TIME_INTERVAL; + NSTimeInterval now = CACurrentMediaTime(); + if (now - _lastProgressiveDecodeTimestamp < min) return; + + if (!_progressiveDecoder) { + _progressiveDecoder = [[YYImageDecoder alloc] initWithScale:[UIScreen mainScreen].scale]; + } + [_progressiveDecoder updateData:_data final:NO]; + if ([self isCancelled]) return; + + if (_progressiveDecoder.type == YYImageTypeUnknown || + _progressiveDecoder.type == YYImageTypeWebP || + _progressiveDecoder.type == YYImageTypeOther) { + _progressiveDecoder = nil; + _progressiveIgnored = YES; + return; + } + if (progressiveBlur) { // only support progressive JPEG and interlaced PNG + if (_progressiveDecoder.type != YYImageTypeJPEG && + _progressiveDecoder.type != YYImageTypePNG) { + _progressiveDecoder = nil; + _progressiveIgnored = YES; + return; + } + } + if (_progressiveDecoder.frameCount == 0) return; + + if (!progressiveBlur) { + YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES]; + if (frame.image) { + [_lock lock]; + if (![self isCancelled]) { + _completion(frame.image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil); + _lastProgressiveDecodeTimestamp = now; + } + [_lock unlock]; + } + return; + } else { + if (_progressiveDecoder.type == YYImageTypeJPEG) { + if (!_progressiveDetected) { + NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0]; + NSDictionary *jpeg = dic[(id)kCGImagePropertyJFIFDictionary]; + NSNumber *isProg = jpeg[(id)kCGImagePropertyJFIFIsProgressive]; + if (!isProg.boolValue) { + _progressiveIgnored = YES; + _progressiveDecoder = nil; + return; + } + _progressiveDetected = YES; + } + + NSInteger scanLength = (NSInteger)_data.length - (NSInteger)_progressiveScanedLength - 4; + if (scanLength <= 2) return; + NSRange scanRange = NSMakeRange(_progressiveScanedLength, scanLength); + NSRange markerRange = [_data rangeOfData:JPEGSOSMarker() options:kNilOptions range:scanRange]; + _progressiveScanedLength = _data.length; + if (markerRange.location == NSNotFound) return; + if ([self isCancelled]) return; + + } else if (_progressiveDecoder.type == YYImageTypePNG) { + if (!_progressiveDetected) { + NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0]; + NSDictionary *png = dic[(id)kCGImagePropertyPNGDictionary]; + NSNumber *isProg = png[(id)kCGImagePropertyPNGInterlaceType]; + if (!isProg.boolValue) { + _progressiveIgnored = YES; + _progressiveDecoder = nil; + return; + } + _progressiveDetected = YES; + } + } + + YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES]; + UIImage *image = frame.image; + if (!image) return; + if ([self isCancelled]) return; + + if (!YYCGImageLastPixelFilled(image.CGImage)) return; + _progressiveDisplayCount++; + + CGFloat radius = 32; + if (_expectedSize > 0) { + radius *= 1.0 / (3 * _data.length / (CGFloat)_expectedSize + 0.6) - 0.25; + } else { + radius /= (_progressiveDisplayCount); + } + image = [image yy_imageByBlurRadius:radius tintColor:nil tintMode:0 saturation:1 maskImage:nil]; + + if (image) { + [_lock lock]; + if (![self isCancelled]) { + _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil); + _lastProgressiveDecodeTimestamp = now; + } + [_lock unlock]; + } + } + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + @autoreleasepool { + [_lock lock]; + _connection = nil; + if (![self isCancelled]) { + __weak typeof(self) _self = self; + dispatch_async([self.class _imageQueue], ^{ + __strong typeof(_self) self = _self; + if (!self) return; + + BOOL shouldDecode = (self.options & YYWebImageOptionIgnoreImageDecoding) == 0; + BOOL allowAnimation = (self.options & YYWebImageOptionIgnoreAnimatedImage) == 0; + UIImage *image; + BOOL hasAnimation = NO; + if (allowAnimation) { + image = [[YYImage alloc] initWithData:self.data scale:[UIScreen mainScreen].scale]; + if (shouldDecode) image = [image yy_imageByDecoded]; + if ([((YYImage *)image) animatedImageFrameCount] > 1) { + hasAnimation = YES; + } + } else { + YYImageDecoder *decoder = [YYImageDecoder decoderWithData:self.data scale:[UIScreen mainScreen].scale]; + image = [decoder frameAtIndex:0 decodeForDisplay:shouldDecode].image; + } + + /* + If the image has animation, save the original image data to disk cache. + If the image is not PNG or JPEG, re-encode the image to PNG or JPEG for + better decoding performance. + */ + YYImageType imageType = YYImageDetectType((__bridge CFDataRef)self.data); + switch (imageType) { + case YYImageTypeJPEG: + case YYImageTypeGIF: + case YYImageTypePNG: + case YYImageTypeWebP: { // save to disk cache + if (!hasAnimation) { + if (imageType == YYImageTypeGIF || + imageType == YYImageTypeWebP) { + self.data = nil; // clear the data, re-encode for disk cache + } + } + } break; + default: { + self.data = nil; // clear the data, re-encode for disk cache + } break; + } + if ([self isCancelled]) return; + + if (self.transform && image) { + UIImage *newImage = self.transform(image, self.request.URL); + if (newImage != image) { + self.data = nil; + } + image = newImage; + if ([self isCancelled]) return; + } + + [self performSelector:@selector(_didReceiveImageFromWeb:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO]; + }); + if (![self.request.URL isFileURL] && (self.options & YYWebImageOptionShowNetworkActivity)) { + [YYWebImageManager decrementNetworkActivityCount]; + } + } + [_lock unlock]; + } +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + @autoreleasepool { + [_lock lock]; + if (![self isCancelled]) { + if (_completion) { + _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error); + } + _connection = nil; + _data = nil; + if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { + [YYWebImageManager decrementNetworkActivityCount]; + } + [self _finish]; + + if (_options & YYWebImageOptionIgnoreFailedURL) { + if (error.code != NSURLErrorNotConnectedToInternet && + error.code != NSURLErrorCancelled && + error.code != NSURLErrorTimedOut && + error.code != NSURLErrorUserCancelledAuthentication) { + URLInBlackListAdd(_request.URL); + } + } + } + [_lock unlock]; + } +} + +#pragma mark - Override NSOperation + +- (void)start { + @autoreleasepool { + [_lock lock]; + self.started = YES; + if ([self isCancelled]) { + [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]]; + self.finished = YES; + } else if ([self isReady] && ![self isFinished] && ![self isExecuting]) { + if (!_request) { + self.finished = YES; + if (_completion) { + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}]; + _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error); + } + } else { + self.executing = YES; + [self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]]; + if ((_options & YYWebImageOptionAllowBackgroundTask) && _YYSharedApplication()) { + __weak __typeof__ (self) _self = self; + if (_taskID == UIBackgroundTaskInvalid) { + _taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{ + __strong __typeof (_self) self = _self; + if (self) { + [self cancel]; + self.finished = YES; + } + }]; + } + } + } + } + [_lock unlock]; + } +} + +- (void)cancel { + [_lock lock]; + if (![self isCancelled]) { + [super cancel]; + self.cancelled = YES; + if ([self isExecuting]) { + self.executing = NO; + [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]]; + } + if (self.started) { + self.finished = YES; + } + } + [_lock unlock]; +} + +- (void)setExecuting:(BOOL)executing { + [_lock lock]; + if (_executing != executing) { + [self willChangeValueForKey:@"isExecuting"]; + _executing = executing; + [self didChangeValueForKey:@"isExecuting"]; + } + [_lock unlock]; +} + +- (BOOL)isExecuting { + [_lock lock]; + BOOL executing = _executing; + [_lock unlock]; + return executing; +} + +- (void)setFinished:(BOOL)finished { + [_lock lock]; + if (_finished != finished) { + [self willChangeValueForKey:@"isFinished"]; + _finished = finished; + [self didChangeValueForKey:@"isFinished"]; + } + [_lock unlock]; +} + +- (BOOL)isFinished { + [_lock lock]; + BOOL finished = _finished; + [_lock unlock]; + return finished; +} + +- (void)setCancelled:(BOOL)cancelled { + [_lock lock]; + if (_cancelled != cancelled) { + [self willChangeValueForKey:@"isCancelled"]; + _cancelled = cancelled; + [self didChangeValueForKey:@"isCancelled"]; + } + [_lock unlock]; +} + +- (BOOL)isCancelled { + [_lock lock]; + BOOL cancelled = _cancelled; + [_lock unlock]; + return cancelled; +} + +- (BOOL)isConcurrent { + return YES; +} + +- (BOOL)isAsynchronous { + return YES; +} + ++ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { + if ([key isEqualToString:@"isExecuting"] || + [key isEqualToString:@"isFinished"] || + [key isEqualToString:@"isCancelled"]) { + return NO; + } + return [super automaticallyNotifiesObserversForKey:key]; +} + +- (NSString *)description { + NSMutableString *string = [NSMutableString stringWithFormat:@"<%@: %p ",self.class, self]; + [string appendFormat:@" executing:%@", [self isExecuting] ? @"YES" : @"NO"]; + [string appendFormat:@" finished:%@", [self isFinished] ? @"YES" : @"NO"]; + [string appendFormat:@" cancelled:%@", [self isCancelled] ? @"YES" : @"NO"]; + [string appendString:@">"]; + return string; +} + +@end diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..97846020 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2019] [Doric.Pub] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Pod/Assets/.gitkeep b/Pod/Assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Pod/Assets/bundle b/Pod/Assets/bundle new file mode 120000 index 00000000..36b24ad0 --- /dev/null +++ b/Pod/Assets/bundle @@ -0,0 +1 @@ +../../../js-framework/bundle \ No newline at end of file diff --git a/Pod/Assets/dist b/Pod/Assets/dist new file mode 120000 index 00000000..9018f703 --- /dev/null +++ b/Pod/Assets/dist @@ -0,0 +1 @@ +../../../debugger/dist \ No newline at end of file diff --git a/Pod/Classes/.gitkeep b/Pod/Classes/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Pod/Classes/Dev/DoricLocalServer.h b/Pod/Classes/Dev/DoricLocalServer.h new file mode 100644 index 00000000..b1ae18f9 --- /dev/null +++ b/Pod/Classes/Dev/DoricLocalServer.h @@ -0,0 +1,31 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricLocalServer.h +// Doric +// +// Created by pengfei.zhou on 2019/8/14. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricLocalServer : NSObject +- (void)startWithPort:(NSUInteger)port; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Dev/DoricLocalServer.m b/Pod/Classes/Dev/DoricLocalServer.m new file mode 100644 index 00000000..8fa1454f --- /dev/null +++ b/Pod/Classes/Dev/DoricLocalServer.m @@ -0,0 +1,130 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricLocalServer.m +// Doric +// +// Created by pengfei.zhou on 2019/8/14. +// + +#import "DoricLocalServer.h" +#import "GCDWebServer.h" +#import "GCDWebServerDataResponse.h" +#import "DoricUtil.h" +#import "DoricContextManager.h" + +#include +#include +#include + +typedef id (^ServerHandler)(GCDWebServerRequest *request); + +@interface DoricLocalServer () +@property(nonatomic, strong) GCDWebServer *server; +@property(nonatomic, strong) NSMutableDictionary *handlers; +@end + +@implementation DoricLocalServer + +- (instancetype)init { + if (self = [super init]) { + _server = [[GCDWebServer alloc] init]; + _handlers = [[NSMutableDictionary alloc] init]; + [self configurate]; + } + return self; +} + +- (NSString *)localIPAddress { + NSString *localIP = nil; + struct ifaddrs *addrs; + if (getifaddrs(&addrs) == 0) { + const struct ifaddrs *cursor = addrs; + while (cursor != NULL) { + if (cursor->ifa_addr->sa_family == AF_INET && (cursor->ifa_flags & IFF_LOOPBACK) == 0) { + //NSString *name = [NSString stringWithUTF8String:cursor->ifa_name]; + //if ([name isEqualToString:@"en0"]) // Wi-Fi adapter + { + localIP = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *) cursor->ifa_addr)->sin_addr)]; + break; + } + } + cursor = cursor->ifa_next; + } + freeifaddrs(addrs); + } + return localIP; +} + +- (GCDWebServerResponse *)handleRequest:(GCDWebServerRequest *)request { + if ([request.path hasPrefix:@"/api/"]) { + NSString *command = [request.path substringFromIndex:@"/api/".length]; + ServerHandler handler = [self.handlers objectForKey:command]; + if (handler) { + id dic = handler(request); + return [GCDWebServerDataResponse responseWithJSONObject:dic]; + } + return [GCDWebServerDataResponse responseWithHTML:@"

It's a API request.

"]; + } + NSBundle *bundle = DoricBundle(); + NSString *filePath = [NSString stringWithFormat:@"%@/dist%@", bundle.bundlePath, request.path]; + NSData *data = [NSData dataWithContentsOfFile:filePath]; + NSURL *url = [NSURL fileURLWithPath:filePath]; + NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url]; + NSHTTPURLResponse *response; + [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:nil]; + return [GCDWebServerDataResponse responseWithData:data contentType:response.MIMEType]; +} + +- (void)configurate { + __weak typeof(self) _self = self; + [self.server addDefaultHandlerForMethod:@"GET" + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse *(GCDWebServerRequest *request) { + __strong typeof(_self) self = _self; + return [self handleRequest:request]; + }]; + [self.handlers setObject:^id(GCDWebServerRequest *request) { + NSMutableArray *array = [[NSMutableArray alloc] init]; + + for (NSValue *value in [[DoricContextManager instance] aliveContexts]) { + DoricContext *context = value.nonretainedObjectValue; + [array addObject:@{ + @"source": context.source, + @"id": context.contextId, + }]; + } + return array; + } + forKey:@"allContexts"]; + + [self.handlers setObject:^id(GCDWebServerRequest *request) { + NSString *contextId = [request.query objectForKey:@"id"]; + DoricContext *context = [[DoricContextManager instance] getContext:contextId]; + return @{ + @"id": context.contextId, + @"source": context.source, + @"script": context.script + }; + } + forKey:@"context"]; +} + +- (void)startWithPort:(NSUInteger)port { + [self.server startWithPort:port bonjourName:nil]; + DoricLog(@"Start Server At %@:%d", [self localIPAddress], port); +} +@end diff --git a/Pod/Classes/Dev/DoricWSClient.h b/Pod/Classes/Dev/DoricWSClient.h new file mode 100644 index 00000000..4fb5afcd --- /dev/null +++ b/Pod/Classes/Dev/DoricWSClient.h @@ -0,0 +1,33 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// WSClient.h +// Doric +// +// Created by pengfei.zhou on 2019/8/14. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricWSClient : NSObject +- (instancetype)initWithUrl:(NSString *)url; + +- (void)close; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Dev/DoricWSClient.m b/Pod/Classes/Dev/DoricWSClient.m new file mode 100644 index 00000000..f927ef4e --- /dev/null +++ b/Pod/Classes/Dev/DoricWSClient.m @@ -0,0 +1,81 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// WSClient.m +// Doric +// +// Created by pengfei.zhou on 2019/8/14. +// + +#import "DoricWSClient.h" +#import +#import "DoricUtil.h" +#import "DoricContextManager.h" + +@interface DoricWSClient () +@property(nonatomic, strong) SRWebSocket *websocket; +@end + +@implementation DoricWSClient +- (instancetype)initWithUrl:(NSString *)url { + if (self = [super init]) { + _websocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:url]]; + _websocket.delegate = self; + [_websocket open]; + } + return self; +} + +- (void)webSocketDidOpen:(SRWebSocket *)webSocket { + DoricLog(@"webSocketDidOpen"); +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload { + DoricLog(@"webSocketdidReceivePong"); +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { + NSData *jsonData = [message dataUsingEncoding:NSUTF8StringEncoding]; + NSError *err; + NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData + options:NSJSONReadingMutableContainers + error:&err]; + if (err) { + DoricLog(@"webSocketdidReceiveMessage parse error:%@", err); + return; + } + NSString *source = [[dic valueForKey:@"source"] mutableCopy]; + NSString *script = [dic valueForKey:@"script"]; + for (NSValue *value in [[DoricContextManager instance] aliveContexts]) { + DoricContext *context = value.nonretainedObjectValue; + if ([source containsString:context.source]) { + [context reload:script]; + } + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { + DoricLog(@"webSocketdidFailWithError"); +} + +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { + DoricLog(@"webSocketdidCloseWithCode"); +} + +- (void)close { + [self.websocket close]; +} +@end diff --git a/Pod/Classes/Doric.h b/Pod/Classes/Doric.h new file mode 100644 index 00000000..e96c2565 --- /dev/null +++ b/Pod/Classes/Doric.h @@ -0,0 +1,28 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#import "DoricContext.h" +#import "DoricLayouts.h" +#import "DoricExtensions.h" +#import "DoricViewNode.h" +#import "DoricRootNode.h" +#import "UIView+Doric.h" +#import "DoricUtil.h" +#import "DoricPanel.h" +#import "DoricJSLoaderManager.h" +#import "DoricNavigatorDelegate.h" +#import "DoricNavBarDelegate.h" +#import "DoricViewController.h" +#import "DoricPromise.h" \ No newline at end of file diff --git a/Pod/Classes/DoricContext.h b/Pod/Classes/DoricContext.h new file mode 100644 index 00000000..b0bf324f --- /dev/null +++ b/Pod/Classes/DoricContext.h @@ -0,0 +1,64 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricContext.h +// Doric +// +// Created by pengfei.zhou on 2019/7/25. +// + +#import +#import "DoricDriver.h" +#import "DoricNavigatorDelegate.h" +#import "DoricNavBarDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@class DoricViewNode; +@class DoricRootNode; + +@interface DoricContext : NSObject +@property(nonatomic, weak) id navigator; +@property(nonatomic, weak) id navBar; +@property(nonatomic, strong) NSString *contextId; +@property(nonatomic, strong) DoricDriver *driver; +@property(nonatomic, strong) NSMutableDictionary *pluginInstanceMap; +@property(nonatomic, strong) NSString *source; +@property(nonatomic, strong) NSString *script;; +@property(nonatomic, strong) NSMutableDictionary *initialParams; +@property(nonatomic, strong) DoricRootNode *rootNode; +@property(nonatomic, strong) NSMutableDictionary *headNodes; + +- (instancetype)initWithScript:(NSString *)script source:(NSString *)source; + +- (DoricAsyncResult *)callEntity:(NSString *)method, ...; + +- (DoricAsyncResult *)callEntity:(NSString *)method withArguments:(va_list)args; + +- (DoricAsyncResult *)callEntity:(NSString *)method withArgumentsArray:(NSArray *)args; + +- (void)initContextWithWidth:(CGFloat)width height:(CGFloat)height; + +- (void)reload:(NSString *)script; + +- (void)onShow; + +- (void)onHidden; + +- (DoricViewNode *)targetViewNode:(NSString *)viewId; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/DoricContext.m b/Pod/Classes/DoricContext.m new file mode 100644 index 00000000..d675a141 --- /dev/null +++ b/Pod/Classes/DoricContext.m @@ -0,0 +1,99 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricContext.m +// Doric +// +// Created by pengfei.zhou on 2019/7/25. +// + +#import "DoricContext.h" +#import "DoricContextManager.h" +#import "DoricRootNode.h" +#import "DoricConstant.h" +#import "DoricExtensions.h" + +@implementation DoricContext + +- (instancetype)initWithScript:(NSString *)script source:(NSString *)source { + if (self = [super init]) { + _driver = [DoricDriver instance]; + _pluginInstanceMap = [NSMutableDictionary new]; + [[DoricContextManager instance] createContext:self script:script source:source]; + _headNodes = [NSMutableDictionary new]; + DoricRootNode *rootNode = [[DoricRootNode alloc] initWithContext:self]; + _rootNode = rootNode; + _script = script; + _source = source; + _initialParams = [@{@"width": @(0), @"height": @(0)} mutableCopy]; + [self callEntity:DORIC_ENTITY_CREATE, nil]; + } + return self; +} + +- (DoricViewNode *)targetViewNode:(NSString *)viewId { + if ([self.rootNode.viewId isEqualToString:viewId]) { + return self.rootNode; + } else { + return self.headNodes[viewId]; + } +} + +- (void)dealloc { + [self callEntity:DORIC_ENTITY_DESTROY, nil]; + [[DoricContextManager instance] destroyContext:self]; +} + +- (DoricAsyncResult *)callEntity:(NSString *)method, ... { + va_list args; + va_start(args, method); + DoricAsyncResult *ret = [self.driver invokeContextEntity:self.contextId method:method arguments:args]; + va_end(args); + return ret; +} + +- (DoricAsyncResult *)callEntity:(NSString *)method withArguments:(va_list)args { + return [self.driver invokeContextEntity:self.contextId method:method arguments:args]; +} + +- (DoricAsyncResult *)callEntity:(NSString *)method withArgumentsArray:(NSArray *)args { + return [self.driver invokeContextEntity:self.contextId method:method argumentsArray:args]; +} + +- (void)initContextWithWidth:(CGFloat)width height:(CGFloat)height { + [self.initialParams also:^(NSMutableDictionary *it) { + it[@"width"] = @(width); + it[@"height"] = @(height); + }]; + [self callEntity:DORIC_ENTITY_INIT, self.initialParams, nil]; +} + +- (void)reload:(NSString *)script { + self.rootNode.viewId = nil; + self.script = script; + [self.driver createContext:self.contextId script:script source:self.source]; + [self callEntity:DORIC_ENTITY_INIT, self.initialParams, nil]; +} + +- (void)onShow { + [self callEntity:DORIC_ENTITY_SHOW, nil]; +} + +- (void)onHidden { + [self callEntity:DORIC_ENTITY_HIDDEN, nil]; +} + +@end diff --git a/Pod/Classes/DoricContextHolder.h b/Pod/Classes/DoricContextHolder.h new file mode 100644 index 00000000..950193e1 --- /dev/null +++ b/Pod/Classes/DoricContextHolder.h @@ -0,0 +1,35 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricComponent.h +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import +#import "DoricContext.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricContextHolder : NSObject +@property(nonatomic, strong) DoricContext *doricContext; + +- (instancetype)initWithContext:(DoricContext *)doricContext; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/DoricContextHolder.m b/Pod/Classes/DoricContextHolder.m new file mode 100644 index 00000000..63f17d6c --- /dev/null +++ b/Pod/Classes/DoricContextHolder.m @@ -0,0 +1,34 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricComponent.m +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import "DoricContextHolder.h" + +@implementation DoricContextHolder + +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [self init]) { + _doricContext = doricContext; + } + return self; +} + +@end diff --git a/Pod/Classes/DoricContextManager.h b/Pod/Classes/DoricContextManager.h new file mode 100644 index 00000000..d8927542 --- /dev/null +++ b/Pod/Classes/DoricContextManager.h @@ -0,0 +1,40 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricContextManager.h +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import +#import "DoricContext.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricContextManager : NSObject ++ (instancetype)instance; + +- (void)createContext:(DoricContext *)context script:(NSString *)script source:(NSString *)source; + +- (void)destroyContext:(DoricContext *)context; + +- (DoricContext *)getContext:(NSString *)contextId; + +- (NSArray *)aliveContexts; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/DoricContextManager.m b/Pod/Classes/DoricContextManager.m new file mode 100644 index 00000000..a6ddc2cf --- /dev/null +++ b/Pod/Classes/DoricContextManager.m @@ -0,0 +1,84 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricContextManager.m +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import "DoricContextManager.h" +#import "DoricContext.h" + +@interface DoricContextManager () + +@property(nonatomic) NSInteger counter; +@property(nonatomic, strong) NSMutableDictionary *doricContextMap; +@property(nonatomic, strong) dispatch_queue_t mapQueue; +@end + +@implementation DoricContextManager + +- (instancetype)init { + if (self = [super init]) { + _doricContextMap = [[NSMutableDictionary alloc] init]; + _counter = 0; + _mapQueue = dispatch_queue_create("doric.contextmap", DISPATCH_QUEUE_SERIAL); + } + return self; +} + ++ (instancetype)instance { + static DoricContextManager *_instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _instance = [[DoricContextManager alloc] init]; + }); + return _instance; +} + +- (void)createContext:(DoricContext *)context script:(NSString *)script source:(NSString *)source { + context.contextId = [NSString stringWithFormat:@"%ld", (long) ++self.counter]; + [context.driver createContext:context.contextId script:script source:source]; + dispatch_sync(self.mapQueue, ^() { + NSValue *value = [NSValue valueWithNonretainedObject:context]; + [self.doricContextMap setValue:value forKey:context.contextId]; + }); +} + +- (DoricContext *)getContext:(NSString *)contextId { + __block DoricContext *context; + dispatch_sync(self.mapQueue, ^{ + NSValue *value = [self.doricContextMap valueForKey:contextId]; + context = value.nonretainedObjectValue; + }); + return context; +} + +- (void)destroyContext:(DoricContext *)context { + NSString *contextId = context.contextId; + [context.driver destroyContext:contextId].finishCallback = ^{ + dispatch_sync(self.mapQueue, ^() { + [self.doricContextMap removeObjectForKey:contextId]; + }); + }; +} + +- (NSArray *)aliveContexts { + return [self.doricContextMap allValues]; +} + +@end diff --git a/Pod/Classes/DoricDriver.h b/Pod/Classes/DoricDriver.h new file mode 100644 index 00000000..4cebc069 --- /dev/null +++ b/Pod/Classes/DoricDriver.h @@ -0,0 +1,59 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricDriver.h +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import +#import "DoricAsyncResult.h" +#import "DoricRegistry.h" + +typedef NS_ENUM(NSInteger, QueueMode) { + JS = 0, + UI, + INDEPENDENT +}; + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricDriver : NSObject ++ (instancetype)instance; + +@property(nonatomic, strong) DoricRegistry *registry; + +- (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source; + +- (DoricAsyncResult *)destroyContext:(NSString *)contextId; + +- (DoricAsyncResult *)invokeDoricMethod:(NSString *)method, ...; + +- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method, ...; + +- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method arguments:(va_list)args; + +- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method argumentsArray:(NSArray *)args; + +- (void)connectDevKit:(NSString *)url; + +- (void)disconnectDevKit; + +- (void)ensureSyncInMainQueue:(dispatch_block_t)block; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/DoricDriver.m b/Pod/Classes/DoricDriver.m new file mode 100644 index 00000000..adc9f0aa --- /dev/null +++ b/Pod/Classes/DoricDriver.m @@ -0,0 +1,193 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricDriver.m +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import "DoricDriver.h" +#import "DoricJSEngine.h" +#import "DoricConstant.h" +#import "DoricWSClient.h" + +@interface DoricDriver () +@property(nonatomic, strong) DoricJSEngine *jsExecutor; +@property(nonatomic, strong) DoricWSClient *wsclient; +@end + +@implementation DoricDriver + +@dynamic registry; + +- (instancetype)init { + if (self = [super init]) { + _jsExecutor = [[DoricJSEngine alloc] init]; + } + return self; +} + +- (DoricRegistry *)registry { + return self.jsExecutor.registry; +} + ++ (instancetype)instance { + static DoricDriver *_instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _instance = [[DoricDriver alloc] init]; + }); + return _instance; +} + +- (DoricAsyncResult *)invokeDoricMethod:(NSString *)method, ... { + va_list args; + va_start(args, method); + DoricAsyncResult *ret = [self invokeDoricMethod:method arguments:args]; + va_end(args); + return ret; +} + +- (DoricAsyncResult *)invokeDoricMethod:(NSString *)method arguments:(va_list)args { + DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; + NSMutableArray *array = [[NSMutableArray alloc] init]; + id arg; + while ((arg = va_arg(args, id)) != nil) { + [array addObject:arg]; + } + __weak typeof(self) _self = self; + dispatch_async(self.jsExecutor.jsQueue, ^() { + __strong typeof(_self) self = _self; + if (!self) return; + @try { + JSValue *jsValue = [self.jsExecutor invokeDoricMethod:method argumentsArray:array]; + [ret setupResult:jsValue]; + } @catch (NSException *exception) { + [ret setupError:exception]; + } + }); + return ret; +} + +- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method, ... { + va_list args; + va_start(args, method); + DoricAsyncResult *ret = [self invokeContextEntity:contextId method:method arguments:args]; + va_end(args); + return ret; +} + +- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method arguments:(va_list)args { + DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; + NSMutableArray *array = [[NSMutableArray alloc] init]; + [array addObject:contextId]; + [array addObject:method]; + id arg = va_arg(args, id); + while (arg != nil) { + [array addObject:arg]; + arg = va_arg(args, JSValue *); + } + __weak typeof(self) _self = self; + dispatch_async(self.jsExecutor.jsQueue, ^() { + __strong typeof(_self) self = _self; + if (!self) return; + @try { + JSValue *jsValue = [self.jsExecutor invokeDoricMethod:DORIC_CONTEXT_INVOKE argumentsArray:array]; + [ret setupResult:jsValue]; + } @catch (NSException *exception) { + [ret setupError:exception]; + } + }); + return ret; +} + +- (DoricAsyncResult *)invokeContextEntity:(NSString *)contextId method:(NSString *)method argumentsArray:(NSArray *)args { + DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; + NSMutableArray *array = [[NSMutableArray alloc] init]; + [array addObject:contextId]; + [array addObject:method]; + for (id arg in args) { + [array addObject:arg]; + } + __weak typeof(self) _self = self; + dispatch_async(self.jsExecutor.jsQueue, ^() { + __strong typeof(_self) self = _self; + if (!self) return; + @try { + JSValue *jsValue = [self.jsExecutor invokeDoricMethod:DORIC_CONTEXT_INVOKE argumentsArray:array]; + [ret setupResult:jsValue]; + } @catch (NSException *exception) { + [ret setupError:exception]; + } + }); + return ret; +} + +- (DoricAsyncResult *)createContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source { + DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; + __weak typeof(self) _self = self; + dispatch_async(self.jsExecutor.jsQueue, ^() { + __strong typeof(_self) self = _self; + if (!self) return; + @try { + [self.jsExecutor prepareContext:contextId script:script source:source]; + [ret setupResult:[NSNumber numberWithBool:YES]]; + } @catch (NSException *exception) { + [ret setupError:exception]; + } + }); + return ret; +} + +- (DoricAsyncResult *)destroyContext:(NSString *)contextId { + DoricAsyncResult *ret = [[DoricAsyncResult alloc] init]; + __weak typeof(self) _self = self; + dispatch_async(self.jsExecutor.jsQueue, ^() { + __strong typeof(_self) self = _self; + if (!self) return; + @try { + [self.jsExecutor destroyContext:contextId]; + [ret setupResult:[NSNumber numberWithBool:YES]]; + } @catch (NSException *exception) { + [ret setupError:exception]; + } + }); + return ret; +} + +- (void)connectDevKit:(NSString *)url { + if (self.wsclient) { + [self.wsclient close]; + } + self.wsclient = [[DoricWSClient alloc] initWithUrl:url]; +} + +- (void)disconnectDevKit { + if (self.wsclient) { + [self.wsclient close]; + self.wsclient = nil; + } +} + +- (void)ensureSyncInMainQueue:(dispatch_block_t)block { + if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), block); + } +} +@end diff --git a/Pod/Classes/DoricPanel.h b/Pod/Classes/DoricPanel.h new file mode 100644 index 00000000..56682d32 --- /dev/null +++ b/Pod/Classes/DoricPanel.h @@ -0,0 +1,29 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import + +#import "DoricContext.h" +#import "DoricNavigatorDelegate.h" + +@interface DoricPanel : UIViewController +@property(nonatomic, strong) DoricContext *doricContext; + +- (void)config:(NSString *)script alias:(NSString *)alias; +@end \ No newline at end of file diff --git a/Pod/Classes/DoricPanel.m b/Pod/Classes/DoricPanel.m new file mode 100644 index 00000000..a1cf9117 --- /dev/null +++ b/Pod/Classes/DoricPanel.m @@ -0,0 +1,54 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import "DoricPanel.h" +#import "Doric.h" + +@implementation DoricPanel + +- (void)config:(NSString *)script alias:(NSString *)alias { + self.doricContext = [[[DoricContext alloc] initWithScript:script source:alias] also:^(DoricContext *it) { + [it.rootNode setupRootView:[[DoricStackView new] also:^(DoricStackView *it) { + [self.view addSubview:it]; + }]]; + }]; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + [self.doricContext.rootNode.view also:^(DoricStackView *it) { + if (it.width != self.view.width || it.height != self.view.height) { + it.width = self.view.width; + it.height = self.view.height; + [self.doricContext initContextWithWidth:it.width height:it.height]; + } + }]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self.doricContext onShow]; +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + [self.doricContext onHidden]; +} + +@end diff --git a/Pod/Classes/DoricRegistry.h b/Pod/Classes/DoricRegistry.h new file mode 100644 index 00000000..021fc88f --- /dev/null +++ b/Pod/Classes/DoricRegistry.h @@ -0,0 +1,43 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricRegistry.h +// Doric +// +// Created by pengfei.zhou on 2019/7/27. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricRegistry : NSObject + +- (NSString *)acquireJSBundle:(NSString *)name; + +- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name; + + +- (void)registerNativePlugin:(Class)pluginClass withName:(NSString *)name; + +- (Class)acquireNativePlugin:(NSString *)name; + +- (void)registerViewNode:(Class)nodeClass withName:(NSString *)name; + +- (Class)acquireViewNode:(NSString *)name; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/DoricRegistry.m b/Pod/Classes/DoricRegistry.m new file mode 100644 index 00000000..4ffefa29 --- /dev/null +++ b/Pod/Classes/DoricRegistry.m @@ -0,0 +1,115 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricRegistry.m +// Doric +// +// Created by pengfei.zhou on 2019/7/27. +// + +#import "DoricRegistry.h" +#import "DoricModalPlugin.h" +#import "DoricNetworkPlugin.h" +#import "DoricShaderPlugin.h" +#import "DoricStackNode.h" +#import "DoricVLayoutNode.h" +#import "DoricHLayoutNode.h" +#import "DoricTextNode.h" +#import "DoricImageNode.h" +#import "DoricListNode.h" +#import "DoricListItemNode.h" +#import "DoricScrollerNode.h" +#import "DoricSliderNode.h" +#import "DoricSlideItemNode.h" +#import "DoricStoragePlugin.h" +#import "DoricNavigatorPlugin.h" +#import "DoricNavBarPlugin.h" +#import "DoricRefreshableNode.h" +#import "DoricFlowLayoutItemNode.h" +#import "DoricFlowLayoutNode.h" +#import "DoricPopoverPlugin.h" +#import "DoricAnimatePlugin.h" + +@interface DoricRegistry () + +@property(nonatomic, strong) NSMutableDictionary *bundles; +@property(nonatomic, strong) NSMutableDictionary *plugins; +@property(nonatomic, strong) NSMutableDictionary *nodes; + +@end + +@implementation DoricRegistry + +- (instancetype)init { + if (self = [super init]) { + _bundles = [[NSMutableDictionary alloc] init]; + _plugins = [[NSMutableDictionary alloc] init]; + _nodes = [[NSMutableDictionary alloc] init]; + [self innerRegister]; + } + return self; +} + +- (void)innerRegister { + [self registerNativePlugin:DoricShaderPlugin.class withName:@"shader"]; + [self registerNativePlugin:DoricModalPlugin.class withName:@"modal"]; + [self registerNativePlugin:DoricNetworkPlugin.class withName:@"network"]; + [self registerNativePlugin:DoricStoragePlugin.class withName:@"storage"]; + [self registerNativePlugin:DoricNavigatorPlugin.class withName:@"navigator"]; + [self registerNativePlugin:DoricNavBarPlugin.class withName:@"navbar"]; + [self registerNativePlugin:DoricPopoverPlugin.class withName:@"popover"]; + [self registerNativePlugin:DoricAnimatePlugin.class withName:@"animate"]; + + [self registerViewNode:DoricStackNode.class withName:@"Stack"]; + [self registerViewNode:DoricVLayoutNode.class withName:@"VLayout"]; + [self registerViewNode:DoricHLayoutNode.class withName:@"HLayout"]; + [self registerViewNode:DoricTextNode.class withName:@"Text"]; + [self registerViewNode:DoricImageNode.class withName:@"Image"]; + [self registerViewNode:DoricListNode.class withName:@"List"]; + [self registerViewNode:DoricListItemNode.class withName:@"ListItem"]; + [self registerViewNode:DoricScrollerNode.class withName:@"Scroller"]; + [self registerViewNode:DoricSliderNode.class withName:@"Slider"]; + [self registerViewNode:DoricSlideItemNode.class withName:@"SlideItem"]; + [self registerViewNode:DoricRefreshableNode.class withName:@"Refreshable"]; + [self registerViewNode:DoricFlowLayoutItemNode.class withName:@"FlowLayoutItem"]; + [self registerViewNode:DoricFlowLayoutNode.class withName:@"FlowLayout"]; +} + +- (void)registerJSBundle:(NSString *)bundle withName:(NSString *)name { + self.bundles[name] = bundle; +} + +- (NSString *)acquireJSBundle:(NSString *)name { + return self.bundles[name]; +} + +- (void)registerNativePlugin:(Class)pluginClass withName:(NSString *)name { + self.plugins[name] = pluginClass; +} + +- (Class)acquireNativePlugin:(NSString *)name { + return self.plugins[name]; +} + +- (void)registerViewNode:(Class)nodeClass withName:(NSString *)name { + self.nodes[name] = nodeClass; +} + +- (Class)acquireViewNode:(NSString *)name { + return self.nodes[name]; +} + +@end diff --git a/Pod/Classes/DoricViewController.h b/Pod/Classes/DoricViewController.h new file mode 100644 index 00000000..99037937 --- /dev/null +++ b/Pod/Classes/DoricViewController.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import +#import "DoricNavigatorDelegate.h" +#import "DoricNavBarDelegate.h" + +@interface DoricViewController : UIViewController +- (instancetype)initWithScheme:(NSString *)scheme alias:(NSString *)alias; +@end \ No newline at end of file diff --git a/Pod/Classes/DoricViewController.m b/Pod/Classes/DoricViewController.m new file mode 100644 index 00000000..b20bb3c8 --- /dev/null +++ b/Pod/Classes/DoricViewController.m @@ -0,0 +1,107 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import "DoricViewController.h" +#import "DoricAsyncResult.h" +#import "DoricJSLoaderManager.h" +#import "DoricPanel.h" +#import "UIView+Doric.h" +#import "DoricExtensions.h" +#import "DoricUtil.h" + +@interface DoricViewController () +@property(nonatomic, strong) DoricPanel *doricPanel; +@property(nonatomic) BOOL navBarHidden; +@property(nonatomic, strong) UIImage *navBarImage; +@end + +@implementation DoricViewController +- (instancetype)initWithScheme:(NSString *)scheme alias:(NSString *)alias { + if (self = [super init]) { + self.edgesForExtendedLayout = UIRectEdgeNone; + DoricAsyncResult *result = [DoricJSLoaderManager.instance request:scheme]; + result.resultCallback = ^(NSString *result) { + dispatch_async(dispatch_get_main_queue(), ^{ + DoricPanel *panel = [DoricPanel new]; + [panel.view also:^(UIView *it) { + it.backgroundColor = [UIColor whiteColor]; + it.width = self.view.width; + it.height = self.view.height; + }]; + [self.view addSubview:panel.view]; + [self addChildViewController:panel]; + [panel config:result alias:alias]; + panel.doricContext.navigator = self; + panel.doricContext.navBar = self; + self.doricPanel = panel; + }); + }; + } + return self; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.navBarHidden = self.navigationController.navigationBarHidden; + self.navBarImage = [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault]; +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + if (self.navigationController.navigationBarHidden != self.navBarHidden) { + [self.navigationController setNavigationBarHidden:self.navBarHidden]; + } + if (self.navBarImage != [self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault]) { + [self.navigationController.navigationBar setBackgroundImage:self.navBarImage forBarMetrics:UIBarMetricsDefault]; + } +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + self.doricPanel.view.width = self.view.width; + self.doricPanel.view.height = self.view.height; +} + +- (void)doric_navigator_push:(NSString *)scheme alias:(NSString *)alias animated:(BOOL)animated { + DoricViewController *viewController = [[DoricViewController alloc] initWithScheme:scheme alias:alias]; + [self.navigationController pushViewController:viewController animated:animated]; +} + +- (void)doric_navigator_pop:(BOOL)animated { + [self.navigationController popViewControllerAnimated:animated]; +} + +- (BOOL)doric_navBar_isHidden { + return self.navigationController.navigationBarHidden; +} + +- (void)doric_navBar_setHidden:(BOOL)hidden { + [self.navigationController setNavigationBarHidden:hidden]; +} + +- (void)doric_navBar_setTitle:(NSString *)title { + self.title = title; +} + +- (void)doric_navBar_setBackgroundColor:(UIColor *)color { + [self.navigationController.navigationBar setBackgroundImage:UIImageWithColor(color) forBarMetrics:UIBarMetricsDefault]; +} + + +@end diff --git a/Pod/Classes/Engine/DoricJSCoreExecutor.h b/Pod/Classes/Engine/DoricJSCoreExecutor.h new file mode 100644 index 00000000..0718eb3e --- /dev/null +++ b/Pod/Classes/Engine/DoricJSCoreExecutor.h @@ -0,0 +1,32 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricJSCoreExecutor.h +// Doric +// +// Created by pengfei.zhou on 2019/7/25. +// + +#import +#import "DoricJSExecutorProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricJSCoreExecutor : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Engine/DoricJSCoreExecutor.m b/Pod/Classes/Engine/DoricJSCoreExecutor.m new file mode 100644 index 00000000..dc397d31 --- /dev/null +++ b/Pod/Classes/Engine/DoricJSCoreExecutor.m @@ -0,0 +1,64 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricJSCoreExecutor.m +// Doric +// +// Created by pengfei.zhou on 2019/7/25. +// + +#import "DoricJSCoreExecutor.h" + +@interface DoricJSCoreExecutor () + +@property(nonatomic, strong) JSContext *jsContext; + +@end + +@implementation DoricJSCoreExecutor +- (instancetype)init { + if (self = [super init]) { + _jsContext = [[JSContext alloc] init]; + } + return self; +} + +- (void)checkJSException { + if (self.jsContext.exception) { + NSString *errMsg = [NSString stringWithFormat:@"%@ (line %@ in the generated bundle)\n/***StackTrace***/\n%@/***StackTrace***/", self.jsContext.exception, self.jsContext.exception[@"line"], self.jsContext.exception[@"stack"]]; + @throw [[NSException alloc] initWithName:@"DoricJS" reason:errMsg userInfo:nil]; + } +} + +- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source { + NSString *ret = [[self.jsContext evaluateScript:script withSourceURL:[NSURL URLWithString:source]] toString]; + [self checkJSException]; + return ret; +} + +- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj { + self.jsContext[name] = obj; + [self checkJSException]; +} + +- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args { + JSValue *obj = [self.jsContext objectForKeyedSubscript:objName]; + JSValue *ret = [obj invokeMethod:funcName withArguments:args]; + [self checkJSException]; + return ret; +} + +@end diff --git a/Pod/Classes/Engine/DoricJSEngine.h b/Pod/Classes/Engine/DoricJSEngine.h new file mode 100644 index 00000000..94404064 --- /dev/null +++ b/Pod/Classes/Engine/DoricJSEngine.h @@ -0,0 +1,46 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricJSEngine.h +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import +#import +#import "DoricRegistry.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricJSEngine : NSObject + +@property(nonatomic, strong) dispatch_queue_t jsQueue; + +@property(nonatomic, strong) DoricRegistry *registry; + +- (void)prepareContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source; + +- (void)destroyContext:(NSString *)contextId; + +- (JSValue *)invokeDoricMethod:(NSString *)method, ...; + +- (JSValue *)invokeDoricMethod:(NSString *)method arguments:(va_list)args; + +- (JSValue *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Engine/DoricJSEngine.m b/Pod/Classes/Engine/DoricJSEngine.m new file mode 100644 index 00000000..12984ea8 --- /dev/null +++ b/Pod/Classes/Engine/DoricJSEngine.m @@ -0,0 +1,181 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricJSEngine.m +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import "DoricJSEngine.h" +#import "DoricJSExecutorProtocol.h" +#import "DoricJSCoreExecutor.h" +#import "DoricJSRemoteExecutor.h" +#import "DoricConstant.h" +#import "DoricUtil.h" +#import "DoricBridgeExtension.h" + +@interface DoricJSEngine () +@property(nonatomic, strong) id jsExecutor; +@property(nonatomic, strong) NSMutableDictionary *timers; +@property(nonatomic, strong) DoricBridgeExtension *bridgeExtension; +@end + +@implementation DoricJSEngine + +- (instancetype)init { + if (self = [super init]) { + _jsQueue = dispatch_queue_create("doric.jsengine", DISPATCH_QUEUE_SERIAL); + _bridgeExtension = [[DoricBridgeExtension alloc] init]; + dispatch_async(_jsQueue, ^() { + self.timers = [[NSMutableDictionary alloc] init]; + // Debug: 切换 + // self.jsExecutor = [[DoricJSRemoteExecutor alloc] init]; + self.jsExecutor = [DoricJSCoreExecutor new]; + self.registry = [[DoricRegistry alloc] init]; + [self initJSExecutor]; + [self initDoricEnvironment]; + }); + } + return self; +} + +- (void)initJSExecutor { + __weak typeof(self) _self = self; + + [self.jsExecutor injectGlobalJSObject:INJECT_LOG obj:^(NSString *type, NSString *message) { + DoricLog(@"JS:%@", message); + }]; + [self.jsExecutor injectGlobalJSObject:INJECT_EMPTY obj:^() { + + }]; + [self.jsExecutor injectGlobalJSObject:INJECT_REQUIRE obj:^(NSString *name) { + __strong typeof(_self) self = _self; + if (!self) return NO; + NSString *content = [self.registry acquireJSBundle:name]; + if (!content) { + DoricLog(@"require js bundle:%@ is empty", name); + return NO; + } + @try { + [self.jsExecutor loadJSScript:[self packageModuleScript:name content:content] + source:[@"Module://" stringByAppendingString:name]]; + } @catch (NSException *e) { + DoricLog(@"require js bundle:%@ error,for %@", name, e.reason); + } + return YES; + }]; + [self.jsExecutor injectGlobalJSObject:INJECT_TIMER_SET + obj:^(NSNumber *timerId, NSNumber *interval, NSNumber *isInterval) { + __strong typeof(_self) self = _self; + NSString *timerId_str = [timerId stringValue]; + BOOL repeat = [isInterval boolValue]; + NSTimer *timer = [NSTimer timerWithTimeInterval:[interval doubleValue] / 1000 target:self selector:@selector(callbackTimer:) userInfo:@{@"timerId": timerId, @"repeat": isInterval} repeats:repeat]; + [self.timers setValue:timer forKey:timerId_str]; + dispatch_async(dispatch_get_main_queue(), ^() { + [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; + }); + }]; + + [self.jsExecutor injectGlobalJSObject:INJECT_TIMER_CLEAR + obj:^(NSString *timerId) { + __strong typeof(_self) self = _self; + NSTimer *timer = [self.timers valueForKey:timerId]; + if (timer) { + [timer invalidate]; + [self.timers removeObjectForKey:timerId]; + } + }]; + + [self.jsExecutor injectGlobalJSObject:INJECT_BRIDGE obj:^(NSString *contextId, NSString *module, NSString *method, NSString *callbackId, id argument) { + return [self.bridgeExtension callNativeWithContextId:contextId module:module method:method callbackId:callbackId argument:argument]; + }]; +} + +- (void)initDoricEnvironment { + [self loadBuiltinJS:DORIC_BUNDLE_SANDBOX]; + NSString *path = [DoricBundle() pathForResource:DORIC_BUNDLE_LIB ofType:@"js" inDirectory:@"bundle"]; + NSString *jsContent = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [self.jsExecutor loadJSScript:[self packageModuleScript:DORIC_MODULE_LIB content:jsContent] + source:[@"Module://" stringByAppendingString:DORIC_MODULE_LIB]]; +} + +- (void)loadBuiltinJS:(NSString *)fileName { + NSString *path = [DoricBundle() pathForResource:DORIC_BUNDLE_SANDBOX ofType:@"js" inDirectory:@"bundle"]; + NSString *jsContent = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [self.jsExecutor loadJSScript:jsContent source:[@"Assets://" stringByAppendingString:fileName]]; +} + +- (JSValue *)invokeDoricMethod:(NSString *)method, ... { + va_list args; + va_start(args, method); + JSValue *ret = [self invokeDoricMethod:method arguments:args]; + va_end(args); + return ret; +} + +- (JSValue *)invokeDoricMethod:(NSString *)method arguments:(va_list)args { + NSMutableArray *array = [[NSMutableArray alloc] init]; + id arg = va_arg(args, id); + while (arg != nil) { + [array addObject:arg]; + arg = va_arg(args, JSValue *); + } + return [self.jsExecutor invokeObject:GLOBAL_DORIC method:method args:array]; +} + +- (JSValue *)invokeDoricMethod:(NSString *)method argumentsArray:(NSArray *)args { + return [self.jsExecutor invokeObject:GLOBAL_DORIC method:method args:args]; +} + +- (NSString *)packageContextScript:(NSString *)contextId content:(NSString *)content { + NSString *ret = [NSString stringWithFormat:TEMPLATE_CONTEXT_CREATE, content, contextId, contextId, contextId]; + return ret; +} + +- (NSString *)packageModuleScript:(NSString *)moduleName content:(NSString *)content { + NSString *ret = [NSString stringWithFormat:TEMPLATE_MODULE, moduleName, content]; + return ret; +} + +- (void)prepareContext:(NSString *)contextId script:(NSString *)script source:(NSString *)source { + [self.jsExecutor loadJSScript:[self packageContextScript:contextId content:script] + source:[@"Context://" stringByAppendingString:source]]; +} + +- (void)destroyContext:(NSString *)contextId { + [self.jsExecutor loadJSScript:[NSString stringWithFormat:TEMPLATE_CONTEXT_DESTROY, contextId] + source:[@"_Context://" stringByAppendingString:contextId]]; +} + +- (void)callbackTimer:(NSTimer *)timer { + NSDictionary *userInfo = timer.userInfo; + NSNumber *timerId = [userInfo valueForKey:@"timerId"]; + NSNumber *repeat = [userInfo valueForKey:@"repeat"]; + __weak typeof(self) _self = self; + dispatch_async(self.jsQueue, ^() { + __strong typeof(_self) self = _self; + @try { + [self invokeDoricMethod:DORIC_TIMER_CALLBACK, timerId, nil]; + } @catch (NSException *exception) { + DoricLog(@"Timer Callback error:%@", exception.reason); + } + if (![repeat boolValue]) { + [self.timers removeObjectForKey:[timerId stringValue]]; + } + }); +} +@end diff --git a/Pod/Classes/Engine/DoricJSExecutorProtocol.h b/Pod/Classes/Engine/DoricJSExecutorProtocol.h new file mode 100644 index 00000000..41f4bd47 --- /dev/null +++ b/Pod/Classes/Engine/DoricJSExecutorProtocol.h @@ -0,0 +1,38 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricJSEngineProtocal.h +// Doric +// +// Created by pengfei.zhou on 2019/7/25. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol DoricJSExecutorProtocol + +- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source; + +- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj; + +- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Engine/DoricJSRemoteExecutor.h b/Pod/Classes/Engine/DoricJSRemoteExecutor.h new file mode 100644 index 00000000..65a34727 --- /dev/null +++ b/Pod/Classes/Engine/DoricJSRemoteExecutor.h @@ -0,0 +1,35 @@ +/* +* Copyright [2019] [Doric.Pub] +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +// +// DoricJSRemoteExecutor.h +// Pods +// +// Created by 王劲鹏 on 2019/10/31. +// + +#import +#import "DoricJSExecutorProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricJSRemoteExecutor : NSObject + +@property(nonatomic, strong) dispatch_semaphore_t semaphore; + +- (void)close; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Engine/DoricJSRemoteExecutor.m b/Pod/Classes/Engine/DoricJSRemoteExecutor.m new file mode 100644 index 00000000..4875126f --- /dev/null +++ b/Pod/Classes/Engine/DoricJSRemoteExecutor.m @@ -0,0 +1,193 @@ +/* +* Copyright [2019] [Doric.Pub] +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +// +// DoricJSRemoteExecutor.m +// Doric +// +// Created by 王劲鹏 on 2019/10/31. +// +#import "DoricJSRemoteExecutor.h" +#import +#import "DoricUtil.h" +#import "DoricJSRemoteArgType.h" +#import "NSString+JsonString.h" + +static NSString * const kUrlStr = @"ws://192.168.24.240:2080"; + +typedef id (^Block0)(void); +typedef id (^Block1)(id arg0); +typedef id (^Block2)(id arg0, id arg1); +typedef id (^Block3)(id arg0, id arg1, id arg2); +typedef id (^Block4)(id arg0, id arg1, id arg2, id arg3); +typedef id (^Block5)(id arg0, id arg1, id arg2, id arg3, id arg4); + +@interface DoricJSRemoteExecutor () +@property(nonatomic, strong) SRWebSocket *srWebSocket; +@property(nonatomic, strong) NSMutableDictionary *blockMDic; +@property(nonatomic, strong) JSValue *temp; +@end + +@implementation DoricJSRemoteExecutor +- (instancetype)init { + if (self = [super init]) { + [self srWebSocket]; + _semaphore = dispatch_semaphore_create(0); + DC_LOCK(self.semaphore); + } + return self; +} + +- (void)webSocketDidOpen:(SRWebSocket *)webSocket { + DoricLog(@"debugger webSocketDidOpen"); + DC_UNLOCK(self.semaphore); +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload { + DoricLog(@"debugger webSocketdidReceivePong"); +} + +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { + NSData *jsonData = [message dataUsingEncoding:NSUTF8StringEncoding]; + NSError *err; + NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData + options:NSJSONReadingMutableContainers + error:&err]; + if (err) { + DoricLog(@"debugger webSocketdidReceiveMessage parse error:%@", err); + return; + } + NSString *cmd = [[dic valueForKey:@"cmd"] copy]; + + if ([cmd isEqualToString:@"injectGlobalJSFunction"]) { + NSString *name = [dic valueForKey:@"name"]; + NSArray *argsArr = [dic valueForKey:@"arguments"]; + NSMutableArray *argsMarr = [NSMutableArray new]; + for (NSUInteger i = 0; i < argsArr.count; i++) { + [argsMarr addObject:argsArr[i]]; + } + + id result; + id tmpBlk = self.blockMDic[name]; + if (argsArr.count == 0) { + result = ((Block0) tmpBlk)(); + } else if (argsArr.count == 1) { + result = ((Block1) tmpBlk)(argsArr[0]); + } else if (argsArr.count == 2) { + result = ((Block2)tmpBlk)(argsArr[0], argsArr[1]); + } else if (argsArr.count == 3) { + result = ((Block3)tmpBlk)(argsArr[0], argsArr[1], argsArr[2]); + } else if (argsArr.count == 4) { + result = ((Block4)tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3]); + } else if (argsArr.count == 5) { + result = ((Block5)tmpBlk)(argsArr[0], argsArr[1], argsArr[2], argsArr[3], argsArr[4]); + } + + } else if ([cmd isEqualToString:@"invokeMethod"]) { + @try { + self.temp = [JSValue valueWithObject:[dic valueForKey:@"result"] inContext:nil]; + } @catch (NSException *exception) { + DoricLog(@"debugger ", NSStringFromSelector(_cmd), exception.reason); + } @finally { + DC_UNLOCK(self.semaphore); + } + } +} + +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { + DoricLog(@"debugger webSocketdidFailWithError"); + DC_UNLOCK(self.semaphore); +} + +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { + DoricLog(@"debugger webSocketdidCloseWithCode"); +} + +- (NSString *)loadJSScript:(NSString *)script source:(NSString *)source { + + return nil; +} + +- (void)injectGlobalJSObject:(NSString *)name obj:(id)obj { + if ([obj isKindOfClass:NSClassFromString(@"NSBlock")]) { + self.blockMDic[name] = obj; + } + NSDictionary *jsonDic = @{ + @"cmd": @"injectGlobalJSFunction", + @"name": name + }; + + NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic]; + if (!jsonStr) { + return; + } + + [self.srWebSocket send:jsonStr]; +} + +- (JSValue *)invokeObject:(NSString *)objName method:(NSString *)funcName args:(NSArray *)args { + + NSMutableArray *argsMArr = [NSMutableArray new]; + for (id arg in args) { + NSDictionary *dic = @{ + @"type": @(DoricargTypeWithArg(arg)), + @"value": arg + }; + [argsMArr addObject:dic]; + } + + NSArray *argsArr = [argsMArr copy]; + + NSDictionary *jsonDic = @{ + @"cmd": @"invokeMethod", + @"objectName": objName, + @"functionName": funcName, + @"javaValues": argsArr + }; + + NSString *jsonStr = [NSString dc_convertToJsonWithDic:jsonDic]; + if (!jsonStr) { + return nil; + } + + [self.srWebSocket send:jsonStr]; + DC_LOCK(self.semaphore); + + return self.temp; +} + +- (void)close { + [self.srWebSocket close]; +} + +#pragma mark - Properties +- (SRWebSocket *)srWebSocket { + if (!_srWebSocket) { + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:kUrlStr] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10]; + _srWebSocket = [[SRWebSocket alloc] initWithURLRequest:request]; + _srWebSocket.delegate = self; + [_srWebSocket open]; + } + return _srWebSocket; +} + +- (NSMutableDictionary *)blockMDic { + if (!_blockMDic) { + _blockMDic = [NSMutableDictionary new]; + } + return _blockMDic; +} + +@end diff --git a/Pod/Classes/Extension/DoricBridgeExtension.h b/Pod/Classes/Extension/DoricBridgeExtension.h new file mode 100644 index 00000000..c510c162 --- /dev/null +++ b/Pod/Classes/Extension/DoricBridgeExtension.h @@ -0,0 +1,31 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricBridgeExtension.h +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricBridgeExtension : NSObject +- (id)callNativeWithContextId:(NSString *)contextId module:(NSString *)module method:(NSString *)method callbackId:(NSString *)callbackId argument:(id)argument; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Extension/DoricBridgeExtension.m b/Pod/Classes/Extension/DoricBridgeExtension.m new file mode 100644 index 00000000..a23b586b --- /dev/null +++ b/Pod/Classes/Extension/DoricBridgeExtension.m @@ -0,0 +1,120 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricBridgeExtension.m +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import "DoricBridgeExtension.h" +#import "DoricRegistry.h" +#import "DoricContextManager.h" +#import "DoricNativePlugin.h" +#import "DoricUtil.h" + +#import + +#import + +@implementation DoricBridgeExtension + +- (id)callNativeWithContextId:(NSString *)contextId module:(NSString *)module method:(NSString *)method callbackId:(NSString *)callbackId argument:(id)argument { + DoricContext *context = [[DoricContextManager instance] getContext:contextId]; + DoricRegistry *registry = context.driver.registry; + Class pluginClass = [registry acquireNativePlugin:module]; + DoricNativePlugin *nativePlugin = context.pluginInstanceMap[module]; + if (nativePlugin == nil) { + nativePlugin = [(DoricNativePlugin *) [pluginClass alloc] initWithContext:context]; + context.pluginInstanceMap[module] = nativePlugin; + } + + return [self findClass:pluginClass target:nativePlugin context:context method:method callbackId:callbackId argument:argument]; +} + +- (id)createParamWithMethodName:(NSString *)method context:(DoricContext *)context callbackId:(NSString *)callbackId argument:(id)argument { + if ([method isEqualToString:@"withPromise"]) { + return [[DoricPromise alloc] initWithContext:context callbackId:callbackId]; + } + return argument; +} + +- (id)findClass:(Class)clz target:(id)target context:(DoricContext *)context method:(NSString *)name callbackId:(NSString *)callbackId argument:(id)argument { + unsigned int count; + id ret = nil; + Method *methods = class_copyMethodList(clz, &count); + BOOL isFound = NO; + for (int i = 0; i < count; i++) { + NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(methods[i])) encoding:NSUTF8StringEncoding]; + NSArray *array = [methodName componentsSeparatedByString:@":"]; + if (array && [array count] > 0) { + if ([array[0] isEqualToString:name]) { + isFound = YES; + SEL selector = NSSelectorFromString(methodName); + NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector]; + if (methodSignature) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = selector; + invocation.target = target; + __weak __typeof__(self) _self = self; + dispatch_block_t block = ^() { + __strong __typeof__(_self) self = _self; + @try { + for (NSUInteger idx = 2; idx < methodSignature.numberOfArguments; idx++) { + if (idx - 2 > [array count]) { + break; + } + id args = [self createParamWithMethodName:array[idx - 2] context:context callbackId:callbackId argument:argument]; + [invocation setArgument:&args atIndex:idx]; + } + [invocation invoke]; + } @catch (NSException *exception) { + DoricLog(@"CallNative Error:%@", exception.reason); + } + }; + + const char *retType = methodSignature.methodReturnType; + if (!strcmp(retType, @encode(void))) { + ret = nil; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); + } else if (!strcmp(retType, @encode(id))) { + void *retValue; + block(); + [invocation getReturnValue:&retValue]; + id returnValue = (__bridge id) retValue; + ret = [JSValue valueWithObject:[returnValue copy] inContext:[JSContext currentContext]]; + } else { + DoricLog(@"CallNative Error:%@", @"Must return object type"); + ret = nil; + } + } + break; + } + } + } + if (methods) { + free(methods); + } + if (!isFound) { + Class superclass = class_getSuperclass(clz); + if (superclass && superclass != [NSObject class]) { + return [self findClass:superclass target:target context:context method:name callbackId:callbackId argument:argument]; + } + } + return ret; + +} +@end diff --git a/Pod/Classes/Loader/DoricHttpJSLoader.h b/Pod/Classes/Loader/DoricHttpJSLoader.h new file mode 100644 index 00000000..ce146f58 --- /dev/null +++ b/Pod/Classes/Loader/DoricHttpJSLoader.h @@ -0,0 +1,25 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import + +#import "DoricLoaderProtocol.h" + +@interface DoricHttpJSLoader : NSObject +@end \ No newline at end of file diff --git a/Pod/Classes/Loader/DoricHttpJSLoader.m b/Pod/Classes/Loader/DoricHttpJSLoader.m new file mode 100644 index 00000000..d9bd6a2c --- /dev/null +++ b/Pod/Classes/Loader/DoricHttpJSLoader.m @@ -0,0 +1,45 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import "DoricHttpJSLoader.h" + + +@implementation DoricHttpJSLoader + +- (BOOL)filter:(NSString *)scheme { + return [scheme hasPrefix:@"http"]; +} + +- (DoricAsyncResult *)request:(NSString *)scheme { + DoricAsyncResult *ret = [DoricAsyncResult new]; + NSURL *URL = [NSURL URLWithString:scheme]; + NSURLRequest *request = [NSURLRequest requestWithURL:URL]; + [[[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]] + dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (!error) { + NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + [ret setupResult:dataStr]; + } else { + [ret setupError:[[NSException alloc] initWithName:@"DoricJSLoaderManager Exception" reason:error.description userInfo:nil]]; + } + }] resume]; + return ret; +} +@end \ No newline at end of file diff --git a/Pod/Classes/Loader/DoricJSLoaderManager.h b/Pod/Classes/Loader/DoricJSLoaderManager.h new file mode 100644 index 00000000..efbf2846 --- /dev/null +++ b/Pod/Classes/Loader/DoricJSLoaderManager.h @@ -0,0 +1,33 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricJSLoaderManager.h +// Doric +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import +#import "DoricLoaderProtocol.h" +#import "DoricAsyncResult.h" + +@interface DoricJSLoaderManager : NSObject ++ (instancetype)instance; + +- (void)addJSLoader:(id )loader; + +- (DoricAsyncResult *)request:(NSString *)scheme; +@end diff --git a/Pod/Classes/Loader/DoricJSLoaderManager.m b/Pod/Classes/Loader/DoricJSLoaderManager.m new file mode 100644 index 00000000..41cf0fc5 --- /dev/null +++ b/Pod/Classes/Loader/DoricJSLoaderManager.m @@ -0,0 +1,68 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricJSLoaderManager.m +// Doric +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import "DoricJSLoaderManager.h" +#import "DoricMainBundleJSLoader.h" +#import "DoricHttpJSLoader.h" +#import "Doric.h" + +@interface DoricJSLoaderManager () +@property(nonatomic, copy) NSSet > *loaders; +@end + +@implementation DoricJSLoaderManager ++ (instancetype)instance { + static DoricJSLoaderManager *_instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _instance = [DoricJSLoaderManager new]; + }); + return _instance; +} + +- (instancetype)init { + if (self = [super init]) { + _loaders = [[NSSet alloc] initWithArray:@[ + [DoricMainBundleJSLoader new], + [DoricHttpJSLoader new], + ]]; + } + return self; +} + +- (void)addJSLoader:(id )loader { + self.loaders = [[self.loaders mutableCopy] also:^(NSMutableSet *it) { + [it addObject:loader]; + }]; +} + +- (DoricAsyncResult *)request:(NSString *)scheme { + __block DoricAsyncResult *ret; + [self.loaders enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { + if ([obj filter:scheme]) { + ret = [obj request:scheme]; + *stop = YES; + } + }]; + return ret; +} +@end diff --git a/Pod/Classes/Loader/DoricLoaderProtocol.h b/Pod/Classes/Loader/DoricLoaderProtocol.h new file mode 100644 index 00000000..b3b509f0 --- /dev/null +++ b/Pod/Classes/Loader/DoricLoaderProtocol.h @@ -0,0 +1,27 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import +#import "DoricAsyncResult.h" + +@protocol DoricLoaderProtocol +- (BOOL)filter:(NSString *)scheme; + +- (DoricAsyncResult *)request:(NSString *)scheme; +@end \ No newline at end of file diff --git a/Pod/Classes/Loader/DoricMainBundleJSLoader.h b/Pod/Classes/Loader/DoricMainBundleJSLoader.h new file mode 100644 index 00000000..ff5d79ae --- /dev/null +++ b/Pod/Classes/Loader/DoricMainBundleJSLoader.h @@ -0,0 +1,25 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import + +#import "DoricLoaderProtocol.h" + +@interface DoricMainBundleJSLoader : NSObject +@end \ No newline at end of file diff --git a/Pod/Classes/Loader/DoricMainBundleJSLoader.m b/Pod/Classes/Loader/DoricMainBundleJSLoader.m new file mode 100644 index 00000000..63663098 --- /dev/null +++ b/Pod/Classes/Loader/DoricMainBundleJSLoader.m @@ -0,0 +1,42 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import "DoricMainBundleJSLoader.h" + + +@implementation DoricMainBundleJSLoader +- (BOOL)filter:(NSString *)scheme { + return [scheme hasPrefix:@"assets"]; +} + +- (DoricAsyncResult *)request:(NSString *)scheme { + DoricAsyncResult *ret = [DoricAsyncResult new]; + NSString *path = [[NSBundle mainBundle] bundlePath]; + NSString *fullPath = [path stringByAppendingPathComponent:[scheme substringFromIndex:@"assets://".length]]; + NSError *error; + NSString *jsContent = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:&error]; + if (error) { + [ret setupError:[NSException new]]; + } else { + [ret setupResult:jsContent]; + } + return ret; +} + +@end \ No newline at end of file diff --git a/Pod/Classes/NavBar/DoricNavBarDelegate.h b/Pod/Classes/NavBar/DoricNavBarDelegate.h new file mode 100644 index 00000000..ad668f5e --- /dev/null +++ b/Pod/Classes/NavBar/DoricNavBarDelegate.h @@ -0,0 +1,31 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/25. +// + +#import + + +@protocol DoricNavBarDelegate +- (BOOL)doric_navBar_isHidden; + +- (void)doric_navBar_setHidden:(BOOL)hidden; + +- (void)doric_navBar_setTitle:(NSString *)title; + +- (void)doric_navBar_setBackgroundColor:(UIColor *)color; +@end \ No newline at end of file diff --git a/Pod/Classes/Navigator/DoricNavigatorDelegate.h b/Pod/Classes/Navigator/DoricNavigatorDelegate.h new file mode 100644 index 00000000..52d4d315 --- /dev/null +++ b/Pod/Classes/Navigator/DoricNavigatorDelegate.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import + +@protocol DoricNavigatorDelegate +- (void)doric_navigator_push:(NSString *)scheme alias:(NSString *)alias animated:(BOOL)animated; + +- (void)doric_navigator_pop:(BOOL)animated; +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricAnimatePlugin.h b/Pod/Classes/Plugin/DoricAnimatePlugin.h new file mode 100644 index 00000000..484284a9 --- /dev/null +++ b/Pod/Classes/Plugin/DoricAnimatePlugin.h @@ -0,0 +1,10 @@ +// +// Created by pengfei.zhou on 2019/11/29. +// + +#import + +#import "DoricNativePlugin.h" + +@interface DoricAnimatePlugin : DoricNativePlugin +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricAnimatePlugin.m b/Pod/Classes/Plugin/DoricAnimatePlugin.m new file mode 100644 index 00000000..317e38a7 --- /dev/null +++ b/Pod/Classes/Plugin/DoricAnimatePlugin.m @@ -0,0 +1,33 @@ +// +// Created by pengfei.zhou on 2019/11/29. +// + +#import "DoricAnimatePlugin.h" +#import "DoricRootNode.h" + +@implementation DoricAnimatePlugin + +- (void)submit:(NSDictionary *)args withPromise:(DoricPromise *)promise { + [promise resolve:nil]; +} + +- (void)animateRender:(NSDictionary *)args withPromise:(DoricPromise *)promise { + NSNumber *duration = args[@"duration"]; + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *viewId = args[@"id"]; + [UIView animateWithDuration:[duration floatValue] / 1000 + animations:^{ + if (self.doricContext.rootNode.viewId == nil) { + self.doricContext.rootNode.viewId = viewId; + [self.doricContext.rootNode blend:args[@"props"]]; + } else { + DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId]; + [viewNode blend:args[@"props"]]; + } + } + completion:^(BOOL finished) { + [promise resolve:nil]; + }]; + }); +} +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricModalPlugin.h b/Pod/Classes/Plugin/DoricModalPlugin.h new file mode 100644 index 00000000..88cfb867 --- /dev/null +++ b/Pod/Classes/Plugin/DoricModalPlugin.h @@ -0,0 +1,32 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricModalPlugin.h +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import +#import "DoricNativePlugin.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricModalPlugin : DoricNativePlugin + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Plugin/DoricModalPlugin.m b/Pod/Classes/Plugin/DoricModalPlugin.m new file mode 100644 index 00000000..77944f23 --- /dev/null +++ b/Pod/Classes/Plugin/DoricModalPlugin.m @@ -0,0 +1,115 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricModalPlugin.m +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import +#import "DoricModalPlugin.h" +#import "DoricUtil.h" + +@implementation DoricModalPlugin + +- (void)toast:(NSDictionary *)dic withPromise:(DoricPromise *)promise { + dispatch_async(dispatch_get_main_queue(), ^{ + __block DoricGravity gravity = BOTTOM; + [dic[@"gravity"] also:^(NSNumber *it) { + gravity = (DoricGravity) [it integerValue]; + }]; + ShowToast(dic[@"msg"], gravity); + }); +} + +- (void)alert:(NSDictionary *)dic withPromise:(DoricPromise *)promise { + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:dic[@"title"] + message:dic[@"msg"] + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *action = [UIAlertAction actionWithTitle:dic[@"okLabel"] ?: NSLocalizedString(@"OK", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [promise resolve:nil]; + }]; + [alert addAction:action]; + UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController; + [vc presentViewController:alert animated:YES completion:nil]; + }); +} + +- (void)confirm:(NSDictionary *)dic withPromise:(DoricPromise *)promise { + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:dic[@"title"] + message:dic[@"msg"] + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:dic[@"okLabel"] ?: NSLocalizedString(@"Ok", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [promise resolve:nil]; + }]; + [alert addAction:okAction]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:dic[@"cancelLabel"] ?: NSLocalizedString(@"Cancel", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [promise reject:nil]; + }]; + [alert addAction:cancelAction]; + UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController; + [vc presentViewController:alert animated:YES completion:nil]; + }); +} + +- (void)prompt:(NSDictionary *)dic withPromise:(DoricPromise *)promise { + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:dic[@"title"] + message:dic[@"msg"] + preferredStyle:UIAlertControllerStyleAlert]; + NSString *placeholder = dic[@"defaultText"]; + NSString *preText = dic[@"text"]; + [alert addTextFieldWithConfigurationHandler:^(UITextField *_Nonnull textField) { + if (placeholder.length > 0) { + textField.placeholder = placeholder; + } + if (preText.length > 0) { + textField.text = preText; + } + }]; + __weak typeof(alert) _alert = alert; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:dic[@"okLabel"] ?: NSLocalizedString(@"Ok", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + __strong typeof(_alert) alert = _alert; + [promise resolve:alert.textFields.lastObject.text]; + }]; + [alert addAction:okAction]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:dic[@"cancelLabel"] ?: NSLocalizedString(@"Cancel", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + __strong typeof(_alert) alert = _alert; + [promise reject:alert.textFields.lastObject.text]; + }]; + [alert addAction:cancelAction]; + + + UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController; + [vc presentViewController:alert animated:YES completion:nil]; + }); +} +@end diff --git a/Pod/Classes/Plugin/DoricNativePlugin.h b/Pod/Classes/Plugin/DoricNativePlugin.h new file mode 100644 index 00000000..0616dfc0 --- /dev/null +++ b/Pod/Classes/Plugin/DoricNativePlugin.h @@ -0,0 +1,34 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricNativePlugin.h +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import +#import "DoricContextHolder.h" +#import "DoricPromise.h" +#import "DoricRegistry.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricNativePlugin : DoricContextHolder + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Plugin/DoricNativePlugin.m b/Pod/Classes/Plugin/DoricNativePlugin.m new file mode 100644 index 00000000..6e1bc8e5 --- /dev/null +++ b/Pod/Classes/Plugin/DoricNativePlugin.m @@ -0,0 +1,27 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricNativePlugin.m +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import "DoricNativePlugin.h" + +@implementation DoricNativePlugin + +@end diff --git a/Pod/Classes/Plugin/DoricNavBarPlugin.h b/Pod/Classes/Plugin/DoricNavBarPlugin.h new file mode 100644 index 00000000..5b214edc --- /dev/null +++ b/Pod/Classes/Plugin/DoricNavBarPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/25. +// + +#import + +#import "DoricNativePlugin.h" + +@interface DoricNavBarPlugin : DoricNativePlugin + +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricNavBarPlugin.m b/Pod/Classes/Plugin/DoricNavBarPlugin.m new file mode 100644 index 00000000..46399c64 --- /dev/null +++ b/Pod/Classes/Plugin/DoricNavBarPlugin.m @@ -0,0 +1,68 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/25. +// + +#import "DoricNavBarPlugin.h" +#import "DoricUtil.h" + +@implementation DoricNavBarPlugin +- (void)isHidden:(NSDictionary *)param withPromise:(DoricPromise *)promise { + if (self.doricContext.navBar) { + dispatch_async(dispatch_get_main_queue(), ^{ + [promise resolve:@([self.doricContext.navBar doric_navBar_isHidden])]; + }); + } else { + [promise reject:@"Not implement NavBar"]; + } +} + +- (void)setHidden:(NSDictionary *)param withPromise:(DoricPromise *)promise { + if (self.doricContext.navBar) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.doricContext.navBar doric_navBar_setHidden:[param[@"hidden"] boolValue]]; + [promise resolve:nil]; + }); + } else { + [promise reject:@"Not implement NavBar"]; + } +} + +- (void)setTitle:(NSDictionary *)param withPromise:(DoricPromise *)promise { + if (self.doricContext.navBar) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.doricContext.navBar doric_navBar_setTitle:param[@"title"]]; + [promise resolve:nil]; + }); + } else { + [promise reject:@"Not implement NavBar"]; + } +} + +- (void)setBgColor:(NSDictionary *)param withPromise:(DoricPromise *)promise { + if (self.doricContext.navBar) { + dispatch_async(dispatch_get_main_queue(), ^{ + UIColor *color = DoricColor(param[@"color"]); + [self.doricContext.navBar doric_navBar_setBackgroundColor:color]; + [promise resolve:nil]; + }); + } else { + [promise reject:@"Not implement NavBar"]; + } +} + +@end diff --git a/Pod/Classes/Plugin/DoricNavigatorPlugin.h b/Pod/Classes/Plugin/DoricNavigatorPlugin.h new file mode 100644 index 00000000..d5ea4947 --- /dev/null +++ b/Pod/Classes/Plugin/DoricNavigatorPlugin.h @@ -0,0 +1,24 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import +#import "DoricNativePlugin.h" + +@interface DoricNavigatorPlugin : DoricNativePlugin +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricNavigatorPlugin.m b/Pod/Classes/Plugin/DoricNavigatorPlugin.m new file mode 100644 index 00000000..3086e571 --- /dev/null +++ b/Pod/Classes/Plugin/DoricNavigatorPlugin.m @@ -0,0 +1,43 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/23. +// + +#import "DoricNavigatorPlugin.h" + + +@implementation DoricNavigatorPlugin +- (void)push:(NSDictionary *)params { + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL animated = YES; + if (params[@"animated"]) { + animated = [params[@"animated"] boolValue]; + } + [self.doricContext.navigator doric_navigator_push:params[@"scheme"] alias:params[@"alias"] animated:animated]; + }); +} + +- (void)pop:(NSDictionary *)params { + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL animated = YES; + if (params[@"animated"]) { + animated = [params[@"animated"] boolValue]; + } + [self.doricContext.navigator doric_navigator_pop:animated]; + }); +} +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricNetworkPlugin.h b/Pod/Classes/Plugin/DoricNetworkPlugin.h new file mode 100644 index 00000000..3598bd80 --- /dev/null +++ b/Pod/Classes/Plugin/DoricNetworkPlugin.h @@ -0,0 +1,24 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/21. +// + +#import +#import "DoricNativePlugin.h" + +@interface DoricNetworkPlugin : DoricNativePlugin +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricNetworkPlugin.m b/Pod/Classes/Plugin/DoricNetworkPlugin.m new file mode 100644 index 00000000..2d03c35f --- /dev/null +++ b/Pod/Classes/Plugin/DoricNetworkPlugin.m @@ -0,0 +1,59 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/21. +// + +#import "DoricNetworkPlugin.h" + + +@implementation DoricNetworkPlugin +- (void)request:(NSDictionary *)dic withPromise:(DoricPromise *)promise { + NSString *url = dic[@"url"]; + NSString *method = dic[@"method"]; + NSDictionary *headers = dic[@"headers"]; + NSNumber *timeout = dic[@"timeout"]; + NSString *data = dic[@"data"]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; + request.HTTPMethod = method.uppercaseString; + if (timeout) { + request.timeoutInterval = [timeout floatValue] / 1000; + } + if (headers) { + [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) { + [request setValue:obj forHTTPHeaderField:key]; + }]; + } + if (data) { + [request setHTTPBody:[data dataUsingEncoding:NSUTF8StringEncoding]]; + } + [[[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]] + dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (!error) { + NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSDictionary *resDic = @{ + @"status": @(((NSHTTPURLResponse *) response).statusCode), + @"headers": ((NSHTTPURLResponse *) response).allHeaderFields, + @"data": dataStr, + }; + [promise resolve:resDic]; + } else { + [promise reject:error.description]; + } + }] resume]; +} +@end diff --git a/Pod/Classes/Plugin/DoricPopoverPlugin.h b/Pod/Classes/Plugin/DoricPopoverPlugin.h new file mode 100644 index 00000000..c9eaaf8f --- /dev/null +++ b/Pod/Classes/Plugin/DoricPopoverPlugin.h @@ -0,0 +1,9 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import +#import "DoricNativePlugin.h" + +@interface DoricPopoverPlugin : DoricNativePlugin +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricPopoverPlugin.m b/Pod/Classes/Plugin/DoricPopoverPlugin.m new file mode 100644 index 00000000..43125f51 --- /dev/null +++ b/Pod/Classes/Plugin/DoricPopoverPlugin.m @@ -0,0 +1,70 @@ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import "DoricPopoverPlugin.h" +#import "DoricRootNode.h" +#import "Doric.h" + +@interface DoricPopoverPlugin () +@property(nonatomic, strong) UIView *fullScreenView; +@end + +@implementation DoricPopoverPlugin +- (void)show:(NSDictionary *)params withPromise:(DoricPromise *)promise { + dispatch_async(dispatch_get_main_queue(), ^{ + UIView *superView = [UIApplication sharedApplication].windows.lastObject; + if (!self.fullScreenView) { + self.fullScreenView = [[DoricStackView new] also:^(UIView *it) { + it.width = superView.width; + it.height = superView.height; + it.top = it.left = 0; + [superView addSubview:it]; + }]; + } + [superView bringSubviewToFront:self.fullScreenView]; + self.fullScreenView.hidden = NO; + NSString *viewId = params[@"id"]; + NSString *type = params[@"type"]; + DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId]; + if (!viewNode) { + viewNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) { + it.viewId = viewId; + [it initWithSuperNode:nil]; + it.view.layoutConfig = [DoricLayoutConfig new]; + [self.fullScreenView addSubview:it.view]; + self.doricContext.headNodes[viewId] = it; + }]; + } + [viewNode blend:params[@"props"]]; + [promise resolve:nil]; + }); +} + +- (void)dismiss:(NSDictionary *)params withPromise:(DoricPromise *)promise { + NSString *viewId = params[@"id"]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (viewId) { + DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId]; + [self dismissViewNode:viewNode]; + } else { + [self dismissPopover]; + } + [promise resolve:nil]; + }); +} + +- (void)dismissViewNode:(DoricViewNode *)node { + [self.doricContext.headNodes removeObjectForKey:node.viewId]; + [node.view removeFromSuperview]; + if (self.doricContext.headNodes.count == 0) { + self.fullScreenView.hidden = YES; + } +} + +- (void)dismissPopover { + for (DoricViewNode *node in self.doricContext.headNodes.allValues) { + [self dismissViewNode:node]; + } +} +@end diff --git a/Pod/Classes/Plugin/DoricPromise.h b/Pod/Classes/Plugin/DoricPromise.h new file mode 100644 index 00000000..26807d07 --- /dev/null +++ b/Pod/Classes/Plugin/DoricPromise.h @@ -0,0 +1,32 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricPromise.h +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import +#import "DoricContext.h" + +@interface DoricPromise : NSObject +- (instancetype)initWithContext:(DoricContext *)context callbackId:(NSString *)callbackId; + +- (void)resolve:(id)result; + +- (void)reject:(id)result; +@end diff --git a/Pod/Classes/Plugin/DoricPromise.m b/Pod/Classes/Plugin/DoricPromise.m new file mode 100644 index 00000000..5df3b2eb --- /dev/null +++ b/Pod/Classes/Plugin/DoricPromise.m @@ -0,0 +1,49 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricPromise.m +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import "DoricPromise.h" +#import "DoricConstant.h" + +@interface DoricPromise () +@property(nonatomic, strong) DoricContext *context; +@property(nonatomic, strong) NSString *callbackId; + +@end + +@implementation DoricPromise + +- (instancetype)initWithContext:(DoricContext *)context callbackId:(NSString *)callbackId { + if (self = [super init]) { + _context = context; + _callbackId = callbackId; + } + return self; +} + +- (void)resolve:(id)result { + [self.context.driver invokeDoricMethod:DORIC_BRIDGE_RESOLVE, self.context.contextId, self.callbackId, result, nil]; +} + +- (void)reject:(id)result { + [self.context.driver invokeDoricMethod:DORIC_BRIDGE_REJECT, self.context.contextId, self.callbackId, result, nil]; +} +@end diff --git a/Pod/Classes/Plugin/DoricShaderPlugin.h b/Pod/Classes/Plugin/DoricShaderPlugin.h new file mode 100644 index 00000000..5aee469f --- /dev/null +++ b/Pod/Classes/Plugin/DoricShaderPlugin.h @@ -0,0 +1,32 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricShaderPlugin.h +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import +#import "DoricNativePlugin.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricShaderPlugin : DoricNativePlugin + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Plugin/DoricShaderPlugin.m b/Pod/Classes/Plugin/DoricShaderPlugin.m new file mode 100644 index 00000000..62fe12f2 --- /dev/null +++ b/Pod/Classes/Plugin/DoricShaderPlugin.m @@ -0,0 +1,143 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricShaderPlugin.m +// Doric +// +// Created by pengfei.zhou on 2019/7/29. +// + +#import "DoricShaderPlugin.h" +#import "DoricRootNode.h" +#import "DoricUtil.h" + +#import + +@implementation DoricShaderPlugin + +- (void)render:(NSDictionary *)argument { + if (!argument) { + return; + } + __weak typeof(self) _self = self; + dispatch_async(dispatch_get_main_queue(), ^{ + __strong typeof(_self) self = _self; + + NSString *viewId = argument[@"id"]; + + if (self.doricContext.rootNode.viewId == nil) { + self.doricContext.rootNode.viewId = viewId; + [self.doricContext.rootNode blend:argument[@"props"]]; + } else { + DoricViewNode *viewNode = [self.doricContext targetViewNode:viewId]; + [viewNode blend:argument[@"props"]]; + } + }); +} + +- (id)command:(NSDictionary *)argument withPromise:(DoricPromise *)promise { + NSArray *viewIds = argument[@"viewIds"]; + id args = argument[@"args"]; + NSString *name = argument[@"name"]; + DoricViewNode *viewNode = nil; + for (NSString *viewId in viewIds) { + if (!viewNode) { + viewNode = [self.doricContext targetViewNode:viewId]; + } else { + if ([viewNode isKindOfClass:[DoricSuperNode class]]) { + viewNode = [((DoricSuperNode *) viewNode) subNodeWithViewId:viewId]; + } + } + } + if (!viewNode) { + [promise reject:@"Cannot find opposite view"]; + return nil; + } else { + return [self findClass:[viewNode class] target:viewNode method:name promise:promise argument:args]; + } +} + +- (id)createParamWithMethodName:(NSString *)method promise:(DoricPromise *)promise argument:(id)argument { + if ([method isEqualToString:@"withPromise"]) { + return promise; + } + return argument; +} + +- (id)findClass:(Class)clz target:(id)target method:(NSString *)name promise:(DoricPromise *)promise argument:(id)argument { + unsigned int count; + id ret = nil; + Method *methods = class_copyMethodList(clz, &count); + BOOL isFound = NO; + for (int i = 0; i < count; i++) { + NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(methods[i])) encoding:NSUTF8StringEncoding]; + NSArray *array = [methodName componentsSeparatedByString:@":"]; + if (array && [array count] > 0) { + if ([array[0] isEqualToString:name]) { + isFound = YES; + SEL selector = NSSelectorFromString(methodName); + NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector]; + if (methodSignature) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = selector; + invocation.target = target; + __weak __typeof__(self) _self = self; + dispatch_block_t block = ^() { + __strong __typeof__(_self) self = _self; + @try { + for (NSUInteger idx = 2; idx < methodSignature.numberOfArguments; idx++) { + if (idx - 2 > [array count]) { + break; + } + id param = [self createParamWithMethodName:array[idx - 2] promise:promise argument:argument]; + [invocation setArgument:¶m atIndex:idx]; + } + [invocation invoke]; + } @catch (NSException *exception) { + DoricLog(@"CallNative Error:%@", exception.reason); + } + }; + dispatch_async(dispatch_get_main_queue(), ^{ + void *retValue; + block(); + const char *retType = methodSignature.methodReturnType; + if (!strcmp(retType, @encode(void))) { + } else { + [invocation getReturnValue:&retValue]; + id returnValue = (__bridge id) retValue; + [promise resolve:returnValue]; + } + }); + return ret; + } + break; + } + } + } + + if (methods) { + free(methods); + } + if (!isFound) { + Class superclass = class_getSuperclass(clz); + if (superclass && superclass != [NSObject class]) { + return [self findClass:superclass target:target method:name promise:promise argument:argument]; + } + } + return ret; +} + +@end diff --git a/Pod/Classes/Plugin/DoricStoragePlugin.h b/Pod/Classes/Plugin/DoricStoragePlugin.h new file mode 100644 index 00000000..1e9359a6 --- /dev/null +++ b/Pod/Classes/Plugin/DoricStoragePlugin.h @@ -0,0 +1,24 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/22. +// + +#import +#import "DoricNativePlugin.h" + +@interface DoricStoragePlugin : DoricNativePlugin +@end \ No newline at end of file diff --git a/Pod/Classes/Plugin/DoricStoragePlugin.m b/Pod/Classes/Plugin/DoricStoragePlugin.m new file mode 100644 index 00000000..95be9b1d --- /dev/null +++ b/Pod/Classes/Plugin/DoricStoragePlugin.m @@ -0,0 +1,98 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/22. +// + +#import "DoricStoragePlugin.h" +#import "YYDiskCache.h" + +static NSString *doric_prefix = @"pref"; + +@interface DoricStoragePlugin () +@property(atomic, strong) NSMutableDictionary *cachedMap; +@property(nonatomic, strong) YYDiskCache *defaultCache; +@property(nonatomic, copy) NSString *basePath; +@end + +@implementation DoricStoragePlugin +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _basePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] + stringByAppendingPathComponent:@"doric"]; + _cachedMap = [NSMutableDictionary new]; + } + return self; +} + +- (YYDiskCache *)defaultCache { + if (!_defaultCache) { + _defaultCache = [[YYDiskCache alloc] initWithPath:[self.basePath stringByAppendingPathComponent:doric_prefix]]; + } + return _defaultCache; +} + +- (YYDiskCache *)getDiskCache:(NSString *)zone { + YYDiskCache *diskCache; + if (zone) { + diskCache = self.cachedMap[zone]; + if (!diskCache) { + diskCache = [[YYDiskCache alloc] initWithPath:[self.basePath + stringByAppendingPathComponent:[NSString stringWithFormat:@"%@_%@", doric_prefix, zone]]]; + self.cachedMap[zone] = diskCache; + } + } else { + diskCache = self.defaultCache; + } + return diskCache; +} + +- (void)setItem:(NSDictionary *)argument withPromise:(DoricPromise *)promise { + NSString *zone = argument[@"zone"]; + NSString *key = argument[@"key"]; + NSString *value = argument[@"value"]; + YYDiskCache *diskCache = [self getDiskCache:zone]; + [diskCache setObject:value forKey:key withBlock:^{ + [promise resolve:nil]; + }]; +} + +- (void)getItem:(NSDictionary *)argument withPromise:(DoricPromise *)promise { + NSString *zone = argument[@"zone"]; + NSString *key = argument[@"key"]; + YYDiskCache *diskCache = [self getDiskCache:zone]; + [diskCache objectForKey:key withBlock:^(NSString *key, NSString *value) { + [promise resolve:value]; + }]; +} + +- (void)remove:(NSDictionary *)argument withPromise:(DoricPromise *)promise { + NSString *zone = argument[@"zone"]; + NSString *key = argument[@"key"]; + YYDiskCache *diskCache = [self getDiskCache:zone]; + [diskCache removeObjectForKey:key withBlock:^(NSString *key) { + [promise resolve:nil]; + }]; +} + +- (void)clear:(NSDictionary *)argument withPromise:(DoricPromise *)promise { + NSString *zone = argument[@"zone"]; + YYDiskCache *diskCache = [self getDiskCache:zone]; + [diskCache removeAllObjectsWithBlock:^{ + [promise resolve:nil]; + }]; +} +@end \ No newline at end of file diff --git a/Pod/Classes/Refresh/DoricRefreshableNode.h b/Pod/Classes/Refresh/DoricRefreshableNode.h new file mode 100644 index 00000000..db9e5357 --- /dev/null +++ b/Pod/Classes/Refresh/DoricRefreshableNode.h @@ -0,0 +1,25 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/26. +// + +#import +#import "DoricSuperNode.h" +#import "DoricSwipeRefreshLayout.h" + +@interface DoricRefreshableNode : DoricSuperNode +@end \ No newline at end of file diff --git a/Pod/Classes/Refresh/DoricRefreshableNode.m b/Pod/Classes/Refresh/DoricRefreshableNode.m new file mode 100644 index 00000000..282fb4fc --- /dev/null +++ b/Pod/Classes/Refresh/DoricRefreshableNode.m @@ -0,0 +1,176 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/26. +// + +#import "DoricRefreshableNode.h" +#import "Doric.h" + +@interface DoricRefreshableNode () +@property(nonatomic, strong) DoricViewNode *contentNode; +@property(nonatomic, copy) NSString *contentViewId; +@property(nonatomic, strong) DoricViewNode *headerNode; +@property(nonatomic, copy) NSString *headerViewId; +@end + +@implementation DoricRefreshableNode +- (DoricSwipeRefreshLayout *)build { + return [[DoricSwipeRefreshLayout new] also:^(DoricSwipeRefreshLayout *it) { + it.swipePullingDelegate = self; + + }]; +} + +- (void)blendView:(DoricSwipeRefreshLayout *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"content" isEqualToString:name]) { + self.contentViewId = prop; + } else if ([@"header" isEqualToString:name]) { + self.headerViewId = prop; + } else if ([@"onRefresh" isEqualToString:name]) { + __weak typeof(self) _self = self; + NSString *funcId = prop; + self.view.onRefreshBlock = ^{ + __strong typeof(_self) self = _self; + [self callJSResponse:funcId, nil]; + }; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + if ([viewId isEqualToString:self.contentViewId]) { + return self.contentNode; + } else if ([viewId isEqualToString:self.headerViewId]) { + return self.headerNode; + } else { + return nil; + } +} + +- (void)blend:(NSDictionary *)props { + [super blend:props]; + [self blendContent]; + [self blendHeader]; +} + +- (void)blendContent { + NSDictionary *contentModel = [self subModelOf:self.contentViewId]; + if (!contentModel) { + return; + } + NSString *viewId = contentModel[@"id"]; + NSString *type = contentModel[@"type"]; + NSDictionary *childProps = contentModel[@"props"]; + if (self.contentNode) { + if ([self.contentNode.viewId isEqualToString:viewId]) { + //skip + } else { + if (self.reusable && [type isEqualToString:self.contentNode.type]) { + [self.contentNode also:^(DoricViewNode *it) { + it.viewId = viewId; + [it blend:childProps]; + }]; + } else { + self.contentNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) { + it.viewId = viewId; + [it initWithSuperNode:self]; + [it blend:childProps]; + self.view.contentView = it.view; + }]; + } + } + } else { + self.contentNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) { + it.viewId = viewId; + [it initWithSuperNode:self]; + [it blend:childProps]; + self.view.contentView = it.view; + }]; + } +} + +- (void)blendHeader { + NSDictionary *headerModel = [self subModelOf:self.headerViewId]; + if (!headerModel) { + return; + } + NSString *viewId = headerModel[@"id"]; + NSString *type = headerModel[@"type"]; + NSDictionary *childProps = headerModel[@"props"]; + if (self.headerNode) { + if ([self.headerNode.viewId isEqualToString:viewId]) { + //skip + } else { + if (self.reusable && [type isEqualToString:self.headerNode.type]) { + [self.headerNode also:^(DoricViewNode *it) { + it.viewId = viewId; + [it blend:childProps]; + }]; + } else { + self.headerNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) { + it.viewId = viewId; + [it initWithSuperNode:self]; + [it blend:childProps]; + self.view.headerView = it.view; + }]; + } + } + } else { + self.headerNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) { + it.viewId = viewId; + [it initWithSuperNode:self]; + [it blend:childProps]; + self.view.headerView = it.view; + }]; + } +} + +- (void)blendSubNode:(NSDictionary *)subModel { + [[self subNodeWithViewId:subModel[@"id"]] blend:subModel[@"props"]]; +} + +- (void)startAnimation { + [self.headerNode callJSResponse:@"startAnimation", nil]; +} + +- (void)stopAnimation { + [self.headerNode callJSResponse:@"stopAnimation", nil]; +} + +- (void)setPullingDistance:(CGFloat)distance { + [self.headerNode callJSResponse:@"setPullingDistance", @(distance), nil]; +} + +- (void)setRefreshing:(NSNumber *)refreshable withPromise:(DoricPromise *)promise { + self.view.refreshing = [refreshable boolValue]; + [promise resolve:nil]; +} + +- (void)setRefreshable:(NSNumber *)refreshing withPromise:(DoricPromise *)promise { + self.view.refreshable = [refreshing boolValue]; + [promise resolve:nil]; +} + +- (NSNumber *)isRefreshing { + return @(self.view.refreshing); +} + +- (NSNumber *)isRefreshable { + return @(self.view.refreshable); +} +@end diff --git a/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h b/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h new file mode 100644 index 00000000..637b2378 --- /dev/null +++ b/Pod/Classes/Refresh/DoricSwipeRefreshLayout.h @@ -0,0 +1,22 @@ +// +// Created by pengfei.zhou on 2019/11/26. +// + +#import + +@protocol DoricSwipePullingDelegate +- (void)startAnimation; + +- (void)stopAnimation; + +- (void)setPullingDistance:(CGFloat)rotation; +@end + +@interface DoricSwipeRefreshLayout : UIScrollView +@property(nonatomic, strong) UIView *contentView; +@property(nonatomic, strong) UIView *headerView; +@property(nonatomic, assign) BOOL refreshable; +@property(nonatomic, assign) BOOL refreshing; +@property(nonatomic, strong) void (^onRefreshBlock)(void); +@property(nonatomic, weak) id swipePullingDelegate; +@end \ No newline at end of file diff --git a/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m b/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m new file mode 100644 index 00000000..25def1a5 --- /dev/null +++ b/Pod/Classes/Refresh/DoricSwipeRefreshLayout.m @@ -0,0 +1,159 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/26. +// + +#import "DoricSwipeRefreshLayout.h" +#import "UIView+Doric.h" +#import "DoricLayouts.h" +#import "Doric.h" + +@interface DoricSwipeRefreshLayout () + +@end + +@implementation DoricSwipeRefreshLayout + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.showsHorizontalScrollIndicator = NO; + self.showsVerticalScrollIndicator = NO; + self.alwaysBounceVertical = YES; + self.delegate = self; + if (@available(iOS 11, *)) { + self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + } + return self; +} + +- (instancetype)init { + if (self = [super init]) { + self.showsHorizontalScrollIndicator = NO; + self.showsVerticalScrollIndicator = NO; + self.alwaysBounceVertical = YES; + self.delegate = self; + if (@available(iOS 11, *)) { + self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + } + return self; +} + +- (CGSize)sizeThatFits:(CGSize)size { + if (self.contentView) { + return [self.contentView measureSize:size]; + } + return CGSizeZero; +} + +- (BOOL)requestFromSubview:(UIView *)subview { + if (subview == self.headerView) { + return NO; + } + return [super requestFromSubview:subview]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; +} + +- (void)layoutSelf:(CGSize)targetSize { + if (self.contentOffset.y != 0) { + return; + } + self.width = targetSize.width; + self.height = targetSize.height; + [self.headerView also:^(UIView *it) { + [it layoutSelf:[it measureSize:targetSize]]; + it.bottom = 0; + it.centerX = self.centerX; + }]; + [self.contentView also:^(UIView *it) { + [it layoutSelf:targetSize]; + }]; + self.contentSize = self.frame.size; +} + +- (void)setContentView:(UIView *)contentView { + if (_contentView) { + [_contentView removeFromSuperview]; + } + _contentView = contentView; + [self addSubview:contentView]; +} + +- (void)setHeaderView:(UIView *)headerView { + if (_headerView) { + [_headerView removeFromSuperview]; + } + _headerView = headerView; + [self addSubview:headerView]; + self.refreshable = YES; +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + if (-scrollView.contentOffset.y >= self.headerView.height) { + dispatch_async(dispatch_get_main_queue(), ^{ + self.refreshing = YES; + }); + } +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + if (scrollView.contentOffset.y <= 0) { + [self.swipePullingDelegate setPullingDistance:-scrollView.contentOffset.y]; + } +} + +- (void)setContentOffset:(CGPoint)contentOffset { + [super setContentOffset:contentOffset]; +} + +- (void)setRefreshing:(BOOL)refreshing { + if (_refreshing == refreshing) { + return; + } + if (refreshing) { + [self setContentOffset:CGPointMake(0, -self.headerView.height) animated:YES]; + self.scrollEnabled = NO; + if (self.onRefreshBlock) { + self.onRefreshBlock(); + } + } else { + [self setContentOffset:(CGPoint) {0, 0} animated:YES]; + self.scrollEnabled = YES; + } + _refreshing = refreshing; +} + +- (void)setRefreshable:(BOOL)refreshable { + self.scrollEnabled = refreshable; + if (refreshable) { + self.contentOffset = (CGPoint) {0, 0}; + self.contentInset = UIEdgeInsetsMake(0, 0, 0, 0); + } +} + +- (BOOL)refreshable { + return self.scrollEnabled; +} + +- (void)setContentSize:(CGSize)contentSize { + [super setContentSize:contentSize]; +} +@end diff --git a/Pod/Classes/Shader/DoricFlowLayoutItemNode.h b/Pod/Classes/Shader/DoricFlowLayoutItemNode.h new file mode 100644 index 00000000..ffea52f1 --- /dev/null +++ b/Pod/Classes/Shader/DoricFlowLayoutItemNode.h @@ -0,0 +1,25 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import +#import "DoricStackNode.h" + + +@interface DoricFlowLayoutItemNode : DoricStackNode +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricFlowLayoutItemNode.m b/Pod/Classes/Shader/DoricFlowLayoutItemNode.m new file mode 100644 index 00000000..417e87e0 --- /dev/null +++ b/Pod/Classes/Shader/DoricFlowLayoutItemNode.m @@ -0,0 +1,48 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import "DoricFlowLayoutItemNode.h" + +@interface DoricFlowLayoutItemView : DoricStackView +@end + +@implementation DoricFlowLayoutItemView +@end + +@interface DoricFlowLayoutItemNode () +@end + + +@implementation DoricFlowLayoutItemNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + self.reusable = YES; + } + return self; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + [super initWithSuperNode:superNode]; + self.reusable = YES; +} + +- (DoricStackView *)build { + return [DoricFlowLayoutItemView new]; +} +@end diff --git a/Pod/Classes/Shader/DoricFlowLayoutNode.h b/Pod/Classes/Shader/DoricFlowLayoutNode.h new file mode 100644 index 00000000..53cd9a39 --- /dev/null +++ b/Pod/Classes/Shader/DoricFlowLayoutNode.h @@ -0,0 +1,24 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import +#import "DoricSuperNode.h" + +@interface DoricFlowLayoutNode : DoricSuperNode +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricFlowLayoutNode.m b/Pod/Classes/Shader/DoricFlowLayoutNode.m new file mode 100644 index 00000000..211b89e2 --- /dev/null +++ b/Pod/Classes/Shader/DoricFlowLayoutNode.m @@ -0,0 +1,322 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/28. +// + +#import "DoricFlowLayoutNode.h" +#import "DoricFlowLayoutItemNode.h" +#import "DoricExtensions.h" +#import + +@protocol DoricFlowLayoutDelegate +- (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath; + +- (CGFloat)doricFlowLayoutColumnSpace; + +- (CGFloat)doricFlowLayoutRowSpace; + +- (NSInteger)doricFlowLayoutColumnCount; + +@end + +@interface DoricFlowLayout : UICollectionViewLayout +@property(nonatomic, readonly) NSInteger columnCount; +@property(nonatomic, readonly) CGFloat columnSpace; +@property(nonatomic, readonly) CGFloat rowSpace; +@property(nonatomic, strong) NSMutableDictionary *columnHeightInfo; +@property(nonatomic, weak) id delegate; +@end + +@implementation DoricFlowLayout +- (instancetype)init { + if (self = [super init]) { + _columnHeightInfo = [NSMutableDictionary new]; + } + return self; +} + +- (NSInteger)columnCount { + return self.delegate.doricFlowLayoutColumnCount ?: 2; +} + +- (CGFloat)columnSpace { + return self.delegate.doricFlowLayoutColumnSpace ?: 0; +} + +- (CGFloat)rowSpace { + return self.delegate.doricFlowLayoutRowSpace ?: 0; +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { + return YES; +} + +- (void)prepareLayout { + [super prepareLayout]; + for (int i = 0; i < self.columnCount; i++) { + self.columnHeightInfo[@(i)] = @(0); + } +} + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { + for (int i = 0; i < self.columnCount; i++) { + self.columnHeightInfo[@(i)] = @(0); + } + NSMutableArray *array = [NSMutableArray array]; + NSInteger count = [self.collectionView numberOfItemsInSection:0]; + for (int i = 0; i < count; i++) { + UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; + [array addObject:attrs]; + } + return array; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { + NSNumber *minYOfColumn = @(0); + for (NSNumber *key in self.columnHeightInfo.allKeys) { + if ([self.columnHeightInfo[key] floatValue] < [self.columnHeightInfo[minYOfColumn] floatValue]) { + minYOfColumn = key; + } + } + + CGFloat width = (self.collectionView.width - self.columnSpace * (self.columnCount - 1)) / self.columnCount; + CGFloat height = [self.delegate doricFlowLayoutItemHeightAtIndexPath:indexPath]; + CGFloat x = (width + self.columnSpace) * [minYOfColumn integerValue]; + CGFloat y = [self.columnHeightInfo[minYOfColumn] floatValue]; + if (y > 0) { + y += self.rowSpace; + } + self.columnHeightInfo[minYOfColumn] = @(y + height); + + UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + attrs.frame = CGRectMake(x, y, width, height); + return attrs; +} + +- (CGSize)collectionViewContentSize { + CGFloat width = self.collectionView.width; + CGFloat height = 0; + for (NSNumber *column in self.columnHeightInfo.allValues) { + height = MAX(height, [column floatValue]); + } + return CGSizeMake(width, height); +} +@end + +@interface DoricFlowLayoutViewCell : UICollectionViewCell +@property(nonatomic, strong) DoricFlowLayoutItemNode *viewNode; +@end + +@implementation DoricFlowLayoutViewCell +@end + +@interface DoricFlowLayoutView : UICollectionView +@end + +@implementation DoricFlowLayoutView +- (CGSize)sizeThatFits:(CGSize)size { + if (self.subviews.count > 0) { + CGFloat width = size.width; + CGFloat height = size.height; + for (UIView *child in self.subviews) { + CGSize childSize = [child measureSize:size]; + width = MAX(childSize.width, width); + height = MAX(childSize.height, height); + } + return CGSizeMake(width, height); + } + return size; +} + +- (void)layoutSelf:(CGSize)targetSize { + [super layoutSelf:targetSize]; + [self reloadData]; +} +@end + +@interface DoricFlowLayoutNode () +@property(nonatomic, strong) NSMutableDictionary *itemViewIds; +@property(nonatomic, strong) NSMutableDictionary *itemSizeInfo; +@property(nonatomic, assign) NSUInteger itemCount; +@property(nonatomic, assign) NSUInteger batchCount; +@property(nonatomic, assign) NSUInteger columnCount; +@property(nonatomic, assign) CGFloat columnSpace; +@property(nonatomic, assign) CGFloat rowSpace; +@end + +@implementation DoricFlowLayoutNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _itemViewIds = [NSMutableDictionary new]; + _itemSizeInfo = [NSMutableDictionary new]; + _batchCount = 15; + _columnCount = 2; + } + return self; +} + +- (UICollectionView *)build { + DoricFlowLayout *flowLayout = [[DoricFlowLayout alloc] init]; + flowLayout.delegate = self; + return [[[DoricFlowLayoutView alloc] initWithFrame:CGRectZero + collectionViewLayout:flowLayout] + also:^(UICollectionView *it) { + it.backgroundColor = [UIColor whiteColor]; + it.pagingEnabled = YES; + it.delegate = self; + it.dataSource = self; + [it registerClass:[DoricFlowLayoutViewCell class] forCellWithReuseIdentifier:@"doricCell"]; + }]; +} + +- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"columnSpace" isEqualToString:name]) { + self.columnSpace = [prop floatValue]; + [self.view.collectionViewLayout invalidateLayout]; + } else if ([@"rowSpace" isEqualToString:name]) { + self.rowSpace = [prop floatValue]; + [self.view.collectionViewLayout invalidateLayout]; + } else if ([@"columnCount" isEqualToString:name]) { + self.columnCount = [prop unsignedIntegerValue]; + [self.view reloadData]; + [self.view.collectionViewLayout invalidateLayout]; + } else if ([@"itemCount" isEqualToString:name]) { + self.itemCount = [prop unsignedIntegerValue]; + [self.view reloadData]; + } else if ([@"renderItem" isEqualToString:name]) { + [self.itemViewIds removeAllObjects]; + [self clearSubModel]; + [self.view reloadData]; + } else if ([@"batchCount" isEqualToString:name]) { + self.batchCount = [prop unsignedIntegerValue]; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (NSDictionary *)itemModelAt:(NSUInteger)position { + NSString *viewId = self.itemViewIds[@(position)]; + if (viewId && viewId.length > 0) { + return [self subModelOf:viewId]; + } else { + DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil]; + JSValue *models = [result waitUntilResult]; + NSArray *array = [models toArray]; + [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + NSString *thisViewId = obj[@"id"]; + [self setSubModel:obj in:thisViewId]; + NSUInteger pos = position + idx; + self.itemViewIds[@(pos)] = thisViewId; + }]; + return array[0]; + } +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + __block DoricViewNode *ret = nil; + [self.doricContext.driver ensureSyncInMainQueue:^{ + for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { + if ([collectionViewCell isKindOfClass:[DoricFlowLayoutViewCell class]]) { + DoricFlowLayoutItemNode *node = ((DoricFlowLayoutViewCell *) collectionViewCell).viewNode; + if ([viewId isEqualToString:node.viewId]) { + ret = node; + break; + } + } + } + }]; + return ret; +} + +- (void)blendSubNode:(NSDictionary *)subModel { + NSString *viewId = subModel[@"id"]; + DoricViewNode *viewNode = [self subNodeWithViewId:viewId]; + if (viewNode) { + [viewNode blend:subModel[@"props"]]; + } else { + NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy]; + [self recursiveMixin:subModel to:model]; + [self setSubModel:model in:viewId]; + } + [self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { + if ([viewId isEqualToString:obj]) { + *stop = YES; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0]; + [UIView performWithoutAnimation:^{ + [self.view reloadItemsAtIndexPaths:@[indexPath]]; + }]; + } + }]; +} + +- (void)callItem:(NSUInteger)position size:(CGSize)size { + NSValue *old = self.itemSizeInfo[@(position)]; + if (old && CGSizeEqualToSize([old CGSizeValue], size)) { + return; + } + self.itemSizeInfo[@(position)] = [NSValue valueWithCGSize:size]; + [self.view.collectionViewLayout invalidateLayout]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.itemCount; +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSDictionary *model = [self itemModelAt:position]; + NSDictionary *props = model[@"props"]; + DoricFlowLayoutViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; + if (!cell.viewNode) { + DoricFlowLayoutItemNode *itemNode = [[DoricFlowLayoutItemNode alloc] initWithContext:self.doricContext]; + [itemNode initWithSuperNode:self]; + cell.viewNode = itemNode; + [cell.contentView addSubview:itemNode.view]; + } + DoricFlowLayoutItemNode *node = cell.viewNode; + node.viewId = model[@"id"]; + [node blend:props]; + CGFloat width = (collectionView.width - (self.columnCount - 1) * self.columnSpace) / self.columnCount; + CGSize size = [node.view measureSize:CGSizeMake(width, collectionView.height)]; + [node.view layoutSelf:size]; + [self callItem:position size:size]; + return cell; +} + +- (CGFloat)doricFlowLayoutItemHeightAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSValue *value = self.itemSizeInfo[@(position)]; + if (value) { + return [value CGSizeValue].height; + } else { + return 100; + } +} + +- (CGFloat)doricFlowLayoutColumnSpace { + return self.columnSpace; +} + +- (CGFloat)doricFlowLayoutRowSpace { + return self.rowSpace; +} + +- (NSInteger)doricFlowLayoutColumnCount { + return self.columnCount; +} + +@end diff --git a/Pod/Classes/Shader/DoricGroupNode.h b/Pod/Classes/Shader/DoricGroupNode.h new file mode 100644 index 00000000..202eb7c2 --- /dev/null +++ b/Pod/Classes/Shader/DoricGroupNode.h @@ -0,0 +1,30 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricGroupNode.h +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricSuperNode.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricGroupNode : DoricSuperNode +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Shader/DoricGroupNode.m b/Pod/Classes/Shader/DoricGroupNode.m new file mode 100644 index 00000000..c35156fb --- /dev/null +++ b/Pod/Classes/Shader/DoricGroupNode.m @@ -0,0 +1,171 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricGroupNode.m +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import +#import "DoricGroupNode.h" + +@interface DoricGroupNode () +@property(nonatomic, copy) NSArray *childNodes; +@property(nonatomic, copy) NSArray *childViewIds; +@end + +@implementation DoricGroupNode + +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _childNodes = @[]; + _childViewIds = @[]; + } + return self; +} + +- (UIView *)build { + UIView *ret = [[UIView alloc] init]; + ret.clipsToBounds = YES; + return ret; +} + +- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"children" isEqualToString:name]) { + self.childViewIds = prop; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (void)blend:(NSDictionary *)props { + [super blend:props]; + [self configChildNodes]; +} + +- (DoricLayoutConfig *)generateDefaultLayoutParams { + DoricLayoutConfig *params = [[DoricLayoutConfig alloc] init]; + return params; +} + +- (void)configChildNodes { + NSMutableArray *childNodes = [self.childNodes mutableCopy]; + for (NSUInteger idx = 0; idx < self.childViewIds.count; idx++) { + NSString *viewId = self.childViewIds[idx]; + NSDictionary *model = [self subModelOf:viewId]; + NSString *type = model[@"type"]; + if (idx < self.childNodes.count) { + DoricViewNode *oldNode = childNodes[idx]; + if ([viewId isEqualToString:oldNode.viewId]) { + ///Same,skip + } else { + if (self.reusable) { + if ([oldNode.type isEqualToString:type]) { + ///Same type,can be reused + oldNode.viewId = viewId; + [oldNode blend:model[@"props"]]; + } else { + ///Replace this view + [childNodes removeObjectAtIndex:idx]; + [oldNode.view removeFromSuperview]; + DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type]; + if ([viewNode isKindOfClass:[DoricGroupNode class]]) { + ((DoricGroupNode *) viewNode).reusable = self.reusable; + } + viewNode.viewId = viewId; + [viewNode initWithSuperNode:self]; + [viewNode blend:model[@"props"]]; + [childNodes insertObject:viewNode atIndex:idx]; + [self.view insertSubview:viewNode.view atIndex:idx]; + } + } else { + ///Find in remain nodes + NSInteger position = -1; + for (NSUInteger start = idx + 1; start < childNodes.count; start++) { + DoricViewNode *node = childNodes[start]; + if ([viewId isEqualToString:node.viewId]) { + position = start; + break; + } + } + if (position >= 0) { + ///Found ,swap idx,position + DoricViewNode *reused = childNodes[(NSUInteger) position]; + [childNodes removeObjectAtIndex:(NSUInteger) position]; + [childNodes removeObjectAtIndex:idx]; + [childNodes insertObject:reused atIndex:idx]; + [childNodes insertObject:oldNode atIndex:(NSUInteger) position]; + + ///View swap index + [reused.view removeFromSuperview]; + [oldNode.view removeFromSuperview]; + [self.view insertSubview:reused.view atIndex:idx]; + [self.view insertSubview:oldNode.view atIndex:position]; + } else { + ///Not found,insert + DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type]; + viewNode.viewId = viewId; + [viewNode initWithSuperNode:self]; + [viewNode blend:model[@"props"]]; + [childNodes insertObject:viewNode atIndex:idx]; + [self.view insertSubview:viewNode.view atIndex:idx]; + } + } + } + } else { + /// Insert + DoricViewNode *viewNode = [DoricViewNode create:self.doricContext withType:type]; + if ([viewNode isKindOfClass:[DoricGroupNode class]]) { + ((DoricGroupNode *) viewNode).reusable = self.reusable; + } + viewNode.viewId = viewId; + [viewNode initWithSuperNode:self]; + [viewNode blend:model[@"props"]]; + [childNodes addObject:viewNode]; + [self.view addSubview:viewNode.view]; + } + } + + NSUInteger count = childNodes.count; + for (NSUInteger idx = self.childViewIds.count; idx < count; idx++) { + DoricViewNode *viewNode = childNodes.lastObject; + [childNodes removeLastObject]; + [viewNode.view removeFromSuperview]; + } + self.childNodes = [childNodes copy]; +} + +- (void)blendSubNode:(NSDictionary *)subModel { + NSString *viewId = subModel[@"id"]; + [self.childNodes enumerateObjectsUsingBlock:^(DoricViewNode *obj, NSUInteger idx, BOOL *stop) { + if ([viewId isEqualToString:obj.viewId]) { + [obj blend:subModel[@"props"]]; + *stop = YES; + } + }]; +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + for (DoricViewNode *viewNode in self.childNodes) { + if ([viewId isEqualToString:viewNode.viewId]) { + return viewNode; + } + } + return nil; +} + +@end diff --git a/Pod/Classes/Shader/DoricHLayoutNode.h b/Pod/Classes/Shader/DoricHLayoutNode.h new file mode 100644 index 00000000..edcbda0a --- /dev/null +++ b/Pod/Classes/Shader/DoricHLayoutNode.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricHLayoutNode.h +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricGroupNode.h" + +@interface DoricHLayoutNode : DoricGroupNode +@end diff --git a/Pod/Classes/Shader/DoricHLayoutNode.m b/Pod/Classes/Shader/DoricHLayoutNode.m new file mode 100644 index 00000000..ee66564a --- /dev/null +++ b/Pod/Classes/Shader/DoricHLayoutNode.m @@ -0,0 +1,40 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricHLayoutNode.m +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricHLayoutNode.h" +#import "DoricUtil.h" + +@implementation DoricHLayoutNode +- (DoricHLayoutView *)build { + return [DoricHLayoutView new]; +} + +- (void)blendView:(DoricHLayoutView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([name isEqualToString:@"gravity"]) { + view.gravity = (DoricGravity) [(NSNumber *) prop integerValue]; + } else if ([name isEqualToString:@"space"]) { + view.space = [(NSNumber *) prop floatValue]; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} +@end diff --git a/Pod/Classes/Shader/DoricImageNode.h b/Pod/Classes/Shader/DoricImageNode.h new file mode 100644 index 00000000..7f3db495 --- /dev/null +++ b/Pod/Classes/Shader/DoricImageNode.h @@ -0,0 +1,31 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricImageNode.h +// Doric +// +// Created by pengfei.zhou on 2019/8/6. +// + +#import "DoricViewNode.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricImageNode : DoricViewNode + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Shader/DoricImageNode.m b/Pod/Classes/Shader/DoricImageNode.m new file mode 100644 index 00000000..95cfd878 --- /dev/null +++ b/Pod/Classes/Shader/DoricImageNode.m @@ -0,0 +1,87 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricImageNode.m +// Doric +// +// Created by pengfei.zhou on 2019/8/6. +// + +#import "DoricImageNode.h" +#import "Doric.h" +#import "YYWebImage.h" + +@interface DoricImageNode () +@property(nonatomic, copy) NSString *loadCallbackId; +@end + +@implementation DoricImageNode + +- (UIImageView *)build { + return [[YYAnimatedImageView new] also:^(UIImageView *it) { + it.clipsToBounds = YES; + }]; +} + +- (void)blendView:(UIImageView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"imageUrl" isEqualToString:name]) { + __weak typeof(self) _self = self; + [view yy_setImageWithURL:[NSURL URLWithString:prop] placeholder:nil options:0 completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) { + __strong typeof(_self) self = _self; + if (error) { + if (self.loadCallbackId.length > 0) { + [self callJSResponse:self.loadCallbackId, nil]; + } + } else { + if (self.loadCallbackId.length > 0) { + [self callJSResponse:self.loadCallbackId, + @{@"width": @(image.size.width), @"height": @(image.size.height)}, + nil]; + } + [self requestLayout]; + } + }]; + } else if ([@"scaleType" isEqualToString:name]) { + switch ([prop integerValue]) { + case 1: { + self.view.contentMode = UIViewContentModeScaleAspectFit; + break; + } + case 2: { + self.view.contentMode = UIViewContentModeScaleAspectFill; + break; + } + default: { + self.view.contentMode = UIViewContentModeScaleToFill; + break; + } + } + } else if ([@"loadCallback" isEqualToString:name]) { + self.loadCallbackId = prop; + } else if ([@"imageBase64" isEqualToString:name]) { + NSString *base64 = prop; + if (YES == [base64 hasPrefix:@"data:image"]) { + base64 = [base64 componentsSeparatedByString:@","].lastObject; + } + NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64 + options:NSDataBase64DecodingIgnoreUnknownCharacters]; + UIImage *image = [UIImage imageWithData:imageData]; + self.view.image = image; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} +@end diff --git a/Pod/Classes/Shader/DoricLayouts.h b/Pod/Classes/Shader/DoricLayouts.h new file mode 100644 index 00000000..fcddee0c --- /dev/null +++ b/Pod/Classes/Shader/DoricLayouts.h @@ -0,0 +1,99 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/10/23. +// + +#import + +typedef UIEdgeInsets DoricMargin; + +DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom); + +typedef NS_ENUM(NSInteger, DoricLayoutSpec) { + DoricLayoutExact = 0, + DoricLayoutWrapContent = 1, + DoricLayoutAtMost = 2, +}; + +typedef NS_ENUM(NSInteger, DoricGravity) { + SPECIFIED = 1, + START = 1 << 1, + END = 1 << 2, + SHIFT_X = 0, + SHIFT_Y = 4, + LEFT = (START | SPECIFIED) << SHIFT_X, + RIGHT = (END | SPECIFIED) << SHIFT_X, + TOP = (START | SPECIFIED) << SHIFT_Y, + BOTTOM = (END | SPECIFIED) << SHIFT_Y, + CENTER_X = SPECIFIED << SHIFT_X, + CENTER_Y = SPECIFIED << SHIFT_Y, + CENTER = CENTER_X | CENTER_Y, +}; + +@interface DoricLayoutConfig : NSObject +@property(nonatomic, assign) DoricLayoutSpec widthSpec; +@property(nonatomic, assign) DoricLayoutSpec heightSpec; +@property(nonatomic) DoricMargin margin; +@property(nonatomic, assign) DoricGravity alignment; +@property(nonatomic, assign) NSUInteger weight; + +- (instancetype)init; + +- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height; + +- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height margin:(DoricMargin)margin; + +@end + + +@interface DoricLayoutContainer : UIView +@end + +@interface DoricStackView : DoricLayoutContainer +@end + +@interface DoricLinearView : DoricLayoutContainer +@property(nonatomic, assign) DoricGravity gravity; +@property(nonatomic, assign) CGFloat space; +@end + + +@interface DoricVLayoutView : DoricLinearView +@end + +@interface DoricHLayoutView : DoricLinearView +@end + +@interface UIView (DoricLayoutConfig) +@property(nonatomic, strong) DoricLayoutConfig *layoutConfig; +@end + +@interface UIView (DoricTag) +@property(nonatomic, copy) NSString *tagString; + +- (UIView *)viewWithTagString:(NSString *)tagString; +@end + +@interface UIView (DoricLayouts) +- (void)layoutSelf:(CGSize)targetSize; + +- (CGSize)measureSize:(CGSize)targetSize; + +- (void)doricLayoutSubviews; + +- (BOOL)requestFromSubview:(UIView *)subview; +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricLayouts.m b/Pod/Classes/Shader/DoricLayouts.m new file mode 100644 index 00000000..c1e0adfb --- /dev/null +++ b/Pod/Classes/Shader/DoricLayouts.m @@ -0,0 +1,457 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/10/23. +// + +#import "DoricLayouts.h" +#import +#import +#import "UIView+Doric.h" + +static const void *kLayoutConfig = &kLayoutConfig; + +@implementation UIView (DoricLayoutConfig) +@dynamic layoutConfig; + +- (void)setLayoutConfig:(DoricLayoutConfig *)layoutConfig { + objc_setAssociatedObject(self, kLayoutConfig, layoutConfig, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (DoricLayoutConfig *)layoutConfig { + return objc_getAssociatedObject(self, kLayoutConfig); +} + +@end + +static const void *kTagString = &kTagString; + +@implementation UIView (DoricTag) + +- (void)setTagString:(NSString *)tagString { + objc_setAssociatedObject(self, kTagString, tagString, OBJC_ASSOCIATION_COPY_NONATOMIC); + self.tag = [tagString hash]; +} + +- (NSString *)tagString { + return objc_getAssociatedObject(self, kTagString); +} + + +- (UIView *)viewWithTagString:(NSString *)tagString { + // notice the potential hash collision + return [self viewWithTag:[tagString hash]]; +} + +@end + + +@implementation UIView (DoricLayouts) +/** + * Measure self's size + * */ +- (CGSize)measureSize:(CGSize)targetSize { + CGFloat width = self.width; + CGFloat height = self.height; + + DoricLayoutConfig *config = self.layoutConfig; + if (!config) { + config = [DoricLayoutConfig new]; + } + if (config.widthSpec == DoricLayoutAtMost + || config.widthSpec == DoricLayoutWrapContent) { + width = targetSize.width - config.margin.left - config.margin.right; + } + if (config.heightSpec == DoricLayoutAtMost + || config.heightSpec == DoricLayoutWrapContent) { + height = targetSize.height - config.margin.top - config.margin.bottom; + } + + CGSize contentSize = [self sizeThatFits:CGSizeMake(width, height)]; + if (config.widthSpec == DoricLayoutWrapContent) { + width = contentSize.width; + } + if (config.heightSpec == DoricLayoutWrapContent) { + height = contentSize.height; + } + return CGSizeMake(width, height); +} + +/** + * layout self and subviews + * */ +- (void)layoutSelf:(CGSize)targetSize { + self.width = targetSize.width; + self.height = targetSize.height; + for (UIView *view in self.subviews) { + [view layoutSelf:[view measureSize:targetSize]]; + } +} + + +- (void)doricLayoutSubviews { + if ([self.superview requestFromSubview:self]) { + [self.superview doricLayoutSubviews]; + } else { + [self layoutSelf:CGSizeMake(self.width, self.height)]; + } +} + +- (BOOL)requestFromSubview:(UIView *)subview { + if (self.layoutConfig + && self.layoutConfig.widthSpec != DoricLayoutExact + && self.layoutConfig.heightSpec != DoricLayoutExact) { + return YES; + } + return NO; +} +@end + +DoricMargin DoricMarginMake(CGFloat left, CGFloat top, CGFloat right, CGFloat bottom) { + DoricMargin margin; + margin.left = left; + margin.top = top; + margin.right = right; + margin.bottom = bottom; + return margin; +} + +@implementation DoricLayoutConfig +- (instancetype)init { + if (self = [super init]) { + _widthSpec = DoricLayoutExact; + _heightSpec = DoricLayoutExact; + } + return self; +} + +- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height { + if (self = [super init]) { + _widthSpec = width; + _heightSpec = height; + } + return self; +} + +- (instancetype)initWithWidth:(DoricLayoutSpec)width height:(DoricLayoutSpec)height margin:(DoricMargin)margin { + if (self = [super init]) { + _widthSpec = width; + _heightSpec = height; + _margin = margin; + } + return self; +} +@end + + +@interface DoricLayoutContainer () +@property(nonatomic, assign) CGFloat contentWidth; +@property(nonatomic, assign) CGFloat contentHeight; +@property(nonatomic, assign) NSUInteger contentWeight; +@end + +@implementation DoricLayoutContainer +- (void)setNeedsLayout { + [super setNeedsLayout]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + [self doricLayoutSubviews]; +} +@end + + +@interface DoricStackView () +@end + +@implementation DoricStackView + +- (CGSize)sizeThatFits:(CGSize)size { + CGFloat contentWidth = 0; + CGFloat contentHeight = 0; + for (UIView *child in self.subviews) { + if (child.isHidden) { + continue; + } + DoricLayoutConfig *childConfig = child.layoutConfig; + if (!childConfig) { + childConfig = [DoricLayoutConfig new]; + } + CGSize childSize; + if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) { + childSize = [child measureSize:CGSizeMake(size.width, size.height)]; + } else { + childSize = child.bounds.size; + } + contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right); + contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom); + } + self.contentWidth = contentWidth; + self.contentHeight = contentHeight; + return CGSizeMake(contentWidth, contentHeight); +} + +- (void)layoutSelf:(CGSize)targetSize { + self.width = targetSize.width; + self.height = targetSize.height; + for (UIView *child in self.subviews) { + if (child.isHidden) { + continue; + } + if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) { + continue; + } + DoricLayoutConfig *childConfig = child.layoutConfig; + if (!childConfig) { + childConfig = [DoricLayoutConfig new]; + } + CGSize size = [child measureSize:CGSizeMake(targetSize.width, targetSize.height)]; + [child layoutSelf:size]; + DoricGravity gravity = childConfig.alignment; + if ((gravity & LEFT) == LEFT) { + child.left = 0; + } else if ((gravity & RIGHT) == RIGHT) { + child.right = targetSize.width; + } else if ((gravity & CENTER_X) == CENTER_X) { + child.centerX = targetSize.width / 2; + } else { + if (childConfig.margin.left) { + child.left = childConfig.margin.left; + } else if (childConfig.margin.right) { + child.right = targetSize.width - childConfig.margin.right; + } + } + if ((gravity & TOP) == TOP) { + child.top = 0; + } else if ((gravity & BOTTOM) == BOTTOM) { + child.bottom = targetSize.height; + } else if ((gravity & CENTER_Y) == CENTER_Y) { + child.centerY = targetSize.height / 2; + } else { + if (childConfig.margin.top) { + child.top = childConfig.margin.top; + } else if (childConfig.margin.bottom) { + child.bottom = targetSize.height - childConfig.margin.bottom; + } + } + } +} +@end + +@implementation DoricLinearView +@end + +@implementation DoricVLayoutView + +- (CGSize)sizeThatFits:(CGSize)size { + CGFloat contentWidth = 0; + CGFloat contentHeight = 0; + NSUInteger contentWeight = 0; + for (UIView *child in self.subviews) { + if (child.isHidden) { + continue; + } + DoricLayoutConfig *childConfig = child.layoutConfig; + if (!childConfig) { + childConfig = [DoricLayoutConfig new]; + } + CGSize childSize; + if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) { + childSize = [child measureSize:CGSizeMake(size.width, size.height - contentHeight)]; + } else { + childSize = child.bounds.size; + } + contentWidth = MAX(contentWidth, childSize.width + childConfig.margin.left + childConfig.margin.right); + contentHeight += childSize.height + self.space + childConfig.margin.top + childConfig.margin.bottom; + contentWeight += childConfig.weight; + } + contentHeight -= self.space; + self.contentWidth = contentWidth; + self.contentHeight = contentHeight; + self.contentWeight = contentWeight; + if (contentWeight) { + contentHeight = size.height; + } + return CGSizeMake(contentWidth, contentHeight); +} + +- (void)layoutSelf:(CGSize)targetSize { + self.width = targetSize.width; + self.height = targetSize.height; + CGFloat yStart = 0; + if ((self.gravity & TOP) == TOP) { + yStart = 0; + } else if ((self.gravity & BOTTOM) == BOTTOM) { + yStart = targetSize.height - self.contentHeight; + } else if ((self.gravity & CENTER_Y) == CENTER_Y) { + yStart = (targetSize.height - self.contentHeight) / 2; + } + CGFloat remain = targetSize.height - self.contentHeight; + for (UIView *child in self.subviews) { + if (child.isHidden) { + continue; + } + if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) { + continue; + } + DoricLayoutConfig *childConfig = child.layoutConfig; + if (!childConfig) { + childConfig = [DoricLayoutConfig new]; + } + + CGSize size = [child measureSize:CGSizeMake(targetSize.width, targetSize.height - yStart)]; + if (childConfig.weight) { + size.height += remain / self.contentWeight * childConfig.weight; + } + [child layoutSelf:size]; + DoricGravity gravity = childConfig.alignment | self.gravity; + if ((gravity & LEFT) == LEFT) { + child.left = 0; + } else if ((gravity & RIGHT) == RIGHT) { + child.right = self.width; + } else if ((gravity & CENTER_X) == CENTER_X) { + child.centerX = targetSize.width / 2; + } else { + if (childConfig.margin.left) { + child.left = childConfig.margin.left; + } else if (childConfig.margin.right) { + child.right = targetSize.width - childConfig.margin.right; + } + } + if (childConfig.margin.top) { + yStart += childConfig.margin.top; + } + child.top = yStart; + yStart = child.bottom + self.space; + if (childConfig.margin.bottom) { + yStart += childConfig.margin.bottom; + } + } +} +@end + +@implementation DoricHLayoutView +- (CGSize)sizeThatFits:(CGSize)size { + CGFloat contentWidth = 0; + CGFloat contentHeight = 0; + NSUInteger contentWeight = 0; + for (UIView *child in self.subviews) { + if (child.isHidden) { + continue; + } + DoricLayoutConfig *childConfig = child.layoutConfig; + if (!childConfig) { + childConfig = [DoricLayoutConfig new]; + } + CGSize childSize; + if (CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) { + childSize = [child measureSize:CGSizeMake(size.width - contentWidth, size.height)]; + } else { + childSize = child.bounds.size; + } + contentWidth += childSize.width + self.space + childConfig.margin.left + childConfig.margin.right; + contentHeight = MAX(contentHeight, childSize.height + childConfig.margin.top + childConfig.margin.bottom); + contentWeight += childConfig.weight; + } + contentWidth -= self.space; + self.contentWidth = contentWidth; + self.contentHeight = contentHeight; + self.contentWeight = contentWeight; + if (contentWeight) { + contentWidth = size.width; + } + return CGSizeMake(contentWidth, contentHeight); +} + +- (void)layoutSelf:(CGSize)targetSize { + self.width = targetSize.width; + self.height = targetSize.height; + CGFloat xStart = 0; + if (self.contentWeight) { + xStart = 0; + } else if ((self.gravity & LEFT) == LEFT) { + xStart = 0; + } else if ((self.gravity & RIGHT) == RIGHT) { + xStart = targetSize.width - self.contentWidth; + } else if ((self.gravity & CENTER_X) == CENTER_X) { + xStart = (targetSize.width - self.contentWidth) / 2; + } + CGFloat remain = targetSize.width - self.contentWidth; + for (UIView *child in self.subviews) { + if (child.isHidden) { + continue; + } + if (!CGAffineTransformEqualToTransform(child.transform, CGAffineTransformIdentity)) { + continue; + } + DoricLayoutConfig *childConfig = child.layoutConfig; + if (!childConfig) { + childConfig = [DoricLayoutConfig new]; + } + + CGSize size = [child measureSize:CGSizeMake(targetSize.width - xStart, targetSize.height)]; + if (childConfig.weight) { + size.width += remain / self.contentWeight * childConfig.weight; + } + + [child layoutSelf:size]; + + DoricGravity gravity = childConfig.alignment | self.gravity; + if ((gravity & TOP) == TOP) { + child.top = 0; + } else if ((gravity & BOTTOM) == BOTTOM) { + child.bottom = targetSize.height; + } else if ((gravity & CENTER_Y) == CENTER_Y) { + child.centerY = targetSize.height / 2; + } else { + if (childConfig.margin.top) { + child.top = childConfig.margin.top; + } else if (childConfig.margin.bottom) { + child.bottom = targetSize.height - childConfig.margin.bottom; + } + } + + if (childConfig.margin.left) { + xStart += childConfig.margin.left; + } + child.left = xStart; + xStart = child.right + self.space; + if (childConfig.margin.right) { + xStart += childConfig.margin.right; + } + } +} +@end + + +DoricVLayoutView *vLayout(NSArray <__kindof UIView *> *views) { + DoricVLayoutView *layout = [[DoricVLayoutView alloc] initWithFrame:CGRectZero]; + for (__kindof UIView *uiView in views) { + [layout addSubview:uiView]; + } + layout.layoutConfig = [[DoricLayoutConfig alloc] initWithWidth:DoricLayoutWrapContent height:DoricLayoutWrapContent]; + return layout; +} + +DoricHLayoutView *hLayout(NSArray <__kindof UIView *> *views) { + DoricHLayoutView *layout = [[DoricHLayoutView alloc] initWithFrame:CGRectZero]; + for (__kindof UIView *uiView in views) { + [layout addSubview:uiView]; + } + layout.layoutConfig = [[DoricLayoutConfig alloc] initWithWidth:DoricLayoutWrapContent height:DoricLayoutWrapContent]; + return layout; +} diff --git a/Pod/Classes/Shader/DoricListItemNode.h b/Pod/Classes/Shader/DoricListItemNode.h new file mode 100644 index 00000000..a5f5c720 --- /dev/null +++ b/Pod/Classes/Shader/DoricListItemNode.h @@ -0,0 +1,25 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import + +#import "DoricStackNode.h" + +@interface DoricListItemNode : DoricStackNode +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricListItemNode.m b/Pod/Classes/Shader/DoricListItemNode.m new file mode 100644 index 00000000..f08abf9d --- /dev/null +++ b/Pod/Classes/Shader/DoricListItemNode.m @@ -0,0 +1,49 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import "DoricListItemNode.h" +#import "DoricExtensions.h" + +@interface DoricListItemNode () +@end + +@interface DoricListItemView : DoricStackView +@end + +@implementation DoricListItemView +@end + + +@implementation DoricListItemNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + self.reusable = YES; + } + return self; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + [super initWithSuperNode:superNode]; + self.reusable = YES; +} + +- (DoricStackView *)build { + return [DoricListItemView new]; +} +@end diff --git a/Pod/Classes/Shader/DoricListNode.h b/Pod/Classes/Shader/DoricListNode.h new file mode 100644 index 00000000..97244086 --- /dev/null +++ b/Pod/Classes/Shader/DoricListNode.h @@ -0,0 +1,24 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import +#import "DoricSuperNode.h" + +@interface DoricListNode : DoricSuperNode +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricListNode.m b/Pod/Classes/Shader/DoricListNode.m new file mode 100644 index 00000000..7791bc71 --- /dev/null +++ b/Pod/Classes/Shader/DoricListNode.m @@ -0,0 +1,216 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import +#import "DoricListNode.h" +#import "DoricExtensions.h" +#import "DoricListItemNode.h" +#import "DoricLayouts.h" +#import "DoricRefreshableNode.h" + +@interface DoricTableViewCell : UITableViewCell +@property(nonatomic, strong) DoricListItemNode *doricListItemNode; +@end + +@implementation DoricTableViewCell +@end + +@interface DoricTableView : UITableView +@end + +@implementation DoricTableView +- (CGSize)sizeThatFits:(CGSize)size { + if (self.subviews.count > 0) { + CGFloat width = size.width; + CGFloat height = 0; + + for (UIView *child in self.subviews) { + CGSize childSize = [child measureSize:size]; + width = MAX(childSize.width, width); + height += childSize.height; + } + return CGSizeMake(width, MAX(height, size.height)); + } + return size; +} + +- (void)layoutSelf:(CGSize)targetSize { + [super layoutSelf:targetSize]; + [self reloadData]; +} +@end + + +@interface DoricListNode () +@property(nonatomic, strong) NSMutableDictionary *itemViewIds; +@property(nonatomic, strong) NSMutableDictionary *itemHeights; +@property(nonatomic, assign) NSUInteger itemCount; +@property(nonatomic, assign) NSUInteger batchCount; +@end + +@implementation DoricListNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _itemViewIds = [NSMutableDictionary new]; + _itemHeights = [NSMutableDictionary new]; + _batchCount = 15; + } + return self; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + [super initWithSuperNode:superNode]; + if ([superNode isKindOfClass:[DoricRefreshableNode class]]) { + self.view.bounces = NO; + } +} + +- (UITableView *)build { + return [[DoricTableView new] also:^(UITableView *it) { + it.dataSource = self; + it.delegate = self; + it.separatorStyle = UITableViewCellSeparatorStyleNone; + }]; +} + +- (void)blendView:(UITableView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"itemCount" isEqualToString:name]) { + self.itemCount = [prop unsignedIntegerValue]; + [self.view reloadData]; + } else if ([@"renderItem" isEqualToString:name]) { + [self.itemViewIds removeAllObjects]; + [self clearSubModel]; + [self.view reloadData]; + } else if ([@"batchCount" isEqualToString:name]) { + self.batchCount = [prop unsignedIntegerValue]; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (void)blend:(NSDictionary *)props { + [super blend:props]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.itemCount; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSDictionary *model = [self itemModelAt:position]; + NSDictionary *props = model[@"props"]; + NSString *reuseId = props[@"identifier"]; + + DoricTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId ?: @"doriccell"]; + if (!cell) { + cell = [[DoricTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId ?: @"doriccell"]; + DoricListItemNode *listItemNode = [[DoricListItemNode alloc] initWithContext:self.doricContext]; + [listItemNode initWithSuperNode:self]; + cell.doricListItemNode = listItemNode; + [cell.contentView addSubview:listItemNode.view]; + } + DoricListItemNode *node = cell.doricListItemNode; + node.viewId = model[@"id"]; + [node blend:props]; + CGSize size = [node.view measureSize:CGSizeMake(tableView.width, tableView.height)]; + [node.view layoutSelf:size]; + [self callItem:position height:size.height]; + return cell; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSNumber *heightNumber = self.itemHeights[@(position)]; + if (heightNumber) { + return [heightNumber floatValue]; + } else { + return 44.f; + } + +} + +- (NSDictionary *)itemModelAt:(NSUInteger)position { + NSString *viewId = self.itemViewIds[@(position)]; + if (viewId && viewId.length > 0) { + return [self subModelOf:viewId]; + } else { + DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil]; + JSValue *models = [result waitUntilResult]; + NSArray *array = [models toArray]; + [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + NSString *thisViewId = obj[@"id"]; + [self setSubModel:obj in:thisViewId]; + NSUInteger pos = position + idx; + self.itemViewIds[@(pos)] = thisViewId; + }]; + return array[0]; + } +} + +- (void)blendSubNode:(NSDictionary *)subModel { + NSString *viewId = subModel[@"id"]; + DoricViewNode *viewNode = [self subNodeWithViewId:viewId]; + if (viewNode) { + [viewNode blend:subModel[@"props"]]; + } else { + NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy]; + [self recursiveMixin:subModel to:model]; + [self setSubModel:model in:viewId]; + } + [self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { + if ([viewId isEqualToString:obj]) { + *stop = YES; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0]; + [UIView performWithoutAnimation:^{ + [self.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + }]; + } + }]; +} + +- (void)callItem:(NSUInteger)position height:(CGFloat)height { + NSNumber *old = self.itemHeights[@(position)]; + if (old && old.floatValue == height) { + return; + } + self.itemHeights[@(position)] = @(height); + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:position inSection:0]; + [UIView performWithoutAnimation:^{ + [self.view reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + }]; +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + __block DoricViewNode *ret = nil; + [self.doricContext.driver ensureSyncInMainQueue:^{ + for (UITableViewCell *tableViewCell in self.view.visibleCells) { + if ([tableViewCell isKindOfClass:[DoricTableViewCell class]]) { + DoricListItemNode *node = ((DoricTableViewCell *) tableViewCell).doricListItemNode; + if ([viewId isEqualToString:node.viewId]) { + ret = node; + break; + } + } + } + }]; + return ret; +} + +@end diff --git a/Pod/Classes/Shader/DoricRootNode.h b/Pod/Classes/Shader/DoricRootNode.h new file mode 100644 index 00000000..32abe898 --- /dev/null +++ b/Pod/Classes/Shader/DoricRootNode.h @@ -0,0 +1,33 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricRootNode.h +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricStackNode.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricRootNode : DoricStackNode + +- (void)setupRootView:(DoricStackView *)view; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Shader/DoricRootNode.m b/Pod/Classes/Shader/DoricRootNode.m new file mode 100644 index 00000000..669c6bbb --- /dev/null +++ b/Pod/Classes/Shader/DoricRootNode.m @@ -0,0 +1,33 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricRootNode.m +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricRootNode.h" + +@implementation DoricRootNode +- (void)setupRootView:(DoricStackView *)view { + self.view = view; +} + +- (void)requestLayout { + [self.view setNeedsLayout]; +} +@end diff --git a/Pod/Classes/Shader/DoricScrollerNode.h b/Pod/Classes/Shader/DoricScrollerNode.h new file mode 100644 index 00000000..ec8a2253 --- /dev/null +++ b/Pod/Classes/Shader/DoricScrollerNode.h @@ -0,0 +1,30 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricScrollerNode.h +// Doric +// +// Created by pengfei.zhou on 2019/11/19. +// +#import +#import "DoricSuperNode.h" + +@interface DoricScrollView : UIScrollView +@property(nonatomic, strong) UIView *contentView; +@end + +@interface DoricScrollerNode : DoricSuperNode +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricScrollerNode.m b/Pod/Classes/Shader/DoricScrollerNode.m new file mode 100644 index 00000000..ef7d699a --- /dev/null +++ b/Pod/Classes/Shader/DoricScrollerNode.m @@ -0,0 +1,122 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricScrollerNode.m +// Doric +// +// Created by pengfei.zhou on 2019/11/19. +// +#import "DoricScrollerNode.h" +#import "DoricExtensions.h" +#import "DoricRefreshableNode.h" + +@implementation DoricScrollView + +- (void)setContentView:(UIView *)contentView { + if (_contentView) { + [_contentView removeFromSuperview]; + } + _contentView = contentView; + [self addSubview:contentView]; +} + +- (CGSize)sizeThatFits:(CGSize)size { + if (self.contentView) { + return [self.contentView sizeThatFits:size]; + } + return CGSizeZero; +} + +- (void)layoutSelf:(CGSize)targetSize { + [super layoutSelf:targetSize]; + [self setContentSize:self.contentView.frame.size]; +} + +@end + +@interface DoricScrollerNode () +@property(nonatomic, strong) DoricViewNode *childNode; +@property(nonatomic, copy) NSString *childViewId; +@end + +@implementation DoricScrollerNode +- (DoricScrollView *)build { + return [DoricScrollView new]; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + [super initWithSuperNode:superNode]; + if ([superNode isKindOfClass:[DoricRefreshableNode class]]) { + self.view.bounces = NO; + } +} + +- (void)blend:(NSDictionary *)props { + [super blend:props]; + NSDictionary *childModel = [self subModelOf:self.childViewId]; + if (!childModel) { + return; + } + NSString *viewId = childModel[@"id"]; + NSString *type = childModel[@"type"]; + NSDictionary *childProps = childModel[@"props"]; + if (self.childNode) { + if ([self.childNode.viewId isEqualToString:viewId]) { + //skip + } else { + if (self.reusable && [type isEqualToString:self.childNode.type]) { + [self.childNode also:^(DoricViewNode *it) { + it.viewId = viewId; + [it blend:childProps]; + }]; + } else { + self.childNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) { + it.viewId = viewId; + [it initWithSuperNode:self]; + [it blend:childProps]; + self.view.contentView = it.view; + }]; + } + } + } else { + self.childNode = [[DoricViewNode create:self.doricContext withType:type] also:^(DoricViewNode *it) { + it.viewId = viewId; + [it initWithSuperNode:self]; + [it blend:childProps]; + self.view.contentView = it.view; + }]; + } +} + +- (void)blendView:(DoricScrollView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"content" isEqualToString:name]) { + self.childViewId = prop; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (void)blendSubNode:(NSDictionary *)subModel { + [self.childNode blend:subModel[@"props"]]; +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + if ([viewId isEqualToString:self.childViewId]) { + return self.childNode; + } + return nil; +} +@end diff --git a/Pod/Classes/Shader/DoricSlideItemNode.h b/Pod/Classes/Shader/DoricSlideItemNode.h new file mode 100644 index 00000000..195c173c --- /dev/null +++ b/Pod/Classes/Shader/DoricSlideItemNode.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricSlideItemNode.h +// Doric +// +// Created by pengfei.zhou on 2019/11/19. +// +#import +#import "DoricStackNode.h" + +@interface DoricSlideItemNode : DoricStackNode +@end diff --git a/Pod/Classes/Shader/DoricSlideItemNode.m b/Pod/Classes/Shader/DoricSlideItemNode.m new file mode 100644 index 00000000..9e254323 --- /dev/null +++ b/Pod/Classes/Shader/DoricSlideItemNode.m @@ -0,0 +1,51 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricSlideItemNode.m +// Doric +// +// Created by pengfei.zhou on 2019/11/19. +// +#import "DoricSlideItemNode.h" + +@interface DoricSlideItemView : DoricStackView +@end + +@implementation DoricSlideItemView +- (void)layoutSubviews { + [super layoutSubviews]; +} +@end + +@implementation DoricSlideItemNode + +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + self.reusable = YES; + } + return self; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + [super initWithSuperNode:superNode]; + self.reusable = YES; + self.view.clipsToBounds = YES; +} + +- (DoricStackView *)build { + return [DoricSlideItemView new]; +} +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricSliderNode.h b/Pod/Classes/Shader/DoricSliderNode.h new file mode 100644 index 00000000..61da6935 --- /dev/null +++ b/Pod/Classes/Shader/DoricSliderNode.h @@ -0,0 +1,27 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricSliderNode.h +// Doric +// +// Created by pengfei.zhou on 2019/11/19. +// +#import + +#import "DoricSuperNode.h" + +@interface DoricSliderNode : DoricSuperNode +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricSliderNode.m b/Pod/Classes/Shader/DoricSliderNode.m new file mode 100644 index 00000000..899b88f8 --- /dev/null +++ b/Pod/Classes/Shader/DoricSliderNode.m @@ -0,0 +1,201 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricSliderNode.m +// Doric +// +// Created by pengfei.zhou on 2019/11/19. +// +#import +#import "DoricSliderNode.h" +#import "Doric.h" +#import "DoricSlideItemNode.h" + +@interface DoricSliderViewCell : UICollectionViewCell +@property(nonatomic, strong) DoricSlideItemNode *doricSlideItemNode; +@end + +@implementation DoricSliderViewCell +@end + +@interface DoricSliderNode () +@property(nonatomic, strong) NSMutableDictionary *itemViewIds; +@property(nonatomic, assign) NSUInteger itemCount; +@property(nonatomic, assign) NSUInteger batchCount; +@end + +@interface DoricSliderView : UICollectionView +@end + +@implementation DoricSliderView +- (CGSize)sizeThatFits:(CGSize)size { + if (self.subviews.count > 0) { + CGFloat width = size.width; + CGFloat height = size.height; + for (UIView *child in self.subviews) { + CGSize childSize = [child measureSize:size]; + width = MAX(childSize.width, width); + height = MAX(childSize.height, height); + } + return CGSizeMake(width, height); + } + return size; +} + +- (void)layoutSelf:(CGSize)targetSize { + [super layoutSelf:targetSize]; + [self reloadData]; +} +@end + +@implementation DoricSliderNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _itemViewIds = [NSMutableDictionary new]; + _batchCount = 15; + } + return self; +} + +- (UICollectionView *)build { + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal]; + + return [[[DoricSliderView alloc] initWithFrame:CGRectZero + collectionViewLayout:flowLayout] + also:^(UICollectionView *it) { + it.backgroundColor = [UIColor whiteColor]; + it.pagingEnabled = YES; + it.delegate = self; + it.dataSource = self; + [it registerClass:[DoricSliderViewCell class] forCellWithReuseIdentifier:@"doricCell"]; + }]; +} + +- (void)blendView:(UICollectionView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"itemCount" isEqualToString:name]) { + self.itemCount = [prop unsignedIntegerValue]; + [self.view reloadData]; + } else if ([@"renderPage" isEqualToString:name]) { + [self.itemViewIds removeAllObjects]; + [self clearSubModel]; + [self.view reloadData]; + } else if ([@"batchCount" isEqualToString:name]) { + self.batchCount = [prop unsignedIntegerValue]; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.itemCount; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + return CGSizeMake(self.view.width, self.view.height); +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { + return 0; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { + return 0; +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + NSUInteger position = (NSUInteger) indexPath.row; + NSDictionary *model = [self itemModelAt:position]; + NSDictionary *props = model[@"props"]; + DoricSliderViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"doricCell" forIndexPath:indexPath]; + if (!cell.doricSlideItemNode) { + DoricSlideItemNode *slideItemNode = [[DoricSlideItemNode alloc] initWithContext:self.doricContext]; + [slideItemNode initWithSuperNode:self]; + cell.doricSlideItemNode = slideItemNode; + [cell.contentView addSubview:slideItemNode.view]; + } + DoricSlideItemNode *node = cell.doricSlideItemNode; + node.viewId = model[@"id"]; + [node blend:props]; + CGSize size = [node.view measureSize:CGSizeMake(collectionView.width, collectionView.height)]; + [node.view layoutSelf:size]; + return cell; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath { + return NO; +} + +- (NSDictionary *)itemModelAt:(NSUInteger)position { + NSString *viewId = self.itemViewIds[@(position)]; + if (viewId && viewId.length > 0) { + return [self subModelOf:viewId]; + } else { + DoricAsyncResult *result = [self callJSResponse:@"renderBunchedItems", @(position), @(self.batchCount), nil]; + JSValue *models = [result waitUntilResult]; + NSArray *array = [models toArray]; + [array enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + NSString *thisViewId = obj[@"id"]; + [self setSubModel:obj in:thisViewId]; + NSUInteger pos = position + idx; + self.itemViewIds[@(pos)] = thisViewId; + }]; + return array[0]; + } +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + __block DoricViewNode *ret = nil; + [self.doricContext.driver ensureSyncInMainQueue:^{ + for (UICollectionViewCell *collectionViewCell in self.view.visibleCells) { + if ([collectionViewCell isKindOfClass:[DoricSliderViewCell class]]) { + DoricSlideItemNode *node = ((DoricSliderViewCell *) collectionViewCell).doricSlideItemNode; + if ([viewId isEqualToString:node.viewId]) { + ret = node; + break; + } + } + } + }]; + return ret; +} + +- (void)blendSubNode:(NSDictionary *)subModel { + NSString *viewId = subModel[@"id"]; + DoricViewNode *viewNode = [self subNodeWithViewId:viewId]; + if (viewNode) { + [viewNode blend:subModel[@"props"]]; + } else { + NSMutableDictionary *model = [[self subModelOf:viewId] mutableCopy]; + [self recursiveMixin:subModel to:model]; + [self setSubModel:model in:viewId]; + } + [self.itemViewIds enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { + if ([viewId isEqualToString:obj]) { + *stop = YES; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[key integerValue] inSection:0]; + [UIView performWithoutAnimation:^{ + [self.view reloadItemsAtIndexPaths:@[indexPath]]; + }]; + } + }]; +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + NSUInteger pageIndex = (NSUInteger) (scrollView.contentOffset.x / scrollView.width); + scrollView.contentOffset = CGPointMake(pageIndex * scrollView.width, scrollView.contentOffset.y); +} +@end diff --git a/Pod/Classes/Shader/DoricStackNode.h b/Pod/Classes/Shader/DoricStackNode.h new file mode 100644 index 00000000..947d9c75 --- /dev/null +++ b/Pod/Classes/Shader/DoricStackNode.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricStackNode.h +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricGroupNode.h" + +@interface DoricStackNode : DoricGroupNode +@end diff --git a/Pod/Classes/Shader/DoricStackNode.m b/Pod/Classes/Shader/DoricStackNode.m new file mode 100644 index 00000000..7b0ea338 --- /dev/null +++ b/Pod/Classes/Shader/DoricStackNode.m @@ -0,0 +1,34 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricStackNode.m +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricStackNode.h" + +@implementation DoricStackNode + +- (DoricStackView *)build { + return [DoricStackView new]; +} + +- (void)blendView:(DoricStackView *)view forPropName:(NSString *)name propValue:(id)prop { + [super blendView:view forPropName:name propValue:prop]; +} +@end diff --git a/Pod/Classes/Shader/DoricSuperNode.h b/Pod/Classes/Shader/DoricSuperNode.h new file mode 100644 index 00000000..ec456c99 --- /dev/null +++ b/Pod/Classes/Shader/DoricSuperNode.h @@ -0,0 +1,41 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import +#import "DoricViewNode.h" + +@interface DoricSuperNode : DoricViewNode +@property(nonatomic, assign) BOOL reusable; + +- (DoricLayoutConfig *)generateDefaultLayoutParams; + +- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig; + +- (void)blendSubNode:(NSDictionary *)subModel; + +- (NSDictionary *)subModelOf:(NSString *)viewId; + +- (void)setSubModel:(NSDictionary *)model in:(NSString *)viewId; + +- (void)clearSubModel; + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId; + +- (void)recursiveMixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel; +@end \ No newline at end of file diff --git a/Pod/Classes/Shader/DoricSuperNode.m b/Pod/Classes/Shader/DoricSuperNode.m new file mode 100644 index 00000000..232722b4 --- /dev/null +++ b/Pod/Classes/Shader/DoricSuperNode.m @@ -0,0 +1,160 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/11/15. +// + +#import "DoricSuperNode.h" +#import "DoricExtensions.h" + +@interface DoricSuperNode () +@property(nonatomic, strong) NSMutableDictionary *subNodes; +@end + +@implementation DoricSuperNode +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _subNodes = [NSMutableDictionary new]; + } + return self; +} + +- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([@"subviews" isEqualToString:name]) { + NSArray *subviews = prop; + for (NSMutableDictionary *subModel in subviews) { + [self mixinSubNode:subModel]; + [self blendSubNode:subModel]; + } + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (void)mixinSubNode:(NSMutableDictionary *)dictionary { + NSString *viewId = dictionary[@"id"]; + NSMutableDictionary *oldModel = self.subNodes[viewId]; + if (oldModel) { + [self mixin:dictionary to:oldModel]; + } else { + self.subNodes[viewId] = dictionary; + } +} + +- (void)mixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel { + NSDictionary *srcProp = srcModel[@"props"]; + NSMutableDictionary *targetProp = targetModel[@"props"]; + [srcProp enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + if (![@"subviews" isEqualToString:key]) { + targetProp[key] = obj; + } + }]; + targetModel[@"props"] = targetProp; +} + +- (void)recursiveMixin:(NSDictionary *)srcModel to:(NSMutableDictionary *)targetModel { + NSDictionary *srcProp = srcModel[@"props"]; + NSMutableDictionary *targetProp = targetModel[@"props"]; + NSMutableArray *targetOri = targetProp[@"subviews"]; + + [srcProp enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { + if ([@"subviews" isEqualToString:key]) { + NSArray *subviews = obj; + if (subviews) { + for (NSDictionary *subview in subviews) { + NSString *viewId = subview[@"id"]; + [targetOri enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + if ([viewId isEqualToString:obj[@"id"]]) { + NSMutableDictionary *mutableDictionary = [obj mutableCopy]; + [self recursiveMixin:subview to:mutableDictionary]; + targetOri[idx] = [mutableDictionary copy]; + *stop = YES; + } + }]; + } + } + } else { + targetProp[key] = obj; + } + }]; +} + +- (void)blendSubNode:(DoricViewNode *)subNode layoutConfig:(NSDictionary *)layoutConfig { + DoricLayoutConfig *params = subNode.layoutConfig; + + [layoutConfig[@"widthSpec"] also:^(NSNumber *it) { + if (it) { + params.widthSpec = (DoricLayoutSpec) [it integerValue]; + } + }]; + + [layoutConfig[@"heightSpec"] also:^(NSNumber *it) { + if (it) { + params.heightSpec = (DoricLayoutSpec) [it integerValue]; + } + }]; + + NSDictionary *margin = layoutConfig[@"margin"]; + if (margin) { + params.margin = DoricMarginMake( + [(NSNumber *) margin[@"left"] floatValue], + [(NSNumber *) margin[@"top"] floatValue], + [(NSNumber *) margin[@"right"] floatValue], + [(NSNumber *) margin[@"bottom"] floatValue]); + } + + NSNumber *alignment = layoutConfig[@"alignment"]; + if (alignment) { + params.alignment = (DoricGravity) [alignment integerValue]; + } + NSNumber *weight = layoutConfig[@"weight"]; + if (weight) { + params.weight = (DoricGravity) [weight integerValue]; + } +} + +- (void)blendSubNode:(NSDictionary *)subModel { + NSAssert(NO, @"Should override class:%@ ,method:%@.", NSStringFromClass([self class]), + NSStringFromSelector(_cmd)); +} + +- (DoricLayoutConfig *)generateDefaultLayoutParams { + DoricLayoutConfig *params = [[DoricLayoutConfig alloc] init]; + return params; +} + + +- (NSDictionary *)subModelOf:(NSString *)viewId { + return self.subNodes[viewId]; +} + +- (void)setSubModel:(NSDictionary *)model in:(NSString *)viewId { + self.subNodes[viewId] = [model mutableCopy]; +} + +- (void)clearSubModel { + [self.subNodes removeAllObjects]; +} + +- (DoricViewNode *)subNodeWithViewId:(NSString *)viewId { + NSAssert(NO, @"Should override class:%@ ,method:%@.", NSStringFromClass([self class]), + NSStringFromSelector(_cmd)); + return nil; +} +- (void)requestLayout { + [self.view setNeedsLayout]; +} +@end diff --git a/Pod/Classes/Shader/DoricTextNode.h b/Pod/Classes/Shader/DoricTextNode.h new file mode 100644 index 00000000..81650835 --- /dev/null +++ b/Pod/Classes/Shader/DoricTextNode.h @@ -0,0 +1,31 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricTextNode.h +// Doric +// +// Created by pengfei.zhou on 2019/7/31. +// + +#import "DoricViewNode.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface DoricTextNode : DoricViewNode + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Shader/DoricTextNode.m b/Pod/Classes/Shader/DoricTextNode.m new file mode 100644 index 00000000..9b54525c --- /dev/null +++ b/Pod/Classes/Shader/DoricTextNode.m @@ -0,0 +1,60 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricTextNode.m +// Doric +// +// Created by pengfei.zhou on 2019/7/31. +// + +#import "DoricTextNode.h" +#import "DoricUtil.h" +#import "DoricGroupNode.h" +#import "Doric.h" + +@implementation DoricTextNode +- (UILabel *)build { + return [[[UILabel alloc] init] also:^(UILabel *it) { + it.textAlignment = NSTextAlignmentCenter; + }]; +} + +- (void)blendView:(UILabel *)view forPropName:(NSString *)name propValue:(id)prop { + if ([name isEqualToString:@"text"]) { + view.text = prop; + } else if ([name isEqualToString:@"textSize"]) { + view.font = [UIFont systemFontOfSize:[(NSNumber *) prop floatValue]]; + } else if ([name isEqualToString:@"textColor"]) { + view.textColor = DoricColor(prop); + } else if ([name isEqualToString:@"textAlignment"]) { + DoricGravity gravity = (DoricGravity) [(NSNumber *) prop integerValue]; + NSTextAlignment alignment = NSTextAlignmentCenter; + if ((gravity & LEFT) == LEFT) { + alignment = NSTextAlignmentLeft; + } else if ((gravity & RIGHT) == RIGHT) { + alignment = NSTextAlignmentRight; + } + view.textAlignment = alignment; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +- (void)blend:(NSDictionary *)props { + [super blend:props]; + [self.view.superview setNeedsLayout]; +} +@end diff --git a/Pod/Classes/Shader/DoricVLayoutNode.h b/Pod/Classes/Shader/DoricVLayoutNode.h new file mode 100644 index 00000000..853f3f7b --- /dev/null +++ b/Pod/Classes/Shader/DoricVLayoutNode.h @@ -0,0 +1,26 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricVLayoutNode.h +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricGroupNode.h" + +@interface DoricVLayoutNode : DoricGroupNode +@end diff --git a/Pod/Classes/Shader/DoricVLayoutNode.m b/Pod/Classes/Shader/DoricVLayoutNode.m new file mode 100644 index 00000000..d7b5d94d --- /dev/null +++ b/Pod/Classes/Shader/DoricVLayoutNode.m @@ -0,0 +1,41 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricVLayoutNode.m +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricVLayoutNode.h" + +@implementation DoricVLayoutNode + +- (DoricVLayoutView *)build { + return [DoricVLayoutView new]; +} + +- (void)blendView:(DoricVLayoutView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([name isEqualToString:@"gravity"]) { + view.gravity = (DoricGravity) [(NSNumber *) prop integerValue]; + } else if ([name isEqualToString:@"space"]) { + view.space = [(NSNumber *) prop floatValue]; + } else { + [super blendView:view forPropName:name propValue:prop]; + } +} + +@end diff --git a/Pod/Classes/Shader/DoricViewNode.h b/Pod/Classes/Shader/DoricViewNode.h new file mode 100644 index 00000000..faad1da5 --- /dev/null +++ b/Pod/Classes/Shader/DoricViewNode.h @@ -0,0 +1,56 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricViewNode.h +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricContextHolder.h" +#import "DoricLayouts.h" +#import "UIView+Doric.h" + +@class DoricSuperNode; + +@interface DoricViewNode : DoricContextHolder + +@property(nonatomic, strong) V view; +@property(nonatomic, weak) DoricSuperNode *superNode; +@property(nonatomic) NSInteger index; + +@property(nonatomic, copy) NSString *viewId; + +@property(nonatomic, copy) NSString *type; + +@property(nonatomic, readonly) DoricLayoutConfig *layoutConfig; + +@property(nonatomic, readonly) NSArray *idList; + +- (void)initWithSuperNode:(DoricSuperNode *)superNode; + +- (V)build; + +- (void)blend:(NSDictionary *)props; + +- (void)blendView:(V)view forPropName:(NSString *)name propValue:(id)prop; + +- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ...; + ++ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type; + +- (void)requestLayout; +@end diff --git a/Pod/Classes/Shader/DoricViewNode.m b/Pod/Classes/Shader/DoricViewNode.m new file mode 100644 index 00000000..09e9ee8b --- /dev/null +++ b/Pod/Classes/Shader/DoricViewNode.m @@ -0,0 +1,627 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricViewNode.m +// Doric +// +// Created by pengfei.zhou on 2019/7/30. +// + +#import "DoricViewNode.h" +#import "DoricUtil.h" +#import "DoricGroupNode.h" +#import "DoricRootNode.h" +#import "DoricConstant.h" +#import "DoricSuperNode.h" +#import "DoricExtensions.h" +#import "DoricPromise.h" + +void DoricAddEllipticArcPath(CGMutablePathRef path, + CGPoint origin, + CGFloat radius, + CGFloat startAngle, + CGFloat endAngle) { + CGAffineTransform t = CGAffineTransformMakeTranslation(origin.x, origin.y); + CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, NO); +} + + +CGPathRef DoricCreateRoundedRectPath(CGRect bounds, + CGFloat leftTop, + CGFloat rightTop, + CGFloat rightBottom, + CGFloat leftBottom) { + const CGFloat minX = CGRectGetMinX(bounds); + const CGFloat minY = CGRectGetMinY(bounds); + const CGFloat maxX = CGRectGetMaxX(bounds); + const CGFloat maxY = CGRectGetMaxY(bounds); + + CGMutablePathRef path = CGPathCreateMutable(); + DoricAddEllipticArcPath(path, (CGPoint) { + minX + leftTop, minY + leftTop + }, leftTop, M_PI, 3 * M_PI_2); + DoricAddEllipticArcPath(path, (CGPoint) { + maxX - rightTop, minY + rightTop + }, rightTop, 3 * M_PI_2, 0); + DoricAddEllipticArcPath(path, (CGPoint) { + maxX - rightBottom, maxY - rightBottom + }, rightBottom, 0, M_PI_2); + DoricAddEllipticArcPath(path, (CGPoint) { + minX + leftBottom, maxY - leftBottom + }, leftBottom, M_PI_2, M_PI); + CGPathCloseSubpath(path); + return path; +} + +@interface AnimationCallback : NSObject +@property(nonatomic, strong) NSMutableDictionary *dictionary; +@property(nonatomic, strong) void (^startBlock)(AnimationCallback *callback); + +@property(nonatomic, strong) void (^endBlock)(AnimationCallback *callback); +@end + +@implementation AnimationCallback +- (instancetype)init { + if (self = [super init]) { + _dictionary = [NSMutableDictionary new]; + } + return self; +} + +- (void)animationDidStart:(CAAnimation *)anim { + if (self.startBlock) { + self.startBlock(self); + } +} + +- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { + if (self.endBlock) { + self.endBlock(self); + } +} +@end + +@interface DoricViewNode () +@property(nonatomic, strong) NSMutableDictionary *callbackIds; +@property(nonatomic, copy) NSNumber *translationX; +@property(nonatomic, copy) NSNumber *translationY; +@property(nonatomic, copy) NSNumber *scaleX; +@property(nonatomic, copy) NSNumber *scaleY; +@property(nonatomic, copy) NSNumber *rotation; +@property(nonatomic, copy) NSNumber *pivotX; +@property(nonatomic, copy) NSNumber *pivotY; +@end + +@implementation DoricViewNode + +- (instancetype)initWithContext:(DoricContext *)doricContext { + if (self = [super initWithContext:doricContext]) { + _callbackIds = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)initWithSuperNode:(DoricSuperNode *)superNode { + if ([self isKindOfClass:[DoricSuperNode class]]) { + ((DoricSuperNode *) self).reusable = superNode.reusable; + } + self.superNode = superNode; + self.view = [[self build] also:^(UIView *it) { + it.layoutConfig = [superNode generateDefaultLayoutParams]; + }]; +} + +- (DoricLayoutConfig *)layoutConfig { + return self.view.layoutConfig; +} + +- (UIView *)build { + return [[UIView alloc] init]; +} + +- (void)blend:(NSDictionary *)props { + self.view.layoutConfig = self.layoutConfig; + for (NSString *key in props) { + id value = props[key]; + [self blendView:self.view forPropName:key propValue:value]; + } + [self transformProperties]; +} + +- (void)transformProperties { + CGAffineTransform transform = CGAffineTransformIdentity; + if (self.translationX || self.translationY) { + transform = CGAffineTransformTranslate(transform, [self.translationX floatValue] ?: 0, [self.translationY floatValue] ?: 0); + } + if (self.scaleX || self.scaleY) { + transform = CGAffineTransformScale(transform, [self.scaleX floatValue] ?: 1, [self.scaleY floatValue] ?: 1); + } + if (self.rotation) { + transform = CGAffineTransformRotate(transform, (self.rotation.floatValue ?: 0) * M_PI); + } + if (!CGAffineTransformEqualToTransform(transform, self.view.transform)) { + self.view.transform = transform; + } + if (self.pivotX || self.pivotY) { + self.view.layer.anchorPoint = CGPointMake(self.pivotX.floatValue + ?: 0.5f, self.pivotY.floatValue ?: 0.5f); + } +} + +- (void)blendView:(UIView *)view forPropName:(NSString *)name propValue:(id)prop { + if ([name isEqualToString:@"width"]) { + NSNumber *width = (NSNumber *) prop; + if ([width floatValue] >= 0) { + view.width = [width floatValue]; + } + } else if ([name isEqualToString:@"height"]) { + NSNumber *height = (NSNumber *) prop; + if ([height floatValue] >= 0) { + view.height = [height floatValue]; + } + } else if ([name isEqualToString:@"x"]) { + view.x = [(NSNumber *) prop floatValue]; + } else if ([name isEqualToString:@"y"]) { + view.y = [(NSNumber *) prop floatValue]; + } else if ([name isEqualToString:@"backgroundColor"]) { + view.backgroundColor = DoricColor(prop); + } else if ([name isEqualToString:@"alpha"]) { + view.alpha = [prop floatValue]; + } else if ([name isEqualToString:@"layoutConfig"]) { + if (self.superNode && [prop isKindOfClass:[NSDictionary class]]) { + [self.superNode blendSubNode:self layoutConfig:prop]; + } else { + [self blendLayoutConfig:prop]; + } + } else if ([name isEqualToString:@"onClick"]) { + self.callbackIds[@"onClick"] = prop; + view.userInteractionEnabled = YES; + UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClick:)]; + [view addGestureRecognizer:tapGestureRecognizer]; + } else if ([name isEqualToString:@"border"]) { + NSDictionary *dic = prop; + CGFloat width = [(NSNumber *) dic[@"width"] floatValue]; + UIColor *color = DoricColor((NSNumber *) dic[@"color"]); + view.layer.borderWidth = width; + view.layer.borderColor = color.CGColor; + } else if ([name isEqualToString:@"corners"]) { + if ([prop isKindOfClass:NSNumber.class]) { + view.layer.cornerRadius = [(NSNumber *) prop floatValue]; + } else if ([prop isKindOfClass:NSDictionary.class]) { + NSDictionary *dic = prop; + CGFloat leftTop = [(NSNumber *) dic[@"leftTop"] floatValue]; + CGFloat rightTop = [(NSNumber *) dic[@"rightTop"] floatValue]; + CGFloat rightBottom = [(NSNumber *) dic[@"rightBottom"] floatValue]; + CGFloat leftBottom = [(NSNumber *) dic[@"leftBottom"] floatValue]; + if (ABS(leftTop - rightTop) > CGFLOAT_MIN + || ABS(leftTop - rightBottom) > CGFLOAT_MIN + || ABS(leftTop - leftBottom) > CGFLOAT_MIN) { + view.layer.cornerRadius = 0; + dispatch_async(dispatch_get_main_queue(), ^{ + CAShapeLayer *shapeLayer = [CAShapeLayer layer]; + CGPathRef path = DoricCreateRoundedRectPath(self.view.bounds, leftTop, rightTop, rightBottom, leftBottom); + shapeLayer.path = path; + CGPathRelease(path); + view.layer.mask = shapeLayer; + }); + } else { + view.layer.cornerRadius = leftTop; + view.layer.mask = nil; + } + } + } else if ([name isEqualToString:@"shadow"]) { + NSDictionary *dic = prop; + CGFloat opacity = [(NSNumber *) dic[@"opacity"] floatValue]; + if (opacity > CGFLOAT_MIN) { + view.clipsToBounds = NO; + UIColor *color = DoricColor((NSNumber *) dic[@"color"]); + view.layer.shadowColor = color.CGColor; + view.layer.shadowRadius = [(NSNumber *) dic[@"radius"] floatValue]; + view.layer.shadowOffset = CGSizeMake([(NSNumber *) dic[@"offsetX"] floatValue], [(NSNumber *) dic[@"offsetY"] floatValue]); + view.layer.shadowOpacity = (float) opacity; + } else { + view.clipsToBounds = YES; + } + + } else if ([name isEqualToString:@"translationX"]) { + self.translationX = prop; + } else if ([name isEqualToString:@"translationY"]) { + self.translationY = prop; + } else if ([name isEqualToString:@"scaleX"]) { + self.scaleX = prop; + } else if ([name isEqualToString:@"scaleY"]) { + self.scaleY = prop; + } else if ([name isEqualToString:@"pivotX"]) { + self.pivotX = prop; + } else if ([name isEqualToString:@"pivotY"]) { + self.pivotY = prop; + } else if ([name isEqualToString:@"rotation"]) { + self.rotation = prop; + } else { + DoricLog(@"Blend View error for View Type :%@, prop is %@", self.class, name); + } +} + +- (void)onClick:(UIView *)view { + [self callJSResponse:self.callbackIds[@"onClick"], nil]; +} + +- (NSArray *)idList { + NSMutableArray *ret = [[NSMutableArray alloc] init]; + DoricViewNode *node = self; + do { + [ret addObject:node.viewId]; + node = node.superNode; + } while (node); + + return [[ret reverseObjectEnumerator] allObjects]; +} + +- (DoricAsyncResult *)callJSResponse:(NSString *)funcId, ... { + NSMutableArray *array = [[NSMutableArray alloc] init]; + [array addObject:self.idList]; + [array addObject:funcId]; + va_list args; + va_start(args, funcId); + id arg; + while ((arg = va_arg(args, id)) != nil) { + [array addObject:arg]; + } + DoricAsyncResult *ret = [self.doricContext callEntity:DORIC_ENTITY_RESPONSE withArgumentsArray:array]; + va_end(args); + return ret; +} + ++ (__kindof DoricViewNode *)create:(DoricContext *)context withType:(NSString *)type { + DoricRegistry *registry = context.driver.registry; + Class clz = [registry acquireViewNode:type]; + DoricViewNode *viewNode = [(DoricViewNode *) [clz alloc] initWithContext:context]; + viewNode.type = type; + return viewNode; +} + +- (void)requestLayout { + [self.superNode requestLayout]; +} + +- (NSNumber *)getWidth { + return @(self.view.width); +} + +- (NSNumber *)getHeight { + return @(self.view.height); +} + +- (void)blendLayoutConfig:(NSDictionary *)params { + [params[@"widthSpec"] also:^(NSNumber *it) { + if (it) { + self.layoutConfig.widthSpec = (DoricLayoutSpec) [it integerValue]; + } + }]; + + [params[@"heightSpec"] also:^(NSNumber *it) { + if (it) { + self.layoutConfig.heightSpec = (DoricLayoutSpec) [it integerValue]; + } + }]; + + NSDictionary *margin = params[@"margin"]; + if (margin) { + self.layoutConfig.margin = DoricMarginMake( + [(NSNumber *) margin[@"left"] floatValue], + [(NSNumber *) margin[@"top"] floatValue], + [(NSNumber *) margin[@"right"] floatValue], + [(NSNumber *) margin[@"bottom"] floatValue]); + } + + NSNumber *alignment = params[@"alignment"]; + if (alignment) { + self.layoutConfig.alignment = (DoricGravity) [alignment integerValue]; + } + NSNumber *weight = params[@"weight"]; + if (weight) { + self.layoutConfig.weight = (DoricGravity) [weight integerValue]; + } +} + +- (NSDictionary *)transformation { + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + if (self.translationX) { + dictionary[@"translationX"] = self.translationX; + } + if (self.translationY) { + dictionary[@"translationY"] = self.translationY; + } + if (self.scaleX) { + dictionary[@"scaleX"] = self.scaleX; + } + if (self.scaleY) { + dictionary[@"scaleY"] = self.scaleY; + } + if (self.rotation) { + dictionary[@"rotation"] = self.rotation; + } + return dictionary; +} + +- (void)doAnimation:(id)params withPromise:(DoricPromise *)promise { + CAAnimation *animation = [self parseAnimation:params]; + AnimationCallback *originDelegate = animation.delegate; + AnimationCallback *animationCallback = [[AnimationCallback new] also:^(AnimationCallback *it) { + it.startBlock = ^(AnimationCallback *callback) { + if (originDelegate) { + originDelegate.startBlock(callback); + } + [self transformProperties]; + }; + it.endBlock = ^(AnimationCallback *callback) { + if (originDelegate) { + originDelegate.endBlock(callback); + } + [self transformProperties]; + [promise resolve:self.transformation]; + }; + }]; + animation.delegate = animationCallback; + if (params[@"delay"]) { + animation.beginTime = CACurrentMediaTime() + [params[@"delay"] floatValue] / 1000; + } + [self.view.layer addAnimation:animation forKey:nil]; +} + +- (CFTimeInterval)computeDurationOfAnimations:(NSArray *)animations { + __block CFTimeInterval interval = 0; + [animations forEach:^(CAAnimation *obj) { + interval = MAX(interval, obj.beginTime + obj.duration * (1 + obj.repeatCount)); + }]; + return interval; +} + +- (CAAnimation *)parseAnimation:(id)params { + if (params[@"animations"]) { + NSArray *anims = params[@"animations"]; + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + NSMutableArray *animations = [NSMutableArray new]; + [anims forEach:^(id obj) { + [animations addObject:[self parseAnimation:obj]]; + }]; + animationGroup.duration = [self computeDurationOfAnimations:animations]; + animationGroup.animations = animations; + animationGroup.delegate = [[AnimationCallback new] also:^(AnimationCallback *it) { + it.startBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.startBlock) { + obj.startBlock(obj); + } + }]; + }; + it.endBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.endBlock) { + obj.endBlock(obj); + } + }]; + }; + }]; + if (params[@"delay"]) { + animationGroup.beginTime = [params[@"delay"] floatValue] / 1000; + } + return animationGroup; + } else if ([params isKindOfClass:[NSDictionary class]]) { + NSArray *changeables = params[@"changeables"]; + NSString *type = params[@"type"]; + if ([@"TranslationAnimation" isEqualToString:type]) { + __block CGPoint from = self.view.layer.position; + __block CGPoint to = self.view.layer.position; + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; + [changeables forEach:^(NSDictionary *obj) { + NSString *key = obj[@"key"]; + if ([@"translationX" isEqualToString:key]) { + from.x += [obj[@"fromValue"] floatValue] - self.translationX.floatValue; + to.x += [obj[@"toValue"] floatValue] - self.translationX.floatValue; + [self setFillMode:animation + key:key + startValue:obj[@"fromValue"] + endValue:obj[@"toValue"] + fillMode:params[@"fillMode"]]; + } else if ([@"translationY" isEqualToString:key]) { + from.y += [obj[@"fromValue"] floatValue] - self.translationY.floatValue; + to.y += [obj[@"toValue"] floatValue] - self.translationY.floatValue; + [self setFillMode:animation + key:key + startValue:obj[@"fromValue"] + endValue:obj[@"toValue"] + fillMode:params[@"fillMode"]]; + } + + }]; + animation.fromValue = [NSValue valueWithCGPoint:from]; + animation.toValue = [NSValue valueWithCGPoint:to]; + if (params[@"timingFunction"]) { + animation.timingFunction = [self translateToTimingFunction:params[@"timingFunction"]]; + } + [self setAnimation:animation params:params]; + return animation; + } else { + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + NSMutableArray *animations = [NSMutableArray new]; + + [changeables forEach:^(NSDictionary *obj) { + CABasicAnimation *animation = [self parseChangeable:obj fillMode:params[@"fillMode"]]; + if (params[@"timingFunction"]) { + animation.timingFunction = [self translateToTimingFunction:params[@"timingFunction"]]; + } + [animations addObject:animation]; + }]; + animationGroup.animations = animations; + animationGroup.delegate = [[AnimationCallback new] also:^(AnimationCallback *it) { + it.startBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.startBlock) { + obj.startBlock(obj); + } + }]; + }; + it.endBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.endBlock) { + obj.endBlock(obj); + } + }]; + }; + }]; + [self setAnimation:animationGroup params:params]; + return animationGroup; + } + } + return nil; +} + +- (void)setAnimation:(CAAnimation *)animation params:(NSDictionary *)params { + if (params[@"repeatCount"]) { + NSInteger repeatCount = [params[@"repeatCount"] integerValue]; + if (repeatCount < 0) { + repeatCount = NSNotFound; + } + animation.repeatCount = repeatCount; + } + if (params[@"repeatMode"]) { + NSInteger repeatMode = [params[@"repeatMode"] integerValue]; + animation.autoreverses = repeatMode == 2; + } + + if (params[@"delay"]) { + animation.beginTime = [params[@"delay"] floatValue] / 1000; + } + animation.duration = [params[@"duration"] floatValue] / 1000; +} + +- (void)setFillMode:(CAAnimation *)animation + key:(NSString *)key + startValue:(NSNumber *)startValue + endValue:(NSNumber *)endValue + fillMode:(NSNumber *)fillMode { + NSUInteger fillModeInt = fillMode.unsignedIntegerValue; + if ((fillModeInt & 2) == 2) { + [self setAnimatedValue:key value:startValue]; + } + AnimationCallback *callback = [AnimationCallback new]; + AnimationCallback *originCallback = animation.delegate; + __weak typeof(self) _self = self; + callback.startBlock = ^(AnimationCallback *callback) { + __strong typeof(_self) self = _self; + callback.dictionary[key] = [self getAnimatedValue:key]; + if (originCallback) { + originCallback.startBlock(callback); + } + }; + callback.endBlock = ^(AnimationCallback *callback) { + __strong typeof(_self) self = _self; + if ((fillModeInt & 1) == 1) { + [self setAnimatedValue:key value:endValue]; + } + if (originCallback) { + originCallback.endBlock(callback); + } + }; + animation.delegate = callback; +} + +- (NSNumber *)getAnimatedValue:(NSString *)key { + if ([@"translationX" isEqualToString:key]) { + return self.translationX; + } + if ([@"translationY" isEqualToString:key]) { + return self.translationY; + } + if ([@"scaleX" isEqualToString:key]) { + return self.scaleX; + } + if ([@"scaleY" isEqualToString:key]) { + return self.scaleY; + } + if ([@"rotation" isEqualToString:key]) { + return self.rotation; + } + return nil; +} + +- (void)setAnimatedValue:(NSString *)key value:(NSNumber *)value { + if ([@"translationX" isEqualToString:key]) { + self.translationX = value; + } else if ([@"translationY" isEqualToString:key]) { + self.translationY = value; + } else if ([@"scaleX" isEqualToString:key]) { + self.scaleX = value; + } else if ([@"scaleY" isEqualToString:key]) { + self.scaleY = value; + } else if ([@"rotation" isEqualToString:key]) { + self.rotation = value; + } +} + +- (CABasicAnimation *)parseChangeable:(NSDictionary *)params fillMode:(NSNumber *)fillMode { + NSString *key = params[@"key"]; + CABasicAnimation *animation = [CABasicAnimation animation]; + if ([@"scaleX" isEqualToString:key]) { + animation.keyPath = @"transform.scale.x"; + animation.fromValue = params[@"fromValue"]; + animation.toValue = params[@"toValue"]; + } else if ([@"scaleY" isEqualToString:key]) { + animation.keyPath = @"transform.scale.y"; + animation.fromValue = params[@"fromValue"]; + animation.toValue = params[@"toValue"]; + } else if ([@"rotation" isEqualToString:key]) { + animation.keyPath = @"transform.rotation.z"; + animation.fromValue = @([params[@"fromValue"] floatValue] * M_PI); + animation.toValue = @([params[@"toValue"] floatValue] * M_PI); + } else if ([@"backgroundColor" isEqualToString:key]) { + animation.keyPath = @"backgroundColor"; + animation.fromValue = params[@"fromValue"]; + animation.toValue = params[@"toValue"]; + } + [self setFillMode:animation + key:key + startValue:params[@"fromValue"] + endValue:params[@"toValue"] + fillMode:fillMode]; + return animation; +} + +- (CAMediaTimingFunction *)translateToTimingFunction:(NSNumber *)timingFunction { + switch (timingFunction.integerValue) { + case 1: + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + case 2: + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; + case 3: + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + case 4: + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + default: + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + } +} + +@end diff --git a/Pod/Classes/Shader/UIView+Doric.h b/Pod/Classes/Shader/UIView+Doric.h new file mode 100644 index 00000000..06502cbb --- /dev/null +++ b/Pod/Classes/Shader/UIView+Doric.h @@ -0,0 +1,42 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// UIView+Doric.h +// Doric_Example +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 CocoaPods. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIView (Doric) +@property(nonatomic) CGFloat x; +@property(nonatomic) CGFloat y; +@property(nonatomic) CGFloat width; +@property(nonatomic) CGFloat height; +@property(nonatomic) CGFloat centerX; +@property(nonatomic) CGFloat centerY; +@property(nonatomic) CGFloat top; +@property(nonatomic) CGFloat left; +@property(nonatomic) CGFloat right; +@property(nonatomic) CGFloat bottom; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Shader/UIView+Doric.m b/Pod/Classes/Shader/UIView+Doric.m new file mode 100644 index 00000000..a81d7021 --- /dev/null +++ b/Pod/Classes/Shader/UIView+Doric.m @@ -0,0 +1,128 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// UIView+Doric.m +// Doric_Example +// +// Created by pengfei.zhou on 2019/7/25. +// Copyright © 2019 CocoaPods. All rights reserved. +// + +#import "UIView+Doric.h" + +@implementation UIView (Doric) + +- (CGFloat)x { + return self.frame.origin.x; +} + +- (void)setX:(CGFloat)x { + CGRect frame = self.frame; + frame.origin.x = x; + [self setFrame:frame]; +} + +- (CGFloat)y { + return self.frame.origin.y; +} + +- (void)setY:(CGFloat)y { + CGRect frame = self.frame; + frame.origin.y = y; + [self setFrame:frame]; +} + +- (CGFloat)left { + return self.frame.origin.x; +} + +- (void)setLeft:(CGFloat)left { + CGRect frame = self.frame; + frame.origin.x = left; + [self setFrame:frame]; +} + +- (CGFloat)right { + return self.frame.origin.x + self.frame.size.width; +} + +- (void)setRight:(CGFloat)right { + CGRect frame = self.frame; + frame.origin.x = right - self.frame.size.width; + [self setFrame:frame]; +} + +- (CGFloat)top { + return self.frame.origin.y; +} + +- (void)setTop:(CGFloat)top { + CGRect frame = self.frame; + frame.origin.y = top; + [self setFrame:frame]; +} + +- (CGFloat)bottom { + return self.frame.origin.y + self.frame.size.height; +} + +- (void)setBottom:(CGFloat)bottom { + CGRect frame = self.frame; + frame.origin.y = bottom - self.frame.size.height; + [self setFrame:frame]; +} + +- (CGFloat)width { + return self.frame.size.width; +} + +- (void)setWidth:(CGFloat)width { + CGRect frame = self.frame; + frame.size.width = width; + self.frame = frame; +} + +- (CGFloat)height { + return self.frame.size.height; +} + +- (void)setHeight:(CGFloat)height { + CGRect frame = self.frame; + frame.size.height = height; + self.frame = frame; +} + +- (CGFloat)centerX { + return self.frame.origin.x + self.frame.size.width / 2; +} + +- (void)setCenterX:(CGFloat)centerX { + CGRect frame = self.frame; + frame.origin.x = centerX - self.frame.size.width / 2; + [self setFrame:frame]; +} + +- (CGFloat)centerY { + return self.frame.origin.y + self.frame.size.height / 2; +} + +- (void)setCenterY:(CGFloat)centerY { + CGRect frame = self.frame; + frame.origin.y = centerY - self.frame.size.height / 2; + [self setFrame:frame]; +} + +@end diff --git a/Pod/Classes/Util/Category/NSString+JsonString.h b/Pod/Classes/Util/Category/NSString+JsonString.h new file mode 100644 index 00000000..9581e9a3 --- /dev/null +++ b/Pod/Classes/Util/Category/NSString+JsonString.h @@ -0,0 +1,18 @@ +// +// NSString+JsonString.h +// Doric +// +// Created by Insomnia on 2019/11/7. +// + + + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (JsonString) ++ (NSString *)dc_convertToJsonWithDic:(NSDictionary *)dic; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Util/Category/NSString+JsonString.m b/Pod/Classes/Util/Category/NSString+JsonString.m new file mode 100644 index 00000000..bcdd6437 --- /dev/null +++ b/Pod/Classes/Util/Category/NSString+JsonString.m @@ -0,0 +1,23 @@ +// +// NSString+JsonString.m +// Doric +// +// Created by Insomnia on 2019/11/7. +// + +#import "NSString+JsonString.h" +#import "DoricUtil.h" + +@implementation NSString (JsonString) ++ (NSString *)dc_convertToJsonWithDic:(NSDictionary *)dic { + NSError *err; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&err]; + NSString *jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + if (err) { + DoricLog(NSStringFromSelector(_cmd), @"Convert dictionary to json string failed."); + return nil; + } + return jsonStr; +} +@end diff --git a/Pod/Classes/Util/DoricAsyncResult.h b/Pod/Classes/Util/DoricAsyncResult.h new file mode 100644 index 00000000..6a5b6a7d --- /dev/null +++ b/Pod/Classes/Util/DoricAsyncResult.h @@ -0,0 +1,44 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricAsyncResult.h +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface DoricAsyncResult : NSObject +@property(nonatomic, strong) void (^resultCallback)(R result); +@property(nonatomic, strong) void (^exceptionCallback)(NSException *e); +@property(nonatomic, strong) void (^finishCallback)(void); + +- (void)setupResult:(R)result; + +- (void)setupError:(NSException *)exception; + +- (BOOL)hasResult; + +- (R)getResult; + +- (R)waitUntilResult; +@end + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Util/DoricAsyncResult.m b/Pod/Classes/Util/DoricAsyncResult.m new file mode 100644 index 00000000..e70b8805 --- /dev/null +++ b/Pod/Classes/Util/DoricAsyncResult.m @@ -0,0 +1,92 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricAsyncResult.m +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import "DoricAsyncResult.h" + +@interface DoricAsyncResult () +@property(nonatomic, strong) id result; +@end + +@implementation DoricAsyncResult + +- (void)setupResult:(id)result { + self.result = result; + if (self.resultCallback) { + self.resultCallback(result); + } + if (self.finishCallback) { + self.finishCallback(); + } +} + +- (void)setupError:(NSException *)exception { + self.result = exception; + if (self.exceptionCallback) { + self.exceptionCallback(exception); + } + if (self.finishCallback) { + self.finishCallback(); + } +} + +- (BOOL)hasResult { + return self.result; +} + +- (id)getResult { + return self.result; +} + +- (void)setResultCallback:(void (^)(id))callback { + _resultCallback = callback; + if (self.result && ![self.result isKindOfClass:[NSException class]]) { + callback(self.result); + } +} + +- (void)setExceptionCallback:(void (^)(NSException *))exceptionCallback { + _exceptionCallback = exceptionCallback; + if ([self.result isKindOfClass:[NSException class]]) { + exceptionCallback(self.result); + } +} + +- (void)setFinishCallback:(void (^)(void))finishCallback { + _finishCallback = finishCallback; + if (self.result) { + finishCallback(); + } +} + +- (id)waitUntilResult { + if (self.result) { + return self.result; + } + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + self.resultCallback = ^(id r) { + dispatch_semaphore_signal(semaphore); + }; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + return self.result; +} +@end diff --git a/Pod/Classes/Util/DoricConstant.h b/Pod/Classes/Util/DoricConstant.h new file mode 100644 index 00000000..06b34fd2 --- /dev/null +++ b/Pod/Classes/Util/DoricConstant.h @@ -0,0 +1,65 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricConstant.h +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import + +extern NSString *const DORIC_BUNDLE_SANDBOX; +extern NSString *const DORIC_BUNDLE_LIB; +extern NSString *const DORIC_MODULE_LIB; + + +extern NSString *const INJECT_LOG; +extern NSString *const INJECT_REQUIRE; +extern NSString *const INJECT_TIMER_SET; +extern NSString *const INJECT_TIMER_CLEAR; +extern NSString *const INJECT_BRIDGE; +extern NSString *const INJECT_EMPTY; + +extern NSString *const TEMPLATE_CONTEXT_CREATE; + +extern NSString *const TEMPLATE_MODULE; + +extern NSString *const TEMPLATE_CONTEXT_DESTROY; + +extern NSString *const GLOBAL_DORIC; + +extern NSString *const DORIC_CONTEXT_RELEASE; + +extern NSString *const DORIC_CONTEXT_INVOKE; + +extern NSString *const DORIC_TIMER_CALLBACK; + +extern NSString *const DORIC_BRIDGE_RESOLVE; + +extern NSString *const DORIC_BRIDGE_REJECT; + +extern NSString *const DORIC_ENTITY_RESPONSE; + +extern NSString *const DORIC_ENTITY_INIT; + +extern NSString *const DORIC_ENTITY_CREATE; + +extern NSString *const DORIC_ENTITY_DESTROY; + +extern NSString *const DORIC_ENTITY_SHOW; + +extern NSString *const DORIC_ENTITY_HIDDEN; diff --git a/Pod/Classes/Util/DoricConstant.m b/Pod/Classes/Util/DoricConstant.m new file mode 100644 index 00000000..09a742a4 --- /dev/null +++ b/Pod/Classes/Util/DoricConstant.m @@ -0,0 +1,82 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricConstant.m +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import "DoricConstant.h" + +NSString *const DORIC_BUNDLE_SANDBOX = @"doric-sandbox"; +NSString *const DORIC_BUNDLE_LIB = @"doric-lib"; +NSString *const DORIC_MODULE_LIB = @"doric"; + + +NSString *const INJECT_LOG = @"nativeLog"; +NSString *const INJECT_REQUIRE = @"nativeRequire"; +NSString *const INJECT_TIMER_SET = @"nativeSetTimer"; +NSString *const INJECT_TIMER_CLEAR = @"nativeClearTimer"; +NSString *const INJECT_BRIDGE = @"nativeBridge"; +NSString *const INJECT_EMPTY = @"nativeEmpty"; + +NSString *const TEMPLATE_CONTEXT_CREATE = @"Reflect.apply(" + "function(doric,context,Entry,require,exports){" "\n" + "%@" "\n" + "},doric.jsObtainContext(\"%@\"),[" + "undefined," + "doric.jsObtainContext(\"%@\")," + "doric.jsObtainEntry(\"%@\")," + "doric.__require__" + ",{}" + "])"; + +NSString *const TEMPLATE_MODULE = @"Reflect.apply(doric.jsRegisterModule,this,[" + "\"%@\"," + "Reflect.apply(function(__module){" + "(function(module,exports,require){" "\n" + "%@" "\n" + "})(__module,__module.exports,doric.__require__);" + "\nreturn __module.exports;" + "},this,[{exports:{}}])" + "])"; + +NSString *const TEMPLATE_CONTEXT_DESTROY = @"doric.jsReleaseContext(\"%@\")"; + +NSString *const GLOBAL_DORIC = @"doric"; + +NSString *const DORIC_CONTEXT_RELEASE = @"jsReleaseContext"; + +NSString *const DORIC_CONTEXT_INVOKE = @"jsCallEntityMethod"; + +NSString *const DORIC_TIMER_CALLBACK = @"jsCallbackTimer"; + +NSString *const DORIC_BRIDGE_RESOLVE = @"jsCallResolve"; + +NSString *const DORIC_BRIDGE_REJECT = @"jsCallReject"; + +NSString *const DORIC_ENTITY_RESPONSE = @"__response__"; + +NSString *const DORIC_ENTITY_INIT = @"__init__"; + +NSString *const DORIC_ENTITY_CREATE = @"__onCreate__"; + +NSString *const DORIC_ENTITY_DESTROY = @"__onDestroy__"; + +NSString *const DORIC_ENTITY_SHOW = @"__onShow__"; + +NSString *const DORIC_ENTITY_HIDDEN = @"__onHidden__"; diff --git a/Pod/Classes/Util/DoricExtensions.h b/Pod/Classes/Util/DoricExtensions.h new file mode 100644 index 00000000..aad626fd --- /dev/null +++ b/Pod/Classes/Util/DoricExtensions.h @@ -0,0 +1,47 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/10/23. +// + +#import + + +@interface NSObject (Doric) +- (id)apply:(id (^)(id it))block; + +- (instancetype)also:(void (^)(id it))block; + +- (void)let:(void (^)(id it))block; +@end + +@interface NSArray (Doric) +- (void)forEachIndexed:(void (NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx))block; + +- (NSArray *)mapIndexed:(id (NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx))block; + +- (NSArray *)flatMapIndexed:(NSArray *(NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx))block; + +- (NSArray *)filterIndexed:(BOOL (NS_NOESCAPE ^)(ObjectType obj, NSUInteger idx))block; + +- (void)forEach:(void (NS_NOESCAPE ^)(ObjectType obj))block; + +- (NSArray *)map:(id (NS_NOESCAPE ^)(ObjectType obj))block; + +- (NSArray *)flatMap:(NSArray *(NS_NOESCAPE ^)(ObjectType obj))block; + +- (NSArray *)filter:(BOOL (NS_NOESCAPE ^)(ObjectType obj))block; +@end \ No newline at end of file diff --git a/Pod/Classes/Util/DoricExtensions.m b/Pod/Classes/Util/DoricExtensions.m new file mode 100644 index 00000000..e0f44c47 --- /dev/null +++ b/Pod/Classes/Util/DoricExtensions.m @@ -0,0 +1,102 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Created by pengfei.zhou on 2019/10/23. +// + +#import "DoricExtensions.h" + +@implementation NSObject (Doric) +- (id)apply:(id (^)(id it))block { + return block(self); +} + +- (instancetype)also:(void (^)(id it))block { + block(self); + return self; +} + +- (void)let:(void (^)(id it))block { + block(self); +} +@end + +@implementation NSArray (Doric) +- (void)forEachIndexed:(void (NS_NOESCAPE ^)(id obj, NSUInteger idx))block { + [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + block(obj, idx); + }]; +} + +- (NSArray *)mapIndexed:(id (NS_NOESCAPE ^)(id obj, NSUInteger idx))block { + NSMutableArray *temp = [NSMutableArray new]; + [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [temp addObject:block(obj, idx)]; + }]; + return [temp copy]; +} + +- (NSArray *)flatMapIndexed:(NSArray *(NS_NOESCAPE ^)(id obj, NSUInteger idx))block { + NSMutableArray *temp = [NSMutableArray new]; + [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [temp addObjectsFromArray:block(obj, idx)]; + }]; + return [temp copy]; +} + +- (NSArray *)filterIndexed:(BOOL (NS_NOESCAPE ^)(id obj, NSUInteger idx))block { + NSMutableArray *temp = [NSMutableArray new]; + [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if (block(obj, idx)) { + [temp addObject:obj]; + } + }]; + return [temp copy]; +} + +- (void)forEach:(void (NS_NOESCAPE ^)(id obj))block { + for (id obj in self) { + block(obj); + } +} + +- (NSArray *)map:(id (NS_NOESCAPE ^)(id obj))block { + NSMutableArray *temp = [NSMutableArray new]; + for (id obj in self) { + [temp addObject:block(obj)]; + } + return [temp copy]; +} + +- (NSArray *)flatMap:(NSArray *(NS_NOESCAPE ^)(id obj))block { + NSMutableArray *temp = [NSMutableArray new]; + for (id obj in self) { + [temp addObjectsFromArray:block(obj)]; + } + return [temp copy]; +} + +- (NSArray *)filter:(BOOL (NS_NOESCAPE ^)(id obj))block { + NSMutableArray *temp = [NSMutableArray new]; + for (id obj in self) { + if (block(obj)) { + [temp addObject:obj]; + } + } + return [temp copy]; +} + +@end diff --git a/Pod/Classes/Util/DoricJSRemoteArgType.h b/Pod/Classes/Util/DoricJSRemoteArgType.h new file mode 100644 index 00000000..f18ea41d --- /dev/null +++ b/Pod/Classes/Util/DoricJSRemoteArgType.h @@ -0,0 +1,23 @@ +// +// DoricJSRemoteArgType.h +// Doric +// +// Created by Insomnia on 2019/11/7. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, DoricJSRemoteArgType) { + DoricJSRemoteArgTypeNil = 0, + DoricJSRemoteArgTypeInteger, + DoricJSRemoteArgTypeBool, + DoricJSRemoteArgTypeString, + DoricJSRemoteArgTypeObject, + DoricJSRemoteArgTypeArray, +}; + +DoricJSRemoteArgType DoricargTypeWithArg(id arg); + +NS_ASSUME_NONNULL_END diff --git a/Pod/Classes/Util/DoricJSRemoteArgType.m b/Pod/Classes/Util/DoricJSRemoteArgType.m new file mode 100644 index 00000000..1b018109 --- /dev/null +++ b/Pod/Classes/Util/DoricJSRemoteArgType.m @@ -0,0 +1,12 @@ +// +// DoricJSRemoteArgType.m +// Doric +// +// Created by Insomnia on 2019/11/7. +// + +#import "DoricJSRemoteArgType.h" +DoricJSRemoteArgType DoricargTypeWithArg(id arg) { + // TODO: 类型缺失 + return DoricJSRemoteArgTypeString; +} diff --git a/Pod/Classes/Util/DoricUtil.h b/Pod/Classes/Util/DoricUtil.h new file mode 100644 index 00000000..ec67ceee --- /dev/null +++ b/Pod/Classes/Util/DoricUtil.h @@ -0,0 +1,42 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricUtil.h +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import +#import "DoricLayouts.h" + +void DoricLog(NSString *_Nonnull format, ...); + +UIColor *_Nonnull DoricColor(NSNumber *_Nonnull number); + +NSBundle *_Nonnull DoricBundle(void); + +#ifndef DC_LOCK +#define DC_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); +#endif + +#ifndef DC_UNLOCK +#define DC_UNLOCK(lock) dispatch_semaphore_signal(lock); +#endif + +void ShowToast(NSString *_Nonnull text, DoricGravity gravity); + +UIImage *_Nonnull UIImageWithColor(UIColor *color); \ No newline at end of file diff --git a/Pod/Classes/Util/DoricUtil.m b/Pod/Classes/Util/DoricUtil.m new file mode 100644 index 00000000..bf41f236 --- /dev/null +++ b/Pod/Classes/Util/DoricUtil.m @@ -0,0 +1,88 @@ +/* + * Copyright [2019] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// DoricUtil.m +// Doric +// +// Created by pengfei.zhou on 2019/7/26. +// + +#import "DoricUtil.h" +#import "DoricContext.h" +#import "UIView+Doric.h" + +void DoricLog(NSString *_Nonnull format, ...) { + va_list args; + va_start(args, format); + NSLogv([NSString stringWithFormat:@"Doric:%@", format], args); + va_end(args); +} + +UIColor *DoricColor(NSNumber *number) { + CGFloat r, g, b, a; + long colorValue = [number longValue]; + a = ((colorValue >> 24) & 0xff) / 255.0f; + r = ((colorValue >> 16) & 0xff) / 255.0f; + g = ((colorValue >> 8) & 0xff) / 255.0f; + b = ((colorValue >> 0) & 0xff) / 255.0f; + return [UIColor colorWithRed:r green:g blue:b alpha:a]; +} + +NSBundle *DoricBundle() { + NSBundle *bundle = [NSBundle bundleForClass:[DoricContext class]]; + NSURL *url = [bundle URLForResource:@"Doric" withExtension:@"bundle"]; + return [NSBundle bundleWithURL:url]; +} + + +void ShowToast(NSString *text, DoricGravity gravity) { + UIView *superView = [UIApplication sharedApplication].windows.lastObject; + UILabel *label = [[UILabel alloc] init]; + label.font = [UIFont systemFontOfSize:20.f]; + label.text = text; + label.textAlignment = NSTextAlignmentCenter; + label.layer.masksToBounds = YES; + label.backgroundColor = [UIColor grayColor]; + label.textColor = [UIColor whiteColor]; + [label sizeToFit]; + label.width += 30; + label.height += 10; + label.layer.cornerRadius = label.height / 2; + label.centerX = superView.width / 2; + if ((gravity & BOTTOM) == BOTTOM) { + label.bottom = superView.height - 20; + } else if ((gravity & TOP) == TOP) { + label.top = 108; + } else { + label.centerY = (superView.height - 88) / 2; + } + + [superView addSubview:label]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [label removeFromSuperview]; + }); +} + +UIImage *UIImageWithColor(UIColor *color) { + CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); + UIGraphicsBeginImageContext(rect.size); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetFillColorWithColor(context, [color CGColor]); + CGContextFillRect(context, rect); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +}