├── .gitignore ├── LICENSE ├── Podfile ├── Podfile.lock ├── Polymer.podspec ├── Polymer.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Polymer.xcworkspace └── contents.xcworkspacedata ├── Polymer ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Example │ ├── Models │ │ ├── SpotifyArtist.h │ │ ├── SpotifyArtist.m │ │ ├── SpotifyImageRef.h │ │ └── SpotifyImageRef.m │ ├── SpotifyEndpoints.h │ └── SpotifyEndpoints.m ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Source │ ├── PLYEndpoint.h │ ├── PLYEndpoint.m │ ├── PLYNetworking.h │ └── PLYNetworking.m ├── ViewController.h ├── ViewController.m └── main.m ├── PolymerTests ├── Info.plist └── PolymerTests.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | Pods/ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, '7.1' 4 | 5 | pod 'Genome', '0.1' 6 | pod 'AFNetworking', '~>2.0' 7 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (2.5.2): 3 | - AFNetworking/NSURLConnection (= 2.5.2) 4 | - AFNetworking/NSURLSession (= 2.5.2) 5 | - AFNetworking/Reachability (= 2.5.2) 6 | - AFNetworking/Security (= 2.5.2) 7 | - AFNetworking/Serialization (= 2.5.2) 8 | - AFNetworking/UIKit (= 2.5.2) 9 | - AFNetworking/NSURLConnection (2.5.2): 10 | - AFNetworking/Reachability 11 | - AFNetworking/Security 12 | - AFNetworking/Serialization 13 | - AFNetworking/NSURLSession (2.5.2): 14 | - AFNetworking/Reachability 15 | - AFNetworking/Security 16 | - AFNetworking/Serialization 17 | - AFNetworking/Reachability (2.5.2) 18 | - AFNetworking/Security (2.5.2) 19 | - AFNetworking/Serialization (2.5.2) 20 | - AFNetworking/UIKit (2.5.2): 21 | - AFNetworking/NSURLConnection 22 | - AFNetworking/NSURLSession 23 | - Genome (0.1) 24 | 25 | DEPENDENCIES: 26 | - AFNetworking (~> 2.0) 27 | - Genome (= 0.1) 28 | 29 | SPEC CHECKSUMS: 30 | AFNetworking: fefbce9660acb17f48ae0011292d4da0f457bf36 31 | Genome: 7aa8b47f7275df8d6e65afe1f35799a7177675f9 32 | 33 | COCOAPODS: 0.36.3 34 | -------------------------------------------------------------------------------- /Polymer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'Polymer' 3 | spec.version = '0.1.5' 4 | spec.license = 'MIT' 5 | spec.homepage = 'https://github.com/LoganWright/Polymer' 6 | spec.authors = { 'Logan Wright' => 'logan.william.wright@gmail.com' } 7 | spec.summary = 'Endpoint focused networking for iOS and OS X.' 8 | spec.source = { :git => 'https://github.com/LoganWright/Polymer.git', :tag => '0.1.5' } 9 | spec.source_files = 'Polymer/Source/**/*.{h,m}' 10 | spec.ios.deployment_target = "7.0" 11 | spec.osx.deployment_target = "10.8" 12 | spec.requires_arc = true 13 | spec.dependency 'AFNetworking', '~> 2.0' 14 | spec.dependency 'Genome', '0.1' 15 | spec.social_media_url = 'https://twitter.com/logmaestro' 16 | end 17 | -------------------------------------------------------------------------------- /Polymer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8047ABC31A9FF411008093A4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABC21A9FF411008093A4 /* main.m */; }; 11 | 8047ABC61A9FF411008093A4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABC51A9FF411008093A4 /* AppDelegate.m */; }; 12 | 8047ABCC1A9FF411008093A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8047ABCA1A9FF411008093A4 /* Main.storyboard */; }; 13 | 8047ABCE1A9FF411008093A4 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8047ABCD1A9FF411008093A4 /* Images.xcassets */; }; 14 | 8047ABD11A9FF411008093A4 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8047ABCF1A9FF411008093A4 /* LaunchScreen.xib */; }; 15 | 8047ABDD1A9FF411008093A4 /* PolymerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABDC1A9FF411008093A4 /* PolymerTests.m */; }; 16 | 8047ABEB1A9FF534008093A4 /* PLYEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABE81A9FF534008093A4 /* PLYEndpoint.m */; }; 17 | 8047ABEC1A9FF534008093A4 /* PLYNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABEA1A9FF534008093A4 /* PLYNetworking.m */; }; 18 | 8047ABEF1A9FF544008093A4 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABEE1A9FF544008093A4 /* ViewController.m */; }; 19 | 8047ABF81A9FF586008093A4 /* SpotifyArtist.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABF31A9FF586008093A4 /* SpotifyArtist.m */; }; 20 | 8047ABF91A9FF586008093A4 /* SpotifyImageRef.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABF51A9FF586008093A4 /* SpotifyImageRef.m */; }; 21 | 8047ABFA1A9FF586008093A4 /* SpotifyEndpoints.m in Sources */ = {isa = PBXBuildFile; fileRef = 8047ABF71A9FF586008093A4 /* SpotifyEndpoints.m */; }; 22 | 9F276AD54BD3DF0E8A87EECA /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DE0383B994C08B693808D1F3 /* libPods.a */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 8047ABD71A9FF411008093A4 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 8047ABB51A9FF411008093A4 /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 8047ABBC1A9FF411008093A4; 31 | remoteInfo = Polymer; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | 3012B6C3FE5EB86E20D2425A /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 37 | 8047ABBD1A9FF411008093A4 /* Polymer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Polymer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 8047ABC11A9FF411008093A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 8047ABC21A9FF411008093A4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = main.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 40 | 8047ABC41A9FF411008093A4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = AppDelegate.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 41 | 8047ABC51A9FF411008093A4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppDelegate.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 42 | 8047ABCB1A9FF411008093A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | 8047ABCD1A9FF411008093A4 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 44 | 8047ABD01A9FF411008093A4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 45 | 8047ABD61A9FF411008093A4 /* PolymerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PolymerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 8047ABDB1A9FF411008093A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 8047ABDC1A9FF411008093A4 /* PolymerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PolymerTests.m; sourceTree = ""; }; 48 | 8047ABE71A9FF534008093A4 /* PLYEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PLYEndpoint.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 49 | 8047ABE81A9FF534008093A4 /* PLYEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = PLYEndpoint.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 50 | 8047ABE91A9FF534008093A4 /* PLYNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PLYNetworking.h; sourceTree = ""; }; 51 | 8047ABEA1A9FF534008093A4 /* PLYNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PLYNetworking.m; sourceTree = ""; }; 52 | 8047ABED1A9FF544008093A4 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 53 | 8047ABEE1A9FF544008093A4 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 54 | 8047ABF21A9FF586008093A4 /* SpotifyArtist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotifyArtist.h; sourceTree = ""; }; 55 | 8047ABF31A9FF586008093A4 /* SpotifyArtist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotifyArtist.m; sourceTree = ""; }; 56 | 8047ABF41A9FF586008093A4 /* SpotifyImageRef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotifyImageRef.h; sourceTree = ""; }; 57 | 8047ABF51A9FF586008093A4 /* SpotifyImageRef.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotifyImageRef.m; sourceTree = ""; }; 58 | 8047ABF61A9FF586008093A4 /* SpotifyEndpoints.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SpotifyEndpoints.h; sourceTree = ""; }; 59 | 8047ABF71A9FF586008093A4 /* SpotifyEndpoints.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpotifyEndpoints.m; sourceTree = ""; }; 60 | A0FBB3DBC2B8B49B82E248CB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 61 | DE0383B994C08B693808D1F3 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | 8047ABBA1A9FF411008093A4 /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | 9F276AD54BD3DF0E8A87EECA /* libPods.a in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | 8047ABD31A9FF411008093A4 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 3CC104FA348DA8081E5005FA /* Frameworks */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | DE0383B994C08B693808D1F3 /* libPods.a */, 87 | ); 88 | name = Frameworks; 89 | sourceTree = ""; 90 | }; 91 | 7CDC12C8988837A7FC16DF12 /* Pods */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | A0FBB3DBC2B8B49B82E248CB /* Pods.debug.xcconfig */, 95 | 3012B6C3FE5EB86E20D2425A /* Pods.release.xcconfig */, 96 | ); 97 | name = Pods; 98 | sourceTree = ""; 99 | }; 100 | 8047ABB41A9FF411008093A4 = { 101 | isa = PBXGroup; 102 | children = ( 103 | 8047ABBF1A9FF411008093A4 /* Polymer */, 104 | 8047ABD91A9FF411008093A4 /* PolymerTests */, 105 | 8047ABBE1A9FF411008093A4 /* Products */, 106 | 7CDC12C8988837A7FC16DF12 /* Pods */, 107 | 3CC104FA348DA8081E5005FA /* Frameworks */, 108 | ); 109 | sourceTree = ""; 110 | }; 111 | 8047ABBE1A9FF411008093A4 /* Products */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 8047ABBD1A9FF411008093A4 /* Polymer.app */, 115 | 8047ABD61A9FF411008093A4 /* PolymerTests.xctest */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | 8047ABBF1A9FF411008093A4 /* Polymer */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 8047ABE61A9FF534008093A4 /* Source */, 124 | 8047ABF01A9FF586008093A4 /* Example */, 125 | 8047ABC41A9FF411008093A4 /* AppDelegate.h */, 126 | 8047ABC51A9FF411008093A4 /* AppDelegate.m */, 127 | 8047ABED1A9FF544008093A4 /* ViewController.h */, 128 | 8047ABEE1A9FF544008093A4 /* ViewController.m */, 129 | 8047ABCA1A9FF411008093A4 /* Main.storyboard */, 130 | 8047ABCD1A9FF411008093A4 /* Images.xcassets */, 131 | 8047ABCF1A9FF411008093A4 /* LaunchScreen.xib */, 132 | 8047ABC01A9FF411008093A4 /* Supporting Files */, 133 | ); 134 | path = Polymer; 135 | sourceTree = ""; 136 | }; 137 | 8047ABC01A9FF411008093A4 /* Supporting Files */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 8047ABC11A9FF411008093A4 /* Info.plist */, 141 | 8047ABC21A9FF411008093A4 /* main.m */, 142 | ); 143 | name = "Supporting Files"; 144 | sourceTree = ""; 145 | }; 146 | 8047ABD91A9FF411008093A4 /* PolymerTests */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 8047ABDC1A9FF411008093A4 /* PolymerTests.m */, 150 | 8047ABDA1A9FF411008093A4 /* Supporting Files */, 151 | ); 152 | path = PolymerTests; 153 | sourceTree = ""; 154 | }; 155 | 8047ABDA1A9FF411008093A4 /* Supporting Files */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 8047ABDB1A9FF411008093A4 /* Info.plist */, 159 | ); 160 | name = "Supporting Files"; 161 | sourceTree = ""; 162 | }; 163 | 8047ABE61A9FF534008093A4 /* Source */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 8047ABE71A9FF534008093A4 /* PLYEndpoint.h */, 167 | 8047ABE81A9FF534008093A4 /* PLYEndpoint.m */, 168 | 8047ABE91A9FF534008093A4 /* PLYNetworking.h */, 169 | 8047ABEA1A9FF534008093A4 /* PLYNetworking.m */, 170 | ); 171 | path = Source; 172 | sourceTree = ""; 173 | }; 174 | 8047ABF01A9FF586008093A4 /* Example */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 8047ABF11A9FF586008093A4 /* Models */, 178 | 8047ABF61A9FF586008093A4 /* SpotifyEndpoints.h */, 179 | 8047ABF71A9FF586008093A4 /* SpotifyEndpoints.m */, 180 | ); 181 | path = Example; 182 | sourceTree = ""; 183 | }; 184 | 8047ABF11A9FF586008093A4 /* Models */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | 8047ABF21A9FF586008093A4 /* SpotifyArtist.h */, 188 | 8047ABF31A9FF586008093A4 /* SpotifyArtist.m */, 189 | 8047ABF41A9FF586008093A4 /* SpotifyImageRef.h */, 190 | 8047ABF51A9FF586008093A4 /* SpotifyImageRef.m */, 191 | ); 192 | path = Models; 193 | sourceTree = ""; 194 | }; 195 | /* End PBXGroup section */ 196 | 197 | /* Begin PBXNativeTarget section */ 198 | 8047ABBC1A9FF411008093A4 /* Polymer */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = 8047ABE01A9FF411008093A4 /* Build configuration list for PBXNativeTarget "Polymer" */; 201 | buildPhases = ( 202 | C068E9532B6CA3262178DE83 /* Check Pods Manifest.lock */, 203 | 8047ABB91A9FF411008093A4 /* Sources */, 204 | 8047ABBA1A9FF411008093A4 /* Frameworks */, 205 | 8047ABBB1A9FF411008093A4 /* Resources */, 206 | 6350524818B642A3B25DD11B /* Copy Pods Resources */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | ); 212 | name = Polymer; 213 | productName = Polymer; 214 | productReference = 8047ABBD1A9FF411008093A4 /* Polymer.app */; 215 | productType = "com.apple.product-type.application"; 216 | }; 217 | 8047ABD51A9FF411008093A4 /* PolymerTests */ = { 218 | isa = PBXNativeTarget; 219 | buildConfigurationList = 8047ABE31A9FF411008093A4 /* Build configuration list for PBXNativeTarget "PolymerTests" */; 220 | buildPhases = ( 221 | 8047ABD21A9FF411008093A4 /* Sources */, 222 | 8047ABD31A9FF411008093A4 /* Frameworks */, 223 | 8047ABD41A9FF411008093A4 /* Resources */, 224 | ); 225 | buildRules = ( 226 | ); 227 | dependencies = ( 228 | 8047ABD81A9FF411008093A4 /* PBXTargetDependency */, 229 | ); 230 | name = PolymerTests; 231 | productName = PolymerTests; 232 | productReference = 8047ABD61A9FF411008093A4 /* PolymerTests.xctest */; 233 | productType = "com.apple.product-type.bundle.unit-test"; 234 | }; 235 | /* End PBXNativeTarget section */ 236 | 237 | /* Begin PBXProject section */ 238 | 8047ABB51A9FF411008093A4 /* Project object */ = { 239 | isa = PBXProject; 240 | attributes = { 241 | LastUpgradeCheck = 0610; 242 | ORGANIZATIONNAME = LowriDevs; 243 | TargetAttributes = { 244 | 8047ABBC1A9FF411008093A4 = { 245 | CreatedOnToolsVersion = 6.1.1; 246 | }; 247 | 8047ABD51A9FF411008093A4 = { 248 | CreatedOnToolsVersion = 6.1.1; 249 | TestTargetID = 8047ABBC1A9FF411008093A4; 250 | }; 251 | }; 252 | }; 253 | buildConfigurationList = 8047ABB81A9FF411008093A4 /* Build configuration list for PBXProject "Polymer" */; 254 | compatibilityVersion = "Xcode 3.2"; 255 | developmentRegion = English; 256 | hasScannedForEncodings = 0; 257 | knownRegions = ( 258 | en, 259 | Base, 260 | ); 261 | mainGroup = 8047ABB41A9FF411008093A4; 262 | productRefGroup = 8047ABBE1A9FF411008093A4 /* Products */; 263 | projectDirPath = ""; 264 | projectRoot = ""; 265 | targets = ( 266 | 8047ABBC1A9FF411008093A4 /* Polymer */, 267 | 8047ABD51A9FF411008093A4 /* PolymerTests */, 268 | ); 269 | }; 270 | /* End PBXProject section */ 271 | 272 | /* Begin PBXResourcesBuildPhase section */ 273 | 8047ABBB1A9FF411008093A4 /* Resources */ = { 274 | isa = PBXResourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 8047ABCC1A9FF411008093A4 /* Main.storyboard in Resources */, 278 | 8047ABD11A9FF411008093A4 /* LaunchScreen.xib in Resources */, 279 | 8047ABCE1A9FF411008093A4 /* Images.xcassets in Resources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | 8047ABD41A9FF411008093A4 /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | /* End PBXResourcesBuildPhase section */ 291 | 292 | /* Begin PBXShellScriptBuildPhase section */ 293 | 6350524818B642A3B25DD11B /* Copy Pods Resources */ = { 294 | isa = PBXShellScriptBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | ); 298 | inputPaths = ( 299 | ); 300 | name = "Copy Pods Resources"; 301 | outputPaths = ( 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | shellPath = /bin/sh; 305 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; 306 | showEnvVarsInLog = 0; 307 | }; 308 | C068E9532B6CA3262178DE83 /* Check Pods Manifest.lock */ = { 309 | isa = PBXShellScriptBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | ); 313 | inputPaths = ( 314 | ); 315 | name = "Check Pods Manifest.lock"; 316 | outputPaths = ( 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | shellPath = /bin/sh; 320 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 321 | showEnvVarsInLog = 0; 322 | }; 323 | /* End PBXShellScriptBuildPhase section */ 324 | 325 | /* Begin PBXSourcesBuildPhase section */ 326 | 8047ABB91A9FF411008093A4 /* Sources */ = { 327 | isa = PBXSourcesBuildPhase; 328 | buildActionMask = 2147483647; 329 | files = ( 330 | 8047ABEC1A9FF534008093A4 /* PLYNetworking.m in Sources */, 331 | 8047ABEB1A9FF534008093A4 /* PLYEndpoint.m in Sources */, 332 | 8047ABC61A9FF411008093A4 /* AppDelegate.m in Sources */, 333 | 8047ABF91A9FF586008093A4 /* SpotifyImageRef.m in Sources */, 334 | 8047ABC31A9FF411008093A4 /* main.m in Sources */, 335 | 8047ABEF1A9FF544008093A4 /* ViewController.m in Sources */, 336 | 8047ABF81A9FF586008093A4 /* SpotifyArtist.m in Sources */, 337 | 8047ABFA1A9FF586008093A4 /* SpotifyEndpoints.m in Sources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | 8047ABD21A9FF411008093A4 /* Sources */ = { 342 | isa = PBXSourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | 8047ABDD1A9FF411008093A4 /* PolymerTests.m in Sources */, 346 | ); 347 | runOnlyForDeploymentPostprocessing = 0; 348 | }; 349 | /* End PBXSourcesBuildPhase section */ 350 | 351 | /* Begin PBXTargetDependency section */ 352 | 8047ABD81A9FF411008093A4 /* PBXTargetDependency */ = { 353 | isa = PBXTargetDependency; 354 | target = 8047ABBC1A9FF411008093A4 /* Polymer */; 355 | targetProxy = 8047ABD71A9FF411008093A4 /* PBXContainerItemProxy */; 356 | }; 357 | /* End PBXTargetDependency section */ 358 | 359 | /* Begin PBXVariantGroup section */ 360 | 8047ABCA1A9FF411008093A4 /* Main.storyboard */ = { 361 | isa = PBXVariantGroup; 362 | children = ( 363 | 8047ABCB1A9FF411008093A4 /* Base */, 364 | ); 365 | name = Main.storyboard; 366 | sourceTree = ""; 367 | }; 368 | 8047ABCF1A9FF411008093A4 /* LaunchScreen.xib */ = { 369 | isa = PBXVariantGroup; 370 | children = ( 371 | 8047ABD01A9FF411008093A4 /* Base */, 372 | ); 373 | name = LaunchScreen.xib; 374 | sourceTree = ""; 375 | }; 376 | /* End PBXVariantGroup section */ 377 | 378 | /* Begin XCBuildConfiguration section */ 379 | 8047ABDE1A9FF411008093A4 /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 384 | CLANG_CXX_LIBRARY = "libc++"; 385 | CLANG_ENABLE_MODULES = YES; 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | CLANG_WARN_BOOL_CONVERSION = YES; 388 | CLANG_WARN_CONSTANT_CONVERSION = YES; 389 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 390 | CLANG_WARN_EMPTY_BODY = YES; 391 | CLANG_WARN_ENUM_CONVERSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 397 | COPY_PHASE_STRIP = NO; 398 | ENABLE_STRICT_OBJC_MSGSEND = YES; 399 | GCC_C_LANGUAGE_STANDARD = gnu99; 400 | GCC_DYNAMIC_NO_PIC = NO; 401 | GCC_OPTIMIZATION_LEVEL = 0; 402 | GCC_PREPROCESSOR_DEFINITIONS = ( 403 | "DEBUG=1", 404 | "$(inherited)", 405 | ); 406 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 407 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 408 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 409 | GCC_WARN_UNDECLARED_SELECTOR = YES; 410 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 411 | GCC_WARN_UNUSED_FUNCTION = YES; 412 | GCC_WARN_UNUSED_VARIABLE = YES; 413 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 414 | MTL_ENABLE_DEBUG_INFO = YES; 415 | ONLY_ACTIVE_ARCH = YES; 416 | SDKROOT = iphoneos; 417 | }; 418 | name = Debug; 419 | }; 420 | 8047ABDF1A9FF411008093A4 /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ALWAYS_SEARCH_USER_PATHS = NO; 424 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 425 | CLANG_CXX_LIBRARY = "libc++"; 426 | CLANG_ENABLE_MODULES = YES; 427 | CLANG_ENABLE_OBJC_ARC = YES; 428 | CLANG_WARN_BOOL_CONVERSION = YES; 429 | CLANG_WARN_CONSTANT_CONVERSION = YES; 430 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 431 | CLANG_WARN_EMPTY_BODY = YES; 432 | CLANG_WARN_ENUM_CONVERSION = YES; 433 | CLANG_WARN_INT_CONVERSION = YES; 434 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 435 | CLANG_WARN_UNREACHABLE_CODE = YES; 436 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 437 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 438 | COPY_PHASE_STRIP = YES; 439 | ENABLE_NS_ASSERTIONS = NO; 440 | ENABLE_STRICT_OBJC_MSGSEND = YES; 441 | GCC_C_LANGUAGE_STANDARD = gnu99; 442 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 443 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 444 | GCC_WARN_UNDECLARED_SELECTOR = YES; 445 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 446 | GCC_WARN_UNUSED_FUNCTION = YES; 447 | GCC_WARN_UNUSED_VARIABLE = YES; 448 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 449 | MTL_ENABLE_DEBUG_INFO = NO; 450 | SDKROOT = iphoneos; 451 | VALIDATE_PRODUCT = YES; 452 | }; 453 | name = Release; 454 | }; 455 | 8047ABE11A9FF411008093A4 /* Debug */ = { 456 | isa = XCBuildConfiguration; 457 | baseConfigurationReference = A0FBB3DBC2B8B49B82E248CB /* Pods.debug.xcconfig */; 458 | buildSettings = { 459 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 460 | INFOPLIST_FILE = Polymer/Info.plist; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | }; 464 | name = Debug; 465 | }; 466 | 8047ABE21A9FF411008093A4 /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | baseConfigurationReference = 3012B6C3FE5EB86E20D2425A /* Pods.release.xcconfig */; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | INFOPLIST_FILE = Polymer/Info.plist; 472 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | }; 475 | name = Release; 476 | }; 477 | 8047ABE41A9FF411008093A4 /* Debug */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | BUNDLE_LOADER = "$(TEST_HOST)"; 481 | FRAMEWORK_SEARCH_PATHS = ( 482 | "$(SDKROOT)/Developer/Library/Frameworks", 483 | "$(inherited)", 484 | ); 485 | GCC_PREPROCESSOR_DEFINITIONS = ( 486 | "DEBUG=1", 487 | "$(inherited)", 488 | ); 489 | INFOPLIST_FILE = PolymerTests/Info.plist; 490 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 491 | PRODUCT_NAME = "$(TARGET_NAME)"; 492 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Polymer.app/Polymer"; 493 | }; 494 | name = Debug; 495 | }; 496 | 8047ABE51A9FF411008093A4 /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | BUNDLE_LOADER = "$(TEST_HOST)"; 500 | FRAMEWORK_SEARCH_PATHS = ( 501 | "$(SDKROOT)/Developer/Library/Frameworks", 502 | "$(inherited)", 503 | ); 504 | INFOPLIST_FILE = PolymerTests/Info.plist; 505 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Polymer.app/Polymer"; 508 | }; 509 | name = Release; 510 | }; 511 | /* End XCBuildConfiguration section */ 512 | 513 | /* Begin XCConfigurationList section */ 514 | 8047ABB81A9FF411008093A4 /* Build configuration list for PBXProject "Polymer" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 8047ABDE1A9FF411008093A4 /* Debug */, 518 | 8047ABDF1A9FF411008093A4 /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | 8047ABE01A9FF411008093A4 /* Build configuration list for PBXNativeTarget "Polymer" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | 8047ABE11A9FF411008093A4 /* Debug */, 527 | 8047ABE21A9FF411008093A4 /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | 8047ABE31A9FF411008093A4 /* Build configuration list for PBXNativeTarget "PolymerTests" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | 8047ABE41A9FF411008093A4 /* Debug */, 536 | 8047ABE51A9FF411008093A4 /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | /* End XCConfigurationList section */ 542 | }; 543 | rootObject = 8047ABB51A9FF411008093A4 /* Project object */; 544 | } 545 | -------------------------------------------------------------------------------- /Polymer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Polymer.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Polymer/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/26/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Polymer/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/26/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Polymer/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Polymer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Polymer/Example/Models/SpotifyArtist.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyArtist.h 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SpotifyArtist : NSObject 13 | @property (strong, nonatomic) NSURL *externalSpotifyUrl; 14 | @property (nonatomic) NSInteger numberOfFollowers; 15 | @property (strong, nonatomic) NSArray *genres; 16 | @property (strong, nonatomic) NSURL *url; 17 | @property (copy, nonatomic) NSString *identifier; 18 | @property (strong, nonatomic) NSArray *images; 19 | @property (copy, nonatomic) NSString *name; 20 | @property (nonatomic) NSInteger popularity; 21 | @property (copy, nonatomic) NSString *type; 22 | @property (strong, nonatomic) NSURL *uri; 23 | @end 24 | -------------------------------------------------------------------------------- /Polymer/Example/Models/SpotifyArtist.m: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyArtist.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import "SpotifyArtist.h" 10 | 11 | @implementation SpotifyArtist 12 | + (NSDictionary *)mapping { 13 | NSMutableDictionary *mapping = [NSMutableDictionary dictionary]; 14 | mapping[@"externalSpotifyUrl"] = @"external_urls.spotify"; 15 | mapping[@"numberOfFollowers"] = @"followers.total"; 16 | mapping[@"genres"] = @"genres"; 17 | mapping[@"url"] = @"href"; 18 | mapping[@"identifier"] = @"id"; 19 | mapping[@"images@SpotifyImageRef"] = @"images"; 20 | mapping[@"name"] = @"name"; 21 | mapping[@"popularity"] = @"popularity"; 22 | mapping[@"type"] = @"type"; 23 | mapping[@"uri"] = @"uri"; 24 | return mapping; 25 | } 26 | @end 27 | -------------------------------------------------------------------------------- /Polymer/Example/Models/SpotifyImageRef.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyImageRef.h 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SpotifyImageRef : NSObject 13 | @property (nonatomic) NSInteger height; 14 | @property (nonatomic) NSInteger width; 15 | @property (copy, nonatomic) NSURL *url; 16 | @end 17 | -------------------------------------------------------------------------------- /Polymer/Example/Models/SpotifyImageRef.m: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyImageRef.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import "SpotifyImageRef.h" 10 | 11 | @implementation SpotifyImageRef 12 | + (NSDictionary *)mapping { 13 | NSMutableDictionary *mapping = [NSMutableDictionary dictionary]; 14 | mapping[@"height"] = @"height"; 15 | mapping[@"width"] = @"width"; 16 | mapping[@"url"] = @"url"; 17 | return mapping; 18 | } 19 | @end 20 | -------------------------------------------------------------------------------- /Polymer/Example/SpotifyEndpoints.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyEndpoints.h 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "PLYEndpoint.h" 11 | 12 | @interface SpotifyBaseEndpoint : PLYEndpoint 13 | @end 14 | 15 | @interface SpotifySearchEndpoint : SpotifyBaseEndpoint 16 | @end 17 | -------------------------------------------------------------------------------- /Polymer/Example/SpotifyEndpoints.m: -------------------------------------------------------------------------------- 1 | // 2 | // SpotifyEndpoints.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import "SpotifyEndpoints.h" 10 | #import "SpotifyArtist.h" 11 | 12 | @implementation SpotifyBaseEndpoint 13 | - (NSString *)baseUrl { 14 | return @"https://api.spotify.com/v1"; 15 | } 16 | @end 17 | 18 | @implementation SpotifySearchEndpoint 19 | - (Class)returnClass { 20 | return [SpotifyArtist class]; 21 | } 22 | - (NSString *)endpointUrl { 23 | return @"search"; 24 | } 25 | - (NSString *)responseKeyPath { 26 | return @"artists.items"; 27 | } 28 | @end -------------------------------------------------------------------------------- /Polymer/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Polymer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | io.loganwright.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Polymer/Source/PLYEndpoint.h: -------------------------------------------------------------------------------- 1 | // 2 | // PLYEndpoint.h 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/20/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AFHTTPRequestSerializer, AFHTTPResponseSerializer; 12 | @protocol AFURLRequestSerialization, AFURLResponseSerialization; 13 | 14 | /*! 15 | * Used to specify an argument that takes either an NSDictionary, or an NSArray 16 | */ 17 | @protocol PLYParameterEncodableType 18 | @end 19 | @interface NSDictionary () 20 | @end 21 | @interface NSArray () 22 | @end 23 | 24 | /*! 25 | * We're using this to specify our return type should be NSArray, NSDictionary, or NSString, explicitly. 26 | */ 27 | @protocol GenomeMappableRawType 28 | @end 29 | @interface NSArray () 30 | @end 31 | @interface NSDictionary () 32 | @end 33 | @interface NSString () 34 | @end 35 | 36 | @interface PLYEndpoint : NSObject 37 | 38 | #pragma mark - Config 39 | 40 | /*! 41 | * Use this to configure the base Url. An ideal architecture only overrides this in one base class and then each endpoint subclasses from there 42 | */ 43 | @property (nonatomic, readonly, copy) NSString *baseUrl; 44 | 45 | /*! 46 | * The endpoint url that will be appended to the end of the base url, or a complete URL 47 | * Example: @"/repos/:owner/:name/issues/:identifier" 48 | */ 49 | @property (nonatomic, readonly, copy) NSString *endpointUrl; 50 | 51 | /*! 52 | * The type of class that is returned from this endpoint. 53 | * 54 | * @warning Must conform to protocol JSONMappableObject 55 | */ 56 | @property (nonatomic, readonly) Class returnClass; 57 | 58 | /*! 59 | * The key to use when parsing a JSON response. 60 | */ 61 | @property (nonatomic, readonly, copy) NSString *responseKeyPath; 62 | 63 | #pragma mark - Initialization 64 | 65 | /*! 66 | * Initializes a new endpoint with an empty slug and an empty query parameters 67 | * 68 | * @return A new instance of the endpoint 69 | */ 70 | + (instancetype)endpoint; 71 | 72 | /*! 73 | * Initializes the endpoint with the given slug that should be used to populate the endpoint 74 | * 75 | * @param slug an object or a dictionary w/ values that directly correspond to the declared slugpaths within the endpoint 76 | * 77 | * @return a fully initialized endpoint 78 | */ 79 | + (instancetype)endpointWithSlug:(id)slug; 80 | 81 | /*! 82 | * Initializes the endpoint with the given slug that should be used to populate the endpoint 83 | * 84 | * @param slug an object or a dictionary w/ values that directly correspond to the declared slugpaths within the endpoint 85 | * 86 | * @return a fully initialized endpoint 87 | */ 88 | - (instancetype)initWithSlug:(id)slug; 89 | 90 | /*! 91 | * Initializes the endpoint with the given query parameters to populate the request 92 | * 93 | * @param parameters a PLYParameterEncodableType containing the parameters to send with the request 94 | * 95 | * @return a fully initialized endpoint 96 | */ 97 | + (instancetype)endpointWithParameters:(id)parameters; 98 | 99 | /*! 100 | * Initializes the endpoint with the given query parameters to populate the request 101 | * 102 | * @param parameters a PLYParameterEncodableType containing the parameters to send with the request 103 | * 104 | * @return a fully initialized endpoint 105 | */ 106 | - (instancetype)initWithParameters:(id)parameters; 107 | 108 | /*! 109 | * Initializes the endpoint with the given slug and query parameters 110 | * 111 | * @param slug an object or a dictionary w/ values that directly correspond to the declared slugpaths within the endpoint 112 | * @param parameters a PLYParameterEncodableType containing the parameters to send with the request 113 | * 114 | * @return a fully initialized endpoint 115 | */ 116 | + (instancetype)endpointWithSlug:(id)slug 117 | andParameters:(id)parameters; 118 | 119 | /*! 120 | * Initializes the endpoint with the given slug and query parameters 121 | * 122 | * @param slug an object or a dictionary w/ values that directly correspond to the declared slugpaths within the endpoint 123 | * @param parameters a PLYParameterEncodableType containing the parameters to send with the request 124 | * 125 | * @return a fully initialized endpoint 126 | */ 127 | - (instancetype)initWithSlug:(id)slug 128 | andParameters:(id)parameters NS_DESIGNATED_INITIALIZER; 129 | 130 | #pragma mark - Slug Mapping 131 | 132 | /*! 133 | * Use this to prevent a value from being set to a slug, for example an NSInteger identifier might be invalid if it has a value of 0, or less than one and shouldn't be appended to the url. 134 | * 135 | * @param value the value that will be injected into the url 136 | * @param slugPath the path that will be replaced 137 | * 138 | * @return whether or not to inject this value into the url at the given slug path, if NO, the path is not appended 139 | */ 140 | - (BOOL)valueIsValid:(id)value 141 | forSlugPath:(NSString *)slugPath; 142 | 143 | /*! 144 | * When populating the endpoint with a given slug, by default, the endpoint will call: `[slug valueForKeyPath:slugPath];`. You can use this to override that behavior and provide custom functionality that allows for multiple slug types to be used. 145 | * 146 | * @param slugPath the slug path that is being populated 147 | * @param slug the slug that is being used to populate the endpoint's slug paths 148 | * 149 | * @return the value to insert into the url, or nil if the slugpath should be removed from the url 150 | */ 151 | - (id)valueForSlugPath:(NSString *)slugPath 152 | withSlug:(id)slug; 153 | 154 | #pragma mark - Networking 155 | 156 | /*! 157 | * The content types that can be accepted 158 | */ 159 | @property (nonatomic, readonly) NSSet *acceptableContentTypes; 160 | 161 | /*! 162 | * The header fields to be added to the request 163 | */ 164 | @property (nonatomic, readonly) NSDictionary *headerFields; 165 | 166 | /*! 167 | * A custom request serializer 168 | */ 169 | @property (nonatomic, readonly) AFHTTPRequestSerializer *requestSerializer; 170 | 171 | /*! 172 | * A custom response serializer 173 | */ 174 | @property (nonatomic, readonly) AFHTTPResponseSerializer *responseSerializer; 175 | 176 | /*! 177 | * Network method to perform a get request for a given endpoint 178 | * 179 | * @param completion the return from the completion. Override the variable names in the completion block to suit the method to your needs, for example: 180 | * @code [ep getWithCompletion:(void(^)(MyModel *model, NSError *error) { 181 | // Handle response here. 182 | }]; 183 | // or 184 | [ep getWithCompletion:(void(^)(NSArray *models, NSError *error) { 185 | // Handle response here. 186 | }]; 187 | */ 188 | - (void)getWithCompletion:(void(^)(id object, NSError *error))completion; 189 | 190 | /*! 191 | * @see getWithCompletion: 192 | * 193 | * @param completion the return from the completion. Override the variable names in the completion block to suit the method to your needs, for example: 194 | */ 195 | - (void)putWithCompletion:(void(^)(id object, NSError *error))completion; 196 | 197 | /*! 198 | * @see getWithCompletion: 199 | * 200 | * @param completion the return from the completion. Override the variable names in the completion block to suit the method to your needs, for example: 201 | */ 202 | - (void)postWithCompletion:(void(^)(id object, NSError *error))completion; 203 | 204 | /*! 205 | * @see getWithCompletion: 206 | * 207 | * @param completion the return from the completion. Override the variable names in the completion block to suit the method to your needs, for example: 208 | */ 209 | - (void)patchWithCompletion:(void(^)(id object, NSError *error))completion; 210 | 211 | /*! 212 | * @see getWithCompletion: 213 | * 214 | * @param completion the return from the completion. Override the variable names in the completion block to suit the method to your needs, for example: 215 | */ 216 | - (void)deleteWithCompletion:(void(^)(id object, NSError *error))completion; 217 | 218 | /*! 219 | * When a response is received from an api, you can use this method to provide customized behavior. A common use ase for this is when using an XML api that can not be automatically converted to a dictionary. You can use this to transform the data to a mappable type. You can also implement customized functionality as necessary for specialized circumstances. 220 | * 221 | * @param response the response to map 222 | * 223 | * @return the value received from the response 224 | */ 225 | - (id)transformResponseToMappableRawType:(id)response; 226 | 227 | #pragma mark - Header Mapping 228 | 229 | /*! 230 | * In some cases, a header includes values that need to be appended to the model. For these situations, the header can be appended to the JSON mapping dictionary. If the response is a dictionary, an additional field will be added called 'Header', and values can be accessed via keypath syntax, ie: Header.etag. 231 | * 232 | * If the response is an array, it will be appended to the key "response" for mapping. 233 | * 234 | * The use case is when the header has values you want parsed into your model. For example a results page where the next, previous, and last page are included in the header. In these situations, you can use the keypath Header.Link, etc. in your mapping to map from the header. 235 | * 236 | * Use Header.headerValue when accessing header values in your object mapping 237 | * 238 | * Headers will be appended in the following format 239 | * @code // Array Responses 240 | @{ 241 | @"Header" : @{ 242 | @"headerKey" : @"headerVal" 243 | }, 244 | @"response" : @[] // array response 245 | } 246 | 247 | // Dictionary Responses 248 | @{ 249 | @"Header" : @{ 250 | @"headerKey" : @"headerVal" 251 | }, 252 | // The rest of the response appears here w/ top level keys as normal 253 | } 254 | */ 255 | @property (nonatomic, readonly) BOOL shouldAppendHeaderToResponse; 256 | 257 | @end 258 | -------------------------------------------------------------------------------- /Polymer/Source/PLYEndpoint.m: -------------------------------------------------------------------------------- 1 | // 2 | // PLYEndpoint.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/20/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import "PLYEndpoint.h" 10 | #import "PLYNetworking.h" 11 | #import 12 | 13 | static BOOL LOG = NO; 14 | 15 | @interface PLYEndpoint () 16 | @property (strong, nonatomic) id slug; 17 | @property (strong, nonatomic) id parameters; 18 | @property (nonatomic, readonly) NSString *populatedUrl; 19 | @end 20 | 21 | @implementation PLYEndpoint 22 | 23 | #pragma mark - Initialization 24 | 25 | + (instancetype)endpoint { 26 | return [self endpointWithSlug:nil andParameters:nil]; 27 | } 28 | 29 | + (instancetype)endpointWithSlug:(id)slug { 30 | return [self endpointWithSlug:slug andParameters:nil]; 31 | } 32 | 33 | - (instancetype)initWithSlug:(id)slug { 34 | return [self initWithSlug:slug andParameters:nil]; 35 | } 36 | 37 | + (instancetype)endpointWithParameters:(id)parameters { 38 | return [self endpointWithSlug:nil andParameters:parameters]; 39 | } 40 | 41 | - (instancetype)initWithParameters:(id)parameters { 42 | return [self initWithSlug:nil andParameters:parameters]; 43 | } 44 | 45 | + (instancetype)endpointWithSlug:(id)slug andParameters:(id)parameters { 46 | return [[self alloc] initWithSlug:slug andParameters:parameters]; 47 | } 48 | 49 | - (instancetype)initWithSlug:(id)slug andParameters:(id)parameters { 50 | self = [super init]; 51 | if (self) { 52 | _slug = slug; 53 | _parameters = parameters; 54 | [self assertValidImplementation]; 55 | } 56 | return self; 57 | } 58 | 59 | /*! 60 | * Use this space to run checks early that ensure an endpoint is valid before continuing. 61 | */ 62 | - (void)assertValidImplementation { 63 | NSAssert([self.returnClass conformsToProtocol:@protocol(GenomeObject)], 64 | @"ReturnClasses are required to conform to protocol JSONMappableObject : %@", 65 | NSStringFromClass(self.returnClass)); 66 | } 67 | 68 | #pragma mark - Url Assembly 69 | 70 | - (NSString *)populatedUrl { 71 | NSString *baseUrl = self.baseUrl; 72 | if ([baseUrl hasSuffix:@"/"]) { 73 | baseUrl = [baseUrl substringToIndex:baseUrl.length - 1]; 74 | } 75 | NSString *endpointUrl = [self populatedEndpointUrl]; 76 | return [NSString stringWithFormat:@"%@%@", baseUrl, endpointUrl]; 77 | } 78 | 79 | - (NSString *)populatedEndpointUrl { 80 | NSMutableString *url = [NSMutableString string]; 81 | NSArray *urlComponents = [self.endpointUrl componentsSeparatedByString:@"/"]; 82 | for (NSString *urlComponent in urlComponents) { 83 | if ([urlComponent hasPrefix:@":"]) { 84 | NSString *slugPath = [urlComponent substringFromIndex:1]; 85 | @try { 86 | id value = [self valueForSlugPath:slugPath 87 | withSlug:self.slug]; 88 | if ([self valueIsValid:value forSlugPath:slugPath]) { 89 | [url appendFormat:@"/%@", value]; 90 | } else if (LOG) { 91 | NSLog(@"Slug value %@ nil for keypath %@ : %@", 92 | value, NSStringFromClass([self.slug class]), slugPath); 93 | } 94 | } 95 | @catch (NSException *e) { 96 | // Just dumping the exception here -- Seek out a cleaner way to do this. 97 | if (LOG) { 98 | NSLog(@"No slug value found for keypath %@ : %@", 99 | NSStringFromClass([self.slug class]), slugPath); 100 | } 101 | } 102 | } else if (urlComponent.length > 0) { 103 | [url appendFormat:@"/%@", urlComponent]; 104 | } 105 | } 106 | return url; 107 | } 108 | 109 | - (BOOL)valueIsValid:(id)value 110 | forSlugPath:(NSString *)slugPath { 111 | // Provided here to be overridden if necessary. 112 | return (value != nil && ![value isEqual:[NSNull null]]); 113 | } 114 | 115 | - (id)valueForSlugPath:(NSString *)slugPath withSlug:(id)slug { 116 | // Default implementation, can be overridden. 117 | return [slug valueForKeyPath:slugPath]; 118 | } 119 | 120 | #pragma mark - URL Component Overrides 121 | 122 | /* 123 | These values are intended to be overridden in a subclass! 124 | */ 125 | 126 | - (NSString *)baseUrl { 127 | NSString *reason = [NSString stringWithFormat:@"Must be overriden by subclass! %@", 128 | NSStringFromClass([self class])]; 129 | @throw [NSException exceptionWithName:@"BaseUrl not implemented" 130 | reason:reason 131 | userInfo:nil]; 132 | } 133 | 134 | - (NSString *)endpointUrl { 135 | NSString *reason = [NSString stringWithFormat:@"Must be overriden by subclass! %@", 136 | NSStringFromClass([self class])]; 137 | @throw [NSException exceptionWithName:@"EndpointUrl not implemented" 138 | reason:reason 139 | userInfo:nil]; 140 | } 141 | 142 | - (Class)returnClass { 143 | NSString *reason = [NSString stringWithFormat:@"Must be overriden by subclass! %@", 144 | NSStringFromClass([self class])]; 145 | @throw [NSException exceptionWithName:@"ReturnClass not implemented" 146 | reason:reason 147 | userInfo:nil]; 148 | } 149 | 150 | #pragma mark - Networking Configuration 151 | 152 | /* 153 | These are intended to be overridden by an endpoint if it has values that need to be added 154 | */ 155 | - (NSSet *)acceptableContentTypes { 156 | return nil; 157 | } 158 | 159 | - (NSDictionary *)headerFields { 160 | return nil; 161 | } 162 | 163 | - (AFHTTPRequestSerializer *)requestSerializer { 164 | return nil; 165 | } 166 | 167 | - (AFHTTPResponseSerializer *)responseSerializer { 168 | return nil; 169 | } 170 | 171 | #pragma mark - HTTP Calls 172 | 173 | - (void)getWithCompletion:(void(^)(id object, NSError *error))completion { 174 | [PLYNetworking getForEndpoint:self withCompletion:completion]; 175 | } 176 | 177 | - (void)putWithCompletion:(void(^)(id object, NSError *error))completion { 178 | [PLYNetworking putForEndpoint:self withCompletion:completion]; 179 | } 180 | 181 | - (void)postWithCompletion:(void(^)(id object, NSError *error))completion { 182 | [PLYNetworking postForEndpoint:self withCompletion:completion]; 183 | } 184 | 185 | - (void)patchWithCompletion:(void(^)(id object, NSError *error))completion { 186 | [PLYNetworking patchForEndpoint:self withCompletion:completion]; 187 | } 188 | 189 | - (void)deleteWithCompletion:(void(^)(id object, NSError *error))completion { 190 | [PLYNetworking deleteForEndpoint:self withCompletion:completion]; 191 | } 192 | 193 | #pragma mark - Response Data Transformer 194 | 195 | - (id)transformResponseToMappableRawType:(id)response { 196 | if (LOG) { 197 | NSLog(@"Transforming response: %@ for endpoint : %@", response, [self class]); 198 | } 199 | 200 | id responseObject; 201 | if ([response isKindOfClass:[NSData class]]) { 202 | NSData *responseData = response; 203 | /* 204 | This is the default transformer that attempts to handle when data is received from a url. Override this for custom behavior. 205 | */ 206 | NSError *err; 207 | id jsonResponse = [NSJSONSerialization JSONObjectWithData:responseData 208 | options:NSJSONReadingAllowFragments 209 | error:&err]; 210 | if (jsonResponse && !err) { 211 | responseObject = jsonResponse; 212 | } else { 213 | NSString *string = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; 214 | if (string) { 215 | responseObject = string; 216 | } 217 | } 218 | } else { 219 | return responseObject = response; 220 | } 221 | return responseObject; 222 | } 223 | 224 | #pragma mark - Header Mapping 225 | 226 | - (BOOL)shouldAppendHeaderToResponse { 227 | return NO; 228 | } 229 | 230 | @end 231 | 232 | -------------------------------------------------------------------------------- /Polymer/Source/PLYNetworking.h: -------------------------------------------------------------------------------- 1 | // 2 | // PLYNetworking.h 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/20/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class PLYEndpoint; 12 | 13 | @interface PLYNetworking : NSObject 14 | 15 | + (void)getForEndpoint:(PLYEndpoint *)endpoint 16 | withCompletion:(void(^)(id object, NSError *error))completion; 17 | 18 | + (void)putForEndpoint:(PLYEndpoint *)endpoint 19 | withCompletion:(void(^)(id object, NSError *error))completion; 20 | 21 | + (void)postForEndpoint:(PLYEndpoint *)endpoint 22 | withCompletion:(void(^)(id object, NSError *error))completion; 23 | 24 | + (void)patchForEndpoint:(PLYEndpoint *)endpoint 25 | withCompletion:(void(^)(id object, NSError *error))completion; 26 | 27 | + (void)deleteForEndpoint:(PLYEndpoint *)endpoint 28 | withCompletion:(void(^)(id object, NSError *error))completion; 29 | 30 | @end 31 | 32 | -------------------------------------------------------------------------------- /Polymer/Source/PLYNetworking.m: -------------------------------------------------------------------------------- 1 | // 2 | // PLYNetworking.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/20/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import "PLYNetworking.h" 10 | #import "PLYEndpoint.h" 11 | #import 12 | #import 13 | 14 | static BOOL LOG = NO; 15 | 16 | @interface PLYEndpoint () 17 | @property (strong, nonatomic) id slug; 18 | @property (strong, nonatomic) NSDictionary *parameters; 19 | @property (nonatomic, readonly) NSString *populatedUrl; 20 | @end 21 | 22 | @implementation PLYNetworking 23 | 24 | #pragma mark - Configuration 25 | 26 | + (AFHTTPRequestOperationManager *)operationManagerWithEndpoint:(PLYEndpoint *)endpoint { 27 | 28 | AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; 29 | 30 | AFHTTPRequestSerializer *requestSerializer = endpoint.requestSerializer; 31 | if (requestSerializer) { 32 | manager.requestSerializer = requestSerializer; 33 | } 34 | AFHTTPResponseSerializer *responseSerializer = endpoint.responseSerializer; 35 | if (responseSerializer) { 36 | manager.responseSerializer = responseSerializer; 37 | } 38 | 39 | NSSet *acceptableContentTypes = endpoint.acceptableContentTypes; 40 | if (acceptableContentTypes) { 41 | manager.responseSerializer.acceptableContentTypes = acceptableContentTypes; 42 | } 43 | 44 | NSDictionary *headerFields = endpoint.headerFields; 45 | for (NSString *headerKey in headerFields.allKeys) { 46 | id headerValue = headerFields[headerKey]; 47 | [manager.requestSerializer setValue:headerValue forHTTPHeaderField:headerKey]; 48 | } 49 | 50 | return manager; 51 | } 52 | 53 | #pragma mark - Interaction 54 | 55 | + (void)getForEndpoint:(PLYEndpoint *)endpoint 56 | withCompletion:(void(^)(id object, NSError *error))completion { 57 | AFHTTPRequestOperationManager *manager = [self operationManagerWithEndpoint:endpoint]; 58 | af_networkSuccessBlock success = successBlock(endpoint, completion); 59 | af_networkFailureBlock failure = failureBlock(endpoint, completion); 60 | [manager GET:endpoint.populatedUrl 61 | parameters:endpoint.parameters 62 | success:success 63 | failure:failure]; 64 | } 65 | 66 | + (void)putForEndpoint:(PLYEndpoint *)endpoint 67 | withCompletion:(void(^)(id object, NSError *error))completion { 68 | AFHTTPRequestOperationManager *manager = [self operationManagerWithEndpoint:endpoint]; 69 | af_networkSuccessBlock success = successBlock(endpoint, completion); 70 | af_networkFailureBlock failure = failureBlock(endpoint, completion); 71 | [manager PUT:endpoint.populatedUrl 72 | parameters:endpoint.parameters 73 | success:success 74 | failure:failure]; 75 | } 76 | 77 | + (void)postForEndpoint:(PLYEndpoint *)endpoint 78 | withCompletion:(void(^)(id object, NSError *error))completion { 79 | AFHTTPRequestOperationManager *manager = [self operationManagerWithEndpoint:endpoint]; 80 | af_networkSuccessBlock success = successBlock(endpoint, completion); 81 | af_networkFailureBlock failure = failureBlock(endpoint, completion); 82 | [manager POST:endpoint.populatedUrl 83 | parameters:endpoint.parameters 84 | success:success 85 | failure:failure]; 86 | } 87 | 88 | + (void)patchForEndpoint:(PLYEndpoint *)endpoint 89 | withCompletion:(void(^)(id object, NSError *error))completion { 90 | AFHTTPRequestOperationManager *manager = [self operationManagerWithEndpoint:endpoint]; 91 | af_networkSuccessBlock success = successBlock(endpoint, completion); 92 | af_networkFailureBlock failure = failureBlock(endpoint, completion); 93 | [manager PATCH:endpoint.populatedUrl 94 | parameters:endpoint.parameters 95 | success:success 96 | failure:failure]; 97 | } 98 | 99 | + (void)deleteForEndpoint:(PLYEndpoint *)endpoint 100 | withCompletion:(void(^)(id object, NSError *error))completion { 101 | AFHTTPRequestOperationManager *manager = [self operationManagerWithEndpoint:endpoint]; 102 | af_networkSuccessBlock success = successBlock(endpoint, completion); 103 | af_networkFailureBlock failure = failureBlock(endpoint, completion); 104 | [manager DELETE:endpoint.populatedUrl 105 | parameters:endpoint.parameters 106 | success:success 107 | failure:failure]; 108 | } 109 | 110 | #pragma mark - Success | Failure Blocks 111 | 112 | typedef void(^af_networkSuccessBlock)(AFHTTPRequestOperation *operation, id responseObject); 113 | typedef void(^af_networkFailureBlock)(AFHTTPRequestOperation *operation, NSError *error); 114 | typedef void(^dv_responseBlock)(id object, NSError *error); 115 | 116 | af_networkSuccessBlock successBlock(PLYEndpoint *endpoint, dv_responseBlock completion) { 117 | /* 118 | This area could be cleaned up a bit. Functional. 119 | */ 120 | return ^(AFHTTPRequestOperation *operation, id responseObject) { 121 | 122 | responseObject = [endpoint transformResponseToMappableRawType:responseObject]; 123 | 124 | if (endpoint.responseKeyPath && [responseObject isKindOfClass:[NSDictionary class]]) { 125 | if (LOG) { 126 | NSLog(@"Using responseKey: %@ forEndpoint: %@", 127 | endpoint.responseKeyPath, NSStringFromClass([endpoint class])); 128 | } 129 | responseObject = [responseObject valueForKeyPath:endpoint.responseKeyPath]; 130 | } else if (endpoint.responseKeyPath && LOG) { 131 | NSLog(@"Response key %@ not valid for response type %@", 132 | endpoint.responseKeyPath, [responseObject class]); 133 | } 134 | 135 | if (endpoint.shouldAppendHeaderToResponse) { 136 | if (LOG) { 137 | NSLog(@"Appending header to response for endpoint: %@", [endpoint class]); 138 | } 139 | 140 | if ([responseObject isKindOfClass:[NSArray class]]) { 141 | responseObject = @{@"Header" : operation.response.allHeaderFields, 142 | @"response" : responseObject}; 143 | } else if ([responseObject isKindOfClass:[NSDictionary class]]) { 144 | NSMutableDictionary *mutableResponse = [responseObject mutableCopy]; 145 | mutableResponse[@"Header"] = operation.response.allHeaderFields; 146 | responseObject = mutableResponse; 147 | } else if ([responseObject isKindOfClass:[NSString class]]) { 148 | NSMutableDictionary *dictionaryRep = [parameterStringToDictionary(responseObject) mutableCopy]; 149 | if (dictionaryRep) { 150 | dictionaryRep[@"Header"] = operation.response.allHeaderFields; 151 | responseObject = dictionaryRep; 152 | } else { 153 | if (LOG) { 154 | NSLog(@"Unable to parse response string into dictionary representation! Can't append header values"); 155 | } 156 | } 157 | } else if (LOG) { 158 | NSLog(@"Received response of unknown type to append header: %@ : RESPONSE : %@", 159 | [responseObject class], responseObject); 160 | } 161 | } 162 | 163 | if ([responseObject isKindOfClass:[NSDictionary class]]) { 164 | id object = [endpoint.returnClass gm_mappedObjectWithJsonRepresentation:responseObject 165 | inResponseContext:responseObject]; 166 | completion(object, nil); 167 | } else if ([responseObject isKindOfClass:[NSArray class]]) { 168 | NSArray *objects = [responseObject gm_mapToGenomeObjectClass:endpoint.returnClass 169 | inResponseContext:responseObject]; 170 | completion(objects, nil); 171 | } else if ([responseObject isKindOfClass:[NSString class]]) { 172 | NSDictionary *dictionaryRepresentation = parameterStringToDictionary(responseObject); 173 | if (dictionaryRepresentation) { 174 | id object = [endpoint.returnClass gm_mappedObjectWithJsonRepresentation:dictionaryRepresentation 175 | inResponseContext:responseObject]; 176 | completion(object, nil); 177 | } else { 178 | if (LOG) { 179 | /* 180 | Strings should follow the syntax key=value&anotherKey=anotherValue to be mapped properly 181 | */ 182 | NSLog(@"Unable to proccess string: %@ into dictionary", responseObject); 183 | } 184 | completion(responseObject, nil); 185 | } 186 | } else { 187 | if (LOG) { 188 | NSLog(@"Received response of unknown type: %@ : RESPONSE : %@", 189 | [responseObject class], responseObject); 190 | } 191 | completion(responseObject, nil); 192 | } 193 | 194 | }; 195 | } 196 | 197 | af_networkFailureBlock failureBlock(PLYEndpoint *endpoint, dv_responseBlock completion) { 198 | return ^(AFHTTPRequestOperation *operation, NSError *error) { 199 | completion(nil, error); 200 | }; 201 | } 202 | 203 | #pragma mark - Conversion Helpers 204 | 205 | NSDictionary *parameterStringToDictionary(NSString *parameterString) { 206 | parameterString = [parameterString stringByRemovingPercentEncoding]; 207 | NSArray *params = [parameterString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"&="]]; 208 | if ((params.count % 2) != 0) { 209 | return nil; 210 | } else { 211 | NSMutableDictionary *dictionaryRepresentation = [NSMutableDictionary dictionary]; 212 | for (int i = 0; i < params.count; i += 2) { 213 | dictionaryRepresentation[params[i]] = params[i + 1]; 214 | } 215 | return dictionaryRepresentation; 216 | } 217 | } 218 | 219 | @end 220 | -------------------------------------------------------------------------------- /Polymer/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Polymer/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/23/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | // Endpoints Page 12 | #import "SpotifyEndpoints.h" 13 | 14 | // Models 15 | #import "SpotifyArtist.h" 16 | #import "SpotifyImageRef.h" 17 | 18 | @interface ViewController () 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | // Do any additional setup after loading the view, typically from a nib. 27 | 28 | PLYEndpoint *ep = [SpotifySearchEndpoint endpointWithParameters:@{@"q" : @"beyonce", @"type" : @"artist"}]; 29 | [ep getWithCompletion:^(NSArray *artists, NSError *error) { 30 | NSLog(@"Got artists: %@ w/ error: %@", artists, error); 31 | }]; 32 | } 33 | 34 | - (void)didReceiveMemoryWarning { 35 | [super didReceiveMemoryWarning]; 36 | // Dispose of any resources that can be recreated. 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Polymer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Polymer 4 | // 5 | // Created by Logan Wright on 2/26/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PolymerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | io.loganwright.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PolymerTests/PolymerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PolymerTests.m 3 | // PolymerTests 4 | // 5 | // Created by Logan Wright on 2/26/15. 6 | // Copyright (c) 2015 LowriDevs. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface PolymerTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation PolymerTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Polymer 2 | 3 | Polymer is an endpoint focused networking library for Objective-C and Swift that is meant to make interaction with REST webservices simple, fast, and fun! By treating the endpoints of a webservice as objects, it makes interaction more straightforward and readable while still leveraging and simplifying some of the amazing mapping technologies we've grown used to in consuming apis. 4 | 5 | The goal of this library is to be as minimalistic as possible while providing maximum customization. This is achieved by making transparent methods that can be easily overridden when necessary to handle edge cases and customize the behavior of an endpoint. 6 | 7 | ###Genome 8 | 9 | Polymer features the mapping library Genome 10 | 11 | ###AFNetworking 12 | 13 | Polymer relies on AFNetworking for its network operations 14 | 15 | --- 16 | 17 | 18 | [![Version](https://img.shields.io/cocoapods/v/Polymer.svg?style=flat)](http://cocoapods.org/pods/Polymer) 19 | [![License](https://img.shields.io/cocoapods/l/Polymer.svg?style=flat)](http://cocoapods.org/pods/Polymer) 20 | [![Platform](https://img.shields.io/cocoapods/p/Polymer.svg?style=flat)](http://cocoapods.org/pods/Polymer) 21 | 22 | --- 23 | 24 | Documentation 25 |
26 | Initial Setup 27 |
28 | Getting Started Guide -- Spotify Search 29 |
30 |       Models 31 |
32 |       Endpoints 33 |
34 |       Use 35 |
36 | Endpoints 37 |
38 |       Base Endpoint 39 |
40 |             Base Url 41 |
42 |             Header Fields 43 |
44 |             Acceptable Content Types 45 |
46 |       Individual Endpoint 47 |
48 |             Return Class 49 |
50 |             Endpoint Url 51 |
52 |                   Slug Mapping 53 |
54 |             Response Key Path 55 |
56 |             Serializers 57 |
58 |                   Response Serializer 59 |
60 |                   Request Serializer 61 |
62 |             Append Header 63 |
64 |       Transform Response 65 |
66 | Networking - Examples 67 |
68 |       GET 69 |
70 |       POST 71 |
72 |       PUT 73 |
74 |       PATCH 75 |
76 |       DELETE 77 | 78 | --- 79 | 80 | #Initial Setup 81 | 82 | If you wish to install the library manually, you'll need to also include AFNetworking and Genome 83 | 84 | It is highly recommended that you install Polymer through cocoapods. Here is a personal cocoapods reference just in case it may be of use: Cocoapods Setup Guide 85 | 86 | Podfile: `pod 'Polymer'` 87 |
Import: `#import ` 88 | 89 | #Getting Started 90 | 91 | The best way to describe Polymer is to show how it is used. Let's query some artists from the spotify web api. Here's an example response from this endpoint. 92 | 93 | ```JSON 94 | { 95 | "artists" : { 96 | "href" : "https://api.spotify.com/v1/search?query=tania+bowra&offset=0&limit=20&type=artist", 97 | "items" : [ { 98 | "external_urls" : { 99 | "spotify" : "https://open.spotify.com/artist/08td7MxkoHQkXnWAYD8d6Q" 100 | }, 101 | "followers" : { 102 | "href" : null, 103 | "total" : 12 104 | }, 105 | "genres" : [ ], 106 | "href" : "https://api.spotify.com/v1/artists/08td7MxkoHQkXnWAYD8d6Q", 107 | "id" : "08td7MxkoHQkXnWAYD8d6Q", 108 | "images" : [ { 109 | "height" : 640, 110 | "url" : "https://i.scdn.co/image/f2798ddab0c7b76dc2d270b65c4f67ddef7f6718", 111 | "width" : 640 112 | }, { 113 | "height" : 300, 114 | "url" : "https://i.scdn.co/image/b414091165ea0f4172089c2fc67bb35aa37cfc55", 115 | "width" : 300 116 | }, { 117 | "height" : 64, 118 | "url" : "https://i.scdn.co/image/8522fc78be4bf4e83fea8e67bb742e7d3dfe21b4", 119 | "width" : 64 120 | } ], 121 | "name" : "Tania Bowra", 122 | "popularity" : 4, 123 | "type" : "artist", 124 | "uri" : "spotify:artist:08td7MxkoHQkXnWAYD8d6Q" 125 | } ], 126 | "limit" : 20, 127 | "next" : null, 128 | "offset" : 0, 129 | "previous" : null, 130 | "total" : 1 131 | } 132 | } 133 | ``` 134 | 135 | For our example, the only thing we really care about is the artist objects located at the keypath `artists.items`. We will use this keypath later. First, let's isolate our artist object for mapping, it looks like this: 136 | 137 | #####SpotifyArtist Json Representation 138 | 139 | ```JSON 140 | { 141 | "external_urls" : { 142 | "spotify" : "https://open.spotify.com/artist/08td7MxkoHQkXnWAYD8d6Q" 143 | }, 144 | "followers" : { 145 | "href" : null, 146 | "total" : 12 147 | }, 148 | "genres" : [ ], 149 | "href" : "https://api.spotify.com/v1/artists/08td7MxkoHQkXnWAYD8d6Q", 150 | "id" : "08td7MxkoHQkXnWAYD8d6Q", 151 | "images" : [ { 152 | "height" : 640, 153 | "url" : "https://i.scdn.co/image/f2798ddab0c7b76dc2d270b65c4f67ddef7f6718", 154 | "width" : 640 155 | }, { 156 | "height" : 300, 157 | "url" : "https://i.scdn.co/image/b414091165ea0f4172089c2fc67bb35aa37cfc55", 158 | "width" : 300 159 | }, { 160 | "height" : 64, 161 | "url" : "https://i.scdn.co/image/8522fc78be4bf4e83fea8e67bb742e7d3dfe21b4", 162 | "width" : 64 163 | } ], 164 | "name" : "Tania Bowra", 165 | "popularity" : 4, 166 | "type" : "artist", 167 | "uri" : "spotify:artist:08td7MxkoHQkXnWAYD8d6Q" 168 | } 169 | ``` 170 | 171 | Let's how it would look modeled as an ObjC object. 172 | 173 | ####Spotify Models 174 | 175 | #####SpotifyArtist Model 176 | 177 | The first thing we need to do is create our object and make sure it conforms to `GenomeMappableObject` protocol. Here's how our object looks now. 178 | 179 | `SpotifyArtist.h` 180 | 181 | ```ObjC 182 | #import 183 | #import 184 | 185 | @interface SpotifyArtist : NSObject 186 | @end 187 | ``` 188 | 189 | Now let's fill in the properties that map to the JSON. Our final model header will look like this: 190 | 191 | `SpotifyArtist.h` 192 | 193 | ```ObjC 194 | #import 195 | #import 196 | 197 | @interface SpotifyArtist : NSObject 198 | @property (strong, nonatomic) NSURL *externalSpotifyUrl; 199 | @property (nonatomic) NSInteger numberOfFollowers; 200 | @property (strong, nonatomic) NSArray *genres; 201 | @property (strong, nonatomic) NSURL *url; 202 | @property (copy, nonatomic) NSString *identifier; 203 | @property (strong, nonatomic) NSArray *images; 204 | @property (copy, nonatomic) NSString *name; 205 | @property (nonatomic) NSInteger popularity; 206 | @property (copy, nonatomic) NSString *type; 207 | @property (strong, nonatomic) NSURL *uri; 208 | @end 209 | ``` 210 | 211 | `GenomeMappableObject` protocol requires implementing an instance method that is called `mapping` and returns an `NSMutableDictionary`. This will be used under the hood when converting the JSON response to model objects. Modelling supports the following syntax: 212 | 213 | ```ObjC 214 | mapping[@"<#propertyName#>"] = @"<#associatedJsonKeyPath#>"; 215 | ``` 216 | 217 | This operation tries to be smart and if you have a property that is a class that also corresponds to a `GenomeObject`, it will be mapped automatically. If your property is an array of `GenomeObject`s, the type needs to be declared explicitly since this can't be discovered through introspection. To do this, you use the following syntax: 218 | 219 | ```ObjC 220 | mapping[@"<#arrayPropertyName#>@<#ClassName#>"] = @"<#associatedJsonKeyPath#>"; 221 | ``` 222 | 223 | The `@` syntax is an important feature of Genome and it will be included quite often. If you would like to be a bit more type safe, you can use this convenience function to declare your keys: 224 | 225 | ```ObjC 226 | propertyMap(@"<#propertyName#>", [<#classType#> class]) 227 | ``` 228 | 229 | This is a bit safer way to map so that if you refactor your class names, you don't need to do a project search to replace your key mappings. 230 | 231 | This syntax can also be used to declare a `GenomeTransformer` to go along with the class. More on that later. Let's look at our mapping for `SpotifyArtist` 232 | 233 | `SpotifyArtist.m` 234 | 235 | ```ObjC 236 | #import "SpotifyArtist.h" 237 | 238 | @implementation SpotifyArtist 239 | + (NSDictionary *)mapping { 240 | NSMutableDictionary *mapping = [NSMutableDictionary dictionary]; 241 | // Note keypaths in associated JSON 242 | mapping[@"externalSpotifyUrl"] = @"external_urls.spotify"; 243 | mapping[@"numberOfFollowers"] = @"followers.total"; 244 | mapping[@"genres"] = @"genres"; 245 | mapping[@"url"] = @"href"; 246 | mapping[@"identifier"] = @"id"; 247 | // Note array type specification 248 | mapping[@"images@SpotifyImageRef"] = @"images"; 249 | mapping[@"name"] = @"name"; 250 | mapping[@"popularity"] = @"popularity"; 251 | mapping[@"type"] = @"type"; 252 | mapping[@"uri"] = @"uri"; 253 | return mapping; 254 | } 255 | @end 256 | ``` 257 | 258 | As you can see above, our `images` property is an array and we are mapping its contents to `SpotifyImageRef` models that we haven't created yet. Let's look at the json contained at the `images` key: 259 | 260 | ```JSON 261 | "images" : [ { 262 | "height" : 640, 263 | "url" : "https://i.scdn.co/image/f2798ddab0c7b76dc2d270b65c4f67ddef7f6718", 264 | "width" : 640 265 | }, { 266 | "height" : 300, 267 | "url" : "https://i.scdn.co/image/b414091165ea0f4172089c2fc67bb35aa37cfc55", 268 | "width" : 300 269 | }, { 270 | "height" : 64, 271 | "url" : "https://i.scdn.co/image/8522fc78be4bf4e83fea8e67bb742e7d3dfe21b4", 272 | "width" : 64 273 | } ] 274 | ``` 275 | 276 | Let's create a model for the individual objects that looks like this: 277 | 278 | #####SpotifyImageRef 279 | 280 | `SpotifyImageRef.h` 281 | 282 | ```ObjC 283 | #import 284 | #import 285 | 286 | @interface SpotifyImageRef : NSObject 287 | @property (nonatomic) NSInteger height; 288 | @property (nonatomic) NSInteger width; 289 | @property (copy, nonatomic) NSURL *url; 290 | @end 291 | ``` 292 | 293 | Note: You can declare `GenomeObject` protocol in the implementation file if you prefer. This is more clear for examples. 294 | 295 | `SpotifyImageRef.m` 296 | 297 | ```ObjC 298 | #import "SpotifyImageRef.h" 299 | 300 | @implementation SpotifyImageRef 301 | + (NSDictionary *)mapping { 302 | NSMutableDictionary *mapping = [NSMutableDictionary dictionary]; 303 | mapping[@"height"] = @"height"; 304 | mapping[@"width"] = @"width"; 305 | mapping[@"url"] = @"url"; 306 | return mapping; 307 | } 308 | @end 309 | ``` 310 | 311 | This is a pretty straightforward object and our property names correspond directly with the JSON. As of now, it is still necessary to declare these properties in your mapping. This is done to allow absolute control over the operation. 312 | 313 | That's it, our models are all set up, now we need to set up our endpoints for the spotify api. 314 | 315 | ####Spotify Endpoints 316 | 317 | I prefer endpoints declared in a single file because it prevents having to add additional imports when endpoints are added and a lot of them end up being interdependent. 318 | 319 | In your endpoints file, import 320 | 321 | `SpotifyEndpoints.h` 322 | 323 | ```ObjC 324 | #import 325 | #import 326 | ``` 327 | 328 | The first thing I'm going to do is declare a base endpoint. This is done to provide the base Url and any other request configurations you want for a given api. 329 | 330 | ```ObjC 331 | #import 332 | #import 333 | 334 | @interface SpotifyBaseEndpoint : PLYEndpoint 335 | @end 336 | 337 | ``` 338 | 339 | Now let's look at the implementation: 340 | 341 | `SpotifyEndpoints.m` 342 | 343 | ```ObjC 344 | #import "SpotifyEndpoints.h" 345 | #import "SpotifyArtist.h" 346 | 347 | @implementation SpotifyBaseEndpoint 348 | - (NSString *)baseUrl { 349 | return @"https://api.spotify.com/v1"; 350 | } 351 | @end 352 | ``` 353 | 354 | Spotify is a modern and clean api, and most characteristics are able to be inferred quite easily, if you would like more control over your base endpoint, you can create something more complex by adding more method overrides. A more specified API might look something like this: 355 | 356 | ```ObjC 357 | @implementation GHBaseEndpoint 358 | - (NSSet *)acceptableContentTypes { 359 | return [NSSet setWithObjects:@"text/html", @"application/json", nil]; 360 | } 361 | 362 | - (AFHTTPRequestSerializer *)requestSerializer { 363 | return [AFJSONRequestSerializer serializer]; 364 | } 365 | 366 | - (NSDictionary *)headerFields { 367 | NSMutableDictionary *headerFields = [NSMutableDictionary dictionary]; 368 | headerFields[@"Accept"] = @"application/vnd.github.v3+json"; 369 | 370 | NSString *token = [storage accessToken]; 371 | if (token) { 372 | NSString *tokenHeader = [NSString stringWithFormat:@"Token %@", token]; 373 | headerFields[GHNetworkingHeaderKeyAuthorization] = tokenHeader; 374 | } 375 | 376 | return headerFields; 377 | } 378 | 379 | - (NSString *)baseUrl { 380 | return @"https://api.github.com"; 381 | } 382 | @end 383 | ``` 384 | 385 | Ok, now back to Spotify. It's time to add a new endpoint for our search. This endpoint, and all future endpoints desiring this base url will subclass our spotify base endpoint. 386 | 387 | Here's how our endpoints file looks after adding the search endpoint: 388 | 389 | `SpotifyEndpoints.h` 390 | 391 | ```ObjC 392 | #import 393 | #import 394 | 395 | @interface SpotifyBaseEndpoint : PLYEndpoint 396 | @end 397 | 398 | // Note subclass 399 | @interface SpotifySearchEndpoint : SpotifyBaseEndpoint 400 | @end 401 | ``` 402 | 403 | `SpotifyEndpoints.m` 404 | 405 | ```ObjC 406 | #import "SpotifyEndpoints.h" 407 | #import "SpotifyArtist.h" 408 | 409 | @implementation SpotifyBaseEndpoint 410 | - (NSString *)baseUrl { 411 | return @"https://api.spotify.com/v1"; 412 | } 413 | @end 414 | 415 | @implementation SpotifySearchEndpoint 416 | - (Class)returnClass { 417 | return [SpotifyArtist class]; 418 | } 419 | - (NSString *)endpointUrl { 420 | return @"search"; 421 | } 422 | - (NSString *)responseKeyPath { 423 | return @"artists.items"; 424 | } 425 | @end 426 | ``` 427 | 428 | An endpoint meant for use implements at minimum 3 methods. `baseUrl`, `endpointUrl`, and `returnClass`. In `SpotifySearchEndpoint` above, you'll notice that `baseUrl` isn't overridden. This is because it subclasses from `SpotifyBaseEndpoint` which overrides the `baseUrl`. All future subclasses can inherit this base. 429 | 430 | `baseUrl` - The base url for the api. What the endpoints will be appended to. 431 | 432 | `endpointUrl` - The url for the endpoint. You can declare a more advanced endpoint by prefixing slugs w/ a colon `:`. These can be smartly mapped from objects to generate endpoints. (more on slug mapping later). 433 | 434 | `responseKeyPath` - As we specified at the beginning, this is a simple example and we don't need all of the information from the response. We only want the array of artists located at the key path `artists.items`. By declaring this in our endpoint, we're telling it. Fetch items from url endpoint `search`, then from the response, get the object at keypath `artists.items`. Then map the objects within that response to type `SpotifyArtist`. 435 | 436 | That's it, we're ready to use the search api! 437 | 438 | ####Use! 439 | 440 | Everything is set up, let's get some objects down from the server! At minimum, the spotify search endpoint requires two parameters, query : `q` and type `artist`, `album`, or `track`. For our example, we're querying artists, so we'll have that for our type. Now we initialize our endpoint and call get. 441 | 442 | ```ObjC 443 | PLYEndpoint *ep = [SpotifySearchEndpoint endpointWithParameters:@{@"q" : @"beyonce", @"type" : @"artist"}]; 444 | [ep getWithCompletion:^(id object, NSError *error) { 445 | NSArray *artists = (NSArray *)object; 446 | NSLog(@"Got artists: %@ w/ error: %@", artists, error); 447 | }]; 448 | ``` 449 | 450 | Because objective-c allows flexibility in type casting, we can skip the cast in the above example and replace our object with its type explicitly. 451 | 452 | ```ObjC 453 | PLYEndpoint *ep = [SpotifySearchEndpoint endpointWithParameters:@{@"q" : @"beyonce", @"type" : @"artist"}]; 454 | [ep getWithCompletion:^(NSArray *artists, NSError *error) { 455 | NSLog(@"Got artists: %@ w/ error: %@", artists, error); 456 | }]; 457 | ``` 458 | 459 | This can also be done to individual model objects, not just `NSArray`s. The headers are heavily documented for more information! 460 | 461 | ###Endpoints 462 | 463 | Think of your api's endpoint as an object, and this class as its model. It has the following components: 464 | 465 | It starts with a set of base properties that are meant to be overridden in your endpoint subclass. 466 | 467 | ####Base Endpoint 468 | 469 | When consuming a webservice, there is often a base set of configurations that apply to all endpoints. These overrides are often declared in a base class that is then subclassed by endpoints; however, these can always be overridden by an individual endpoint as necessary. 470 | 471 | #####Base Url 472 | 473 | This indicates the base Url that the endpoint Url should be appended to. It is common practice to override this in a base class for your api and subclass further for endpoints (see Getting Started). 474 | 475 | ######Objc 476 | 477 | ```ObjC 478 | - (NSString *)baseUrl { 479 | return @"http://api.somewebservice.com"; 480 | } 481 | ``` 482 | 483 | ######Swift 484 | 485 | ```Swift 486 | override var baseUrl: String { 487 | return "http://api.somewebservice.com" 488 | } 489 | ``` 490 | 491 | #####Header Fields 492 | 493 | This is where you can declare the header fields necessary when making web requests to the api. The most common use cases of this involve accept types and tokens. Again, it is common for this to exist in the base endpoint for a given api, but it can be overriddent for specific endpoints as necessary. A basic implementation can look something like this: 494 | 495 | ######ObjC 496 | 497 | ```ObjC 498 | - (NSDictionary *)headerFields { 499 | NSMutableDictionary *header = [NSMutableDictionary dictionary]; 500 | header[@"Accept"] = @"application/vnd.somewebservice.com+json; version=1"; 501 | header[@"Authorization"] = [NSString stringWithFormat:@"Token token=%@", MY_TOKEN]; 502 | return header; 503 | } 504 | ``` 505 | 506 | ######Swift 507 | 508 | ```Swift 509 | override var headerFields: [NSObject : AnyObject] { 510 | var header: [NSObject : AnyObject] = [:] 511 | header["Accept"] = "application/vnd.somewebservice.com+json; version=1" 512 | header["Authorization"] = "Token token=\(MY_TOKEN)" 513 | return header 514 | } 515 | ``` 516 | 517 | #####Acceptable Content Types 518 | 519 | You can use this to specify the content types to be accepted from a webservice. Again, this is often specified in the base class, but it can be overridden by individual endpoints as necessary. 520 | 521 | > Note: For modern webservices, it is often not necessary to override this function. 522 | 523 | ######ObjC 524 | 525 | ```ObjC 526 | - (NSSet *)acceptableContentTypes { 527 | return [NSSet setWithObjects:@"application/json", @"text/html", nil]; 528 | } 529 | override var acceptableContentTypes: Set { 530 | return Set(["application/json", "text/html"]) 531 | } 532 | ``` 533 | 534 | ######Swift 535 | 536 | ```Swift 537 | override var acceptableContentTypes: Set { 538 | return Set(["application/json", "text/html", "text/html; charset=utf-8"]) 539 | } 540 | ``` 541 | 542 | ####Individual Endpoints 543 | 544 | Once your base endpoint is defined, your individual endpoints should subclass that to specify individual behavior. Remember that for specific situations, each of the above methods can also be subclassed in your endpoint model. 545 | 546 | #####Return Class 547 | 548 | Use this to define what model the response for this endpoint should be mapped to. If this endpoint is an array response, the endpoint will return an array of this class. 549 | 550 | > NOTE: This class must conform to GenomeObject protocol 551 | 552 | ######ObjC 553 | 554 | ```ObjC 555 | - (Class)returnClass { 556 | return [Post class]; 557 | } 558 | ``` 559 | 560 | ######Swift 561 | 562 | ```Swift 563 | override var returnClass: AnyClass { 564 | return Post.self 565 | } 566 | ``` 567 | 568 | #####Endpoint Url 569 | 570 | This is where you declare the endpoint to append to the base url. You can also use this place to indicate slug paths to use when populating your url. A common implementation looks something like this: 571 | 572 | ######ObjC 573 | 574 | ```ObjC 575 | - (NSString *)endpointUrl { 576 | return @"posts/:identifier"; 577 | } 578 | ``` 579 | 580 | ######Swift 581 | 582 | ```Swift 583 | override var endpointUrl: String { 584 | return "posts/:identifier" 585 | } 586 | ```` 587 | 588 | ####Slug Mapping 589 | 590 | Slug mapping is a powerful feature that allows you to populate a given endpoint with slug values as necessary. For example, look at the endpoint url declared above as `posts/:identifier`. This means that if we pass a slug into our endpoints initialization, our url will be filled in with the appropriate values. Let's use the following example: 591 | 592 | ```ObjC 593 | PostsEndpoint *pe = [PostsEndpoint endpointWithSlug:@{@"identifier" : @"17"}]; 594 | [pe getWithCompletion: ... ]; 595 | ``` 596 | 597 | Given the example above, our endpoint `posts/:identifier` would be mapped to look like this`http://someBaseUrl.com/posts/17`. 598 | 599 | This feature can be used several different ways. The first, as you see above simply replaces the value declared in the endpointUrl with the value passed in the dictionary. 600 | 601 | #####1. Dictionaries 602 | 603 | If a dictionary has the key declared as a slug path ein the endpointUrl, the value for that key will be superimposed into the Url. If no slug, or no value is found, that url component will be ommitted. In the above example, our final url would be `http://someBaseUrl.com/posts` if the endpiont were passed a nil slug. 604 | 605 | #####2. Objects - With Keys 606 | 607 | You can also pass an object that has the specified keypath. This means that if our post had a property declared like so: 608 | 609 | ````ObjC 610 | @interface Post : NSObject 611 | 612 | @property (copy, nonatomic) NSString *identifier; 613 | 614 | @end 615 | ``` 616 | 617 | Now, if we passed the Post as a slug into our endpoint like this, it would be automatically populated: 618 | 619 | ```ObjC 620 | Post *post = ...; 621 | PostsEndpoint *ep = [PostsEndpoint endpointWithSlug:post]; 622 | [ep getWithCompletion: ... ]; 623 | ``` 624 | 625 | If our `post` object declared above has an identifier of `352` then we would be sending a get request to the endpoint `http://someBaseUrl.com/posts/352` 626 | 627 | #####3. Multiple Object Types 628 | 629 | In some situations, we want to pass a variety of objects to an endpoint and define more specifically how that endpoint should be populated with the slug. For these situations, you can override `valueForSlugPath:withSlug` to define what value should be used to populate the url. Our endpoint might look like this: 630 | 631 | ```ObjC 632 | @implementation PostsEndpoint 633 | /* 634 | ... 635 | */ 636 | - (id)valueForSlugPath:(NSString *)slugPath withSlug:(id)slug { 637 | if ([slug isKindOfClass:[Comment class]]) { 638 | Comment *comment = (Comment *)slug; 639 | return comment.post.identifier; 640 | } else { 641 | return [super valueForSlugPath:slugPath withSlug:slug]; 642 | } 643 | } 644 | @end 645 | ``` 646 | 647 | By overriding as demonstrated above, we can pass our endpoint a `Dictionary`, a `Post` object, or a `Comment` object and when we fetch from our Posts endpoint, we'll interact with the appropriate endpoint. 648 | a 649 | 650 | ######Slug Mapping Nil Check 651 | 652 | By default, a value is checked if it is `nil` or `NSNull`. If either of these is true, the path is not mapped. In rare cases, you may need to specify what constitutes as `nil`. For example, sometimes when using an NSInteger, it is 0 but needs to be nil. 653 | 654 | ```ObjC 655 | @implementation PostsEndpoint 656 | /* 657 | ... 658 | */ 659 | - (BOOL)valueIsValid:(id)value forSlugPath:(NSString *)slugPath { 660 | if ([slugPath isEqualToString:@"identifier"]) { 661 | return [value intValue] > 0; 662 | } else { 663 | return YES; 664 | } 665 | } 666 | @end 667 | ``` 668 | 669 | #####Response Key Path 670 | 671 | If you wish to use a portion of the response located at a specified keypath, you can declare it here. This is only necessary for specific situations. For example, if our response looked like this: 672 | 673 | ``` 674 | [ 675 | "results" : [ 676 | // ... results 677 | ] 678 | ] 679 | ``` 680 | 681 | We could specify to map only the array located at `results` by declaring like so: 682 | 683 | ######ObjC 684 | 685 | ```ObjC 686 | - (NSString *)responseKeyPath { 687 | return @"results"; 688 | } 689 | ``` 690 | 691 | ######Swift 692 | 693 | ```Swift 694 | override var responseKeyPath: String { 695 | return "results" 696 | } 697 | ``` 698 | 699 | ####Serializers 700 | 701 | In rare situations, you may need to provide a request or response serializer yourself. In those situations use the following: 702 | 703 | 704 | #####Response Serializer 705 | 706 | ```ObjC 707 | - (AFHTTPResponseSerializer *)responseSerializer { 708 | return ...; 709 | } 710 | ``` 711 | 712 | #####Request Serializer 713 | 714 | ```ObjC 715 | - (AFHTTPRequestSerializer *)requestSerializer { 716 | return ...; 717 | } 718 | ``` 719 | 720 | ####Append Header 721 | 722 | In some situations, the header contains valueable data we want to include in mapping. A common example of this is when next / last urls are included for paging in the header. 723 | 724 | If the response is a dictionary, an additional field will be added called 'Header', and values can be accessed via keypath syntax, ie: `Header.etag`. 725 | 726 | ``` 727 | [ 728 | "Header" : [ 729 | "headerKey" : "headerVal", 730 | // ... 731 | ] 732 | "responseKey" : "response Val" 733 | "responseKey2": "response val" 734 | ] 735 | ``` 736 | 737 | If the response is an array, it will be appended to the key `"response"` for mapping. 738 | 739 | ``` 740 | [ 741 | "Header" : [ 742 | "headerKey" : "headerVal" 743 | ] 744 | "response" : [ 745 | // ... array response 746 | ] 747 | ``` 748 | 749 | ######ObjC 750 | 751 | ```ObjC 752 | - (BOOL)shouldAppendHeaderToResponse { 753 | return YES; 754 | } 755 | ``` 756 | 757 | ######Swift 758 | 759 | ```Swift 760 | override var shouldAppendHeaderToResponse: Bool { 761 | return true 762 | } 763 | ``` 764 | 765 | ####Transform Response 766 | 767 | For some apis, the data we receive isn't able to be parsed a valid json representation for mapping. This is most common with XML webservices. In those situations, you can override `transformResponseDataToMappableRawType:`. This can also be overridden for customize behavior of specialized circumstances. 768 | 769 | ######ObjC 770 | 771 | ```ObjC 772 | - (id)transformResponseToMappableRawType:(id)response { 773 | if ([response isKindOfClass:[NSData class]]) { 774 | NSData *responseData = response; 775 | NSDictionary *responseDictionary = ... convert response data; 776 | return responseDictionary; 777 | } else { 778 | return response; 779 | } 780 | } 781 | ``` 782 | 783 | ######Swit 784 | 785 | ```Swift 786 | override func transformResponseToMappableRawType(response: AnyObject) -> GenomeMappableRawType? { 787 | if let data = response as? NSData { 788 | return ... converted data 789 | } else { 790 | return response as? GenomeMappableRawType 791 | } 792 | } 793 | ``` 794 | 795 | ####Networking Examples 796 | 797 | Once you've modeled your endpoint, the majority of the work is done! You simply intialize your endpoint with a slug and parameters as necessary and you're on your way! 798 | 799 | #####Get 800 | 801 | ######Get specific post 802 | 803 | ```ObjC 804 | PostsEndpoint *ep = [PostsEndpoint endpointWithSlug:@{@"identifier" : @"3"}]; 805 | [ep getWithCompletion:^(Post *post, NSError *error){ 806 | // ... 807 | }]; 808 | ``` 809 | 810 | ######Get a user's posts 811 | 812 | ```ObjC 813 | PostsEndpoint *ep = [PostsEndpoint endpointWithParameters:@{@"user_id" : currentUser.identifier}]; 814 | [ep getWithCompletion:^(NSArray *posts, NSError *error){ 815 | // ... array of Post objects 816 | }]; 817 | ``` 818 | 819 | #####Post 820 | 821 | ```ObjC 822 | NSDictionary *newPost = @{ 823 | @"title" : @"New Post", 824 | @"body" : @"This is a cool new post" 825 | }; 826 | PostsEndpoint *ep = [PostsEndpoint endpointWithParameters:newPost]; 827 | [ep postWithCompletion:^(Post *post, NSError *error){ 828 | // ... created new post, or error 829 | }]; 830 | ``` 831 | 832 | #####Put 833 | 834 | ```ObjC 835 | NSArray *tags = [ 836 | @"red", 837 | @"fun", 838 | @"summer" 839 | ] 840 | 841 | PostTagEndpoint *ep = [PostTagEndpoint endpointWithSlug:post 842 | andParameters:tags]; 843 | [ep putWithCompletion:^(NSArray *tags, NSError *error){ 844 | // ... created or updated tags for post. 845 | }]; 846 | ``` 847 | 848 | #####Patch 849 | 850 | ```ObjC 851 | NSDictionary *updatedParams = @{ 852 | @"title" : @"New Title", 853 | @"body" : @"Updated body" 854 | }; 855 | 856 | PostsEndpoint *ep = [PostsEndpoint endpointWithSlug:post 857 | andParameters:updatedParams]; 858 | [ep patchWithCompletion:^(Post *post, NSError *error){ 859 | // ... created new post, or error 860 | }]; 861 | ``` 862 | 863 | #####Delete 864 | 865 | ```ObjC 866 | PostEndpoint *ep = [PostEndpoint endpointWithSlug:post]; 867 | [ep deleteWithCompletion:^(Post *deletedPost, NSError *error) { 868 | // .. deleted object or error 869 | }] 870 | ``` 871 | 872 | ####Mapping 873 | 874 | For more information, see Genome 875 | --------------------------------------------------------------------------------