Repository: MQCCoder/XYIAPKit Branch: master Commit: ebdb2c4a017b Files: 101 Total size: 414.4 KB Directory structure: gitextract_jyftf8bp/ ├── .gitattributes ├── .gitignore ├── .travis.yml ├── Example/ │ ├── Podfile │ ├── Pods/ │ │ ├── Local Podspecs/ │ │ │ └── XYIAPKit.podspec.json │ │ ├── Pods.xcodeproj/ │ │ │ └── project.pbxproj │ │ ├── Target Support Files/ │ │ │ ├── Pods-XYIAPKit_Example/ │ │ │ │ ├── Info.plist │ │ │ │ ├── Pods-XYIAPKit_Example-acknowledgements.markdown │ │ │ │ ├── Pods-XYIAPKit_Example-acknowledgements.plist │ │ │ │ ├── Pods-XYIAPKit_Example-dummy.m │ │ │ │ ├── Pods-XYIAPKit_Example-frameworks.sh │ │ │ │ ├── Pods-XYIAPKit_Example-resources.sh │ │ │ │ ├── Pods-XYIAPKit_Example-umbrella.h │ │ │ │ ├── Pods-XYIAPKit_Example.debug.xcconfig │ │ │ │ ├── Pods-XYIAPKit_Example.modulemap │ │ │ │ └── Pods-XYIAPKit_Example.release.xcconfig │ │ │ ├── Pods-XYIAPKit_Tests/ │ │ │ │ ├── Info.plist │ │ │ │ ├── Pods-XYIAPKit_Tests-acknowledgements.markdown │ │ │ │ ├── Pods-XYIAPKit_Tests-acknowledgements.plist │ │ │ │ ├── Pods-XYIAPKit_Tests-dummy.m │ │ │ │ ├── Pods-XYIAPKit_Tests-frameworks.sh │ │ │ │ ├── Pods-XYIAPKit_Tests-resources.sh │ │ │ │ ├── Pods-XYIAPKit_Tests-umbrella.h │ │ │ │ ├── Pods-XYIAPKit_Tests.debug.xcconfig │ │ │ │ ├── Pods-XYIAPKit_Tests.modulemap │ │ │ │ └── Pods-XYIAPKit_Tests.release.xcconfig │ │ │ ├── XYIAPKit/ │ │ │ │ ├── Info.plist │ │ │ │ ├── ResourceBundle-XYIAPKit-Info.plist │ │ │ │ ├── XYIAPKit-dummy.m │ │ │ │ ├── XYIAPKit-prefix.pch │ │ │ │ ├── XYIAPKit-umbrella.h │ │ │ │ ├── XYIAPKit.modulemap │ │ │ │ └── XYIAPKit.xcconfig │ │ │ └── YYModel/ │ │ │ ├── Info.plist │ │ │ ├── YYModel-dummy.m │ │ │ ├── YYModel-prefix.pch │ │ │ ├── YYModel-umbrella.h │ │ │ ├── YYModel.modulemap │ │ │ └── YYModel.xcconfig │ │ └── YYModel/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── YYModel/ │ │ ├── NSObject+YYModel.h │ │ ├── NSObject+YYModel.m │ │ ├── YYClassInfo.h │ │ ├── YYClassInfo.m │ │ └── YYModel.h │ ├── Tests/ │ │ ├── Tests-Info.plist │ │ ├── Tests-Prefix.pch │ │ ├── Tests.m │ │ └── en.lproj/ │ │ └── InfoPlist.strings │ ├── XYIAPKit/ │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Images.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── XYAppDelegate.h │ │ ├── XYAppDelegate.m │ │ ├── XYIAPKit-Info.plist │ │ ├── XYIAPKit-Prefix.pch │ │ ├── XYViewController.h │ │ ├── XYViewController.m │ │ ├── en.lproj/ │ │ │ └── InfoPlist.strings │ │ └── main.m │ ├── XYIAPKit.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── XYIAPKit-Example.xcscheme │ └── XYIAPKit.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── README.md ├── XYIAPKit/ │ ├── Assets/ │ │ ├── .gitkeep │ │ └── AppleIncRootCertificate.cer │ ├── Core/ │ │ ├── NSNotification+XYStore.h │ │ ├── NSNotification+XYStore.m │ │ ├── XYReceiptRefreshService.h │ │ ├── XYReceiptRefreshService.m │ │ ├── XYStore.h │ │ ├── XYStore.m │ │ ├── XYStoreProductService.h │ │ ├── XYStoreProductService.m │ │ └── XYStoreProtocol.h │ ├── Persistence/ │ │ ├── KeychainPersistence/ │ │ │ ├── XYStoreKeychainPersistence.h │ │ │ └── XYStoreKeychainPersistence.m │ │ └── UserDefaultPersistence/ │ │ ├── XYStoreTransaction.h │ │ ├── XYStoreTransaction.m │ │ ├── XYStoreUserDefaultsPersistence.h │ │ └── XYStoreUserDefaultsPersistence.m │ ├── Receipt/ │ │ ├── LocalReceiptVerify/ │ │ │ ├── XYAppReceipt.h │ │ │ ├── XYAppReceipt.m │ │ │ ├── XYStoreAppReceiptVerifier.h │ │ │ └── XYStoreAppReceiptVerifier.m │ │ └── iTunesReceiptVerify/ │ │ ├── NSDate+XYStoreExtension.h │ │ ├── NSDate+XYStoreExtension.m │ │ ├── XYInAppReceipt.h │ │ ├── XYInAppReceipt.m │ │ ├── XYStoreiTunesReceiptVerifier.h │ │ ├── XYStoreiTunesReceiptVerifier.m │ │ ├── XYiTunesReceipt.h │ │ ├── XYiTunesReceipt.m │ │ ├── XYiTunesResponse.h │ │ └── XYiTunesResponse.m │ └── XYIAPKit.h └── XYIAPKit.podspec ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.c linguist-language=Objective-C *.c linguist-language=Objective-C ================================================ FILE: .gitignore ================================================ # OS X .DS_Store # Xcode build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ *.xccheckout profile *.moved-aside DerivedData *.hmap *.ipa # Bundler .bundle Carthage # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # # Note: if you ignore the Pods directory, make sure to uncomment # `pod install` in .travis.yml # # Pods/ ================================================ FILE: .travis.yml ================================================ # references: # * http://www.objc.io/issue-6/travis-ci.html # * https://github.com/supermarin/xcpretty#usage osx_image: xcode7.3 language: objective-c # cache: cocoapods # podfile: Example/Podfile # before_install: # - gem install cocoapods # Since Travis is not always on latest version # - pod install --project-directory=Example script: - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/XYIAPKit.xcworkspace -scheme XYIAPKit-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty - pod lib lint ================================================ FILE: Example/Podfile ================================================ use_frameworks! target 'XYIAPKit_Example' do pod 'XYIAPKit', :path => '../' pod 'XYIAPKit/Core', :path => '../' pod 'XYIAPKit/iTunesReceiptVerify', :path => '../' pod 'XYIAPKit/UserDefaultPersistence', :path => '../' pod 'XYIAPKit/KeychainPersistence', :path => '../' target 'XYIAPKit_Tests' do inherit! :search_paths end end ================================================ FILE: Example/Pods/Local Podspecs/XYIAPKit.podspec.json ================================================ { "name": "XYIAPKit", "version": "0.8.0", "summary": "In App purchase sdk, support auto-renewable subscription", "description": "非常好用的内购组件,支持自动续期订阅的过期校验、票据检验、简单易用\n提供功能:\n 1)、产品查询\n 2)、产品购买\n 3)、恢复内购\n 4)、票据校验\n 5)、交易存储", "homepage": "https://github.com/mqc123450/XYIAPKit", "license": { "type": "MIT", "file": "LICENSE" }, "authors": { "qichao.ma": "“qichao.ma@quvideo.com”" }, "source": { "git": "https://github.com/mqc123450/XYIAPKit.git", "tag": "0.8.0" }, "platforms": { "ios": "8.0" }, "source_files": "XYIAPKit/*.h", "public_header_files": "XYIAPKit/*.h", "frameworks": "StoreKit", "requires_arc": true, "default_subspecs": "Core", "subspecs": [ { "name": "Core", "source_files": "XYIAPKit/Core/*.{h,m}" }, { "name": "KeychainPersistence", "dependencies": { "XYIAPKit/Core": [ ] }, "source_files": "XYIAPKit/Persistence/KeychainPersistence/*.{h,m}", "frameworks": "Security" }, { "name": "UserDefaultPersistence", "dependencies": { "XYIAPKit/Core": [ ] }, "source_files": "XYIAPKit/Persistence/UserDefaultPersistence/*.{h,m}" }, { "name": "iTunesReceiptVerify", "dependencies": { "XYIAPKit/Core": [ ], "YYModel": [ "~> 1.0.4" ] }, "source_files": "XYIAPKit/Receipt/iTunesReceiptVerify/*.{h,m}" } ] } ================================================ FILE: Example/Pods/Pods.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 0190CB2B09235A151B59F6B91A9FA50B /* XYiTunesResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EB35C135D92DD6C06FFD00ACDB7B51F /* XYiTunesResponse.m */; }; 061611EA41272DC0D8D48D6D9E461A86 /* XYStoreProductService.h in Headers */ = {isa = PBXBuildFile; fileRef = 3794F7EC8004D150065C1D1BD626F0AF /* XYStoreProductService.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0CB1FA41D725C8ED2B8510CFAD33420C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41C1500F3D1717C2A5296677BC797AE5 /* Foundation.framework */; }; 149F9CC2DAA28052C98A2731D5456E07 /* NSDate+XYStoreExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = BF3D7015065F30AE878C53F6486EABC9 /* NSDate+XYStoreExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1AC67C6EC7459FEF0084D64E321D8E25 /* YYModel-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C0682D46679BA668693995C21D19D6 /* YYModel-dummy.m */; }; 1BCA97FC9953C4DF9CC5045541DFCC33 /* XYReceiptRefreshService.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D57A73B0C9F628C7DE280E6D5C05B75 /* XYReceiptRefreshService.h */; settings = {ATTRIBUTES = (Public, ); }; }; 221B6F4D5BF0AB12740BC37986BA949F /* NSNotification+XYStore.h in Headers */ = {isa = PBXBuildFile; fileRef = A155BCF09E0CE8548FBF50253E9C6E0D /* NSNotification+XYStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25961FEA367E9289CE1B22C2FC130F43 /* NSObject+YYModel.h in Headers */ = {isa = PBXBuildFile; fileRef = B7D46F03FE96EAC5592C287E14BE8AB2 /* NSObject+YYModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2D1ADFC4982DDF689EB0CDDA189BEBAC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41C1500F3D1717C2A5296677BC797AE5 /* Foundation.framework */; }; 31FA590B80708EF135F5F11E72F781F2 /* Pods-XYIAPKit_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 793632D7AAC7377DF8231B0D1F7A8378 /* Pods-XYIAPKit_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3C9E9C6DA9242675DF7740C11A97A327 /* XYInAppReceipt.h in Headers */ = {isa = PBXBuildFile; fileRef = 36F995D2107846677EDA90FECC5C7C25 /* XYInAppReceipt.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3ECA77D9D15BB3CF74333352D0246469 /* XYiTunesReceipt.h in Headers */ = {isa = PBXBuildFile; fileRef = 9EB3272EDCC559E829F8CD7506AC88AE /* XYiTunesReceipt.h */; settings = {ATTRIBUTES = (Public, ); }; }; 44E7101C18001B970E5AFAE46082B0EC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41C1500F3D1717C2A5296677BC797AE5 /* Foundation.framework */; }; 4A2EFA98FA53D0DCDC396510A07EBCC0 /* YYClassInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 805AB6FB2D3B85037CCBBB0ED0327065 /* YYClassInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4E7AB2B44F4052611A1E840DC2C1D3B5 /* NSNotification+XYStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 024D52FD954BB82B64D91E85883F4981 /* NSNotification+XYStore.m */; }; 4EC0BD2033EE9ECB9F07929E0D6175AA /* XYStoreiTunesReceiptVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C4B76D9F95F8A546C04C42B7554A26C /* XYStoreiTunesReceiptVerifier.m */; }; 4F991B13E9B467054AC5D017CFADD1B4 /* XYStore.m in Sources */ = {isa = PBXBuildFile; fileRef = A421F09B1B37C6457F5A71423F98EEFF /* XYStore.m */; }; 54464427F27D9B1A5F9B9C328DC20C8E /* XYIAPKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 6CD849A26EEA37B208995F82F28653A4 /* XYIAPKit-dummy.m */; }; 54C60CFE5F5C1B74AC953A1141A60F02 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25F0A35DC256AC625352FE0944843621 /* Security.framework */; }; 5511B5013827AFC06D870B4A2D0DBC38 /* YYClassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 52EEB1BF0CC695DE3CFBFD144B740510 /* YYClassInfo.m */; }; 57975ECF69A086BDFEC683453F03EBF1 /* XYStoreKeychainPersistence.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C24B4C35F097AE05916B5422A2711BF /* XYStoreKeychainPersistence.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5A5019C06608706082054DDD45D6019F /* XYStoreUserDefaultsPersistence.h in Headers */ = {isa = PBXBuildFile; fileRef = C5C38C3EDDDD1890760FC2D891E2EDA5 /* XYStoreUserDefaultsPersistence.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5A8FC916815B1902D70DFAC5EFF2C9E6 /* Pods-XYIAPKit_Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 8615CDA113AA0D635C14EA85E841DC83 /* Pods-XYIAPKit_Example-dummy.m */; }; 5EFCCED49FB8320B428C21D73C4262B9 /* Pods-XYIAPKit_Tests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 9004122B064C700884E4B1F83E6C2EED /* Pods-XYIAPKit_Tests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 659C5EF1AE841C571CA88B1CC903FD75 /* XYStoreTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = F34B439BA7F999D91D8C6240438DFC8F /* XYStoreTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6B93A3500752BAF0D980B1569A79E364 /* YYModel-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 9EB8BFDD6C7FA43A5F3A40E0E838FA9A /* YYModel-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7501FF229371D018E3AA0B3E6F2CA8FA /* XYStoreKeychainPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 73314FFDB64100A9BAD50ADB0107932E /* XYStoreKeychainPersistence.m */; }; 776E12826C7E86F7B2A14B7CD4FEB36D /* YYModel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F538AD47DB5635F8DE7A63D5F98AC028 /* YYModel.framework */; }; 77C79C6C0162A0905AC0C94B9A222B4D /* XYStoreProductService.m in Sources */ = {isa = PBXBuildFile; fileRef = 6CB886BBFD5417AFA9148385A98B53F0 /* XYStoreProductService.m */; }; 7DB356D00A46476249AAA408E82378CC /* XYStoreTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 635A42EE1C4111088F322577200DDED8 /* XYStoreTransaction.m */; }; 8C9D5CA848CE4B852098330197BD3490 /* XYIAPKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 81C5D3B2633F8532CEF7754D2FC7F9E0 /* XYIAPKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 95C8A03D3719CC519333FAD651646D1D /* XYiTunesResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B273698DA49DC21DB77E3F96C5844DF /* XYiTunesResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCB20653388F1024B0E23D099409E54F /* XYStoreProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F739038BE0282B78767A7424C18EE6E /* XYStoreProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; BF26738274100CB6248BFDE882420650 /* Pods-XYIAPKit_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E2AD4841DC55C4FD36D565E19FEA3D67 /* Pods-XYIAPKit_Tests-dummy.m */; }; C340FF145DDBBC01B6E3D66087DD576E /* XYStoreiTunesReceiptVerifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 8155D4C9AF960C1807819740F3A417DC /* XYStoreiTunesReceiptVerifier.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA9B91B21DB8BE2389BF3FE4E977C0AD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 224A7D30F868EB84BED069D6915C4BFF /* CoreFoundation.framework */; }; CCD9756310DD3335BE91BD8E5CC4C183 /* XYStoreUserDefaultsPersistence.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C84BBD405109372802B21E4ACE8A580 /* XYStoreUserDefaultsPersistence.m */; }; CE4B4F8E4239CEDD4C096619BF695E33 /* XYIAPKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D52ACA47D09F6232840F443CA5621BC /* XYIAPKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; CE91CF8A87DFDF95ABAA9A6F2BFD7568 /* XYStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CE1BAA18E41A7406CE0D93089FD7366 /* XYStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; D64FF4398E941C83EA6A2727B3EBDC7E /* XYiTunesReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FD8C60FD1A86803FC188A73977486ED /* XYiTunesReceipt.m */; }; DCB012E3C059BCEF114485DD716F515B /* YYModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 5967A2C13205CF40B171004E24C1443B /* YYModel.h */; settings = {ATTRIBUTES = (Public, ); }; }; E58B7C7CCD1B2A8A39B335E2E69120F0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41C1500F3D1717C2A5296677BC797AE5 /* Foundation.framework */; }; E90D78D14A0B9B2FBDDE5A6F59A8A1AC /* XYReceiptRefreshService.m in Sources */ = {isa = PBXBuildFile; fileRef = 19839084974243B94E00BAE9ACBD4E33 /* XYReceiptRefreshService.m */; }; EF232F305F5CE9AD9EB2CCE712A8AB3F /* NSObject+YYModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 965C7A1073637327C2D52CCD3BEDBFE4 /* NSObject+YYModel.m */; }; F0537418745BDDF25CBF6680797D2BA3 /* NSDate+XYStoreExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = A9DF752B2379F8B37FE7FF91F57A61D4 /* NSDate+XYStoreExtension.m */; }; F66472A6744CC5B45BE6D046F0FC8937 /* XYInAppReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AD057D60C6F794C0E8B9E5B101C2ABE /* XYInAppReceipt.m */; }; FEE74D991DE3383479669038A4E7AA62 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 781A632EB53F2B7A7AF1A52C276D30A8 /* StoreKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 4EE354A6739A8819E64CDA5BD279E18C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; proxyType = 1; remoteGlobalIDString = C0774F21470BDBFFE195E55CCBCB18CD; remoteInfo = YYModel; }; 7EF4460EED0B05F16E7C608C644BB80D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; proxyType = 1; remoteGlobalIDString = 4ECD54A3EE5329BA3C267CC738C1FE18; remoteInfo = XYIAPKit; }; D5E546E2779D34CD7BE943AC6EA5EBAC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; proxyType = 1; remoteGlobalIDString = C0774F21470BDBFFE195E55CCBCB18CD; remoteInfo = YYModel; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 024D52FD954BB82B64D91E85883F4981 /* NSNotification+XYStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNotification+XYStore.m"; path = "XYIAPKit/Core/NSNotification+XYStore.m"; sourceTree = ""; }; 03BE81B1EFABD8459ACEA32C4407EF73 /* Pods-XYIAPKit_Example-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-XYIAPKit_Example-resources.sh"; sourceTree = ""; }; 09F087CF34FA268F3350238A98FD5F22 /* XYIAPKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = XYIAPKit.framework; path = XYIAPKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 10BD8B6B11E877BB19B64A601378A31C /* Pods-XYIAPKit_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = "Pods-XYIAPKit_Example.modulemap"; sourceTree = ""; }; 1293AD3298E12CD16282987431984A1C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; 1636A9F770CBEF2FB70DE8525F6A7F3E /* Pods-XYIAPKit_Tests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-XYIAPKit_Tests-acknowledgements.plist"; sourceTree = ""; }; 1886C14942AEEE2BAD604B9E8ADA92C6 /* Pods-XYIAPKit_Tests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-XYIAPKit_Tests-frameworks.sh"; sourceTree = ""; }; 19839084974243B94E00BAE9ACBD4E33 /* XYReceiptRefreshService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYReceiptRefreshService.m; path = XYIAPKit/Core/XYReceiptRefreshService.m; sourceTree = ""; }; 1B009B9D2F2AB405C1E7F901F4ECC60E /* Pods-XYIAPKit_Tests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = "Pods-XYIAPKit_Tests.modulemap"; sourceTree = ""; }; 1B6206B71A39167B5C517EF4C77B299E /* YYModel.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = YYModel.modulemap; sourceTree = ""; }; 1F739038BE0282B78767A7424C18EE6E /* XYStoreProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYStoreProtocol.h; path = XYIAPKit/Core/XYStoreProtocol.h; sourceTree = ""; }; 224A7D30F868EB84BED069D6915C4BFF /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/CoreFoundation.framework; sourceTree = DEVELOPER_DIR; }; 227FCB7D61B88DDEE976C4C7540CD0D9 /* Pods-XYIAPKit_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-XYIAPKit_Example-acknowledgements.markdown"; sourceTree = ""; }; 25F0A35DC256AC625352FE0944843621 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; 2AD057D60C6F794C0E8B9E5B101C2ABE /* XYInAppReceipt.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYInAppReceipt.m; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYInAppReceipt.m; sourceTree = ""; }; 2CE1BAA18E41A7406CE0D93089FD7366 /* XYStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYStore.h; path = XYIAPKit/Core/XYStore.h; sourceTree = ""; }; 2EB35C135D92DD6C06FFD00ACDB7B51F /* XYiTunesResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYiTunesResponse.m; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesResponse.m; sourceTree = ""; }; 3155740A2FA1B48EB955100E2F67A552 /* Pods-XYIAPKit_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-XYIAPKit_Example.release.xcconfig"; sourceTree = ""; }; 32C0682D46679BA668693995C21D19D6 /* YYModel-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "YYModel-dummy.m"; sourceTree = ""; }; 3408657EECD8D8897C8F30C388C59D2A /* Pods-XYIAPKit_Tests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-XYIAPKit_Tests-resources.sh"; sourceTree = ""; }; 36F995D2107846677EDA90FECC5C7C25 /* XYInAppReceipt.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYInAppReceipt.h; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYInAppReceipt.h; sourceTree = ""; }; 3794F7EC8004D150065C1D1BD626F0AF /* XYStoreProductService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYStoreProductService.h; path = XYIAPKit/Core/XYStoreProductService.h; sourceTree = ""; }; 3C4B76D9F95F8A546C04C42B7554A26C /* XYStoreiTunesReceiptVerifier.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYStoreiTunesReceiptVerifier.m; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYStoreiTunesReceiptVerifier.m; sourceTree = ""; }; 41B554B698D9A817D3C56478894BA8FA /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41C1500F3D1717C2A5296677BC797AE5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 4315C1F8CA3D2875D29A8AC5D97BDFD1 /* YYModel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = YYModel.framework; path = YYModel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 490EC24AC5D35552342324CCB6F3302C /* Pods-XYIAPKit_Tests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-XYIAPKit_Tests-acknowledgements.markdown"; sourceTree = ""; }; 4ABF94DC2D45C955486403AF9A5F3640 /* XYIAPKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XYIAPKit-prefix.pch"; sourceTree = ""; }; 4C84BBD405109372802B21E4ACE8A580 /* XYStoreUserDefaultsPersistence.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYStoreUserDefaultsPersistence.m; path = XYIAPKit/Persistence/UserDefaultPersistence/XYStoreUserDefaultsPersistence.m; sourceTree = ""; }; 50761F2D51A3BD7ADDF801EF382C4929 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5153F5F09CD668B257D6F37B774F3372 /* Pods-XYIAPKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-XYIAPKit_Tests.debug.xcconfig"; sourceTree = ""; }; 52EEB1BF0CC695DE3CFBFD144B740510 /* YYClassInfo.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYClassInfo.m; path = YYModel/YYClassInfo.m; sourceTree = ""; }; 57685DF090E684F90ED838FAB8653BAD /* XYIAPKit.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = XYIAPKit.xcconfig; sourceTree = ""; }; 5967A2C13205CF40B171004E24C1443B /* YYModel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYModel.h; path = YYModel/YYModel.h; sourceTree = ""; }; 5C24B4C35F097AE05916B5422A2711BF /* XYStoreKeychainPersistence.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYStoreKeychainPersistence.h; path = XYIAPKit/Persistence/KeychainPersistence/XYStoreKeychainPersistence.h; sourceTree = ""; }; 5D52ACA47D09F6232840F443CA5621BC /* XYIAPKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "XYIAPKit-umbrella.h"; sourceTree = ""; }; 5D57A73B0C9F628C7DE280E6D5C05B75 /* XYReceiptRefreshService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYReceiptRefreshService.h; path = XYIAPKit/Core/XYReceiptRefreshService.h; sourceTree = ""; }; 635A42EE1C4111088F322577200DDED8 /* XYStoreTransaction.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYStoreTransaction.m; path = XYIAPKit/Persistence/UserDefaultPersistence/XYStoreTransaction.m; sourceTree = ""; }; 6515E9142A8C44450A68DFD3316814F5 /* Pods-XYIAPKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-XYIAPKit_Example.debug.xcconfig"; sourceTree = ""; }; 6CB886BBFD5417AFA9148385A98B53F0 /* XYStoreProductService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYStoreProductService.m; path = XYIAPKit/Core/XYStoreProductService.m; sourceTree = ""; }; 6CD849A26EEA37B208995F82F28653A4 /* XYIAPKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "XYIAPKit-dummy.m"; sourceTree = ""; }; 73314FFDB64100A9BAD50ADB0107932E /* XYStoreKeychainPersistence.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYStoreKeychainPersistence.m; path = XYIAPKit/Persistence/KeychainPersistence/XYStoreKeychainPersistence.m; sourceTree = ""; }; 781A632EB53F2B7A7AF1A52C276D30A8 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.3.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; 793632D7AAC7377DF8231B0D1F7A8378 /* Pods-XYIAPKit_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-XYIAPKit_Example-umbrella.h"; sourceTree = ""; }; 7F2FAD82758807D8F42CD51D40983F36 /* Pods-XYIAPKit_Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-XYIAPKit_Example-acknowledgements.plist"; sourceTree = ""; }; 7FD8C60FD1A86803FC188A73977486ED /* XYiTunesReceipt.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYiTunesReceipt.m; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesReceipt.m; sourceTree = ""; }; 805AB6FB2D3B85037CCBBB0ED0327065 /* YYClassInfo.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYClassInfo.h; path = YYModel/YYClassInfo.h; sourceTree = ""; }; 8155D4C9AF960C1807819740F3A417DC /* XYStoreiTunesReceiptVerifier.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYStoreiTunesReceiptVerifier.h; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYStoreiTunesReceiptVerifier.h; sourceTree = ""; }; 81C5D3B2633F8532CEF7754D2FC7F9E0 /* XYIAPKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYIAPKit.h; path = XYIAPKit/XYIAPKit.h; sourceTree = ""; }; 8615CDA113AA0D635C14EA85E841DC83 /* Pods-XYIAPKit_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-XYIAPKit_Example-dummy.m"; sourceTree = ""; }; 8CFD8BFBAB73DBF02097DACCD0271C7C /* Pods-XYIAPKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-XYIAPKit_Tests.release.xcconfig"; sourceTree = ""; }; 9004122B064C700884E4B1F83E6C2EED /* Pods-XYIAPKit_Tests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-XYIAPKit_Tests-umbrella.h"; sourceTree = ""; }; 93A332424F48023EB611C173EB58D0C2 /* Pods_XYIAPKit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_XYIAPKit_Tests.framework; path = "Pods-XYIAPKit_Tests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 965C7A1073637327C2D52CCD3BEDBFE4 /* NSObject+YYModel.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+YYModel.m"; path = "YYModel/NSObject+YYModel.m"; sourceTree = ""; }; 9B273698DA49DC21DB77E3F96C5844DF /* XYiTunesResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYiTunesResponse.h; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesResponse.h; sourceTree = ""; }; 9C5BB56959983EABC0C5B3B44CBEF36F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = ""; }; 9EB3272EDCC559E829F8CD7506AC88AE /* XYiTunesReceipt.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYiTunesReceipt.h; path = XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesReceipt.h; sourceTree = ""; }; 9EB8BFDD6C7FA43A5F3A40E0E838FA9A /* YYModel-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "YYModel-umbrella.h"; sourceTree = ""; }; A155BCF09E0CE8548FBF50253E9C6E0D /* NSNotification+XYStore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSNotification+XYStore.h"; path = "XYIAPKit/Core/NSNotification+XYStore.h"; sourceTree = ""; }; A2014A34A0545E453DAEC4F1ACA6EFC1 /* XYIAPKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; path = XYIAPKit.modulemap; sourceTree = ""; }; A421F09B1B37C6457F5A71423F98EEFF /* XYStore.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = XYStore.m; path = XYIAPKit/Core/XYStore.m; sourceTree = ""; }; A5C98E7CF02C568CFE18C996E0CE5855 /* Pods-XYIAPKit_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-XYIAPKit_Example-frameworks.sh"; sourceTree = ""; }; A9DF752B2379F8B37FE7FF91F57A61D4 /* NSDate+XYStoreExtension.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSDate+XYStoreExtension.m"; path = "XYIAPKit/Receipt/iTunesReceiptVerify/NSDate+XYStoreExtension.m"; sourceTree = ""; }; B7D46F03FE96EAC5592C287E14BE8AB2 /* NSObject+YYModel.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSObject+YYModel.h"; path = "YYModel/NSObject+YYModel.h"; sourceTree = ""; }; BF3D7015065F30AE878C53F6486EABC9 /* NSDate+XYStoreExtension.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSDate+XYStoreExtension.h"; path = "XYIAPKit/Receipt/iTunesReceiptVerify/NSDate+XYStoreExtension.h"; sourceTree = ""; }; C09F538BC783989A268B6A3C2CC9741E /* XYIAPKit.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; path = XYIAPKit.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; C3C7EDE62C882DD4D17C9B00BAA0A454 /* YYModel.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = YYModel.xcconfig; sourceTree = ""; }; C5C38C3EDDDD1890760FC2D891E2EDA5 /* XYStoreUserDefaultsPersistence.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYStoreUserDefaultsPersistence.h; path = XYIAPKit/Persistence/UserDefaultPersistence/XYStoreUserDefaultsPersistence.h; sourceTree = ""; }; CD76A9661DB93661D7E5D3047DDB078A /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E017EDC1FFAF720060C16F9F6678643C /* YYModel-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "YYModel-prefix.pch"; sourceTree = ""; }; E2AD4841DC55C4FD36D565E19FEA3D67 /* Pods-XYIAPKit_Tests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-XYIAPKit_Tests-dummy.m"; sourceTree = ""; }; F34B439BA7F999D91D8C6240438DFC8F /* XYStoreTransaction.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = XYStoreTransaction.h; path = XYIAPKit/Persistence/UserDefaultPersistence/XYStoreTransaction.h; sourceTree = ""; }; F538AD47DB5635F8DE7A63D5F98AC028 /* YYModel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YYModel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F613DF08298A7EE24DA8B33580F70C17 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FED033A3212E7D1603232AED76FE0E97 /* Pods_XYIAPKit_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_XYIAPKit_Example.framework; path = "Pods-XYIAPKit_Example.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 087A90F83DE38641435916A70C2CE920 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 44E7101C18001B970E5AFAE46082B0EC /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 21D350EB95D3160E52755CC44D37336A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0CB1FA41D725C8ED2B8510CFAD33420C /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 772FE6F29C1A86B44EB34B6D45251773 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2D1ADFC4982DDF689EB0CDDA189BEBAC /* Foundation.framework in Frameworks */, 54C60CFE5F5C1B74AC953A1141A60F02 /* Security.framework in Frameworks */, FEE74D991DE3383479669038A4E7AA62 /* StoreKit.framework in Frameworks */, 776E12826C7E86F7B2A14B7CD4FEB36D /* YYModel.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; B966B3AACD2B1AFE7A25ADBA691799ED /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( CA9B91B21DB8BE2389BF3FE4E977C0AD /* CoreFoundation.framework in Frameworks */, E58B7C7CCD1B2A8A39B335E2E69120F0 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 06D5BCDFCE1D1B4ED2A1D46C5155CEDA /* Pods-XYIAPKit_Tests */ = { isa = PBXGroup; children = ( CD76A9661DB93661D7E5D3047DDB078A /* Info.plist */, 1B009B9D2F2AB405C1E7F901F4ECC60E /* Pods-XYIAPKit_Tests.modulemap */, 490EC24AC5D35552342324CCB6F3302C /* Pods-XYIAPKit_Tests-acknowledgements.markdown */, 1636A9F770CBEF2FB70DE8525F6A7F3E /* Pods-XYIAPKit_Tests-acknowledgements.plist */, E2AD4841DC55C4FD36D565E19FEA3D67 /* Pods-XYIAPKit_Tests-dummy.m */, 1886C14942AEEE2BAD604B9E8ADA92C6 /* Pods-XYIAPKit_Tests-frameworks.sh */, 3408657EECD8D8897C8F30C388C59D2A /* Pods-XYIAPKit_Tests-resources.sh */, 9004122B064C700884E4B1F83E6C2EED /* Pods-XYIAPKit_Tests-umbrella.h */, 5153F5F09CD668B257D6F37B774F3372 /* Pods-XYIAPKit_Tests.debug.xcconfig */, 8CFD8BFBAB73DBF02097DACCD0271C7C /* Pods-XYIAPKit_Tests.release.xcconfig */, ); name = "Pods-XYIAPKit_Tests"; path = "Target Support Files/Pods-XYIAPKit_Tests"; sourceTree = ""; }; 09380EA71E3EBD81A87882157DB36C71 /* Pods */ = { isa = PBXGroup; children = ( 0EADFB64E1E3B0D228C305C49EE28D12 /* YYModel */, ); name = Pods; sourceTree = ""; }; 0EADFB64E1E3B0D228C305C49EE28D12 /* YYModel */ = { isa = PBXGroup; children = ( B7D46F03FE96EAC5592C287E14BE8AB2 /* NSObject+YYModel.h */, 965C7A1073637327C2D52CCD3BEDBFE4 /* NSObject+YYModel.m */, 805AB6FB2D3B85037CCBBB0ED0327065 /* YYClassInfo.h */, 52EEB1BF0CC695DE3CFBFD144B740510 /* YYClassInfo.m */, 5967A2C13205CF40B171004E24C1443B /* YYModel.h */, 6CBFD2F103FFE1F250918CCE24B686CA /* Support Files */, ); name = YYModel; path = YYModel; sourceTree = ""; }; 13A9BD2FDB0B790406639BA9B57C7F16 /* Support Files */ = { isa = PBXGroup; children = ( 41B554B698D9A817D3C56478894BA8FA /* Info.plist */, A2014A34A0545E453DAEC4F1ACA6EFC1 /* XYIAPKit.modulemap */, 57685DF090E684F90ED838FAB8653BAD /* XYIAPKit.xcconfig */, 6CD849A26EEA37B208995F82F28653A4 /* XYIAPKit-dummy.m */, 4ABF94DC2D45C955486403AF9A5F3640 /* XYIAPKit-prefix.pch */, 5D52ACA47D09F6232840F443CA5621BC /* XYIAPKit-umbrella.h */, ); name = "Support Files"; path = "Example/Pods/Target Support Files/XYIAPKit"; sourceTree = ""; }; 18C988F098DB04E0A86257BD6080E78F /* Development Pods */ = { isa = PBXGroup; children = ( 87688B8A352A8CA93EB643A620EE190C /* XYIAPKit */, ); name = "Development Pods"; sourceTree = ""; }; 1BE391897633F124B6BFA29300C9C7CF /* KeychainPersistence */ = { isa = PBXGroup; children = ( 5C24B4C35F097AE05916B5422A2711BF /* XYStoreKeychainPersistence.h */, 73314FFDB64100A9BAD50ADB0107932E /* XYStoreKeychainPersistence.m */, ); name = KeychainPersistence; sourceTree = ""; }; 5B28468599CE82A970E6153A9F29E4B0 /* Pods-XYIAPKit_Example */ = { isa = PBXGroup; children = ( 50761F2D51A3BD7ADDF801EF382C4929 /* Info.plist */, 10BD8B6B11E877BB19B64A601378A31C /* Pods-XYIAPKit_Example.modulemap */, 227FCB7D61B88DDEE976C4C7540CD0D9 /* Pods-XYIAPKit_Example-acknowledgements.markdown */, 7F2FAD82758807D8F42CD51D40983F36 /* Pods-XYIAPKit_Example-acknowledgements.plist */, 8615CDA113AA0D635C14EA85E841DC83 /* Pods-XYIAPKit_Example-dummy.m */, A5C98E7CF02C568CFE18C996E0CE5855 /* Pods-XYIAPKit_Example-frameworks.sh */, 03BE81B1EFABD8459ACEA32C4407EF73 /* Pods-XYIAPKit_Example-resources.sh */, 793632D7AAC7377DF8231B0D1F7A8378 /* Pods-XYIAPKit_Example-umbrella.h */, 6515E9142A8C44450A68DFD3316814F5 /* Pods-XYIAPKit_Example.debug.xcconfig */, 3155740A2FA1B48EB955100E2F67A552 /* Pods-XYIAPKit_Example.release.xcconfig */, ); name = "Pods-XYIAPKit_Example"; path = "Target Support Files/Pods-XYIAPKit_Example"; sourceTree = ""; }; 68D4D76D28BEFB10552E5EF9CA6636AF /* Pod */ = { isa = PBXGroup; children = ( 9C5BB56959983EABC0C5B3B44CBEF36F /* LICENSE */, 1293AD3298E12CD16282987431984A1C /* README.md */, C09F538BC783989A268B6A3C2CC9741E /* XYIAPKit.podspec */, ); name = Pod; sourceTree = ""; }; 6CBFD2F103FFE1F250918CCE24B686CA /* Support Files */ = { isa = PBXGroup; children = ( F613DF08298A7EE24DA8B33580F70C17 /* Info.plist */, 1B6206B71A39167B5C517EF4C77B299E /* YYModel.modulemap */, C3C7EDE62C882DD4D17C9B00BAA0A454 /* YYModel.xcconfig */, 32C0682D46679BA668693995C21D19D6 /* YYModel-dummy.m */, E017EDC1FFAF720060C16F9F6678643C /* YYModel-prefix.pch */, 9EB8BFDD6C7FA43A5F3A40E0E838FA9A /* YYModel-umbrella.h */, ); name = "Support Files"; path = "../Target Support Files/YYModel"; sourceTree = ""; }; 7DB346D0F39D3F0E887471402A8071AB = { isa = PBXGroup; children = ( 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, 18C988F098DB04E0A86257BD6080E78F /* Development Pods */, EE02E376E3DC7E7C8C1BE5DCC0B99A7E /* Frameworks */, 09380EA71E3EBD81A87882157DB36C71 /* Pods */, F2B887FA5F6C6B51A743E04B103E62F8 /* Products */, A9EEFA94774E7345E6D1F65EC2AA03E2 /* Targets Support Files */, ); sourceTree = ""; }; 87688B8A352A8CA93EB643A620EE190C /* XYIAPKit */ = { isa = PBXGroup; children = ( 81C5D3B2633F8532CEF7754D2FC7F9E0 /* XYIAPKit.h */, B76475C7A19FB53F68110D96521CCA59 /* Core */, E7ECAA0257FCB5125DA1478EB8834638 /* iTunesReceiptVerify */, 1BE391897633F124B6BFA29300C9C7CF /* KeychainPersistence */, 68D4D76D28BEFB10552E5EF9CA6636AF /* Pod */, 13A9BD2FDB0B790406639BA9B57C7F16 /* Support Files */, F14E70A12F12C0AD619DC1CC4D30523E /* UserDefaultPersistence */, ); name = XYIAPKit; path = ../..; sourceTree = ""; }; A9EEFA94774E7345E6D1F65EC2AA03E2 /* Targets Support Files */ = { isa = PBXGroup; children = ( 5B28468599CE82A970E6153A9F29E4B0 /* Pods-XYIAPKit_Example */, 06D5BCDFCE1D1B4ED2A1D46C5155CEDA /* Pods-XYIAPKit_Tests */, ); name = "Targets Support Files"; sourceTree = ""; }; AE3361776E6CB588EA7843C980927B90 /* iOS */ = { isa = PBXGroup; children = ( 224A7D30F868EB84BED069D6915C4BFF /* CoreFoundation.framework */, 41C1500F3D1717C2A5296677BC797AE5 /* Foundation.framework */, 25F0A35DC256AC625352FE0944843621 /* Security.framework */, 781A632EB53F2B7A7AF1A52C276D30A8 /* StoreKit.framework */, ); name = iOS; sourceTree = ""; }; B76475C7A19FB53F68110D96521CCA59 /* Core */ = { isa = PBXGroup; children = ( A155BCF09E0CE8548FBF50253E9C6E0D /* NSNotification+XYStore.h */, 024D52FD954BB82B64D91E85883F4981 /* NSNotification+XYStore.m */, 5D57A73B0C9F628C7DE280E6D5C05B75 /* XYReceiptRefreshService.h */, 19839084974243B94E00BAE9ACBD4E33 /* XYReceiptRefreshService.m */, 2CE1BAA18E41A7406CE0D93089FD7366 /* XYStore.h */, A421F09B1B37C6457F5A71423F98EEFF /* XYStore.m */, 3794F7EC8004D150065C1D1BD626F0AF /* XYStoreProductService.h */, 6CB886BBFD5417AFA9148385A98B53F0 /* XYStoreProductService.m */, 1F739038BE0282B78767A7424C18EE6E /* XYStoreProtocol.h */, ); name = Core; sourceTree = ""; }; E7ECAA0257FCB5125DA1478EB8834638 /* iTunesReceiptVerify */ = { isa = PBXGroup; children = ( BF3D7015065F30AE878C53F6486EABC9 /* NSDate+XYStoreExtension.h */, A9DF752B2379F8B37FE7FF91F57A61D4 /* NSDate+XYStoreExtension.m */, 36F995D2107846677EDA90FECC5C7C25 /* XYInAppReceipt.h */, 2AD057D60C6F794C0E8B9E5B101C2ABE /* XYInAppReceipt.m */, 9EB3272EDCC559E829F8CD7506AC88AE /* XYiTunesReceipt.h */, 7FD8C60FD1A86803FC188A73977486ED /* XYiTunesReceipt.m */, 9B273698DA49DC21DB77E3F96C5844DF /* XYiTunesResponse.h */, 2EB35C135D92DD6C06FFD00ACDB7B51F /* XYiTunesResponse.m */, 8155D4C9AF960C1807819740F3A417DC /* XYStoreiTunesReceiptVerifier.h */, 3C4B76D9F95F8A546C04C42B7554A26C /* XYStoreiTunesReceiptVerifier.m */, ); name = iTunesReceiptVerify; sourceTree = ""; }; EE02E376E3DC7E7C8C1BE5DCC0B99A7E /* Frameworks */ = { isa = PBXGroup; children = ( F538AD47DB5635F8DE7A63D5F98AC028 /* YYModel.framework */, AE3361776E6CB588EA7843C980927B90 /* iOS */, ); name = Frameworks; sourceTree = ""; }; F14E70A12F12C0AD619DC1CC4D30523E /* UserDefaultPersistence */ = { isa = PBXGroup; children = ( F34B439BA7F999D91D8C6240438DFC8F /* XYStoreTransaction.h */, 635A42EE1C4111088F322577200DDED8 /* XYStoreTransaction.m */, C5C38C3EDDDD1890760FC2D891E2EDA5 /* XYStoreUserDefaultsPersistence.h */, 4C84BBD405109372802B21E4ACE8A580 /* XYStoreUserDefaultsPersistence.m */, ); name = UserDefaultPersistence; sourceTree = ""; }; F2B887FA5F6C6B51A743E04B103E62F8 /* Products */ = { isa = PBXGroup; children = ( FED033A3212E7D1603232AED76FE0E97 /* Pods_XYIAPKit_Example.framework */, 93A332424F48023EB611C173EB58D0C2 /* Pods_XYIAPKit_Tests.framework */, 09F087CF34FA268F3350238A98FD5F22 /* XYIAPKit.framework */, 4315C1F8CA3D2875D29A8AC5D97BDFD1 /* YYModel.framework */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 3A3ACFC50D255C7178E5814CB26830B1 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 31FA590B80708EF135F5F11E72F781F2 /* Pods-XYIAPKit_Example-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; A971EC61C1A2979C1C44B112F5FFD21F /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 5EFCCED49FB8320B428C21D73C4262B9 /* Pods-XYIAPKit_Tests-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; B382A2ED6B059D7355147875F936071E /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 25961FEA367E9289CE1B22C2FC130F43 /* NSObject+YYModel.h in Headers */, 4A2EFA98FA53D0DCDC396510A07EBCC0 /* YYClassInfo.h in Headers */, 6B93A3500752BAF0D980B1569A79E364 /* YYModel-umbrella.h in Headers */, DCB012E3C059BCEF114485DD716F515B /* YYModel.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; BA4C28D77710A12F103F8C14AFC51DA2 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 149F9CC2DAA28052C98A2731D5456E07 /* NSDate+XYStoreExtension.h in Headers */, 221B6F4D5BF0AB12740BC37986BA949F /* NSNotification+XYStore.h in Headers */, CE4B4F8E4239CEDD4C096619BF695E33 /* XYIAPKit-umbrella.h in Headers */, 8C9D5CA848CE4B852098330197BD3490 /* XYIAPKit.h in Headers */, 3C9E9C6DA9242675DF7740C11A97A327 /* XYInAppReceipt.h in Headers */, 3ECA77D9D15BB3CF74333352D0246469 /* XYiTunesReceipt.h in Headers */, 95C8A03D3719CC519333FAD651646D1D /* XYiTunesResponse.h in Headers */, 1BCA97FC9953C4DF9CC5045541DFCC33 /* XYReceiptRefreshService.h in Headers */, CE91CF8A87DFDF95ABAA9A6F2BFD7568 /* XYStore.h in Headers */, C340FF145DDBBC01B6E3D66087DD576E /* XYStoreiTunesReceiptVerifier.h in Headers */, 57975ECF69A086BDFEC683453F03EBF1 /* XYStoreKeychainPersistence.h in Headers */, 061611EA41272DC0D8D48D6D9E461A86 /* XYStoreProductService.h in Headers */, BCB20653388F1024B0E23D099409E54F /* XYStoreProtocol.h in Headers */, 659C5EF1AE841C571CA88B1CC903FD75 /* XYStoreTransaction.h in Headers */, 5A5019C06608706082054DDD45D6019F /* XYStoreUserDefaultsPersistence.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 44E07BE6285328DE74E7DFB2F59998CC /* Pods-XYIAPKit_Tests */ = { isa = PBXNativeTarget; buildConfigurationList = AA305A28AD005A11A7111A786DD36650 /* Build configuration list for PBXNativeTarget "Pods-XYIAPKit_Tests" */; buildPhases = ( 566EF5D5FFB5F8AD441162E9902413FF /* Sources */, 087A90F83DE38641435916A70C2CE920 /* Frameworks */, A971EC61C1A2979C1C44B112F5FFD21F /* Headers */, ); buildRules = ( ); dependencies = ( ); name = "Pods-XYIAPKit_Tests"; productName = "Pods-XYIAPKit_Tests"; productReference = 93A332424F48023EB611C173EB58D0C2 /* Pods_XYIAPKit_Tests.framework */; productType = "com.apple.product-type.framework"; }; 4ECD54A3EE5329BA3C267CC738C1FE18 /* XYIAPKit */ = { isa = PBXNativeTarget; buildConfigurationList = 6D94804578581EA04C0E1BA4190A59DE /* Build configuration list for PBXNativeTarget "XYIAPKit" */; buildPhases = ( DC1CB4D2E43301FD625914C50A59E3E9 /* Sources */, 772FE6F29C1A86B44EB34B6D45251773 /* Frameworks */, BA4C28D77710A12F103F8C14AFC51DA2 /* Headers */, ); buildRules = ( ); dependencies = ( CB5C1DDD78A169406E280B00309A8BDF /* PBXTargetDependency */, ); name = XYIAPKit; productName = XYIAPKit; productReference = 09F087CF34FA268F3350238A98FD5F22 /* XYIAPKit.framework */; productType = "com.apple.product-type.framework"; }; 5C61C506FCDE0B96EA3ACE01BBDA2004 /* Pods-XYIAPKit_Example */ = { isa = PBXNativeTarget; buildConfigurationList = FE3FF5EDFD8EE00CDE08D1BD60940FAB /* Build configuration list for PBXNativeTarget "Pods-XYIAPKit_Example" */; buildPhases = ( B2BCFD9E5D4089C5BF64E86021B6AC1A /* Sources */, 21D350EB95D3160E52755CC44D37336A /* Frameworks */, 3A3ACFC50D255C7178E5814CB26830B1 /* Headers */, ); buildRules = ( ); dependencies = ( FFE3C2A5231C3AB9776AB44445A58AF0 /* PBXTargetDependency */, B33DC2E66CE1814BF0BC280054BA1131 /* PBXTargetDependency */, ); name = "Pods-XYIAPKit_Example"; productName = "Pods-XYIAPKit_Example"; productReference = FED033A3212E7D1603232AED76FE0E97 /* Pods_XYIAPKit_Example.framework */; productType = "com.apple.product-type.framework"; }; C0774F21470BDBFFE195E55CCBCB18CD /* YYModel */ = { isa = PBXNativeTarget; buildConfigurationList = 0622F73CFD4C7E21BD1076372320CCB4 /* Build configuration list for PBXNativeTarget "YYModel" */; buildPhases = ( 5A8AC762D71950F42E40CF8BAE29D758 /* Sources */, B966B3AACD2B1AFE7A25ADBA691799ED /* Frameworks */, B382A2ED6B059D7355147875F936071E /* Headers */, ); buildRules = ( ); dependencies = ( ); name = YYModel; productName = YYModel; productReference = 4315C1F8CA3D2875D29A8AC5D97BDFD1 /* YYModel.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ D41D8CD98F00B204E9800998ECF8427E /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; LastUpgradeCheck = 0700; }; buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 7DB346D0F39D3F0E887471402A8071AB; productRefGroup = F2B887FA5F6C6B51A743E04B103E62F8 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 5C61C506FCDE0B96EA3ACE01BBDA2004 /* Pods-XYIAPKit_Example */, 44E07BE6285328DE74E7DFB2F59998CC /* Pods-XYIAPKit_Tests */, 4ECD54A3EE5329BA3C267CC738C1FE18 /* XYIAPKit */, C0774F21470BDBFFE195E55CCBCB18CD /* YYModel */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ 566EF5D5FFB5F8AD441162E9902413FF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( BF26738274100CB6248BFDE882420650 /* Pods-XYIAPKit_Tests-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 5A8AC762D71950F42E40CF8BAE29D758 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( EF232F305F5CE9AD9EB2CCE712A8AB3F /* NSObject+YYModel.m in Sources */, 5511B5013827AFC06D870B4A2D0DBC38 /* YYClassInfo.m in Sources */, 1AC67C6EC7459FEF0084D64E321D8E25 /* YYModel-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B2BCFD9E5D4089C5BF64E86021B6AC1A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 5A8FC916815B1902D70DFAC5EFF2C9E6 /* Pods-XYIAPKit_Example-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; DC1CB4D2E43301FD625914C50A59E3E9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F0537418745BDDF25CBF6680797D2BA3 /* NSDate+XYStoreExtension.m in Sources */, 4E7AB2B44F4052611A1E840DC2C1D3B5 /* NSNotification+XYStore.m in Sources */, 54464427F27D9B1A5F9B9C328DC20C8E /* XYIAPKit-dummy.m in Sources */, F66472A6744CC5B45BE6D046F0FC8937 /* XYInAppReceipt.m in Sources */, D64FF4398E941C83EA6A2727B3EBDC7E /* XYiTunesReceipt.m in Sources */, 0190CB2B09235A151B59F6B91A9FA50B /* XYiTunesResponse.m in Sources */, E90D78D14A0B9B2FBDDE5A6F59A8A1AC /* XYReceiptRefreshService.m in Sources */, 4F991B13E9B467054AC5D017CFADD1B4 /* XYStore.m in Sources */, 4EC0BD2033EE9ECB9F07929E0D6175AA /* XYStoreiTunesReceiptVerifier.m in Sources */, 7501FF229371D018E3AA0B3E6F2CA8FA /* XYStoreKeychainPersistence.m in Sources */, 77C79C6C0162A0905AC0C94B9A222B4D /* XYStoreProductService.m in Sources */, 7DB356D00A46476249AAA408E82378CC /* XYStoreTransaction.m in Sources */, CCD9756310DD3335BE91BD8E5CC4C183 /* XYStoreUserDefaultsPersistence.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ B33DC2E66CE1814BF0BC280054BA1131 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = YYModel; target = C0774F21470BDBFFE195E55CCBCB18CD /* YYModel */; targetProxy = D5E546E2779D34CD7BE943AC6EA5EBAC /* PBXContainerItemProxy */; }; CB5C1DDD78A169406E280B00309A8BDF /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = YYModel; target = C0774F21470BDBFFE195E55CCBCB18CD /* YYModel */; targetProxy = 4EE354A6739A8819E64CDA5BD279E18C /* PBXContainerItemProxy */; }; FFE3C2A5231C3AB9776AB44445A58AF0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = XYIAPKit; target = 4ECD54A3EE5329BA3C267CC738C1FE18 /* XYIAPKit */; targetProxy = 7EF4460EED0B05F16E7C608C644BB80D /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 201D6ADB48BE8E19C034ECD938798C69 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 6515E9142A8C44450A68DFD3316814F5 /* Pods-XYIAPKit_Example.debug.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-XYIAPKit_Example/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_XYIAPKit_Example; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 2B4A2119648244D65599A0E86589BFFA /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5153F5F09CD668B257D6F37B774F3372 /* Pods-XYIAPKit_Tests.debug.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-XYIAPKit_Tests/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_XYIAPKit_Tests; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 33DA7F43A1D2FA3C74A8C8FC246E1FA6 /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = 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_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_SIGNING_REQUIRED = NO; 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 = 9.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; STRIP_INSTALLED_PRODUCT = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SYMROOT = "${SRCROOT}/../build"; }; name = Debug; }; 44D0A1E246DB2003E201E4DA0E8C11E0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 57685DF090E684F90ED838FAB8653BAD /* XYIAPKit.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/XYIAPKit/XYIAPKit-prefix.pch"; INFOPLIST_FILE = "Target Support Files/XYIAPKit/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/XYIAPKit/XYIAPKit.modulemap"; PRODUCT_NAME = XYIAPKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 5F60B76B0319D4AF76C114EBA12223A1 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3155740A2FA1B48EB955100E2F67A552 /* Pods-XYIAPKit_Example.release.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-XYIAPKit_Example/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_XYIAPKit_Example; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 7201017C2625B36FDBD2126594F77939 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 57685DF090E684F90ED838FAB8653BAD /* XYIAPKit.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/XYIAPKit/XYIAPKit-prefix.pch"; INFOPLIST_FILE = "Target Support Files/XYIAPKit/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/XYIAPKit/XYIAPKit.modulemap"; PRODUCT_NAME = XYIAPKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 731DC216E1A58545B559F6E0A2418060 /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = 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_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_SIGNING_REQUIRED = NO; 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 = 9.3; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; STRIP_INSTALLED_PRODUCT = NO; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; AC6D525A720795C9FE57D6E07833EF9A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8CFD8BFBAB73DBF02097DACCD0271C7C /* Pods-XYIAPKit_Tests.release.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "Target Support Files/Pods-XYIAPKit_Tests/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MACH_O_TYPE = staticlib; MODULEMAP_FILE = "Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests.modulemap"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = Pods_XYIAPKit_Tests; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; DE8E1EE8F87A58998D6817104D09B30B /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = C3C7EDE62C882DD4D17C9B00BAA0A454 /* YYModel.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/YYModel/YYModel-prefix.pch"; INFOPLIST_FILE = "Target Support Files/YYModel/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/YYModel/YYModel.modulemap"; PRODUCT_NAME = YYModel; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; F8B16E8C5DDAAD26471F157BA14D0B1E /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = C3C7EDE62C882DD4D17C9B00BAA0A454 /* YYModel.xcconfig */; buildSettings = { CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREFIX_HEADER = "Target Support Files/YYModel/YYModel-prefix.pch"; INFOPLIST_FILE = "Target Support Files/YYModel/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = "Target Support Files/YYModel/YYModel.modulemap"; PRODUCT_NAME = YYModel; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0622F73CFD4C7E21BD1076372320CCB4 /* Build configuration list for PBXNativeTarget "YYModel" */ = { isa = XCConfigurationList; buildConfigurations = ( DE8E1EE8F87A58998D6817104D09B30B /* Debug */, F8B16E8C5DDAAD26471F157BA14D0B1E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( 33DA7F43A1D2FA3C74A8C8FC246E1FA6 /* Debug */, 731DC216E1A58545B559F6E0A2418060 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6D94804578581EA04C0E1BA4190A59DE /* Build configuration list for PBXNativeTarget "XYIAPKit" */ = { isa = XCConfigurationList; buildConfigurations = ( 44D0A1E246DB2003E201E4DA0E8C11E0 /* Debug */, 7201017C2625B36FDBD2126594F77939 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; AA305A28AD005A11A7111A786DD36650 /* Build configuration list for PBXNativeTarget "Pods-XYIAPKit_Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 2B4A2119648244D65599A0E86589BFFA /* Debug */, AC6D525A720795C9FE57D6E07833EF9A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; FE3FF5EDFD8EE00CDE08D1BD60940FAB /* Build configuration list for PBXNativeTarget "Pods-XYIAPKit_Example" */ = { isa = XCConfigurationList; buildConfigurations = ( 201D6ADB48BE8E19C034ECD938798C69 /* Debug */, 5F60B76B0319D4AF76C114EBA12223A1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; } ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: ## XYIAPKit Copyright (c) 2018 1073605877 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. ## YYModel 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 ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-acknowledgements.plist ================================================ PreferenceSpecifiers FooterText This application makes use of the following third party libraries: Title Acknowledgements Type PSGroupSpecifier FooterText Copyright (c) 2018 1073605877 <qichao.ma@quvideo.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 XYIAPKit 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 YYModel Type PSGroupSpecifier FooterText Generated by CocoaPods - https://cocoapods.org Title Type PSGroupSpecifier StringsTable Acknowledgements Title Acknowledgements ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-dummy.m ================================================ #import @interface PodsDummy_Pods_XYIAPKit_Example : NSObject @end @implementation PodsDummy_Pods_XYIAPKit_Example @end ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-frameworks.sh ================================================ #!/bin/sh set -e echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" # Used as a return value for each invocation of `strip_invalid_archs` function. STRIP_BINARY_RETVAL=0 # 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 .*.??????") # Copies and strips a vendored framework install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink "${source}")" fi # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Copies and strips a vendored dSYM install_dsym() { local source="$1" if [ -r "$source" ]; then # Copy the dSYM into a the targets temp dir. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" local basename basename="$(basename -s .framework.dSYM "$source")" binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then strip_invalid_archs "$binary" fi if [[ $STRIP_BINARY_RETVAL == 1 ]]; then # Move the stripped file into its final destination. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" else # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" fi fi } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identitiy echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then code_sign_cmd="$code_sign_cmd &" fi echo "$code_sign_cmd" eval "$code_sign_cmd" fi } # Strip invalid architectures strip_invalid_archs() { binary="$1" # Get architectures for current target binary binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" # Intersect them with the architectures we are building for intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" # If there are no archs supported by this binary then warn the user if [[ -z "$intersected_archs" ]]; then echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." STRIP_BINARY_RETVAL=0 return fi stripped="" for arch in $binary_archs; do if ! [[ "${ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" || exit 1 stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi STRIP_BINARY_RETVAL=1 } if [[ "$CONFIGURATION" == "Debug" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/XYIAPKit/XYIAPKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework" fi if [[ "$CONFIGURATION" == "Release" ]]; then install_framework "${BUILT_PRODUCTS_DIR}/XYIAPKit/XYIAPKit.framework" install_framework "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework" fi if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then wait fi ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-resources.sh ================================================ #!/bin/sh set -e 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 } 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" 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}" fi ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double Pods_XYIAPKit_ExampleVersionNumber; FOUNDATION_EXPORT const unsigned char Pods_XYIAPKit_ExampleVersionString[]; ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example.debug.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit/XYIAPKit.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" OTHER_LDFLAGS = $(inherited) -framework "XYIAPKit" -framework "YYModel" 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 ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example.modulemap ================================================ framework module Pods_XYIAPKit_Example { umbrella header "Pods-XYIAPKit_Example-umbrella.h" export * module * { export * } } ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example.release.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit/XYIAPKit.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" OTHER_LDFLAGS = $(inherited) -framework "XYIAPKit" -framework "YYModel" 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 ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-acknowledgements.markdown ================================================ # Acknowledgements This application makes use of the following third party libraries: Generated by CocoaPods - https://cocoapods.org ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-acknowledgements.plist ================================================ 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 ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-dummy.m ================================================ #import @interface PodsDummy_Pods_XYIAPKit_Tests : NSObject @end @implementation PodsDummy_Pods_XYIAPKit_Tests @end ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-frameworks.sh ================================================ #!/bin/sh set -e echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" # Used as a return value for each invocation of `strip_invalid_archs` function. STRIP_BINARY_RETVAL=0 # 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 .*.??????") # Copies and strips a vendored framework install_framework() { if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then local source="${BUILT_PRODUCTS_DIR}/$1" elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" elif [ -r "$1" ]; then local source="$1" fi local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" if [ -L "${source}" ]; then echo "Symlinked..." source="$(readlink "${source}")" fi # Use filter instead of exclude so missing patterns don't throw errors. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" local basename basename="$(basename -s .framework "$1")" binary="${destination}/${basename}.framework/${basename}" if ! [ -r "$binary" ]; then binary="${destination}/${basename}" fi # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then strip_invalid_archs "$binary" fi # Resign the code if required by the build settings to avoid unstable apps code_sign_if_enabled "${destination}/$(basename "$1")" # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then local swift_runtime_libs swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) for lib in $swift_runtime_libs; do echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" code_sign_if_enabled "${destination}/${lib}" done fi } # Copies and strips a vendored dSYM install_dsym() { local source="$1" if [ -r "$source" ]; then # Copy the dSYM into a the targets temp dir. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" local basename basename="$(basename -s .framework.dSYM "$source")" binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" # Strip invalid architectures so "fat" simulator / device frameworks work on device if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then strip_invalid_archs "$binary" fi if [[ $STRIP_BINARY_RETVAL == 1 ]]; then # Move the stripped file into its final destination. echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" else # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" fi fi } # Signs a framework with the provided identity code_sign_if_enabled() { if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then # Use the current code_sign_identitiy echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'" if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then code_sign_cmd="$code_sign_cmd &" fi echo "$code_sign_cmd" eval "$code_sign_cmd" fi } # Strip invalid architectures strip_invalid_archs() { binary="$1" # Get architectures for current target binary binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" # Intersect them with the architectures we are building for intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" # If there are no archs supported by this binary then warn the user if [[ -z "$intersected_archs" ]]; then echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." STRIP_BINARY_RETVAL=0 return fi stripped="" for arch in $binary_archs; do if ! [[ "${ARCHS}" == *"$arch"* ]]; then # Strip non-valid architectures in-place lipo -remove "$arch" -output "$binary" "$binary" || exit 1 stripped="$stripped $arch" fi done if [[ "$stripped" ]]; then echo "Stripped $binary of architectures:$stripped" fi STRIP_BINARY_RETVAL=1 } if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then wait fi ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-resources.sh ================================================ #!/bin/sh set -e 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 } 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" 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}" fi ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif FOUNDATION_EXPORT double Pods_XYIAPKit_TestsVersionNumber; FOUNDATION_EXPORT const unsigned char Pods_XYIAPKit_TestsVersionString[]; ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests.debug.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit/XYIAPKit.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" 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 ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests.modulemap ================================================ framework module Pods_XYIAPKit_Tests { umbrella header "Pods-XYIAPKit_Tests-umbrella.h" export * module * { export * } } ================================================ FILE: Example/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests.release.xcconfig ================================================ FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit/XYIAPKit.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" 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 ================================================ FILE: Example/Pods/Target Support Files/XYIAPKit/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 0.8.0 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Example/Pods/Target Support Files/XYIAPKit/ResourceBundle-XYIAPKit-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType BNDL CFBundleShortVersionString 0.4.0 CFBundleSignature ???? CFBundleVersion 1 NSPrincipalClass ================================================ FILE: Example/Pods/Target Support Files/XYIAPKit/XYIAPKit-dummy.m ================================================ #import @interface PodsDummy_XYIAPKit : NSObject @end @implementation PodsDummy_XYIAPKit @end ================================================ FILE: Example/Pods/Target Support Files/XYIAPKit/XYIAPKit-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: Example/Pods/Target Support Files/XYIAPKit/XYIAPKit-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "XYIAPKit.h" #import "NSNotification+XYStore.h" #import "XYReceiptRefreshService.h" #import "XYStore.h" #import "XYStoreProductService.h" #import "XYStoreProtocol.h" #import "XYStoreKeychainPersistence.h" #import "XYStoreTransaction.h" #import "XYStoreUserDefaultsPersistence.h" #import "NSDate+XYStoreExtension.h" #import "XYInAppReceipt.h" #import "XYiTunesReceipt.h" #import "XYiTunesResponse.h" #import "XYStoreiTunesReceiptVerifier.h" FOUNDATION_EXPORT double XYIAPKitVersionNumber; FOUNDATION_EXPORT const unsigned char XYIAPKitVersionString[]; ================================================ FILE: Example/Pods/Target Support Files/XYIAPKit/XYIAPKit.modulemap ================================================ framework module XYIAPKit { umbrella header "XYIAPKit-umbrella.h" export * module * { export * } } ================================================ FILE: Example/Pods/Target Support Files/XYIAPKit/XYIAPKit.xcconfig ================================================ CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" OTHER_LDFLAGS = -framework "Security" -framework "StoreKit" 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 ================================================ FILE: Example/Pods/Target Support Files/YYModel/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType FMWK CFBundleShortVersionString 1.0.4 CFBundleSignature ???? CFBundleVersion ${CURRENT_PROJECT_VERSION} NSPrincipalClass ================================================ FILE: Example/Pods/Target Support Files/YYModel/YYModel-dummy.m ================================================ #import @interface PodsDummy_YYModel : NSObject @end @implementation PodsDummy_YYModel @end ================================================ FILE: Example/Pods/Target Support Files/YYModel/YYModel-prefix.pch ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif ================================================ FILE: Example/Pods/Target Support Files/YYModel/YYModel-umbrella.h ================================================ #ifdef __OBJC__ #import #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "NSObject+YYModel.h" #import "YYClassInfo.h" #import "YYModel.h" FOUNDATION_EXPORT double YYModelVersionNumber; FOUNDATION_EXPORT const unsigned char YYModelVersionString[]; ================================================ FILE: Example/Pods/Target Support Files/YYModel/YYModel.modulemap ================================================ framework module YYModel { umbrella header "YYModel-umbrella.h" export * module * { export * } } ================================================ FILE: Example/Pods/Target Support Files/YYModel/YYModel.xcconfig ================================================ CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYModel GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" OTHER_LDFLAGS = -framework "CoreFoundation" -framework "Foundation" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_ROOT = ${SRCROOT} PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYModel PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} SKIP_INSTALL = YES ================================================ FILE: Example/Pods/YYModel/LICENSE ================================================ 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. ================================================ FILE: Example/Pods/YYModel/README.md ================================================ YYModel ============== [![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/ibireme/YYModel/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/YYModel.svg?style=flat)](http://cocoapods.org/?q= YYModel)  [![CocoaPods](http://img.shields.io/cocoapods/p/YYModel.svg?style=flat)](http://cocoapods.org/?q= YYModel)  [![Build Status](https://travis-ci.org/ibireme/YYModel.svg?branch=master)](https://travis-ci.org/ibireme/YYModel)  [![codecov.io](https://codecov.io/github/ibireme/YYModel/coverage.svg?branch=master)](https://codecov.io/github/ibireme/YYModel?branch=master) High performance model framework for iOS/OSX.
(It's a component of [YYKit](https://github.com/ibireme/YYKit)) Performance ============== Time cost (process GithubUser 10000 times on iPhone 6): ![Benchmark result](https://raw.github.com/ibireme/YYModel/master/Benchmark/Result.png ) See `Benchmark/ModelBenchmark.xcodeproj` for more benchmark case. Features ============== - **High performance**: The conversion performance is close to handwriting code. - **Automatic type conversion**: The object types can be automatically converted. - **Type Safe**: All data types will be verified to ensure type-safe during the conversion process. - **Non-intrusive**: There is no need to make the model class inherit from other base class. - **Lightwight**: This library contains only 5 files. - **Docs and unit testing**: 100% docs coverage, 99.6% code coverage. Usage ============== ###Simple model json convert // JSON: { "uid":123456, "name":"Harry", "created":"1965-07-31T00:00:00+0000" } // Model: @interface User : NSObject @property UInt64 uid; @property NSString *name; @property NSDate *created; @end @implementation User @end // Convert json to model: User *user = [User yy_modelWithJSON:json]; // Convert model to json: NSDictionary *json = [user yy_modelToJSONObject]; If the type of an object in JSON/Dictionary cannot be matched to the property of the model, the following automatic conversion is performed. If the automatic conversion failed, the value will be ignored.
JSON/Dictionary Model
NSString NSNumber,NSURL,SEL,Class
NSNumber NSString
NSString/NSNumber C number (BOOL,int,float,NSUInteger,UInt64,...)
NaN and Inf will be ignored
NSString NSDate parsed with these formats:
yyyy-MM-dd
yyyy-MM-dd HH:mm:ss
yyyy-MM-dd'T'HH:mm:ss
yyyy-MM-dd'T'HH:mm:ssZ
EEE MMM dd HH:mm:ss Z yyyy
NSDate NSString formatted with ISO8601:
"YYYY-MM-dd'T'HH:mm:ssZ"
NSValue struct (CGRect,CGSize,...)
NSNull nil,0
"no","false",... @(NO),0
"yes","true",... @(YES),1
###Match model property to different JSON key // JSON: { "n":"Harry Pottery", "p": 256, "ext" : { "desc" : "A book written by J.K.Rowing." }, "ID" : 100010 } // Model: @interface Book : NSObject @property NSString *name; @property NSInteger page; @property NSString *desc; @property NSString *bookID; @end @implementation Book + (NSDictionary *)modelCustomPropertyMapper { return @{@"name" : @"n", @"page" : @"p", @"desc" : @"ext.desc", @"bookID" : @[@"id",@"ID",@"book_id"]}; } @end You can map a json key (key path) or an array of json key (key path) to one or multiple property name. If there's no mapper for a property, it will use the property's name as default. ###Nested model // JSON { "author":{ "name":"J.K.Rowling", "birthday":"1965-07-31T00:00:00+0000" }, "name":"Harry Potter", "pages":256 } // Model: (no need to do anything) @interface Author : NSObject @property NSString *name; @property NSDate *birthday; @end @implementation Author @end @interface Book : NSObject @property NSString *name; @property NSUInteger pages; @property Author *author; @end @implementation Book @end ### Container property @class Shadow, Border, Attachment; @interface Attributes @property NSString *name; @property NSArray *shadows; //Array @property NSSet *borders; //Set @property NSMutableDictionary *attachments; //Dict @end @implementation Attributes + (NSDictionary *)modelContainerPropertyGenericClass { // value should be Class or Class name. return @{@"shadows" : [Shadow class], @"borders" : Border.class, @"attachments" : @"Attachment" }; } @end ### Whitelist and blacklist @interface User @property NSString *name; @property NSUInteger age; @end @implementation Attributes + (NSArray *)modelPropertyBlacklist { return @[@"test1", @"test2"]; } + (NSArray *)modelPropertyWhitelist { return @[@"name"]; } @end ###Data validate and custom transform // JSON: { "name":"Harry", "timestamp" : 1445534567 } // Model: @interface User @property NSString *name; @property NSDate *createdAt; @end @implementation User - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic { NSNumber *timestamp = dic[@"timestamp"]; if (![timestamp isKindOfClass:[NSNumber class]]) return NO; _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue]; return YES; } - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic { if (!_createdAt) return NO; dic[@"timestamp"] = @(n.timeIntervalSince1970); return YES; } @end ###Coding/Copying/hash/equal/description @interface YYShadow :NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) CGSize size; @end @implementation YYShadow - (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; return [self yy_modelInitWithCoder:aDecoder]; } - (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; } - (NSUInteger)hash { return [self yy_modelHash]; } - (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; } - (NSString *)description { return [self yy_modelDescription]; } @end Installation ============== ### CocoaPods 1. Add `pod 'YYModel'` to your Podfile. 2. Run `pod install` or `pod update`. 3. Import \. ### Carthage 1. Add `github "ibireme/YYModel"` 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 YYModel subdirectory. 2. Add the source files to your Xcode project. 3. Import `YYModel.h`. Documentation ============== Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYModel/).
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 ============== YYModel is provided under the MIT license. See LICENSE file for details.

--- 中文介绍 ============== 高性能 iOS/OSX 模型转换框架。
(该项目是 [YYKit](https://github.com/ibireme/YYKit) 组件之一) 性能 ============== 处理 GithubUser 数据 10000 次耗时统计 (iPhone 6): ![Benchmark result](https://raw.github.com/ibireme/YYModel/master/Benchmark/Result.png ) 更多测试代码和用例见 `Benchmark/ModelBenchmark.xcodeproj`。 特性 ============== - **高性能**: 模型转换性能接近手写解析代码。 - **自动类型转换**: 对象类型可以自动转换,详情见下方表格。 - **类型安全**: 转换过程中,所有的数据类型都会被检测一遍,以保证类型安全,避免崩溃问题。 - **无侵入性**: 模型无需继承自其他基类。 - **轻量**: 该框架只有 5 个文件 (包括.h文件)。 - **文档和单元测试**: 文档覆盖率100%, 代码覆盖率99.6%。 使用方法 ============== ###简单的 Model 与 JSON 相互转换 // JSON: { "uid":123456, "name":"Harry", "created":"1965-07-31T00:00:00+0000" } // Model: @interface User : NSObject @property UInt64 uid; @property NSString *name; @property NSDate *created; @end @implementation User @end // 将 JSON (NSData,NSString,NSDictionary) 转换为 Model: User *user = [User yy_modelWithJSON:json]; // 将 Model 转换为 JSON 对象: NSDictionary *json = [user yy_modelToJSONObject]; 当 JSON/Dictionary 中的对象类型与 Model 属性不一致时,YYModel 将会进行如下自动转换。自动转换不支持的值将会被忽略,以避免各种潜在的崩溃问题。
JSON/Dictionary Model
NSString NSNumber,NSURL,SEL,Class
NSNumber NSString
NSString/NSNumber 基础类型 (BOOL,int,float,NSUInteger,UInt64,...)
NaN 和 Inf 会被忽略
NSString NSDate 以下列格式解析:
yyyy-MM-dd
yyyy-MM-dd HH:mm:ss
yyyy-MM-dd'T'HH:mm:ss
yyyy-MM-dd'T'HH:mm:ssZ
EEE MMM dd HH:mm:ss Z yyyy
NSDate NSString 格式化为 ISO8601:
"YYYY-MM-dd'T'HH:mm:ssZ"
NSValue struct (CGRect,CGSize,...)
NSNull nil,0
"no","false",... @(NO),0
"yes","true",... @(YES),1
###Model 属性名和 JSON 中的 Key 不相同 // JSON: { "n":"Harry Pottery", "p": 256, "ext" : { "desc" : "A book written by J.K.Rowing." }, "ID" : 100010 } // Model: @interface Book : NSObject @property NSString *name; @property NSInteger page; @property NSString *desc; @property NSString *bookID; @end @implementation Book //返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。 + (NSDictionary *)modelCustomPropertyMapper { return @{@"name" : @"n", @"page" : @"p", @"desc" : @"ext.desc", @"bookID" : @[@"id",@"ID",@"book_id"]}; } @end 你可以把一个或一组 json key (key path) 映射到一个或多个属性。如果一个属性没有映射关系,那默认会使用相同属性名作为映射。 在 json->model 的过程中:如果一个属性对应了多个 json key,那么转换过程会按顺序查找,并使用第一个不为空的值。 在 model->json 的过程中:如果一个属性对应了多个 json key (key path),那么转换过程仅会处理第一个 json key (key path);如果多个属性对应了同一个 json key,则转换过过程会使用其中任意一个不为空的值。 ###Model 包含其他 Model // JSON { "author":{ "name":"J.K.Rowling", "birthday":"1965-07-31T00:00:00+0000" }, "name":"Harry Potter", "pages":256 } // Model: 什么都不用做,转换会自动完成 @interface Author : NSObject @property NSString *name; @property NSDate *birthday; @end @implementation Author @end @interface Book : NSObject @property NSString *name; @property NSUInteger pages; @property Author *author; //Book 包含 Author 属性 @end @implementation Book @end ###容器类属性 @class Shadow, Border, Attachment; @interface Attributes @property NSString *name; @property NSArray *shadows; //Array @property NSSet *borders; //Set @property NSMutableDictionary *attachments; //Dict @end @implementation Attributes // 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。 + (NSDictionary *)modelContainerPropertyGenericClass { return @{@"shadows" : [Shadow class], @"borders" : Border.class, @"attachments" : @"Attachment" }; } @end ###黑名单与白名单 @interface User @property NSString *name; @property NSUInteger age; @end @implementation Attributes // 如果实现了该方法,则处理过程中会忽略该列表内的所有属性 + (NSArray *)modelPropertyBlacklist { return @[@"test1", @"test2"]; } // 如果实现了该方法,则处理过程中不会处理该列表外的属性。 + (NSArray *)modelPropertyWhitelist { return @[@"name"]; } @end ###数据校验与自定义转换 // JSON: { "name":"Harry", "timestamp" : 1445534567 } // Model: @interface User @property NSString *name; @property NSDate *createdAt; @end @implementation User // 当 JSON 转为 Model 完成后,该方法会被调用。 // 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。 // 你也可以在这里做一些自动转换不能完成的工作。 - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic { NSNumber *timestamp = dic[@"timestamp"]; if (![timestamp isKindOfClass:[NSNumber class]]) return NO; _createdAt = [NSDate dateWithTimeIntervalSince1970:timestamp.floatValue]; return YES; } // 当 Model 转为 JSON 完成后,该方法会被调用。 // 你可以在这里对数据进行校验,如果校验不通过,可以返回 NO,则该 Model 会被忽略。 // 你也可以在这里做一些自动转换不能完成的工作。 - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic { if (!_createdAt) return NO; dic[@"timestamp"] = @(n.timeIntervalSince1970); return YES; } @end ###Coding/Copying/hash/equal/description @interface YYShadow :NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) CGSize size; @end @implementation YYShadow // 直接添加以下代码即可自动完成 - (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; return [self yy_modelInitWithCoder:aDecoder]; } - (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; } - (NSUInteger)hash { return [self yy_modelHash]; } - (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; } - (NSString *)description { return [self yy_modelDescription]; } @end 安装 ============== ### CocoaPods 1. 在 Podfile 中添加 `pod 'YYModel'`。 2. 执行 `pod install` 或 `pod update`。 3. 导入 \。 ### Carthage 1. 在 Cartfile 中添加 `github "ibireme/YYModel"`。 2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。 3. 导入 \。 ### 手动安装 1. 下载 YYModel 文件夹内的所有内容。 2. 将 YYModel 内的源文件添加(拖放)到你的工程。 3. 导入 `YYModel.h`。 文档 ============== 你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYModel/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。 系统要求 ============== 该项目最低支持 `iOS 6.0` 和 `Xcode 7.0`。 许可证 ============== YYModel 使用 MIT 许可证,详情见 LICENSE 文件。 相关链接 ============== [iOS JSON 模型转换库评测](http://blog.ibireme.com/2015/10/23/ios_model_framework_benchmark/) ================================================ FILE: Example/Pods/YYModel/YYModel/NSObject+YYModel.h ================================================ // // NSObject+YYModel.h // YYModel // // Created by ibireme on 15/5/10. // 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 data-model method: * Convert json to any object, or convert any object to json. * Set object properties with a key-value dictionary (like KVC). * Implementations of `NSCoding`, `NSCopying`, `-hash` and `-isEqual:`. See `YYModel` protocol for custom methods. Sample Code: ********************** json convertor ********************* @interface YYAuthor : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) NSDate *birthday; @end @implementation YYAuthor @end @interface YYBook : NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSUInteger pages; @property (nonatomic, strong) YYAuthor *author; @end @implementation YYBook @end int main() { // create model from json YYBook *book = [YYBook yy_modelWithJSON:@"{\"name\": \"Harry Potter\", \"pages\": 256, \"author\": {\"name\": \"J.K.Rowling\", \"birthday\": \"1965-07-31\" }}"]; // convert model to json NSString *json = [book yy_modelToJSONString]; // {"author":{"name":"J.K.Rowling","birthday":"1965-07-31T00:00:00+0000"},"name":"Harry Potter","pages":256} } ********************** Coding/Copying/hash/equal ********************* @interface YYShadow :NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) CGSize size; @end @implementation YYShadow - (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; return [self yy_modelInitWithCoder:aDecoder]; } - (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; } - (NSUInteger)hash { return [self yy_modelHash]; } - (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; } @end */ @interface NSObject (YYModel) /** Creates and returns a new instance of the receiver from a json. This method is thread-safe. @param json A json object in `NSDictionary`, `NSString` or `NSData`. @return A new instance created from the json, or nil if an error occurs. */ + (nullable instancetype)yy_modelWithJSON:(id)json; /** Creates and returns a new instance of the receiver from a key-value dictionary. This method is thread-safe. @param dictionary A key-value dictionary mapped to the instance's properties. Any invalid key-value pair in dictionary will be ignored. @return A new instance created from the dictionary, or nil if an error occurs. @discussion The key in `dictionary` will mapped to the reciever's property name, and the value will set to the property. If the value's type does not match the property, this method will try to convert the value based on these rules: `NSString` or `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger... `NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd". `NSString` -> NSURL. `NSValue` -> struct or union, such as CGRect, CGSize, ... `NSString` -> SEL, Class. */ + (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary; /** Set the receiver's properties with a json object. @discussion Any invalid data in json will be ignored. @param json A json object of `NSDictionary`, `NSString` or `NSData`, mapped to the receiver's properties. @return Whether succeed. */ - (BOOL)yy_modelSetWithJSON:(id)json; /** Set the receiver's properties with a key-value dictionary. @param dic A key-value dictionary mapped to the receiver's properties. Any invalid key-value pair in dictionary will be ignored. @discussion The key in `dictionary` will mapped to the reciever's property name, and the value will set to the property. If the value's type doesn't match the property, this method will try to convert the value based on these rules: `NSString`, `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger... `NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd". `NSString` -> NSURL. `NSValue` -> struct or union, such as CGRect, CGSize, ... `NSString` -> SEL, Class. @return Whether succeed. */ - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic; /** Generate a json object from the receiver's properties. @return A json object in `NSDictionary` or `NSArray`, or nil if an error occurs. See [NSJSONSerialization isValidJSONObject] for more information. @discussion Any of the invalid property is ignored. If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it just convert the inner object to json object. */ - (nullable id)yy_modelToJSONObject; /** Generate a json string's data from the receiver's properties. @return A json string's data, or nil if an error occurs. @discussion Any of the invalid property is ignored. If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the inner object to json string. */ - (nullable NSData *)yy_modelToJSONData; /** Generate a json string from the receiver's properties. @return A json string, or nil if an error occurs. @discussion Any of the invalid property is ignored. If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the inner object to json string. */ - (nullable NSString *)yy_modelToJSONString; /** Copy a instance with the receiver's properties. @return A copied instance, or nil if an error occurs. */ - (nullable id)yy_modelCopy; /** Encode the receiver's properties to a coder. @param aCoder An archiver object. */ - (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder; /** Decode the receiver's properties from a decoder. @param aDecoder An archiver object. @return self */ - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder; /** Get a hash code with the receiver's properties. @return Hash code. */ - (NSUInteger)yy_modelHash; /** Compares the receiver with another object for equality, based on properties. @param model Another object. @return `YES` if the reciever is equal to the object, otherwise `NO`. */ - (BOOL)yy_modelIsEqual:(id)model; /** Description method for debugging purposes based on properties. @return A string that describes the contents of the receiver. */ - (NSString *)yy_modelDescription; @end /** Provide some data-model method for NSArray. */ @interface NSArray (YYModel) /** Creates and returns an array from a json-array. This method is thread-safe. @param cls The instance's class in array. @param json A json array of `NSArray`, `NSString` or `NSData`. Example: [{"name","Mary"},{name:"Joe"}] @return A array, or nil if an error occurs. */ + (nullable NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json; @end /** Provide some data-model method for NSDictionary. */ @interface NSDictionary (YYModel) /** Creates and returns a dictionary from a json. This method is thread-safe. @param cls The value instance's class in dictionary. @param json A json dictionary of `NSDictionary`, `NSString` or `NSData`. Example: {"user1":{"name","Mary"}, "user2": {name:"Joe"}} @return A dictionary, or nil if an error occurs. */ + (nullable NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json; @end /** If the default model transform does not fit to your model class, implement one or more method in this protocol to change the default key-value transform process. There's no need to add '' to your class header. */ @protocol YYModel @optional /** Custom property mapper. @discussion If the key in JSON/Dictionary does not match to the model's property name, implements this method and returns the additional mapper. Example: json: { "n":"Harry Pottery", "p": 256, "ext" : { "desc" : "A book written by J.K.Rowling." }, "ID" : 100010 } model: @interface YYBook : NSObject @property NSString *name; @property NSInteger page; @property NSString *desc; @property NSString *bookID; @end @implementation YYBook + (NSDictionary *)modelCustomPropertyMapper { return @{@"name" : @"n", @"page" : @"p", @"desc" : @"ext.desc", @"bookID": @[@"id", @"ID", @"book_id"]}; } @end @return A custom mapper for properties. */ + (nullable NSDictionary *)modelCustomPropertyMapper; /** The generic class mapper for container properties. @discussion If the property is a container object, such as NSArray/NSSet/NSDictionary, implements this method and returns a property->class mapper, tells which kind of object will be add to the array/set/dictionary. Example: @class YYShadow, YYBorder, YYAttachment; @interface YYAttributes @property NSString *name; @property NSArray *shadows; @property NSSet *borders; @property NSDictionary *attachments; @end @implementation YYAttributes + (NSDictionary *)modelContainerPropertyGenericClass { return @{@"shadows" : [YYShadow class], @"borders" : YYBorder.class, @"attachments" : @"YYAttachment" }; } @end @return A class mapper. */ + (nullable NSDictionary *)modelContainerPropertyGenericClass; /** If you need to create instances of different classes during json->object transform, use the method to choose custom class based on dictionary data. @discussion If the model implements this method, it will be called to determine resulting class during `+modelWithJSON:`, `+modelWithDictionary:`, conveting object of properties of parent objects (both singular and containers via `+modelContainerPropertyGenericClass`). Example: @class YYCircle, YYRectangle, YYLine; @implementation YYShape + (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary { if (dictionary[@"radius"] != nil) { return [YYCircle class]; } else if (dictionary[@"width"] != nil) { return [YYRectangle class]; } else if (dictionary[@"y2"] != nil) { return [YYLine class]; } else { return [self class]; } } @end @param dictionary The json/kv dictionary. @return Class to create from this dictionary, `nil` to use current class. */ + (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary; /** All the properties in blacklist will be ignored in model transform process. Returns nil to ignore this feature. @return An array of property's name. */ + (nullable NSArray *)modelPropertyBlacklist; /** If a property is not in the whitelist, it will be ignored in model transform process. Returns nil to ignore this feature. @return An array of property's name. */ + (nullable NSArray *)modelPropertyWhitelist; /** This method's behavior is similar to `- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;`, but be called before the model transform. @discussion If the model implements this method, it will be called before `+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`. If this method returns nil, the transform process will ignore this model. @param dic The json/kv dictionary. @return Returns the modified dictionary, or nil to ignore this model. */ - (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic; /** If the default json-to-model transform does not fit to your model object, implement this method to do additional process. You can also use this method to validate the model's properties. @discussion If the model implements this method, it will be called at the end of `+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`. If this method returns NO, the transform process will ignore this model. @param dic The json/kv dictionary. @return Returns YES if the model is valid, or NO to ignore this model. */ - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic; /** If the default model-to-json transform does not fit to your model class, implement this method to do additional process. You can also use this method to validate the json dictionary. @discussion If the model implements this method, it will be called at the end of `-modelToJSONObject` and `-modelToJSONString`. If this method returns NO, the transform process will ignore this json dictionary. @param dic The json dictionary. @return Returns YES if the model is valid, or NO to ignore this model. */ - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic; @end NS_ASSUME_NONNULL_END ================================================ FILE: Example/Pods/YYModel/YYModel/NSObject+YYModel.m ================================================ // // NSObject+YYModel.m // YYModel // // Created by ibireme on 15/5/10. // 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 "NSObject+YYModel.h" #import "YYClassInfo.h" #import #define force_inline __inline__ __attribute__((always_inline)) /// Foundation Class Type typedef NS_ENUM (NSUInteger, YYEncodingNSType) { YYEncodingTypeNSUnknown = 0, YYEncodingTypeNSString, YYEncodingTypeNSMutableString, YYEncodingTypeNSValue, YYEncodingTypeNSNumber, YYEncodingTypeNSDecimalNumber, YYEncodingTypeNSData, YYEncodingTypeNSMutableData, YYEncodingTypeNSDate, YYEncodingTypeNSURL, YYEncodingTypeNSArray, YYEncodingTypeNSMutableArray, YYEncodingTypeNSDictionary, YYEncodingTypeNSMutableDictionary, YYEncodingTypeNSSet, YYEncodingTypeNSMutableSet, }; /// Get the Foundation class type from property info. static force_inline YYEncodingNSType YYClassGetNSType(Class cls) { if (!cls) return YYEncodingTypeNSUnknown; if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString; if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString; if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber; if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber; if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue; if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData; if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData; if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate; if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL; if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray; if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray; if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary; if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary; if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet; if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet; return YYEncodingTypeNSUnknown; } /// Whether the type is c number. static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) { switch (type & YYEncodingTypeMask) { case YYEncodingTypeBool: case YYEncodingTypeInt8: case YYEncodingTypeUInt8: case YYEncodingTypeInt16: case YYEncodingTypeUInt16: case YYEncodingTypeInt32: case YYEncodingTypeUInt32: case YYEncodingTypeInt64: case YYEncodingTypeUInt64: case YYEncodingTypeFloat: case YYEncodingTypeDouble: case YYEncodingTypeLongDouble: return YES; default: return NO; } } /// Parse a number value from 'id'. static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) { static NSCharacterSet *dot; static NSDictionary *dic; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)]; dic = @{@"TRUE" : @(YES), @"True" : @(YES), @"true" : @(YES), @"FALSE" : @(NO), @"False" : @(NO), @"false" : @(NO), @"YES" : @(YES), @"Yes" : @(YES), @"yes" : @(YES), @"NO" : @(NO), @"No" : @(NO), @"no" : @(NO), @"NIL" : (id)kCFNull, @"Nil" : (id)kCFNull, @"nil" : (id)kCFNull, @"NULL" : (id)kCFNull, @"Null" : (id)kCFNull, @"null" : (id)kCFNull, @"(NULL)" : (id)kCFNull, @"(Null)" : (id)kCFNull, @"(null)" : (id)kCFNull, @"" : (id)kCFNull, @"" : (id)kCFNull, @"" : (id)kCFNull}; }); if (!value || value == (id)kCFNull) return nil; if ([value isKindOfClass:[NSNumber class]]) return value; if ([value isKindOfClass:[NSString class]]) { NSNumber *num = dic[value]; if (num) { if (num == (id)kCFNull) return nil; return num; } if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) { const char *cstring = ((NSString *)value).UTF8String; if (!cstring) return nil; double num = atof(cstring); if (isnan(num) || isinf(num)) return nil; return @(num); } else { const char *cstring = ((NSString *)value).UTF8String; if (!cstring) return nil; return @(atoll(cstring)); } } return nil; } /// Parse string to date. static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) { typedef NSDate* (^YYNSDateParseBlock)(NSString *string); #define kParserNum 34 static YYNSDateParseBlock blocks[kParserNum + 1] = {0}; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ { /* 2014-01-20 // Google */ NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; formatter.dateFormat = @"yyyy-MM-dd"; blocks[10] = ^(NSString *string) { return [formatter dateFromString:string]; }; } { /* 2014-01-20 12:24:48 2014-01-20T12:24:48 // Google 2014-01-20 12:24:48.000 2014-01-20T12:24:48.000 */ NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init]; formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss"; NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init]; formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss"; NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init]; formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS"; NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init]; formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS"; blocks[19] = ^(NSString *string) { if ([string characterAtIndex:10] == 'T') { return [formatter1 dateFromString:string]; } else { return [formatter2 dateFromString:string]; } }; blocks[23] = ^(NSString *string) { if ([string characterAtIndex:10] == 'T') { return [formatter3 dateFromString:string]; } else { return [formatter4 dateFromString:string]; } }; } { /* 2014-01-20T12:24:48Z // Github, Apple 2014-01-20T12:24:48+0800 // Facebook 2014-01-20T12:24:48+12:00 // Google 2014-01-20T12:24:48.000Z 2014-01-20T12:24:48.000+0800 2014-01-20T12:24:48.000+12:00 */ NSDateFormatter *formatter = [NSDateFormatter new]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; NSDateFormatter *formatter2 = [NSDateFormatter new]; formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ"; blocks[20] = ^(NSString *string) { return [formatter dateFromString:string]; }; blocks[24] = ^(NSString *string) { return [formatter dateFromString:string]?: [formatter2 dateFromString:string]; }; blocks[25] = ^(NSString *string) { return [formatter dateFromString:string]; }; blocks[28] = ^(NSString *string) { return [formatter2 dateFromString:string]; }; blocks[29] = ^(NSString *string) { return [formatter2 dateFromString:string]; }; } { /* Fri Sep 04 00:12:21 +0800 2015 // Weibo, Twitter Fri Sep 04 00:12:21.000 +0800 2015 */ NSDateFormatter *formatter = [NSDateFormatter new]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; NSDateFormatter *formatter2 = [NSDateFormatter new]; formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy"; blocks[30] = ^(NSString *string) { return [formatter dateFromString:string]; }; blocks[34] = ^(NSString *string) { return [formatter2 dateFromString:string]; }; } }); if (!string) return nil; if (string.length > kParserNum) return nil; YYNSDateParseBlock parser = blocks[string.length]; if (!parser) return nil; return parser(string); #undef kParserNum } /// Get the 'NSBlock' class. static force_inline Class YYNSBlockClass() { static Class cls; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ void (^block)(void) = ^{}; cls = ((NSObject *)block).class; while (class_getSuperclass(cls) != [NSObject class]) { cls = class_getSuperclass(cls); } }); return cls; // current is "NSBlock" } /** Get the ISO date formatter. ISO8601 format example: 2010-07-09T16:13:30+12:00 2011-01-11T11:11:11+0000 2011-01-26T19:06:43Z length: 20/24/25 */ static force_inline NSDateFormatter *YYISODateFormatter() { static NSDateFormatter *formatter = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [[NSDateFormatter alloc] init]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; }); return formatter; } /// Get the value with key paths from dictionary /// The dic should be NSDictionary, and the keyPath should not be nil. static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) { id value = nil; for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) { value = dic[keyPaths[i]]; if (i + 1 < max) { if ([value isKindOfClass:[NSDictionary class]]) { dic = value; } else { return nil; } } } return value; } /// Get the value with multi key (or key path) from dictionary /// The dic should be NSDictionary static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) { id value = nil; for (NSString *key in multiKeys) { if ([key isKindOfClass:[NSString class]]) { value = dic[key]; if (value) break; } else { value = YYValueForKeyPath(dic, (NSArray *)key); if (value) break; } } return value; } /// A property info in object model. @interface _YYModelPropertyMeta : NSObject { @package NSString *_name; ///< property's name YYEncodingType _type; ///< property's type YYEncodingNSType _nsType; ///< property's Foundation type BOOL _isCNumber; ///< is c number type Class _cls; ///< property's class, or nil Class _genericCls; ///< container's generic class, or nil if threr's no generic class SEL _getter; ///< getter, or nil if the instances cannot respond SEL _setter; ///< setter, or nil if the instances cannot respond BOOL _isKVCCompatible; ///< YES if it can access with key-value coding BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary: /* property->key: _mappedToKey:key _mappedToKeyPath:nil _mappedToKeyArray:nil property->keyPath: _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil property->keys: _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath _mappedToKeyArray:keys(array) */ NSString *_mappedToKey; ///< the key mapped to NSArray *_mappedToKeyPath; ///< the key path mapped to (nil if the name is not key path) NSArray *_mappedToKeyArray; ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys) YYClassPropertyInfo *_info; ///< property's info _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key. } @end @implementation _YYModelPropertyMeta + (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic { // support pseudo generic class with protocol name if (!generic && propertyInfo.protocols) { for (NSString *protocol in propertyInfo.protocols) { Class cls = objc_getClass(protocol.UTF8String); if (cls) { generic = cls; break; } } } _YYModelPropertyMeta *meta = [self new]; meta->_name = propertyInfo.name; meta->_type = propertyInfo.type; meta->_info = propertyInfo; meta->_genericCls = generic; if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) { meta->_nsType = YYClassGetNSType(propertyInfo.cls); } else { meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type); } if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) { /* It seems that NSKeyedUnarchiver cannot decode NSValue except these structs: */ static NSSet *types = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSMutableSet *set = [NSMutableSet new]; // 32 bit [set addObject:@"{CGSize=ff}"]; [set addObject:@"{CGPoint=ff}"]; [set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"]; [set addObject:@"{CGAffineTransform=ffffff}"]; [set addObject:@"{UIEdgeInsets=ffff}"]; [set addObject:@"{UIOffset=ff}"]; // 64 bit [set addObject:@"{CGSize=dd}"]; [set addObject:@"{CGPoint=dd}"]; [set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"]; [set addObject:@"{CGAffineTransform=dddddd}"]; [set addObject:@"{UIEdgeInsets=dddd}"]; [set addObject:@"{UIOffset=dd}"]; types = set; }); if ([types containsObject:propertyInfo.typeEncoding]) { meta->_isStructAvailableForKeyedArchiver = YES; } } meta->_cls = propertyInfo.cls; if (generic) { meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)]; } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) { meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)]; } if (propertyInfo.getter) { if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) { meta->_getter = propertyInfo.getter; } } if (propertyInfo.setter) { if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) { meta->_setter = propertyInfo.setter; } } if (meta->_getter && meta->_setter) { /* KVC invalid type: long double pointer (such as SEL/CoreFoundation object) */ switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeBool: case YYEncodingTypeInt8: case YYEncodingTypeUInt8: case YYEncodingTypeInt16: case YYEncodingTypeUInt16: case YYEncodingTypeInt32: case YYEncodingTypeUInt32: case YYEncodingTypeInt64: case YYEncodingTypeUInt64: case YYEncodingTypeFloat: case YYEncodingTypeDouble: case YYEncodingTypeObject: case YYEncodingTypeClass: case YYEncodingTypeBlock: case YYEncodingTypeStruct: case YYEncodingTypeUnion: { meta->_isKVCCompatible = YES; } break; default: break; } } return meta; } @end /// A class info in object model. @interface _YYModelMeta : NSObject { @package YYClassInfo *_classInfo; /// Key:mapped key and key path, Value:_YYModelPropertyMeta. NSDictionary *_mapper; /// Array<_YYModelPropertyMeta>, all property meta of this model. NSArray *_allPropertyMetas; /// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path. NSArray *_keyPathPropertyMetas; /// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys. NSArray *_multiKeysPropertyMetas; /// The number of mapped key (and key path), same to _mapper.count. NSUInteger _keyMappedCount; /// Model class type. YYEncodingNSType _nsType; BOOL _hasCustomWillTransformFromDictionary; BOOL _hasCustomTransformFromDictionary; BOOL _hasCustomTransformToDictionary; BOOL _hasCustomClassFromDictionary; } @end @implementation _YYModelMeta - (instancetype)initWithClass:(Class)cls { YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls]; if (!classInfo) return nil; self = [super init]; // Get black list NSSet *blacklist = nil; if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) { NSArray *properties = [(id)cls modelPropertyBlacklist]; if (properties) { blacklist = [NSSet setWithArray:properties]; } } // Get white list NSSet *whitelist = nil; if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) { NSArray *properties = [(id)cls modelPropertyWhitelist]; if (properties) { whitelist = [NSSet setWithArray:properties]; } } // Get container property's generic class NSDictionary *genericMapper = nil; if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) { genericMapper = [(id)cls modelContainerPropertyGenericClass]; if (genericMapper) { NSMutableDictionary *tmp = [NSMutableDictionary new]; [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if (![key isKindOfClass:[NSString class]]) return; Class meta = object_getClass(obj); if (!meta) return; if (class_isMetaClass(meta)) { tmp[key] = obj; } else if ([obj isKindOfClass:[NSString class]]) { Class cls = NSClassFromString(obj); if (cls) { tmp[key] = cls; } } }]; genericMapper = tmp; } } // Create all property metas. NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new]; YYClassInfo *curClassInfo = classInfo; while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy) for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) { if (!propertyInfo.name) continue; if (blacklist && [blacklist containsObject:propertyInfo.name]) continue; if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue; _YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]]; if (!meta || !meta->_name) continue; if (!meta->_getter || !meta->_setter) continue; if (allPropertyMetas[meta->_name]) continue; allPropertyMetas[meta->_name] = meta; } curClassInfo = curClassInfo.superClassInfo; } if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy; // create mapper NSMutableDictionary *mapper = [NSMutableDictionary new]; NSMutableArray *keyPathPropertyMetas = [NSMutableArray new]; NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new]; if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) { NSDictionary *customMapper = [(id )cls modelCustomPropertyMapper]; [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) { _YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName]; if (!propertyMeta) return; [allPropertyMetas removeObjectForKey:propertyName]; if ([mappedToKey isKindOfClass:[NSString class]]) { if (mappedToKey.length == 0) return; propertyMeta->_mappedToKey = mappedToKey; NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."]; for (NSString *onePath in keyPath) { if (onePath.length == 0) { NSMutableArray *tmp = keyPath.mutableCopy; [tmp removeObject:@""]; keyPath = tmp; break; } } if (keyPath.count > 1) { propertyMeta->_mappedToKeyPath = keyPath; [keyPathPropertyMetas addObject:propertyMeta]; } propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } else if ([mappedToKey isKindOfClass:[NSArray class]]) { NSMutableArray *mappedToKeyArray = [NSMutableArray new]; for (NSString *oneKey in ((NSArray *)mappedToKey)) { if (![oneKey isKindOfClass:[NSString class]]) continue; if (oneKey.length == 0) continue; NSArray *keyPath = [oneKey componentsSeparatedByString:@"."]; if (keyPath.count > 1) { [mappedToKeyArray addObject:keyPath]; } else { [mappedToKeyArray addObject:oneKey]; } if (!propertyMeta->_mappedToKey) { propertyMeta->_mappedToKey = oneKey; propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil; } } if (!propertyMeta->_mappedToKey) return; propertyMeta->_mappedToKeyArray = mappedToKeyArray; [multiKeysPropertyMetas addObject:propertyMeta]; propertyMeta->_next = mapper[mappedToKey] ?: nil; mapper[mappedToKey] = propertyMeta; } }]; } [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { propertyMeta->_mappedToKey = name; propertyMeta->_next = mapper[name] ?: nil; mapper[name] = propertyMeta; }]; if (mapper.count) _mapper = mapper; if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas; if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas; _classInfo = classInfo; _keyMappedCount = _allPropertyMetas.count; _nsType = YYClassGetNSType(cls); _hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]); _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]); _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]); _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]); return self; } /// Returns the cached model class meta + (instancetype)metaWithClass:(Class)cls { if (!cls) return nil; static CFMutableDictionaryRef cache; static dispatch_once_t onceToken; static dispatch_semaphore_t lock; dispatch_once(&onceToken, ^{ cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls)); dispatch_semaphore_signal(lock); if (!meta || meta->_classInfo.needUpdate) { meta = [[_YYModelMeta alloc] initWithClass:cls]; if (meta) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta)); dispatch_semaphore_signal(lock); } } return meta; } @end /** Get number from property. @discussion Caller should hold strong reference to the parameters before this function returns. @param model Should not be nil. @param meta Should not be nil, meta.isCNumber should be YES, meta.getter should not be nil. @return A number object, or nil if failed. */ static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model, __unsafe_unretained _YYModelPropertyMeta *meta) { switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeBool: { return @(((bool (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeInt8: { return @(((int8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeUInt8: { return @(((uint8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeInt16: { return @(((int16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeUInt16: { return @(((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeInt32: { return @(((int32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeUInt32: { return @(((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeInt64: { return @(((int64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeUInt64: { return @(((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter)); } case YYEncodingTypeFloat: { float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); if (isnan(num) || isinf(num)) return nil; return @(num); } case YYEncodingTypeDouble: { double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); if (isnan(num) || isinf(num)) return nil; return @(num); } case YYEncodingTypeLongDouble: { double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); if (isnan(num) || isinf(num)) return nil; return @(num); } default: return nil; } } /** Set number to property. @discussion Caller should hold strong reference to the parameters before this function returns. @param model Should not be nil. @param num Can be nil. @param meta Should not be nil, meta.isCNumber should be YES, meta.setter should not be nil. */ static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model, __unsafe_unretained NSNumber *num, __unsafe_unretained _YYModelPropertyMeta *meta) { switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeBool: { ((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue); } break; case YYEncodingTypeInt8: { ((void (*)(id, SEL, int8_t))(void *) objc_msgSend)((id)model, meta->_setter, (int8_t)num.charValue); } break; case YYEncodingTypeUInt8: { ((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint8_t)num.unsignedCharValue); } break; case YYEncodingTypeInt16: { ((void (*)(id, SEL, int16_t))(void *) objc_msgSend)((id)model, meta->_setter, (int16_t)num.shortValue); } break; case YYEncodingTypeUInt16: { ((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint16_t)num.unsignedShortValue); } break; case YYEncodingTypeInt32: { ((void (*)(id, SEL, int32_t))(void *) objc_msgSend)((id)model, meta->_setter, (int32_t)num.intValue); } case YYEncodingTypeUInt32: { ((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint32_t)num.unsignedIntValue); } break; case YYEncodingTypeInt64: { if ([num isKindOfClass:[NSDecimalNumber class]]) { ((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue); } else { ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.longLongValue); } } break; case YYEncodingTypeUInt64: { if ([num isKindOfClass:[NSDecimalNumber class]]) { ((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue); } else { ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.unsignedLongLongValue); } } break; case YYEncodingTypeFloat: { float f = num.floatValue; if (isnan(f) || isinf(f)) f = 0; ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)model, meta->_setter, f); } break; case YYEncodingTypeDouble: { double d = num.doubleValue; if (isnan(d) || isinf(d)) d = 0; ((void (*)(id, SEL, double))(void *) objc_msgSend)((id)model, meta->_setter, d); } break; case YYEncodingTypeLongDouble: { long double d = num.doubleValue; if (isnan(d) || isinf(d)) d = 0; ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)model, meta->_setter, (long double)d); } // break; commented for code coverage in next line default: break; } } /** Set value to model with a property meta. @discussion Caller should hold strong reference to the parameters before this function returns. @param model Should not be nil. @param value Should not be nil, but can be NSNull. @param meta Should not be nil, and meta->_setter should not be nil. */ static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta) { if (meta->_isCNumber) { NSNumber *num = YYNSNumberCreateFromID(value); ModelSetNumberToProperty(model, num, meta); if (num) [num class]; // hold the number } else if (meta->_nsType) { if (value == (id)kCFNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else { switch (meta->_nsType) { case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { if ([value isKindOfClass:[NSString class]]) { if (meta->_nsType == YYEncodingTypeNSString) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy); } } else if ([value isKindOfClass:[NSNumber class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSNumber *)value).stringValue : ((NSNumber *)value).stringValue.mutableCopy); } else if ([value isKindOfClass:[NSData class]]) { NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string); } else if ([value isKindOfClass:[NSURL class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSURL *)value).absoluteString : ((NSURL *)value).absoluteString.mutableCopy); } else if ([value isKindOfClass:[NSAttributedString class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (meta->_nsType == YYEncodingTypeNSString) ? ((NSAttributedString *)value).string : ((NSAttributedString *)value).string.mutableCopy); } } break; case YYEncodingTypeNSValue: case YYEncodingTypeNSNumber: case YYEncodingTypeNSDecimalNumber: { if (meta->_nsType == YYEncodingTypeNSNumber) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSNumberCreateFromID(value)); } else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) { if ([value isKindOfClass:[NSDecimalNumber class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else if ([value isKindOfClass:[NSNumber class]]) { NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum); } else if ([value isKindOfClass:[NSString class]]) { NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value]; NSDecimal dec = decNum.decimalValue; if (dec._length == 0 && dec._isNegative) { decNum = nil; // NaN } ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum); } } else { // YYEncodingTypeNSValue if ([value isKindOfClass:[NSValue class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } } } break; case YYEncodingTypeNSData: case YYEncodingTypeNSMutableData: { if ([value isKindOfClass:[NSData class]]) { if (meta->_nsType == YYEncodingTypeNSData) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { NSMutableData *data = ((NSData *)value).mutableCopy; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data); } } else if ([value isKindOfClass:[NSString class]]) { NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding]; if (meta->_nsType == YYEncodingTypeNSMutableData) { data = ((NSData *)data).mutableCopy; } ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data); } } break; case YYEncodingTypeNSDate: { if ([value isKindOfClass:[NSDate class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else if ([value isKindOfClass:[NSString class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSDateFromString(value)); } } break; case YYEncodingTypeNSURL: { if ([value isKindOfClass:[NSURL class]]) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else if ([value isKindOfClass:[NSString class]]) { NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet]; NSString *str = [value stringByTrimmingCharactersInSet:set]; if (str.length == 0) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]); } } } break; case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: { if (meta->_genericCls) { NSArray *valueArr = nil; if ([value isKindOfClass:[NSArray class]]) valueArr = value; else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects; if (valueArr) { NSMutableArray *objectArr = [NSMutableArray new]; for (id one in valueArr) { if ([one isKindOfClass:meta->_genericCls]) { [objectArr addObject:one]; } else if ([one isKindOfClass:[NSDictionary class]]) { Class cls = meta->_genericCls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:one]; if (!cls) cls = meta->_genericCls; // for xcode code coverage } NSObject *newOne = [cls new]; [newOne yy_modelSetWithDictionary:one]; if (newOne) [objectArr addObject:newOne]; } } ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr); } } else { if ([value isKindOfClass:[NSArray class]]) { if (meta->_nsType == YYEncodingTypeNSArray) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSArray *)value).mutableCopy); } } else if ([value isKindOfClass:[NSSet class]]) { if (meta->_nsType == YYEncodingTypeNSArray) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects.mutableCopy); } } } } break; case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: { if ([value isKindOfClass:[NSDictionary class]]) { if (meta->_genericCls) { NSMutableDictionary *dic = [NSMutableDictionary new]; [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) { if ([oneValue isKindOfClass:[NSDictionary class]]) { Class cls = meta->_genericCls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:oneValue]; if (!cls) cls = meta->_genericCls; // for xcode code coverage } NSObject *newOne = [cls new]; [newOne yy_modelSetWithDictionary:(id)oneValue]; if (newOne) dic[oneKey] = newOne; } }]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic); } else { if (meta->_nsType == YYEncodingTypeNSDictionary) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSDictionary *)value).mutableCopy); } } } } break; case YYEncodingTypeNSSet: case YYEncodingTypeNSMutableSet: { NSSet *valueSet = nil; if ([value isKindOfClass:[NSArray class]]) valueSet = [NSMutableSet setWithArray:value]; else if ([value isKindOfClass:[NSSet class]]) valueSet = ((NSSet *)value); if (meta->_genericCls) { NSMutableSet *set = [NSMutableSet new]; for (id one in valueSet) { if ([one isKindOfClass:meta->_genericCls]) { [set addObject:one]; } else if ([one isKindOfClass:[NSDictionary class]]) { Class cls = meta->_genericCls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:one]; if (!cls) cls = meta->_genericCls; // for xcode code coverage } NSObject *newOne = [cls new]; [newOne yy_modelSetWithDictionary:one]; if (newOne) [set addObject:newOne]; } } ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set); } else { if (meta->_nsType == YYEncodingTypeNSSet) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet); } else { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)valueSet).mutableCopy); } } } // break; commented for code coverage in next line default: break; } } } else { BOOL isNull = (value == (id)kCFNull); switch (meta->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { if (isNull) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil); } else if ([value isKindOfClass:meta->_cls] || !meta->_cls) { ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value); } else if ([value isKindOfClass:[NSDictionary class]]) { NSObject *one = nil; if (meta->_getter) { one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter); } if (one) { [one yy_modelSetWithDictionary:value]; } else { Class cls = meta->_cls; if (meta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:value]; if (!cls) cls = meta->_genericCls; // for xcode code coverage } one = [cls new]; [one yy_modelSetWithDictionary:value]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one); } } } break; case YYEncodingTypeClass: { if (isNull) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL); } else { Class cls = nil; if ([value isKindOfClass:[NSString class]]) { cls = NSClassFromString(value); if (cls) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls); } } else { cls = object_getClass(value); if (cls) { if (class_isMetaClass(cls)) { ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value); } } } } } break; case YYEncodingTypeSEL: { if (isNull) { ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL); } else if ([value isKindOfClass:[NSString class]]) { SEL sel = NSSelectorFromString(value); if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel); } } break; case YYEncodingTypeBlock: { if (isNull) { ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL); } else if ([value isKindOfClass:YYNSBlockClass()]) { ((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value); } } break; case YYEncodingTypeStruct: case YYEncodingTypeUnion: case YYEncodingTypeCArray: { if ([value isKindOfClass:[NSValue class]]) { const char *valueType = ((NSValue *)value).objCType; const char *metaType = meta->_info.typeEncoding.UTF8String; if (valueType && metaType && strcmp(valueType, metaType) == 0) { [model setValue:value forKey:meta->_name]; } } } break; case YYEncodingTypePointer: case YYEncodingTypeCString: { if (isNull) { ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL); } else if ([value isKindOfClass:[NSValue class]]) { NSValue *nsValue = value; if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) { ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue); } } } // break; commented for code coverage in next line default: break; } } } typedef struct { void *modelMeta; ///< _YYModelMeta void *model; ///< id (self) void *dictionary; ///< NSDictionary (json) } ModelSetContext; /** Apply function for dictionary, to set the key-value pair to model. @param _key should not be nil, NSString. @param _value should not be nil. @param _context _context.modelMeta and _context.model should not be nil. */ static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) { ModelSetContext *context = _context; __unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta); __unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)]; __unsafe_unretained id model = (__bridge id)(context->model); while (propertyMeta) { if (propertyMeta->_setter) { ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta); } propertyMeta = propertyMeta->_next; }; } /** Apply function for model property meta, to set dictionary to model. @param _propertyMeta should not be nil, _YYModelPropertyMeta. @param _context _context.model and _context.dictionary should not be nil. */ static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) { ModelSetContext *context = _context; __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary); __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta); if (!propertyMeta->_setter) return; id value = nil; if (propertyMeta->_mappedToKeyArray) { value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray); } else if (propertyMeta->_mappedToKeyPath) { value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath); } else { value = [dictionary objectForKey:propertyMeta->_mappedToKey]; } if (value) { __unsafe_unretained id model = (__bridge id)(context->model); ModelSetValueForProperty(model, value, propertyMeta); } } /** Returns a valid JSON object (NSArray/NSDictionary/NSString/NSNumber/NSNull), or nil if an error occurs. @param model Model, can be nil. @return JSON object, nil if an error occurs. */ static id ModelToJSONObjectRecursive(NSObject *model) { if (!model || model == (id)kCFNull) return model; if ([model isKindOfClass:[NSString class]]) return model; if ([model isKindOfClass:[NSNumber class]]) return model; if ([model isKindOfClass:[NSDictionary class]]) { if ([NSJSONSerialization isValidJSONObject:model]) return model; NSMutableDictionary *newDic = [NSMutableDictionary new]; [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description; if (!stringKey) return; id jsonObj = ModelToJSONObjectRecursive(obj); if (!jsonObj) jsonObj = (id)kCFNull; newDic[stringKey] = jsonObj; }]; return newDic; } if ([model isKindOfClass:[NSSet class]]) { NSArray *array = ((NSSet *)model).allObjects; if ([NSJSONSerialization isValidJSONObject:array]) return array; NSMutableArray *newArray = [NSMutableArray new]; for (id obj in array) { if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { [newArray addObject:obj]; } else { id jsonObj = ModelToJSONObjectRecursive(obj); if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; } } return newArray; } if ([model isKindOfClass:[NSArray class]]) { if ([NSJSONSerialization isValidJSONObject:model]) return model; NSMutableArray *newArray = [NSMutableArray new]; for (id obj in (NSArray *)model) { if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) { [newArray addObject:obj]; } else { id jsonObj = ModelToJSONObjectRecursive(obj); if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj]; } } return newArray; } if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString; if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string; if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model]; if ([model isKindOfClass:[NSData class]]) return nil; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]]; if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil; NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64]; __unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { if (!propertyMeta->_getter) return; id value = nil; if (propertyMeta->_isCNumber) { value = ModelCreateNumberFromProperty(model, propertyMeta); } else if (propertyMeta->_nsType) { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = ModelToJSONObjectRecursive(v); } else { switch (propertyMeta->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = ModelToJSONObjectRecursive(v); if (value == (id)kCFNull) value = nil; } break; case YYEncodingTypeClass: { Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = v ? NSStringFromClass(v) : nil; } break; case YYEncodingTypeSEL: { SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter); value = v ? NSStringFromSelector(v) : nil; } break; default: break; } } if (!value) return; if (propertyMeta->_mappedToKeyPath) { NSMutableDictionary *superDic = dic; NSMutableDictionary *subDic = nil; for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) { NSString *key = propertyMeta->_mappedToKeyPath[i]; if (i + 1 == max) { // end if (!superDic[key]) superDic[key] = value; break; } subDic = superDic[key]; if (subDic) { if ([subDic isKindOfClass:[NSDictionary class]]) { subDic = subDic.mutableCopy; superDic[key] = subDic; } else { break; } } else { subDic = [NSMutableDictionary new]; superDic[key] = subDic; } superDic = subDic; subDic = nil; } } else { if (!dic[propertyMeta->_mappedToKey]) { dic[propertyMeta->_mappedToKey] = value; } } }]; if (modelMeta->_hasCustomTransformToDictionary) { BOOL suc = [((id)model) modelCustomTransformToDictionary:dic]; if (!suc) return nil; } return result; } /// Add indent to string (exclude first line) static NSMutableString *ModelDescriptionAddIndent(NSMutableString *desc, NSUInteger indent) { for (NSUInteger i = 0, max = desc.length; i < max; i++) { unichar c = [desc characterAtIndex:i]; if (c == '\n') { for (NSUInteger j = 0; j < indent; j++) { [desc insertString:@" " atIndex:i + 1]; } i += indent * 4; max += indent * 4; } } return desc; } /// Generaate a description string static NSString *ModelDescription(NSObject *model) { static const int kDescMaxLength = 100; if (!model) return @""; if (model == (id)kCFNull) return @""; if (![model isKindOfClass:[NSObject class]]) return [NSString stringWithFormat:@"%@",model]; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:model.class]; switch (modelMeta->_nsType) { case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: { return [NSString stringWithFormat:@"\"%@\"",model]; } case YYEncodingTypeNSValue: case YYEncodingTypeNSData: case YYEncodingTypeNSMutableData: { NSString *tmp = model.description; if (tmp.length > kDescMaxLength) { tmp = [tmp substringToIndex:kDescMaxLength]; tmp = [tmp stringByAppendingString:@"..."]; } return tmp; } case YYEncodingTypeNSNumber: case YYEncodingTypeNSDecimalNumber: case YYEncodingTypeNSDate: case YYEncodingTypeNSURL: { return [NSString stringWithFormat:@"%@",model]; } case YYEncodingTypeNSSet: case YYEncodingTypeNSMutableSet: { model = ((NSSet *)model).allObjects; } // no break case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: { NSArray *array = (id)model; NSMutableString *desc = [NSMutableString new]; if (array.count == 0) { return [desc stringByAppendingString:@"[]"]; } else { [desc appendFormat:@"[\n"]; for (NSUInteger i = 0, max = array.count; i < max; i++) { NSObject *obj = array[i]; [desc appendString:@" "]; [desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)]; [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; } [desc appendString:@"]"]; return desc; } } case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: { NSDictionary *dic = (id)model; NSMutableString *desc = [NSMutableString new]; if (dic.count == 0) { return [desc stringByAppendingString:@"{}"]; } else { NSArray *keys = dic.allKeys; [desc appendFormat:@"{\n"]; for (NSUInteger i = 0, max = keys.count; i < max; i++) { NSString *key = keys[i]; NSObject *value = dic[key]; [desc appendString:@" "]; [desc appendFormat:@"%@ = %@",key, ModelDescriptionAddIndent(ModelDescription(value).mutableCopy, 1)]; [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; } [desc appendString:@"}"]; } return desc; } default: { NSMutableString *desc = [NSMutableString new]; [desc appendFormat:@"<%@: %p>", model.class, model]; if (modelMeta->_allPropertyMetas.count == 0) return desc; // sort property names NSArray *properties = [modelMeta->_allPropertyMetas sortedArrayUsingComparator:^NSComparisonResult(_YYModelPropertyMeta *p1, _YYModelPropertyMeta *p2) { return [p1->_name compare:p2->_name]; }]; [desc appendFormat:@" {\n"]; for (NSUInteger i = 0, max = properties.count; i < max; i++) { _YYModelPropertyMeta *property = properties[i]; NSString *propertyDesc; if (property->_isCNumber) { NSNumber *num = ModelCreateNumberFromProperty(model, property); propertyDesc = num.stringValue; } else { switch (property->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); propertyDesc = ModelDescription(v); if (!propertyDesc) propertyDesc = @""; } break; case YYEncodingTypeClass: { id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); propertyDesc = ((NSObject *)v).description; if (!propertyDesc) propertyDesc = @""; } break; case YYEncodingTypeSEL: { SEL sel = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); if (sel) propertyDesc = NSStringFromSelector(sel); else propertyDesc = @""; } break; case YYEncodingTypeBlock: { id block = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); propertyDesc = block ? ((NSObject *)block).description : @""; } break; case YYEncodingTypeCArray: case YYEncodingTypeCString: case YYEncodingTypePointer: { void *pointer = ((void* (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter); propertyDesc = [NSString stringWithFormat:@"%p",pointer]; } break; case YYEncodingTypeStruct: case YYEncodingTypeUnion: { NSValue *value = [model valueForKey:property->_name]; propertyDesc = value ? value.description : @"{unknown}"; } break; default: propertyDesc = @""; } } propertyDesc = ModelDescriptionAddIndent(propertyDesc.mutableCopy, 1); [desc appendFormat:@" %@ = %@",property->_name, propertyDesc]; [desc appendString:(i + 1 == max) ? @"\n" : @";\n"]; } [desc appendFormat:@"}"]; return desc; } } } @implementation NSObject (YYModel) + (NSDictionary *)_yy_dictionaryWithJSON:(id)json { if (!json || json == (id)kCFNull) return nil; NSDictionary *dic = nil; NSData *jsonData = nil; if ([json isKindOfClass:[NSDictionary class]]) { dic = json; } else if ([json isKindOfClass:[NSString class]]) { jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; } else if ([json isKindOfClass:[NSData class]]) { jsonData = json; } if (jsonData) { dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; if (![dic isKindOfClass:[NSDictionary class]]) dic = nil; } return dic; } + (instancetype)yy_modelWithJSON:(id)json { NSDictionary *dic = [self _yy_dictionaryWithJSON:json]; return [self yy_modelWithDictionary:dic]; } + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary { if (!dictionary || dictionary == (id)kCFNull) return nil; if (![dictionary isKindOfClass:[NSDictionary class]]) return nil; Class cls = [self class]; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls]; if (modelMeta->_hasCustomClassFromDictionary) { cls = [cls modelCustomClassForDictionary:dictionary] ?: cls; } NSObject *one = [cls new]; if ([one yy_modelSetWithDictionary:dictionary]) return one; return nil; } - (BOOL)yy_modelSetWithJSON:(id)json { NSDictionary *dic = [NSObject _yy_dictionaryWithJSON:json]; return [self yy_modelSetWithDictionary:dic]; } - (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic { if (!dic || dic == (id)kCFNull) return NO; if (![dic isKindOfClass:[NSDictionary class]]) return NO; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)]; if (modelMeta->_keyMappedCount == 0) return NO; if (modelMeta->_hasCustomWillTransformFromDictionary) { dic = [((id)self) modelCustomWillTransformFromDictionary:dic]; if (![dic isKindOfClass:[NSDictionary class]]) return NO; } ModelSetContext context = {0}; context.modelMeta = (__bridge void *)(modelMeta); context.model = (__bridge void *)(self); context.dictionary = (__bridge void *)(dic); if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) { CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context); if (modelMeta->_keyPathPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_multiKeysPropertyMetas) { CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context); } } else { CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, modelMeta->_keyMappedCount), ModelSetWithPropertyMetaArrayFunction, &context); } if (modelMeta->_hasCustomTransformFromDictionary) { return [((id)self) modelCustomTransformFromDictionary:dic]; } return YES; } - (id)yy_modelToJSONObject { /* Apple said: The top level object is an NSArray or NSDictionary. All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull. All dictionary keys are instances of NSString. Numbers are not NaN or infinity. */ id jsonObject = ModelToJSONObjectRecursive(self); if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject; if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject; return nil; } - (NSData *)yy_modelToJSONData { id jsonObject = [self yy_modelToJSONObject]; if (!jsonObject) return nil; return [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:NULL]; } - (NSString *)yy_modelToJSONString { NSData *jsonData = [self yy_modelToJSONData]; if (jsonData.length == 0) return nil; return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; } - (id)yy_modelCopy{ if (self == (id)kCFNull) return self; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; if (modelMeta->_nsType) return [self copy]; NSObject *one = [self.class new]; for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { if (!propertyMeta->_getter || !propertyMeta->_setter) continue; if (propertyMeta->_isCNumber) { switch (propertyMeta->_type & YYEncodingTypeMask) { case YYEncodingTypeBool: { bool num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } break; case YYEncodingTypeInt8: case YYEncodingTypeUInt8: { uint8_t num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } break; case YYEncodingTypeInt16: case YYEncodingTypeUInt16: { uint16_t num = ((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } break; case YYEncodingTypeInt32: case YYEncodingTypeUInt32: { uint32_t num = ((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } break; case YYEncodingTypeInt64: case YYEncodingTypeUInt64: { uint64_t num = ((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } break; case YYEncodingTypeFloat: { float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } break; case YYEncodingTypeDouble: { double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } break; case YYEncodingTypeLongDouble: { long double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num); } // break; commented for code coverage in next line default: break; } } else { switch (propertyMeta->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: case YYEncodingTypeClass: case YYEncodingTypeBlock: { id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value); } break; case YYEncodingTypeSEL: case YYEncodingTypePointer: case YYEncodingTypeCString: { size_t value = ((size_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter); ((void (*)(id, SEL, size_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value); } break; case YYEncodingTypeStruct: case YYEncodingTypeUnion: { @try { NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)]; if (value) { [one setValue:value forKey:propertyMeta->_name]; } } @catch (NSException *exception) {} } // break; commented for code coverage in next line default: break; } } } return one; } - (void)yy_modelEncodeWithCoder:(NSCoder *)aCoder { if (!aCoder) return; if (self == (id)kCFNull) { [((id)self)encodeWithCoder:aCoder]; return; } _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; if (modelMeta->_nsType) { [((id)self)encodeWithCoder:aCoder]; return; } for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { if (!propertyMeta->_getter) return; if (propertyMeta->_isCNumber) { NSNumber *value = ModelCreateNumberFromProperty(self, propertyMeta); if (value) [aCoder encodeObject:value forKey:propertyMeta->_name]; } else { switch (propertyMeta->_type & YYEncodingTypeMask) { case YYEncodingTypeObject: { id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter); if (value && (propertyMeta->_nsType || [value respondsToSelector:@selector(encodeWithCoder:)])) { if ([value isKindOfClass:[NSValue class]]) { if ([value isKindOfClass:[NSNumber class]]) { [aCoder encodeObject:value forKey:propertyMeta->_name]; } } else { [aCoder encodeObject:value forKey:propertyMeta->_name]; } } } break; case YYEncodingTypeSEL: { SEL value = ((SEL (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter); if (value) { NSString *str = NSStringFromSelector(value); [aCoder encodeObject:str forKey:propertyMeta->_name]; } } break; case YYEncodingTypeStruct: case YYEncodingTypeUnion: { if (propertyMeta->_isKVCCompatible && propertyMeta->_isStructAvailableForKeyedArchiver) { @try { NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)]; [aCoder encodeObject:value forKey:propertyMeta->_name]; } @catch (NSException *exception) {} } } break; default: break; } } } } - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder { if (!aDecoder) return self; if (self == (id)kCFNull) return self; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; if (modelMeta->_nsType) return self; for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { if (!propertyMeta->_setter) continue; if (propertyMeta->_isCNumber) { NSNumber *value = [aDecoder decodeObjectForKey:propertyMeta->_name]; if ([value isKindOfClass:[NSNumber class]]) { ModelSetNumberToProperty(self, value, propertyMeta); [value class]; } } else { YYEncodingType type = propertyMeta->_type & YYEncodingTypeMask; switch (type) { case YYEncodingTypeObject: { id value = [aDecoder decodeObjectForKey:propertyMeta->_name]; ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)self, propertyMeta->_setter, value); } break; case YYEncodingTypeSEL: { NSString *str = [aDecoder decodeObjectForKey:propertyMeta->_name]; if ([str isKindOfClass:[NSString class]]) { SEL sel = NSSelectorFromString(str); ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_setter, sel); } } break; case YYEncodingTypeStruct: case YYEncodingTypeUnion: { if (propertyMeta->_isKVCCompatible) { @try { NSValue *value = [aDecoder decodeObjectForKey:propertyMeta->_name]; if (value) [self setValue:value forKey:propertyMeta->_name]; } @catch (NSException *exception) {} } } break; default: break; } } } return self; } - (NSUInteger)yy_modelHash { if (self == (id)kCFNull) return [self hash]; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; if (modelMeta->_nsType) return [self hash]; NSUInteger value = 0; NSUInteger count = 0; for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { if (!propertyMeta->_isKVCCompatible) continue; value ^= [[self valueForKey:NSStringFromSelector(propertyMeta->_getter)] hash]; count++; } if (count == 0) value = (long)((__bridge void *)self); return value; } - (BOOL)yy_modelIsEqual:(id)model { if (self == model) return YES; if (![model isMemberOfClass:self.class]) return NO; _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; if (modelMeta->_nsType) return [self isEqual:model]; if ([self hash] != [model hash]) return NO; for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) { if (!propertyMeta->_isKVCCompatible) continue; id this = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)]; id that = [model valueForKey:NSStringFromSelector(propertyMeta->_getter)]; if (this == that) continue; if (this == nil || that == nil) return NO; if (![this isEqual:that]) return NO; } return YES; } - (NSString *)yy_modelDescription { return ModelDescription(self); } @end @implementation NSArray (YYModel) + (NSArray *)yy_modelArrayWithClass:(Class)cls json:(id)json { if (!json) return nil; NSArray *arr = nil; NSData *jsonData = nil; if ([json isKindOfClass:[NSArray class]]) { arr = json; } else if ([json isKindOfClass:[NSString class]]) { jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; } else if ([json isKindOfClass:[NSData class]]) { jsonData = json; } if (jsonData) { arr = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; if (![arr isKindOfClass:[NSArray class]]) arr = nil; } return [self yy_modelArrayWithClass:cls array:arr]; } + (NSArray *)yy_modelArrayWithClass:(Class)cls array:(NSArray *)arr { if (!cls || !arr) return nil; NSMutableArray *result = [NSMutableArray new]; for (NSDictionary *dic in arr) { if (![dic isKindOfClass:[NSDictionary class]]) continue; NSObject *obj = [cls yy_modelWithDictionary:dic]; if (obj) [result addObject:obj]; } return result; } @end @implementation NSDictionary (YYModel) + (NSDictionary *)yy_modelDictionaryWithClass:(Class)cls json:(id)json { if (!json) return nil; NSDictionary *dic = nil; NSData *jsonData = nil; if ([json isKindOfClass:[NSDictionary class]]) { dic = json; } else if ([json isKindOfClass:[NSString class]]) { jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; } else if ([json isKindOfClass:[NSData class]]) { jsonData = json; } if (jsonData) { dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; if (![dic isKindOfClass:[NSDictionary class]]) dic = nil; } return [self yy_modelDictionaryWithClass:cls dictionary:dic]; } + (NSDictionary *)yy_modelDictionaryWithClass:(Class)cls dictionary:(NSDictionary *)dic { if (!cls || !dic) return nil; NSMutableDictionary *result = [NSMutableDictionary new]; for (NSString *key in dic.allKeys) { if (![key isKindOfClass:[NSString class]]) continue; NSObject *obj = [cls yy_modelWithDictionary:dic[key]]; if (obj) result[key] = obj; } return result; } @end ================================================ FILE: Example/Pods/YYModel/YYModel/YYClassInfo.h ================================================ // // YYClassInfo.h // YYModel // // Created by ibireme on 15/5/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 #import NS_ASSUME_NONNULL_BEGIN /** Type encoding's type. */ typedef NS_OPTIONS(NSUInteger, YYEncodingType) { YYEncodingTypeMask = 0xFF, ///< mask of type value YYEncodingTypeUnknown = 0, ///< unknown YYEncodingTypeVoid = 1, ///< void YYEncodingTypeBool = 2, ///< bool YYEncodingTypeInt8 = 3, ///< char / BOOL YYEncodingTypeUInt8 = 4, ///< unsigned char YYEncodingTypeInt16 = 5, ///< short YYEncodingTypeUInt16 = 6, ///< unsigned short YYEncodingTypeInt32 = 7, ///< int YYEncodingTypeUInt32 = 8, ///< unsigned int YYEncodingTypeInt64 = 9, ///< long long YYEncodingTypeUInt64 = 10, ///< unsigned long long YYEncodingTypeFloat = 11, ///< float YYEncodingTypeDouble = 12, ///< double YYEncodingTypeLongDouble = 13, ///< long double YYEncodingTypeObject = 14, ///< id YYEncodingTypeClass = 15, ///< Class YYEncodingTypeSEL = 16, ///< SEL YYEncodingTypeBlock = 17, ///< block YYEncodingTypePointer = 18, ///< void* YYEncodingTypeStruct = 19, ///< struct YYEncodingTypeUnion = 20, ///< union YYEncodingTypeCString = 21, ///< char* YYEncodingTypeCArray = 22, ///< char[10] (for example) YYEncodingTypeQualifierMask = 0xFF00, ///< mask of qualifier YYEncodingTypeQualifierConst = 1 << 8, ///< const YYEncodingTypeQualifierIn = 1 << 9, ///< in YYEncodingTypeQualifierInout = 1 << 10, ///< inout YYEncodingTypeQualifierOut = 1 << 11, ///< out YYEncodingTypeQualifierBycopy = 1 << 12, ///< bycopy YYEncodingTypeQualifierByref = 1 << 13, ///< byref YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway YYEncodingTypePropertyMask = 0xFF0000, ///< mask of property YYEncodingTypePropertyReadonly = 1 << 16, ///< readonly YYEncodingTypePropertyCopy = 1 << 17, ///< copy YYEncodingTypePropertyRetain = 1 << 18, ///< retain YYEncodingTypePropertyNonatomic = 1 << 19, ///< nonatomic YYEncodingTypePropertyWeak = 1 << 20, ///< weak YYEncodingTypePropertyCustomGetter = 1 << 21, ///< getter= YYEncodingTypePropertyCustomSetter = 1 << 22, ///< setter= YYEncodingTypePropertyDynamic = 1 << 23, ///< @dynamic }; /** Get the type from a Type-Encoding string. @discussion See also: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html @param typeEncoding A Type-Encoding string. @return The encoding type. */ YYEncodingType YYEncodingGetType(const char *typeEncoding); /** Instance variable information. */ @interface YYClassIvarInfo : NSObject @property (nonatomic, assign, readonly) Ivar ivar; ///< ivar opaque struct @property (nonatomic, strong, readonly) NSString *name; ///< Ivar's name @property (nonatomic, assign, readonly) ptrdiff_t offset; ///< Ivar's offset @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding @property (nonatomic, assign, readonly) YYEncodingType type; ///< Ivar's type /** Creates and returns an ivar info object. @param ivar ivar opaque struct @return A new object, or nil if an error occurs. */ - (instancetype)initWithIvar:(Ivar)ivar; @end /** Method information. */ @interface YYClassMethodInfo : NSObject @property (nonatomic, assign, readonly) Method method; ///< method opaque struct @property (nonatomic, strong, readonly) NSString *name; ///< method name @property (nonatomic, assign, readonly) SEL sel; ///< method's selector @property (nonatomic, assign, readonly) IMP imp; ///< method's implementation @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< method's parameter and return types @property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< return value's type @property (nullable, nonatomic, strong, readonly) NSArray *argumentTypeEncodings; ///< array of arguments' type /** Creates and returns a method info object. @param method method opaque struct @return A new object, or nil if an error occurs. */ - (instancetype)initWithMethod:(Method)method; @end /** Property information. */ @interface YYClassPropertyInfo : NSObject @property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct @property (nonatomic, strong, readonly) NSString *name; ///< property's name @property (nonatomic, assign, readonly) YYEncodingType type; ///< property's type @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value @property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name @property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil @property (nullable, nonatomic, strong, readonly) NSArray *protocols; ///< may nil @property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull) @property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull) /** Creates and returns a property info object. @param property property opaque struct @return A new object, or nil if an error occurs. */ - (instancetype)initWithProperty:(objc_property_t)property; @end /** Class information for a class. */ @interface YYClassInfo : NSObject @property (nonatomic, assign, readonly) Class cls; ///< class object @property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object @property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object @property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class @property (nonatomic, strong, readonly) NSString *name; ///< class name @property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info @property (nullable, nonatomic, strong, readonly) NSDictionary *ivarInfos; ///< ivars @property (nullable, nonatomic, strong, readonly) NSDictionary *methodInfos; ///< methods @property (nullable, nonatomic, strong, readonly) NSDictionary *propertyInfos; ///< properties /** If the class is changed (for example: you add a method to this class with 'class_addMethod()'), you should call this method to refresh the class info cache. After called this method, `needUpdate` will returns `YES`, and you should call 'classInfoWithClass' or 'classInfoWithClassName' to get the updated class info. */ - (void)setNeedUpdate; /** If this method returns `YES`, you should stop using this instance and call `classInfoWithClass` or `classInfoWithClassName` to get the updated class info. @return Whether this class info need update. */ - (BOOL)needUpdate; /** Get the class info of a specified Class. @discussion This method will cache the class info and super-class info at the first access to the Class. This method is thread-safe. @param cls A class. @return A class info, or nil if an error occurs. */ + (nullable instancetype)classInfoWithClass:(Class)cls; /** Get the class info of a specified Class. @discussion This method will cache the class info and super-class info at the first access to the Class. This method is thread-safe. @param className A class name. @return A class info, or nil if an error occurs. */ + (nullable instancetype)classInfoWithClassName:(NSString *)className; @end NS_ASSUME_NONNULL_END ================================================ FILE: Example/Pods/YYModel/YYModel/YYClassInfo.m ================================================ // // YYClassInfo.m // YYModel // // Created by ibireme on 15/5/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 "YYClassInfo.h" #import YYEncodingType YYEncodingGetType(const char *typeEncoding) { char *type = (char *)typeEncoding; if (!type) return YYEncodingTypeUnknown; size_t len = strlen(type); if (len == 0) return YYEncodingTypeUnknown; YYEncodingType qualifier = 0; bool prefix = true; while (prefix) { switch (*type) { case 'r': { qualifier |= YYEncodingTypeQualifierConst; type++; } break; case 'n': { qualifier |= YYEncodingTypeQualifierIn; type++; } break; case 'N': { qualifier |= YYEncodingTypeQualifierInout; type++; } break; case 'o': { qualifier |= YYEncodingTypeQualifierOut; type++; } break; case 'O': { qualifier |= YYEncodingTypeQualifierBycopy; type++; } break; case 'R': { qualifier |= YYEncodingTypeQualifierByref; type++; } break; case 'V': { qualifier |= YYEncodingTypeQualifierOneway; type++; } break; default: { prefix = false; } break; } } len = strlen(type); if (len == 0) return YYEncodingTypeUnknown | qualifier; switch (*type) { case 'v': return YYEncodingTypeVoid | qualifier; case 'B': return YYEncodingTypeBool | qualifier; case 'c': return YYEncodingTypeInt8 | qualifier; case 'C': return YYEncodingTypeUInt8 | qualifier; case 's': return YYEncodingTypeInt16 | qualifier; case 'S': return YYEncodingTypeUInt16 | qualifier; case 'i': return YYEncodingTypeInt32 | qualifier; case 'I': return YYEncodingTypeUInt32 | qualifier; case 'l': return YYEncodingTypeInt32 | qualifier; case 'L': return YYEncodingTypeUInt32 | qualifier; case 'q': return YYEncodingTypeInt64 | qualifier; case 'Q': return YYEncodingTypeUInt64 | qualifier; case 'f': return YYEncodingTypeFloat | qualifier; case 'd': return YYEncodingTypeDouble | qualifier; case 'D': return YYEncodingTypeLongDouble | qualifier; case '#': return YYEncodingTypeClass | qualifier; case ':': return YYEncodingTypeSEL | qualifier; case '*': return YYEncodingTypeCString | qualifier; case '^': return YYEncodingTypePointer | qualifier; case '[': return YYEncodingTypeCArray | qualifier; case '(': return YYEncodingTypeUnion | qualifier; case '{': return YYEncodingTypeStruct | qualifier; case '@': { if (len == 2 && *(type + 1) == '?') return YYEncodingTypeBlock | qualifier; else return YYEncodingTypeObject | qualifier; } default: return YYEncodingTypeUnknown | qualifier; } } @implementation YYClassIvarInfo - (instancetype)initWithIvar:(Ivar)ivar { if (!ivar) return nil; self = [super init]; _ivar = ivar; const char *name = ivar_getName(ivar); if (name) { _name = [NSString stringWithUTF8String:name]; } _offset = ivar_getOffset(ivar); const char *typeEncoding = ivar_getTypeEncoding(ivar); if (typeEncoding) { _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; _type = YYEncodingGetType(typeEncoding); } return self; } @end @implementation YYClassMethodInfo - (instancetype)initWithMethod:(Method)method { if (!method) return nil; self = [super init]; _method = method; _sel = method_getName(method); _imp = method_getImplementation(method); const char *name = sel_getName(_sel); if (name) { _name = [NSString stringWithUTF8String:name]; } const char *typeEncoding = method_getTypeEncoding(method); if (typeEncoding) { _typeEncoding = [NSString stringWithUTF8String:typeEncoding]; } char *returnType = method_copyReturnType(method); if (returnType) { _returnTypeEncoding = [NSString stringWithUTF8String:returnType]; free(returnType); } unsigned int argumentCount = method_getNumberOfArguments(method); if (argumentCount > 0) { NSMutableArray *argumentTypes = [NSMutableArray new]; for (unsigned int i = 0; i < argumentCount; i++) { char *argumentType = method_copyArgumentType(method, i); NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil; [argumentTypes addObject:type ? type : @""]; if (argumentType) free(argumentType); } _argumentTypeEncodings = argumentTypes; } return self; } @end @implementation YYClassPropertyInfo - (instancetype)initWithProperty:(objc_property_t)property { if (!property) return nil; self = [super init]; _property = property; const char *name = property_getName(property); if (name) { _name = [NSString stringWithUTF8String:name]; } YYEncodingType type = 0; unsigned int attrCount; objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); for (unsigned int i = 0; i < attrCount; i++) { switch (attrs[i].name[0]) { case 'T': { // Type encoding if (attrs[i].value) { _typeEncoding = [NSString stringWithUTF8String:attrs[i].value]; type = YYEncodingGetType(attrs[i].value); if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) { NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding]; if (![scanner scanString:@"@\"" intoString:NULL]) continue; NSString *clsName = nil; if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) { if (clsName.length) _cls = objc_getClass(clsName.UTF8String); } NSMutableArray *protocols = nil; while ([scanner scanString:@"<" intoString:NULL]) { NSString* protocol = nil; if ([scanner scanUpToString:@">" intoString: &protocol]) { if (protocol.length) { if (!protocols) protocols = [NSMutableArray new]; [protocols addObject:protocol]; } } [scanner scanString:@">" intoString:NULL]; } _protocols = protocols; } } } break; case 'V': { // Instance variable if (attrs[i].value) { _ivarName = [NSString stringWithUTF8String:attrs[i].value]; } } break; case 'R': { type |= YYEncodingTypePropertyReadonly; } break; case 'C': { type |= YYEncodingTypePropertyCopy; } break; case '&': { type |= YYEncodingTypePropertyRetain; } break; case 'N': { type |= YYEncodingTypePropertyNonatomic; } break; case 'D': { type |= YYEncodingTypePropertyDynamic; } break; case 'W': { type |= YYEncodingTypePropertyWeak; } break; case 'G': { type |= YYEncodingTypePropertyCustomGetter; if (attrs[i].value) { _getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } break; case 'S': { type |= YYEncodingTypePropertyCustomSetter; if (attrs[i].value) { _setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]); } } // break; commented for code coverage in next line default: break; } } if (attrs) { free(attrs); attrs = NULL; } _type = type; if (_name.length) { if (!_getter) { _getter = NSSelectorFromString(_name); } if (!_setter) { _setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]); } } return self; } @end @implementation YYClassInfo { BOOL _needUpdate; } - (instancetype)initWithClass:(Class)cls { if (!cls) return nil; self = [super init]; _cls = cls; _superCls = class_getSuperclass(cls); _isMeta = class_isMetaClass(cls); if (!_isMeta) { _metaCls = objc_getMetaClass(class_getName(cls)); } _name = NSStringFromClass(cls); [self _update]; _superClassInfo = [self.class classInfoWithClass:_superCls]; return self; } - (void)_update { _ivarInfos = nil; _methodInfos = nil; _propertyInfos = nil; Class cls = self.cls; unsigned int methodCount = 0; Method *methods = class_copyMethodList(cls, &methodCount); if (methods) { NSMutableDictionary *methodInfos = [NSMutableDictionary new]; _methodInfos = methodInfos; for (unsigned int i = 0; i < methodCount; i++) { YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]]; if (info.name) methodInfos[info.name] = info; } free(methods); } unsigned int propertyCount = 0; objc_property_t *properties = class_copyPropertyList(cls, &propertyCount); if (properties) { NSMutableDictionary *propertyInfos = [NSMutableDictionary new]; _propertyInfos = propertyInfos; for (unsigned int i = 0; i < propertyCount; i++) { YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]]; if (info.name) propertyInfos[info.name] = info; } free(properties); } unsigned int ivarCount = 0; Ivar *ivars = class_copyIvarList(cls, &ivarCount); if (ivars) { NSMutableDictionary *ivarInfos = [NSMutableDictionary new]; _ivarInfos = ivarInfos; for (unsigned int i = 0; i < ivarCount; i++) { YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]]; if (info.name) ivarInfos[info.name] = info; } free(ivars); } if (!_ivarInfos) _ivarInfos = @{}; if (!_methodInfos) _methodInfos = @{}; if (!_propertyInfos) _propertyInfos = @{}; _needUpdate = NO; } - (void)setNeedUpdate { _needUpdate = YES; } - (BOOL)needUpdate { return _needUpdate; } + (instancetype)classInfoWithClass:(Class)cls { if (!cls) return nil; static CFMutableDictionaryRef classCache; static CFMutableDictionaryRef metaCache; static dispatch_once_t onceToken; static dispatch_semaphore_t lock; dispatch_once(&onceToken, ^{ classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); lock = dispatch_semaphore_create(1); }); dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls)); if (info && info->_needUpdate) { [info _update]; } dispatch_semaphore_signal(lock); if (!info) { info = [[YYClassInfo alloc] initWithClass:cls]; if (info) { dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info)); dispatch_semaphore_signal(lock); } } return info; } + (instancetype)classInfoWithClassName:(NSString *)className { Class cls = NSClassFromString(className); return [self classInfoWithClass:cls]; } @end ================================================ FILE: Example/Pods/YYModel/YYModel/YYModel.h ================================================ // // YYModel.h // YYModel // // Created by ibireme on 15/5/10. // 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 YYModelVersionNumber; FOUNDATION_EXPORT const unsigned char YYModelVersionString[]; #import #import #else #import "NSObject+YYModel.h" #import "YYClassInfo.h" #endif ================================================ FILE: Example/Tests/Tests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Example/Tests/Tests-Prefix.pch ================================================ // The contents of this file are implicitly included at the beginning of every test case source file. #ifdef __OBJC__ #endif ================================================ FILE: Example/Tests/Tests.m ================================================ // // XYIAPKitTests.m // XYIAPKitTests // // Created by 1073605877 on 01/08/2018. // Copyright (c) 2018 1073605877. All rights reserved. // @import XCTest; @interface Tests : XCTestCase @end @implementation Tests - (void)setUp { [super 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. [super tearDown]; } - (void)testExample { XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); } @end ================================================ FILE: Example/Tests/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Example/XYIAPKit/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Example/XYIAPKit/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Example/XYIAPKit/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "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" } } ================================================ FILE: Example/XYIAPKit/XYAppDelegate.h ================================================ // // XYAppDelegate.h // XYIAPKit // // Created by 1073605877 on 01/08/2018. // Copyright (c) 2018 1073605877. All rights reserved. // @import UIKit; @interface XYAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: Example/XYIAPKit/XYAppDelegate.m ================================================ // // XYAppDelegate.m // XYIAPKit // // Created by 1073605877 on 01/08/2018. // Copyright (c) 2018 1073605877. All rights reserved. // #import "XYAppDelegate.h" @implementation XYAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. 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 throttle down OpenGL ES frame rates. 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 inactive 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 ================================================ FILE: Example/XYIAPKit/XYIAPKit-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Example/XYIAPKit/XYIAPKit-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #import #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __OBJC__ @import UIKit; @import Foundation; #endif ================================================ FILE: Example/XYIAPKit/XYViewController.h ================================================ // // XYViewController.h // XYIAPKit // // Created by 1073605877 on 01/08/2018. // Copyright (c) 2018 1073605877. All rights reserved. // @import UIKit; @interface XYViewController : UIViewController @end ================================================ FILE: Example/XYIAPKit/XYViewController.m ================================================ // // XYViewController.m // XYIAPKit // // Created by 1073605877 on 01/08/2018. // Copyright (c) 2018 1073605877. All rights reserved. // #import "XYViewController.h" @interface XYViewController () @end @implementation XYViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end ================================================ FILE: Example/XYIAPKit/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Example/XYIAPKit/main.m ================================================ // // main.m // XYIAPKit // // Created by 1073605877 on 01/08/2018. // Copyright (c) 2018 1073605877. All rights reserved. // @import UIKit; #import "XYAppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([XYAppDelegate class])); } } ================================================ FILE: Example/XYIAPKit.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; }; 6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; }; 6003F59E195388D20070C39A /* XYAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* XYAppDelegate.m */; }; 6003F5A7195388D20070C39A /* XYViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* XYViewController.m */; }; 6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; }; 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; 6003F5BC195388D20070C39A /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* Tests.m */; }; 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; 8EC106705EFCA27C3EA4AA2E /* Pods_XYIAPKit_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F9C273051AD5C6533ABF6FC /* Pods_XYIAPKit_Example.framework */; }; C3D9DAEFA1227A80BC68946B /* Pods_XYIAPKit_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAA332B44FE7D1F91DCB06B5 /* Pods_XYIAPKit_Tests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 6003F582195388D10070C39A /* Project object */; proxyType = 1; remoteGlobalIDString = 6003F589195388D20070C39A; remoteInfo = XYIAPKit; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 109490C0E2148F3DDC1E5B30 /* Pods-XYIAPKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XYIAPKit_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example.debug.xcconfig"; sourceTree = ""; }; 37C465A28F30A5383A2C28A0 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 5888448D6F077B919927D2FB /* Pods-XYIAPKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XYIAPKit_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests.release.xcconfig"; sourceTree = ""; }; 6003F58A195388D20070C39A /* XYIAPKit_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XYIAPKit_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 6003F595195388D20070C39A /* XYIAPKit-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "XYIAPKit-Info.plist"; sourceTree = ""; }; 6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 6003F59B195388D20070C39A /* XYIAPKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XYIAPKit-Prefix.pch"; sourceTree = ""; }; 6003F59C195388D20070C39A /* XYAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XYAppDelegate.h; sourceTree = ""; }; 6003F59D195388D20070C39A /* XYAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XYAppDelegate.m; sourceTree = ""; }; 6003F5A5195388D20070C39A /* XYViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XYViewController.h; sourceTree = ""; }; 6003F5A6195388D20070C39A /* XYViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XYViewController.m; sourceTree = ""; }; 6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 6003F5AE195388D20070C39A /* XYIAPKit_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XYIAPKit_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 6003F5BB195388D20070C39A /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = ""; }; 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; 71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 85EF550FE6343DFD651F4B53 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 9F9C273051AD5C6533ABF6FC /* Pods_XYIAPKit_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_XYIAPKit_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AAA332B44FE7D1F91DCB06B5 /* Pods_XYIAPKit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_XYIAPKit_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B1C47D97834666D867759FDF /* Pods-XYIAPKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XYIAPKit_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests.debug.xcconfig"; sourceTree = ""; }; B584493E95780E01F351ACB4 /* Pods-XYIAPKit_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XYIAPKit_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example.release.xcconfig"; sourceTree = ""; }; BBF6E665EF842C909ECE69B2 /* XYIAPKit.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = XYIAPKit.podspec; path = ../XYIAPKit.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 6003F587195388D20070C39A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */, 6003F592195388D20070C39A /* UIKit.framework in Frameworks */, 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */, 8EC106705EFCA27C3EA4AA2E /* Pods_XYIAPKit_Example.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 6003F5AB195388D20070C39A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */, 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */, 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */, C3D9DAEFA1227A80BC68946B /* Pods_XYIAPKit_Tests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 6003F581195388D10070C39A = { isa = PBXGroup; children = ( 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */, 6003F593195388D20070C39A /* Example for XYIAPKit */, 6003F5B5195388D20070C39A /* Tests */, 6003F58C195388D20070C39A /* Frameworks */, 6003F58B195388D20070C39A /* Products */, 718F3DACAF6F4EFA43052263 /* Pods */, ); sourceTree = ""; }; 6003F58B195388D20070C39A /* Products */ = { isa = PBXGroup; children = ( 6003F58A195388D20070C39A /* XYIAPKit_Example.app */, 6003F5AE195388D20070C39A /* XYIAPKit_Tests.xctest */, ); name = Products; sourceTree = ""; }; 6003F58C195388D20070C39A /* Frameworks */ = { isa = PBXGroup; children = ( 6003F58D195388D20070C39A /* Foundation.framework */, 6003F58F195388D20070C39A /* CoreGraphics.framework */, 6003F591195388D20070C39A /* UIKit.framework */, 6003F5AF195388D20070C39A /* XCTest.framework */, 9F9C273051AD5C6533ABF6FC /* Pods_XYIAPKit_Example.framework */, AAA332B44FE7D1F91DCB06B5 /* Pods_XYIAPKit_Tests.framework */, ); name = Frameworks; sourceTree = ""; }; 6003F593195388D20070C39A /* Example for XYIAPKit */ = { isa = PBXGroup; children = ( 6003F59C195388D20070C39A /* XYAppDelegate.h */, 6003F59D195388D20070C39A /* XYAppDelegate.m */, 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */, 6003F5A5195388D20070C39A /* XYViewController.h */, 6003F5A6195388D20070C39A /* XYViewController.m */, 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */, 6003F5A8195388D20070C39A /* Images.xcassets */, 6003F594195388D20070C39A /* Supporting Files */, ); name = "Example for XYIAPKit"; path = XYIAPKit; sourceTree = ""; }; 6003F594195388D20070C39A /* Supporting Files */ = { isa = PBXGroup; children = ( 6003F595195388D20070C39A /* XYIAPKit-Info.plist */, 6003F596195388D20070C39A /* InfoPlist.strings */, 6003F599195388D20070C39A /* main.m */, 6003F59B195388D20070C39A /* XYIAPKit-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; 6003F5B5195388D20070C39A /* Tests */ = { isa = PBXGroup; children = ( 6003F5BB195388D20070C39A /* Tests.m */, 6003F5B6195388D20070C39A /* Supporting Files */, ); path = Tests; sourceTree = ""; }; 6003F5B6195388D20070C39A /* Supporting Files */ = { isa = PBXGroup; children = ( 6003F5B7195388D20070C39A /* Tests-Info.plist */, 6003F5B8195388D20070C39A /* InfoPlist.strings */, 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = { isa = PBXGroup; children = ( BBF6E665EF842C909ECE69B2 /* XYIAPKit.podspec */, 37C465A28F30A5383A2C28A0 /* README.md */, 85EF550FE6343DFD651F4B53 /* LICENSE */, ); name = "Podspec Metadata"; sourceTree = ""; }; 718F3DACAF6F4EFA43052263 /* Pods */ = { isa = PBXGroup; children = ( 109490C0E2148F3DDC1E5B30 /* Pods-XYIAPKit_Example.debug.xcconfig */, B584493E95780E01F351ACB4 /* Pods-XYIAPKit_Example.release.xcconfig */, B1C47D97834666D867759FDF /* Pods-XYIAPKit_Tests.debug.xcconfig */, 5888448D6F077B919927D2FB /* Pods-XYIAPKit_Tests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 6003F589195388D20070C39A /* XYIAPKit_Example */ = { isa = PBXNativeTarget; buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "XYIAPKit_Example" */; buildPhases = ( 426413DDE8323D0AC75BD0F2 /* [CP] Check Pods Manifest.lock */, 6003F586195388D20070C39A /* Sources */, 6003F587195388D20070C39A /* Frameworks */, 6003F588195388D20070C39A /* Resources */, CAE0184E28D96E7A58950A79 /* [CP] Embed Pods Frameworks */, B12343D6C09462FA1EC70018 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = XYIAPKit_Example; productName = XYIAPKit; productReference = 6003F58A195388D20070C39A /* XYIAPKit_Example.app */; productType = "com.apple.product-type.application"; }; 6003F5AD195388D20070C39A /* XYIAPKit_Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "XYIAPKit_Tests" */; buildPhases = ( 0BBA8D9E47CCCAF031123076 /* [CP] Check Pods Manifest.lock */, 6003F5AA195388D20070C39A /* Sources */, 6003F5AB195388D20070C39A /* Frameworks */, 6003F5AC195388D20070C39A /* Resources */, 5EB00C799E43DDE1559661EA /* [CP] Embed Pods Frameworks */, C3893945292A7956686DD735 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( 6003F5B4195388D20070C39A /* PBXTargetDependency */, ); name = XYIAPKit_Tests; productName = XYIAPKitTests; productReference = 6003F5AE195388D20070C39A /* XYIAPKit_Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 6003F582195388D10070C39A /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = XY; LastUpgradeCheck = 0720; ORGANIZATIONNAME = 1073605877; TargetAttributes = { 6003F589195388D20070C39A = { DevelopmentTeam = UX6DFVZ4FJ; }; 6003F5AD195388D20070C39A = { TestTargetID = 6003F589195388D20070C39A; }; }; }; buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "XYIAPKit" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 6003F581195388D10070C39A; productRefGroup = 6003F58B195388D20070C39A /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 6003F589195388D20070C39A /* XYIAPKit_Example */, 6003F5AD195388D20070C39A /* XYIAPKit_Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 6003F588195388D20070C39A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */, 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */, 6003F5A9195388D20070C39A /* Images.xcassets in Resources */, 6003F598195388D20070C39A /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6003F5AC195388D20070C39A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 0BBA8D9E47CCCAF031123076 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-XYIAPKit_Tests-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; }; 426413DDE8323D0AC75BD0F2 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-XYIAPKit_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; }; 5EB00C799E43DDE1559661EA /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; B12343D6C09462FA1EC70018 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/XYIAPKit/XYIAPKit.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/XYIAPKit.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-resources.sh\"\n"; showEnvVarsInLog = 0; }; C3893945292A7956686DD735 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-XYIAPKit_Tests/Pods-XYIAPKit_Tests-resources.sh\"\n"; showEnvVarsInLog = 0; }; CAE0184E28D96E7A58950A79 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-frameworks.sh", "${BUILT_PRODUCTS_DIR}/XYIAPKit/XYIAPKit.framework", "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/XYIAPKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-XYIAPKit_Example/Pods-XYIAPKit_Example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 6003F586195388D20070C39A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6003F59E195388D20070C39A /* XYAppDelegate.m in Sources */, 6003F5A7195388D20070C39A /* XYViewController.m in Sources */, 6003F59A195388D20070C39A /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6003F5AA195388D20070C39A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6003F5BC195388D20070C39A /* Tests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 6003F5B4195388D20070C39A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 6003F589195388D20070C39A /* XYIAPKit_Example */; targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 6003F596195388D20070C39A /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 6003F597195388D20070C39A /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; 6003F5B8195388D20070C39A /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 6003F5B9195388D20070C39A /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 71719F9E1E33DC2100824A3D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 6003F5BD195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; 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 = 9.3; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 6003F5BE195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; 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 = 9.3; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 6003F5C0195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 109490C0E2148F3DDC1E5B30 /* Pods-XYIAPKit_Example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = UX6DFVZ4FJ; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "XYIAPKit/XYIAPKit-Prefix.pch"; INFOPLIST_FILE = "XYIAPKit/XYIAPKit-Info.plist"; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; 6003F5C1195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = B584493E95780E01F351ACB4 /* Pods-XYIAPKit_Example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = UX6DFVZ4FJ; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "XYIAPKit/XYIAPKit-Prefix.pch"; INFOPLIST_FILE = "XYIAPKit/XYIAPKit-Info.plist"; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; 6003F5C3195388D20070C39A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B1C47D97834666D867759FDF /* Pods-XYIAPKit_Tests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "Tests/Tests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XYIAPKit_Example.app/XYIAPKit_Example"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; 6003F5C4195388D20070C39A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5888448D6F077B919927D2FB /* Pods-XYIAPKit_Tests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; INFOPLIST_FILE = "Tests/Tests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/XYIAPKit_Example.app/XYIAPKit_Example"; WRAPPER_EXTENSION = xctest; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 6003F585195388D10070C39A /* Build configuration list for PBXProject "XYIAPKit" */ = { isa = XCConfigurationList; buildConfigurations = ( 6003F5BD195388D20070C39A /* Debug */, 6003F5BE195388D20070C39A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "XYIAPKit_Example" */ = { isa = XCConfigurationList; buildConfigurations = ( 6003F5C0195388D20070C39A /* Debug */, 6003F5C1195388D20070C39A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "XYIAPKit_Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 6003F5C3195388D20070C39A /* Debug */, 6003F5C4195388D20070C39A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 6003F582195388D10070C39A /* Project object */; } ================================================ FILE: Example/XYIAPKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/XYIAPKit.xcodeproj/xcshareddata/xcschemes/XYIAPKit-Example.xcscheme ================================================ ================================================ FILE: Example/XYIAPKit.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Example/XYIAPKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: LICENSE ================================================ Copyright (c) 2018 1073605877 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. ================================================ FILE: README.md ================================================ # XYIAPKit [![CI Status](http://img.shields.io/travis/1073605877/XYIAPKit.svg?style=flat)](https://travis-ci.org/1073605877/XYIAPKit) [![Version](https://img.shields.io/cocoapods/v/XYIAPKit.svg?style=flat)](http://cocoapods.org/pods/XYIAPKit) [![License](https://img.shields.io/cocoapods/l/XYIAPKit.svg?style=flat)](http://cocoapods.org/pods/XYIAPKit) [![Platform](https://img.shields.io/cocoapods/p/XYIAPKit.svg?style=flat)](http://cocoapods.org/pods/XYIAPKit) [iOS内购编程指南](https://www.jianshu.com/p/17e0d11149f3) ## Example To run the example project, clone the repo, and run `pod install` from the Example directory first. ## Requirements ## Installation XYIAPKit is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: ```ruby pod 'XYIAPKit' ``` ## 功能 1、内购的基本功能,产品列表查询、添加购买、恢复购买 2、票据校验、自动续费订阅过期检测 3、交易记录保存 ## 使用方法 1、查询在线商品 ``` /** 请求在线商品,并存储于内存中 @param identifiers 产品id */ - (void)requestProducts:(NSSet*)identifiers success:(void (^)(NSArray *products, NSArray *invalidProductIdentifiers))successBlock failure:(void (^)(NSError *error))failureBlock; ######################################################################################################### NSSet *set = [[NSSet alloc] initWithArray:@[@"1", @"2", @"3", @"4", @"5", @"6"]]; [[XYStore defaultStore] requestProducts:set success:^(NSArray *products, NSArray *invalidProductIdentifiers) { } failure:^(NSError *error) { }]; ``` 2、添加购买 ``` [[XYStore defaultStore] addPayment:productId success:^(SKPaymentTransaction *transaction) { } failure:^(SKPaymentTransaction *transaction, NSError *error) { }]; ``` 3、恢复内购 ``` [[XYStore defaultStore] restoreTransactionsOnSuccess:^(NSArray *transactions) { NSLog(@"restore === %@", transactions); } failure:^(NSError *error) { NSLog(@"restore error === %@", error); }]; ``` 4、添加票据校验 1)、外部票据校验(可以通过信任的服务器进行校验) 创建校验对象(建议创建单例对象,以便监听),遵守XYStoreReceiptVerifier协议 ``` - (void)verifyTransaction:(SKPaymentTransaction *)transaction success:(void (^)(void))successBlock failure:(void (^)(NSError *))failureBlock { __weak typeof(self) weakSelf = self; [[XYStore defaultStore] base64Receipt:^(NSString *base64Data) { [[XYStore defaultStore] fetchProduct:transaction.payment.productIdentifier success:^(SKProduct *product) { //外部校验请求 [weakSelf callBackApple:product receiptData:base64Data success:successBlock failure:failureBlock]; } failure:failureBlock]; } failure:failureBlock]; } ``` 在`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions `中注册票据校验 ``` // 在此设置内购的票据校验,防止掉单问题的发生 [[XYStore defaultStore] registerReceiptVerifier:[XYAppReceiptVerifier shareInstance]]; ``` 2)、APP内部校验 podfile中添加`pod 'XYIAPKit/iTunesReceiptVerify', '~> 0.8.0'` 引入票据校验的sdk 在`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions `中注册票据校验 ``` // 在此设置内购的票据校验,防止掉单问题的发生 [[XYStore defaultStore] registerReceiptVerifier:[XYStoreiTunesReceiptVerifier shareInstance]]; ``` XYStoreiTunesReceiptVerifier中提供对自动续期产品的订阅过期的判断 ``` /** @ 适用自动续期订阅 判断是否已订阅 若果是非自动续费型商品,直接返回NO @param productId 自动续期订阅产品id @return YES: NO:未订阅或者订阅过期 */ - (BOOL)isSubscribedWithAutoRenewProduct:(NSString *)productId; ``` 也提供 针对持续有效的产品,Apple返回的票据记录中会一直保留其票据信息,可以通过票据记录判断是否有效 ``` /** 针对持续有效的产品,Apple返回的票据记录中会一直保留其票据信息,可以通过票据记录判断是否有效 支持:1、非续期订阅 2、非消耗型项目 注:消耗型项目一旦完成,不会长期保留在票据信息中 @param productId 产品id @return YES:消费有效 NO:无效 */ - (BOOL)isValidWithPersistentProductId:(NSString *)productId; ``` 5、添加交易记录存储 1)、存储在NSUserDefaults podfile中添加`pod 'XYIAPKit/UserDefaultPersistence', :'~> 0.8.0'` 在`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions `中注册票据校验 ``` // 在此设置内购的票据校验,防止掉单问题的发生 [[XYStore defaultStore] registerTransactionPersistor:[XYStoreUserDefaultsPersistence shareInstance]]; ``` 2)、存储于Keychain中 podfile中添加`pod 'XYIAPKit/KeychainPersistence', '~> 0.8.0'` 在`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions `中注册票据校验 ``` // 在此设置内购的票据校验,防止掉单问题的发生 [[XYStore defaultStore] registerTransactionPersistor:[XYStoreKeychainPersistence shareInstance]]; ``` ## Author 1073605877 ## License XYIAPKit is available under the MIT license. See the LICENSE file for more info. With reference to RMStore Copyright issues, notice as soon as possible ================================================ FILE: XYIAPKit/Assets/.gitkeep ================================================ ================================================ FILE: XYIAPKit/Core/NSNotification+XYStore.h ================================================ // // NSNotification+XYStore.h // Pods // // Created by qichao.ma on 2018/4/20. // #import #import extern NSString *const XYStoreNotificationInvalidProductIdentifiers; extern NSString *const XYStoreNotificationDownloadProgress; extern NSString *const XYStoreNotificationProductIdentifier; extern NSString *const XYStoreNotificationProducts; extern NSString *const XYStoreNotificationStoreDownload; extern NSString *const XYStoreNotificationStoreError; extern NSString *const XYStoreNotificationStoreReceipt; extern NSString *const XYStoreNotificationTransaction; extern NSString *const XYStoreNotificationTransactions; /** Category on NSNotification to recover store data from userInfo without requiring to know the keys. */ @interface NSNotification (XYStore) @property (nonatomic, readonly) float xy_downloadProgress; /** Array of product identifiers that were not recognized by the App Store. Used in @c storeProductsRequestFinished:. */ @property (nonatomic, readonly) NSArray *xy_invalidProductIdentifiers; /** Used in @c storeDownload*:, @c storePaymentTransactionFinished: and @c storePaymentTransactionFailed:. */ @property (nonatomic, readonly) NSString *xy_productIdentifier; /** Array of SKProducts, one product for each valid product identifier provided in the corresponding request. Used in @c storeProductsRequestFinished:. */ @property (nonatomic, readonly) NSArray *xy_products; /** Used in @c storeDownload*:. */ @property (nonatomic, readonly) SKDownload *xy_storeDownload; /** Used in @c storeDownloadFailed:, @c storePaymentTransactionFailed:, @c storeProductsRequestFailed:, @c storeRefreshReceiptFailed: and @c storeRestoreTransactionsFailed:. */ @property (nonatomic, readonly) NSError *xy_storeError; /** Used in @c storeDownload*:, @c storePaymentTransactionFinished: and in @c storePaymentTransactionFailed:. */ @property (nonatomic, readonly) SKPaymentTransaction *xy_transaction; /** Used in @c storeRestoreTransactionsFinished:. */ @property (nonatomic, readonly) NSArray *xy_transactions; @end ================================================ FILE: XYIAPKit/Core/NSNotification+XYStore.m ================================================ // // NSNotification+XYStore.m // Pods // // Created by qichao.ma on 2018/4/20. // #import "NSNotification+XYStore.h" NSString* const XYStoreNotificationInvalidProductIdentifiers = @"invalidProductIdentifiers"; NSString* const XYStoreNotificationDownloadProgress = @"downloadProgress"; NSString* const XYStoreNotificationProductIdentifier = @"productIdentifier"; NSString* const XYStoreNotificationProducts = @"products"; NSString* const XYStoreNotificationStoreDownload = @"storeDownload"; NSString* const XYStoreNotificationStoreError = @"storeError"; NSString* const XYStoreNotificationStoreReceipt = @"storeReceipt"; NSString* const XYStoreNotificationTransaction = @"transaction"; NSString* const XYStoreNotificationTransactions = @"transactions"; @implementation NSNotification (XYStore) - (float)xy_downloadProgress { return [self.userInfo[XYStoreNotificationDownloadProgress] floatValue]; } - (NSArray*)xy_invalidProductIdentifiers { return (self.userInfo)[XYStoreNotificationInvalidProductIdentifiers]; } - (NSString*)xy_productIdentifier { return (self.userInfo)[XYStoreNotificationProductIdentifier]; } - (NSArray*)xy_products { return (self.userInfo)[XYStoreNotificationProducts]; } - (SKDownload*)xy_storeDownload { return (self.userInfo)[XYStoreNotificationStoreDownload]; } - (NSError*)xy_storeError { return (self.userInfo)[XYStoreNotificationStoreError]; } - (SKPaymentTransaction*)xy_transaction { return (self.userInfo)[XYStoreNotificationTransaction]; } - (NSArray*)xy_transactions { return (self.userInfo)[XYStoreNotificationTransactions]; } @end ================================================ FILE: XYIAPKit/Core/XYReceiptRefreshService.h ================================================ // // XYReceiptRefreshService.h // Pods // // Created by qichao.ma on 2018/4/21. // #import #import @interface XYReceiptRefreshService : NSObject - (void)refreshReceiptOnSuccess:(void(^)(void))successBlock failure:(void(^)(NSError *error))failureBlock; @end ================================================ FILE: XYIAPKit/Core/XYReceiptRefreshService.m ================================================ // // XYReceiptRefreshService.m // Pods // // Created by qichao.ma on 2018/4/21. // #import "XYReceiptRefreshService.h" @interface XYReceiptRefreshService() { SKReceiptRefreshRequest *_refreshReceiptRequest; void (^_refreshReceiptFailureBlock)(NSError* error); void (^_refreshReceiptSuccessBlock)(void); } @end @implementation XYReceiptRefreshService - (void)refreshReceiptOnSuccess:(void(^)(void))successBlock failure:(void(^)(NSError *error))failureBlock { _refreshReceiptFailureBlock = failureBlock; _refreshReceiptSuccessBlock = successBlock; _refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}]; _refreshReceiptRequest.delegate = self; [_refreshReceiptRequest start]; } #pragma mark SKRequestDelegate - (void)requestDidFinish:(SKRequest *)request { NSLog(@"refresh receipt finished"); _refreshReceiptRequest = nil; if (_refreshReceiptSuccessBlock) { _refreshReceiptSuccessBlock(); _refreshReceiptSuccessBlock = nil; } } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { NSLog(@"refresh receipt failed with error %@", error.debugDescription); _refreshReceiptRequest = nil; if (_refreshReceiptFailureBlock) { _refreshReceiptFailureBlock(error); _refreshReceiptFailureBlock = nil; } } @end ================================================ FILE: XYIAPKit/Core/XYStore.h ================================================ // // XYStore.h // Pods // // Created by qichao.ma on 2018/4/19. // #import #import #import "XYStoreProtocol.h" #import "NSNotification+XYStore.h" extern NSString *const XYStoreErrorDomain; extern NSInteger const XYStoreErrorCodeDownloadCanceled; extern NSInteger const XYStoreErrorCodeUnknownProductIdentifier; extern NSInteger const XYStoreErrorCodeUnableToCompleteVerification; @interface XYStore : NSObject + (XYStore *)defaultStore; /** 外部内容下载 注:单利内部为weak持有,建议注册一个单利的contentDownloader @discussion Hosted content from Apple’s server (SKDownload) 自动下载,无需设置contentDownloader。 */ - (void)registerContentDownloader:(id)contentDownloader; /** 票据校验 注:内部为weak持有,建议注册一个单利的receiptVerifier */ - (void)registerReceiptVerifier:(id)receiptVerifier; /** 缓存订单 注:内部为weak持有,建议注册一个单利的transactionPersistor The transaction persistor. It is recommended to provide your own obfuscator if piracy is a concern. The store will use weak obfuscation via `NSKeyedArchiver` by default. @see XYStoreKeychainPersistence @see XYStoreUserDefaultsPersistence */ - (void)registerTransactionPersistor:(id)transactionPersistor; + (BOOL)canMakePayments; - (void)addPayment:(NSString*)productIdentifier; - (void)addPayment:(NSString*)productIdentifier success:(void (^)(SKPaymentTransaction *transaction))successBlock failure:(void (^)(SKPaymentTransaction *transaction, NSError *error))failureBlock; /** @param userIdentifier An opaque identifier for the user’s account on your system. Use this property to help the store detect irregular activity. For example, in a game, it would be unusual for dozens of different iTunes Store accounts to make purchases on behalf of the same in-game character. The recommended implementation is to use a one-way hash of the user’s account name to calculate the value for this property. */ - (void)addPayment:(NSString*)productIdentifier user:(NSString*)userIdentifier success:(void (^)(SKPaymentTransaction *transaction))successBlock failure:(void (^)(SKPaymentTransaction *transaction, NSError *error))failureBlock; /** 请求在线商品,并存储于内存中 @param identifiers 产品id */ - (void)requestProducts:(NSSet*)identifiers; /** 请求在线商品,并存储于内存中 @param identifiers 产品id */ - (void)requestProducts:(NSSet*)identifiers success:(void (^)(NSArray *products, NSArray *invalidProductIdentifiers))successBlock failure:(void (^)(NSError *error))failureBlock; /** 获取单个产品,若内存中已存在,直接返回;若没有则网络获取 @param identifier 产品id */ - (void)fetchProduct:(NSString *)identifier success:(void (^)(SKProduct *product))success failure:(void (^)(NSError *error))failure; /** 恢复购买 */ - (void)restoreTransactions; - (void)restoreTransactionsOnSuccess:(void (^)(NSArray *transactions))successBlock failure:(void (^)(NSError *error))failureBlock; - (void)restoreTransactionsOfUser:(NSString*)userIdentifier onSuccess:(void (^)(NSArray *transactions))successBlock failure:(void (^)(NSError *error))failureBlock; #pragma mark Receipt + (NSURL*)receiptURL; - (void)refreshReceipt; - (void)refreshReceiptOnSuccess:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock; /** 获取base64的票据,主要用于服务器的票据校验 */ - (void)base64Receipt:(void(^)(NSString *base64Data))success failure:(void(^)(NSError *error))failure; #pragma mark Product management - (SKProduct*)productForIdentifier:(NSString*)productIdentifier; + (NSString*)localizedPriceOfProduct:(SKProduct*)product; #pragma mark Notifications /** Adds an observer to the store. Unlike `SKPaymentQueue`, it is not necessary to set an observer. @param observer The observer to add. */ - (void)addStoreObserver:(id)observer; /** Removes an observer from the store. @param observer The observer to remove. */ - (void)removeStoreObserver:(id)observer; @end ================================================ FILE: XYIAPKit/Core/XYStore.m ================================================ // // XYStore.m // Pods // // Created by qichao.ma on 2018/4/19. // #import "XYStore.h" #import "XYStoreProductService.h" #import "XYReceiptRefreshService.h" NSString *const XYStoreErrorDomain = @"com.quvideo.store"; NSInteger const XYStoreErrorCodeDownloadCanceled = 300; NSInteger const XYStoreErrorCodeUnknownProductIdentifier = 100; NSInteger const XYStoreErrorCodeUnableToCompleteVerification = 200; NSString* const XYSKDownloadCanceled = @"XYSKDownloadCanceled"; NSString* const XYSKDownloadFailed = @"XYSKDownloadFailed"; NSString* const XYSKDownloadFinished = @"XYSKDownloadFinished"; NSString* const XYSKDownloadPaused = @"XYSKDownloadPaused"; NSString* const XYSKDownloadUpdated = @"XYSKDownloadUpdated"; NSString* const XYSKPaymentTransactionDeferred = @"XYSKPaymentTransactionDeferred"; NSString* const XYSKPaymentTransactionFailed = @"XYSKPaymentTransactionFailed"; NSString* const XYSKPaymentTransactionFinished = @"XYSKPaymentTransactionFinished"; NSString* const XYSKProductsRequestFailed = @"XYSKProductsRequestFailed"; NSString* const XYSKProductsRequestFinished = @"XYSKProductsRequestFinished"; NSString* const XYSKRefreshReceiptFailed = @"XYSKRefreshReceiptFailed"; NSString* const XYSKRefreshReceiptFinished = @"XYSKRefreshReceiptFinished"; NSString* const XYSKRestoreTransactionsFailed = @"XYSKRestoreTransactionsFailed"; NSString* const XYSKRestoreTransactionsFinished = @"XYSKRestoreTransactionsFinished"; typedef void (^XYSKPaymentTransactionFailureBlock)(SKPaymentTransaction *transaction, NSError *error); typedef void (^XYSKPaymentTransactionSuccessBlock)(SKPaymentTransaction *transaction); typedef void (^XYStoreFailureBlock)(NSError *error); typedef void (^XYStoreSuccessBlock)(void); @interface XYAddPaymentParameters : NSObject @property (nonatomic, strong) XYSKPaymentTransactionSuccessBlock successBlock; @property (nonatomic, strong) XYSKPaymentTransactionFailureBlock failureBlock; @end @implementation XYAddPaymentParameters @end @interface XYStore() { NSInteger _pendingRestoredTransactionsCount; BOOL _restoredCompletedTransactionsFinished; void (^_restoreTransactionsFailureBlock)(NSError* error); void (^_restoreTransactionsSuccessBlock)(NSArray* transactions); } // HACK: We use a dictionary of product identifiers because the returned SKPayment is different from the one we add to the queue. Bad Apple. @property (nonatomic, strong) NSMutableDictionary *addPaymentParameters; @property (nonatomic, strong) NSMutableDictionary *products; @property (nonatomic, strong) NSMutableArray *restoredTransactions; @property (nonatomic, strong) NSMutableSet *productsRequestSet; @property (nonatomic, strong) XYReceiptRefreshService *receiptService; @property (nonatomic, weak) id contentDownloader; @property (nonatomic, weak) id receiptVerifier; @property (nonatomic, weak) id transactionPersistor; @end @implementation XYStore - (instancetype) init { if (self = [super init]) { _restoredTransactions = [NSMutableArray array]; [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } return self; } - (void)dealloc { [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; } + (XYStore *)defaultStore { static XYStore *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[[self class] alloc] init]; }); return sharedInstance; } - (void)registerContentDownloader:(id)contentDownloader { _contentDownloader = contentDownloader; } - (void)registerReceiptVerifier:(id)receiptVerifier { _receiptVerifier = receiptVerifier; } - (void)registerTransactionPersistor:(id)transactionPersistor { _transactionPersistor = transactionPersistor; } #pragma mark StoreKit wrapper + (BOOL)canMakePayments { return [SKPaymentQueue canMakePayments]; } - (void)addPayment:(NSString*)productIdentifier { [self addPayment:productIdentifier success:nil failure:nil]; } - (void)addPayment:(NSString*)productIdentifier success:(void (^)(SKPaymentTransaction *transaction))successBlock failure:(void (^)(SKPaymentTransaction *transaction, NSError *error))failureBlock { [self addPayment:productIdentifier user:nil success:successBlock failure:failureBlock]; } - (void)addPayment:(NSString*)productIdentifier user:(NSString*)userIdentifier success:(void (^)(SKPaymentTransaction *transaction))successBlock failure:(void (^)(SKPaymentTransaction *transaction, NSError *error))failureBlock { __weak typeof(self) weakSelf = self; void(^errorBlock)(NSError *error) = ^(NSError *error) { if (failureBlock) { failureBlock(nil, error); } }; id completeBlock = ^(SKProduct *product) { if (!product) { NSString *errorDesc = NSLocalizedStringFromTable(@"Unknown product identifier", @"XYIAPKit", @"Error description"); NSError *error = [NSError errorWithDomain:XYStoreErrorDomain code:XYStoreErrorCodeUnknownProductIdentifier userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; errorBlock(error); }else { SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; if ([payment respondsToSelector:@selector(setApplicationUsername:)]) { payment.applicationUsername = userIdentifier; } XYAddPaymentParameters *parameters = [[XYAddPaymentParameters alloc] init]; parameters.successBlock = successBlock; parameters.failureBlock = failureBlock; weakSelf.addPaymentParameters[productIdentifier] = parameters; [[SKPaymentQueue defaultQueue] addPayment:payment]; } }; [self fetchProduct:productIdentifier success:completeBlock failure:errorBlock]; } #pragma mark - requestProducts - (void)requestProducts:(NSSet*)identifiers { [self requestProducts:identifiers success:nil failure:nil]; } - (void)requestProducts:(NSSet*)identifiers success:(void (^)(NSArray *products, NSArray *invalidProductIdentifiers))successBlock failure:(void (^)(NSError *error))failureBlock { __weak typeof(self) weakSelf = self; XYStoreProductService *service = [[XYStoreProductService alloc] init]; service.addProductBlock = ^(SKProduct *product) { [weakSelf addProduct:product]; }; service.removeProductRequestBlock = ^(XYStoreProductService *service) { [weakSelf removeProductsRequest:service]; }; [self.productsRequestSet addObject:service]; [service requestProducts:identifiers success:^(NSArray *products, NSArray *invalidIdentifiers) { if (successBlock) { successBlock(products, invalidIdentifiers); } NSDictionary *userInfo = @{XYStoreNotificationProducts: products, XYStoreNotificationInvalidProductIdentifiers: invalidIdentifiers}; [[NSNotificationCenter defaultCenter] postNotificationName:XYSKProductsRequestFinished object:self userInfo:userInfo]; } failure:^(NSError *error) { if (failureBlock) { failureBlock(error); } NSDictionary *userInfo = nil; if (error){ // error might be nil (e.g., on airplane mode) userInfo = @{XYStoreNotificationStoreError: error}; } [[NSNotificationCenter defaultCenter] postNotificationName:XYSKProductsRequestFailed object:self userInfo:userInfo]; }]; } - (void)fetchProduct:(NSString *)identifier success:(void (^)(SKProduct *product))success failure:(void (^)(NSError *error))failure { if (!identifier) { NSString *errorDesc = NSLocalizedStringFromTable(@"Unknown product identifier", @"XYIAPKit", @"Error description"); NSError *error = [NSError errorWithDomain:XYStoreErrorDomain code:XYStoreErrorCodeUnknownProductIdentifier userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; if (failure) { failure(error); } return; } SKProduct *product = [self productForIdentifier:identifier]; if (product) { success(product); return; } // 若内存中没有,网络获取 NSSet *set = [[NSSet alloc] initWithArray:@[identifier]]; [self requestProducts:set success:^(NSArray *products, NSArray *invalidProductIdentifiers) { if (products.count > 0) { if (success) { success(products.firstObject); } } } failure:failure]; } - (void)restoreTransactions { [self restoreTransactionsOnSuccess:nil failure:nil]; } - (void)restoreTransactionsOnSuccess:(void (^)(NSArray *transactions))successBlock failure:(void (^)(NSError *error))failureBlock { _restoredCompletedTransactionsFinished = NO; _pendingRestoredTransactionsCount = 0; _restoredTransactions = [NSMutableArray array]; _restoreTransactionsSuccessBlock = successBlock; _restoreTransactionsFailureBlock = failureBlock; [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } - (void)restoreTransactionsOfUser:(NSString*)userIdentifier onSuccess:(void (^)(NSArray *transactions))successBlock failure:(void (^)(NSError *error))failureBlock { NSAssert([[SKPaymentQueue defaultQueue] respondsToSelector:@selector(restoreCompletedTransactionsWithApplicationUsername:)], @"restoreCompletedTransactionsWithApplicationUsername: not supported in this iOS version. Use restoreTransactionsOnSuccess:failure: instead."); _restoredCompletedTransactionsFinished = NO; _pendingRestoredTransactionsCount = 0; _restoreTransactionsSuccessBlock = successBlock; _restoreTransactionsFailureBlock = failureBlock; [[SKPaymentQueue defaultQueue] restoreCompletedTransactionsWithApplicationUsername:userIdentifier]; } #pragma mark Receipt + (NSURL*)receiptURL { NSAssert(floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1, @"appStoreReceiptURL not supported in this iOS version."); NSURL *url = [NSBundle mainBundle].appStoreReceiptURL; return url; } - (void)refreshReceipt { [self refreshReceiptOnSuccess:nil failure:nil]; } - (void)refreshReceiptOnSuccess:(XYStoreSuccessBlock)successBlock failure:(XYStoreFailureBlock)failureBlock { [self.receiptService refreshReceiptOnSuccess:^{ if (successBlock) { successBlock(); } [[NSNotificationCenter defaultCenter] postNotificationName:XYSKRefreshReceiptFinished object:self]; } failure:^(NSError *error) { if (failureBlock) { failureBlock(error); } NSDictionary *userInfo = nil; if (error) { userInfo = @{XYStoreNotificationStoreError: error}; } [[NSNotificationCenter defaultCenter] postNotificationName:XYSKRefreshReceiptFailed object:self userInfo:userInfo]; }]; } - (void)base64Receipt:(void(^)(NSString *base64Data))success failure:(void(^)(NSError *error))failure { void(^handler)(NSURL *url) = ^(NSURL *url) { NSData *data = [NSData dataWithContentsOfURL:url]; NSString *base64Data = [data base64EncodedStringWithOptions:0]; if (success) { success(base64Data); } }; NSURL *URL = [NSBundle mainBundle].appStoreReceiptURL; if (URL) { handler(URL); }else { [self refreshReceiptOnSuccess:^{ NSURL *URL = [NSBundle mainBundle].appStoreReceiptURL; if (URL) { handler(URL); }else { if (failure) { failure([NSError errorWithDomain:@"com.iapkit" code:100001 userInfo:@{NSLocalizedDescriptionKey : @"None appStoreReceiptUR"}]); } } } failure:failure]; } } #pragma mark Product management - (SKProduct*)productForIdentifier:(NSString*)productIdentifier { return self.products[productIdentifier]; } + (NSString*)localizedPriceOfProduct:(SKProduct*)product { NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; numberFormatter.locale = product.priceLocale; NSString *formattedString = [numberFormatter stringFromNumber:product.price]; return formattedString; } #pragma mark Observers - (void)addStoreObserver:(id)observer { [self addStoreObserver:observer selector:@selector(storeDownloadCanceled:) notificationName:XYSKDownloadCanceled]; [self addStoreObserver:observer selector:@selector(storeDownloadFailed:) notificationName:XYSKDownloadFailed]; [self addStoreObserver:observer selector:@selector(storeDownloadFinished:) notificationName:XYSKDownloadFinished]; [self addStoreObserver:observer selector:@selector(storeDownloadPaused:) notificationName:XYSKDownloadPaused]; [self addStoreObserver:observer selector:@selector(storeDownloadUpdated:) notificationName:XYSKDownloadUpdated]; [self addStoreObserver:observer selector:@selector(storeProductsRequestFailed:) notificationName:XYSKProductsRequestFailed]; [self addStoreObserver:observer selector:@selector(storeProductsRequestFinished:) notificationName:XYSKProductsRequestFinished]; [self addStoreObserver:observer selector:@selector(storePaymentTransactionDeferred:) notificationName:XYSKPaymentTransactionDeferred]; [self addStoreObserver:observer selector:@selector(storePaymentTransactionFailed:) notificationName:XYSKPaymentTransactionFailed]; [self addStoreObserver:observer selector:@selector(storePaymentTransactionFinished:) notificationName:XYSKPaymentTransactionFinished]; [self addStoreObserver:observer selector:@selector(storeRefreshReceiptFailed:) notificationName:XYSKRefreshReceiptFailed]; [self addStoreObserver:observer selector:@selector(storeRefreshReceiptFinished:) notificationName:XYSKRefreshReceiptFinished]; [self addStoreObserver:observer selector:@selector(storeRestoreTransactionsFailed:) notificationName:XYSKRestoreTransactionsFailed]; [self addStoreObserver:observer selector:@selector(storeRestoreTransactionsFinished:) notificationName:XYSKRestoreTransactionsFinished]; } - (void)removeStoreObserver:(id)observer { [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKDownloadCanceled object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKDownloadFailed object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKDownloadFinished object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKDownloadPaused object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKDownloadUpdated object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKProductsRequestFailed object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKProductsRequestFinished object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKPaymentTransactionDeferred object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKPaymentTransactionFailed object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKPaymentTransactionFinished object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKRefreshReceiptFailed object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKRefreshReceiptFinished object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKRestoreTransactionsFailed object:self]; [[NSNotificationCenter defaultCenter] removeObserver:observer name:XYSKRestoreTransactionsFinished object:self]; } // Private - (void)addStoreObserver:(id)observer selector:(SEL)aSelector notificationName:(NSString*)notificationName { if ([observer respondsToSelector:aSelector]) { [[NSNotificationCenter defaultCenter] addObserver:observer selector:aSelector name:notificationName object:self]; } } #pragma mark SKPaymentTransactionObserver - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: [self didPurchaseTransaction:transaction queue:queue]; break; case SKPaymentTransactionStateFailed: [self didFailTransaction:transaction queue:queue error:transaction.error]; break; case SKPaymentTransactionStateRestored: [self didRestoreTransaction:transaction queue:queue]; break; case SKPaymentTransactionStateDeferred: [self didDeferTransaction:transaction]; break; default: break; } } } - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { NSLog(@"restore transactions finished"); _restoredCompletedTransactionsFinished = YES; [self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:nil]; } - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { NSLog(@"restored transactions failed with error %@", error.debugDescription); if (_restoreTransactionsFailureBlock != nil) { _restoreTransactionsFailureBlock(error); _restoreTransactionsFailureBlock = nil; } NSDictionary *userInfo = nil; if (error) { userInfo = @{XYStoreNotificationStoreError: error}; } [[NSNotificationCenter defaultCenter] postNotificationName:XYSKRestoreTransactionsFailed object:self userInfo:userInfo]; } - (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads { for (SKDownload *download in downloads) { switch (download.downloadState) { case SKDownloadStateActive: [self didUpdateDownload:download queue:queue]; break; case SKDownloadStateCancelled: [self didCancelDownload:download queue:queue]; break; case SKDownloadStateFailed: [self didFailDownload:download queue:queue]; break; case SKDownloadStateFinished: [self didFinishDownload:download queue:queue]; break; case SKDownloadStatePaused: [self didPauseDownload:download queue:queue]; break; case SKDownloadStateWaiting: // Do nothing break; } } } - (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product { return YES; } #pragma mark Download State - (void)didCancelDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue { SKPaymentTransaction *transaction = download.transaction; NSLog(@"download %@ for product %@ canceled", download.contentIdentifier, download.transaction.payment.productIdentifier); [self postNotificationWithName:XYSKDownloadCanceled download:download userInfoExtras:nil]; NSError *error = [NSError errorWithDomain:XYStoreErrorDomain code:XYStoreErrorCodeDownloadCanceled userInfo:@{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Download canceled", @"XYStore", @"Error description")}]; const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; if (!hasPendingDownloads) { [self didFailTransaction:transaction queue:queue error:error]; } } - (void)didFailDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue { NSError *error = download.error; SKPaymentTransaction *transaction = download.transaction; NSLog(@"download %@ for product %@ failed with error %@", download.contentIdentifier, transaction.payment.productIdentifier, error.debugDescription); NSDictionary *extras = error ? @{XYStoreNotificationStoreError : error} : nil; [self postNotificationWithName:XYSKDownloadFailed download:download userInfoExtras:extras]; const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; if (!hasPendingDownloads) { [self didFailTransaction:transaction queue:queue error:error]; } } - (void)didFinishDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue { SKPaymentTransaction *transaction = download.transaction; NSLog(@"download %@ for product %@ finished", download.contentIdentifier, transaction.payment.productIdentifier); [self postNotificationWithName:XYSKDownloadFinished download:download userInfoExtras:nil]; const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction]; if (!hasPendingDownloads) { [self finishTransaction:download.transaction queue:queue]; } } - (void)didPauseDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue { NSLog(@"download %@ for product %@ paused", download.contentIdentifier, download.transaction.payment.productIdentifier); [self postNotificationWithName:XYSKDownloadPaused download:download userInfoExtras:nil]; } - (void)didUpdateDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue { NSLog(@"download %@ for product %@ updated", download.contentIdentifier, download.transaction.payment.productIdentifier); NSDictionary *extras = @{XYStoreNotificationDownloadProgress : @(download.progress)}; [self postNotificationWithName:XYSKDownloadUpdated download:download userInfoExtras:extras]; } + (BOOL)hasPendingDownloadsInTransaction:(SKPaymentTransaction*)transaction { for (SKDownload *download in transaction.downloads) { switch (download.downloadState) { case SKDownloadStateActive: case SKDownloadStatePaused: case SKDownloadStateWaiting: return YES; case SKDownloadStateCancelled: case SKDownloadStateFailed: case SKDownloadStateFinished: continue; } } return NO; } #pragma mark Transaction State - (void)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue { NSLog(@"transaction purchased with product %@", transaction.payment.productIdentifier); if (self.receiptVerifier != nil) { [self.receiptVerifier verifyTransaction:transaction success:^{ [self didVerifyTransaction:transaction queue:queue]; } failure:^(NSError *error) { [self didFailTransaction:transaction queue:queue error:error]; }]; } else { NSLog(@"WARNING: no receipt verification"); [self didVerifyTransaction:transaction queue:queue]; } } - (void)didFailTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue error:(NSError*)error { SKPayment *payment = transaction.payment; NSString* productIdentifier = payment.productIdentifier; NSLog(@"transaction failed with product %@ and error %@", productIdentifier, error.debugDescription); if (error.code != XYStoreErrorCodeUnableToCompleteVerification) { // If we were unable to complete the verification we want StoreKit to keep reminding us of the transaction [queue finishTransaction:transaction]; } XYAddPaymentParameters *parameters = [self popAddPaymentParametersForIdentifier:productIdentifier]; if (parameters.failureBlock != nil) { parameters.failureBlock(transaction, error); } NSDictionary *extras = error ? @{XYStoreNotificationStoreError : error} : nil; [self postNotificationWithName:XYSKPaymentTransactionFailed transaction:transaction userInfoExtras:extras]; if (transaction.transactionState == SKPaymentTransactionStateRestored) { [self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction]; } } - (void)didRestoreTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue { NSLog(@"transaction restored with product %@", transaction.originalTransaction.payment.productIdentifier); _pendingRestoredTransactionsCount++; if (self.receiptVerifier != nil) { [self.receiptVerifier verifyTransaction:transaction success:^{ [self didVerifyTransaction:transaction queue:queue]; } failure:^(NSError *error) { [self didFailTransaction:transaction queue:queue error:error]; }]; } else { NSLog(@"WARNING: no receipt verification"); [self didVerifyTransaction:transaction queue:queue]; } } - (void)didDeferTransaction:(SKPaymentTransaction *)transaction { [self postNotificationWithName:XYSKPaymentTransactionDeferred transaction:transaction userInfoExtras:nil]; } - (void)didVerifyTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue { if (self.contentDownloader != nil) { [self.contentDownloader downloadContentForTransaction:transaction success:^{ [self postNotificationWithName:XYSKDownloadFinished transaction:transaction userInfoExtras:nil]; [self didDownloadSelfHostedContentForTransaction:transaction queue:queue]; } progress:^(float progress) { NSDictionary *extras = @{XYStoreNotificationDownloadProgress : @(progress)}; [self postNotificationWithName:XYSKDownloadUpdated transaction:transaction userInfoExtras:extras]; } failure:^(NSError *error) { NSDictionary *extras = error ? @{XYStoreNotificationStoreError : error} : nil; [self postNotificationWithName:XYSKDownloadFailed transaction:transaction userInfoExtras:extras]; [self didFailTransaction:transaction queue:queue error:error]; }]; } else { [self didDownloadSelfHostedContentForTransaction:transaction queue:queue]; } } - (void)didDownloadSelfHostedContentForTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue { NSArray *downloads = [transaction respondsToSelector:@selector(downloads)] ? transaction.downloads : @[]; if (downloads.count > 0) { NSLog(@"starting downloads for product %@ started", transaction.payment.productIdentifier); [queue startDownloads:downloads]; } else { [self finishTransaction:transaction queue:queue]; } } - (void)finishTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue { SKPayment *payment = transaction.payment; NSString* productIdentifier = payment.productIdentifier; [queue finishTransaction:transaction]; [self.transactionPersistor persistTransaction:transaction]; XYAddPaymentParameters *wrapper = [self popAddPaymentParametersForIdentifier:productIdentifier]; if (wrapper.successBlock != nil) { wrapper.successBlock(transaction); } if (transaction.transactionState == SKPaymentTransactionStatePurchased) { [self postNotificationWithName:XYSKPaymentTransactionFinished transaction:transaction userInfoExtras:nil]; } if (transaction.transactionState == SKPaymentTransactionStateRestored) { [self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction]; } } - (void)notifyRestoreTransactionFinishedIfApplicableAfterTransaction:(SKPaymentTransaction*)transaction { if (transaction != nil) { [_restoredTransactions addObject:transaction]; _pendingRestoredTransactionsCount--; } if (_restoredCompletedTransactionsFinished && _pendingRestoredTransactionsCount == 0) { // Wait until all restored transations have been verified NSArray *restoredTransactions = [_restoredTransactions copy]; if (_restoreTransactionsSuccessBlock != nil) { _restoreTransactionsSuccessBlock(restoredTransactions); _restoreTransactionsSuccessBlock = nil; } NSDictionary *userInfo = @{ XYStoreNotificationTransactions : restoredTransactions }; [[NSNotificationCenter defaultCenter] postNotificationName:XYSKRestoreTransactionsFinished object:self userInfo:userInfo]; } } - (XYAddPaymentParameters*)popAddPaymentParametersForIdentifier:(NSString*)identifier { XYAddPaymentParameters *parameters = self.addPaymentParameters[identifier]; [self.addPaymentParameters removeObjectForKey:identifier]; return parameters; } #pragma mark Private - (void)addProduct:(SKProduct*)product { self.products[product.productIdentifier] = product; } - (void)postNotificationWithName:(NSString*)notificationName download:(SKDownload*)download userInfoExtras:(NSDictionary*)extras { NSMutableDictionary *mutableExtras = extras ? [NSMutableDictionary dictionaryWithDictionary:extras] : [NSMutableDictionary dictionary]; mutableExtras[XYStoreNotificationStoreDownload] = download; [self postNotificationWithName:notificationName transaction:download.transaction userInfoExtras:mutableExtras]; } - (void)postNotificationWithName:(NSString*)notificationName transaction:(SKPaymentTransaction*)transaction userInfoExtras:(NSDictionary*)extras { NSString *productIdentifier = transaction.payment.productIdentifier; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[XYStoreNotificationTransaction] = transaction; userInfo[XYStoreNotificationProductIdentifier] = productIdentifier; if (extras) { [userInfo addEntriesFromDictionary:extras]; } [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo]; } - (void)removeProductsRequest:(XYStoreProductService *)request { [self.productsRequestSet removeObject:request]; } #pragma mark - lazy load - (NSMutableSet *)productsRequestSet { if (!_productsRequestSet) { _productsRequestSet = [NSMutableSet set]; } return _productsRequestSet; } - (XYReceiptRefreshService *)receiptService { if (!_receiptService) { _receiptService = [[XYReceiptRefreshService alloc] init]; } return _receiptService; } - (NSMutableDictionary *)addPaymentParameters { if (!_addPaymentParameters) { _addPaymentParameters = [NSMutableDictionary dictionary]; } return _addPaymentParameters; } - (NSMutableDictionary *)products { if (!_products) { _products = [NSMutableDictionary dictionary]; } return _products; } @end ================================================ FILE: XYIAPKit/Core/XYStoreProductService.h ================================================ // // XYStoreProductService.h // Pods // // Created by qichao.ma on 2018/4/21. // #import #import typedef void (^XYSKProductsRequestFailureBlock)(NSError *error); typedef void (^XYSKProductsRequestSuccessBlock)(NSArray *products, NSArray *invalidIdentifiers); @interface XYStoreProductService : NSObject @property (nonatomic, copy) void(^addProductBlock)(SKProduct *product); @property (nonatomic, copy) void(^removeProductRequestBlock)(XYStoreProductService *service); - (void)requestProducts:(NSSet*)identifiers success:(XYSKProductsRequestSuccessBlock)successBlock failure:(XYSKProductsRequestFailureBlock)failureBlock; @end ================================================ FILE: XYIAPKit/Core/XYStoreProductService.m ================================================ // // XYStoreProductService.m // Pods // // Created by qichao.ma on 2018/4/21. // #import "XYStoreProductService.h" #import "XYStore.h" @interface XYStoreProductService() @property (nonatomic, copy) XYSKProductsRequestSuccessBlock successBlock; @property (nonatomic, copy) XYSKProductsRequestFailureBlock failureBlock; @end @implementation XYStoreProductService - (void)requestProducts:(NSSet*)identifiers success:(XYSKProductsRequestSuccessBlock)successBlock failure:(XYSKProductsRequestFailureBlock)failureBlock { _successBlock = successBlock; _failureBlock = failureBlock; SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers]; productsRequest.delegate = self; [productsRequest start]; } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSLog(@"products request received response"); NSArray *products = [NSArray arrayWithArray:response.products]; NSArray *invalidProductIdentifiers = [NSArray arrayWithArray:response.invalidProductIdentifiers]; for (SKProduct *product in products) { NSLog(@"received product with id %@", product.productIdentifier); if (_addProductBlock) { _addProductBlock(product); } } [invalidProductIdentifiers enumerateObjectsUsingBlock:^(NSString *invalid, NSUInteger idx, BOOL *stop) { NSLog(@"invalid product with id %@", invalid); }]; if (self.successBlock) { self.successBlock(products, invalidProductIdentifiers); } } - (void)requestDidFinish:(SKRequest *)request { if (_removeProductRequestBlock) { _removeProductRequestBlock(self); } } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { NSLog(@"products request failed with error %@", error.debugDescription); if (self.failureBlock) { self.failureBlock(error); } if (_removeProductRequestBlock) { _removeProductRequestBlock(self); } } @end ================================================ FILE: XYIAPKit/Core/XYStoreProtocol.h ================================================ // // XYStoreProtocol.h // Pods // // Created by qichao.ma on 2018/4/20. // #ifndef XYStoreProtocol_h #define XYStoreProtocol_h #import @protocol XYStoreContentDownloader - (void)downloadContentForTransaction:(SKPaymentTransaction*)transaction success:(void (^)(void))successBlock progress:(void (^)(float progress))progressBlock failure:(void (^)(NSError *error))failureBlock; @end @protocol XYStoreTransactionPersistor - (void)persistTransaction:(SKPaymentTransaction*)transaction; @end @protocol XYStoreReceiptVerifier /** Verifies the given transaction and calls the given success or failure block accordingly. @param transaction The transaction to be verified. @param successBlock Called if the transaction passed verification. Must be called in the main queu. @param failureBlock Called if the transaction failed verification. If verification could not be completed (e.g., due to connection issues), then error must be of code XYStoreErrorCodeUnableToCompleteVerification to prevent XYStore to finish the transaction. Must be called in the main queu. */ - (void)verifyTransaction:(SKPaymentTransaction*)transaction success:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock; @end @protocol XYStoreObserver @optional /** Tells the observer that a download has been canceled. @discussion Only for Apple-hosted downloads. */ - (void)storeDownloadCanceled:(NSNotification*)notification; /** Tells the observer that a download has failed. Use @c storeError to get the cause. */ - (void)storeDownloadFailed:(NSNotification*)notification; /** Tells the observer that a download has finished. */ - (void)storeDownloadFinished:(NSNotification*)notification __attribute__((availability(ios,introduced=6.0))); /** Tells the observer that a download has been paused. @discussion Only for Apple-hosted downloads. */ - (void)storeDownloadPaused:(NSNotification*)notification; /** Tells the observer that a download has been updated. Use downloadProgress to get the progress. */ - (void)storeDownloadUpdated:(NSNotification*)notification; - (void)storePaymentTransactionDeferred:(NSNotification*)notification; - (void)storePaymentTransactionFailed:(NSNotification*)notification; - (void)storePaymentTransactionFinished:(NSNotification*)notification; /** Tells the observer that request has changed */ - (void)storeProductsRequestFailed:(NSNotification*)notification; - (void)storeProductsRequestFinished:(NSNotification*)notification; /** Tells the observer that receipt has changed */ - (void)storeRefreshReceiptFailed:(NSNotification*)notification; - (void)storeRefreshReceiptFinished:(NSNotification*)notification; /** Tells the observer that transactions has changed */ - (void)storeRestoreTransactionsFailed:(NSNotification*)notification; - (void)storeRestoreTransactionsFinished:(NSNotification*)notification; @end #endif /* XYStoreProtocol_h */ ================================================ FILE: XYIAPKit/Persistence/KeychainPersistence/XYStoreKeychainPersistence.h ================================================ // // XYStoreKeychainPersistence.h // AFNetworking // // Created by qichao.ma on 2018/4/19. // #import #import "XYStore.h" /** Transaction persistence using the keychain. */ @interface XYStoreKeychainPersistence : NSObject + (instancetype)shareInstance; /** Remove all persisted transactions from the keychain. */ - (void)removeTransactions; /** Consume the given product if available. Intended for consumable products. @param productIdentifier Identifier of the product to be consumed. @return YES if the product was consumed, NO otherwise. */ - (BOOL)consumeProductOfIdentifier:(NSString*)productIdentifier; /** Returns the number of persisted transactions for the given product that have not been consumed. Intended for consumable products. @param productIdentifier Identifier of the product to be counted. @return The number of persisted transactions for the given product that have not been consumed. */ - (NSInteger)countProductOfdentifier:(NSString*)productIdentifier; /** Indicates wheter the given product has been purchased. Intended for non-consumables. @param productIdentifier Identifier of the product. @return YES if there is at least one transaction for the given product, NO otherwise. Note that if the product is consumable this method will still return YES even if all transactions have been consumed. */ - (BOOL)isPurchasedProductOfIdentifier:(NSString*)productIdentifier; /** Returns the product identifiers of all products whose transactions have been persisted. */ @property (nonatomic, readonly, copy) NSSet *purchasedProductIdentifiers; @end ================================================ FILE: XYIAPKit/Persistence/KeychainPersistence/XYStoreKeychainPersistence.m ================================================ // // XYStoreKeychainPersistence.m // AFNetworking // // Created by qichao.ma on 2018/4/19. // #import "XYStoreKeychainPersistence.h" #import NSString* const XYStoreTransactionsKeychainKey = @"XYStoreTransactions"; #pragma mark - Keychain NSMutableDictionary* XYKeychainGetSearchDictionary(NSString *key) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; dictionary[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; NSData *encodedIdentifier = [key dataUsingEncoding:NSUTF8StringEncoding]; dictionary[(__bridge id)kSecAttrGeneric] = encodedIdentifier; dictionary[(__bridge id)kSecAttrAccount] = encodedIdentifier; NSString *serviceName = [NSBundle mainBundle].bundleIdentifier; dictionary[(__bridge id)kSecAttrService] = serviceName; return dictionary; } void XYKeychainSetValue(NSData *value, NSString *key) { NSMutableDictionary *searchDictionary = XYKeychainGetSearchDictionary(key); OSStatus status = errSecSuccess; CFTypeRef ignore; if (SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, &ignore) == errSecSuccess) { // Update if (!value) { status = SecItemDelete((__bridge CFDictionaryRef)searchDictionary); } else { NSMutableDictionary *updateDictionary = [NSMutableDictionary dictionary]; updateDictionary[(__bridge id)kSecValueData] = value; status = SecItemUpdate((__bridge CFDictionaryRef)searchDictionary, (__bridge CFDictionaryRef)updateDictionary); } } else if (value) { // Add searchDictionary[(__bridge id)kSecValueData] = value; status = SecItemAdd((__bridge CFDictionaryRef)searchDictionary, NULL); } if (status != errSecSuccess) { NSLog(@"XYStoreKeychainPersistence: failed to set key %@ with error %ld.", key, (long)status); } } NSData* XYKeychainGetValue(NSString *key) { NSMutableDictionary *searchDictionary = XYKeychainGetSearchDictionary(key); searchDictionary[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne; searchDictionary[(__bridge id)kSecReturnData] = (id)kCFBooleanTrue; CFDataRef value = nil; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary, (CFTypeRef *)&value); if (status != errSecSuccess && status != errSecItemNotFound) { NSLog(@"XYStoreKeychainPersistence: failed to get key %@ with error %ld.", key, (long)status); } return (__bridge NSData*)value; } @implementation XYStoreKeychainPersistence { NSDictionary *_transactionsDictionary; } + (instancetype)shareInstance { static XYStoreKeychainPersistence *shareInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shareInstance = [[XYStoreKeychainPersistence alloc] init]; }); return shareInstance; } #pragma mark - XYStoreTransactionPersistor - (void)persistTransaction:(SKPaymentTransaction*)paymentTransaction { SKPayment *payment = paymentTransaction.payment; NSString *productIdentifier = payment.productIdentifier; NSDictionary *transactions = [self transactionsDictionary]; NSInteger count = [transactions[productIdentifier] integerValue]; count++; NSMutableDictionary *updatedTransactions = [NSMutableDictionary dictionaryWithDictionary:transactions]; updatedTransactions[productIdentifier] = @(count); [self setTransactionsDictionary:updatedTransactions]; } #pragma mark - Public - (void)removeTransactions { [self setTransactionsDictionary:nil]; } - (BOOL)consumeProductOfIdentifier:(NSString*)productIdentifier { NSDictionary *transactions = [self transactionsDictionary]; NSInteger count = [transactions[productIdentifier] integerValue]; if (count > 0) { count--; NSMutableDictionary *updatedTransactions = [NSMutableDictionary dictionaryWithDictionary:transactions]; updatedTransactions[productIdentifier] = @(count); [self setTransactionsDictionary:updatedTransactions]; return YES; } else { return NO; } } - (NSInteger)countProductOfdentifier:(NSString*)productIdentifier { NSDictionary *transactions = [self transactionsDictionary]; NSInteger count = [transactions[productIdentifier] integerValue]; return count; } - (BOOL)isPurchasedProductOfIdentifier:(NSString*)productIdentifier { NSDictionary *transactions = [self transactionsDictionary]; return transactions[productIdentifier] != nil; } - (NSSet*)purchasedProductIdentifiers { NSDictionary *transactions = [self transactionsDictionary]; NSArray *productIdentifiers = transactions.allKeys; return [NSSet setWithArray:productIdentifiers]; } #pragma mark - Private - (NSDictionary*)transactionsDictionary { if (!_transactionsDictionary) { // Reading the keychain is slow so we cache its values in memory NSData *data = XYKeychainGetValue(XYStoreTransactionsKeychainKey); NSDictionary *transactions = @{}; if (data) { NSError *error; transactions = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (!transactions) { NSLog(@"XYStoreKeychainPersistence: failed to read JSON data with error %@", error); } } _transactionsDictionary = transactions; } return _transactionsDictionary; } - (void)setTransactionsDictionary:(NSDictionary*)dictionary { _transactionsDictionary = dictionary; NSData *data = nil; if (dictionary) { NSError *error; data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error]; if (!data) { NSLog(@"XYStoreKeychainPersistence: failed to write JSON data with error %@", error); } } XYKeychainSetValue(data, XYStoreTransactionsKeychainKey); } @end ================================================ FILE: XYIAPKit/Persistence/UserDefaultPersistence/XYStoreTransaction.h ================================================ // // XYStoreTransaction.h // Pods // // Created by qichao.ma on 2018/4/19. // #import #import @interface XYStoreTransaction : NSObject @property(nonatomic, assign) BOOL consumed; @property(nonatomic, copy) NSString *productIdentifier; @property(nonatomic, copy) NSDate *transactionDate; @property(nonatomic, copy) NSString *transactionIdentifier; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 @property(nonatomic, strong) NSData *transactionReceipt; #endif - (instancetype)initWithPaymentTransaction:(SKPaymentTransaction*)paymentTransaction; @end ================================================ FILE: XYIAPKit/Persistence/UserDefaultPersistence/XYStoreTransaction.m ================================================ // // XYStoreTransaction.m // Pods // // Created by qichao.ma on 2018/4/19. // #import "XYStoreTransaction.h" NSString* const XYStoreCoderConsumedKey = @"consumed"; NSString* const XYStoreCoderProductIdentifierKey = @"productIdentifier"; NSString* const XYStoreCoderTransactionDateKey = @"transactionDate"; NSString* const XYStoreCoderTransactionIdentifierKey = @"transactionIdentifier"; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 NSString* const XYStoreCoderTransactionReceiptKey = @"transactionReceipt"; #endif @implementation XYStoreTransaction - (instancetype)initWithPaymentTransaction:(SKPaymentTransaction*)paymentTransaction { if (self = [super init]) { _productIdentifier = paymentTransaction.payment.productIdentifier; _transactionDate = paymentTransaction.transactionDate; _transactionIdentifier = paymentTransaction.transactionIdentifier; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 _transactionReceipt = paymentTransaction.transactionReceipt; #endif } return self; } - (instancetype)initWithCoder:(NSCoder *)decoder { if (self = [super init]) { _consumed = [decoder decodeBoolForKey:XYStoreCoderConsumedKey]; _productIdentifier = [decoder decodeObjectForKey:XYStoreCoderProductIdentifierKey]; _transactionDate = [decoder decodeObjectForKey:XYStoreCoderTransactionDateKey]; _transactionIdentifier = [decoder decodeObjectForKey:XYStoreCoderTransactionIdentifierKey]; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 _transactionReceipt = [decoder decodeObjectForKey:XYStoreCoderTransactionReceiptKey]; #endif } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeBool:self.consumed forKey:XYStoreCoderConsumedKey]; [coder encodeObject:self.productIdentifier forKey:XYStoreCoderProductIdentifierKey]; [coder encodeObject:self.transactionDate forKey:XYStoreCoderTransactionDateKey]; if (self.transactionIdentifier != nil) { [coder encodeObject:self.transactionIdentifier forKey:XYStoreCoderTransactionIdentifierKey]; } #if __IPHONE_OS_VERSION_MIN_REQUIRED < 70000 if (self.transactionReceipt != nil) { [coder encodeObject:self.transactionReceipt forKey:XYStoreCoderTransactionReceiptKey]; } #endif } @end ================================================ FILE: XYIAPKit/Persistence/UserDefaultPersistence/XYStoreUserDefaultsPersistence.h ================================================ // // XYStoreUserDefaultsPersistence.h // Pods // // Created by qichao.ma on 2018/4/19. // #import #import "XYStore.h" @class XYStoreTransaction; @interface XYStoreUserDefaultsPersistence : NSObject + (instancetype)shareInstance; /** Remove all transactions from user defaults. */ - (void)removeTransactions; /** Consume the given product if available. Intended for consumable products. @param productIdentifier Identifier of the product to be consumed. @return YES if the product was consumed, NO otherwise. */ - (BOOL)consumeProductOfIdentifier:(NSString*)productIdentifier; /** Returns the number of transactions for the given product that have not been consumed. Intended for consumable products. @return The number of transactions for the given product that have not been consumed. */ - (NSInteger)countProductOfdentifier:(NSString*)productIdentifier; /** Indicates wheter the given product has been purchased. Intended for non-consumables. @param productIdentifier Identifier of the product. @return YES if there is at least one transaction for the given product, NO otherwise. Note that if the product is consumable this method will still return YES even if all transactions have been consumed. */ - (BOOL)isPurchasedProductOfIdentifier:(NSString*)productIdentifier; /** Returns the product identifiers of all products that have a transaction. */ - (NSSet*)purchasedProductIdentifiers; /** Returns all the transactions for the given product. @param productIdentifier Identifier of the product whose transactions will be returned. @return An array of XYStoreTransaction objects (not SKPaymentTransaction) for the given product. @see XYStoreTransaction */ - (NSArray*)transactionsForProductOfIdentifier:(NSString*)productIdentifier; @end /** Subclasess should override these methods to use their own obfuscation. */ @interface XYStoreUserDefaultsPersistence(Obfuscation) /** Returns a data representation of the given transaction. The default implementation uses NSKeyedArchiver. @param transaction Transaction to be converted into data @return Data representation of the given transaction */ - (NSData*)dataWithTransaction:(XYStoreTransaction*)transaction; /** Returns a transaction from the given data. The default implementation uses NSKeyedUnarchiver. @param data Data from which a transaction will be obtained @return Transaction from the given data */ - (XYStoreTransaction*)transactionWithData:(NSData*)data; @end ================================================ FILE: XYIAPKit/Persistence/UserDefaultPersistence/XYStoreUserDefaultsPersistence.m ================================================ // // XYStoreUserDefaultsPersistence.m // Pods // // Created by qichao.ma on 2018/4/19. // #import "XYStoreUserDefaultsPersistence.h" #import "XYStoreTransaction.h" NSString* const XYStoreTransactionsUserDefaultsKey = @"XYStoreTransactions"; @implementation XYStoreUserDefaultsPersistence + (instancetype)shareInstance { static XYStoreUserDefaultsPersistence *shareInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shareInstance = [[XYStoreUserDefaultsPersistence alloc] init]; }); return shareInstance; } #pragma mark - XYStoreTransactionPersistor - (void)persistTransaction:(SKPaymentTransaction*)paymentTransaction { NSUserDefaults *defaults = [self userDefaults]; NSDictionary *purchases = [defaults objectForKey:XYStoreTransactionsUserDefaultsKey] ? : @{}; SKPayment *payment = paymentTransaction.payment; NSString *productIdentifier = payment.productIdentifier; NSArray *transactions = purchases[productIdentifier] ? : @[]; NSMutableArray *updatedTransactions = [NSMutableArray arrayWithArray:transactions]; XYStoreTransaction *transaction = [[XYStoreTransaction alloc] initWithPaymentTransaction:paymentTransaction]; NSData *data = [self dataWithTransaction:transaction]; [updatedTransactions addObject:data]; [self setTransactions:updatedTransactions forProductIdentifier:productIdentifier]; } #pragma mark - Public - (void)removeTransactions { NSUserDefaults *defaults = [self userDefaults]; [defaults removeObjectForKey:XYStoreTransactionsUserDefaultsKey]; [defaults synchronize]; } - (BOOL)consumeProductOfIdentifier:(NSString*)productIdentifier { NSUserDefaults *defaults = [self userDefaults]; NSDictionary *purchases = [defaults objectForKey:XYStoreTransactionsUserDefaultsKey] ? : @{}; NSArray *transactions = purchases[productIdentifier] ? : @[]; for (NSData *data in transactions) { XYStoreTransaction *transaction = [self transactionWithData:data]; if (!transaction.consumed) { transaction.consumed = YES; NSData *updatedData = [self dataWithTransaction:transaction]; NSMutableArray *updatedTransactions = [NSMutableArray arrayWithArray:transactions]; NSInteger index = [updatedTransactions indexOfObject:data]; updatedTransactions[index] = updatedData; [self setTransactions:updatedTransactions forProductIdentifier:productIdentifier]; return YES; } } return NO; } - (NSInteger)countProductOfdentifier:(NSString*)productIdentifier { NSArray *transactions = [self transactionsForProductOfIdentifier:productIdentifier]; NSInteger count = 0; for (XYStoreTransaction *transaction in transactions) { if (!transaction.consumed) { count++; } } return count; } - (BOOL)isPurchasedProductOfIdentifier:(NSString*)productIdentifier { NSArray *transactions = [self transactionsForProductOfIdentifier:productIdentifier]; return transactions.count > 0; } - (NSSet*)purchasedProductIdentifiers { NSUserDefaults *defaults = [self userDefaults]; NSDictionary *purchases = [defaults objectForKey:XYStoreTransactionsUserDefaultsKey]; NSSet *productIdentifiers = [NSSet setWithArray:purchases.allKeys]; return productIdentifiers; } - (NSArray*)transactionsForProductOfIdentifier:(NSString*)productIdentifier { NSUserDefaults *defaults = [self userDefaults]; NSDictionary *purchases = [defaults objectForKey:XYStoreTransactionsUserDefaultsKey]; NSArray *obfuscatedTransactions = purchases[productIdentifier] ? : @[]; NSMutableArray *transactions = [NSMutableArray arrayWithCapacity:obfuscatedTransactions.count]; for (NSData *data in obfuscatedTransactions) { XYStoreTransaction *transaction = [self transactionWithData:data]; [transactions addObject:transaction]; } return transactions; } - (NSUserDefaults *)userDefaults { return [NSUserDefaults standardUserDefaults]; } #pragma mark - Obfuscation - (NSData*)dataWithTransaction:(XYStoreTransaction*)transaction { NSMutableData *data = [[NSMutableData alloc] init]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [archiver encodeObject:transaction]; [archiver finishEncoding]; return data; } - (XYStoreTransaction*)transactionWithData:(NSData*)data { NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; XYStoreTransaction *transaction = [unarchiver decodeObject]; [unarchiver finishDecoding]; return transaction; } #pragma mark - Private - (void)setTransactions:(NSArray*)transactions forProductIdentifier:(NSString*)productIdentifier { NSUserDefaults *defaults = [self userDefaults]; NSDictionary *purchases = [defaults objectForKey:XYStoreTransactionsUserDefaultsKey] ? : @{}; NSMutableDictionary *updatedPurchases = [NSMutableDictionary dictionaryWithDictionary:purchases]; updatedPurchases[productIdentifier] = transactions; [defaults setObject:updatedPurchases forKey:XYStoreTransactionsUserDefaultsKey]; [defaults synchronize]; } @end ================================================ FILE: XYIAPKit/Receipt/LocalReceiptVerify/XYAppReceipt.h ================================================ // // XYAppReceipt.h // Pods // // Created by qichao.ma on 2018/4/19. // #import /** Represents the app receipt. */ @interface XYAppReceipt : NSObject /** The app’s bundle identifier. This corresponds to the value of CFBundleIdentifier in the Info.plist file. */ @property (nonatomic, strong, readonly) NSString *bundleIdentifier; /** The bundle identifier as data, as contained in the receipt. Used to verifiy the receipt's hash. @see verifyReceiptHash */ @property (nonatomic, strong, readonly) NSData *bundleIdentifierData; /** The app’s version number. This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist. */ @property (nonatomic, strong, readonly) NSString *appVersion; /** An opaque value used as part of the SHA-1 hash. */ @property (nonatomic, strong, readonly) NSData *opaqueValue; /** A SHA-1 hash, used to validate the receipt. */ @property (nonatomic, strong, readonly) NSData *receiptHash; /** Array of in-app purchases contained in the receipt. @see RMAppReceiptIAP */ @property (nonatomic, strong, readonly) NSArray *inAppPurchases; /** The version of the app that was originally purchased. This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist file when the purchase was originally made. In the sandbox environment, the value of this field is always “1.0”. */ @property (nonatomic, strong, readonly) NSString *originalAppVersion; /** The date that the app receipt expires. Only for apps purchased through the Volume Purchase Program. If nil, the receipt does not expire. When validating a receipt, compare this date to the current date to determine whether the receipt is expired. Do not try to use this date to calculate any other information, such as the time remaining before expiration. */ @property (nonatomic, strong, readonly) NSDate *expirationDate; /** Returns an initialized app receipt from the given data. @param asn1Data ASN1 data @return An initialized app receipt from the given data. */ - (instancetype)initWithASN1Data:(NSData*)asn1Data NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; /** Returns whether there is an in-app purchase in the receipt for the given product. @param productIdentifier The identifier of the product. @return YES if there is an in-app purchase for the given product, NO otherwise. */ - (BOOL)containsInAppPurchaseOfProductIdentifier:(NSString*)productIdentifier; /** Returns whether the receipt contains an active auto-renewable subscription for the given product identifier and for the given date. @param productIdentifier The identifier of the auto-renewable subscription. @param date The date in which the latest auto-renewable subscription should be active. If you are using the current date, you might not want to take it from the device in case the user has changed it. @return YES if the latest auto-renewable subscription is active for the given date, NO otherwise. @warning Auto-renewable subscription lapses are possible. If you are checking against the current date, you might want to deduct some time as tolerance. @warning If this method fails Apple recommends to refresh the receipt and try again once. */ - (BOOL)containsActiveAutoRenewableSubscriptionOfProductIdentifier:(NSString *)productIdentifier forDate:(NSDate *)date; /** Returns wheter the receipt hash corresponds to the device's GUID by calcuting the expected hash using the GUID, bundleIdentifierData and opaqueValue. @return YES if the hash contained in the receipt corresponds to the device's GUID, NO otherwise. */ - (BOOL)verifyReceiptHash; /** Returns the app receipt contained in the bundle, if any and valid. Extracts the receipt in ASN1 from the PKCS #7 container, and then parses the ASN1 data into a RMAppReceipt instance. If an Apple Root certificate is available, it will also verify that the signature of the receipt is valid. @return The app receipt contained in the bundle, or nil if there is no receipt or if it is invalid. @see refreshReceipt @see setAppleRootCertificateURL: */ + (XYAppReceipt *)bundleReceipt; /** Sets the url of the Apple Root certificate that will be used to verifiy the signature of the bundle receipt. If none is provided, the resource AppleIncRootCertificate.cer will be used. If no certificate is available, no signature verification will be performed. @param url The url of the Apple Root certificate. */ + (void)setAppleRootCertificateURL:(NSURL*)url; + (void)enumerateASN1Attributes:(const uint8_t*)p length:(long)tlength usingBlock:(void (^)(NSData *data, int type))block; @end /** Represents an in-app purchase in the app receipt. */ @interface XYAppReceiptIAP : NSObject /** The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property. */ @property (nonatomic, readonly) NSInteger quantity; /** The product identifier of the item that was purchased. This value corresponds to the productIdentifier property of the SKPayment object stored in the transaction’s payment property. */ @property (nonatomic, strong, readonly) NSString *productIdentifier; /** The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property. */ @property (nonatomic, strong, readonly) NSString *transactionIdentifier; /** For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier. This value corresponds to the original transaction’s transactionIdentifier property. All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field. */ @property (nonatomic, strong, readonly) NSString *originalTransactionIdentifier; /** The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property. For a transaction that restores a previous transaction, the purchase date is the date of the restoration. Use `originalPurchaseDate` to get the date of the original transaction. In an auto-renewable subscription receipt, this is always the date when the subscription was purchased or renewed, regardles of whether the transaction has been restored */ @property (nonatomic, strong, readonly) NSDate *purchaseDate; /** For a transaction that restores a previous transaction, the date of the original transaction. This value corresponds to the original transaction’s transactionDate property. In an auto-renewable subscription receipt, this indicates the beginning of the subscription period, even if the subscription has been renewed. */ @property (nonatomic, strong, readonly) NSDate *originalPurchaseDate; /** The expiration date for the subscription. Only present for auto-renewable subscription receipts. */ @property (nonatomic, strong, readonly) NSDate *subscriptionExpirationDate; /** For a transaction that was canceled by Apple customer support, the date of the cancellation. */ @property (nonatomic, strong, readonly) NSDate *cancellationDate; /** The primary key for identifying subscription purchases. */ @property (nonatomic, readonly) NSInteger webOrderLineItemID; /** Returns an initialized in-app purchase from the given data. @param asn1Data ASN1 data @return An initialized in-app purchase from the given data. */ - (instancetype)initWithASN1Data:(NSData*)asn1Data NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; /** Returns whether the auto renewable subscription is active for the given date. @param date The date in which the auto-renewable subscription should be active. If you are using the current date, you might not want to take it from the device in case the user has changed it. @return YES if the auto-renewable subscription is active for the given date, NO otherwise. @warning Auto-renewable subscription lapses are possible. If you are checking against the current date, you might want to deduct some time as tolerance. @warning If this method fails Apple recommends to refresh the receipt and try again once. */ - (BOOL)isActiveAutoRenewableSubscriptionForDate:(NSDate*)date; @end ================================================ FILE: XYIAPKit/Receipt/LocalReceiptVerify/XYAppReceipt.m ================================================ // // XYAppReceipt.m // Pods // // Created by qichao.ma on 2018/4/19. // #import "XYAppReceipt.h" #import #import #import #import #import // From https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW1 NSInteger const XYAppReceiptASN1TypeBundleIdentifier = 2; NSInteger const XYAppReceiptASN1TypeAppVersion = 3; NSInteger const XYAppReceiptASN1TypeOpaqueValue = 4; NSInteger const XYAppReceiptASN1TypeHash = 5; NSInteger const XYAppReceiptASN1TypeInAppPurchaseReceipt = 17; NSInteger const XYAppReceiptASN1TypeOriginalAppVersion = 19; NSInteger const XYAppReceiptASN1TypeExpirationDate = 21; NSInteger const XYAppReceiptASN1TypeQuantity = 1701; NSInteger const XYAppReceiptASN1TypeProductIdentifier = 1702; NSInteger const XYAppReceiptASN1TypeTransactionIdentifier = 1703; NSInteger const XYAppReceiptASN1TypePurchaseDate = 1704; NSInteger const XYAppReceiptASN1TypeOriginalTransactionIdentifier = 1705; NSInteger const XYAppReceiptASN1TypeOriginalPurchaseDate = 1706; NSInteger const XYAppReceiptASN1TypeSubscriptionExpirationDate = 1708; NSInteger const XYAppReceiptASN1TypeWebOrderLineItemID = 1711; NSInteger const XYAppReceiptASN1TypeCancellationDate = 1712; #pragma mark - ANS1 static int XYASN1ReadInteger(const uint8_t **pp, long omax) { int tag, asn1Class; long length; int value = 0; ASN1_get_object(pp, &length, &tag, &asn1Class, omax); if (tag == V_ASN1_INTEGER) { for (int i = 0; i < length; i++) { value = value * 0x100 + (*pp)[i]; } } *pp += length; return value; } static NSData* XYASN1ReadOctectString(const uint8_t **pp, long omax) { int tag, asn1Class; long length; NSData *data = nil; ASN1_get_object(pp, &length, &tag, &asn1Class, omax); if (tag == V_ASN1_OCTET_STRING) { data = [NSData dataWithBytes:*pp length:length]; } *pp += length; return data; } static NSString* XYASN1ReadString(const uint8_t **pp, long omax, int expectedTag, NSStringEncoding encoding) { int tag, asn1Class; long length; NSString *value = nil; ASN1_get_object(pp, &length, &tag, &asn1Class, omax); if (tag == expectedTag) { value = [[NSString alloc] initWithBytes:*pp length:length encoding:encoding]; } *pp += length; return value; } static NSString* XYASN1ReadUTF8String(const uint8_t **pp, long omax) { return XYASN1ReadString(pp, omax, V_ASN1_UTF8STRING, NSUTF8StringEncoding); } static NSString* XYASN1ReadIA5SString(const uint8_t **pp, long omax) { return XYASN1ReadString(pp, omax, V_ASN1_IA5STRING, NSASCIIStringEncoding); } static NSURL *_appleRootCertificateURL = nil; @implementation XYAppReceipt - (instancetype)initWithASN1Data:(NSData*)asn1Data { if (self = [super init]) { NSMutableArray *purchases = [NSMutableArray array]; // Explicit casting to avoid errors when compiling as Objective-C++ [XYAppReceipt enumerateASN1Attributes:(const uint8_t*)asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) { const uint8_t *s = (const uint8_t*)data.bytes; const NSUInteger length = data.length; switch (type) { case XYAppReceiptASN1TypeBundleIdentifier: _bundleIdentifierData = data; _bundleIdentifier = XYASN1ReadUTF8String(&s, length); break; case XYAppReceiptASN1TypeAppVersion: _appVersion = XYASN1ReadUTF8String(&s, length); break; case XYAppReceiptASN1TypeOpaqueValue: _opaqueValue = data; break; case XYAppReceiptASN1TypeHash: _receiptHash = data; break; case XYAppReceiptASN1TypeInAppPurchaseReceipt: { XYAppReceiptIAP *purchase = [[XYAppReceiptIAP alloc] initWithASN1Data:data]; [purchases addObject:purchase]; break; } case XYAppReceiptASN1TypeOriginalAppVersion: _originalAppVersion = XYASN1ReadUTF8String(&s, length); break; case XYAppReceiptASN1TypeExpirationDate: { NSString *string = XYASN1ReadIA5SString(&s, length); _expirationDate = [XYAppReceipt formatRFC3339String:string]; break; } } }]; _inAppPurchases = purchases; } return self; } - (BOOL)containsInAppPurchaseOfProductIdentifier:(NSString*)productIdentifier { for (XYAppReceiptIAP *purchase in _inAppPurchases) { if ([purchase.productIdentifier isEqualToString:productIdentifier]) { return YES; } } return NO; } -(BOOL)containsActiveAutoRenewableSubscriptionOfProductIdentifier:(NSString *)productIdentifier forDate:(NSDate *)date { XYAppReceiptIAP *lastTransaction = nil; for (XYAppReceiptIAP *iap in self.inAppPurchases) { if (![iap.productIdentifier isEqualToString:productIdentifier]) continue; if (!lastTransaction || [iap.subscriptionExpirationDate compare:lastTransaction.subscriptionExpirationDate] == NSOrderedDescending) { lastTransaction = iap; } } return [lastTransaction isActiveAutoRenewableSubscriptionForDate:date]; } - (BOOL)verifyReceiptHash { // TODO: Getting the uuid in Mac is different. See: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5 NSUUID *uuid = [UIDevice currentDevice].identifierForVendor; unsigned char uuidBytes[16]; [uuid getUUIDBytes:uuidBytes]; // Order taken from: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5 NSMutableData *data = [NSMutableData data]; [data appendBytes:uuidBytes length:sizeof(uuidBytes)]; [data appendData:self.opaqueValue]; [data appendData:self.bundleIdentifierData]; NSMutableData *expectedHash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH]; SHA1((const uint8_t*)data.bytes, data.length, (uint8_t*)expectedHash.mutableBytes); // Explicit casting to avoid errors when compiling as Objective-C++ return [expectedHash isEqualToData:self.receiptHash]; } + (XYAppReceipt*)bundleReceipt { NSURL *URL = [NSBundle mainBundle].appStoreReceiptURL; NSString *path = URL.path; const BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:nil]; if (!exists) return nil; NSData *data = [XYAppReceipt dataFromPCKS7Path:path]; if (!data) return nil; XYAppReceipt *receipt = [[XYAppReceipt alloc] initWithASN1Data:data]; return receipt; } + (void)setAppleRootCertificateURL:(NSURL*)url { _appleRootCertificateURL = url; } #pragma mark - Utils + (NSData*)dataFromPCKS7Path:(NSString*)path { const char *cpath = path.stringByStandardizingPath.fileSystemRepresentation; FILE *fp = fopen(cpath, "rb"); if (!fp) return nil; PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL); fclose(fp); if (!p7) return nil; NSBundle *bundle = [NSBundle bundleWithURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"XYIAPKit" withExtension:@"bundle"]]; NSData *data; NSURL *certificateURL = _appleRootCertificateURL ? : [bundle URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"]; NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL]; if (!certificateData || [self verifyPCKS7:p7 withCertificateData:certificateData]) { struct pkcs7_st *contents = p7->d.sign->contents; if (PKCS7_type_is_data(contents)) { ASN1_OCTET_STRING *octets = contents->d.data; data = [NSData dataWithBytes:octets->data length:octets->length]; } } PKCS7_free(p7); return data; } + (BOOL)verifyPCKS7:(PKCS7*)container withCertificateData:(NSData*)certificateData { // Based on: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17 static int verified = 1; int result = 0; OpenSSL_add_all_digests(); // Required for PKCS7_verify to work X509_STORE *store = X509_STORE_new(); if (store) { const uint8_t *certificateBytes = (uint8_t *)(certificateData.bytes); X509 *certificate = d2i_X509(NULL, &certificateBytes, (long)certificateData.length); if (certificate) { X509_STORE_add_cert(store, certificate); BIO *payload = BIO_new(BIO_s_mem()); result = PKCS7_verify(container, NULL, store, NULL, payload, 0); BIO_free(payload); X509_free(certificate); } } X509_STORE_free(store); EVP_cleanup(); // Balances OpenSSL_add_all_digests (), perhttp://www.openssl.org/docs/crypto/OpenSSL_add_all_algorithms.html return result == verified; } /* Based on https://github.com/rmaddy/VerifyStoreReceiptiOS */ + (void)enumerateASN1Attributes:(const uint8_t*)p length:(long)tlength usingBlock:(void (^)(NSData *data, int type))block { int type, tag; long length; const uint8_t *end = p + tlength; ASN1_get_object(&p, &length, &type, &tag, end - p); if (type != V_ASN1_SET) return; while (p < end) { ASN1_get_object(&p, &length, &type, &tag, end - p); if (type != V_ASN1_SEQUENCE) break; const uint8_t *sequenceEnd = p + length; const int attributeType = XYASN1ReadInteger(&p, sequenceEnd - p); XYASN1ReadInteger(&p, sequenceEnd - p); // Consume attribute version NSData *data = XYASN1ReadOctectString(&p, sequenceEnd - p); if (data) { block(data, attributeType); } while (p < sequenceEnd) { // Skip remaining fields ASN1_get_object(&p, &length, &type, &tag, sequenceEnd - p); p += length; } } } + (NSDate*)formatRFC3339String:(NSString*)string { static NSDateFormatter *formatter; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ formatter = [[NSDateFormatter alloc] init]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; }); NSDate *date = [formatter dateFromString:string]; return date; } @end @implementation XYAppReceiptIAP - (instancetype)initWithASN1Data:(NSData*)asn1Data { if (self = [super init]) { // Explicit casting to avoid errors when compiling as Objective-C++ [XYAppReceipt enumerateASN1Attributes:(const uint8_t*)asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) { const uint8_t *p = (const uint8_t*)data.bytes; const NSUInteger length = data.length; switch (type) { case XYAppReceiptASN1TypeQuantity: _quantity = XYASN1ReadInteger(&p, length); break; case XYAppReceiptASN1TypeProductIdentifier: _productIdentifier = XYASN1ReadUTF8String(&p, length); break; case XYAppReceiptASN1TypeTransactionIdentifier: _transactionIdentifier = XYASN1ReadUTF8String(&p, length); break; case XYAppReceiptASN1TypePurchaseDate: { NSString *string = XYASN1ReadIA5SString(&p, length); _purchaseDate = [XYAppReceipt formatRFC3339String:string]; break; } case XYAppReceiptASN1TypeOriginalTransactionIdentifier: _originalTransactionIdentifier = XYASN1ReadUTF8String(&p, length); break; case XYAppReceiptASN1TypeOriginalPurchaseDate: { NSString *string = XYASN1ReadIA5SString(&p, length); _originalPurchaseDate = [XYAppReceipt formatRFC3339String:string]; break; } case XYAppReceiptASN1TypeSubscriptionExpirationDate: { NSString *string = XYASN1ReadIA5SString(&p, length); _subscriptionExpirationDate = [XYAppReceipt formatRFC3339String:string]; break; } case XYAppReceiptASN1TypeWebOrderLineItemID: _webOrderLineItemID = XYASN1ReadInteger(&p, length); break; case XYAppReceiptASN1TypeCancellationDate: { NSString *string = XYASN1ReadIA5SString(&p, length); _cancellationDate = [XYAppReceipt formatRFC3339String:string]; break; } } }]; } return self; } - (BOOL)isActiveAutoRenewableSubscriptionForDate:(NSDate*)date { NSAssert(self.subscriptionExpirationDate != nil, @"The product %@ is not an auto-renewable subscription.", self.productIdentifier); if (self.cancellationDate) return NO; return [self.purchaseDate compare:date] != NSOrderedDescending && [date compare:self.subscriptionExpirationDate] != NSOrderedDescending; } @end ================================================ FILE: XYIAPKit/Receipt/LocalReceiptVerify/XYStoreAppReceiptVerifier.h ================================================ // // XYStoreAppReceiptVerifier.h // Pods // // Created by qichao.ma on 2018/4/19. // #import #import "XYStore.h" /** 票据校验 */ @interface XYStoreAppReceiptVerifier : NSObject @property (nonatomic, strong) NSString *bundleIdentifier; /** The value that will be used to validate the bundle version included in the app receipt. Given that it is possible to modify the app bundle in jailbroken devices, setting this value from a hardcoded string might provide better protection. @return The given value, or the app's bundle version by defult. */ @property (nonatomic, strong) NSString *bundleVersion; /** Verifies the app receipt by checking the integrity of the receipt, comparing its bundle identifier and bundle version to the values returned by the corresponding properties and verifying the receipt hash. @return YES if the receipt is verified, NO otherwise. @discussion If validation fails in iOS, Apple recommends to refresh the receipt and try again. */ - (BOOL)verifyAppReceipt; @end ================================================ FILE: XYIAPKit/Receipt/LocalReceiptVerify/XYStoreAppReceiptVerifier.m ================================================ // // XYStoreAppReceiptVerifier.m // Pods // // Created by qichao.ma on 2018/4/19. // #import "XYStoreAppReceiptVerifier.h" #import "XYAppReceipt.h" @implementation XYStoreAppReceiptVerifier - (void)verifyTransaction:(SKPaymentTransaction*)transaction success:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock { XYAppReceipt *receipt = [XYAppReceipt bundleReceipt]; const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // nil,为了下面的再次验证 if (verified) return; // 刷新票据,再次认证 [[XYStore defaultStore] refreshReceiptOnSuccess:^{ XYAppReceipt *receipt = [XYAppReceipt bundleReceipt]; [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:failureBlock]; } failure:^(NSError *error) { [self failWithBlock:failureBlock error:error]; }]; } - (BOOL)verifyAppReceipt { XYAppReceipt *receipt = [XYAppReceipt bundleReceipt]; return [self verifyAppReceipt:receipt]; } #pragma mark - Properties - (NSString*)bundleIdentifier { if (!_bundleIdentifier) { return [NSBundle mainBundle].bundleIdentifier; } return _bundleIdentifier; } - (NSString*)bundleVersion { if (!_bundleVersion) { #if TARGET_OS_IPHONE return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; #else return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; #endif } return _bundleVersion; } #pragma mark - Private - (BOOL)verifyAppReceipt:(XYAppReceipt*)receipt { if (!receipt) return NO; if (![receipt.bundleIdentifier isEqualToString:self.bundleIdentifier]) return NO; if (![receipt.appVersion isEqualToString:self.bundleVersion]) return NO; if (![receipt verifyReceiptHash]) return NO; return YES; } - (BOOL)verifyTransaction:(SKPaymentTransaction*)transaction inReceipt:(XYAppReceipt*)receipt success:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock { const BOOL receiptVerified = [self verifyAppReceipt:receipt]; if (!receiptVerified) { [self failWithBlock:failureBlock message:@"The app receipt failed verification"]; return NO; } SKPayment *payment = transaction.payment; const BOOL transactionVerified = [receipt containsInAppPurchaseOfProductIdentifier:payment.productIdentifier]; if (!transactionVerified) { [self failWithBlock:failureBlock message:@"The app receipt does not contain the given product"]; return NO; } if (successBlock) { successBlock(); } return YES; } - (void)failWithBlock:(void (^)(NSError *error))failureBlock message:(NSString*)message { NSError *error = [NSError errorWithDomain:XYStoreErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : message}]; [self failWithBlock:failureBlock error:error]; } - (void)failWithBlock:(void (^)(NSError *error))failureBlock error:(NSError*)error { if (failureBlock) { failureBlock(error); } } @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/NSDate+XYStoreExtension.h ================================================ // // NSDate+XYStoreExtension.h // Pods // // Created by qichao.ma on 2018/5/3. // #import @interface NSDate (XYStoreExtension) /** 时区时间转NSDate @param dateString "2018-03-07 06:07:36 Etc/GMT" "2018-03-23 08:30:22 America/Los_Angeles" */ + (NSDate *)dateWithZoneDateString:(NSString *)dateString; /** @return dateString "2018-03-07 06:07:36 Etc/GMT" */ + (NSString *)GMTdateString:(NSDate *)date; /** 毫秒时间戳 @param timestamp 毫秒时间戳 */ + (NSDate *)dateWithMSTimestamp:(NSInteger)timestamp; @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/NSDate+XYStoreExtension.m ================================================ // // NSDate+XYStoreExtension.m // Pods // // Created by qichao.ma on 2018/5/3. // #import "NSDate+XYStoreExtension.h" @implementation NSDate (XYStoreExtension) + (NSDate *)dateWithZoneDateString:(NSString *)dateString { NSArray *array = [dateString componentsSeparatedByString:@" "]; if (array.count != 3) { return nil; } NSString *zoneName = array.lastObject; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; formatter.timeZone = [NSTimeZone timeZoneWithName:zoneName]; NSDate *date = [formatter dateFromString:[NSString stringWithFormat:@"%@ %@", array[0], array[1]]]; return date; } + (NSString *)GMTdateString:(NSDate *)date { if (!date) { return nil; } NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"Etc/GMT"];//东八区时间 NSString *dateStr = [formatter stringFromDate:date]; return [NSString stringWithFormat:@"%@ %@", dateStr, @"Etc/GMT"]; } + (NSDate *)dateWithMSTimestamp:(NSInteger)timestamp { NSDate *date = [NSDate dateWithTimeIntervalSince1970:(NSTimeInterval)(timestamp / 1000)]; return date; } @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYInAppReceipt.h ================================================ // // XYInAppReceipt.h // Pods // // Created by qichao.ma on 2018/5/3. // #import /** 具体苹果返回字段见最下方 */ @interface XYInAppReceipt : NSObject /** The default value is 1, the minimum value is 1, and the maximum value is 10. */ @property (nonatomic, assign) NSInteger quantity; @property (nonatomic, copy) NSString *product_id; @property (nonatomic, copy) NSString *transaction_id; @property (nonatomic, copy) NSString *original_transaction_id; @property (nonatomic, strong) NSDate *purchase_date; @property (nonatomic, strong) NSDate *original_purchase_date; /** 仅用于,自动续费订阅 true,表示处于 免费试用 时期 如果已有票据中含有is_trial_period或者is_in_intro_offer_period为true,用户不再具备有此项资格 For a subscription, whether or not it is in the free trial period. This key is only present for auto-renewable subscription receipts. The value for this key is "true" if the customer’s subscription is currently in the free trial period, or "false" if not. Note: If a previous subscription period in the receipt has the value “true” for either the is_trial_period or the is_in_intro_offer_period key, the user is not eligible for a free trial or introductory price within that subscription group. */ @property (nonatomic, assign) BOOL is_trial_period; //**********************以上为四种内购类型公共字段,下面字段为自动续期订阅独有字段********************* /** This key is only present for auto-renewable subscription receipts. Use this value to identify the date when the subscription will renew or expire, to determine if a customer should have access to content or service. After validating the latest receipt, if the subscription expiration date for the latest renewal transaction is a past date, it is safe to assume that the subscription has expired. */ @property (nonatomic, strong) NSDate *expires_date; /** “1” - Customer canceled their subscription. “2” - Billing error; for example customer’s payment information was no longer valid. “3” - Customer did not agree to a recent price increase. “4” - Product was not available for purchase at the time of renewal. “5” - Unknown error */ @property (nonatomic, assign) NSInteger expiration_intent; /** 对于订阅过期的自动续费产品,苹果是否会尝试自动续费 For an expired subscription, whether or not Apple is still attempting to automatically renew the subscription. “1” - App Store is still attempting to renew the subscription. “0” - App Store has stopped attempting to renew the subscription. This key is only present for auto-renewable subscription receipts. If the customer’s subscription failed to renew because the App Store was unable to complete the transaction, this value will reflect whether or not the App Store is still trying to renew the subscription. */ @property (nonatomic, assign) BOOL is_in_billing_retry_period; /** 仅用于,自动续费订阅 true,表示处于 引导价格 时期 如果已有票据中含有is_trial_period或者is_in_intro_offer_period为true,用户不再具备有此项资格 For an auto-renewable subscription, whether or not it is in the introductory price period. This key is only present for auto-renewable subscription receipts. The value for this key is "true" if the customer’s subscription is currently in an introductory price period, or "false" if not. Note: If a previous subscription period in the receipt has the value “true” for either the is_trial_period or the is_in_intro_offer_period key, the user is not eligible for a free trial or introductory price within that subscription group. */ @property (nonatomic, assign) BOOL is_in_intro_offer_period; /** 退款操作时间 For a transaction that was canceled by Apple customer support, the time and date of the cancellation. For an auto-renewable subscription plan that was upgraded, the time and date of the upgrade transaction. Treat a canceled receipt the same as if no purchase had ever been made. A canceled in-app purchase remains in the receipt indefinitely. Only applicable if the refund was for a non-consumable product, an auto-renewable subscription, a non-renewing subscription, or for a free subscription. */ @property (nonatomic, strong) NSDate *cancellation_date; /** 内购取消的原因 “1” - Customer canceled their transaction due to an actual or perceived issue within your app. “0” - Transaction was canceled for another reason, for example, if the customer made the purchase accidentally. Use this value along with the cancellation date to identify possible issues in your app that may lead customers to contact Apple customer support. */ @property (nonatomic, copy) NSString *cancellation_reason; /** APP唯一标识符 */ @property (nonatomic, copy) NSString *app_item_id; /** This key is not present for receipts created in the test environment. Use this value to identify the version of the app that the customer bought */ @property (nonatomic, copy) NSString *version_external_identifier; /** This value is a unique ID that identifies purchase events across devices, including subscription renewal purchase events. */ @property (nonatomic, copy) NSString *web_order_line_item_id; /** The current renewal status for the auto-renewable subscription. “1” - Subscription will renew at the end of the current subscription period. “0” - Customer has turned off automatic renewal for their subscription. This key is only present for auto-renewable subscription receipts, for active or expired subscriptions. The value for this key should not be interpreted as the customer’s subscription status. You can use this value to display an alternative subscription product in your app, for example, a lower level subscription plan that the customer can downgrade to from their current plan. */ @property (nonatomic, assign) NSInteger auto_renew_status; /** The current renewal preference for the auto-renewable subscription. This key is only present for auto-renewable subscription receipts. The value for this key corresponds to the productIdentifier property of the product that the customer’s subscription renews. You can use this value to present an alternative service level to the customer before the current subscription period ends. */ @property (nonatomic, copy) NSString *auto_renew_product_id; /** The current price consent status for a subscription price increase. “1” - Customer has agreed to the price increase. Subscription will renew at the higher price. “0” - Customer has not taken action regarding the increased price. Subscription expires if the customer takes no action before the renewal date. This key is only present for auto-renewable subscription receipts if the subscription price was increased without keeping the existing price for active subscribers. You can use this value to track customer adoption of the new price and take appropriate action. */ @property (nonatomic, assign) NSInteger price_consent_status; @end /** 自定续期订阅 { "expires_date" = "2018-05-04 11:11:09 Etc/GMT"; "expires_date_ms" = 1525432269000; "expires_date_pst" = "2018-05-04 04:11:09 America/Los_Angeles"; "is_in_intro_offer_period" = false; "is_trial_period" = false; "original_purchase_date" = "2018-05-04 10:11:10 Etc/GMT"; "original_purchase_date_ms" = 1525428670000; "original_purchase_date_pst" = "2018-05-04 03:11:10 America/Los_Angeles"; "original_transaction_id" = 1000000356225087; "product_id" = 1312830469; "purchase_date" = "2018-05-04 10:11:09 Etc/GMT"; "purchase_date_ms" = 1525428669000; "purchase_date_pst" = "2018-05-04 03:11:09 America/Los_Angeles"; quantity = 1; "transaction_id" = 1000000395911972; "web_order_line_item_id" = 1000000038645384; } 非自动续期 { "is_trial_period" = false; "original_purchase_date" = "2018-05-04 10:13:23 Etc/GMT"; "original_purchase_date_ms" = 1525428803000; "original_purchase_date_pst" = "2018-05-04 03:13:23 America/Los_Angeles"; "original_transaction_id" = 1000000395914364; "product_id" = 1003; "purchase_date" = "2018-05-04 10:13:23 Etc/GMT"; "purchase_date_ms" = 1525428803000; "purchase_date_pst" = "2018-05-04 03:13:23 America/Los_Angeles"; quantity = 1; "transaction_id" = 1000000395914364; } */ ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYInAppReceipt.m ================================================ // // XYInAppReceipt.m // Pods // // Created by qichao.ma on 2018/5/3. // #import "XYInAppReceipt.h" #import "NSDate+XYStoreExtension.h" @implementation XYInAppReceipt // YYModel解析 - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic { NSString *purchase_date = dic[@"purchase_date"]; _purchase_date = [NSDate dateWithZoneDateString:purchase_date]; NSString *original_purchase_date = dic[@"original_purchase_date"]; _original_purchase_date = [NSDate dateWithZoneDateString:original_purchase_date]; NSString *expires_date = dic[@"expires_date"]; _expires_date = [NSDate dateWithZoneDateString:expires_date]; NSString *cancellation_date = dic[@"cancellation_date"]; _cancellation_date = [NSDate dateWithZoneDateString:cancellation_date]; return YES; } // YYModel解析 - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic { dic[@"purchase_date"] = [NSDate GMTdateString:_purchase_date]; dic[@"original_purchase_date"] = [NSDate GMTdateString:_original_purchase_date]; dic[@"expires_date"] = [NSDate GMTdateString:_expires_date]; dic[@"cancellation_date"] = [NSDate GMTdateString:_cancellation_date]; return YES; } @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYStoreiTunesReceiptVerifier.h ================================================ // // XYStoreiTunesReceiptVerifier.h // Pods // // Created by qichao.ma on 2018/5/2. // #import #import "XYStore.h" @interface XYStoreiTunesReceiptVerifier : NSObject /** App 专用共享密钥 App 专用共享密钥是用于接收此 App 自动续订订阅收据的唯一代码。 如果您需要将此 App 转让给其他开发人员,或者需要将主共享密钥设置为专用,可能需要使用 App 专用共享密钥。 */ @property (nonatomic, copy) NSString *sharedSecretKey; + (instancetype)shareInstance; /** @ 适用自动续期订阅 判断是否已订阅 若果是非自动续费型商品,直接返回NO @param productId 自动续期订阅产品id @return YES: NO:未订阅或者订阅过期 */ - (BOOL)isSubscribedWithAutoRenewProduct:(NSString *)productId; /** @ 适用自动续期订阅 @param applicationUsername An opaque identifier for the user’s account on your system. Use this property to help the store detect irregular activity. For example, in a game, it would be unusual for dozens of different iTunes Store accounts to make purchases on behalf of the same in-game character. The recommended implementation is to use a one-way hash of the user’s account name to calculate the value for this property. */ - (BOOL)isSubscribedWithAutoRenewProduct:(NSString *)productId applicationUsername:(NSString *)applicationUsername; /** 针对持续有效的产品,Apple返回的票据记录中会一直保留其票据信息,可以通过票据记录判断是否有效 支持:1、非续期订阅 2、非消耗型项目 注:消耗型项目一旦完成,不会长期保留在票据信息中 @param productId 产品id @return YES:消费有效 NO:无效 */ - (BOOL)isValidWithPersistentProductId:(NSString *)productId; - (BOOL)isValidWithPersistentProductId:(NSString *)productId applicationUsername:(NSString *)applicationUsername; @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYStoreiTunesReceiptVerifier.m ================================================ // // XYStoreiTunesReceiptVerifier.m // Pods // // Created by qichao.ma on 2018/5/2. // #import "XYStoreiTunesReceiptVerifier.h" #import "XYStore.h" #import "YYModel.h" #import "XYiTunesResponse.h" NSString *const XYStoreiTunesVerifyReceiptURL = @"https://buy.itunes.apple.com/verifyReceipt"; NSString *const XYStoreiTunesSandboxVerifyReceiptURL = @"https://sandbox.itunes.apple.com/verifyReceipt"; NSString *const XYCachePreferenceKeyPrefix = @"xy_cache_pre_key_prefix"; @interface XYStoreiTunesReceiptVerifier() @property (nonatomic, strong) NSMutableDictionary *verifiedReceipts; @end @implementation XYStoreiTunesReceiptVerifier + (instancetype)shareInstance { static XYStoreiTunesReceiptVerifier *shareInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shareInstance = [[XYStoreiTunesReceiptVerifier alloc] init]; }); return shareInstance; } - (BOOL)isSubscribedWithAutoRenewProduct:(NSString *)productId { return [self isSubscribedWithAutoRenewProduct:productId applicationUsername:nil]; } - (BOOL)isSubscribedWithAutoRenewProduct:(NSString *)productId applicationUsername:(NSString *)applicationUsername { XYiTunesResponse *iTunesResponse = [self iTunesResponseInfoWithProductId:productId applicationUsername:applicationUsername]; if (!iTunesResponse) { return NO; } // 无票据信息直接返回NO if (!iTunesResponse.latest_receipt_info || iTunesResponse.latest_receipt_info.count <= 0) { return NO; } return [self checkIsSubscribed:iTunesResponse productId:productId]; } - (BOOL)checkIsSubscribed:(XYiTunesResponse *)iTunesResponse productId:(NSString *)productId { NSDate *expires_date; for (XYInAppReceipt *appReceipt in iTunesResponse.latest_receipt_info) { if ([appReceipt.product_id isEqualToString:productId] == NO) { continue; } if (expires_date) { expires_date = [expires_date laterDate:appReceipt.expires_date]; }else { expires_date = appReceipt.expires_date; } } // 不包含过期信息表示无自动续期交易 if (!expires_date) { return NO; } // 将当前时间统一为utc时间进行对比 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss Z"; NSString *currentDateString = [dateFormatter stringFromDate:[NSDate date]]; NSDate *currentDate = [dateFormatter dateFromString:currentDateString]; if (([expires_date timeIntervalSinceDate:iTunesResponse.receipt.request_date] > 0) && ([expires_date timeIntervalSinceDate:currentDate] > 0)) { // 1、对比请求时间 // 针对SKPaymentTransactionObserver的监听,当交易信息发生更新时,苹果会自动推送当前的交易状态, // 缓存票据更新时的请求时间,通过与过期时间对比来确定用户的订阅是否过期 // 此方式可以避免用户修改系统时间造成的问题,也能保证及时的更新用户订阅状况 // 也可使用获取当前外部服务器的时间,当然需要异步操作,时间成本比较高 // 2、对比系统时间 // 防止订阅后,用户强制断网 return YES; } return NO; } - (BOOL)isValidWithPersistentProductId:(NSString *)productId { return [self isValidWithPersistentProductId:productId applicationUsername:nil]; } - (BOOL)isValidWithPersistentProductId:(NSString *)productId applicationUsername:(NSString *)applicationUsername { XYiTunesResponse *iTunesResponse = [self iTunesResponseInfoWithProductId:productId applicationUsername:applicationUsername]; if (!iTunesResponse) { return NO; } // 无票据信息直接返回NO if (!iTunesResponse.latest_receipt_info || iTunesResponse.latest_receipt_info.count <= 0) { return NO; } BOOL isValid = NO; for (XYInAppReceipt *appReceipt in iTunesResponse.latest_receipt_info) { if ([appReceipt.product_id isEqualToString:productId]) { isValid = YES; break; } } return isValid; } #pragma mark - XYStoreReceiptVerifier - (void)verifyTransaction:(SKPaymentTransaction*)transaction success:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock { NSString *receiptUrl = XYStoreiTunesVerifyReceiptURL; #if DEBUG receiptUrl = XYStoreiTunesSandboxVerifyReceiptURL; #endif __weak typeof(self) weakSelf = self; [[XYStore defaultStore] base64Receipt:^(NSString *base64Data) { [weakSelf verifyRequestData:base64Data url:receiptUrl transaction:transaction success:successBlock failure:failureBlock]; } failure:failureBlock]; } - (void)verifyRequestData:(NSString *)base64Data url:(NSString *)url transaction:(SKPaymentTransaction *)transaction success:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock { NSMutableDictionary *params = [NSMutableDictionary dictionary]; [params setValue:base64Data forKey:@"receipt-data"]; [params setValue:self.sharedSecretKey forKey:@"password"]; NSError *jsonError; NSData *josonData = [NSJSONSerialization dataWithJSONObject:params options:NSJSONWritingPrettyPrinted error:&jsonError]; if (jsonError) { NSLog(@"verifyRequestData failed: error = %@", jsonError); } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; request.HTTPBody = josonData; static NSString *requestMethod = @"POST"; request.HTTPMethod = requestMethod; __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSError *error; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; dispatch_async(dispatch_get_main_queue(), ^{ if (!data) { NSError *wrapperError = [weakSelf unableVerifyReceiptError:error]; if (failureBlock != nil) failureBlock(wrapperError); return; } NSError *jsonError; NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (!responseJSON) { NSLog(@"Failed To Parse Server Response"); if (failureBlock != nil) failureBlock(jsonError); } static NSString *statusKey = @"status"; NSInteger statusCode = [responseJSON[statusKey] integerValue]; static NSInteger successCode = 0; static NSInteger sandboxCode = 21007; if (statusCode == successCode) { [weakSelf saveVerifiedReceipts:transaction response:responseJSON]; if (successBlock != nil) successBlock(); } else if (statusCode == sandboxCode) { [weakSelf sandboxVerify:base64Data transaction:transaction success:successBlock failure:failureBlock]; } else { NSLog(@"Verification Failed With Code %ld", (long)statusCode); NSError *serverError = [NSError errorWithDomain:XYStoreErrorDomain code:statusCode userInfo:nil]; if (failureBlock != nil) failureBlock(serverError); } }); }); } /** From: https://developer.apple.com/library/ios/#technotes/tn2259/_index.html See also: http://stackoverflow.com/questions/9677193/ios-storekit-can-i-detect-when-im-in-the-sandbox Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store. */ - (void)sandboxVerify:(NSString *)base64Data transaction:(SKPaymentTransaction *)transaction success:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock { NSLog(@"Verifying Sandbox Receipt"); [self verifyRequestData:base64Data url:XYStoreiTunesSandboxVerifyReceiptURL transaction:transaction success:successBlock failure:failureBlock]; } - (NSError *)unableVerifyReceiptError:(NSError *)error { NSLog(@"Server Connection Failed"); NSString *errorDesc = @"Connection to Apple failed. Check the underlying error for more info."; NSError *wrapperError = [NSError errorWithDomain:XYStoreErrorDomain code:XYStoreErrorCodeUnableToCompleteVerification userInfo:@{ NSUnderlyingErrorKey : error, NSLocalizedDescriptionKey : errorDesc }]; return wrapperError; } // 存储对应的key - (NSString *)verifiedReceiptPrefrenceKey:(NSString *)productId applicationUsername:(NSString *)applicationUsername { NSString *userName = applicationUsername; if ([applicationUsername isEqual:NULL] || [applicationUsername isKindOfClass:[NSNull class]] || !applicationUsername) { userName = @""; } return [NSString stringWithFormat:@"%@_%@%@", XYCachePreferenceKeyPrefix, userName, productId]; } // 缓存票据校验结果 - (void)saveVerifiedReceipts:(SKPaymentTransaction *)transaction response:(NSDictionary *)response { if (!transaction) { return; } NSString *key = [self verifiedReceiptPrefrenceKey:transaction.payment.productIdentifier applicationUsername:transaction.payment.applicationUsername]; [self.verifiedReceipts setValue:response forKey:key]; NSString *responseJSON = [response yy_modelToJSONString]; [[NSUserDefaults standardUserDefaults] setValue:responseJSON forKey:key]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (XYiTunesResponse *)iTunesResponseInfoWithProductId:(NSString *)productId { return [self iTunesResponseInfoWithProductId:productId applicationUsername:nil]; } - (XYiTunesResponse *)iTunesResponseInfoWithProductId:(NSString *)productId applicationUsername:(NSString *)applicationUsername { if (!productId) { return nil; } NSString *key = [self verifiedReceiptPrefrenceKey:productId applicationUsername:applicationUsername]; NSDictionary *response = [self.verifiedReceipts valueForKey:key]; if (!response) { id value = [[NSUserDefaults standardUserDefaults] valueForKey:key]; if (value) { value = [self.class dictionaryWithJSON:value]; } response = value; } if (response) { return [XYiTunesResponse yy_modelWithDictionary:response]; } return nil; } - (NSMutableDictionary *)verifiedReceipts { if (!_verifiedReceipts) { _verifiedReceipts = [NSMutableDictionary dictionary]; } return _verifiedReceipts; } + (NSDictionary *)dictionaryWithJSON:(id)json { if (!json || json == (id)kCFNull) return nil; NSDictionary *dic = nil; NSData *jsonData = nil; if ([json isKindOfClass:[NSDictionary class]]) { dic = json; } else if ([json isKindOfClass:[NSString class]]) { jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding]; } else if ([json isKindOfClass:[NSData class]]) { jsonData = json; } if (jsonData) { dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL]; if (![dic isKindOfClass:[NSDictionary class]]) dic = nil; } return dic; } @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesReceipt.h ================================================ // // XYiTunesReceipt.h // Pods // // Created by qichao.ma on 2018/5/2. // #import #import "XYInAppReceipt.h" @interface XYiTunesReceipt : NSObject @property (nonatomic, copy) NSString *adam_id; @property (nonatomic, copy) NSString *app_item_id; @property (nonatomic, copy) NSString *application_version; @property (nonatomic, copy) NSString *bundle_id; @property (nonatomic, copy) NSString *download_id; /** In the JSON file, the value of this key is an array containing all in-app purchase receipts based on the in-app purchase transactions present in the input base-64 receipt-data. For receipts containing auto-renewable subscriptions, check the value of the latest_receipt_info key to get the status of the most recent renewal. Note: An empty array is a valid receipt. The in-app purchase receipt for a consumable product is added to the receipt when the purchase is made. It is kept in the receipt until your app finishes that transaction. After that point, it is removed from the receipt the next time the receipt is updated - for example, when the user makes another purchase or if your app explicitly refreshes the receipt. The in-app purchase receipt for a non-consumable product, auto-renewable subscription, non-renewing subscription, or free subscription remains in the receipt indefinitely. */ @property (nonatomic, strong) NSArray *in_app; @property (nonatomic, copy) NSString *original_application_version; @property (nonatomic, strong) NSDate *original_purchase_date; @property (nonatomic, strong) NSDate *receipt_creation_date; @property (nonatomic, strong) NSDate *request_date; @property (nonatomic, copy) NSString *receipt_type; @property (nonatomic, copy) NSString *version_external_identifier; @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesReceipt.m ================================================ // // XYiTunesReceipt.m // Pods // // Created by qichao.ma on 2018/5/2. // #import "XYiTunesReceipt.h" #import "NSDate+XYStoreExtension.h" @implementation XYiTunesReceipt // 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。 + (NSDictionary *)modelContainerPropertyGenericClass { return @{ @"in_app" : [XYInAppReceipt class], }; } // YYModel解析 - (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic { NSString *original_purchase_date = dic[@"original_purchase_date"]; _original_purchase_date = [NSDate dateWithZoneDateString:original_purchase_date]; NSString *receipt_creation_date = dic[@"receipt_creation_date"]; _receipt_creation_date = [NSDate dateWithZoneDateString:receipt_creation_date]; NSString *request_date = dic[@"request_date"]; _request_date = [NSDate dateWithZoneDateString:request_date]; return YES; } // YYModel解析 - (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic { dic[@"original_purchase_date"] = [NSDate GMTdateString:_original_purchase_date]; dic[@"receipt_creation_date"] = [NSDate GMTdateString:_receipt_creation_date]; dic[@"request_date"] = [NSDate GMTdateString:_request_date]; return YES; } @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesResponse.h ================================================ // // XYiTunesResponse.h // Pods // // Created by qichao.ma on 2018/5/2. // #import #import "XYiTunesReceipt.h" #import "XYInAppReceipt.h" @class XYPendingRenewalInfo; /** The values of the latest_receipt and latest_receipt_info keys are useful when checking whether an auto-renewable subscription is currently active. The values of latest_expired_receipt_info key are useful when checking whether an auto-renewable subscription has expired. Use this along with the value for Subscription Expiration Intent to get the reason for expiration. By providing an app receipt or any transaction receipt for the subscription and checking these values, you can get information about the currently-active subscription period. If the receipt being validated is for the latest renewal, the value for latest_receipt is the same as receipt-data (in the request) and the value for latest_receipt_info is the same as receipt. */ @interface XYiTunesResponse : NSObject /** For iOS 7 style app receipts, the status code is reflects the status of the app receipt as a whole. For example, if you send a valid app receipt that contains an expired subscription, the response is 0 because the receipt as a whole is valid. https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW5 Status Code 21000 The App Store could not read the JSON object you provided. 21002 The data in the receipt-data property was malformed or missing. 21003 The receipt could not be authenticated. 21004 The shared secret you provided does not match the shared secret on file for your account. 21005 The receipt server is not currently available. 21006 This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response. Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions. 21007 This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead. 21008 This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead. 21010 This receipt could not be authorized. Treat this the same as if a purchase was never made. 21100-21199 Internal data access error. */ @property (nonatomic, assign) NSInteger status; /** A JSON representation of the receipt that was sent for verification. For information about keys found in a receipt, see Receipt Fields. */ @property (nonatomic, strong) XYiTunesReceipt *receipt; /** @ auto-renewable subscription Only returned for receipts containing auto-renewable subscriptions. For iOS 6 style transaction receipts, this is the base-64 encoded receipt for the most recent renewal. For iOS 7 style app receipts, this is the latest base-64 encoded app receipt. */ @property (nonatomic, copy) NSString *latest_receipt; /** @ auto-renewable subscription Only returned for receipts containing auto-renewable subscriptions. For iOS 6 style transaction receipts, this is the JSON representation of the receipt for the most recent renewal. For iOS 7 style app receipts, the value of this key is an array containing all in-app purchase transactions. This excludes transactions for a consumable product that have been marked as finished by your app. */ @property (nonatomic, strong) NSArray *latest_receipt_info; /** @ auto-renewable subscription Only returned for iOS 7 style app receipts containing auto-renewable subscriptions. In the JSON file, the value of this key is an array where each element contains the pending renewal information for each auto-renewable subscription identified by the Product Identifier. A pending renewal may refer to a renewal that is scheduled in the future or a renewal that failed in the past for some reason. */ @property (nonatomic, strong) NSArray *pending_renewal_info; @end /** The values of pending_renewal_info key are useful to get critical information about any pending renewal transactions for an auto-renewable subscription. */ @interface XYPendingRenewalInfo : NSObject @property (nonatomic, copy) NSString *auto_renew_product_id; @property (nonatomic, copy) NSString *original_transaction_id; @property (nonatomic, copy) NSString *product_id; /** “1” - Subscription will renew at the end of the current subscription period. “0” - Customer has turned off automatic renewal for their subscription. */ @property (nonatomic, assign) NSInteger auto_renew_status; @end ================================================ FILE: XYIAPKit/Receipt/iTunesReceiptVerify/XYiTunesResponse.m ================================================ // // XYiTunesResponse.m // Pods // // Created by qichao.ma on 2018/5/2. // #import "XYiTunesResponse.h" @implementation XYiTunesResponse // 返回容器类中的所需要存放的数据类型 (以 Class 或 Class Name 的形式)。 + (NSDictionary *)modelContainerPropertyGenericClass { return @{ @"pending_renewal_info" : [XYPendingRenewalInfo class], @"latest_receipt_info" : [XYInAppReceipt class], }; } @end @implementation XYPendingRenewalInfo @end ================================================ FILE: XYIAPKit/XYIAPKit.h ================================================ // // XYIAPKit.h // Pods // // Created by qichao.ma on 2018/4/21. // #ifndef XYIAPKit_h #define XYIAPKit_h #import "XYStore.h" #endif /* XYIAPKit_h */ ================================================ FILE: XYIAPKit.podspec ================================================ # # Be sure to run `pod lib lint XYIAPKit.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 http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'XYIAPKit' s.version = '1.0.1' s.summary = 'In App purchase sdk, support auto-renewable subscription' s.description = <<-DESC 非常好用的内购组件,支持自动续期订阅的过期校验、票据检验、简单易用 提供功能: 1)、产品查询 2)、产品购买 3)、恢复内购 4)、票据校验 5)、交易存储 DESC s.homepage = 'https://github.com/mqc123450/XYIAPKit' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'qichao.ma' => '“qichao.ma@quvideo.com”' } s.source = { :git => 'https://github.com/mqc123450/XYIAPKit.git', :tag => s.version.to_s } s.ios.deployment_target = '8.0' s.platform = :ios, '8.0' s.source_files = 'XYIAPKit/*.h' s.public_header_files = 'XYIAPKit/*.h' s.frameworks = 'StoreKit' s.frameworks = 'StoreKit' s.requires_arc = true s.default_subspec = 'Core' s.subspec 'Core' do |core| core.source_files = 'XYIAPKit/Core/*.{h,m}' end s.subspec 'KeychainPersistence' do |ke| ke.dependency 'XYIAPKit/Core' ke.source_files = 'XYIAPKit/Persistence/KeychainPersistence/*.{h,m}' ke.frameworks = 'Security' end s.subspec 'UserDefaultPersistence' do |us| us.dependency 'XYIAPKit/Core' us.source_files = 'XYIAPKit/Persistence/UserDefaultPersistence/*.{h,m}' end s.subspec 'iTunesReceiptVerify' do |it| it.dependency 'XYIAPKit/Core' it.source_files = 'XYIAPKit/Receipt/iTunesReceiptVerify/*.{h,m}' it.dependency 'YYModel', '~> 1.0.4' end end