├── .nojekyll ├── ExampleIOS ├── Podfile ├── KKBOXOpenAPITests │ ├── KKBOXOpenAPITests.swift │ └── Info.plist ├── KKBOXOpenAPI │ ├── API.swift │ ├── KKTextViewController.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── KKChartsTableViewController.swift │ ├── KKNewHitsTableViewController.swift │ ├── KKFeaturedPlaylistCategoryTableViewController.swift │ ├── Info.plist │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── KKFeaturedPlaylistCategoriesTableViewController.swift │ ├── KKFeaturedPlaylistsTableViewController.swift │ ├── KKPlaylistTableViewController.swift │ └── KKMainTableViewController.swift ├── KKBOXOpenAPIDemo.entitlements ├── Podfile.lock └── KKBOXOpenAPIDemo.xcodeproj │ └── project.pbxproj ├── Tests ├── LinuxMain.swift └── KKBOXOpenAPITests │ ├── XCTestManifests.swift │ └── Tests.swift ├── .gitignore ├── .slather.yml ├── Sources └── KKBOXOpenAPI │ ├── include │ ├── KKBOXOpenAPI.h │ ├── OpenAPIObjects.h │ └── OpenAPI.h │ ├── NSData+LFHTTPFormExtensions.h │ ├── NSData+LFHTTPFormExtensions.m │ ├── OpenAPI+Privates.m │ └── OpenAPIObjects.m ├── jazzy.sh ├── .github └── workflows │ ├── ci.yml │ └── jazzy.yml ├── .travis.yml ├── KKBOXOpenAPI.xcodeproj ├── KKBOXOpenAPI_Info.plist ├── KKBOXOpenAPITests_Info.plist ├── xcshareddata │ └── xcschemes │ │ └── KKBOXOpenAPI-Package.xcscheme └── project.pbxproj ├── KKBOXOpenAPI.podspec ├── Package.swift ├── README.md └── LICENSE.txt /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ExampleIOS/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | pod "KKBOXOpenAPI", :path => "../" 4 | 5 | target 'KKBOXOpenAPIDemo' do 6 | platform :ios, '9.0' 7 | end 8 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import OpenAPI_ObjectiveCTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += OpenAPI_ObjectiveCTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.pbxuser 3 | *.mode1v3 4 | *.tm_build_errors 5 | *.perspectivev3 6 | *.xcworkspace 7 | *.ipa 8 | *.swp 9 | *.gcno 10 | xcuserdata 11 | .DS_Store 12 | .idea 13 | .vscode 14 | Pods 15 | cobertura.xml 16 | .swiftpm 17 | .build 18 | docs -------------------------------------------------------------------------------- /Tests/KKBOXOpenAPITests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(OpenAPI_ObjectiveCTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPITests/KKBOXOpenAPITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKBOXOpenAPITests.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import XCTest 8 | 9 | class KKBOXOpenAPITests: XCTestCase { 10 | } 11 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: cobertura_xml 2 | xcodeproj: KKBOXOpenAPI/KKBOXOpenAPI.xcodeproj 3 | scheme: KKBOXOpenAPI 4 | configuration: Debug 5 | source_directory: KKBOXOpenAPI 6 | output_directory: ./ 7 | binary-basename: KKBOXOpenAPITests.xctest 8 | ignore: 9 | - KKBOXOpenAPI/Tests/* 10 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/API.swift: -------------------------------------------------------------------------------- 1 | // 2 | // API.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | import KKBOXOpenAPI 9 | 10 | let sharedAPI: KKBOXOpenAPI! = KKBOXOpenAPI(clientID: "2074348baadf2d445980625652d9a54f", secret: "ac731b44fb2cf1ea766f43b5a65e82b8") 11 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPIDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ExampleIOS/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - KKBOXOpenAPI (1.3.0) 3 | 4 | DEPENDENCIES: 5 | - KKBOXOpenAPI (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | KKBOXOpenAPI: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | KKBOXOpenAPI: 08b0ed025bc2ada0da7ba11a2cc506cc03397327 13 | 14 | PODFILE CHECKSUM: b1afaa49049c7bf1f1677eaa33d94ef80aeda71f 15 | 16 | COCOAPODS: 1.8.4 17 | -------------------------------------------------------------------------------- /Sources/KKBOXOpenAPI/include/KKBOXOpenAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKBOXOpenAPIiOS.h 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | @import Foundation; 8 | 9 | FOUNDATION_EXPORT double KKBOXOpenAPIiOSVersionNumber; 10 | FOUNDATION_EXPORT const unsigned char KKBOXOpenAPIiOSVersionString[]; 11 | 12 | #import "OpenAPI.h" 13 | #import "OpenAPIObjects.h" 14 | -------------------------------------------------------------------------------- /jazzy.sh: -------------------------------------------------------------------------------- 1 | jazzy \ 2 | --objc \ 3 | --author KKBOX \ 4 | --author_url https://www.kkbox.com \ 5 | --github_url https://github.com/kkbox/OpenAPI-ObjectiveC \ 6 | --github-file-prefix https://github.com/kkbox/OpenAPI-ObjectiveC/tree/1.2.0 \ 7 | --module-version 1.2.0 \ 8 | --umbrella-header Sources/KKBOXOpenAPI/include/KKBOXOpenAPI.h \ 9 | --framework-root . \ 10 | --module KKBOXOpenAPI 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | name: Build 7 | runs-on: macOS-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Pod Lib Lint 11 | run: pod lib lint 12 | - name: Test 13 | run: xcodebuild -project KKBOXOpenAPI/KKBOXOpenAPI.xcodeproj -scheme KKBOXOpenAPITests -destination 'platform=iOS Simulator,name=iPhone XS,OS=12.4' test 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | matrix: 4 | include: 5 | - os: osx 6 | osx_image: xcode10.1 7 | 8 | before_install: 9 | - gem install xcov 10 | - gem install slather 11 | script: 12 | - xcodebuild -project KKBOXOpenAPI/KKBOXOpenAPI.xcodeproj -scheme KKBOXOpenAPI 13 | - xcodebuild -project KKBOXOpenAPI/KKBOXOpenAPI.xcodeproj -scheme KKBOXOpenAPITests -destination 'platform=iOS Simulator,name=iPhone XS,OS=12.1' test 14 | after_success: 15 | - xcov --project KKBOXOpenAPI/KKBOXOpenAPI.xcodeproj --scheme KKBOXOpenAPITests 16 | - slather 17 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKTextViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKTextViewController.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | 9 | class KKTextViewController: UIViewController { 10 | var text = "" { 11 | didSet { 12 | _ = self.view 13 | self.textView!.text = self.text 14 | } 15 | } 16 | var textView: UITextView? 17 | 18 | override func loadView() { 19 | self.view = UIView(frame: UIScreen.main.bounds) 20 | self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 21 | self.textView = UITextView(frame: self.view.bounds) 22 | self.textView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] 23 | self.view.addSubview(self.textView!) 24 | self.textView!.text = self.text 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/Assets.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 | } -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 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 | -------------------------------------------------------------------------------- /KKBOXOpenAPI.xcodeproj/KKBOXOpenAPI_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /KKBOXOpenAPI.xcodeproj/KKBOXOpenAPITests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // 4 | // Copyright (c) 2016 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | 9 | @UIApplicationMain 10 | class AppDelegate: UIResponder, UIApplicationDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | let mainVC = KKMainTableViewController(style: .grouped) 16 | let navVC = UINavigationController(rootViewController: mainVC) 17 | self.window = UIWindow() 18 | self.window?.rootViewController = navVC 19 | self.window?.makeKeyAndVisible() 20 | return true 21 | } 22 | 23 | func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { 24 | return true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/jazzy.yml: -------------------------------------------------------------------------------- 1 | name: Jazzy Docs 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build: 9 | runs-on: macOS-latest 10 | env: 11 | DEVELOPER_DIR: /Applications/Xcode_11.app/Contents/Developer 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v1 15 | - name: Install Jazzy 16 | run: gem install jazzy 17 | - name: Run Jazzy 18 | run: sh jazzy.sh 19 | - name: Deploy 20 | run: | 21 | cd docs 22 | git init 23 | git config user.name "CI" 24 | git config user.email "jazzy-ci@github.com" 25 | git remote add secure-origin https://${{ secrets.ACCESS_TOKEN }}@github.com/KKBOX/OpenAPI-ObjectiveC.git 26 | git checkout -b gh-pages 27 | git add . 28 | git commit -m "Updated docs" 29 | git push --force secure-origin gh-pages 30 | -------------------------------------------------------------------------------- /KKBOXOpenAPI.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "KKBOXOpenAPI" 3 | s.version = "1.3.1" 4 | s.license = {:type => 'Apache 2.0', :file => "LICENSE.txt"} 5 | s.summary = "KKBOX's Open API SDK for iOS, macOS, watchOS and tvOS." 6 | s.description = <<-DESC 7 | KKBOX's Open API SDK for developers working on Apple platforms such as iOS, macOS, watchOS and tvOS. 8 | DESC 9 | s.homepage = "https://github.com/KKBOX/OpenAPI-ObjectiveC/" 10 | s.documentation_url = 'https://kkbox.github.io/OpenAPI-ObjectiveC/' 11 | s.author = {"zonble" => "zonble@gmail.com"} 12 | s.source = {:git => "https://github.com/KKBOX/OpenAPI-ObjectiveC.git", :tag => s.version.to_s} 13 | 14 | s.platform = :ios, :tvos, :osx 15 | s.ios.deployment_target = '7.0' 16 | s.osx.deployment_target = '10.9' 17 | s.watchos.deployment_target = '2.0' 18 | s.tvos.deployment_target = '9.0' 19 | s.requires_arc = true 20 | s.source_files = 'Sources/KKBOXOpenAPI/**/*.{h,m}' 21 | s.ios.frameworks = 'UIKit' 22 | s.osx.frameworks = 'AppKit' 23 | s.tvos.frameworks = 'UIKit' 24 | end 25 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "KKBOXOpenAPI", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "KKBOXOpenAPI", 12 | targets: ["KKBOXOpenAPI"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "KKBOXOpenAPI", 23 | dependencies: [], 24 | publicHeadersPath: "include" 25 | ), 26 | .testTarget( 27 | name: "KKBOXOpenAPITests", 28 | dependencies: ["KKBOXOpenAPI"]), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKChartsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKChartsTableViewController.swift 3 | // 4 | // Copyright (c) 2008-2017 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | 9 | class KKChartsTableViewController: KKFeaturedPlaylistsTableViewController { 10 | 11 | override func viewDidLoad() { 12 | super.viewDidLoad() 13 | self.title = "Charts" 14 | } 15 | 16 | override func load() { 17 | var offset = 0 18 | switch self.state { 19 | case let .loaded(playlists:playlists, paging:_, summary:_): offset = playlists.count 20 | default: break 21 | } 22 | 23 | sharedAPI.fetchCharts(territory: .taiwan, offset: offset, limit: 10) { playlists, paging, summary, error in 24 | if let error = error { 25 | switch self.state { 26 | case .loaded(playlists: _, paging: _, summary: _): return 27 | default: break 28 | } 29 | self.state = .error(error) 30 | return 31 | } 32 | 33 | switch self.state { 34 | case let .loaded(playlists:currentPlaylists, paging:_, summary:_): 35 | self.state = .loaded(playlists: currentPlaylists + playlists!, paging: paging!, summary: summary!) 36 | default: 37 | self.state = .loaded(playlists: playlists!, paging: paging!, summary: summary!) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKNewHitsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKNewHitsTableViewController.swift 3 | // 4 | // Copyright (c) 2008-2017 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | import KKBOXOpenAPI 9 | 10 | class KKNewHitsTableViewController: KKFeaturedPlaylistsTableViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | self.title = "New Hits" 15 | } 16 | 17 | override func load() { 18 | var offset = 0 19 | switch self.state { 20 | case let .loaded(playlists:playlists, paging:_, summary:_): offset = playlists.count 21 | default: break 22 | } 23 | 24 | sharedAPI.fetchNewHitsPlaylists(territory: .taiwan, offset: offset, limit: 10) { playlists, paging, summary, error in 25 | if let error = error { 26 | switch self.state { 27 | case .loaded(playlists: _, paging: _, summary: _): return 28 | default: break 29 | } 30 | self.state = .error(error) 31 | return 32 | } 33 | 34 | switch self.state { 35 | case let .loaded(playlists:currentPlaylists, paging:_, summary:_): 36 | self.state = .loaded(playlists: currentPlaylists + playlists!, paging: paging!, summary: summary!) 37 | default: 38 | self.state = .loaded(playlists: playlists!, paging: paging!, summary: summary!) 39 | } 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Sources/KKBOXOpenAPI/NSData+LFHTTPFormExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+LFHTTPFormExtensions.h 3 | // 4 | // Copyright (c) 2007-2009 Lithoglyph Inc. (http://lithoglyph.com) 5 | // Copyright (c) 2007-2009 Lukhnos D. Liu (http://lukhnos.org) 6 | // 7 | // Permission is hereby granted, free of charge, to any person 8 | // obtaining a copy of this software and associated documentation 9 | // files (the "Software"), to deal in the Software without 10 | // restriction, including without limitation the rights to use, 11 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the 13 | // Software is furnished to do so, subject to the following 14 | // conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be 17 | // included in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | // OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import 30 | 31 | @interface NSData (LFHTTPFormExtensions) 32 | + (id)dataAsWWWURLEncodedFormFromDictionary:(NSDictionary *)formDictionary; 33 | @end 34 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKFeaturedPlaylistCategoryTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKFeaturedPlaylistCategoryTableViewController.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | import KKBOXOpenAPI 9 | 10 | class KKFeaturedPlaylistCategoryTableViewController: KKFeaturedPlaylistsTableViewController { 11 | 12 | private (set) var categoryID: String 13 | 14 | init(categoryID: String, style: UITableView.Style) { 15 | self.categoryID = categoryID 16 | super.init(style: style) 17 | } 18 | 19 | required init?(coder aDecoder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | override func load() { 24 | var offset = 0 25 | switch self.state { 26 | case let .loaded(playlists:playlists, paging:_, summary:_): offset = playlists.count 27 | default: break 28 | } 29 | sharedAPI?.fetchFeaturedPlaylistCategoryPlaylists(category: categoryID, territory: .taiwan, offset: offset, limit: 20) { (category, playlists, paging, summary, error) in 30 | if let error = error { 31 | switch self.state { 32 | case .loaded(playlists: _, paging: _, summary: _): return 33 | default: break 34 | } 35 | self.state = .error(error) 36 | return 37 | } 38 | 39 | switch self.state { 40 | case let .loaded(playlists:currentPlaylists, paging:_, summary:_): 41 | self.state = .loaded(playlists: currentPlaylists + playlists!, paging: paging!, summary: summary!) 42 | default: 43 | self.title = category!.title 44 | self.state = .loaded(playlists: playlists!, paging: paging!, summary: summary!) 45 | } 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Viewer 26 | CFBundleURLIconFile 27 | 28 | CFBundleURLName 29 | http 30 | CFBundleURLSchemes 31 | 32 | http 33 | 34 | 35 | 36 | CFBundleVersion 37 | 1 38 | LSRequiresIPhoneOS 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/Base.lproj/LaunchScreen.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 | 27 | 28 | -------------------------------------------------------------------------------- /Sources/KKBOXOpenAPI/NSData+LFHTTPFormExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+LFHTTPFormExtensions.m 3 | // 4 | // Copyright (c) 2007-2009 Lithoglyph Inc. (http://lithoglyph.com) 5 | // Copyright (c) 2007-2009 Lukhnos D. Liu (http://lukhnos.org) 6 | // 7 | // Permission is hereby granted, free of charge, to any person 8 | // obtaining a copy of this software and associated documentation 9 | // files (the "Software"), to deal in the Software without 10 | // restriction, including without limitation the rights to use, 11 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the 13 | // Software is furnished to do so, subject to the following 14 | // conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be 17 | // included in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | // OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import "NSData+LFHTTPFormExtensions.h" 30 | 31 | NS_INLINE NSString *LFHFEEscape(NSString *inValue) { 32 | NSMutableCharacterSet *URLQueryPartAllowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; 33 | [URLQueryPartAllowedCharacterSet removeCharactersInString:@"&+"]; 34 | 35 | NSString *escapedString = [inValue stringByAddingPercentEncodingWithAllowedCharacters:URLQueryPartAllowedCharacterSet]; 36 | return escapedString; 37 | } 38 | 39 | @implementation NSData (LFHTTPFormExtensions) 40 | + (id)dataAsWWWURLEncodedFormFromDictionary:(NSDictionary *)formDictionary 41 | { 42 | NSMutableString *combinedDataString = [NSMutableString string]; 43 | NSEnumerator *enumerator = [formDictionary keyEnumerator]; 44 | id key; 45 | id value; 46 | 47 | if ((key = [enumerator nextObject])) { 48 | value = formDictionary[key]; 49 | [combinedDataString appendString:[NSString stringWithFormat:@"%@=%@", LFHFEEscape(key), LFHFEEscape(value)]]; 50 | 51 | while ((key = [enumerator nextObject])) { 52 | value = formDictionary[key]; 53 | [combinedDataString appendString:[NSString stringWithFormat:@"&%@=%@", LFHFEEscape(key), LFHFEEscape(value)]]; 54 | } 55 | } 56 | 57 | return [combinedDataString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO]; 58 | } 59 | @end 60 | -------------------------------------------------------------------------------- /KKBOXOpenAPI.xcodeproj/xcshareddata/xcschemes/KKBOXOpenAPI-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKFeaturedPlaylistCategoriesTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKFeaturedPlaylistCategoriesTableViewController.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | import KKBOXOpenAPI 9 | 10 | enum KKFeaturedPlaylistCategoriesTableViewControllerState { 11 | case unknown 12 | case error(Error) 13 | case loading 14 | case loaded(categories: [FeaturedPlaylistCategory], paging: PagingInfo, summary: Summary) 15 | } 16 | 17 | class KKFeaturedPlaylistCategoriesTableViewController: UITableViewController { 18 | 19 | private (set) var state: KKFeaturedPlaylistCategoriesTableViewControllerState = .unknown { 20 | didSet { 21 | self.tableView.reloadData() 22 | } 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | self.title = "Categories" 28 | self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier") 29 | self.load() 30 | } 31 | 32 | private func load() { 33 | var offset = 0 34 | switch self.state { 35 | case let .loaded(categories:categories, paging:_, summary:_): offset = categories.count 36 | default: break 37 | } 38 | 39 | sharedAPI.fetchFeaturedPlaylistCategories(territory: .taiwan, offset: offset, limit: 20) { categories, paging, summary, error in 40 | if let error = error { 41 | switch self.state { 42 | case .loaded(categories: _, paging: _, summary: _): return 43 | default: break 44 | } 45 | self.state = .error(error) 46 | return 47 | } 48 | 49 | switch self.state { 50 | case let .loaded(categories:currentPlaylists, paging:_, summary:_): 51 | self.state = .loaded(categories: currentPlaylists + categories!, paging: paging!, summary: summary!) 52 | default: 53 | self.state = .loaded(categories: categories!, paging: paging!, summary: summary!) 54 | } 55 | } 56 | } 57 | 58 | // MARK: - Table view data source 59 | 60 | override func numberOfSections(in tableView: UITableView) -> Int { 61 | return 1 62 | } 63 | 64 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 65 | switch self.state { 66 | case let .loaded(categories:categories, paging:_, summary:_): 67 | return categories.count 68 | default: 69 | return 0 70 | } 71 | } 72 | 73 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 74 | let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) 75 | switch self.state { 76 | case let .loaded(categories:categories, paging:_, summary:_): 77 | let category = categories[indexPath.row] 78 | cell.textLabel?.text = category.title 79 | cell.accessoryType = .disclosureIndicator 80 | default: 81 | break 82 | } 83 | return cell 84 | } 85 | 86 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 87 | tableView.deselectRow(at: indexPath, animated: true) 88 | switch self.state { 89 | case let .loaded(categories:categories, paging:_, summary:_): 90 | let category = categories[indexPath.row] 91 | let controller = KKFeaturedPlaylistCategoryTableViewController(categoryID: category.id, style: .plain) 92 | self.navigationController?.pushViewController(controller, animated: true) 93 | default: 94 | break 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKFeaturedPlaylistsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKFeaturedPlaylistsTableViewController.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | import KKBOXOpenAPI 9 | 10 | enum KKFeaturedPlaylistsTableViewControllerState { 11 | case unknown 12 | case error(Error) 13 | case loading 14 | case loaded(playlists: [PlaylistInfo], paging: PagingInfo, summary: Summary) 15 | } 16 | 17 | class KKFeaturedPlaylistsTableViewController: UITableViewController { 18 | var state: KKFeaturedPlaylistsTableViewControllerState = .unknown { 19 | didSet { 20 | self.tableView.reloadData() 21 | } 22 | } 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | self.title = "Featured Playlists" 27 | self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier") 28 | self.load() 29 | } 30 | 31 | func load() { 32 | var offset = 0 33 | switch self.state { 34 | case let .loaded(playlists:playlists, paging:_, summary:_): offset = playlists.count 35 | default: break 36 | } 37 | 38 | sharedAPI.fetchFeaturedPlaylists(territory: .taiwan, offset: offset, limit: 20) { playlists, paging, summary, error in 39 | if let error = error { 40 | switch self.state { 41 | case .loaded(playlists: _, paging: _, summary: _): return 42 | default: break 43 | } 44 | self.state = .error(error) 45 | return 46 | } 47 | 48 | switch self.state { 49 | case let .loaded(playlists:currentPlaylists, paging:_, summary:_): 50 | self.state = .loaded(playlists: currentPlaylists + playlists!, paging: paging!, summary: summary!) 51 | default: 52 | self.state = .loaded(playlists: playlists!, paging: paging!, summary: summary!) 53 | } 54 | } 55 | } 56 | 57 | // MARK: - Table view data source 58 | 59 | override func numberOfSections(in tableView: UITableView) -> Int { 60 | return 1 61 | } 62 | 63 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 64 | switch self.state { 65 | case let .loaded(playlists:playlists, paging:_, summary:_): 66 | return playlists.count 67 | default: 68 | return 0 69 | } 70 | } 71 | 72 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 73 | let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) 74 | 75 | switch self.state { 76 | case let .loaded(playlists:playlists, paging:_, summary:summary): 77 | let playlist = playlists[indexPath.row]; 78 | cell.textLabel?.text = playlist.title 79 | cell.accessoryType = .disclosureIndicator 80 | if indexPath.row == playlists.count - 1 && indexPath.row < summary.total - 1 { 81 | self.load() 82 | } 83 | default: 84 | break 85 | } 86 | return cell 87 | } 88 | 89 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 90 | switch self.state { 91 | case let .loaded(playlists:playlists, paging:_, summary:_): 92 | let playlist = playlists[indexPath.row]; 93 | let controller = KKPlaylistTableViewController(playlistID: playlist.id, style: .plain) 94 | self.navigationController?.pushViewController(controller, animated: true) 95 | default: 96 | break 97 | } 98 | } 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKPlaylistTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKPlaylistTableViewController.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | import KKBOXOpenAPI 9 | 10 | enum KKPlaylistTableViewControllerState { 11 | case unknown 12 | case error(Error) 13 | case loading 14 | case loaded(playlist: PlaylistInfo, tracks: [TrackInfo], paging: PagingInfo, summary: Summary) 15 | } 16 | 17 | class KKPlaylistTableViewController: UITableViewController { 18 | private (set) var playlistID: String 19 | private (set) var state: KKPlaylistTableViewControllerState = .unknown { 20 | didSet { 21 | switch self.state { 22 | case let .loaded(playlist:playlist, tracks:_, paging:_, summary:_): 23 | self.title = playlist.title 24 | default: break 25 | } 26 | self.tableView.reloadData() 27 | } 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | self.state = .loading 33 | self.load() 34 | } 35 | 36 | init(playlistID: String, style: UITableView.Style) { 37 | self.playlistID = playlistID 38 | super.init(style: style) 39 | } 40 | 41 | required init?(coder aDecoder: NSCoder) { 42 | fatalError("init(coder:) has not been implemented") 43 | } 44 | 45 | func load() { 46 | switch self.state { 47 | case let .loaded(playlist:playlist, tracks:currentTracks, paging:_, summary:_): 48 | let offset = currentTracks.count 49 | sharedAPI.fetchPlaylistTracks(id: playlistID, territory: .taiwan, offset: offset, limit: 20) { tracks, paging, summary, error in 50 | if error != nil { 51 | return 52 | } 53 | self.state = .loaded(playlist: playlist, tracks: currentTracks + tracks!, paging: paging!, summary: summary!) 54 | } 55 | break 56 | default: 57 | sharedAPI.fetchPlaylist(id: self.playlistID, territory: .taiwan) { playlist, paging, summary, error in 58 | if let error = error { 59 | self.state = .error(error) 60 | return 61 | } 62 | self.state = .loaded(playlist: playlist!, tracks: playlist!.tracks, paging: paging!, summary: summary!) 63 | } 64 | } 65 | } 66 | 67 | // MARK: - Table view data source 68 | 69 | override func numberOfSections(in tableView: UITableView) -> Int { 70 | return 1 71 | } 72 | 73 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 74 | switch self.state { 75 | case let .loaded(playlist:_, tracks:tracks, paging:_, summary:_): 76 | return tracks.count 77 | default: 78 | return 0 79 | } 80 | } 81 | 82 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 83 | var cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") 84 | if cell == nil { 85 | cell = UITableViewCell(style: .subtitle, reuseIdentifier: "reuseIdentifier") 86 | } 87 | 88 | switch self.state { 89 | case let .loaded(playlist:_, tracks:tracks, paging:_, summary:summary): 90 | let track = tracks[indexPath.row] 91 | cell?.textLabel?.text = track.name 92 | cell?.detailTextLabel?.text = track.album?.artist.name ?? "N/A" 93 | 94 | if indexPath.row == tracks.count - 1 && indexPath.row < summary.total - 1 { 95 | self.load() 96 | } 97 | 98 | default: 99 | break 100 | } 101 | return cell! 102 | } 103 | 104 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 105 | tableView.deselectRow(at: indexPath, animated: true) 106 | switch self.state { 107 | case let .loaded(playlist:_, tracks:tracks, paging:_, summary:_): 108 | let track = tracks[indexPath.row] 109 | if let url = track.url { 110 | UIApplication.shared.openURL(url) 111 | } 112 | default: 113 | break 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPI/KKMainTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KKMainTableViewController.swift 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import UIKit 8 | import KKBOXOpenAPI 9 | 10 | class KKMainTableViewController: UITableViewController { 11 | 12 | override init(style: UITableView.Style) { 13 | super.init(style: style) 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | self.title = "KKBOX Open API" 23 | } 24 | 25 | override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | } 28 | 29 | // MARK: - Table view routines 30 | 31 | override func numberOfSections(in tableView: UITableView) -> Int { 32 | return 2 33 | } 34 | 35 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 36 | return [1, 4][section] 37 | } 38 | 39 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 40 | var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cell") 41 | if cell == nil { 42 | cell = UITableViewCell(style: .default, reuseIdentifier: "cell") 43 | } 44 | if indexPath.section == 0 { 45 | let titlesForLoggingIn = 46 | ["Login with Client Credential"] 47 | cell.textLabel?.text = titlesForLoggingIn[indexPath.row] 48 | } else if indexPath.section == 1 { 49 | let titleForAPIs = 50 | ["Featured Playlists", 51 | "Featured Playlist Categories", 52 | "New Hits", 53 | "Charts"] 54 | cell.textLabel?.text = titleForAPIs[indexPath.row] 55 | } 56 | return cell 57 | } 58 | 59 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 60 | tableView.deselectRow(at: indexPath, animated: true) 61 | if indexPath.section == 0 { 62 | let methodsForLoggingIn = 63 | [self.loginWithClientCredential] 64 | methodsForLoggingIn[indexPath.row]() 65 | } 66 | if indexPath.section == 1 { 67 | let methodsForLoggingIn = 68 | [self.showFeaturedPlaylists, 69 | self.showFeaturedPlaylistCategories, 70 | self.showNewHits, 71 | self.showCharts] 72 | methodsForLoggingIn[indexPath.row]() 73 | } 74 | } 75 | 76 | // MARK: - 77 | 78 | func loginWithClientCredential() { 79 | UIApplication.shared.beginIgnoringInteractionEvents() 80 | sharedAPI.fetchAccessTokenByClientCredential { 81 | accessToken, error in 82 | UIApplication.shared.endIgnoringInteractionEvents() 83 | if error != nil { 84 | let alert = UIAlertController(title: "Failed to login", message: error?.localizedDescription, preferredStyle: .alert) 85 | alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil)) 86 | self.present(alert, animated: true, completion: nil) 87 | return 88 | } 89 | } 90 | let alert = UIAlertController(title: "Logged In!", message: nil, preferredStyle: .alert) 91 | alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil)) 92 | self.present(alert, animated: true, completion: nil) 93 | } 94 | 95 | func showFeaturedPlaylists() { 96 | let controller = KKFeaturedPlaylistsTableViewController(style: .plain) 97 | self.navigationController?.pushViewController(controller, animated: true) 98 | } 99 | 100 | func showFeaturedPlaylistCategories() { 101 | let controller = KKFeaturedPlaylistCategoriesTableViewController(style: .plain) 102 | self.navigationController?.pushViewController(controller, animated: true) 103 | } 104 | 105 | func showNewHits() { 106 | let controller = KKNewHitsTableViewController(style: .plain) 107 | self.navigationController?.pushViewController(controller, animated: true) 108 | } 109 | 110 | func showCharts() { 111 | let controller = KKChartsTableViewController(style: .plain) 112 | self.navigationController?.pushViewController(controller, animated: true) 113 | } 114 | 115 | } 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KKBOX Open API Developer SDK for iOS/macOS/watchOS/tvOS 2 | 3 | Copyright © 2016-2020 KKBOX Technologies Limited 4 | 5 | [![Actions Status](https://github.com/KKBOX/OpenAPI-ObjectiveC/workflows/Build/badge.svg)](https://github.com/KKBOX/OpenAPI-ObjectiveC/actions)  6 | [![build](https://api.travis-ci.org/KKBOX/OpenAPI-ObjectiveC.svg)](https://travis-ci.org/KKBOX/OpenAPI-ObjectiveC)  7 | [![License Apache](https://img.shields.io/badge/license-Apache-green.svg?style=flat)](https://raw.githubusercontent.com/KKBOX/OpenAPI-ObjectiveC/master/LICENSE)  8 | [![CocoaPods](http://img.shields.io/cocoapods/v/KKBOXOpenAPI.svg?style=flat)](http://cocoapods.org/pods/KKBOXOpenAPI)  9 | [![Support](https://img.shields.io/badge/macOS-10.9-blue.svg)](https://www.apple.com/tw/macos)  10 | [![Support](https://img.shields.io/badge/iOS-7-blue.svg)](https://www.apple.com/tw/ios)  11 | [![Support](https://img.shields.io/badge/watchOS-2-blue.svg)](https://www.apple.com/tw/watchos)  12 | [![Support](https://img.shields.io/badge/tvOS-9-blue.svg)](https://www.apple.com/tw/tvos)  13 | 14 | ## About 15 | 16 | The SDK helps to access KKBOX's Open API. You can easily add the SDK to your 17 | Xcode project, and start an app powered by KKBOX. You may obtain information 18 | about song tracks, albums, artists and playlists as well. 19 | 20 | The SDK is developed in Objective-C programing language, but you can still 21 | bridge the SDK to your Swift code. You can use the SDK on various Apple 22 | platforms such as iOS, macOS, watchOS and tvOS. 23 | 24 | If you are looking for a pure Swift SDK, please take a look at 25 | [KKBOX Open API Swift SDK](https://github.com/KKBOX/OpenAPI-Swift). 26 | 27 | For further information, please visit 28 | [KKBOX Developer Site](https://developer.kkbox.com). 29 | 30 | ## Requirement 31 | 32 | The SDK supports 33 | 34 | - 📱 iOS 7.x or above 35 | - 💻 Mac OS X 10.9 or above 36 | - ⌚️ watchOS 2.x or above 37 | - 📺 tvOS 9.x or above 38 | 39 | ## Build ⚒ 40 | 41 | You need the latest Xcode and macOS. Xcode 10 and macOS 10.14 Mojave are 42 | recommended. 43 | 44 | ## Installation 45 | 46 | ### Swift Package Manager 47 | 48 | You can install the library via Swift Package Manager (SPM). Just add the 49 | following lines to your Package.swift file. 50 | 51 | ``` swift 52 | dependencies: [ 53 | .package(url: "https://github.com/KKBOX/OpenAPI-ObjectiveC.git", from: "1.3.1"), 54 | ], 55 | ``` 56 | 57 | Then run swift build. 58 | 59 | Or, you can use the "Add Package Dependency" command under the "Swift Packages" 60 | menu in Xcode 11. 61 | 62 | ### CocoaPods 63 | 64 | The SDK supports CocoaPods. Please add `pod 'KKBOXOpenAPI'` 65 | to your Podfile, and then call `pod install`. 66 | 67 | ## Usage 68 | 69 | To start using the SDK, you need to create an instance of KKBOXOpenAPI. 70 | 71 | ```swift 72 | let API = KKBOXOpenAPI(clientID: "YOUR_CLIENT_ID", secret: "YOUR_CLIENT_SECRET") 73 | ``` 74 | 75 | Then, ask the instance to fetch an access token by passing a client credential. 76 | 77 | ```swift 78 | API.fetchAccessTokenByClientCredential { token, error in ... } 79 | ``` 80 | 81 | Finally, you can start to do the API calls. For example, you can fetch the details 82 | of a song track by calling 'fetchTrack'. 83 | 84 | ```swift 85 | self.API.fetchTrack(withTrackID: trackID, territory: .taiwan) { track, error in ... } 86 | ``` 87 | 88 | You can develop your app using the SDK with Swift or Objective-C programming 89 | language, although we have only Swift sample code here. 90 | 91 | The project contains a demo project. Please open KKBOXOpenAPI.xcodeproj 92 | located in the "ExampleIOS" folder with Xcode and give it a try. 93 | 94 | ## API Documentation 📖 95 | 96 | - Documentation for the SDK is available at https://kkbox.github.io/OpenAPI-ObjectiveC/ . 97 | - KKBOX's Open API documentation is available at https://developer.kkbox.com/ . 98 | 99 | ## License 100 | 101 | Copyright 2016-2020 KKBOX Technologies Limited 102 | 103 | Licensed under the Apache License, Version 2.0 (the "License"); 104 | you may not use this file except in compliance with the License. 105 | You may obtain a copy of the License at 106 | 107 | http://www.apache.org/licenses/LICENSE-2.0 108 | 109 | Unless required by applicable law or agreed to in writing, software 110 | distributed under the License is distributed on an "AS IS" BASIS, 111 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 112 | See the License for the specific language governing permissions and 113 | limitations under the License. 114 | -------------------------------------------------------------------------------- /Sources/KKBOXOpenAPI/OpenAPI+Privates.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKBOXOpenAPI+Privates.m 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | #import "OpenAPI.h" 8 | #import "NSData+LFHTTPFormExtensions.h" 9 | 10 | static NSString *const KKUserAgent = @"KKBOX Open API iOS SDK"; 11 | 12 | NSString *KKStringFromTerritoryCode(KKTerritoryCode code) { 13 | switch (code) { 14 | case KKTerritoryCodeTaiwan: 15 | return @"TW"; 16 | case KKTerritoryCodeHongKong: 17 | return @"HK"; 18 | case KKTerritoryCodeSingapore: 19 | return @"SG"; 20 | case KKTerritoryCodeMalaysia: 21 | return @"MY"; 22 | case KKTerritoryCodeJapan: 23 | return @"JP"; 24 | default: 25 | break; 26 | } 27 | return @""; 28 | } 29 | 30 | @implementation KKBOXOpenAPI (Privates) 31 | 32 | - (NSURLSessionDataTask *)_postToURL:(NSURL *)URL POSTParameters:(NSDictionary *)parameters headers:(NSDictionary *)headers callback:(void (^)(id, NSError *))callback 33 | { 34 | NSMutableDictionary *newHeaders = [headers mutableCopy]; 35 | newHeaders[@"Content-type"] = @"application/x-www-form-urlencoded"; 36 | NSData *POSTData = [NSData dataAsWWWURLEncodedFormFromDictionary:parameters]; 37 | return [self _postToURL:URL POSTData:POSTData headers:newHeaders callback:callback]; 38 | } 39 | 40 | - (NSURLSessionDataTask *)_postToURL:(NSURL *)URL POSTData:(NSData *)POSTData headers:(NSDictionary *)headers callback:(void (^)(id, NSError *))callback 41 | { 42 | NSParameterAssert(URL); 43 | NSParameterAssert(POSTData); 44 | NSParameterAssert(headers); 45 | NSParameterAssert(callback); 46 | 47 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL]; 48 | [request setHTTPMethod:@"POST"]; 49 | 50 | for (NSString *key in headers.allKeys) { 51 | [request setValue:headers[key] forHTTPHeaderField:key]; 52 | } 53 | [request setValue:KKUserAgent forHTTPHeaderField:@"User-Agent"]; 54 | [request setHTTPBody:POSTData]; 55 | 56 | NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { 57 | if (error) { 58 | dispatch_async(dispatch_get_main_queue(), ^{ 59 | callback(nil, error); 60 | }); 61 | return; 62 | } 63 | NSError *JSONError = nil; 64 | id JSONObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONError]; 65 | if (JSONError) { 66 | dispatch_async(dispatch_get_main_queue(), ^{ 67 | callback(nil, JSONError); 68 | }); 69 | } 70 | dispatch_async(dispatch_get_main_queue(), ^{ 71 | callback(JSONObject, nil); 72 | }); 73 | }]; 74 | [task resume]; 75 | return task; 76 | } 77 | 78 | - (nonnull NSURLSessionDataTask *)_apiTaskWithURL:(nonnull NSURL *)URL callback:(nonnull KKBOXOpenAPIDataCallback)callback; 79 | { 80 | NSParameterAssert(self.accessToken); 81 | NSParameterAssert(URL); 82 | NSParameterAssert(callback); 83 | 84 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL]; 85 | [request setHTTPMethod:@"GET"]; 86 | [request setValue:KKUserAgent forHTTPHeaderField:@"User-Agent"]; 87 | NSString *auth = [NSString stringWithFormat:@"Bearer %@", self.accessToken.accessToken]; 88 | [request setValue:auth forHTTPHeaderField:@"Authorization"]; 89 | NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { 90 | if (error) { 91 | dispatch_async(dispatch_get_main_queue(), ^{ 92 | callback(nil, error); 93 | }); 94 | return; 95 | } 96 | NSError *JSONError = nil; 97 | id JSONObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONError]; 98 | if (JSONError) { 99 | dispatch_async(dispatch_get_main_queue(), ^{ 100 | callback(nil, JSONError); 101 | }); 102 | return; 103 | } 104 | NSDictionary *APIErrorDictionary = JSONObject[@"error"]; 105 | if ([APIErrorDictionary isKindOfClass:[NSDictionary class]]) { 106 | NSInteger code = [APIErrorDictionary[@"code"] integerValue]; 107 | NSString *errorMessage = APIErrorDictionary[@"message"] ?: @"API Error"; 108 | NSError *APIError = [NSError errorWithDomain:@"KKBOXOpenAPIErrorDomain" code:code userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; 109 | dispatch_async(dispatch_get_main_queue(), ^{ 110 | callback(nil, APIError); 111 | }); 112 | return; 113 | } 114 | dispatch_async(dispatch_get_main_queue(), ^{ 115 | callback(JSONObject, nil); 116 | }); 117 | }]; 118 | [task resume]; 119 | return task; 120 | } 121 | 122 | @end 123 | -------------------------------------------------------------------------------- /Sources/KKBOXOpenAPI/include/OpenAPIObjects.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKBOXOpenAPIObjects.h 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | @import Foundation; 8 | 9 | #if TARGET_OS_MACOS 10 | @import AppKit; 11 | #endif 12 | #if TARGET_OS_IOS || TARGET_OS_SIMULATOR || TARGET_OS_TV 13 | @import UIKit; 14 | #endif 15 | #if TARGET_OS_WATCH 16 | @import WatchKit; 17 | #endif 18 | 19 | 20 | /** The model objects used in KKBOX's Open API. */ 21 | @interface KKBOXOpenAPIObject : NSObject 22 | /** 23 | Create an instance by a given dictionary. 24 | 25 | @param dictionary The dictionary. 26 | @return A KKBOXOpenAPIObject instance. 27 | */ 28 | - (nonnull instancetype)initWithDictionary:(nonnull NSDictionary *)dictionary; 29 | @end 30 | 31 | /** The object that represents the pagination of a API response in list type. */ 32 | NS_SWIFT_NAME(PagingInfo) 33 | @interface KKPagingInfo : KKBOXOpenAPIObject 34 | /** The max amount of items in a page. */ 35 | @property (readonly, assign, nonatomic) NSInteger limit; 36 | /** Where the list begins. */ 37 | @property (readonly, assign, nonatomic) NSInteger offset; 38 | /** The URL for the API call for the previous page. */ 39 | @property (readonly, strong, nonatomic, nullable) NSURL *previous; 40 | /** The URL for the API call for the next page. */ 41 | @property (readonly, strong, nonatomic, nullable) NSURL *next; 42 | @end 43 | 44 | /** The summary of a list. */ 45 | NS_SWIFT_NAME(Summary) 46 | @interface KKSummary : KKBOXOpenAPIObject 47 | /** The total amount of items matching the criteria. */ 48 | @property (readonly, assign, nonatomic) NSInteger total; 49 | @end 50 | 51 | /** The object represents information about an image. */ 52 | NS_SWIFT_NAME(ImageInfo) 53 | @interface KKImageInfo : KKBOXOpenAPIObject 54 | /** Width of the image. */ 55 | @property (readonly, assign, nonatomic) CGFloat width; 56 | /** Height of the image. */ 57 | @property (readonly, assign, nonatomic) CGFloat height; 58 | /** URL of the image. */ 59 | @property (readonly, strong, nonatomic, nullable) NSURL *imageURL NS_SWIFT_NAME(url); 60 | @end 61 | 62 | /** The object represents information about an artist on KKBOX. */ 63 | NS_SWIFT_NAME(ArtistInfo) 64 | @interface KKArtistInfo : KKBOXOpenAPIObject 65 | /** The ID of the artist. */ 66 | @property (readonly, strong, nonatomic, nonnull) NSString *artistID NS_SWIFT_NAME(id); 67 | /** The name of the artist. */ 68 | @property (readonly, strong, nonatomic, nonnull) NSString *artistName NS_SWIFT_NAME(name); 69 | /** The URL of webpage about the artist. */ 70 | @property (readonly, strong, nonatomic, nullable) NSURL *artistURL NS_SWIFT_NAME(url); 71 | /** The images of the artist. */ 72 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 73 | @end 74 | 75 | /** The object represents information about an album on KKBOX. */ 76 | NS_SWIFT_NAME(KKAlbumInfo) 77 | @interface KKAlbumInfo : KKBOXOpenAPIObject 78 | /** The ID of the album. */ 79 | @property (readonly, strong, nonatomic, nonnull) NSString *albumID NS_SWIFT_NAME(id); 80 | /** The name of the album. */ 81 | @property (readonly, strong, nonatomic, nonnull) NSString *albumName NS_SWIFT_NAME(name); 82 | /** The URL of the webpage about the album. */ 83 | @property (readonly, strong, nonatomic, nullable) NSURL *albumURL NS_SWIFT_NAME(url); 84 | /** The artist of the album. */ 85 | @property (readonly, strong, nonatomic, nonnull) KKArtistInfo *artist; 86 | /** The images of the album. */ 87 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 88 | /** When was the album released. */ 89 | @property (readonly, strong, nonatomic, nonnull) NSString *releaseDate; 90 | /** Is the album explicit or not. */ 91 | @property (readonly, assign, nonatomic) BOOL explicitness; 92 | /** The territories that the album is availanble at. */ 93 | @property (readonly, strong, nonatomic, nonnull) NSSet *territoriesThatAvailableAt; 94 | @end 95 | 96 | /** The object represents a track on KKBOX. */ 97 | NS_SWIFT_NAME(TrackInfo) 98 | @interface KKTrackInfo : KKBOXOpenAPIObject 99 | /** The ID of the track. */ 100 | @property (readonly, strong, nonatomic, nonnull) NSString *trackID NS_SWIFT_NAME(id); 101 | /** The name of the track.*/ 102 | @property (readonly, strong, nonatomic, nonnull) NSString *trackName NS_SWIFT_NAME(name); 103 | /** The URL of the webpage of the track. */ 104 | @property (readonly, strong, nonatomic, nullable) NSURL *trackURL NS_SWIFT_NAME(url); 105 | /** The album that the track belong to. */ 106 | @property (readonly, strong, nonatomic, nullable) KKAlbumInfo *album; 107 | /** Length of the track. */ 108 | @property (readonly, assign, nonatomic) NSTimeInterval duration; 109 | /** The track order of the track in an album. */ 110 | @property (readonly, assign, nonatomic) NSInteger trackOrderInAlbum; 111 | /** Is the track explicit or not. */ 112 | @property (readonly, assign, nonatomic) BOOL explicitness; 113 | /** The territories that the track is available at. */ 114 | @property (readonly, strong, nonatomic, nonnull) NSSet *territoriesThatAvailableAt; 115 | @end 116 | 117 | /** The object represents a user on KKBOX. */ 118 | NS_SWIFT_NAME(UserInfo) 119 | @interface KKUserInfo : KKBOXOpenAPIObject 120 | /** The ID of the user. */ 121 | @property (readonly, strong, nonatomic, nonnull) NSString *userID NS_SWIFT_NAME(id); 122 | /** The name of the user. */ 123 | @property (readonly, strong, nonatomic, nonnull) NSString *userName NS_SWIFT_NAME(name); 124 | /** The description of the user. */ 125 | @property (readonly, strong, nonatomic, nonnull) NSString *userDescription; 126 | /** The URL of the page of the user on KKBOX. */ 127 | @property (readonly, strong, nonatomic, nullable) NSURL *userURL NS_SWIFT_NAME(url); 128 | /** The profile images of the user. */ 129 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 130 | @end 131 | 132 | /** The object represents a playlist on KKBOX. */ 133 | NS_SWIFT_NAME(PlaylistInfo) 134 | @interface KKPlaylistInfo : KKBOXOpenAPIObject 135 | /** The ID of the playlist. */ 136 | @property (readonly, strong, nonatomic, nonnull) NSString *playlistID NS_SWIFT_NAME(id); 137 | /** The title of the playlist. */ 138 | @property (readonly, strong, nonatomic, nonnull) NSString *playlistTitle NS_SWIFT_NAME(title); 139 | /** The description of the playlist. */ 140 | @property (readonly, strong, nonatomic, nonnull) NSString *playlistDescription; 141 | /** The URL of the webpage about the playlist on KKBOX. */ 142 | @property (readonly, strong, nonatomic, nonnull) NSURL *playlistURL NS_SWIFT_NAME(url); 143 | /** The curator of the playlist. */ 144 | @property (readonly, strong, nonatomic, nonnull) KKUserInfo *playlistOwner NS_SWIFT_NAME(owner); 145 | /** The images of the playlist. */ 146 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 147 | /** The tracks contained in the playlist. */ 148 | @property (readonly, strong, nonatomic, nonnull) NSArray *tracks; 149 | /** When was the playlist updated. */ 150 | @property (readonly, strong, nonatomic, nonnull) NSString *lastUpdateDate; 151 | @end 152 | 153 | /** The object represents a featured playlist category. */ 154 | NS_SWIFT_NAME(FeaturedPlaylistCategory) 155 | @interface KKFeaturedPlaylistCategory : KKBOXOpenAPIObject 156 | /** The ID of the category. */ 157 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryID NS_SWIFT_NAME(id); 158 | /** The title of the category. */ 159 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryTitle NS_SWIFT_NAME(title); 160 | /** The images of the category. */ 161 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 162 | @end 163 | 164 | /** The object represents a new release album category. */ 165 | NS_SWIFT_NAME(NewReleaseAlbumsCategory) 166 | @interface KKNewReleaseAlbumsCategory : KKBOXOpenAPIObject 167 | /** The ID of the category. */ 168 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryID NS_SWIFT_NAME(id); 169 | /** The title of the category. */ 170 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryTitle NS_SWIFT_NAME(title); 171 | @end 172 | 173 | /** The object represents a mood/genre radio station on KKBOX. */ 174 | NS_SWIFT_NAME(RadioStation) 175 | @interface KKRadioStation : KKBOXOpenAPIObject 176 | /** The ID of the station. */ 177 | @property (readonly, strong, nonatomic, nonnull) NSString *stationID NS_SWIFT_NAME(id); 178 | /** The name of the station. */ 179 | @property (readonly, strong, nonatomic, nonnull) NSString *stationName NS_SWIFT_NAME(name); 180 | /** The category of the station. Note: not every station is catogorized. */ 181 | @property (readonly, strong, nonatomic, nullable) NSString *stationCategory NS_SWIFT_NAME(category); 182 | /** The images of the station. Noe: not every station has images. */ 183 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 184 | @end 185 | 186 | /** The object represents search results. */ 187 | NS_SWIFT_NAME(SearchResults) 188 | @interface KKSearchResults : KKBOXOpenAPIObject 189 | /** Track search results. Available when searching for tracks is specified. */ 190 | @property (readonly, strong, nonatomic, nullable) NSArray *tracks; 191 | @property (readonly, strong, nonatomic, nullable) KKPagingInfo *tracksPaging; 192 | @property (readonly, strong, nonatomic, nullable) KKSummary *tracksSummary; 193 | 194 | /** Album search results. Available when searching for albums is specified. */ 195 | @property (readonly, strong, nonatomic, nullable) NSArray *albums; 196 | @property (readonly, strong, nonatomic, nullable) KKPagingInfo *albumsPaging; 197 | @property (readonly, strong, nonatomic, nullable) KKSummary *albumsSummary; 198 | 199 | /** Artists search results. Available when searching for artists is specified. */ 200 | @property (readonly, strong, nonatomic, nullable) NSArray *artists; 201 | @property (readonly, strong, nonatomic, nullable) KKPagingInfo *artistsPaging; 202 | @property (readonly, strong, nonatomic, nullable) KKSummary *artistsSummary; 203 | 204 | /** Playlists search results. Available when searching for playlists is specified. */ 205 | @property (readonly, strong, nonatomic, nullable) NSArray *playlists; 206 | @property (readonly, strong, nonatomic, nullable) KKPagingInfo *playlistsPaging; 207 | @property (readonly, strong, nonatomic, nullable) KKSummary *playlistsSummary; 208 | 209 | /** The overall pagination info. */ 210 | @property (readonly, strong, nonatomic, nonnull) KKPagingInfo *paging; 211 | /** The overall summary. */ 212 | @property (readonly, strong, nonatomic, nonnull) KKSummary *summary; 213 | @end 214 | 215 | @interface KKChildrenCategory : KKBOXOpenAPIObject 216 | /** The ID of the category. */ 217 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryID NS_SWIFT_NAME(id); 218 | /** The title of the category. */ 219 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryTitle NS_SWIFT_NAME(title); 220 | /** The images of the category. */ 221 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 222 | @end 223 | 224 | NS_SWIFT_NAME(ChildrenCategoryGroup) 225 | @interface KKChildrenCategoryGroup : KKBOXOpenAPIObject 226 | /** The ID of the category. */ 227 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryID NS_SWIFT_NAME(id); 228 | /** The title of the category. */ 229 | @property (readonly, strong, nonatomic, nonnull) NSString *categoryTitle NS_SWIFT_NAME(title); 230 | /** The images of the category. */ 231 | @property (readonly, strong, nonatomic, nonnull) NSArray *images; 232 | /** The images of the category. */ 233 | @property (readonly, strong, nonatomic, nonnull) NSArray *subcategories; 234 | @end 235 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 KKBOX Technologies Limited 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Sources/KKBOXOpenAPI/OpenAPIObjects.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKBOXOpenAPIObjects.m 3 | // 4 | // Copyright (c) 2017 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | #import "OpenAPIObjects.h" 8 | #import "OpenAPI.h" 9 | 10 | @interface KKBOXOpenAPIObject () 11 | - (void)handleDictionary; 12 | 13 | @property (strong, nonatomic, nullable) NSDictionary *dictionary; 14 | @end 15 | 16 | @interface KKPagingInfo () 17 | @property (assign, nonatomic) NSInteger limit; 18 | @property (assign, nonatomic) NSInteger offset; 19 | @property (strong, nonatomic, nullable) NSURL *previous; 20 | @property (strong, nonatomic, nullable) NSURL *next; 21 | @end 22 | 23 | @interface KKSummary () 24 | @property (assign, nonatomic) NSInteger total; 25 | @end 26 | 27 | @interface KKImageInfo () 28 | @property (assign, nonatomic) CGFloat width; 29 | @property (assign, nonatomic) CGFloat height; 30 | @property (strong, nonatomic, nullable) NSURL *imageURL; 31 | @end 32 | 33 | @interface KKArtistInfo () 34 | @property (strong, nonatomic, nonnull) NSString *artistID; 35 | @property (strong, nonatomic, nonnull) NSString *artistName; 36 | @property (strong, nonatomic, nullable) NSURL *artistURL; 37 | @property (strong, nonatomic, nonnull) NSArray *images; 38 | @end 39 | 40 | @interface KKAlbumInfo () 41 | @property (strong, nonatomic, nonnull) NSString *albumID; 42 | @property (strong, nonatomic, nonnull) NSString *albumName; 43 | @property (strong, nonatomic, nullable) NSURL *albumURL; 44 | @property (strong, nonatomic, nonnull) KKArtistInfo *artist; 45 | @property (strong, nonatomic, nonnull) NSArray *images; 46 | @property (strong, nonatomic, nonnull) NSString *releaseDate; 47 | @property (assign, nonatomic) BOOL explicitness; 48 | @property (strong, nonatomic, nonnull) NSSet *territoriesThatAvailableAt; 49 | @end 50 | 51 | @interface KKTrackInfo () 52 | @property (strong, nonatomic, nonnull) NSString *trackID; 53 | @property (strong, nonatomic, nonnull) NSString *trackName; 54 | @property (strong, nonatomic, nullable) NSURL *trackURL; 55 | @property (strong, nonatomic, nullable) KKAlbumInfo *album; 56 | @property (assign, nonatomic) NSTimeInterval duration; 57 | @property (assign, nonatomic) NSInteger trackOrderInAlbum; 58 | @property (assign, nonatomic) BOOL explicitness; 59 | @property (strong, nonatomic, nonnull) NSSet *territoriesThatAvailableAt; 60 | @end 61 | 62 | @interface KKUserInfo () 63 | @property (strong, nonatomic, nonnull) NSString *userID; 64 | @property (strong, nonatomic, nonnull) NSString *userName; 65 | @property (strong, nonatomic, nonnull) NSString *userDescription; 66 | @property (strong, nonatomic, nonnull) NSURL *userURL; 67 | @property (strong, nonatomic, nonnull) NSArray *images; 68 | @end 69 | 70 | @interface KKPlaylistInfo () 71 | @property (strong, nonatomic, nonnull) NSString *playlistID; 72 | @property (strong, nonatomic, nonnull) NSString *playlistTitle; 73 | @property (strong, nonatomic, nonnull) NSString *playlistDescription; 74 | @property (strong, nonatomic, nonnull) NSURL *playlistURL; 75 | @property (strong, nonatomic, nonnull) KKUserInfo *playlistOwner; 76 | @property (strong, nonatomic, nonnull) NSArray *images; 77 | @property (strong, nonatomic, nonnull) NSArray *tracks; 78 | @property (strong, nonatomic, nonnull) NSString *lastUpdateDate; 79 | @end 80 | 81 | @interface KKFeaturedPlaylistCategory () 82 | @property (strong, nonatomic, nonnull) NSString *categoryID; 83 | @property (strong, nonatomic, nonnull) NSString *categoryTitle; 84 | @property (strong, nonatomic, nonnull) NSArray *images; 85 | @end 86 | 87 | @interface KKRadioStation () 88 | @property (strong, nonatomic, nonnull) NSString *stationID; 89 | @property (strong, nonatomic, nonnull) NSString *stationName; 90 | @property (strong, nonatomic, nullable) NSString *stationCategory; 91 | @property (strong, nonatomic, nonnull) NSArray *images; 92 | @end 93 | 94 | @interface KKSearchResults () 95 | @property (strong, nonatomic, nullable) NSArray *tracks; 96 | @property (strong, nonatomic, nullable) KKPagingInfo *tracksPaging; 97 | @property (strong, nonatomic, nullable) KKSummary *tracksSummary; 98 | @property (strong, nonatomic, nullable) NSArray *albums; 99 | @property (strong, nonatomic, nullable) KKPagingInfo *albumsPaging; 100 | @property (strong, nonatomic, nullable) KKSummary *albumsSummary; 101 | @property (strong, nonatomic, nullable) NSArray *artists; 102 | @property (strong, nonatomic, nullable) KKPagingInfo *artistsPaging; 103 | @property (strong, nonatomic, nullable) KKSummary *artistsSummary; 104 | @property (strong, nonatomic, nullable) NSArray *playlists; 105 | @property (strong, nonatomic, nullable) KKPagingInfo *playlistsPaging; 106 | @property (strong, nonatomic, nullable) KKSummary *playlistsSummary; 107 | @property (strong, nonatomic, nonnull) KKPagingInfo *paging; 108 | @property (strong, nonatomic, nonnull) KKSummary *summary; 109 | @end 110 | 111 | @interface KKNewReleaseAlbumsCategory () 112 | @property (strong, nonatomic, nonnull) NSString *categoryID; 113 | @property (strong, nonatomic, nonnull) NSString *categoryTitle; 114 | @end 115 | 116 | @interface KKChildrenCategory () 117 | @property (strong, nonatomic, nonnull) NSString *categoryID; 118 | @property (strong, nonatomic, nonnull) NSString *categoryTitle; 119 | @property (strong, nonatomic, nonnull) NSArray *images; 120 | @end 121 | 122 | @interface KKChildrenCategoryGroup () 123 | @property (strong, nonatomic, nonnull) NSString *categoryID; 124 | @property (strong, nonatomic, nonnull) NSString *categoryTitle; 125 | @property (strong, nonatomic, nonnull) NSArray *images; 126 | @property (strong, nonatomic, nonnull) NSArray *subcategories; 127 | @end 128 | 129 | 130 | #pragma mark - 131 | 132 | @interface KKBOXOpenAPIObjectParsingHelper : NSObject 133 | + (NSSet *)territoriesFromArray:(NSArray *)array; 134 | 135 | + (NSArray *)imageArrayFromArray:(NSArray *)dictionaryImages; 136 | 137 | + (NSArray *)subcategoriesFromArray:(NSArray *)dictionaryImages; 138 | @end 139 | 140 | @implementation KKBOXOpenAPIObjectParsingHelper 141 | + (NSSet *)territoriesFromArray:(NSArray *)array 142 | { 143 | if (![array isKindOfClass:[NSArray class]]) { 144 | return [NSSet set]; 145 | } 146 | 147 | NSMutableSet *set = [NSMutableSet set]; 148 | for (NSString *s in array) { 149 | if (![s isKindOfClass:[NSString class]]) { 150 | continue; 151 | } 152 | if ([s isEqualToString:@"TW"]) { 153 | [set addObject:@(KKTerritoryCodeTaiwan)]; 154 | } 155 | else if ([s isEqualToString:@"HK"]) { 156 | [set addObject:@(KKTerritoryCodeHongKong)]; 157 | } 158 | else if ([s isEqualToString:@"SG"]) { 159 | [set addObject:@(KKTerritoryCodeSingapore)]; 160 | } 161 | else if ([s isEqualToString:@"MY"]) { 162 | [set addObject:@(KKTerritoryCodeMalaysia)]; 163 | } 164 | else if ([s isEqualToString:@"JP"]) { 165 | [set addObject:@(KKTerritoryCodeJapan)]; 166 | } 167 | } 168 | return set; 169 | } 170 | 171 | + (NSArray *)imageArrayFromArray:(NSArray *)dictionaryImages 172 | { 173 | NSMutableArray *images = [NSMutableArray array]; 174 | if ([dictionaryImages isKindOfClass:[NSArray class]]) { 175 | for (NSDictionary *imageDictionary in dictionaryImages) { 176 | if ([imageDictionary isKindOfClass:[NSDictionary class]]) { 177 | KKImageInfo *imageInfo = [[KKImageInfo alloc] initWithDictionary:imageDictionary]; 178 | [images addObject:imageInfo]; 179 | } 180 | } 181 | } 182 | return images; 183 | } 184 | 185 | + (NSArray *)subcategoriesFromArray:(NSArray *)dictionaryImages 186 | { 187 | NSMutableArray *images = [NSMutableArray array]; 188 | if ([dictionaryImages isKindOfClass:[NSArray class]]) { 189 | for (NSDictionary *imageDictionary in dictionaryImages) { 190 | if ([imageDictionary isKindOfClass:[NSDictionary class]]) { 191 | KKChildrenCategory *imageInfo = [[KKChildrenCategory alloc] initWithDictionary:imageDictionary]; 192 | [images addObject:imageInfo]; 193 | } 194 | } 195 | } 196 | return images; 197 | } 198 | 199 | @end 200 | 201 | @implementation KKBOXOpenAPIObject 202 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary; 203 | { 204 | self = [super init]; 205 | if (self) { 206 | if ([dictionary isKindOfClass:[NSDictionary class]]) { 207 | self.dictionary = dictionary; 208 | [self handleDictionary]; 209 | } 210 | } 211 | return self; 212 | } 213 | 214 | - (void)handleDictionary 215 | { 216 | } 217 | 218 | - (NSString *)description 219 | { 220 | NSString *description = [NSString stringWithFormat:@"<%@ %p> %@", NSStringFromClass([self class]), self, [self.dictionary description]]; 221 | return description; 222 | } 223 | @end 224 | 225 | @implementation KKPagingInfo 226 | - (void)handleDictionary 227 | { 228 | NSDictionary *dictionary = self.dictionary; 229 | if ([dictionary[@"limit"] respondsToSelector:@selector(integerValue)]) { 230 | self.limit = [dictionary[@"limit"] integerValue]; 231 | } 232 | if ([dictionary[@"offset"] respondsToSelector:@selector(integerValue)]) { 233 | self.offset = [dictionary[@"offset"] integerValue]; 234 | } 235 | if ([dictionary[@"previous"] isKindOfClass:[NSString class]]) { 236 | self.previous = [NSURL URLWithString:dictionary[@"previous"]]; 237 | } 238 | if ([dictionary[@"next"] isKindOfClass:[NSString class]]) { 239 | self.previous = [NSURL URLWithString:dictionary[@"next"]]; 240 | } 241 | } 242 | @end 243 | 244 | @implementation KKSummary 245 | - (void)handleDictionary 246 | { 247 | NSDictionary *dictionary = self.dictionary; 248 | if ([dictionary[@"total"] respondsToSelector:@selector(integerValue)]) { 249 | self.total = [dictionary[@"total"] integerValue]; 250 | } 251 | } 252 | @end 253 | 254 | @implementation KKImageInfo 255 | - (void)handleDictionary 256 | { 257 | NSDictionary *dictionary = self.dictionary; 258 | if ([dictionary[@"width"] respondsToSelector:@selector(floatValue)]) { 259 | self.width = [dictionary[@"width"] floatValue]; 260 | } 261 | if ([dictionary[@"height"] respondsToSelector:@selector(floatValue)]) { 262 | self.height = [dictionary[@"height"] floatValue]; 263 | } 264 | if ([dictionary[@"url"] isKindOfClass:[NSString class]]) { 265 | self.imageURL = [NSURL URLWithString:dictionary[@"url"]]; 266 | } 267 | } 268 | @end 269 | 270 | @implementation KKArtistInfo 271 | - (void)handleDictionary 272 | { 273 | NSDictionary *dictionary = self.dictionary; 274 | self.artistID = [dictionary[@"id"] isKindOfClass:[NSString class]] ? dictionary[@"id"] : @""; 275 | self.artistName = [dictionary[@"name"] isKindOfClass:[NSString class]] ? dictionary[@"name"] : @""; 276 | if ([dictionary[@"url"] isKindOfClass:[NSString class]]) { 277 | self.artistURL = [NSURL URLWithString:dictionary[@"url"]]; 278 | } 279 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 280 | } 281 | @end 282 | 283 | @implementation KKAlbumInfo 284 | - (void)handleDictionary 285 | { 286 | NSDictionary *dictionary = self.dictionary; 287 | self.albumID = [dictionary[@"id"] isKindOfClass:[NSString class]] ? dictionary[@"id"] : @""; 288 | self.albumName = [dictionary[@"name"] isKindOfClass:[NSString class]] ? dictionary[@"name"] : @""; 289 | if ([dictionary[@"url"] isKindOfClass:[NSString class]]) { 290 | self.albumURL = [NSURL URLWithString:dictionary[@"url"]]; 291 | } 292 | self.artist = [[KKArtistInfo alloc] initWithDictionary:([dictionary[@"artist"] isKindOfClass:[NSDictionary class]] ? dictionary[@"artist"] : @{})]; 293 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 294 | self.releaseDate = [dictionary[@"release_date"] isKindOfClass:[NSString class]] ? dictionary[@"release_date"] : @""; 295 | self.explicitness = [dictionary[@"explicitness"] respondsToSelector:@selector(boolValue)] ? [dictionary[@"explicitness"] boolValue] : NO; 296 | self.territoriesThatAvailableAt = [KKBOXOpenAPIObjectParsingHelper territoriesFromArray:dictionary[@"available_territories"]]; 297 | } 298 | @end 299 | 300 | @implementation KKTrackInfo 301 | - (void)handleDictionary 302 | { 303 | NSDictionary *dictionary = self.dictionary; 304 | self.trackID = [dictionary[@"id"] isKindOfClass:[NSString class]] ? dictionary[@"id"] : @""; 305 | self.trackName = [dictionary[@"name"] isKindOfClass:[NSString class]] ? dictionary[@"name"] : @""; 306 | if ([dictionary[@"url"] isKindOfClass:[NSString class]]) { 307 | self.trackURL = [NSURL URLWithString:dictionary[@"url"]]; 308 | } 309 | if ([dictionary[@"album"] isKindOfClass:[NSDictionary class]]) { 310 | self.album = [[KKAlbumInfo alloc] initWithDictionary:dictionary[@"album"]]; 311 | } 312 | self.trackOrderInAlbum = [dictionary[@"track_number"] respondsToSelector:@selector(integerValue)] ? [dictionary[@"track_number"] integerValue] : 0; 313 | self.duration = [dictionary[@"duration"] respondsToSelector:@selector(doubleValue)] ? [dictionary[@"duration"] doubleValue] / 1000.0 : 0; 314 | self.explicitness = [dictionary[@"explicitness"] respondsToSelector:@selector(boolValue)] ? [dictionary[@"explicitness"] boolValue] : NO; 315 | self.territoriesThatAvailableAt = [KKBOXOpenAPIObjectParsingHelper territoriesFromArray:dictionary[@"available_territories"]]; 316 | } 317 | @end 318 | 319 | @implementation KKUserInfo 320 | - (void)handleDictionary 321 | { 322 | NSDictionary *dictionary = self.dictionary; 323 | self.userID = [dictionary[@"id"] isKindOfClass:[NSString class]] ? dictionary[@"id"] : @""; 324 | self.userName = [dictionary[@"name"] isKindOfClass:[NSString class]] ? dictionary[@"name"] : @""; 325 | if ([dictionary[@"url"] isKindOfClass:[NSString class]]) { 326 | self.userURL = [NSURL URLWithString:dictionary[@"url"]]; 327 | } 328 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 329 | } 330 | @end 331 | 332 | @implementation KKPlaylistInfo 333 | - (void)handleDictionary 334 | { 335 | NSDictionary *dictionary = self.dictionary; 336 | self.playlistID = dictionary[@"id"] ?: @""; 337 | self.playlistTitle = dictionary[@"title"] ?: @""; 338 | self.playlistDescription = dictionary[@"description"] ?: @""; 339 | if ([dictionary[@"url"] isKindOfClass:[NSString class]]) { 340 | self.playlistURL = [NSURL URLWithString:dictionary[@"url"]]; 341 | } 342 | self.playlistOwner = [[KKUserInfo alloc] initWithDictionary:dictionary[@"owner"]]; 343 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 344 | NSMutableArray *tracks = [[NSMutableArray alloc] init]; 345 | if ([dictionary[@"tracks"][@"data"] isKindOfClass:[NSArray class]]) { 346 | for (NSDictionary *trackDictionary in dictionary[@"tracks"][@"data"]) { 347 | KKTrackInfo *track = [[KKTrackInfo alloc] initWithDictionary:trackDictionary]; 348 | [tracks addObject:track]; 349 | } 350 | } 351 | self.lastUpdateDate = [dictionary[@"updated_at"] isKindOfClass:[NSString class]] ? dictionary[@"updated_at"] : @""; 352 | self.tracks = tracks; 353 | } 354 | @end 355 | 356 | @implementation KKFeaturedPlaylistCategory 357 | - (void)handleDictionary 358 | { 359 | NSDictionary *dictionary = self.dictionary; 360 | self.categoryID = dictionary[@"id"] ?: @""; 361 | self.categoryTitle = dictionary[@"title"] ?: @""; 362 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 363 | } 364 | @end 365 | 366 | @implementation KKRadioStation 367 | - (void)handleDictionary 368 | { 369 | NSDictionary *dictionary = self.dictionary; 370 | self.stationCategory = dictionary[@"category"]; 371 | self.stationID = dictionary[@"id"] ?: @""; 372 | self.stationName = dictionary[@"name"] ?: @""; 373 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 374 | } 375 | @end 376 | 377 | @implementation KKSearchResults 378 | - (void)handleDictionary 379 | { 380 | NSDictionary *dictionary = self.dictionary; 381 | 382 | if ([dictionary[@"tracks"] isKindOfClass:[NSDictionary class]] && [dictionary[@"tracks"][@"data"] isKindOfClass:[NSArray class]]) { 383 | NSMutableArray *tracks = [[NSMutableArray alloc] init]; 384 | for (NSDictionary *trackDictionary in dictionary[@"tracks"][@"data"]) { 385 | KKTrackInfo *track = [[KKTrackInfo alloc] initWithDictionary:trackDictionary]; 386 | [tracks addObject:track]; 387 | } 388 | self.tracks = tracks; 389 | self.tracksPaging = [[KKPagingInfo alloc] initWithDictionary:(dictionary[@"tracks"][@"paging"] ?: @{})]; 390 | self.tracksSummary = [[KKSummary alloc] initWithDictionary:(dictionary[@"tracks"][@"summary"] ?: @{})]; 391 | } 392 | 393 | if ([dictionary[@"albums"] isKindOfClass:[NSDictionary class]] && [dictionary[@"albums"][@"data"] isKindOfClass:[NSArray class]]) { 394 | NSMutableArray *albums = [[NSMutableArray alloc] init]; 395 | for (NSDictionary *albumDictionary in dictionary[@"albums"][@"data"]) { 396 | KKAlbumInfo *track = [[KKAlbumInfo alloc] initWithDictionary:albumDictionary]; 397 | [albums addObject:track]; 398 | } 399 | self.albums = albums; 400 | self.albumsPaging = [[KKPagingInfo alloc] initWithDictionary:(dictionary[@"albums"][@"paging"] ?: @{})]; 401 | self.albumsSummary = [[KKSummary alloc] initWithDictionary:(dictionary[@"albums"][@"summary"] ?: @{})]; 402 | } 403 | 404 | if ([dictionary[@"artists"] isKindOfClass:[NSDictionary class]] && [dictionary[@"artists"][@"data"] isKindOfClass:[NSArray class]]) { 405 | NSMutableArray *artists = [[NSMutableArray alloc] init]; 406 | for (NSDictionary *artistDictionary in dictionary[@"artists"][@"data"]) { 407 | KKArtistInfo *artist = [[KKArtistInfo alloc] initWithDictionary:artistDictionary]; 408 | [artists addObject:artist]; 409 | } 410 | self.artists = artists; 411 | self.artistsPaging = [[KKPagingInfo alloc] initWithDictionary:(dictionary[@"artists"][@"paging"] ?: @{})]; 412 | self.artistsSummary = [[KKSummary alloc] initWithDictionary:(dictionary[@"artists"][@"summary"] ?: @{})]; 413 | } 414 | 415 | if ([dictionary[@"playlists"] isKindOfClass:[NSDictionary class]] && [dictionary[@"playlists"][@"data"] isKindOfClass:[NSArray class]]) { 416 | NSMutableArray *playlists = [[NSMutableArray alloc] init]; 417 | for (NSDictionary *playlistDictionary in dictionary[@"playlists"][@"data"]) { 418 | KKPlaylistInfo *playlist = [[KKPlaylistInfo alloc] initWithDictionary:playlistDictionary]; 419 | [playlists addObject:playlist]; 420 | } 421 | self.playlists = playlists; 422 | self.playlistsPaging = [[KKPagingInfo alloc] initWithDictionary:(dictionary[@"playlists"][@"paging"] ?: @{})]; 423 | self.playlistsSummary = [[KKSummary alloc] initWithDictionary:(dictionary[@"playlists"][@"summary"] ?: @{})]; 424 | } 425 | 426 | self.paging = [[KKPagingInfo alloc] initWithDictionary:(dictionary[@"paging"] ?: @{})]; 427 | self.summary = [[KKSummary alloc] initWithDictionary:(dictionary[@"summary"] ?: @{})]; 428 | } 429 | @end 430 | 431 | @implementation KKNewReleaseAlbumsCategory 432 | - (void)handleDictionary 433 | { 434 | NSDictionary *dictionary = self.dictionary; 435 | self.categoryID = dictionary[@"id"] ?: @""; 436 | self.categoryTitle = dictionary[@"title"] ?: @""; 437 | } 438 | @end 439 | 440 | @implementation KKChildrenCategory 441 | - (void)handleDictionary 442 | { 443 | NSDictionary *dictionary = self.dictionary; 444 | self.categoryID = dictionary[@"id"] ?: @""; 445 | self.categoryTitle = dictionary[@"title"] ?: @""; 446 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 447 | } 448 | @end 449 | 450 | 451 | @implementation KKChildrenCategoryGroup 452 | - (void)handleDictionary 453 | { 454 | NSDictionary *dictionary = self.dictionary; 455 | self.categoryID = dictionary[@"id"] ?: @""; 456 | self.categoryTitle = dictionary[@"title"] ?: @""; 457 | self.images = [KKBOXOpenAPIObjectParsingHelper imageArrayFromArray:dictionary[@"images"]]; 458 | self.subcategories = [KKBOXOpenAPIObjectParsingHelper subcategoriesFromArray:dictionary[@"subcategories"]]; 459 | } 460 | @end 461 | -------------------------------------------------------------------------------- /Tests/KKBOXOpenAPITests/Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tests.swift 3 | // 4 | // Copyright (c) 2017 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | import XCTest 8 | import KKBOXOpenAPI 9 | 10 | class Tests: XCTestCase { 11 | var API: KKBOXOpenAPI! 12 | 13 | override func setUp() { 14 | super.setUp() 15 | self.API = KKBOXOpenAPI(clientID: "2074348baadf2d445980625652d9a54f", secret: "ac731b44fb2cf1ea766f43b5a65e82b8") 16 | } 17 | 18 | override func tearDown() { 19 | super.tearDown() 20 | } 21 | 22 | // MARK: - 23 | 24 | func testAccessToken() { 25 | let d = ["access_token": "1234", 26 | "expires_in": 1234567890 as Double, 27 | "token_type": "token_type", 28 | "scope": "scope" 29 | ] as [String: Any] 30 | let accessToken = KKAccessToken(dictionary: d) 31 | XCTAssertEqual(accessToken.accessToken, d["access_token"] as! String) 32 | XCTAssertEqual(accessToken.expiresIn, d["expires_in"] as! TimeInterval) 33 | XCTAssertEqual(accessToken.tokenType, d["token_type"] as? String) 34 | XCTAssertEqual(accessToken.scope, d["scope"] as? String) 35 | } 36 | 37 | // func testScopeParamater() { 38 | // XCTAssertEqual(self.API._scopeParameter([.all]), "all") 39 | // XCTAssertEqual(self.API._scopeParameter([.userProfile]), "user_profile") 40 | // XCTAssertEqual(self.API._scopeParameter([.userTerritory]), "user_territory") 41 | // XCTAssertEqual(self.API._scopeParameter([.userAccountStatus]), "user_account_status") 42 | // XCTAssertEqual(self.API._scopeParameter([.userProfile, .userTerritory]), "user_profile user_territory") 43 | // XCTAssertEqual(Set(self.API._scopeParameter([.userTerritory, .userAccountStatus]).split(separator: " ")), Set("user_territory user_account_status".split(separator: " "))) 44 | // XCTAssertEqual(Set(self.API._scopeParameter([.userProfile, .userAccountStatus]).split(separator: " ")), Set("user_profile user_account_status".split(separator: " "))) 45 | // XCTAssertEqual(self.API._scopeParameter([.userProfile, .userTerritory, .userAccountStatus]), "all") 46 | // } 47 | 48 | // MARK: - 49 | 50 | func validate(track: TrackInfo) { 51 | XCTAssertNotNil(track) 52 | XCTAssertTrue(track.id.count > 0) 53 | XCTAssertTrue(track.name.count > 0) 54 | XCTAssertTrue(track.duration > 0) 55 | XCTAssertNotNil(track.url) 56 | XCTAssertTrue(track.trackOrderInAlbum > 0) 57 | // XCTAssertTrue(track.territoriesThatAvailableAt.count > 0) 58 | // XCTAssertTrue(track.territoriesThatAvailableAt.contains(KKTerritoryCode.taiwan.rawValue as NSNumber)) 59 | if let album = track.album { 60 | self.validate(album: album) 61 | } 62 | } 63 | 64 | func validate(album: KKAlbumInfo) { 65 | XCTAssertNotNil(album) 66 | XCTAssertTrue(album.id.count > 0) 67 | XCTAssertTrue(album.name.count > 0) 68 | XCTAssertNotNil(album.url) 69 | XCTAssertTrue(album.images.count == 3) 70 | // XCTAssertTrue(album.releaseDate.count > 0) 71 | // XCTAssertTrue(album.territoriesThatAvailableAt.count > 0, "\(album.albumName)") 72 | // XCTAssertTrue(album.territoriesThatAvailableAt.contains(KKTerritoryCode.taiwan.rawValue as NSNumber)) 73 | self.validate(artist: album.artist) 74 | } 75 | 76 | func validate(artist: ArtistInfo) { 77 | XCTAssertNotNil(artist) 78 | XCTAssertTrue(artist.id.count > 0) 79 | XCTAssertTrue(artist.name.count > 0) 80 | XCTAssertNotNil(artist.url) 81 | XCTAssertTrue(artist.images.count == 2) 82 | } 83 | 84 | func validate(playlist: PlaylistInfo) { 85 | XCTAssertNotNil(playlist); 86 | XCTAssertTrue(playlist.id.count > 0); 87 | XCTAssertTrue(playlist.title.count > 0); 88 | // XCTAssertTrue(playlist.playlistDescription.count > 0); 89 | XCTAssertNotNil(playlist.url); 90 | if (playlist.tracks.count > 0) { 91 | for track in playlist.tracks { 92 | self.validate(track: track) 93 | } 94 | } 95 | } 96 | 97 | func validate(user: UserInfo) { 98 | XCTAssertTrue(user.id.count > 0) 99 | XCTAssertTrue(user.name.count > 0) 100 | XCTAssertNotNil(user.url) 101 | XCTAssertNotNil(user.userDescription) 102 | XCTAssertTrue(user.images.count > 0) 103 | } 104 | 105 | func waitForToken() { 106 | let e = self.expectation(description: "wait for token") 107 | self.API.fetchAccessTokenByClientCredential { token, error in 108 | e.fulfill() 109 | if let error = error { 110 | XCTFail("Failed to fetch token \(error.localizedDescription)") 111 | return 112 | } 113 | } 114 | self.wait(for: [e], timeout: 3) 115 | } 116 | 117 | func useExplicitToken() { 118 | self.API.accessToken = KKAccessToken(dictionary: 119 | ["access_token": "qHeIPUO2hS8eJ0FKS9tUsQ==", 120 | "expires_in": 3153600000, 121 | "refresh_token": "hrJp4YOYxbjDVhQG+nnqpg==", 122 | "scope": "all", 123 | "token_type": "Bearer"] 124 | ) 125 | } 126 | 127 | // MARK: - 128 | 129 | func testFetchTrack() { 130 | self.waitForToken() 131 | let e = self.expectation(description: "testFetchTrack") 132 | let trackID = "4kxvr3wPWkaL9_y3o_" 133 | self.API.fetchTrack(id: trackID, territory: .taiwan) { track, error in 134 | e.fulfill() 135 | if let _ = error { 136 | XCTFail("Failed to fetch track \(String(describing: error))") 137 | return 138 | } 139 | self.validate(track: track!) 140 | } 141 | self.wait(for: [e], timeout: 3) 142 | } 143 | 144 | func testFetchInvalidTrack() { 145 | self.waitForToken() 146 | let e = self.expectation(description: "testFetchTrack") 147 | let trackID = "121231231" 148 | self.API.fetchTrack(id: trackID, territory: .taiwan) { track, error in 149 | e.fulfill() 150 | XCTAssertNotNil(error) 151 | } 152 | self.wait(for: [e], timeout: 3) 153 | } 154 | 155 | func testFetchAlbum() { 156 | self.waitForToken() 157 | let e = self.expectation(description: "testFetchAlbum") 158 | self.API.fetchAlbum(id: "WpTPGzNLeutVFHcFq6", territory: .taiwan) { album, error in 159 | e.fulfill() 160 | if let _ = error { 161 | XCTFail("Failed to fetch album \(String(describing: error))") 162 | return 163 | } 164 | self.validate(album: album!) 165 | } 166 | self.wait(for: [e], timeout: 3) 167 | } 168 | 169 | func testFetchTracksInAlbum() { 170 | self.waitForToken() 171 | let e = self.expectation(description: "testFetchTracksInAlbum") 172 | self.API.fetchAlbumTracks(id: "WpTPGzNLeutVFHcFq6", territory: .taiwan) { tracks, paging, summary, error in 173 | e.fulfill() 174 | if let _ = error { 175 | XCTFail("Failed to fetch tracks \(String(describing: error))") 176 | return 177 | } 178 | XCTAssertNotNil(tracks) 179 | XCTAssertTrue(tracks!.count > 0) 180 | for track in tracks! { 181 | self.validate(track: track) 182 | } 183 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 184 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 185 | } 186 | self.wait(for: [e], timeout: 3) 187 | } 188 | 189 | func testFetchArtist() { 190 | self.waitForToken() 191 | let e = self.expectation(description: "testFetchArtist") 192 | self.API.fetchArtist(id: "8q3_xzjl89Yakn_7GB", territory: .taiwan) { artist, error in 193 | e.fulfill() 194 | if let _ = error { 195 | XCTFail("Failed to fetch artist \(String(describing: error))") 196 | return 197 | } 198 | self.validate(artist: artist!) 199 | } 200 | self.wait(for: [e], timeout: 3) 201 | } 202 | 203 | func testFetchArtistAlbums() { 204 | self.waitForToken() 205 | let e = self.expectation(description: "testFetchArtistAlbums") 206 | self.API.fetchArtistAlbums(id: "8q3_xzjl89Yakn_7GB", territory: .taiwan) { albums, paging, summary, error in 207 | e.fulfill() 208 | if let _ = error { 209 | XCTFail("Failed to fetch albums \(String(describing: error))") 210 | return 211 | } 212 | XCTAssertNotNil(albums) 213 | for album in albums! { 214 | self.validate(album: album) 215 | } 216 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 217 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 218 | } 219 | self.wait(for: [e], timeout: 3) 220 | } 221 | 222 | func testFetchTopTracksWithArtistID() { 223 | self.waitForToken() 224 | let e = self.expectation(description: "testFetchTopSongsWithArtistID") 225 | self.API.fetchArtistTopTracks(id: "8q3_xzjl89Yakn_7GB", territory: .taiwan) { songs, paging, summary, error in 226 | e.fulfill() 227 | if let _ = error { 228 | XCTFail("Failed to fetch tracks \(String(describing: error))") 229 | return 230 | } 231 | XCTAssertNotNil(songs) 232 | for track in songs! { 233 | self.validate(track: track) 234 | } 235 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 236 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 237 | } 238 | self.wait(for: [e], timeout: 3) 239 | } 240 | 241 | func testFetchRelatedArtistsWithArtistID() { 242 | self.waitForToken() 243 | let e = self.expectation(description: "testFetchRelatedArtistsWithArtistID") 244 | self.API.fetchRelatedArtists(id: "8q3_xzjl89Yakn_7GB", territory: .taiwan) { artists, paging, summary, error in 245 | e.fulfill() 246 | if let _ = error { 247 | XCTFail("Failed to fetch artist \(String(describing: error))") 248 | return 249 | } 250 | XCTAssertNotNil(artists) 251 | for artist in artists! { 252 | self.validate(artist: artist) 253 | } 254 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 255 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 256 | } 257 | self.wait(for: [e], timeout: 3) 258 | } 259 | 260 | func testFetchPlaylistWithPlaylistID() { 261 | self.waitForToken() 262 | let e = self.expectation(description: "testFetchPlaylistWithPlaylistID") 263 | self.API.fetchPlaylist(id: "OsyceCHOw-NvK5j6Vo", territory: .taiwan) { playlist, paging, summary, error in 264 | e.fulfill() 265 | if let _ = error { 266 | XCTFail("Failed to fetch playlist \(String(describing: error))") 267 | return 268 | } 269 | self.validate(playlist: playlist!) 270 | XCTAssertTrue(playlist!.tracks.count > 0) 271 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 272 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 273 | } 274 | self.wait(for: [e], timeout: 3) 275 | } 276 | 277 | func testFetchSongsInPlaylistWithPlaylistID() { 278 | self.waitForToken() 279 | let e = self.expectation(description: "testFetchSongsInPlaylistWithPlaylistID") 280 | self.API.fetchPlaylistTracks(id: "OsyceCHOw-NvK5j6Vo", territory: .taiwan) { tracks, paging, summary, error in 281 | e.fulfill() 282 | if let _ = error { 283 | XCTFail("Failed to fetch playlist \(String(describing: error))") 284 | return 285 | } 286 | XCTAssertTrue(tracks!.count > 0) 287 | for track in tracks! { 288 | self.validate(track: track) 289 | } 290 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 291 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 292 | } 293 | self.wait(for: [e], timeout: 3) 294 | } 295 | 296 | func testFetchFeaturedPlaylists() { 297 | self.waitForToken() 298 | let e = self.expectation(description: "testFetchFeaturedPlaylists") 299 | self.API.fetchFeaturedPlaylists(territory: .taiwan) { playlists, paging, summary, error in 300 | e.fulfill() 301 | if let _ = error { 302 | XCTFail("Failed to fetch playlist \(String(describing: error))") 303 | return 304 | } 305 | XCTAssertTrue(playlists!.count > 0) 306 | for playlist in playlists! { 307 | self.validate(playlist: playlist) 308 | } 309 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 310 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 311 | } 312 | self.wait(for: [e], timeout: 3) 313 | } 314 | 315 | func testFetchNewHitsPlaylists() { 316 | self.waitForToken() 317 | let e = self.expectation(description: "testFetchNewHitsPlaylists") 318 | self.API.fetchNewHitsPlaylists(territory: .taiwan) { playlists, paging, summary, error in 319 | e.fulfill() 320 | if let _ = error { 321 | XCTFail("Failed to fetch playlist \(String(describing: error))") 322 | return 323 | } 324 | XCTAssertTrue(playlists!.count > 0) 325 | for playlist in playlists! { 326 | self.validate(playlist: playlist) 327 | } 328 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 329 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 330 | } 331 | self.wait(for: [e], timeout: 3) 332 | } 333 | 334 | func testFetchFeaturedPlaylistCategories() { 335 | self.waitForToken() 336 | let e = self.expectation(description: "fetchFeaturedPlaylistCategories") 337 | self.API.fetchFeaturedPlaylistCategories(territory: .taiwan) { categories, paging, summary, error in 338 | e.fulfill() 339 | if let _ = error { 340 | XCTFail("Failed to fetch playlist \(String(describing: error))") 341 | return 342 | } 343 | XCTAssertTrue(categories!.count > 0) 344 | for category in categories! { 345 | XCTAssertTrue(category.id.count > 0) 346 | XCTAssertTrue(category.title.count > 0) 347 | XCTAssertTrue(category.images.count == 2) 348 | } 349 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 350 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 351 | } 352 | self.wait(for: [e], timeout: 3) 353 | } 354 | 355 | func testFetchFeaturedPlaylistInCategory() { 356 | self.waitForToken() 357 | let e = self.expectation(description: "testFetchFeaturedPlaylistInCategory") 358 | self.API.fetchFeaturedPlaylistCategoryPlaylists(category: "CrBHGk1J1KEsQlPLoz", territory: .taiwan) { category, playlists, paging, summary, error in 359 | e.fulfill() 360 | if let _ = error { 361 | XCTFail("Failed to fetch playlist \(String(describing: error))") 362 | return 363 | } 364 | 365 | XCTAssertTrue(category!.id.count > 0) 366 | XCTAssertTrue(category!.title.count > 0) 367 | XCTAssertTrue(category!.images.count == 2) 368 | 369 | for playlist in playlists! { 370 | XCTAssertTrue(playlist.id.count > 0) 371 | XCTAssertTrue(playlist.title.count > 0) 372 | } 373 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 374 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 375 | } 376 | self.wait(for: [e], timeout: 3) 377 | } 378 | 379 | func testFetchMoodStations() { 380 | self.waitForToken() 381 | let e = self.expectation(description: "testFetchMoodStations") 382 | self.API.fetchMoodStations(territory: .taiwan) { stations, paging, summary, error in 383 | e.fulfill() 384 | if let _ = error { 385 | XCTFail("Failed to fetch station \(String(describing: error))") 386 | return 387 | } 388 | for station in stations! { 389 | XCTAssertTrue(station.id.count > 0, "station id") 390 | XCTAssertTrue(station.name.count > 0, "station title") 391 | XCTAssertTrue(station.images.count > 0, "images") 392 | } 393 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 394 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 395 | } 396 | self.wait(for: [e], timeout: 3) 397 | } 398 | 399 | func testFetchMoodStation() { 400 | self.waitForToken() 401 | let e = self.expectation(description: "testFetchMoodStations") 402 | self.API.fetchMoodStation(id: "4tmrBI125HMtMlO9OF", territory: .taiwan) { station, tracks, paging, summary, error in 403 | e.fulfill() 404 | if let _ = error { 405 | XCTFail("Failed to fetch station \(String(describing: error))") 406 | return 407 | } 408 | XCTAssertTrue(station!.id.count > 0, "station id") 409 | XCTAssertTrue(station!.name.count > 0, "station title") 410 | XCTAssertTrue(station!.images.count > 0, "images") 411 | 412 | for track in tracks! { 413 | self.validate(track: track) 414 | } 415 | 416 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 417 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 418 | } 419 | self.wait(for: [e], timeout: 3) 420 | } 421 | 422 | func testFetchGenreStations() { 423 | self.waitForToken() 424 | let e = self.expectation(description: "testFetchGenreStations") 425 | self.API.fetchGenreStations(territory: .taiwan) { stations, paging, summary, error in 426 | e.fulfill() 427 | if let _ = error { 428 | XCTFail("Failed to fetch station \(String(describing: error))") 429 | return 430 | } 431 | for station in stations! { 432 | XCTAssertTrue(station.id.count > 0, "station id") 433 | XCTAssertTrue(station.name.count > 0, "station title") 434 | } 435 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 436 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 437 | } 438 | self.wait(for: [e], timeout: 3) 439 | } 440 | 441 | 442 | func testFetchGenreStation() { 443 | self.waitForToken() 444 | let e = self.expectation(description: "testFetchGenreStation") 445 | self.API.fetchGenreStation(id: "9ZAb9rkyd3JFDBC0wF", territory: .taiwan) { station, tracks, paging, summary, error in 446 | e.fulfill() 447 | if let _ = error { 448 | XCTFail("Failed to fetch station \(String(describing: error))") 449 | return 450 | } 451 | XCTAssertTrue(station!.id.count > 0, "station id") 452 | XCTAssertTrue(station!.name.count > 0, "station title") 453 | 454 | for track in tracks! { 455 | self.validate(track: track) 456 | } 457 | 458 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 459 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 460 | } 461 | self.wait(for: [e], timeout: 3) 462 | } 463 | 464 | func testSearch() { 465 | self.waitForToken() 466 | let e = self.expectation(description: "testSearch") 467 | self.API.search(keyword: "Love", types: [.track, .album, .artist, .playlist], territory: .taiwan) { result, error in 468 | e.fulfill() 469 | if let _ = error { 470 | XCTFail("Failed to search \(String(describing: error))") 471 | return 472 | } 473 | XCTAssertTrue(result!.tracks!.count > 0) 474 | XCTAssertTrue(result!.albums!.count > 0) 475 | XCTAssertTrue(result!.artists!.count > 0) 476 | XCTAssertTrue(result!.playlists!.count > 0) 477 | } 478 | 479 | self.wait(for: [e], timeout: 10) 480 | } 481 | 482 | func testFetchNewReleaseAlbumCategories() { 483 | self.waitForToken() 484 | let e = self.expectation(description: "testFetchNewReleaseAlbumCategories") 485 | self.API.fetchNewReleaseAlbumCategories(territory: .taiwan) { categories, paging, summary, error in 486 | e.fulfill() 487 | if let _ = error { 488 | XCTFail("Failed to fetch categories. \(String(describing: error))") 489 | return 490 | } 491 | for category in categories! { 492 | XCTAssertTrue(category.id.count > 0) 493 | XCTAssertTrue(category.title.count > 0) 494 | } 495 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 496 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 497 | } 498 | self.wait(for: [e], timeout: 3) 499 | } 500 | 501 | func testFetchNewReleaseAlbumsUnderCategory() { 502 | self.waitForToken() 503 | let e = self.expectation(description: "testFetchNewReleaseAlbumsUnderCategory") 504 | self.API.fetchNewReleaseAlbums(id: "0pGAIGDf5SqYh_SyHr", territory: .taiwan) { category, albums, paging, summary, error in 505 | e.fulfill() 506 | if let _ = error { 507 | XCTFail("Failed to fetch albums. \(String(describing: error))") 508 | return 509 | } 510 | 511 | XCTAssertTrue(category!.id.count > 0) 512 | XCTAssertTrue(category!.title.count > 0) 513 | 514 | for album in albums! { 515 | self.validate(album: album) 516 | } 517 | 518 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 519 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 520 | } 521 | self.wait(for: [e], timeout: 3) 522 | } 523 | 524 | func testFetchCharts() { 525 | self.waitForToken() 526 | let e = self.expectation(description: "testFetchCharts") 527 | self.API.fetchCharts(territory: .taiwan) { charts, paging, summary, error in 528 | e.fulfill() 529 | if let _ = error { 530 | XCTFail("Failed to fetch charts. \(String(describing: error))") 531 | return 532 | } 533 | 534 | for playlist in charts! { 535 | self.validate(playlist: playlist) 536 | } 537 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 538 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 539 | } 540 | self.wait(for: [e], timeout: 3) 541 | } 542 | 543 | func testFetchChildrenCategories() { 544 | self.waitForToken() 545 | let e = self.expectation(description: "testFetchChildrenCategories") 546 | self.API.fetchChildrenCategories(territory: .taiwan) { categories, paging, summary, error in 547 | e.fulfill() 548 | if let _ = error { 549 | XCTFail("Failed to fetch charts. \(String(describing: error))") 550 | return 551 | } 552 | for category in categories! { 553 | XCTAssertNotNil(category.id) 554 | XCTAssertNotNil(category.title) 555 | XCTAssertTrue(category.images.count > 0) 556 | } 557 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 558 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 559 | } 560 | self.wait(for: [e], timeout: 3) 561 | } 562 | 563 | func testFetchChildrenCategory() { 564 | self.waitForToken() 565 | let e = self.expectation(description: "testFetchChildrenCategory") 566 | self.API.fetchChildrenCategory(id: "Ksb_8l5NAnG7pCJEUU", territory: .taiwan) { group, paging, summary, error in 567 | e.fulfill() 568 | if let _ = error { 569 | XCTFail("Failed to fetch charts. \(String(describing: error))") 570 | return 571 | } 572 | XCTAssertNotNil(group!.id) 573 | XCTAssertNotNil(group!.title) 574 | XCTAssertTrue(group!.images.count > 0) 575 | XCTAssertTrue(group!.subcategories.count > 0) 576 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 577 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 578 | } 579 | self.wait(for: [e], timeout: 3) 580 | } 581 | 582 | func testfetchChildrenCategoryPlaylists() { 583 | self.waitForToken() 584 | let e = self.expectation(description: "testfetchChildrenCategoryPlaylists") 585 | self.API.fetchChildrenCategoryPlaylists(id: "-rUEH1DCQOsYM4aN39", territory: .taiwan, offset: 0, limit: 100) { playlists, paging, summary, error in 586 | e.fulfill() 587 | if let _ = error { 588 | XCTFail("Failed to fetch charts. \(String(describing: error))") 589 | return 590 | } 591 | 592 | for playlist in playlists! { 593 | self.validate(playlist: playlist) 594 | } 595 | XCTAssertNotNil(paging); XCTAssertTrue(paging!.limit > 0) 596 | XCTAssertNotNil(summary); XCTAssertTrue(summary!.total > 0) 597 | } 598 | self.wait(for: [e], timeout: 3) 599 | } 600 | 601 | } 602 | 603 | -------------------------------------------------------------------------------- /KKBOXOpenAPI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = "1"; 4 | objectVersion = "46"; 5 | objects = { 6 | "KKBOXOpenAPI::KKBOXOpenAPI" = { 7 | isa = "PBXNativeTarget"; 8 | buildConfigurationList = "OBJ_32"; 9 | buildPhases = ( 10 | "OBJ_35", 11 | "OBJ_40", 12 | "OBJ_44" 13 | ); 14 | dependencies = ( 15 | ); 16 | name = "KKBOXOpenAPI"; 17 | productName = "KKBOXOpenAPI"; 18 | productReference = "KKBOXOpenAPI::KKBOXOpenAPI::Product"; 19 | productType = "com.apple.product-type.framework"; 20 | }; 21 | "KKBOXOpenAPI::KKBOXOpenAPI::Product" = { 22 | isa = "PBXFileReference"; 23 | path = "KKBOXOpenAPI.framework"; 24 | sourceTree = "BUILT_PRODUCTS_DIR"; 25 | }; 26 | "KKBOXOpenAPI::KKBOXOpenAPIPackageTests::ProductTarget" = { 27 | isa = "PBXAggregateTarget"; 28 | buildConfigurationList = "OBJ_52"; 29 | buildPhases = ( 30 | ); 31 | dependencies = ( 32 | "OBJ_55" 33 | ); 34 | name = "KKBOXOpenAPIPackageTests"; 35 | productName = "KKBOXOpenAPIPackageTests"; 36 | }; 37 | "KKBOXOpenAPI::KKBOXOpenAPITests" = { 38 | isa = "PBXNativeTarget"; 39 | buildConfigurationList = "OBJ_57"; 40 | buildPhases = ( 41 | "OBJ_60", 42 | "OBJ_63" 43 | ); 44 | dependencies = ( 45 | "OBJ_65" 46 | ); 47 | name = "KKBOXOpenAPITests"; 48 | productName = "KKBOXOpenAPITests"; 49 | productReference = "KKBOXOpenAPI::KKBOXOpenAPITests::Product"; 50 | productType = "com.apple.product-type.bundle.unit-test"; 51 | }; 52 | "KKBOXOpenAPI::KKBOXOpenAPITests::Product" = { 53 | isa = "PBXFileReference"; 54 | path = "KKBOXOpenAPITests.xctest"; 55 | sourceTree = "BUILT_PRODUCTS_DIR"; 56 | }; 57 | "KKBOXOpenAPI::SwiftPMPackageDescription" = { 58 | isa = "PBXNativeTarget"; 59 | buildConfigurationList = "OBJ_46"; 60 | buildPhases = ( 61 | "OBJ_49" 62 | ); 63 | dependencies = ( 64 | ); 65 | name = "KKBOXOpenAPIPackageDescription"; 66 | productName = "KKBOXOpenAPIPackageDescription"; 67 | productType = "com.apple.product-type.framework"; 68 | }; 69 | "OBJ_1" = { 70 | isa = "PBXProject"; 71 | attributes = { 72 | LastSwiftMigration = "9999"; 73 | LastUpgradeCheck = "9999"; 74 | }; 75 | buildConfigurationList = "OBJ_2"; 76 | compatibilityVersion = "Xcode 3.2"; 77 | developmentRegion = "en"; 78 | hasScannedForEncodings = "0"; 79 | knownRegions = ( 80 | "en" 81 | ); 82 | mainGroup = "OBJ_5"; 83 | productRefGroup = "OBJ_22"; 84 | projectDirPath = "."; 85 | targets = ( 86 | "KKBOXOpenAPI::KKBOXOpenAPI", 87 | "KKBOXOpenAPI::SwiftPMPackageDescription", 88 | "KKBOXOpenAPI::KKBOXOpenAPIPackageTests::ProductTarget", 89 | "KKBOXOpenAPI::KKBOXOpenAPITests" 90 | ); 91 | }; 92 | "OBJ_10" = { 93 | isa = "PBXFileReference"; 94 | path = "NSData+LFHTTPFormExtensions.m"; 95 | sourceTree = ""; 96 | }; 97 | "OBJ_11" = { 98 | isa = "PBXFileReference"; 99 | path = "OpenAPI+Privates.m"; 100 | sourceTree = ""; 101 | }; 102 | "OBJ_12" = { 103 | isa = "PBXFileReference"; 104 | path = "OpenAPI.m"; 105 | sourceTree = ""; 106 | }; 107 | "OBJ_13" = { 108 | isa = "PBXFileReference"; 109 | path = "OpenAPIObjects.m"; 110 | sourceTree = ""; 111 | }; 112 | "OBJ_14" = { 113 | isa = "PBXGroup"; 114 | children = ( 115 | "OBJ_15", 116 | "OBJ_16", 117 | "OBJ_17" 118 | ); 119 | name = "include"; 120 | path = "include"; 121 | sourceTree = ""; 122 | }; 123 | "OBJ_15" = { 124 | isa = "PBXFileReference"; 125 | path = "OpenAPI.h"; 126 | sourceTree = ""; 127 | }; 128 | "OBJ_16" = { 129 | isa = "PBXFileReference"; 130 | path = "OpenAPIObjects.h"; 131 | sourceTree = ""; 132 | }; 133 | "OBJ_17" = { 134 | isa = "PBXFileReference"; 135 | path = "KKBOXOpenAPI.h"; 136 | sourceTree = ""; 137 | }; 138 | "OBJ_18" = { 139 | isa = "PBXGroup"; 140 | children = ( 141 | "OBJ_19" 142 | ); 143 | name = "Tests"; 144 | path = ""; 145 | sourceTree = "SOURCE_ROOT"; 146 | }; 147 | "OBJ_19" = { 148 | isa = "PBXGroup"; 149 | children = ( 150 | "OBJ_20", 151 | "OBJ_21" 152 | ); 153 | name = "KKBOXOpenAPITests"; 154 | path = "Tests/KKBOXOpenAPITests"; 155 | sourceTree = "SOURCE_ROOT"; 156 | }; 157 | "OBJ_2" = { 158 | isa = "XCConfigurationList"; 159 | buildConfigurations = ( 160 | "OBJ_3", 161 | "OBJ_4" 162 | ); 163 | defaultConfigurationIsVisible = "0"; 164 | defaultConfigurationName = "Release"; 165 | }; 166 | "OBJ_20" = { 167 | isa = "PBXFileReference"; 168 | path = "Tests.swift"; 169 | sourceTree = ""; 170 | }; 171 | "OBJ_21" = { 172 | isa = "PBXFileReference"; 173 | path = "XCTestManifests.swift"; 174 | sourceTree = ""; 175 | }; 176 | "OBJ_22" = { 177 | isa = "PBXGroup"; 178 | children = ( 179 | "KKBOXOpenAPI::KKBOXOpenAPI::Product", 180 | "KKBOXOpenAPI::KKBOXOpenAPITests::Product" 181 | ); 182 | name = "Products"; 183 | path = ""; 184 | sourceTree = "BUILT_PRODUCTS_DIR"; 185 | }; 186 | "OBJ_25" = { 187 | isa = "PBXFileReference"; 188 | path = "docs"; 189 | sourceTree = "SOURCE_ROOT"; 190 | }; 191 | "OBJ_26" = { 192 | isa = "PBXFileReference"; 193 | path = "ExampleIOS"; 194 | sourceTree = "SOURCE_ROOT"; 195 | }; 196 | "OBJ_27" = { 197 | isa = "PBXFileReference"; 198 | path = "jazzy.sh"; 199 | sourceTree = ""; 200 | }; 201 | "OBJ_28" = { 202 | isa = "PBXFileReference"; 203 | path = "KKBOXOpenAPI.podspec"; 204 | sourceTree = ""; 205 | }; 206 | "OBJ_29" = { 207 | isa = "PBXFileReference"; 208 | path = "README.md"; 209 | sourceTree = ""; 210 | }; 211 | "OBJ_3" = { 212 | isa = "XCBuildConfiguration"; 213 | buildSettings = { 214 | CLANG_ENABLE_OBJC_ARC = "YES"; 215 | COMBINE_HIDPI_IMAGES = "YES"; 216 | COPY_PHASE_STRIP = "NO"; 217 | DEBUG_INFORMATION_FORMAT = "dwarf"; 218 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 219 | ENABLE_NS_ASSERTIONS = "YES"; 220 | GCC_OPTIMIZATION_LEVEL = "0"; 221 | GCC_PREPROCESSOR_DEFINITIONS = ( 222 | "$(inherited)", 223 | "SWIFT_PACKAGE=1", 224 | "DEBUG=1" 225 | ); 226 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 227 | ONLY_ACTIVE_ARCH = "YES"; 228 | OTHER_SWIFT_FLAGS = ( 229 | "$(inherited)", 230 | "-DXcode" 231 | ); 232 | PRODUCT_NAME = "$(TARGET_NAME)"; 233 | SDKROOT = "macosx"; 234 | SUPPORTED_PLATFORMS = ( 235 | "macosx", 236 | "iphoneos", 237 | "iphonesimulator", 238 | "appletvos", 239 | "appletvsimulator", 240 | "watchos", 241 | "watchsimulator" 242 | ); 243 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 244 | "$(inherited)", 245 | "SWIFT_PACKAGE", 246 | "DEBUG" 247 | ); 248 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 249 | USE_HEADERMAP = "NO"; 250 | }; 251 | name = "Debug"; 252 | }; 253 | "OBJ_30" = { 254 | isa = "PBXFileReference"; 255 | path = "LICENSE.txt"; 256 | sourceTree = ""; 257 | }; 258 | "OBJ_32" = { 259 | isa = "XCConfigurationList"; 260 | buildConfigurations = ( 261 | "OBJ_33", 262 | "OBJ_34" 263 | ); 264 | defaultConfigurationIsVisible = "0"; 265 | defaultConfigurationName = "Release"; 266 | }; 267 | "OBJ_33" = { 268 | isa = "XCBuildConfiguration"; 269 | buildSettings = { 270 | CLANG_ENABLE_MODULES = "YES"; 271 | DEFINES_MODULE = "YES"; 272 | ENABLE_TESTABILITY = "YES"; 273 | FRAMEWORK_SEARCH_PATHS = ( 274 | "$(inherited)", 275 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 276 | ); 277 | HEADER_SEARCH_PATHS = ( 278 | "$(inherited)", 279 | "$(SRCROOT)/Sources/KKBOXOpenAPI/include" 280 | ); 281 | INFOPLIST_FILE = "KKBOXOpenAPI.xcodeproj/KKBOXOpenAPI_Info.plist"; 282 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 283 | LD_RUNPATH_SEARCH_PATHS = ( 284 | "$(inherited)", 285 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 286 | ); 287 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 288 | OTHER_CFLAGS = ( 289 | "$(inherited)" 290 | ); 291 | OTHER_LDFLAGS = ( 292 | "$(inherited)" 293 | ); 294 | OTHER_SWIFT_FLAGS = ( 295 | "$(inherited)" 296 | ); 297 | PRODUCT_BUNDLE_IDENTIFIER = "KKBOXOpenAPI"; 298 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 299 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 300 | SKIP_INSTALL = "YES"; 301 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 302 | "$(inherited)" 303 | ); 304 | TARGET_NAME = "KKBOXOpenAPI"; 305 | TVOS_DEPLOYMENT_TARGET = "9.0"; 306 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 307 | }; 308 | name = "Debug"; 309 | }; 310 | "OBJ_34" = { 311 | isa = "XCBuildConfiguration"; 312 | buildSettings = { 313 | CLANG_ENABLE_MODULES = "YES"; 314 | DEFINES_MODULE = "YES"; 315 | ENABLE_TESTABILITY = "YES"; 316 | FRAMEWORK_SEARCH_PATHS = ( 317 | "$(inherited)", 318 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 319 | ); 320 | HEADER_SEARCH_PATHS = ( 321 | "$(inherited)", 322 | "$(SRCROOT)/Sources/KKBOXOpenAPI/include" 323 | ); 324 | INFOPLIST_FILE = "KKBOXOpenAPI.xcodeproj/KKBOXOpenAPI_Info.plist"; 325 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 326 | LD_RUNPATH_SEARCH_PATHS = ( 327 | "$(inherited)", 328 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" 329 | ); 330 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 331 | OTHER_CFLAGS = ( 332 | "$(inherited)" 333 | ); 334 | OTHER_LDFLAGS = ( 335 | "$(inherited)" 336 | ); 337 | OTHER_SWIFT_FLAGS = ( 338 | "$(inherited)" 339 | ); 340 | PRODUCT_BUNDLE_IDENTIFIER = "KKBOXOpenAPI"; 341 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 342 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 343 | SKIP_INSTALL = "YES"; 344 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 345 | "$(inherited)" 346 | ); 347 | TARGET_NAME = "KKBOXOpenAPI"; 348 | TVOS_DEPLOYMENT_TARGET = "9.0"; 349 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 350 | }; 351 | name = "Release"; 352 | }; 353 | "OBJ_35" = { 354 | isa = "PBXSourcesBuildPhase"; 355 | files = ( 356 | "OBJ_36", 357 | "OBJ_37", 358 | "OBJ_38", 359 | "OBJ_39" 360 | ); 361 | }; 362 | "OBJ_36" = { 363 | isa = "PBXBuildFile"; 364 | fileRef = "OBJ_10"; 365 | }; 366 | "OBJ_37" = { 367 | isa = "PBXBuildFile"; 368 | fileRef = "OBJ_11"; 369 | }; 370 | "OBJ_38" = { 371 | isa = "PBXBuildFile"; 372 | fileRef = "OBJ_12"; 373 | }; 374 | "OBJ_39" = { 375 | isa = "PBXBuildFile"; 376 | fileRef = "OBJ_13"; 377 | }; 378 | "OBJ_4" = { 379 | isa = "XCBuildConfiguration"; 380 | buildSettings = { 381 | CLANG_ENABLE_OBJC_ARC = "YES"; 382 | COMBINE_HIDPI_IMAGES = "YES"; 383 | COPY_PHASE_STRIP = "YES"; 384 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 385 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 386 | GCC_OPTIMIZATION_LEVEL = "s"; 387 | GCC_PREPROCESSOR_DEFINITIONS = ( 388 | "$(inherited)", 389 | "SWIFT_PACKAGE=1" 390 | ); 391 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 392 | OTHER_SWIFT_FLAGS = ( 393 | "$(inherited)", 394 | "-DXcode" 395 | ); 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | SDKROOT = "macosx"; 398 | SUPPORTED_PLATFORMS = ( 399 | "macosx", 400 | "iphoneos", 401 | "iphonesimulator", 402 | "appletvos", 403 | "appletvsimulator", 404 | "watchos", 405 | "watchsimulator" 406 | ); 407 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 408 | "$(inherited)", 409 | "SWIFT_PACKAGE" 410 | ); 411 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 412 | USE_HEADERMAP = "NO"; 413 | }; 414 | name = "Release"; 415 | }; 416 | "OBJ_40" = { 417 | isa = "PBXHeadersBuildPhase"; 418 | files = ( 419 | "OBJ_41", 420 | "OBJ_42", 421 | "OBJ_43" 422 | ); 423 | }; 424 | "OBJ_41" = { 425 | isa = "PBXBuildFile"; 426 | fileRef = "OBJ_15"; 427 | settings = { 428 | ATTRIBUTES = ( 429 | "Public" 430 | ); 431 | }; 432 | }; 433 | "OBJ_42" = { 434 | isa = "PBXBuildFile"; 435 | fileRef = "OBJ_16"; 436 | settings = { 437 | ATTRIBUTES = ( 438 | "Public" 439 | ); 440 | }; 441 | }; 442 | "OBJ_43" = { 443 | isa = "PBXBuildFile"; 444 | fileRef = "OBJ_17"; 445 | settings = { 446 | ATTRIBUTES = ( 447 | "Public" 448 | ); 449 | }; 450 | }; 451 | "OBJ_44" = { 452 | isa = "PBXFrameworksBuildPhase"; 453 | files = ( 454 | ); 455 | }; 456 | "OBJ_46" = { 457 | isa = "XCConfigurationList"; 458 | buildConfigurations = ( 459 | "OBJ_47", 460 | "OBJ_48" 461 | ); 462 | defaultConfigurationIsVisible = "0"; 463 | defaultConfigurationName = "Release"; 464 | }; 465 | "OBJ_47" = { 466 | isa = "XCBuildConfiguration"; 467 | buildSettings = { 468 | LD = "/usr/bin/true"; 469 | OTHER_SWIFT_FLAGS = ( 470 | "-swift-version", 471 | "5", 472 | "-I", 473 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 474 | "-target", 475 | "x86_64-apple-macosx10.10", 476 | "-sdk", 477 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 478 | "-package-description-version", 479 | "5.1" 480 | ); 481 | SWIFT_VERSION = "5.0"; 482 | }; 483 | name = "Debug"; 484 | }; 485 | "OBJ_48" = { 486 | isa = "XCBuildConfiguration"; 487 | buildSettings = { 488 | LD = "/usr/bin/true"; 489 | OTHER_SWIFT_FLAGS = ( 490 | "-swift-version", 491 | "5", 492 | "-I", 493 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 494 | "-target", 495 | "x86_64-apple-macosx10.10", 496 | "-sdk", 497 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 498 | "-package-description-version", 499 | "5.1" 500 | ); 501 | SWIFT_VERSION = "5.0"; 502 | }; 503 | name = "Release"; 504 | }; 505 | "OBJ_49" = { 506 | isa = "PBXSourcesBuildPhase"; 507 | files = ( 508 | "OBJ_50" 509 | ); 510 | }; 511 | "OBJ_5" = { 512 | isa = "PBXGroup"; 513 | children = ( 514 | "OBJ_6", 515 | "OBJ_7", 516 | "OBJ_18", 517 | "OBJ_22", 518 | "OBJ_25", 519 | "OBJ_26", 520 | "OBJ_27", 521 | "OBJ_28", 522 | "OBJ_29", 523 | "OBJ_30" 524 | ); 525 | path = ""; 526 | sourceTree = ""; 527 | }; 528 | "OBJ_50" = { 529 | isa = "PBXBuildFile"; 530 | fileRef = "OBJ_6"; 531 | }; 532 | "OBJ_52" = { 533 | isa = "XCConfigurationList"; 534 | buildConfigurations = ( 535 | "OBJ_53", 536 | "OBJ_54" 537 | ); 538 | defaultConfigurationIsVisible = "0"; 539 | defaultConfigurationName = "Release"; 540 | }; 541 | "OBJ_53" = { 542 | isa = "XCBuildConfiguration"; 543 | buildSettings = { 544 | }; 545 | name = "Debug"; 546 | }; 547 | "OBJ_54" = { 548 | isa = "XCBuildConfiguration"; 549 | buildSettings = { 550 | }; 551 | name = "Release"; 552 | }; 553 | "OBJ_55" = { 554 | isa = "PBXTargetDependency"; 555 | target = "KKBOXOpenAPI::KKBOXOpenAPITests"; 556 | }; 557 | "OBJ_57" = { 558 | isa = "XCConfigurationList"; 559 | buildConfigurations = ( 560 | "OBJ_58", 561 | "OBJ_59" 562 | ); 563 | defaultConfigurationIsVisible = "0"; 564 | defaultConfigurationName = "Release"; 565 | }; 566 | "OBJ_58" = { 567 | isa = "XCBuildConfiguration"; 568 | buildSettings = { 569 | CLANG_ENABLE_MODULES = "YES"; 570 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 571 | FRAMEWORK_SEARCH_PATHS = ( 572 | "$(inherited)", 573 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 574 | ); 575 | HEADER_SEARCH_PATHS = ( 576 | "$(inherited)", 577 | "$(SRCROOT)/Sources/KKBOXOpenAPI/include" 578 | ); 579 | INFOPLIST_FILE = "KKBOXOpenAPI.xcodeproj/KKBOXOpenAPITests_Info.plist"; 580 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 581 | LD_RUNPATH_SEARCH_PATHS = ( 582 | "$(inherited)", 583 | "@loader_path/../Frameworks", 584 | "@loader_path/Frameworks" 585 | ); 586 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 587 | OTHER_CFLAGS = ( 588 | "$(inherited)" 589 | ); 590 | OTHER_LDFLAGS = ( 591 | "$(inherited)" 592 | ); 593 | OTHER_SWIFT_FLAGS = ( 594 | "$(inherited)" 595 | ); 596 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 597 | "$(inherited)" 598 | ); 599 | SWIFT_VERSION = "5.0"; 600 | TARGET_NAME = "KKBOXOpenAPITests"; 601 | TVOS_DEPLOYMENT_TARGET = "9.0"; 602 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 603 | }; 604 | name = "Debug"; 605 | }; 606 | "OBJ_59" = { 607 | isa = "XCBuildConfiguration"; 608 | buildSettings = { 609 | CLANG_ENABLE_MODULES = "YES"; 610 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 611 | FRAMEWORK_SEARCH_PATHS = ( 612 | "$(inherited)", 613 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 614 | ); 615 | HEADER_SEARCH_PATHS = ( 616 | "$(inherited)", 617 | "$(SRCROOT)/Sources/KKBOXOpenAPI/include" 618 | ); 619 | INFOPLIST_FILE = "KKBOXOpenAPI.xcodeproj/KKBOXOpenAPITests_Info.plist"; 620 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 621 | LD_RUNPATH_SEARCH_PATHS = ( 622 | "$(inherited)", 623 | "@loader_path/../Frameworks", 624 | "@loader_path/Frameworks" 625 | ); 626 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 627 | OTHER_CFLAGS = ( 628 | "$(inherited)" 629 | ); 630 | OTHER_LDFLAGS = ( 631 | "$(inherited)" 632 | ); 633 | OTHER_SWIFT_FLAGS = ( 634 | "$(inherited)" 635 | ); 636 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 637 | "$(inherited)" 638 | ); 639 | SWIFT_VERSION = "5.0"; 640 | TARGET_NAME = "KKBOXOpenAPITests"; 641 | TVOS_DEPLOYMENT_TARGET = "9.0"; 642 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 643 | }; 644 | name = "Release"; 645 | }; 646 | "OBJ_6" = { 647 | isa = "PBXFileReference"; 648 | explicitFileType = "sourcecode.swift"; 649 | path = "Package.swift"; 650 | sourceTree = ""; 651 | }; 652 | "OBJ_60" = { 653 | isa = "PBXSourcesBuildPhase"; 654 | files = ( 655 | "OBJ_61", 656 | "OBJ_62" 657 | ); 658 | }; 659 | "OBJ_61" = { 660 | isa = "PBXBuildFile"; 661 | fileRef = "OBJ_20"; 662 | }; 663 | "OBJ_62" = { 664 | isa = "PBXBuildFile"; 665 | fileRef = "OBJ_21"; 666 | }; 667 | "OBJ_63" = { 668 | isa = "PBXFrameworksBuildPhase"; 669 | files = ( 670 | "OBJ_64" 671 | ); 672 | }; 673 | "OBJ_64" = { 674 | isa = "PBXBuildFile"; 675 | fileRef = "KKBOXOpenAPI::KKBOXOpenAPI::Product"; 676 | }; 677 | "OBJ_65" = { 678 | isa = "PBXTargetDependency"; 679 | target = "KKBOXOpenAPI::KKBOXOpenAPI"; 680 | }; 681 | "OBJ_7" = { 682 | isa = "PBXGroup"; 683 | children = ( 684 | "OBJ_8" 685 | ); 686 | name = "Sources"; 687 | path = ""; 688 | sourceTree = "SOURCE_ROOT"; 689 | }; 690 | "OBJ_8" = { 691 | isa = "PBXGroup"; 692 | children = ( 693 | "OBJ_9", 694 | "OBJ_10", 695 | "OBJ_11", 696 | "OBJ_12", 697 | "OBJ_13", 698 | "OBJ_14" 699 | ); 700 | name = "KKBOXOpenAPI"; 701 | path = "Sources/KKBOXOpenAPI"; 702 | sourceTree = "SOURCE_ROOT"; 703 | }; 704 | "OBJ_9" = { 705 | isa = "PBXFileReference"; 706 | path = "NSData+LFHTTPFormExtensions.h"; 707 | sourceTree = ""; 708 | }; 709 | }; 710 | rootObject = "OBJ_1"; 711 | } 712 | -------------------------------------------------------------------------------- /ExampleIOS/KKBOXOpenAPIDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 98DA8F7B47DA756A6E37453C /* Pods_KKBOXOpenAPIDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB16D1B07F7B9EAA4DB46BD5 /* Pods_KKBOXOpenAPIDemo.framework */; }; 11 | D41EFF771C58C832000B4D10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41EFF761C58C832000B4D10 /* AppDelegate.swift */; }; 12 | D41EFF821C58C832000B4D10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D41EFF811C58C832000B4D10 /* Assets.xcassets */; }; 13 | D41EFF851C58C832000B4D10 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D41EFF831C58C832000B4D10 /* LaunchScreen.storyboard */; }; 14 | D41EFF901C58C832000B4D10 /* KKBOXOpenAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41EFF8F1C58C832000B4D10 /* KKBOXOpenAPITests.swift */; }; 15 | D4B2EEE71F24493F005A37F6 /* KKMainTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D1FCA51C59400E0042ECF7 /* KKMainTableViewController.swift */; }; 16 | D4D1FCA81C5942800042ECF7 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D1FCA71C5942800042ECF7 /* API.swift */; }; 17 | D4D5450F1FF12CA400A2A00E /* KKFeaturedPlaylistsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D5450E1FF12CA400A2A00E /* KKFeaturedPlaylistsTableViewController.swift */; }; 18 | D4D545111FF1333800A2A00E /* KKPlaylistTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D545101FF1333800A2A00E /* KKPlaylistTableViewController.swift */; }; 19 | D4D545131FF13B5600A2A00E /* KKFeaturedPlaylistCategoriesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D545121FF13B5500A2A00E /* KKFeaturedPlaylistCategoriesTableViewController.swift */; }; 20 | D4D545151FF13D6700A2A00E /* KKFeaturedPlaylistCategoryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D545141FF13D6700A2A00E /* KKFeaturedPlaylistCategoryTableViewController.swift */; }; 21 | D4D545171FF143F800A2A00E /* KKNewHitsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D545161FF143F800A2A00E /* KKNewHitsTableViewController.swift */; }; 22 | D4D545191FF1460000A2A00E /* KKChartsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4D545181FF1460000A2A00E /* KKChartsTableViewController.swift */; }; 23 | D4E0919A1C74EE3A0018F379 /* KKTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E091991C74EE3A0018F379 /* KKTextViewController.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | D41EFF8C1C58C832000B4D10 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = D41EFF6B1C58C832000B4D10 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = D41EFF721C58C832000B4D10; 32 | remoteInfo = KKBOXOpenAPI; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXCopyFilesBuildPhase section */ 37 | D4E4B0F01D7C76370026A356 /* Embed Frameworks */ = { 38 | isa = PBXCopyFilesBuildPhase; 39 | buildActionMask = 2147483647; 40 | dstPath = ""; 41 | dstSubfolderSpec = 10; 42 | files = ( 43 | ); 44 | name = "Embed Frameworks"; 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXCopyFilesBuildPhase section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 094F7029882721C2B48BA779 /* Pods-KKBOXOpenAPIDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KKBOXOpenAPIDemo.debug.xcconfig"; path = "Target Support Files/Pods-KKBOXOpenAPIDemo/Pods-KKBOXOpenAPIDemo.debug.xcconfig"; sourceTree = ""; }; 51 | 80AFC7EBE8C01A6AFD028D1C /* Pods-KKBOXOpenAPI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KKBOXOpenAPI.release.xcconfig"; path = "Target Support Files/Pods-KKBOXOpenAPI/Pods-KKBOXOpenAPI.release.xcconfig"; sourceTree = ""; }; 52 | A411F335ED9A2249F93A4EA2 /* Pods-KKBOXOpenAPIDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KKBOXOpenAPIDemo.release.xcconfig"; path = "Target Support Files/Pods-KKBOXOpenAPIDemo/Pods-KKBOXOpenAPIDemo.release.xcconfig"; sourceTree = ""; }; 53 | B314CF84CE8ED77B30EC9DC2 /* Pods-KKBOXOpenAPI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KKBOXOpenAPI.debug.xcconfig"; path = "Target Support Files/Pods-KKBOXOpenAPI/Pods-KKBOXOpenAPI.debug.xcconfig"; sourceTree = ""; }; 54 | BB16D1B07F7B9EAA4DB46BD5 /* Pods_KKBOXOpenAPIDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KKBOXOpenAPIDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | D41EFF731C58C832000B4D10 /* KKBOXOpenAPIDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KKBOXOpenAPIDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | D41EFF761C58C832000B4D10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 57 | D41EFF811C58C832000B4D10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 58 | D41EFF841C58C832000B4D10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 59 | D41EFF861C58C832000B4D10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | D41EFF8B1C58C832000B4D10 /* KKBOXOpenAPIDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KKBOXOpenAPIDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | D41EFF8F1C58C832000B4D10 /* KKBOXOpenAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KKBOXOpenAPITests.swift; sourceTree = ""; }; 62 | D41EFF911C58C833000B4D10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | D44FDCAD23EBCED500E8B881 /* KKBOXOpenAPIDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KKBOXOpenAPIDemo.entitlements; sourceTree = ""; }; 64 | D4D1FCA51C59400E0042ECF7 /* KKMainTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KKMainTableViewController.swift; sourceTree = ""; }; 65 | D4D1FCA71C5942800042ECF7 /* API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; 66 | D4D5450E1FF12CA400A2A00E /* KKFeaturedPlaylistsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KKFeaturedPlaylistsTableViewController.swift; sourceTree = ""; }; 67 | D4D545101FF1333800A2A00E /* KKPlaylistTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KKPlaylistTableViewController.swift; sourceTree = ""; }; 68 | D4D545121FF13B5500A2A00E /* KKFeaturedPlaylistCategoriesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KKFeaturedPlaylistCategoriesTableViewController.swift; sourceTree = ""; }; 69 | D4D545141FF13D6700A2A00E /* KKFeaturedPlaylistCategoryTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KKFeaturedPlaylistCategoryTableViewController.swift; sourceTree = ""; }; 70 | D4D545161FF143F800A2A00E /* KKNewHitsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KKNewHitsTableViewController.swift; sourceTree = ""; }; 71 | D4D545181FF1460000A2A00E /* KKChartsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KKChartsTableViewController.swift; sourceTree = ""; }; 72 | D4E091991C74EE3A0018F379 /* KKTextViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KKTextViewController.swift; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | D41EFF701C58C832000B4D10 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | 98DA8F7B47DA756A6E37453C /* Pods_KKBOXOpenAPIDemo.framework in Frameworks */, 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | D41EFF881C58C832000B4D10 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 7AE6883BC62FEC0A6D40A512 /* Frameworks */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | BB16D1B07F7B9EAA4DB46BD5 /* Pods_KKBOXOpenAPIDemo.framework */, 98 | ); 99 | name = Frameworks; 100 | sourceTree = ""; 101 | }; 102 | 847C40962FBA870834565A2D /* Pods */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | B314CF84CE8ED77B30EC9DC2 /* Pods-KKBOXOpenAPI.debug.xcconfig */, 106 | 80AFC7EBE8C01A6AFD028D1C /* Pods-KKBOXOpenAPI.release.xcconfig */, 107 | 094F7029882721C2B48BA779 /* Pods-KKBOXOpenAPIDemo.debug.xcconfig */, 108 | A411F335ED9A2249F93A4EA2 /* Pods-KKBOXOpenAPIDemo.release.xcconfig */, 109 | ); 110 | path = Pods; 111 | sourceTree = ""; 112 | }; 113 | D41EFF6A1C58C832000B4D10 = { 114 | isa = PBXGroup; 115 | children = ( 116 | D44FDCAD23EBCED500E8B881 /* KKBOXOpenAPIDemo.entitlements */, 117 | D41EFF751C58C832000B4D10 /* KKBOXOpenAPI */, 118 | D41EFF8E1C58C832000B4D10 /* KKBOXOpenAPITests */, 119 | D41EFF741C58C832000B4D10 /* Products */, 120 | 847C40962FBA870834565A2D /* Pods */, 121 | 7AE6883BC62FEC0A6D40A512 /* Frameworks */, 122 | ); 123 | sourceTree = ""; 124 | }; 125 | D41EFF741C58C832000B4D10 /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | D41EFF731C58C832000B4D10 /* KKBOXOpenAPIDemo.app */, 129 | D41EFF8B1C58C832000B4D10 /* KKBOXOpenAPIDemoTests.xctest */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | D41EFF751C58C832000B4D10 /* KKBOXOpenAPI */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | D41EFF761C58C832000B4D10 /* AppDelegate.swift */, 138 | D4D1FCA71C5942800042ECF7 /* API.swift */, 139 | D4D1FCA51C59400E0042ECF7 /* KKMainTableViewController.swift */, 140 | D4D5450E1FF12CA400A2A00E /* KKFeaturedPlaylistsTableViewController.swift */, 141 | D4D545101FF1333800A2A00E /* KKPlaylistTableViewController.swift */, 142 | D4D545121FF13B5500A2A00E /* KKFeaturedPlaylistCategoriesTableViewController.swift */, 143 | D4D545141FF13D6700A2A00E /* KKFeaturedPlaylistCategoryTableViewController.swift */, 144 | D4D545161FF143F800A2A00E /* KKNewHitsTableViewController.swift */, 145 | D4D545181FF1460000A2A00E /* KKChartsTableViewController.swift */, 146 | D4E091991C74EE3A0018F379 /* KKTextViewController.swift */, 147 | D41EFF811C58C832000B4D10 /* Assets.xcassets */, 148 | D41EFF831C58C832000B4D10 /* LaunchScreen.storyboard */, 149 | D41EFF861C58C832000B4D10 /* Info.plist */, 150 | ); 151 | path = KKBOXOpenAPI; 152 | sourceTree = ""; 153 | }; 154 | D41EFF8E1C58C832000B4D10 /* KKBOXOpenAPITests */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | D41EFF8F1C58C832000B4D10 /* KKBOXOpenAPITests.swift */, 158 | D41EFF911C58C833000B4D10 /* Info.plist */, 159 | ); 160 | path = KKBOXOpenAPITests; 161 | sourceTree = ""; 162 | }; 163 | /* End PBXGroup section */ 164 | 165 | /* Begin PBXNativeTarget section */ 166 | D41EFF721C58C832000B4D10 /* KKBOXOpenAPIDemo */ = { 167 | isa = PBXNativeTarget; 168 | buildConfigurationList = D41EFF9F1C58C833000B4D10 /* Build configuration list for PBXNativeTarget "KKBOXOpenAPIDemo" */; 169 | buildPhases = ( 170 | 0889004FD4823791CC04410A /* [CP] Check Pods Manifest.lock */, 171 | D41EFF6F1C58C832000B4D10 /* Sources */, 172 | D41EFF701C58C832000B4D10 /* Frameworks */, 173 | D41EFF711C58C832000B4D10 /* Resources */, 174 | D4E4B0F01D7C76370026A356 /* Embed Frameworks */, 175 | CD914E7A8CDA0E1F0DAE7CB5 /* [CP] Embed Pods Frameworks */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | ); 181 | name = KKBOXOpenAPIDemo; 182 | productName = KKBOXOpenAPI; 183 | productReference = D41EFF731C58C832000B4D10 /* KKBOXOpenAPIDemo.app */; 184 | productType = "com.apple.product-type.application"; 185 | }; 186 | D41EFF8A1C58C832000B4D10 /* KKBOXOpenAPIDemoTests */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = D41EFFA21C58C833000B4D10 /* Build configuration list for PBXNativeTarget "KKBOXOpenAPIDemoTests" */; 189 | buildPhases = ( 190 | D41EFF871C58C832000B4D10 /* Sources */, 191 | D41EFF881C58C832000B4D10 /* Frameworks */, 192 | D41EFF891C58C832000B4D10 /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | D41EFF8D1C58C832000B4D10 /* PBXTargetDependency */, 198 | ); 199 | name = KKBOXOpenAPIDemoTests; 200 | productName = KKBOXOpenAPITests; 201 | productReference = D41EFF8B1C58C832000B4D10 /* KKBOXOpenAPIDemoTests.xctest */; 202 | productType = "com.apple.product-type.bundle.unit-test"; 203 | }; 204 | /* End PBXNativeTarget section */ 205 | 206 | /* Begin PBXProject section */ 207 | D41EFF6B1C58C832000B4D10 /* Project object */ = { 208 | isa = PBXProject; 209 | attributes = { 210 | LastSwiftUpdateCheck = 0720; 211 | LastUpgradeCheck = 1120; 212 | ORGANIZATIONNAME = KKBOX; 213 | TargetAttributes = { 214 | D41EFF721C58C832000B4D10 = { 215 | CreatedOnToolsVersion = 7.2; 216 | DevelopmentTeam = GA2DMW4FFR; 217 | LastSwiftMigration = 0910; 218 | }; 219 | D41EFF8A1C58C832000B4D10 = { 220 | CreatedOnToolsVersion = 7.2; 221 | LastSwiftMigration = 0910; 222 | TestTargetID = D41EFF721C58C832000B4D10; 223 | }; 224 | }; 225 | }; 226 | buildConfigurationList = D41EFF6E1C58C832000B4D10 /* Build configuration list for PBXProject "KKBOXOpenAPIDemo" */; 227 | compatibilityVersion = "Xcode 3.2"; 228 | developmentRegion = en; 229 | hasScannedForEncodings = 0; 230 | knownRegions = ( 231 | en, 232 | Base, 233 | ); 234 | mainGroup = D41EFF6A1C58C832000B4D10; 235 | productRefGroup = D41EFF741C58C832000B4D10 /* Products */; 236 | projectDirPath = ""; 237 | projectRoot = ""; 238 | targets = ( 239 | D41EFF721C58C832000B4D10 /* KKBOXOpenAPIDemo */, 240 | D41EFF8A1C58C832000B4D10 /* KKBOXOpenAPIDemoTests */, 241 | ); 242 | }; 243 | /* End PBXProject section */ 244 | 245 | /* Begin PBXResourcesBuildPhase section */ 246 | D41EFF711C58C832000B4D10 /* Resources */ = { 247 | isa = PBXResourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | D41EFF851C58C832000B4D10 /* LaunchScreen.storyboard in Resources */, 251 | D41EFF821C58C832000B4D10 /* Assets.xcassets in Resources */, 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | D41EFF891C58C832000B4D10 /* Resources */ = { 256 | isa = PBXResourcesBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXShellScriptBuildPhase section */ 265 | 0889004FD4823791CC04410A /* [CP] Check Pods Manifest.lock */ = { 266 | isa = PBXShellScriptBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | inputFileListPaths = ( 271 | ); 272 | inputPaths = ( 273 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 274 | "${PODS_ROOT}/Manifest.lock", 275 | ); 276 | name = "[CP] Check Pods Manifest.lock"; 277 | outputFileListPaths = ( 278 | ); 279 | outputPaths = ( 280 | "$(DERIVED_FILE_DIR)/Pods-KKBOXOpenAPIDemo-checkManifestLockResult.txt", 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | shellPath = /bin/sh; 284 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 285 | showEnvVarsInLog = 0; 286 | }; 287 | CD914E7A8CDA0E1F0DAE7CB5 /* [CP] Embed Pods Frameworks */ = { 288 | isa = PBXShellScriptBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | ); 292 | inputPaths = ( 293 | "${PODS_ROOT}/Target Support Files/Pods-KKBOXOpenAPIDemo/Pods-KKBOXOpenAPIDemo-frameworks.sh", 294 | "${BUILT_PRODUCTS_DIR}/KKBOXOpenAPI/KKBOXOpenAPI.framework", 295 | ); 296 | name = "[CP] Embed Pods Frameworks"; 297 | outputPaths = ( 298 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KKBOXOpenAPI.framework", 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | shellPath = /bin/sh; 302 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-KKBOXOpenAPIDemo/Pods-KKBOXOpenAPIDemo-frameworks.sh\"\n"; 303 | showEnvVarsInLog = 0; 304 | }; 305 | /* End PBXShellScriptBuildPhase section */ 306 | 307 | /* Begin PBXSourcesBuildPhase section */ 308 | D41EFF6F1C58C832000B4D10 /* Sources */ = { 309 | isa = PBXSourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | D4B2EEE71F24493F005A37F6 /* KKMainTableViewController.swift in Sources */, 313 | D4D545171FF143F800A2A00E /* KKNewHitsTableViewController.swift in Sources */, 314 | D4E0919A1C74EE3A0018F379 /* KKTextViewController.swift in Sources */, 315 | D4D545151FF13D6700A2A00E /* KKFeaturedPlaylistCategoryTableViewController.swift in Sources */, 316 | D4D545191FF1460000A2A00E /* KKChartsTableViewController.swift in Sources */, 317 | D4D1FCA81C5942800042ECF7 /* API.swift in Sources */, 318 | D4D545111FF1333800A2A00E /* KKPlaylistTableViewController.swift in Sources */, 319 | D41EFF771C58C832000B4D10 /* AppDelegate.swift in Sources */, 320 | D4D545131FF13B5600A2A00E /* KKFeaturedPlaylistCategoriesTableViewController.swift in Sources */, 321 | D4D5450F1FF12CA400A2A00E /* KKFeaturedPlaylistsTableViewController.swift in Sources */, 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | D41EFF871C58C832000B4D10 /* Sources */ = { 326 | isa = PBXSourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | D41EFF901C58C832000B4D10 /* KKBOXOpenAPITests.swift in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | /* End PBXSourcesBuildPhase section */ 334 | 335 | /* Begin PBXTargetDependency section */ 336 | D41EFF8D1C58C832000B4D10 /* PBXTargetDependency */ = { 337 | isa = PBXTargetDependency; 338 | target = D41EFF721C58C832000B4D10 /* KKBOXOpenAPIDemo */; 339 | targetProxy = D41EFF8C1C58C832000B4D10 /* PBXContainerItemProxy */; 340 | }; 341 | /* End PBXTargetDependency section */ 342 | 343 | /* Begin PBXVariantGroup section */ 344 | D41EFF831C58C832000B4D10 /* LaunchScreen.storyboard */ = { 345 | isa = PBXVariantGroup; 346 | children = ( 347 | D41EFF841C58C832000B4D10 /* Base */, 348 | ); 349 | name = LaunchScreen.storyboard; 350 | sourceTree = ""; 351 | }; 352 | /* End PBXVariantGroup section */ 353 | 354 | /* Begin XCBuildConfiguration section */ 355 | D41EFF9D1C58C833000B4D10 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_COMMA = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_EMPTY_BODY = YES; 371 | CLANG_WARN_ENUM_CONVERSION = YES; 372 | CLANG_WARN_INFINITE_RECURSION = YES; 373 | CLANG_WARN_INT_CONVERSION = YES; 374 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 375 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 376 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 378 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 379 | CLANG_WARN_STRICT_PROTOTYPES = YES; 380 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | DEBUG_INFORMATION_FORMAT = dwarf; 386 | ENABLE_STRICT_OBJC_MSGSEND = YES; 387 | ENABLE_TESTABILITY = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu99; 389 | GCC_DYNAMIC_NO_PIC = NO; 390 | GCC_NO_COMMON_BLOCKS = YES; 391 | GCC_OPTIMIZATION_LEVEL = 0; 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "DEBUG=1", 394 | "$(inherited)", 395 | ); 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | HEADER_SEARCH_PATHS = "../../KKBOXOpenAPI/KKBOXOpenAPI/**"; 403 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 404 | MTL_ENABLE_DEBUG_INFO = YES; 405 | ONLY_ACTIVE_ARCH = YES; 406 | OTHER_LDFLAGS = "-ObjC"; 407 | SDKROOT = iphoneos; 408 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 409 | }; 410 | name = Debug; 411 | }; 412 | D41EFF9E1C58C833000B4D10 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ALWAYS_SEARCH_USER_PATHS = NO; 416 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 417 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 418 | CLANG_CXX_LIBRARY = "libc++"; 419 | CLANG_ENABLE_MODULES = YES; 420 | CLANG_ENABLE_OBJC_ARC = YES; 421 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 422 | CLANG_WARN_BOOL_CONVERSION = YES; 423 | CLANG_WARN_COMMA = YES; 424 | CLANG_WARN_CONSTANT_CONVERSION = YES; 425 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 426 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 427 | CLANG_WARN_EMPTY_BODY = YES; 428 | CLANG_WARN_ENUM_CONVERSION = YES; 429 | CLANG_WARN_INFINITE_RECURSION = YES; 430 | CLANG_WARN_INT_CONVERSION = YES; 431 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 433 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 434 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 435 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 436 | CLANG_WARN_STRICT_PROTOTYPES = YES; 437 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 438 | CLANG_WARN_UNREACHABLE_CODE = YES; 439 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 440 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 441 | COPY_PHASE_STRIP = NO; 442 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 443 | ENABLE_NS_ASSERTIONS = NO; 444 | ENABLE_STRICT_OBJC_MSGSEND = YES; 445 | GCC_C_LANGUAGE_STANDARD = gnu99; 446 | GCC_NO_COMMON_BLOCKS = YES; 447 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 448 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 449 | GCC_WARN_UNDECLARED_SELECTOR = YES; 450 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 451 | GCC_WARN_UNUSED_FUNCTION = YES; 452 | GCC_WARN_UNUSED_VARIABLE = YES; 453 | HEADER_SEARCH_PATHS = "../../KKBOXOpenAPI/KKBOXOpenAPI/**"; 454 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 455 | MTL_ENABLE_DEBUG_INFO = NO; 456 | OTHER_LDFLAGS = "-ObjC"; 457 | SDKROOT = iphoneos; 458 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 459 | VALIDATE_PRODUCT = YES; 460 | }; 461 | name = Release; 462 | }; 463 | D41EFFA01C58C833000B4D10 /* Debug */ = { 464 | isa = XCBuildConfiguration; 465 | baseConfigurationReference = 094F7029882721C2B48BA779 /* Pods-KKBOXOpenAPIDemo.debug.xcconfig */; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | CLANG_ENABLE_MODULES = YES; 469 | CODE_SIGN_ENTITLEMENTS = KKBOXOpenAPIDemo.entitlements; 470 | DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; 471 | DEVELOPMENT_TEAM = GA2DMW4FFR; 472 | INFOPLIST_FILE = KKBOXOpenAPI/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 474 | PRODUCT_BUNDLE_IDENTIFIER = com.kkbox.KKBOXOpenAPISample; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | SUPPORTS_MACCATALYST = YES; 477 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 478 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 479 | SWIFT_VERSION = 5.0; 480 | TARGETED_DEVICE_FAMILY = "1,2"; 481 | }; 482 | name = Debug; 483 | }; 484 | D41EFFA11C58C833000B4D10 /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | baseConfigurationReference = A411F335ED9A2249F93A4EA2 /* Pods-KKBOXOpenAPIDemo.release.xcconfig */; 487 | buildSettings = { 488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 | CLANG_ENABLE_MODULES = YES; 490 | CODE_SIGN_ENTITLEMENTS = KKBOXOpenAPIDemo.entitlements; 491 | DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; 492 | DEVELOPMENT_TEAM = GA2DMW4FFR; 493 | INFOPLIST_FILE = KKBOXOpenAPI/Info.plist; 494 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 495 | PRODUCT_BUNDLE_IDENTIFIER = com.kkbox.KKBOXOpenAPISample; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | SUPPORTS_MACCATALYST = YES; 498 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 499 | SWIFT_VERSION = 5.0; 500 | TARGETED_DEVICE_FAMILY = "1,2"; 501 | }; 502 | name = Release; 503 | }; 504 | D41EFFA31C58C833000B4D10 /* Debug */ = { 505 | isa = XCBuildConfiguration; 506 | buildSettings = { 507 | BUNDLE_LOADER = "$(TEST_HOST)"; 508 | CLANG_ENABLE_MODULES = YES; 509 | INFOPLIST_FILE = KKBOXOpenAPITests/Info.plist; 510 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 511 | PRODUCT_BUNDLE_IDENTIFIER = com.kkbox.KKBOXOpenAPITests; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 514 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 515 | SWIFT_VERSION = 4.0; 516 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KKBOXOpenAPIDemo.app/KKBOXOpenAPIDemo"; 517 | }; 518 | name = Debug; 519 | }; 520 | D41EFFA41C58C833000B4D10 /* Release */ = { 521 | isa = XCBuildConfiguration; 522 | buildSettings = { 523 | BUNDLE_LOADER = "$(TEST_HOST)"; 524 | CLANG_ENABLE_MODULES = YES; 525 | INFOPLIST_FILE = KKBOXOpenAPITests/Info.plist; 526 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 527 | PRODUCT_BUNDLE_IDENTIFIER = com.kkbox.KKBOXOpenAPITests; 528 | PRODUCT_NAME = "$(TARGET_NAME)"; 529 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 530 | SWIFT_VERSION = 4.0; 531 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/KKBOXOpenAPIDemo.app/KKBOXOpenAPIDemo"; 532 | }; 533 | name = Release; 534 | }; 535 | /* End XCBuildConfiguration section */ 536 | 537 | /* Begin XCConfigurationList section */ 538 | D41EFF6E1C58C832000B4D10 /* Build configuration list for PBXProject "KKBOXOpenAPIDemo" */ = { 539 | isa = XCConfigurationList; 540 | buildConfigurations = ( 541 | D41EFF9D1C58C833000B4D10 /* Debug */, 542 | D41EFF9E1C58C833000B4D10 /* Release */, 543 | ); 544 | defaultConfigurationIsVisible = 0; 545 | defaultConfigurationName = Release; 546 | }; 547 | D41EFF9F1C58C833000B4D10 /* Build configuration list for PBXNativeTarget "KKBOXOpenAPIDemo" */ = { 548 | isa = XCConfigurationList; 549 | buildConfigurations = ( 550 | D41EFFA01C58C833000B4D10 /* Debug */, 551 | D41EFFA11C58C833000B4D10 /* Release */, 552 | ); 553 | defaultConfigurationIsVisible = 0; 554 | defaultConfigurationName = Release; 555 | }; 556 | D41EFFA21C58C833000B4D10 /* Build configuration list for PBXNativeTarget "KKBOXOpenAPIDemoTests" */ = { 557 | isa = XCConfigurationList; 558 | buildConfigurations = ( 559 | D41EFFA31C58C833000B4D10 /* Debug */, 560 | D41EFFA41C58C833000B4D10 /* Release */, 561 | ); 562 | defaultConfigurationIsVisible = 0; 563 | defaultConfigurationName = Release; 564 | }; 565 | /* End XCConfigurationList section */ 566 | }; 567 | rootObject = D41EFF6B1C58C832000B4D10 /* Project object */; 568 | } 569 | -------------------------------------------------------------------------------- /Sources/KKBOXOpenAPI/include/OpenAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKBOXOpenAPI.h 3 | // 4 | // Copyright (c) 2016-2020 KKBOX Taiwan Co., Ltd. All Rights Reserved. 5 | // 6 | 7 | @import Foundation; 8 | 9 | #import "OpenAPIObjects.h" 10 | 11 | /** 12 | * The access token object. You need a valid access token to access 13 | * KKBOX's APIs. To obtain an access token, please read about KKBOX's 14 | * log-in flow. 15 | */ 16 | @interface KKAccessToken : NSObject 17 | 18 | /** 19 | * Create an access token by giving a dictionary object fetched from 20 | * KKBOX's log-in APIs. 21 | * 22 | * @param inDictionary a given dictionary 23 | * @return an access token 24 | */ 25 | - (nonnull instancetype)initWithDictionary:(nonnull NSDictionary *)inDictionary NS_DESIGNATED_INITIALIZER; 26 | 27 | - (nonnull instancetype)initWithCoder:(nonnull NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; 28 | 29 | /** The access token string. */ 30 | @property (strong, nonatomic, nonnull) NSString *accessToken; 31 | /** How long will the access token expire since now. */ 32 | @property (assign, nonatomic) NSTimeInterval expiresIn; 33 | /** Type of the access token. */ 34 | @property (strong, nonatomic, nullable) NSString *tokenType; 35 | /** Scope of the access token. */ 36 | @property (strong, nonatomic, nullable) NSString *scope; 37 | @end 38 | 39 | /** The territory that KKBOX provides service in. */ 40 | typedef NS_ENUM(NSUInteger, KKTerritoryCode) 41 | { 42 | /** Taiwan */ 43 | KKTerritoryCodeTaiwan, 44 | /** HongKong */ 45 | KKTerritoryCodeHongKong, 46 | /** Singapore */ 47 | KKTerritoryCodeSingapore, 48 | /** Malaysia */ 49 | KKTerritoryCodeMalaysia, 50 | /** Japan */ 51 | KKTerritoryCodeJapan, 52 | } NS_SWIFT_NAME(KKBOXOpenAPI.Territory); 53 | 54 | /** The search types used by the search API. */ 55 | typedef NS_OPTIONS(NSUInteger, KKSearchType) 56 | { 57 | /** Default value */ 58 | KKSearchTypeNone = 0, 59 | /** Search for artists */ 60 | KKSearchTypeArtist = 1 << 0, 61 | /** Search for albums */ 62 | KKSearchTypeAlbum = 1 << 1, 63 | /** Search for song tracks */ 64 | KKSearchTypeTrack = 1 << 2, 65 | /** Search for playlists */ 66 | KKSearchTypePlaylist = 1 << 3 67 | } NS_SWIFT_NAME(KKBOXOpenAPI.SearchType); 68 | 69 | /** The permissions that your client requests. */ 70 | typedef NS_OPTIONS(NSUInteger, KKScope) 71 | { 72 | /** No permission */ 73 | KKScopeNone = 0, 74 | /** Permission to get user profile */ 75 | KKScopeUserProfile = 1 << 0, 76 | /** Permission to get user territory */ 77 | KKScopeUserTerritory = 1 << 1, 78 | /** Permission to get user account status */ 79 | KKScopeUserAccountStatus = 1 << 2, 80 | /** Get all permissions */ 81 | KKScopeAll = KKScopeUserProfile | KKScopeUserTerritory | KKScopeUserAccountStatus 82 | } NS_SWIFT_NAME(KKBOXOpenAPI.Scope); 83 | 84 | /** The errors that happen in the SDK. */ 85 | extern NSString *_Nonnull const KKBOXOpenAPIErrorDomain; 86 | 87 | /** 88 | * Fired when KKBOXOpenAPI completes logging-in into KKBOX and 89 | * creating a new access token. 90 | */ 91 | extern NSString *_Nonnull const KKBOXOpenAPIDidLoginNotification; 92 | /** 93 | * Fired when KKBOXOpenAPI restores a saved access token. You can 94 | * reset the access token by calling the `-logout` method. 95 | */ 96 | extern NSString *_Nonnull const KKBOXOpenAPIDidRestoreAccessTokenNotification; 97 | 98 | /** 99 | * Callback block for log-in API calls. 100 | */ 101 | typedef void (^KKBOXOpenAPILoginCallback)(KKAccessToken *_Nullable, NSError *_Nullable); 102 | 103 | /** 104 | * Callback block for API calls. 105 | */ 106 | typedef void (^KKBOXOpenAPIDataCallback)(id _Nullable, NSError *_Nullable); 107 | 108 | #pragma mark - 109 | 110 | /** 111 | * The class helps to access KKBOX's Open API on Apple platforms such 112 | * as iOS, macOS, watchOS and tvOS. 113 | * 114 | * To start accessing KKBOX's API, you need to register your self to 115 | * obtain a valid client ID(API Key) and shared secret, then you can 116 | * use your client ID and secret to initialize an instance of the 117 | * class. To obtain a client ID, please visit 118 | * https://developer.kkbox.com/. 119 | */ 120 | @interface KKBOXOpenAPI : NSObject 121 | 122 | /** 123 | * Create a new KKBOXOpenAPI instance. (Default scope is all) 124 | * 125 | * @param clientID the API key 126 | * @param secret the API secret 127 | * @return A KKBOXOpenAPI instance 128 | */ 129 | - (nonnull instancetype)initWithClientID:(nonnull NSString *)clientID secret:(nonnull NSString *)secret NS_SWIFT_NAME(init(clientID:secret:)); 130 | 131 | /** 132 | * Create a new KKBOXOpenAPI instance. 133 | * 134 | * @param clientID the API key 135 | * @param secret the API secret 136 | * @param scope the OAuth permission scope 137 | * @return A KKBOXOpenAPI instance 138 | */ 139 | - (nonnull instancetype)initWithClientID:(nonnull NSString *)clientID secret:(nonnull NSString *)secret scope:(KKScope)scope NS_SWIFT_NAME(init(clientID:secret:scope:)); 140 | 141 | /** Clear existing access token. */ 142 | - (void)logout; 143 | 144 | /** The current access token. */ 145 | @property (readwrite, strong, nullable, nonatomic) KKAccessToken *accessToken; 146 | /** If there is a valid access token. */ 147 | @property (readonly, assign) BOOL loggedIn; 148 | @end 149 | 150 | #pragma mark - Client Credential Log-in Flow 151 | 152 | @interface KKBOXOpenAPI (LoginWithClientCredential) 153 | /** 154 | * To start using KKBOx's Open API, you need to log-in in to KKBOX at 155 | * first. You can generate a client credential to fetch an access 156 | * token to let KKBOX identify you. It allows you to access public 157 | * data from KKBOX such as public albums, playlists and so on. 158 | * 159 | * @param callback the callback block. 160 | * @return an NSURLSessionDataTask object that allow you to cancel the 161 | * task. 162 | */ 163 | - (nonnull NSURLSessionDataTask *)fetchAccessTokenByClientCredentialWithCallback:(nonnull KKBOXOpenAPILoginCallback)callback; 164 | @end 165 | 166 | @interface KKBOXOpenAPI (API) 167 | 168 | #pragma mark - Song Tracks 169 | 170 | /** 171 | * Fetch the detailed information of a song track. 172 | * 173 | * See `https://docs-en.kkbox.codes/reference#tracks_track_id`. 174 | * 175 | * @param trackID the ID of the song track 176 | * @param territory the given territory. The displayed information of 177 | * a song track may differ in different territories. 178 | * @param callback the callback block 179 | * @return an NSURLSessionDataTask object that allow you to cancel the 180 | * task. 181 | */ 182 | - (nonnull NSURLSessionDataTask *)fetchTrackWithTrackID:(nonnull NSString *)trackID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKTrackInfo *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchTrack(id:territory:callback:)); 183 | 184 | #pragma mark - Album 185 | 186 | /** 187 | * Fetch the information of a given album. 188 | * 189 | * See `https://docs-en.kkbox.codes/reference#albums_album_id`. 190 | * 191 | * @param albumID the given album ID 192 | * @param territory the given territory 193 | * @param callback the callback block 194 | * @return an NSURLSessionDataTask object that allow you to cancel the 195 | * task. 196 | */ 197 | - (nonnull NSURLSessionDataTask *)fetchAlbumWithAlbumID:(nonnull NSString *)albumID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKAlbumInfo *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchAlbum(id:territory:callback:)); 198 | 199 | /** 200 | * Fetch the song tracks contained in a given album. 201 | * 202 | * See `https://docs-en.kkbox.codes/reference#albums_album_id_tracks`. 203 | * 204 | * @param albumID the given album ID 205 | * @param territory the given territory 206 | * @param callback the callback block 207 | * @return an NSURLSessionDataTask object that allow you to cancel the 208 | * task. 209 | */ 210 | - (nonnull NSURLSessionDataTask *)fetchTracksWithAlbumID:(nonnull NSString *)albumID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchAlbumTracks(id:territory:callback:)); 211 | 212 | /** 213 | * Fetch the song tracks contained in a given album. 214 | * 215 | * See `https://docs-en.kkbox.codes/reference#albums_album_id_tracks`. 216 | * 217 | * @param albumID the given album ID 218 | * @param territory the given territory 219 | * @param callback the callback block 220 | * @param offset the offset 221 | * @param limit the limit of response 222 | * @return an NSURLSessionDataTask object that allow you to cancel the 223 | * task. 224 | */ 225 | - (nonnull NSURLSessionDataTask *)fetchTracksWithAlbumID:(nonnull NSString *)albumID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchAlbumTracks(id:territory:offset:limit:callback:)); 226 | 227 | #pragma mark - Artists 228 | 229 | /** 230 | * Fetch the detailed profile of an artist. 231 | * 232 | * See `https://docs-en.kkbox.codes/reference#artists_artist_id`. 233 | * 234 | * @param artistID the ID of the artist 235 | * @param territory the given territory. The displayed information of 236 | * an artist may differ in different territories. 237 | * @param callback the callback block 238 | * @return an NSURLSessionDataTask object that allow you to cancel the 239 | * task. 240 | */ 241 | - (nonnull NSURLSessionDataTask *)fetchArtistInfoWithArtistID:(nonnull NSString *)artistID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKArtistInfo *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchArtist(id:territory:callback:)); 242 | 243 | /** 244 | * Fetch the list of the albums belong to an artist. 245 | * 246 | * See `https://docs-en.kkbox.codes/reference#artists_artist_id_albums` 247 | * 248 | * @param artistID the ID of the artist 249 | * @param territory the given territory. The albums list may differ in 250 | * different territories since KKBOX may not be licensed to distribute 251 | * music content in all territories. 252 | * @param callback the callback block 253 | * @return an NSURLSessionDataTask object that allow you to cancel the 254 | * task. 255 | */ 256 | - (nonnull NSURLSessionDataTask *)fetchAlbumsBelongToArtistID:(nonnull NSString *)artistID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchArtistAlbums(id:territory:callback:)); 257 | 258 | /** 259 | * Fetch the list of the albums belong to an artist. 260 | * 261 | * See `https://docs-en.kkbox.codes/reference#artists_artist_id_albums` 262 | * 263 | * @param artistID the ID of the artist 264 | * @param territory the given territory. The albums list may differ in 265 | * different territories since KKBOX may not be licensed to distribute 266 | * music content in all territories. 267 | * @param offset the offset 268 | * @param limit the limit of response 269 | * @param callback the callback block 270 | * @return an NSURLSessionDataTask object that allow you to cancel the 271 | * task. 272 | */ 273 | - (nonnull NSURLSessionDataTask *)fetchAlbumsBelongToArtistID:(nonnull NSString *)artistID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchArtistAlbums(id:territory:offset:limit:callback:)); 274 | 275 | /** 276 | * Fetch the top tracks of an artist. 277 | * 278 | * See `https://docs-en.kkbox.codes/reference#artists_artist_id_top-tracks`. 279 | * 280 | * @param artistID the ID of the artist 281 | * @param territory the given territory. The displayed information of 282 | * an artist may differ in different territories. 283 | * @param callback the callback block 284 | * @return an NSURLSessionDataTask object that allow you to cancel the 285 | * task. 286 | */ 287 | - (nonnull NSURLSessionDataTask *)fetchTopTracksWithArtistID:(nonnull NSString *)artistID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchArtistTopTracks(id:territory:callback:)); 288 | 289 | /** 290 | * Fetch the top tracks of an artist. 291 | * 292 | * See `https://docs-en.kkbox.codes/reference#artists_artist_id_top-tracks`. 293 | * 294 | * @param artistID the ID of the artist 295 | * @param territory the given territory. The displayed information of 296 | * an artist may differ in different territories. 297 | * @param offset the offset 298 | * @param limit the limit of response 299 | * @param callback the callback block 300 | * @return an NSURLSessionDataTask object that allow you to cancel the 301 | * task. 302 | */ 303 | - (nonnull NSURLSessionDataTask *)fetchTopTracksWithArtistID:(nonnull NSString *)artistID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchArtistTopTracks(id:territory:offset:limit:callback:)); 304 | 305 | /** 306 | * Fetch related artists of an artist. 307 | * 308 | * See `https://docs-en.kkbox.codes/reference#artists_artist_id_related-artists`. 309 | * 310 | * @param artistID the ID of the artist 311 | * @param territory the given territory. The displayed information of 312 | * an artist may differ in different territories. 313 | * @param callback the callback block 314 | * @return an NSURLSessionDataTask object that allow you to cancel the 315 | * task. 316 | */ 317 | - (nonnull NSURLSessionDataTask *)fetchRelatedArtistsWithArtistID:(nonnull NSString *)artistID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchRelatedArtists(id:territory:callback:)); 318 | 319 | /** 320 | * Fetch related artists of an artist. 321 | * 322 | * See `https://docs-en.kkbox.codes/reference#artists_artist_id_related-artists`. 323 | * 324 | * @param artistID the ID of the artist 325 | * @param territory the given territory. The displayed information of 326 | * an artist may differ in different territories. 327 | * @param offset the offset 328 | * @param limit the limit of response 329 | * @param callback the callback block 330 | * @return an NSURLSessionDataTask object that allow you to cancel the 331 | * task. 332 | */ 333 | - (nonnull NSURLSessionDataTask *)fetchRelatedArtistsWithArtistID:(nonnull NSString *)artistID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchRelatedArtists(id:territory:offset:limit:callback:)); 334 | 335 | #pragma mark - Shared Playlists 336 | 337 | /** 338 | * Fetches information and song tracks of a given playlist. 339 | * 340 | * See `https://docs-en.kkbox.codes/reference#shared-playlists_playlist_id`. 341 | * 342 | * @param playlistID the given playlist ID. 343 | * @param territory the given territory 344 | * @param callback the callback block 345 | * @return an NSURLSessionDataTask object that allow you to cancel the 346 | * task. 347 | */ 348 | - (nonnull NSURLSessionDataTask *)fetchPlaylistWithPlaylistID:(nonnull NSString *)playlistID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKPlaylistInfo *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchPlaylist(id:territory:callback:)); 349 | 350 | /** 351 | * Fetches information and song tracks of a given playlist. 352 | * 353 | * See `https://docs-en.kkbox.codes/reference#shared-playlists_playlist_id_tracks`. 354 | * 355 | * @param playlistID the given playlist ID. 356 | * @param territory the given territory 357 | * @param callback the callback block 358 | * @return an NSURLSessionDataTask object that allow you to cancel the 359 | * task. 360 | */ 361 | - (nonnull NSURLSessionDataTask *)fetchTracksInPlaylistWithPlaylistID:(nonnull NSString *)playlistID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchPlaylistTracks(id:territory:callback:)); 362 | 363 | /** 364 | * Fetches information and song tracks of a given playlist. 365 | * 366 | * See `https://docs-en.kkbox.codes/reference#shared-playlists_playlist_id_tracks`. 367 | * 368 | * @param playlistID the given playlist ID. 369 | * @param territory the given territory 370 | * @param offset the offset 371 | * @param limit the limit of response 372 | * @param callback the callback block 373 | * @return an NSURLSessionDataTask object that allow you to cancel the 374 | * task. 375 | */ 376 | - (nonnull NSURLSessionDataTask *)fetchTracksInPlaylistWithPlaylistID:(nonnull NSString *)playlistID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchPlaylistTracks(id:territory:offset:limit:callback:)); 377 | 378 | #pragma mark - Featured Playlists 379 | 380 | /** 381 | * Fetch featured playlists. 382 | * 383 | * See `https://docs-en.kkbox.codes/reference#featured-playlists`. 384 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 385 | * 386 | * @param territory the given territory 387 | * @param callback the callback block 388 | * @return an NSURLSessionDataTask object that allow you to cancel the 389 | * task. 390 | */ 391 | - (nonnull NSURLSessionDataTask *)fetchFeaturedPlaylistsForTerritory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchFeaturedPlaylists(territory:callback:)); 392 | 393 | /** 394 | * Fetch featured playlists. 395 | * 396 | * See `https://docs-en.kkbox.codes/reference#featured-playlists_playlist_id`. 397 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 398 | * 399 | * @param territory the given territory 400 | * @param offset the offset 401 | * @param limit the limit of response 402 | * @param callback the callback block 403 | * @return an NSURLSessionDataTask object that allow you to cancel the 404 | * task. 405 | */ 406 | - (nonnull NSURLSessionDataTask *)fetchFeaturedPlaylistsForTerritory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchFeaturedPlaylists(territory:offset:limit:callback:)); 407 | 408 | #pragma mark - New-Hits Playlists 409 | 410 | /** 411 | * Fetch new hits playlists. 412 | * 413 | * See `https://docs-en.kkbox.codes/reference#new-hits-playlists`. 414 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 415 | * 416 | * @param territory the given territory 417 | * @param callback the callback block 418 | * @return an NSURLSessionDataTask object that allow you to cancel the 419 | * task. 420 | */ 421 | - (nonnull NSURLSessionDataTask *)fetchNewHitsPlaylistsForTerritory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchNewHitsPlaylists(territory:callback:)); 422 | 423 | /** 424 | * Fetch new hits playlists. 425 | * 426 | * See `https://docs-en.kkbox.codes/reference#new-hits-playlists_playlist_id`. 427 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 428 | * 429 | * @param territory the given territory 430 | * @param offset the offset 431 | * @param limit the limit of response 432 | * @param callback the callback block 433 | * @return an NSURLSessionDataTask object that allow you to cancel the 434 | * task. 435 | */ 436 | - (nonnull NSURLSessionDataTask *)fetchNewHitsPlaylistsForTerritory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchNewHitsPlaylists(territory:offset:limit:callback:)); 437 | 438 | 439 | #pragma mark - Featured Playlists Categories 440 | 441 | /** 442 | * Fetch feature playlist categories. 443 | * 444 | * See `https://docs-en.kkbox.codes/reference#featured-playlist-categories`. 445 | * 446 | * @param territory the given territory 447 | * @param callback the callback block 448 | * @return an NSURLSessionDataTask object that allow you to cancel the 449 | * task. 450 | */ 451 | - (nonnull NSURLSessionDataTask *)fetchFeaturedPlaylistCategoriesForTerritory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchFeaturedPlaylistCategories(territory:callback:)); 452 | 453 | /** 454 | * Fetch feature playlist categories. 455 | * 456 | * See `https://docs-en.kkbox.codes/reference#featured-playlist-categories`. 457 | * 458 | * @param territory the given territory 459 | * @param offset the offset 460 | * @param limit the limit of response 461 | * @param callback the callback block 462 | * @return an NSURLSessionDataTask object that allow you to cancel the 463 | * task. 464 | */ 465 | - (nonnull NSURLSessionDataTask *)fetchFeaturedPlaylistCategoriesForTerritory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchFeaturedPlaylistCategories(territory:offset:limit:callback:)); 466 | 467 | /** 468 | * Fetch the feature playlists contained in a given category. You can 469 | * obtain the categories from the previous method. 470 | * 471 | * See `https://docs-en.kkbox.codes/reference#featured-playlist-categories_category_id`. 472 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 473 | * 474 | * @param category the given category 475 | * @param territory the given territory 476 | * @param callback the callback block 477 | * @return an NSURLSessionDataTask object that allow you to cancel the 478 | * task. 479 | */ 480 | - (nonnull NSURLSessionDataTask *)fetchFeaturedPlaylistsInCategory:(nonnull NSString *)category territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKFeaturedPlaylistCategory *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchFeaturedPlaylistCategoryPlaylists(category:territory:callback:)); 481 | 482 | /** 483 | * Fetch the feature playlists contained in a given category. You can 484 | * obtain the categories from the previous method. 485 | * 486 | * See `https://docs-en.kkbox.codes/reference#featured-playlist-categories_category_id`. 487 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 488 | * 489 | * @param category the given category 490 | * @param territory the given territory 491 | * @param offset the offset 492 | * @param limit the limit of response 493 | * @param callback the callback block 494 | * @return an NSURLSessionDataTask object that allow you to cancel the 495 | * task. 496 | */ 497 | - (nonnull NSURLSessionDataTask *)fetchFeaturedPlaylistsInCategory:(nonnull NSString *)category territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(KKFeaturedPlaylistCategory *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchFeaturedPlaylistCategoryPlaylists(category:territory:offset:limit:callback:)); 498 | 499 | #pragma mark - Radio 500 | 501 | #pragma mark Mood Station 502 | 503 | /** 504 | * Fetch mood station categories. 505 | * 506 | * See `https://docs-en.kkbox.codes/reference#mood-stations`. 507 | * @param territory the given territory 508 | * @param callback the callback block 509 | * @return an NSURLSessionDataTask object that allow you to cancel the 510 | * task. 511 | */ 512 | - (nonnull NSURLSessionDataTask *)fetchMoodStationsForTerritory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchMoodStations(territory:callback:)); 513 | 514 | /** 515 | * Fetch mood stations under a specific radio category. 516 | * 517 | * See `https://docs-en.kkbox.codes/reference#mood-stations_station_id`. 518 | * 519 | * @param stationID the station ID. You can obtain IDs from the 520 | * previous method. 521 | * @param territory the given territory 522 | * @param callback the callback block 523 | * @return an NSURLSessionDataTask object that allow you to cancel the 524 | * task. 525 | */ 526 | - (nonnull NSURLSessionDataTask *)fetchMoodStationWithStationID:(nonnull NSString *)stationID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKRadioStation *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchMoodStation(id:territory:callback:)); 527 | 528 | /** 529 | * Fetch mood stations under a specific radio category. 530 | * 531 | * See `https://docs-en.kkbox.codes/reference#mood-stations_station_id`. 532 | * 533 | * @param stationID the station ID. You can obtain IDs from the 534 | * previous method. 535 | * @param territory the given territory 536 | * @param offset the offset 537 | * @param limit the limit of response 538 | * @param callback the callback block 539 | * @return an NSURLSessionDataTask object that allow you to cancel the 540 | * task. 541 | */ 542 | - (nonnull NSURLSessionDataTask *)fetchMoodStationWithStationID:(nonnull NSString *)stationID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(KKRadioStation *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchMoodStation(id:territory:offset:limit:callback:)); 543 | 544 | #pragma mark Genre Station 545 | 546 | /** 547 | * Fetch the list of genre radio station categories. 548 | * 549 | * See `https://docs-en.kkbox.codes/reference#genre-stations`. 550 | * 551 | * @param territory the given territory 552 | * @param callback the callback block 553 | * @return an NSURLSessionDataTask object that allow you to cancel the 554 | * task. 555 | */ 556 | - (nonnull NSURLSessionDataTask *)fetchGenreStationsForTerritory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchGenreStations(territory:callback:)); 557 | 558 | /** 559 | * Fetch genre-based radio stations under a specific genre category. 560 | * 561 | * See `https://docs-en.kkbox.codes/reference#genre-stations_station_id`. 562 | * 563 | * @param stationID the station ID. You can obtain the list categories 564 | * from the previous method. 565 | * @param territory the given territory 566 | * @param callback the callback block 567 | * @return an NSURLSessionDataTask object that allow you to cancel the 568 | * task. 569 | */ 570 | - (nonnull NSURLSessionDataTask *)fetchGenreStationWithStationID:(nonnull NSString *)stationID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKRadioStation *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchGenreStation(id:territory:callback:)); 571 | 572 | /** 573 | * Fetch genre-based radio stations under a specific genre category. 574 | * 575 | * See `https://docs-en.kkbox.codes/reference#genre-stations_station_id`. 576 | * 577 | * @param stationID the station ID. You can obtain the list categories 578 | * from the previous method. 579 | * @param territory the given territory 580 | * @param offset the offset 581 | * @param limit the limit of response 582 | * @param callback the callback block 583 | * @return an NSURLSessionDataTask object that allow you to cancel the 584 | * task. 585 | */ 586 | - (nonnull NSURLSessionDataTask *)fetchGenreStationWithStationID:(nonnull NSString *)stationID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(KKRadioStation *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchGenreStation(id:territory:offset:limit:callback:)); 587 | 588 | #pragma mark - Search 589 | 590 | /** 591 | * Search within KKBOX's archive. 592 | * 593 | * See `https://docs-en.kkbox.codes/reference#search`. 594 | * 595 | * @param keyword the keyword 596 | * @param searchTypes search for song tracks, albums, artists or playlists. 597 | * @param territory the given territory 598 | * @param callback the callback block 599 | * @return an NSURLSessionDataTask object that allow you to cancel the 600 | * task. 601 | */ 602 | - (nonnull NSURLSessionDataTask *)searchWithKeyword:(nonnull NSString *)keyword searchTypes:(KKSearchType)searchTypes territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKSearchResults *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(search(keyword:types:territory:callback:)); 603 | 604 | /** 605 | * Search within KKBOX's archive. 606 | * 607 | * See `https://docs-en.kkbox.codes/reference#search`. 608 | * 609 | * @param keyword the keyword 610 | * @param searchTypes search for song tracks, albums, artists or playlists. 611 | * @param territory the given territory 612 | * @param offset the offset 613 | * @param limit the limit of response 614 | * @param callback the callback block 615 | * @return an NSURLSessionDataTask object that allow you to cancel the 616 | * task. 617 | */ 618 | - (nonnull NSURLSessionDataTask *)searchWithKeyword:(nonnull NSString *)keyword searchTypes:(KKSearchType)searchTypes territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(KKSearchResults *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(search(keyword:types:territory:offset:limit:callback:)); 619 | 620 | 621 | #pragma mark - New Releases 622 | 623 | /** 624 | * Fetch the categories of new released albums in a specific territory. 625 | * 626 | * See `https://docs-en.kkbox.codes/reference#new-release-categories`. 627 | * 628 | * @param territory the given territory. KKBOX may provide different 629 | * new released albums in different territories. 630 | * @param callback the callback block 631 | * @return an NSURLSessionDataTask object that allow you to cancel the 632 | * task. 633 | */ 634 | - (nonnull NSURLSessionDataTask *)fetchNewReleaseAlbumCategoriesForTerritory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchNewReleaseAlbumCategories(territory:callback:)); 635 | 636 | /** 637 | * Fetch the categories of new released albums in a specific territory. 638 | * 639 | * See `https://docs-en.kkbox.codes/reference#new-release-categories`. 640 | * 641 | * @param territory the given territory. KKBOX may provide different 642 | * new released albums in different territories. 643 | * @param offset the offset 644 | * @param limit the limit of response 645 | * @param callback the callback block 646 | * @return an NSURLSessionDataTask object that allow you to cancel the 647 | * task. 648 | */ 649 | - (nonnull NSURLSessionDataTask *)fetchNewReleaseAlbumCategoriesForTerritory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchNewReleaseAlbumCategories(territory:offset:limit:callback:)); 650 | 651 | /** 652 | * Fetch new released albums in a specific category and territory. 653 | * 654 | * See `https://docs-en.kkbox.codes/reference#new-release-categories_category_id`. 655 | * 656 | * @param categoryID the ID of the category. 657 | * @param territory the given territory. KKBOX may provide different 658 | * new released albums in different territories. 659 | * @param callback the callback block 660 | * @return an NSURLSessionDataTask object that allow you to cancel the 661 | * task. 662 | */ 663 | - (nonnull NSURLSessionDataTask *)fetchNewReleaseAlbumsUnderCategory:(nonnull NSString *)categoryID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKNewReleaseAlbumsCategory *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchNewReleaseAlbums(id:territory:callback:)); 664 | 665 | /** 666 | * Fetch new released albums in a specific category and territory. 667 | * 668 | * See `https://docs-en.kkbox.codes/reference#new-release-categories_category_id`. 669 | * 670 | * @param categoryID the ID of the category. 671 | * @param territory the given territory. KKBOX may provide different 672 | * new released albums in different territories. 673 | * @param offset the offset 674 | * @param limit the limit of response 675 | * @param callback the callback block 676 | * @return an NSURLSessionDataTask object that allow you to cancel the 677 | * task. 678 | */ 679 | - (nonnull NSURLSessionDataTask *)fetchNewReleaseAlbumsUnderCategory:(nonnull NSString *)categoryID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(KKNewReleaseAlbumsCategory *_Nullable, NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchNewReleaseAlbums(id:territory:offset:limit:callback:)); 680 | 681 | #pragma mark - Charts 682 | 683 | /** 684 | * Fetch the categories of charts in a specific territory. 685 | * 686 | * See `https://docs-en.kkbox.codes/reference#charts`. 687 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 688 | * 689 | * @param territory the given territory. KKBOX may provide different 690 | * charts in different territories. 691 | * @param callback the callback block 692 | * @return an NSURLSessionDataTask object that allow you to cancel the 693 | * task. 694 | */ 695 | - (nonnull NSURLSessionDataTask *)fetchChartsForTerritory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchCharts(territory:callback:)); 696 | 697 | /** 698 | * Fetch the categories of charts in a specific territory. 699 | * 700 | * See `https://docs-en.kkbox.codes/reference#charts_chart_id`. 701 | * See also `fetchPlaylistWithPlaylistID:territory:callback:`. 702 | * 703 | * @param territory the given territory. KKBOX may provide different 704 | * charts in different territories. 705 | * @param offset the offset 706 | * @param limit the limit of response 707 | * @param callback the callback block 708 | * @return an NSURLSessionDataTask object that allow you to cancel the 709 | * task. 710 | */ 711 | - (nonnull NSURLSessionDataTask *)fetchChartsForTerritory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchCharts(territory:offset:limit:callback:)); 712 | 713 | #pragma mark - Children Contents 714 | 715 | /** 716 | * Fetch the categories for children content in a specific territory. 717 | * 718 | * See `https://docs-en.kkbox.codes/reference#children-categories`. 719 | * 720 | * @param territory the given territory. KKBOX may provide different 721 | * contents in different territories. 722 | * @param callback the callback block 723 | * @return an NSURLSessionDataTask object that allow you to cancel the 724 | * task. 725 | */ 726 | - (nonnull NSURLSessionDataTask *)fetchChildrenCategories:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchChildrenCategories(territory:callback:)); 727 | 728 | /** 729 | * Fetch subcategories under a children content category. 730 | * 731 | * See `https://docs-en.kkbox.codes/reference#children-categories_category_id`. 732 | * 733 | * @param categoryID ID of the category. 734 | * @param territory the given territory. KKBOX may provide different 735 | * contents in different territories. 736 | * @param callback the callback block 737 | * @return an NSURLSessionDataTask object that allow you to cancel the 738 | * task. 739 | */ 740 | - (nonnull NSURLSessionDataTask *)fetchChildrenCategory:(nonnull NSString *)categoryID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(KKChildrenCategoryGroup *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchChildrenCategory(id:territory:callback:)); 741 | 742 | /** 743 | * Fetch the playlists under a children content category. 744 | * 745 | * See `https://docs-en.kkbox.codes/reference#children-categories_category_id_playlists`. 746 | * 747 | * @param categoryID ID of the category. 748 | * @param territory the given territory. KKBOX may provide different 749 | * contents in different territories. 750 | * @param callback the callback block 751 | * @return an NSURLSessionDataTask object that allow you to cancel the 752 | * task. 753 | */ 754 | - (nonnull NSURLSessionDataTask *)fetchChildrenCategoryPlaylists:(nonnull NSString *)categoryID territory:(KKTerritoryCode)territory callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchChildrenCategoryPlaylists(id:territory:callback:)); 755 | 756 | /** 757 | * Fetch the playlists under a children content category. 758 | * 759 | * See `https://docs-en.kkbox.codes/reference#children-categories_category_id_playlists`. 760 | * 761 | * @param categoryID ID of the category. 762 | * @param territory the given territory. KKBOX may provide different 763 | * contents in different territories. 764 | * @param offset the offset 765 | * @param limit the limit of response 766 | * @param callback the callback block 767 | * @return an NSURLSessionDataTask object that allow you to cancel the 768 | * task. 769 | */ 770 | - (nonnull NSURLSessionDataTask *)fetchChildrenCategoryPlaylists:(nonnull NSString *)categoryID territory:(KKTerritoryCode)territory offset:(NSInteger)offset limit:(NSInteger)limit callback:(nonnull void (^)(NSArray *_Nullable, KKPagingInfo *_Nullable, KKSummary *_Nullable, NSError *_Nullable))callback NS_SWIFT_NAME(fetchChildrenCategoryPlaylists(id:territory:offset:limit:callback:)); 771 | 772 | @end 773 | --------------------------------------------------------------------------------