├── .github ├── release-drafter.yml └── workflows │ ├── deploy-gh-pages.yml │ └── release-drafter.yml ├── .gitignore ├── .gitmodules ├── .version ├── Brewfile ├── Gemfile ├── LICENSE ├── LeanCloud.podspec ├── LeanCloud.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── CLI.xcscheme │ ├── LeanCloud.xcscheme │ ├── LeanCloudTests.xcscheme │ └── RuntimeTests-iOS.xcscheme ├── LeanCloud ├── Info.plist └── LeanCloud.h ├── LeanCloudTests ├── BaseTestCase.swift ├── BridgingHeader.h ├── IMClientTestCase.swift ├── IMConversationTestCase.swift ├── IMLocalStorageTestCase.swift ├── IMMessageTestCase.swift ├── Info.plist ├── LCACLTestCase.swift ├── LCAPITestCase.swift ├── LCApplicationTestCase.swift ├── LCCoreTestCase.swift ├── LCEngineTestCase.swift ├── LCFileTestCase.swift ├── LCInstallationTestCase.swift ├── LCLocalStorageContextTestCase.swift ├── LCObjectTestCase.swift ├── LCPushTestCase.swift ├── LCQueryTestCase.swift ├── LCRelationTestCase.swift ├── LCRouterTestCase.swift ├── LCSMSTestCase.swift ├── LCTypeTestCase.swift ├── LCUserTestCase.swift ├── LiveQueryTestCase.swift ├── RTMBaseTestCase.swift ├── RTMConnectionTestCase.swift ├── RTMRouterTestCase.swift └── Resources │ ├── test.jpg │ ├── test.mp3 │ ├── test.mp4 │ ├── test.png │ └── test.zip ├── Package.swift ├── README.md ├── RuntimeTests-iOS ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── RuntimeTests-iOS.entitlements └── ViewController.swift ├── Sources ├── Foundation │ ├── ACL.swift │ ├── AppRouter.swift │ ├── Application.swift │ ├── Array.swift │ ├── BatchRequest.swift │ ├── Bool.swift │ ├── CQLClient.swift │ ├── CaptchaClient.swift │ ├── Data.swift │ ├── Date.swift │ ├── Dictionary.swift │ ├── EngineClient.swift │ ├── Error.swift │ ├── Extension.swift │ ├── File.swift │ ├── FileUploader.swift │ ├── GeoPoint.swift │ ├── HTTPClient.swift │ ├── Installation.swift │ ├── LocalStorage.swift │ ├── Logger.swift │ ├── MD5.swift │ ├── Null.swift │ ├── Number.swift │ ├── Object.swift │ ├── ObjectProfiler.swift │ ├── ObjectUpdater.swift │ ├── Operation.swift │ ├── PushClient.swift │ ├── Query.swift │ ├── Relation.swift │ ├── Request.swift │ ├── Response.swift │ ├── Result.swift │ ├── Role.swift │ ├── Runtime.swift │ ├── SMSClient.swift │ ├── String.swift │ ├── User.swift │ ├── Utility.swift │ ├── Valuable.swift │ └── Version.swift └── RTM │ ├── Command.pb.swift │ ├── IMClient.swift │ ├── IMConversation.swift │ ├── IMConversationQuery.swift │ ├── IMLocalStorage.swift │ ├── IMMessage.swift │ ├── LiveQuery.swift │ ├── LiveQueryClient.swift │ ├── RTMConnection.swift │ ├── RTMRouter.swift │ └── WebSocket.swift └── main.swift /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'Next Version' 2 | tag-template: '$NEXT_PATCH_VERSION' 3 | categories: 4 | - title: '⚠️ Breaking Changes' 5 | labels: 6 | - 'feat!' 7 | - title: '🚀 New Features' 8 | labels: 9 | - 'feat' 10 | - title: '🐛 Bug Fixes' 11 | labels: 12 | - 'fix' 13 | - title: '🧰 Maintenance' 14 | labels: 15 | - 'refactor' 16 | - 'docs' 17 | change-template: '* $TITLE (#$NUMBER)' 18 | template: | 19 | $CHANGES 20 | -------------------------------------------------------------------------------- /.github/workflows/deploy-gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | deploy: 13 | runs-on: macos-12 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Install dependencies 18 | run: | 19 | sudo gem install jazzy 20 | brew bundle 21 | bundle install 22 | 23 | - name: Set version env var 24 | run: | 25 | echo "CURRENT_VERSION=$(cat .version | xargs)" >> "$GITHUB_ENV" 26 | 27 | - name: Build docs 28 | env: 29 | REPO_URL: "${{ github.server_url }}/${{ github.repository }}" 30 | run: >- 31 | jazzy 32 | --output ./apidocs 33 | --build-tool-arguments 34 | -project,./LeanCloud.xcodeproj,-scheme,LeanCloud,-configuration,Release 35 | --author LeanCloud 36 | --author_url https://leancloud.cn 37 | --module LeanCloud 38 | --module-version $CURRENT_VERSION 39 | --github_url $REPO_URL 40 | --github-file-prefix "${REPO_URL}/tree/${CURRENT_VERSION}" 41 | --root-url "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}" 42 | 43 | - name: Deploy to gh-pages 44 | uses: peaceiris/actions-gh-pages@v3 45 | with: 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | publish_dir: apidocs 48 | user_name: "github-actions[bot]" 49 | user_email: "github-actions[bot]@users.noreply.github.com" 50 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into "master" 14 | - uses: release-drafter/release-drafter@v5 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Brew 2 | Brewfile.lock.json 3 | 4 | # Gem 5 | Gemfile.lock 6 | 7 | # macOS 8 | *.DS_Store 9 | 10 | # Xcode 11 | # 12 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 13 | 14 | ## User settings 15 | xcuserdata/ 16 | 17 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 18 | *.xcscmblueprint 19 | *.xccheckout 20 | 21 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 22 | build/ 23 | DerivedData/ 24 | *.moved-aside 25 | *.pbxuser 26 | !default.pbxuser 27 | *.mode1v3 28 | !default.mode1v3 29 | *.mode2v3 30 | !default.mode2v3 31 | *.perspectivev3 32 | !default.perspectivev3 33 | 34 | ## Other 35 | *.xcuserstate 36 | 37 | ## Obj-C/Swift specific 38 | *.hmap 39 | 40 | ## App packaging 41 | *.ipa 42 | *.dSYM.zip 43 | *.dSYM 44 | 45 | ## Playgrounds 46 | timeline.xctimeline 47 | playground.xcworkspace 48 | 49 | # Swift Package Manager 50 | # 51 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 52 | # Packages/ 53 | # Package.pins 54 | Package.resolved 55 | # *.xcodeproj 56 | # 57 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 58 | # hence it is not needed unless you have added a package configuration file to your project 59 | # .swiftpm 60 | 61 | .build/ 62 | 63 | # CocoaPods 64 | # 65 | # We recommend against adding the Pods directory to your .gitignore. However 66 | # you should judge for yourself, the pros and cons are mentioned at: 67 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 68 | # 69 | # Pods/ 70 | # 71 | # Add this line if you want to avoid checking in source code from the Xcode workspace 72 | # *.xcworkspace 73 | 74 | # Carthage 75 | # 76 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 77 | # Carthage/Checkouts 78 | 79 | Carthage/Build 80 | 81 | # Accio dependency management 82 | Dependencies/ 83 | .accio/ 84 | 85 | # fastlane 86 | # 87 | # It is recommended to not store the screenshots in the git repo. 88 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 89 | # For more information about the recommended setup visit: 90 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 91 | 92 | fastlane/report.xml 93 | fastlane/Preview.html 94 | fastlane/screenshots/**/*.png 95 | fastlane/test_output 96 | 97 | # Code Injection 98 | # 99 | # After new code Injection tools there's a generated folder /iOSInjectionProject 100 | # https://github.com/johnno1962/injectionforxcode 101 | 102 | iOSInjectionProject/ 103 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/swift-sdk/472784ce0d5ea74f0dbbc275a0a7552d500b55ae/.gitmodules -------------------------------------------------------------------------------- /.version: -------------------------------------------------------------------------------- 1 | 17.11.0 2 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew "swift-protobuf" 2 | brew "hub" -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' do 2 | gem 'cocoapods' 3 | gem 'jazzy' 4 | end -------------------------------------------------------------------------------- /LeanCloud.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'LeanCloud' 3 | s.version = '17.11.0' 4 | s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } 5 | s.summary = 'LeanCloud Swift SDK' 6 | s.homepage = 'https://leancloud.cn/' 7 | s.authors = 'LeanCloud' 8 | s.source = { :git => 'https://github.com/leancloud/swift-sdk.git', :tag => s.version } 9 | 10 | s.swift_version = '5.0' 11 | s.default_subspec = 'RTM' 12 | 13 | s.ios.deployment_target = '11.0' 14 | s.osx.deployment_target = '10.13' 15 | s.tvos.deployment_target = '11.0' 16 | s.watchos.deployment_target = '4.0' 17 | 18 | s.subspec 'Foundation' do |ss| 19 | ss.dependency 'Alamofire', '~> 5.7' 20 | 21 | ss.source_files = 'Sources/Foundation/**/*.{swift}' 22 | end 23 | 24 | s.subspec 'RTM' do |ss| 25 | ss.dependency 'SwiftProtobuf', '~> 1.22' 26 | ss.dependency 'GRDB.swift', '~> 6.15.0' 27 | 28 | ss.dependency 'LeanCloud/Foundation', "#{s.version}" 29 | 30 | ss.source_files = 'Sources/RTM/**/*.{swift}' 31 | end 32 | 33 | s.subspec 'RTM-no-local-storage' do |ss| 34 | ss.dependency 'SwiftProtobuf', '~> 1.22' 35 | 36 | ss.dependency 'LeanCloud/Foundation', "#{s.version}" 37 | 38 | ss.source_files = 'Sources/RTM/**/*.{swift}' 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /LeanCloud.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LeanCloud.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LeanCloud.xcodeproj/xcshareddata/xcschemes/CLI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /LeanCloud.xcodeproj/xcshareddata/xcschemes/LeanCloud.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 51 | 57 | 58 | 59 | 60 | 61 | 72 | 73 | 79 | 80 | 81 | 82 | 86 | 87 | 88 | 89 | 95 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /LeanCloud.xcodeproj/xcshareddata/xcschemes/LeanCloudTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 63 | 64 | 70 | 71 | 72 | 73 | 77 | 78 | 79 | 80 | 86 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /LeanCloud.xcodeproj/xcshareddata/xcschemes/RuntimeTests-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /LeanCloud/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LeanCloud/LeanCloud.h: -------------------------------------------------------------------------------- 1 | // 2 | // LeanCloud.h 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/22/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for LeanCloud. 12 | FOUNDATION_EXPORT double LeanCloudVersionNumber; 13 | 14 | //! Project version string for LeanCloud. 15 | FOUNDATION_EXPORT const unsigned char LeanCloudVersionString[]; 16 | -------------------------------------------------------------------------------- /LeanCloudTests/BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // BridgingHeader.h 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 5/3/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | -------------------------------------------------------------------------------- /LeanCloudTests/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 | -------------------------------------------------------------------------------- /LeanCloudTests/LCACLTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCACLTestCase.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 5/6/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCACLTestCase: BaseTestCase { 13 | 14 | func testGetAndSet() { 15 | let acl = LCACL() 16 | 17 | /* Update access permission for public. */ 18 | XCTAssertFalse(acl.getAccess(.read)) 19 | acl.setAccess(.read, allowed: true) 20 | XCTAssertTrue(acl.getAccess(.read)) 21 | XCTAssertFalse(acl.getAccess([.read, .write])) 22 | acl.setAccess(.write, allowed: true) 23 | XCTAssertTrue(acl.getAccess(.write)) 24 | XCTAssertTrue(acl.getAccess([.read, .write])) 25 | 26 | let userID = "1" 27 | let roleName = "2" 28 | let readKey = "read" 29 | let writeKey = "write" 30 | let roleAccessKey = LCACL.accessKey(roleName: roleName) 31 | 32 | /* Update access permission for user. */ 33 | acl.setAccess([.read, .write], allowed: true, forUserID: userID) 34 | XCTAssertTrue(acl.getAccess([.read, .write], forUserID: userID)) 35 | acl.setAccess(.write, allowed: false, forUserID: userID) 36 | XCTAssertFalse(acl.getAccess(.write, forUserID: userID)) 37 | 38 | /* Update access permission for role. */ 39 | acl.setAccess([.read, .write], allowed: true, forRoleName: roleName) 40 | XCTAssertEqual(acl.value[roleAccessKey]!, [readKey: true, writeKey: true]) 41 | acl.setAccess(.write, allowed: false, forRoleName: roleName) 42 | XCTAssertEqual(acl.value[roleAccessKey]!, [readKey: true]) 43 | XCTAssertTrue(acl.getAccess(.read, forRoleName: roleName)) 44 | } 45 | 46 | func testPublicACL() { 47 | let object = TestObject() 48 | object.ACL = LCACL() 49 | XCTAssertTrue(object.save().isSuccess) 50 | XCTAssertTrue(object.fetch().isFailure) 51 | } 52 | 53 | func testPublicRead() { 54 | var user: LCUser! = LCUser() 55 | user.username = uuid.lcString 56 | user.password = uuid.lcString 57 | XCTAssertTrue(user.signUp().isSuccess) 58 | user = LCUser.logIn( 59 | username: user.username!.value, 60 | password: user.password!.value) 61 | .object 62 | XCTAssertNotNil(user) 63 | 64 | let object = self.object() 65 | let acl = LCACL() 66 | acl.setAccess(.read, allowed: true) 67 | acl.setAccess(.write, allowed: false) 68 | acl.setAccess(.read, allowed: false, forUserID: user.objectId!.value) 69 | acl.setAccess(.write, allowed: true, forUserID: user.objectId!.value) 70 | object.ACL = acl 71 | XCTAssertTrue(object.save().isSuccess) 72 | 73 | let query = LCQuery(className: self.className) 74 | query.whereKey("objectId", .equalTo(object.objectId!)) 75 | XCTAssertFalse(query.find().objects!.isEmpty) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /LeanCloudTests/LCAPITestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCAPITestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by Tianyong Tang on 2018/9/5. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCAPITestCase: BaseTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testUrlEncoding() { 25 | XCTAssertEqual("foo bar".urlPathEncoded, "foo%20bar") 26 | XCTAssertEqual("+8610000000".urlQueryEncoded, "%2B8610000000") 27 | } 28 | 29 | func testCancelSingleRequest() { 30 | let dispatchGroup = DispatchGroup() 31 | 32 | dispatchGroup.enter() 33 | 34 | let request = LCApplication.default.httpClient.request(.get, "ping") { response in 35 | dispatchGroup.leave() 36 | } 37 | 38 | request.cancel() /* Cancel request immediately. */ 39 | 40 | dispatchGroup.wait() 41 | } 42 | 43 | var newbornOrphanObservation: NSKeyValueObservation? 44 | 45 | func testCancelSequenceRequest() { 46 | let object = TestObject() 47 | 48 | let newbornOrphan1 = TestObject() 49 | let newbornOrphan2 = TestObject() 50 | 51 | newbornOrphan1.arrayField = [newbornOrphan2] 52 | 53 | object.arrayField = [newbornOrphan1] 54 | 55 | var result: LCBooleanResult? 56 | 57 | let request = object.save { aResult in 58 | result = aResult 59 | } 60 | 61 | newbornOrphanObservation = newbornOrphan2.observe(\.objectId) { (_, change) in 62 | request.cancel() 63 | } 64 | 65 | busywait { result != nil } 66 | 67 | XCTAssertFalse(result!.isSuccess) 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /LeanCloudTests/LCApplicationTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCApplicationTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by zapcannon87 on 2019/5/7. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCApplicationTestCase: BaseTestCase { 13 | 14 | func testRegistry() { 15 | XCTAssertTrue(LCApplication.registry[LCApplication.default.id] === LCApplication.default) 16 | } 17 | 18 | func testLogLevel() { 19 | Array<(LCApplication.LogLevel, LCApplication.LogLevel)>([ 20 | (.all, .verbose), 21 | (.verbose, .debug), 22 | (.debug, .error), 23 | (.error, .off) 24 | ]).forEach { (left, right) in 25 | XCTAssertGreaterThan(left, right) 26 | } 27 | } 28 | 29 | func testBasic() { 30 | if [.cn, .ce].contains(LCApplication.default.region) { 31 | XCTAssertNotNil(LCApplication.default.id) 32 | XCTAssertNotNil(LCApplication.default.key) 33 | XCTAssertNotNil(LCApplication.default.serverURL) 34 | } else if [.us].contains(LCApplication.default.region) { 35 | XCTAssertNotNil(LCApplication.default.id) 36 | XCTAssertNotNil(LCApplication.default.key) 37 | } 38 | } 39 | 40 | func testServerCustomizableModule() { 41 | let host = "avoscloud.com" 42 | let config = LCApplication.Configuration( 43 | customizedServers: [ 44 | .api(host), 45 | .push(host), 46 | .rtm(host), 47 | .engine(host)]) 48 | let app = try! LCApplication( 49 | id: UUID().uuidString, 50 | key: UUID().uuidString, 51 | serverURL: "leancloud.cn", 52 | configuration: config) 53 | 54 | Array([.api, .push, .rtm, .engine]).forEach { (module) in 55 | if module == .rtm { 56 | XCTAssertEqual( 57 | app.appRouter.route(path: "foo", module: module), 58 | URL(string: "https://\(host)/foo")) 59 | } else { 60 | XCTAssertEqual( 61 | app.appRouter.route(path: "foo", module: module), 62 | URL(string: "https://\(host)/\(AppRouter.Configuration.default.apiVersion)/foo")) 63 | } 64 | } 65 | 66 | app.unregister() 67 | } 68 | 69 | func testEnvironment() { 70 | let config = LCApplication.Configuration( 71 | environment: [ 72 | .cloudEngineDevelopment, 73 | .pushDevelopment]) 74 | let app = try! LCApplication( 75 | id: UUID().uuidString, 76 | key: UUID().uuidString, 77 | serverURL: "leancloud.cn", 78 | configuration: config) 79 | 80 | XCTAssertEqual(app.cloudEngineMode, "0") 81 | XCTAssertEqual(app.pushMode, "dev") 82 | 83 | try! app.set( 84 | id: app.id, 85 | key: app.key, 86 | serverURL: app.serverURL, 87 | configuration: LCApplication.Configuration( 88 | environment: .default)) 89 | 90 | XCTAssertEqual(app.cloudEngineMode, "1") 91 | XCTAssertEqual(app.pushMode, "prod") 92 | 93 | app.unregister() 94 | } 95 | 96 | func testRegion() { 97 | Array<(String, LCApplication.Region)>([ 98 | (UUID().uuidString + "-gzGzoHsz", .cn), 99 | (UUID().uuidString.replacingOccurrences(of: "-", with: ""), .cn), 100 | (UUID().uuidString + "-9Nh9j0Va", .ce), 101 | (UUID().uuidString + "-MdYXbMMI", .us) 102 | ]).forEach { (id, region) in 103 | let app = try! LCApplication( 104 | id: id, 105 | key: UUID().uuidString, 106 | serverURL: "leancloud.cn") 107 | XCTAssertEqual(app.region, region) 108 | app.unregister() 109 | } 110 | } 111 | 112 | func testInit() { 113 | Array([ 114 | UUID().uuidString + "-gzGzoHsz", 115 | UUID().uuidString.replacingOccurrences(of: "-", with: ""), 116 | UUID().uuidString + "-9Nh9j0Va" 117 | ]).forEach { (id) in 118 | do { 119 | _ = try LCApplication( 120 | id: id, 121 | key: UUID().uuidString, 122 | serverURL: nil) 123 | XCTFail() 124 | } catch { 125 | XCTAssertTrue(error is LCError) 126 | } 127 | do { 128 | let app = try LCApplication( 129 | id: id, 130 | key: UUID().uuidString, 131 | serverURL: "leancloud.cn") 132 | Array([.api, .push, .rtm, .engine]).forEach { (module) in 133 | if module == .rtm { 134 | XCTAssertEqual( 135 | app.appRouter.route(path: "foo", module: module), 136 | URL(string: "https://leancloud.cn/foo")) 137 | } else { 138 | XCTAssertEqual( 139 | app.appRouter.route(path: "foo", module: module), 140 | URL(string: "https://leancloud.cn/\(AppRouter.Configuration.default.apiVersion)/foo")) 141 | } 142 | } 143 | app.unregister() 144 | } catch { 145 | XCTFail("\(error)") 146 | } 147 | } 148 | 149 | do { 150 | let app = try LCApplication( 151 | id: UUID().uuidString + "-MdYXbMMI", 152 | key: UUID().uuidString) 153 | app.unregister() 154 | } catch { 155 | XCTFail("\(error)") 156 | } 157 | } 158 | 159 | func testDeinit() { 160 | let appID = UUID().uuidString 161 | var app: LCApplication! = try! LCApplication( 162 | id: appID, 163 | key: UUID().uuidString, 164 | serverURL: "https://leancloud.cn") 165 | XCTAssertTrue(LCApplication.registry[appID] === app) 166 | XCTAssertNotNil(app.localStorageContext) 167 | XCTAssertNotNil(app.httpClient) 168 | XCTAssertNotNil(app.appRouter) 169 | weak var wApp = app 170 | app.unregister() 171 | app = nil 172 | delay() 173 | XCTAssertNil(wApp) 174 | } 175 | 176 | func testCurrentInstallation() { 177 | let installation1 = LCApplication.default.currentInstallation 178 | installation1.set( 179 | deviceToken: UUID().uuidString, 180 | apnsTeamId: "LeanCloud") 181 | XCTAssertTrue(installation1.save().isSuccess) 182 | 183 | try! LCApplication.default.set( 184 | id: LCApplication.default.id, 185 | key: LCApplication.default.key, 186 | serverURL: LCApplication.default.serverURL) 187 | let installation2 = LCApplication.default.currentInstallation 188 | 189 | XCTAssertTrue(installation1 !== installation2) 190 | XCTAssertEqual( 191 | installation1.deviceToken?.value, 192 | installation2.deviceToken?.value) 193 | 194 | if let fileURL = LCApplication.default.currentInstallationFileURL, 195 | FileManager.default.fileExists(atPath: fileURL.path) { 196 | try! FileManager.default.removeItem(at: fileURL) 197 | } 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /LeanCloudTests/LCCoreTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCCoreTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by Tianyong Tang on 2018/9/26. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCCoreTestCase: BaseTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testSequenceUnique() { 25 | let object1 = TestObject() 26 | let object2 = TestObject() 27 | let object3 = TestObject() 28 | 29 | XCTAssertEqual( 30 | [object1, object3, object2, object1].unique, 31 | [object1, object3, object2]) 32 | 33 | XCTAssertEqual( 34 | [1, 3, 2, 1].unique, 35 | [1, 3, 2]) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /LeanCloudTests/LCEngineTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCEngineTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by zapcannon87 on 2019/7/4. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCEngineTestCase: BaseTestCase { 13 | 14 | func testError() { 15 | let result = LCEngine.run("error") 16 | XCTAssertNil(result.value) 17 | XCTAssertNotNil(LCEngine.run("error").error) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LeanCloudTests/LCFileTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCFileTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by Tianyong Tang on 2018/9/20. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCFileTestCase: BaseTestCase { 13 | 14 | func testDeinit() { 15 | var f: LCFile! = LCFile() 16 | weak var wf = f 17 | f = nil 18 | XCTAssertNil(wf) 19 | } 20 | 21 | func testSave() { 22 | let fileURL = bundleResourceURL(name: "test", ext: "png") 23 | let application = LCApplication.default 24 | 25 | var file1: LCFile! = LCFile( 26 | application: application, 27 | payload: .fileURL(fileURL: fileURL)) 28 | XCTAssertTrue(file1.save().isSuccess) 29 | XCTAssertNotNil(file1.mimeType) 30 | XCTAssertNotNil(file1.key) 31 | XCTAssertNotNil(file1.name) 32 | XCTAssertNotNil(file1.metaData?.size as? LCNumber) 33 | XCTAssertNotNil(file1.bucket) 34 | XCTAssertNotNil(file1.provider) 35 | XCTAssertNotNil(file1.url) 36 | XCTAssertNotNil(file1.objectId) 37 | XCTAssertNotNil(file1.createdAt) 38 | XCTAssertNotNil(file1.save().error) 39 | 40 | var file2: LCFile! = LCFile( 41 | application: application, 42 | payload: .data(data: try! Data(contentsOf: fileURL))) 43 | file2.name = "image.png" 44 | XCTAssertTrue(file2.save().isSuccess) 45 | XCTAssertNotNil(file2.mimeType) 46 | XCTAssertNotNil(file2.key) 47 | XCTAssertNotNil(file2.name) 48 | XCTAssertNotNil(file2.metaData?.size as? LCNumber) 49 | XCTAssertNotNil(file2.bucket) 50 | XCTAssertNotNil(file2.provider) 51 | XCTAssertNotNil(file2.url) 52 | XCTAssertNotNil(file2.objectId) 53 | XCTAssertNotNil(file2.createdAt) 54 | XCTAssertNotNil(file2.save().error) 55 | 56 | var file3: LCFile! = LCFile( 57 | application: application, 58 | url: file2.url!) 59 | XCTAssertTrue(file3.save().isSuccess) 60 | XCTAssertNotNil(file3.mimeType) 61 | XCTAssertNotNil(file3.name) 62 | XCTAssertEqual(file3.metaData?.__source as? LCString, LCString("external")) 63 | XCTAssertNotNil(file3.url) 64 | XCTAssertNotNil(file3.objectId) 65 | XCTAssertNotNil(file3.createdAt) 66 | XCTAssertNotNil(file3.save().error) 67 | 68 | delay() 69 | 70 | weak var wFile1 = file1 71 | weak var wFile2 = file2 72 | weak var wFile3 = file3 73 | file1 = nil 74 | file2 = nil 75 | file3 = nil 76 | XCTAssertNil(wFile1) 77 | XCTAssertNil(wFile2) 78 | XCTAssertNil(wFile3) 79 | } 80 | 81 | func testSaveAsync() { 82 | let fileURL = bundleResourceURL(name: "test", ext: "png") 83 | var file: LCFile! = LCFile(payload: .fileURL(fileURL: fileURL)) 84 | 85 | expecting { (exp) in 86 | file.save(progress: { (progress) in 87 | XCTAssertTrue(Thread.isMainThread) 88 | print(progress) 89 | }) { (result) in 90 | XCTAssertTrue(Thread.isMainThread) 91 | XCTAssertTrue(result.isSuccess) 92 | XCTAssertNil(result.error) 93 | exp.fulfill() 94 | } 95 | } 96 | 97 | delay() 98 | 99 | weak var wf: LCFile? = file 100 | file = nil 101 | XCTAssertNil(wf) 102 | } 103 | 104 | func testSaveOptions() { 105 | let fileURL = bundleResourceURL(name: "test", ext: "png") 106 | let file = LCFile(payload: .fileURL(fileURL: fileURL)) 107 | XCTAssertTrue(file.save(options: .keepFileName).isSuccess) 108 | XCTAssertTrue(file.url!.value.hasSuffix("/test.png")) 109 | } 110 | 111 | func testFetch() { 112 | let fileURL = bundleResourceURL(name: "test", ext: "png") 113 | let savedFile = LCFile(payload: .fileURL(fileURL: fileURL)) 114 | XCTAssertTrue(savedFile.save().isSuccess) 115 | 116 | let file = LCFile(objectId: savedFile.objectId!) 117 | XCTAssertTrue(file.fetch().isSuccess) 118 | XCTAssertEqual(savedFile.mimeType, file.mimeType) 119 | XCTAssertEqual(savedFile.key, file.key) 120 | XCTAssertEqual(savedFile.name, file.name) 121 | XCTAssertEqual(savedFile.metaData?.size as? LCNumber, file.metaData?.size as? LCNumber) 122 | XCTAssertEqual(savedFile.bucket, file.bucket) 123 | XCTAssertEqual(savedFile.provider, file.provider) 124 | XCTAssertEqual(savedFile.url, file.url) 125 | XCTAssertEqual(savedFile.objectId, file.objectId) 126 | XCTAssertEqual(savedFile.createdAt, file.createdAt) 127 | } 128 | 129 | func testPointer() { 130 | let fileURL = bundleResourceURL(name: "test", ext: "png") 131 | let file = LCFile(payload: .fileURL(fileURL: fileURL)) 132 | XCTAssertTrue(file.save().isSuccess) 133 | 134 | let object = self.object() 135 | object.fileField = file 136 | XCTAssertTrue(object.save().isSuccess) 137 | 138 | let objectShadow = self.object(object.objectId!) 139 | XCTAssertTrue(objectShadow.fetch().isSuccess) 140 | 141 | let shadowFile = objectShadow.fileField as? LCFile 142 | XCTAssertNotNil(shadowFile) 143 | XCTAssertEqual(shadowFile?.mimeType, file.mimeType) 144 | XCTAssertEqual(shadowFile?.key, file.key) 145 | XCTAssertEqual(shadowFile?.name, file.name) 146 | XCTAssertEqual(shadowFile?.metaData?.size as? LCNumber, file.metaData?.size as? LCNumber) 147 | XCTAssertEqual(shadowFile?.bucket, file.bucket) 148 | XCTAssertEqual(shadowFile?.provider, file.provider) 149 | XCTAssertEqual(shadowFile?.url, file.url) 150 | XCTAssertEqual(shadowFile?.objectId, file.objectId) 151 | XCTAssertEqual(shadowFile?.createdAt, file.createdAt) 152 | 153 | object.fileField = LCFile(payload: .fileURL(fileURL: fileURL)) 154 | XCTAssertNotNil(object.save().error) 155 | object.fileField = LCFile(url: file.url!) 156 | XCTAssertTrue(object.save().isSuccess) 157 | 158 | object.filesField = [ 159 | LCFile(payload: .fileURL(fileURL: fileURL)), 160 | LCFile(payload: .fileURL(fileURL: fileURL))] 161 | XCTAssertNotNil(object.save().error) 162 | object.filesField = [LCFile(url: file.url!), LCFile(url: file.url!)] 163 | XCTAssertTrue(object.save().isSuccess) 164 | 165 | object.fileMapField = ["file1": LCFile(payload: .fileURL(fileURL: fileURL))] 166 | XCTAssertNotNil(object.save().error) 167 | object.fileMapField = ["file1": LCFile(url: file.url!)] 168 | XCTAssertTrue(object.save().isSuccess) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /LeanCloudTests/LCInstallationTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCInstallationTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by Tianyong Tang on 2018/10/17. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCInstallationTestCase: BaseTestCase { 13 | 14 | func testCurrentInstallation() { 15 | let fileURL = LCApplication.default.currentInstallationFileURL! 16 | 17 | if FileManager.default.fileExists(atPath: fileURL.path) { 18 | try! FileManager.default.removeItem(at: fileURL) 19 | } 20 | 21 | let installation = LCInstallation() 22 | installation.set( 23 | deviceToken: UUID().uuidString, 24 | apnsTeamId: "LeanCloud") 25 | XCTAssertTrue(installation.save().isSuccess) 26 | 27 | XCTAssertNil(LCInstallation.currentInstallation(application: .default)) 28 | LCInstallation.saveCurrentInstallation(installation) 29 | XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path)) 30 | 31 | let currentInstallation = LCInstallation.currentInstallation(application: .default) 32 | XCTAssertEqual( 33 | currentInstallation?.objectId?.value, 34 | installation.objectId?.value) 35 | XCTAssertEqual( 36 | currentInstallation?.deviceToken?.value, 37 | installation.deviceToken?.value) 38 | XCTAssertEqual( 39 | currentInstallation?.apnsTeamId?.value, 40 | installation.apnsTeamId?.value) 41 | XCTAssertEqual( 42 | currentInstallation?.apnsTopic?.value, 43 | installation.apnsTopic?.value) 44 | XCTAssertEqual( 45 | currentInstallation?.deviceType?.value, 46 | installation.deviceType?.value) 47 | XCTAssertEqual( 48 | currentInstallation?.timeZone?.value, 49 | installation.timeZone?.value) 50 | 51 | if FileManager.default.fileExists(atPath: fileURL.path) { 52 | try! FileManager.default.removeItem(at: fileURL) 53 | } 54 | } 55 | 56 | func testSetDeviceTokenAndTeamID() { 57 | let installation = LCInstallation() 58 | let deviceToken = UUID().uuidString 59 | try! installation.set("deviceToken", value: deviceToken) 60 | try! installation.set("apnsTeamId", value: "LeanCloud") 61 | installation.badge = 0 62 | installation.channels = LCArray(["foo"]) 63 | try! installation.append("channels", element: "bar", unique: true) 64 | XCTAssertTrue(installation.save().isSuccess) 65 | XCTAssertEqual(installation.deviceToken?.value, deviceToken) 66 | XCTAssertEqual(installation.apnsTeamId?.value, "LeanCloud") 67 | XCTAssertEqual(installation.badge, LCNumber(0)) 68 | XCTAssertEqual(installation.channels, LCArray(["foo", "bar"])) 69 | XCTAssertNotNil(installation.timeZone) 70 | XCTAssertNotNil(installation.deviceType) 71 | XCTAssertNotNil(installation.apnsTopic) 72 | XCTAssertNotNil(installation.objectId) 73 | XCTAssertNotNil(installation.createdAt) 74 | 75 | let shadow = LCInstallation(objectId: installation.objectId!) 76 | XCTAssertTrue(shadow.fetch().isSuccess) 77 | XCTAssertEqual(installation.deviceToken, shadow.deviceToken) 78 | XCTAssertEqual(installation.apnsTeamId, shadow.apnsTeamId) 79 | XCTAssertEqual(installation.badge, shadow.badge) 80 | XCTAssertEqual(installation.channels, shadow.channels) 81 | XCTAssertEqual(installation.timeZone, shadow.timeZone) 82 | XCTAssertEqual(installation.deviceType, shadow.deviceType) 83 | XCTAssertEqual(installation.apnsTopic, shadow.apnsTopic) 84 | XCTAssertNotNil(shadow.objectId) 85 | XCTAssertNotNil(shadow.createdAt) 86 | XCTAssertNotNil(shadow.updatedAt) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /LeanCloudTests/LCLocalStorageContextTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCLocalStorageContextTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by zapcannon87 on 2019/4/10. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCLocalStorageTestCase: BaseTestCase { 13 | 14 | var application: LCApplication! 15 | var localStorage: LocalStorageContext { 16 | return application.localStorageContext! 17 | } 18 | 19 | let tuples: [(LocalStorageContext.Place, LocalStorageContext.Module, LocalStorageContext.File)] = [ 20 | (.systemCaches, .router, .appServer), 21 | (.systemCaches, .router, .rtmServer), 22 | (.systemCaches, .push, .installation), 23 | (.persistentData, .storage, .user), 24 | (.persistentData, .IM(clientID: UUID().uuidString), .clientRecord), 25 | (.persistentData, .IM(clientID: UUID().uuidString), .database) 26 | ] 27 | 28 | override func setUp() { 29 | super.setUp() 30 | self.application = try! LCApplication( 31 | id: UUID().uuidString, 32 | key: UUID().uuidString, 33 | serverURL: "leancloud.cn") 34 | } 35 | 36 | override func tearDown() { 37 | [self.application.applicationSupportDirectoryURL, 38 | self.application.cachesDirectoryURL] 39 | .forEach { (url) in 40 | if FileManager.default.fileExists(atPath: url.path) { 41 | try! FileManager.default.removeItem(at: url) 42 | } 43 | } 44 | self.application.unregister() 45 | super.tearDown() 46 | } 47 | 48 | func testInitAndDeinit() { 49 | var ref: LocalStorageContext? = LocalStorageContext(application: self.application) 50 | weak var weakRef: LocalStorageContext? = ref 51 | ref = nil 52 | XCTAssertNil(weakRef) 53 | } 54 | 55 | func testFileURL() { 56 | self.tuples.forEach { (place, module, file) in 57 | let fileURL = try! self.localStorage.fileURL( 58 | place: place, 59 | module: module, 60 | file: file) 61 | let systemPath: String 62 | switch place { 63 | case .systemCaches: 64 | systemPath = "Library/Caches/" 65 | case .persistentData: 66 | systemPath = "Library/Application Support/" 67 | } 68 | XCTAssertTrue(fileURL.path.hasSuffix(systemPath 69 | + LocalStorageContext.domain + "/" 70 | + self.localStorage.application.id.md5.lowercased() + "/" 71 | + module.path + "/" 72 | + file.name)) 73 | var isDirectory: ObjCBool = false 74 | XCTAssertTrue(FileManager.default.fileExists( 75 | atPath: fileURL.deletingLastPathComponent().path, 76 | isDirectory: &isDirectory)) 77 | XCTAssertTrue(isDirectory.boolValue) 78 | } 79 | } 80 | 81 | func testSaveAndGet() { 82 | self.tuples.forEach { (place, module, file) in 83 | let fileURL = try! self.localStorage.fileURL( 84 | place: place, 85 | module: module, 86 | file: file) 87 | let saveTable = TestTable(string: UUID().uuidString) 88 | try! self.localStorage.save(table: saveTable, to: fileURL) 89 | var isDirectory: ObjCBool = true 90 | XCTAssertTrue(FileManager.default.fileExists( 91 | atPath: fileURL.path, 92 | isDirectory: &isDirectory)) 93 | XCTAssertFalse(isDirectory.boolValue) 94 | let getTable: TestTable? = try! self.localStorage.table(from: fileURL) 95 | XCTAssertNotNil(getTable) 96 | XCTAssertEqual(getTable?.string, saveTable.string) 97 | try! self.localStorage.clear(file: fileURL) 98 | XCTAssertFalse(FileManager.default.fileExists( 99 | atPath: fileURL.path)) 100 | } 101 | } 102 | 103 | } 104 | 105 | extension LCLocalStorageTestCase { 106 | 107 | struct TestTable: Codable { 108 | var string: String? 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /LeanCloudTests/LCPushTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCPushTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by zapcannon87 on 2019/7/9. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCPushTestCase: BaseTestCase { 13 | 14 | func testSend() { 15 | let data = ["alert": "test"] 16 | 17 | XCTAssertTrue(LCPush.send(data: data).isSuccess) 18 | } 19 | 20 | func testSendWithQuery() { 21 | let data = ["alert": "test"] 22 | 23 | let query = LCQuery(className: LCInstallation.objectClassName()) 24 | query.whereKey("deviceType", .equalTo("ios")) 25 | 26 | XCTAssertTrue(LCPush.send(data: data, query: query).isSuccess) 27 | } 28 | 29 | func testSendWithChannels() { 30 | let data = ["alert": "test"] 31 | 32 | let channels = ["test"] 33 | 34 | XCTAssertTrue(LCPush.send(data: data, channels: channels).isSuccess) 35 | } 36 | 37 | func testSendWithPushDate() { 38 | let data = ["alert": "test"] 39 | 40 | let pushDate = Date(timeIntervalSinceNow: 5) 41 | 42 | XCTAssertTrue(LCPush.send(data: data, pushDate: pushDate).isSuccess) 43 | } 44 | 45 | func testSendWithExpirationDate() { 46 | let data = ["alert": "test"] 47 | 48 | let expirationDate = Date(timeIntervalSinceNow: 5) 49 | 50 | XCTAssertTrue(LCPush.send(data: data, expirationDate: expirationDate).isSuccess) 51 | } 52 | 53 | func testSendWithExpirationInterval() { 54 | let data = ["alert": "test"] 55 | 56 | XCTAssertTrue(LCPush.send(data: data, expirationInterval: 5).isSuccess) 57 | } 58 | 59 | func testExtraParameters() { 60 | let data = ["alert": "test"] 61 | 62 | let extraParameters = ["apns_team_id": "LeanCloud"] 63 | 64 | XCTAssertTrue(LCPush.send(data: data, extraParameters: extraParameters).isSuccess) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /LeanCloudTests/LCRelationTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCRelationTestCase.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 7/5/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCRelationTestCase: BaseTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testQuery() { 25 | let object = sharedObject 26 | let friend = sharedFriend 27 | let query = object.relationForKey("relationField").query 28 | let result = query.find() 29 | 30 | XCTAssertTrue(result.isSuccess) 31 | XCTAssertTrue(result.objects!.contains(friend)) 32 | } 33 | 34 | func testClassNameRedirection() { 35 | let object = LCObject() 36 | let friend = TestObject() 37 | 38 | try! object.insertRelation("relationField", object: friend) 39 | XCTAssertTrue(object.save().isSuccess) 40 | 41 | let shadow = LCObject(objectId: object.objectId!.value) 42 | let query = shadow.relationForKey("relationField").query 43 | 44 | XCTAssertEqual(query.objectClassName, object.actualClassName) 45 | XCTAssertNotEqual(query.objectClassName, friend.actualClassName) 46 | 47 | let result = query.find() 48 | 49 | XCTAssertTrue(result.isSuccess) 50 | XCTAssertTrue(result.objects!.contains(friend)) 51 | } 52 | 53 | func testInsertAndRemove() { 54 | let object = LCObject() 55 | let child = TestObject() 56 | let friend = object.relationForKey("relationField") 57 | let query = friend.query 58 | 59 | try! friend.insert(child) 60 | XCTAssertTrue(object.save().isSuccess) 61 | XCTAssertTrue(query.find().objects!.contains(child)) 62 | 63 | try! friend.remove(child) 64 | XCTAssertTrue(object.save().isSuccess) 65 | XCTAssertFalse(query.find().objects!.contains(child)) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /LeanCloudTests/LCRouterTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCRouterTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by Tianyong Tang on 2018/9/7. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCRouterTestCase: BaseTestCase { 13 | 14 | static let usApplication = try! LCApplication( 15 | id: BaseTestCase.usApp.id, 16 | key: BaseTestCase.usApp.key) 17 | 18 | var appRouter: AppRouter { 19 | return LCRouterTestCase.usApplication.appRouter 20 | } 21 | 22 | func testModule() { 23 | Array<(String, AppRouter.Module)>([ 24 | ("v1/route", .rtm), 25 | ("/v1/route", .rtm), 26 | ("push", .push), 27 | ("/push", .push), 28 | ("installations", .push), 29 | ("/installations", .push), 30 | ("call", .engine), 31 | ("/call", .engine), 32 | ("functions", .engine), 33 | ("/functions", .engine), 34 | ("user", .api), 35 | ("/user", .api) 36 | ]).forEach { (path, module) in 37 | XCTAssertEqual(appRouter.module(path), module) 38 | } 39 | } 40 | 41 | func testVersionizedPath() { 42 | let constant = "\(AppRouter.Configuration.default.apiVersion)/foo" 43 | ["foo", "/foo"].forEach { (path) in 44 | XCTAssertEqual( 45 | appRouter.versionizedPath(path), 46 | constant) 47 | } 48 | } 49 | 50 | func testAbsolutePath() { 51 | let constant = "/foo" 52 | ["foo", "/foo"].forEach { (path) in 53 | XCTAssertEqual( 54 | appRouter.absolutePath(path), 55 | constant) 56 | } 57 | } 58 | 59 | func testSchemingURL() { 60 | Array<(String, String)>([ 61 | ("example.com", "https://example.com"), 62 | ("http://example.com", "http://example.com"), 63 | ("https://example.com", "https://example.com"), 64 | ("example.com:8000", "https://example.com:8000"), 65 | ("http://example.com:8000", "http://example.com:8000"), 66 | ("https://example.com:8000", "https://example.com:8000") 67 | ]).forEach { (url, result) in 68 | XCTAssertEqual( 69 | appRouter.schemingURL(url), 70 | result) 71 | } 72 | } 73 | 74 | func testAbsoluteURL() { 75 | Array<(String, String, String)>([ 76 | ("example.com", "foo", "https://example.com/foo"), 77 | ("example.com/", "foo", "https://example.com/foo"), 78 | ("example.com/foo", "bar", "https://example.com/foo/bar"), 79 | ("example.com:8000", "foo", "https://example.com:8000/foo"), 80 | ("example.com:8000/", "foo", "https://example.com:8000/foo"), 81 | ("example.com:8000/foo", "bar", "https://example.com:8000/foo/bar"), 82 | 83 | ("https://example.com", "foo", "https://example.com/foo"), 84 | ("https://example.com/", "foo", "https://example.com/foo"), 85 | ("https://example.com/foo", "bar", "https://example.com/foo/bar"), 86 | ("https://example.com:8000", "foo", "https://example.com:8000/foo"), 87 | ("https://example.com:8000/", "foo", "https://example.com:8000/foo"), 88 | ("https://example.com:8000/foo", "bar", "https://example.com:8000/foo/bar"), 89 | 90 | ("http://example.com", "foo", "http://example.com/foo"), 91 | ("http://example.com/", "foo", "http://example.com/foo"), 92 | ("http://example.com/foo", "bar", "http://example.com/foo/bar"), 93 | ("http://example.com:8000", "foo", "http://example.com:8000/foo"), 94 | ("http://example.com:8000/", "foo", "http://example.com:8000/foo"), 95 | ("http://example.com:8000/foo", "bar", "http://example.com:8000/foo/bar"), 96 | ]).forEach { (host, path, result) in 97 | XCTAssertEqual( 98 | appRouter.absoluteURL(host, path: path), 99 | URL(string: result)) 100 | } 101 | } 102 | 103 | func testFallbackURL() { 104 | XCTAssertEqual( 105 | appRouter.fallbackURL(module: .api, path: "foo"), 106 | URL(string: "https://\("jenSt9nv".lowercased()).api.lncldglobal.com/foo")) 107 | XCTAssertEqual( 108 | appRouter.fallbackURL(module: .api, path: "/foo"), 109 | URL(string: "https://\("jenSt9nv".lowercased()).api.lncldglobal.com/foo")) 110 | XCTAssertEqual( 111 | appRouter.fallbackURL(module: .rtm, path: "foo"), 112 | URL(string: "https://\("jenSt9nv".lowercased()).rtm.lncldglobal.com/foo")) 113 | XCTAssertEqual( 114 | appRouter.fallbackURL(module: .rtm, path: "/foo"), 115 | URL(string: "https://\("jenSt9nv".lowercased()).rtm.lncldglobal.com/foo")) 116 | XCTAssertEqual( 117 | appRouter.fallbackURL(module: .push, path: "foo"), 118 | URL(string: "https://\("jenSt9nv".lowercased()).push.lncldglobal.com/foo")) 119 | XCTAssertEqual( 120 | appRouter.fallbackURL(module: .push, path: "/foo"), 121 | URL(string: "https://\("jenSt9nv".lowercased()).push.lncldglobal.com/foo")) 122 | XCTAssertEqual( 123 | appRouter.fallbackURL(module: .engine, path: "foo"), 124 | URL(string: "https://\("jenSt9nv".lowercased()).engine.lncldglobal.com/foo")) 125 | XCTAssertEqual( 126 | appRouter.fallbackURL(module: .engine, path: "/foo"), 127 | URL(string: "https://\("jenSt9nv".lowercased()).engine.lncldglobal.com/foo")) 128 | } 129 | 130 | func testCachedHost() { 131 | appRouter.cacheTable = nil 132 | XCTAssertNil(appRouter.cachedHost(module: .api)) 133 | 134 | delay() 135 | 136 | Array([.api, .push, .rtm, .engine]).forEach { (module) in 137 | XCTAssertNotNil(appRouter.cachedHost(module: module)) 138 | } 139 | 140 | appRouter.cacheTable!.createdTimestamp = 141 | appRouter.cacheTable!.createdTimestamp! - 142 | appRouter.cacheTable!.ttl! 143 | XCTAssertNil(appRouter.cachedHost(module: .api)) 144 | } 145 | 146 | func testGetAppRouter() { 147 | expecting { (exp) in 148 | appRouter.getAppRouter { (response) in 149 | XCTAssertTrue(response.isSuccess) 150 | exp.fulfill() 151 | } 152 | } 153 | } 154 | 155 | func testRequestAppRouter() { 156 | for i in 0...1 { 157 | if i == 1 { 158 | XCTAssertTrue(appRouter.isRequesting) 159 | } 160 | appRouter.requestAppRouter() 161 | } 162 | 163 | delay() 164 | 165 | XCTAssertTrue(FileManager.default.fileExists(atPath: appRouter.cacheFileURL!.path)) 166 | try! LCRouterTestCase.usApplication.set( 167 | id: LCRouterTestCase.usApplication.id, 168 | key: LCRouterTestCase.usApplication.key) 169 | XCTAssertNotNil(appRouter.cacheTable) 170 | } 171 | 172 | func testBatchRequestPath() { 173 | let constant = "/\(AppRouter.Configuration.default.apiVersion)/foo" 174 | ["foo", "/foo"].forEach { (path) in 175 | XCTAssertEqual( 176 | appRouter.batchRequestPath(path), 177 | constant) 178 | } 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /LeanCloudTests/LCSMSTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCSMSTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by zapcannon87 on 2020/1/2. 6 | // Copyright © 2020 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCSMSTestCase: BaseTestCase { 13 | 14 | let mobilePhoneNumber = "" 15 | 16 | // func testRequestVerificationCode() { 17 | // XCTAssertTrue(LCSMSClient.requestVerificationCode( 18 | // mobilePhoneNumber: self.mobilePhoneNumber, 19 | // timeToLive: 1) 20 | // .isSuccess) 21 | // } 22 | 23 | // func testVerifyMobilePhoneNumber() { 24 | // XCTAssertTrue(LCSMSClient.verifyMobilePhoneNumber( 25 | // self.mobilePhoneNumber, 26 | // verificationCode: "") 27 | // .isSuccess) 28 | // } 29 | } 30 | -------------------------------------------------------------------------------- /LeanCloudTests/LCTypeTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCTypeTestCase.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 9/2/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCTypeTestCase: BaseTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func convert(_ object: LCValueConvertible) -> LCValue { 25 | return object.lcValue 26 | } 27 | 28 | func testNullConvertible() { 29 | XCTAssertEqual(convert(NSNull()) as? LCNull, LCNull()) 30 | } 31 | 32 | func testBoolConvertible() { 33 | XCTAssertEqual(convert(true) as? LCBool, true) 34 | XCTAssertEqual(LCBool(true).boolValue, true) 35 | XCTAssertFalse(LCBool(LCBool()).value) 36 | } 37 | 38 | func testIntegerConvertible() { 39 | XCTAssertEqual(convert(Int(42)) as? LCNumber, 42) 40 | XCTAssertEqual(convert(UInt(42)) as? LCNumber, 42) 41 | XCTAssertEqual(convert(Int8(42)) as? LCNumber, 42) 42 | XCTAssertEqual(convert(UInt8(42)) as? LCNumber, 42) 43 | XCTAssertEqual(convert(Int16(42)) as? LCNumber, 42) 44 | XCTAssertEqual(convert(UInt16(42)) as? LCNumber, 42) 45 | XCTAssertEqual(convert(Int32(42)) as? LCNumber, 42) 46 | XCTAssertEqual(convert(UInt32(42)) as? LCNumber, 42) 47 | XCTAssertEqual(convert(Int64(42)) as? LCNumber, 42) 48 | XCTAssertEqual(convert(UInt64(42)) as? LCNumber, 42) 49 | } 50 | 51 | func testFloatConvertible() { 52 | XCTAssertEqual(convert(Float(42)) as? LCNumber, 42) 53 | XCTAssertEqual(convert(Double(42)) as? LCNumber, 42) 54 | XCTAssertEqual(LCNumber(), LCNumber(LCNumber())) 55 | } 56 | 57 | func testStringConvertible() { 58 | XCTAssertEqual(convert("foo") as? LCString, "foo") 59 | XCTAssertEqual(convert(NSString(string: "foo")) as? LCString, "foo") 60 | XCTAssertEqual(LCString(), LCString(LCString())) 61 | } 62 | 63 | func testArrayInit() { 64 | let array1 = LCArray([42]) 65 | let array2 = LCArray(array1) 66 | XCTAssertFalse(array1 === array2) 67 | XCTAssertEqual(array1.value.count, array2.value.count) 68 | XCTAssertEqual(array1.value.first as? LCNumber, array2.value.first as? LCNumber) 69 | } 70 | 71 | func testArrayConvertible() { 72 | let date = Date() 73 | let object = LCObject() 74 | 75 | XCTAssertEqual( 76 | LCArray([42, true, NSNull(), [String: String](), [String](), Data(), date, object]), 77 | try LCArray(unsafeObject: [42, true, NSNull(), [String: String](), [String](), Data(), date, object] as [Any])) 78 | } 79 | 80 | func testArrayLiteral() { 81 | let _: LCArray = ["a"] 82 | let _: LCArray = ["a", 1] 83 | let _: LCArray = ["a", LCNumber(1)] 84 | let _: LCArray = [LCString("a"), 1] 85 | let _: LCArray = [LCString("a"), LCNumber(1)] 86 | } 87 | 88 | func testDictionaryConvertible() { 89 | let date = Date() 90 | let object = LCObject() 91 | 92 | XCTAssertEqual( 93 | LCDictionary(["foo": "bar", "true": true, "dict": ["null": NSNull()], "date": date, "object": object]), 94 | try LCDictionary(unsafeObject: ["foo": "bar", "true": true, "dict": ["null": NSNull()], "date": date, "object": object] as [String : Any])) 95 | 96 | let dic = LCDictionary() 97 | dic["1"] = "a" 98 | dic["2"] = 42 99 | dic["3"] = true 100 | XCTAssertEqual(dic["1"]?.stringValue, "a") 101 | XCTAssertEqual(dic["2"]?.intValue, 42) 102 | XCTAssertEqual(dic["3"]?.boolValue, true) 103 | } 104 | 105 | func testDataConvertible() { 106 | let data = Data() 107 | XCTAssertEqual(convert(data) as? LCData, LCData(data)) 108 | XCTAssertTrue(LCData(LCData()).value.isEmpty) 109 | } 110 | 111 | func testDateConvertible() { 112 | let date = Date() 113 | XCTAssertEqual(convert(date) as? LCDate, LCDate(date)) 114 | XCTAssertEqual(LCDate(date), LCDate(LCDate(date))) 115 | } 116 | 117 | func testGeoPoint() { 118 | XCTAssertEqual(LCGeoPoint(), LCGeoPoint(LCGeoPoint())) 119 | } 120 | 121 | func archiveThenUnarchive(_ object: T) -> T { 122 | return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: object)) as! T 123 | } 124 | 125 | func testCoding() { 126 | let acl = LCACL() 127 | acl.setAccess(.write, allowed: true) 128 | let aclCopy = archiveThenUnarchive(acl) 129 | XCTAssertTrue(aclCopy.getAccess(.write)) 130 | 131 | let array = LCArray([true, 42, "foo"]) 132 | let arrayCopy = archiveThenUnarchive(array) 133 | XCTAssertEqual(arrayCopy, array) 134 | 135 | let bool = LCBool(true) 136 | let boolCopy = archiveThenUnarchive(bool) 137 | XCTAssertEqual(boolCopy, bool) 138 | 139 | let data = LCData(base64EncodedString: "Zm9v")! 140 | let dataCopy = archiveThenUnarchive(data) 141 | XCTAssertEqual(dataCopy, data) 142 | 143 | let date = LCDate() 144 | let dateCopy = archiveThenUnarchive(date) 145 | XCTAssertEqual(dateCopy, date) 146 | 147 | let dictionary = LCDictionary(["foo": "bar", "baz": 42]) 148 | let dictionaryCopy = archiveThenUnarchive(dictionary) 149 | XCTAssertEqual(dictionaryCopy, dictionary) 150 | 151 | let geoPoint = LCGeoPoint(latitude: 12, longitude: 34) 152 | let geoPointCopy = archiveThenUnarchive(geoPoint) 153 | XCTAssertEqual(geoPointCopy, geoPoint) 154 | 155 | let null = LCNull() 156 | let nullCopy = archiveThenUnarchive(null) 157 | XCTAssertEqual(nullCopy, null) 158 | 159 | let number = LCNumber(42) 160 | let numberCopy = archiveThenUnarchive(number) 161 | XCTAssertEqual(numberCopy, number) 162 | 163 | let object = LCObject(objectId: "1234567890") 164 | let friend = LCObject(objectId: "0987654321") 165 | try! object.insertRelation("friend", object: friend) 166 | let objectCopy = archiveThenUnarchive(object) 167 | XCTAssertEqual(objectCopy, object) 168 | let relation = object.relationForKey("friend") 169 | let relationCopy = objectCopy.relationForKey("friend") 170 | XCTAssertEqual(relationCopy.value, relation.value) 171 | 172 | /* Test mutability after unarchiving. */ 173 | objectCopy["foo"] = "bar" as LCString 174 | 175 | let string = LCString("foo") 176 | let stringCopy = archiveThenUnarchive(string) 177 | XCTAssertEqual(stringCopy, string) 178 | } 179 | 180 | func testStringValue() { 181 | let nsNumber = NSNumber(1) 182 | let nsString: String = nsNumber.stringValue 183 | let lcString: String? = nsNumber.stringValue 184 | XCTAssertEqual(nsString, lcString) 185 | 186 | XCTAssertEqual(Int(1).stringValue, "1") 187 | XCTAssertEqual(UInt(1).stringValue, "1") 188 | XCTAssertEqual(Int8(1).stringValue, "1") 189 | XCTAssertEqual(UInt8(1).stringValue, "1") 190 | XCTAssertEqual(Int16(1).stringValue, "1") 191 | XCTAssertEqual(UInt16(1).stringValue, "1") 192 | XCTAssertEqual(Int32(1).stringValue, "1") 193 | XCTAssertEqual(UInt32(1).stringValue, "1") 194 | XCTAssertEqual(Int64(1).stringValue, "1") 195 | XCTAssertEqual(UInt64(1).stringValue, "1") 196 | XCTAssertEqual(Float(1).stringValue, "1.0") 197 | XCTAssertEqual(Double(1).stringValue, "1.0") 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /LeanCloudTests/LCUserTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCUserTestCase.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 7/4/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LCUserTestCase: BaseTestCase { 13 | 14 | func testLoginWithSessionToken() { 15 | let user = LCUser() 16 | user.username = self.uuid.lcString 17 | user.password = self.uuid.lcString 18 | XCTAssertTrue(user.signUp().isSuccess) 19 | 20 | let logedinUser = LCUser.logIn(sessionToken: user.sessionToken!.value).object 21 | XCTAssertEqual(logedinUser?.username, user.username) 22 | 23 | expecting { (exp) in 24 | LCUser.logIn(sessionToken: logedinUser!.sessionToken!.value) { (result) in 25 | XCTAssertTrue(Thread.isMainThread) 26 | XCTAssertTrue(result.isSuccess) 27 | XCTAssertNil(result.error) 28 | XCTAssertEqual(result.object?.username, user.username) 29 | exp.fulfill() 30 | } 31 | } 32 | } 33 | 34 | func testSignUpAndLogIn() { 35 | let user = LCUser() 36 | let application = user.application 37 | 38 | let username = "user\(uuid)" 39 | let password = "qwerty" 40 | 41 | user.username = LCString(username) 42 | user.password = LCString(password) 43 | 44 | XCTAssertTrue(user.signUp().isSuccess) 45 | XCTAssertTrue(LCUser.logIn(username: username, password: password).isSuccess) 46 | XCTAssertNotNil(application.currentUser) 47 | 48 | let email = "\(UUID().uuidString.replacingOccurrences(of: "-", with: ""))@qq.com" 49 | user.email = LCString(email) 50 | XCTAssertTrue(user.save().isSuccess) 51 | 52 | LCUser.logOut() 53 | 54 | XCTAssertTrue(LCUser.logIn(email: email, password: password).isSuccess) 55 | 56 | let current = application.currentUser! 57 | let sessionToken = current.sessionToken!.value 58 | let updatedAt = current.updatedAt! 59 | 60 | XCTAssertTrue(LCUser.logIn(sessionToken: sessionToken).isSuccess) 61 | 62 | let newPassword = "ytrewq" 63 | let result = current.updatePassword(oldPassword: password, newPassword: newPassword) 64 | 65 | XCTAssertTrue(result.isSuccess) 66 | XCTAssertNotEqual(current.sessionToken!.value, sessionToken) 67 | XCTAssertNotEqual(current.updatedAt, updatedAt) 68 | 69 | LCUser.logOut() 70 | } 71 | 72 | func testSignUpOrLogInByMobilePhoneNumberAndVerificationCode() { 73 | if LCApplication.default.id == BaseTestCase.cnApp.id { 74 | let mobilePhoneNumber = "+8618622223333" 75 | let verificationCode = "170402" 76 | let result = LCUser.signUpOrLogIn(mobilePhoneNumber: mobilePhoneNumber, verificationCode: verificationCode) 77 | XCTAssertTrue(result.isSuccess) 78 | XCTAssertNil(result.error) 79 | } 80 | } 81 | 82 | func testAuthDataLogin() { 83 | let user = LCUser() 84 | let authData: [String: Any] = [ 85 | "access_token": UUID().uuidString, 86 | "openid": UUID().uuidString 87 | ] 88 | XCTAssertTrue(user.logIn(authData: authData, platform: .weixin).isSuccess) 89 | XCTAssertNotNil(user.authData) 90 | XCTAssertTrue(user.application.currentUser === user) 91 | } 92 | 93 | func testAuthDataLoginWithUnionID() { 94 | let user = LCUser() 95 | let authData: [String: Any] = [ 96 | "access_token": UUID().uuidString, 97 | "openid": UUID().uuidString 98 | ] 99 | let unionID: String = UUID().uuidString 100 | XCTAssertTrue(user.logIn(authData: authData, platform: .custom(UUID().uuidString), unionID: unionID, unionIDPlatform: .weixin, options: [.mainAccount]).isSuccess) 101 | XCTAssertNotNil(user.authData) 102 | XCTAssertTrue(user.application.currentUser === user) 103 | } 104 | 105 | func testAuthDataLoginFailOnNotExist() { 106 | let user = LCUser() 107 | let authData: [String: Any] = [ 108 | "access_token": UUID().uuidString, 109 | "openid": UUID().uuidString 110 | ] 111 | XCTAssertTrue(user.logIn(authData: authData, platform: .weixin, options: [.failOnNotExist]).isFailure) 112 | } 113 | 114 | func testAuthDataAssociate() { 115 | let user = LCUser() 116 | user.username = UUID().uuidString.lcString 117 | user.password = UUID().uuidString.lcString 118 | XCTAssertTrue(user.signUp().isSuccess) 119 | 120 | let authData: [String: Any] = [ 121 | "access_token": UUID().uuidString, 122 | "openid": UUID().uuidString 123 | ] 124 | do { 125 | let result = try user.associate(authData: authData, platform: .weixin) 126 | XCTAssertTrue(result.isSuccess) 127 | XCTAssertNotNil(user.authData) 128 | } catch { 129 | XCTFail("\(error)") 130 | } 131 | } 132 | 133 | func testAuthDataDisassociate() { 134 | let user = LCUser() 135 | let authData: [String: Any] = [ 136 | "access_token": UUID().uuidString, 137 | "openid": UUID().uuidString 138 | ] 139 | XCTAssertTrue(user.logIn(authData: authData, platform: .weixin).isSuccess) 140 | XCTAssertNotNil(user.authData) 141 | XCTAssertTrue(user.application.currentUser === user) 142 | 143 | do { 144 | let result = try user.disassociate(authData: .weixin) 145 | XCTAssertTrue(result.isSuccess) 146 | XCTAssertTrue((user.authData ?? [:]).isEmpty) 147 | } catch { 148 | XCTFail("\(error)") 149 | } 150 | } 151 | 152 | func testCache() { 153 | let username = UUID().uuidString 154 | let password = UUID().uuidString 155 | let signUpUser = LCUser() 156 | signUpUser.username = username.lcString 157 | signUpUser.password = password.lcString 158 | XCTAssertTrue(signUpUser.signUp() 159 | .isSuccess) 160 | XCTAssertTrue(LCUser.logIn( 161 | username: username, 162 | password: password) 163 | .isSuccess) 164 | XCTAssertNotNil(LCApplication.default.currentUser?.sessionToken?.value) 165 | let sessionToken = LCApplication.default.currentUser?.sessionToken?.value 166 | LCApplication.default._currentUser = nil 167 | XCTAssertEqual( 168 | sessionToken, 169 | LCApplication.default.currentUser?.sessionToken?.value) 170 | LCUser.logOut() 171 | XCTAssertNil(LCApplication.default.currentUser) 172 | } 173 | 174 | func testSaveToLocalAfterSaved() { 175 | let username = UUID().uuidString 176 | let password = UUID().uuidString 177 | let signUpUser = LCUser() 178 | signUpUser.username = username.lcString 179 | signUpUser.password = password.lcString 180 | XCTAssertTrue(signUpUser.signUp() 181 | .isSuccess) 182 | XCTAssertTrue(LCUser.logIn( 183 | username: username, 184 | password: password) 185 | .isSuccess) 186 | let key = "customKey" 187 | let value = UUID().uuidString 188 | try? LCApplication.default.currentUser?.set(key, value: value) 189 | let result = LCApplication.default.currentUser?.save() 190 | XCTAssertNil(result?.error) 191 | LCApplication.default._currentUser = nil 192 | XCTAssertEqual(LCApplication.default.currentUser?[key]?.stringValue, value) 193 | LCUser.logOut() 194 | XCTAssertNil(LCApplication.default.currentUser) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /LeanCloudTests/LiveQueryTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LiveQueryTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by pzheng on 2020/05/13. 6 | // Copyright © 2020 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class LiveQueryTestCase: RTMBaseTestCase { 13 | 14 | func testUserLogin() { 15 | let appID = LCApplication.default.id! 16 | let appKey = LCApplication.default.key! 17 | let serverURL = LCApplication.default.serverURL! 18 | 19 | let application1 = try! LCApplication( 20 | id: appID, 21 | key: appKey, 22 | serverURL: serverURL) 23 | let application2 = try! LCApplication( 24 | id: appID, 25 | key: appKey, 26 | serverURL: serverURL) 27 | 28 | let username = uuid 29 | let password = uuid 30 | let user = LCUser(application: application1) 31 | user.username = username.lcString 32 | user.password = password.lcString 33 | let userSignUpResult = user.signUp() 34 | XCTAssertNil(userSignUpResult.error) 35 | 36 | let user1LogInResult = LCUser.logIn( 37 | application: application1, 38 | username: username, 39 | password: password) 40 | XCTAssertNil(user1LogInResult.error) 41 | 42 | if let objectId = user.objectId?.value, 43 | let user1 = user1LogInResult.object, 44 | let _ = user1.sessionToken, 45 | user1 === application1.currentUser { 46 | var liveQuery1: LiveQuery! 47 | var liveQuery2: LiveQuery! 48 | expecting( 49 | description: "user login", 50 | count: 4) 51 | { (exp) in 52 | liveQuery1 = try! LiveQuery( 53 | application: application1, 54 | query: { 55 | let query = LCQuery( 56 | application: application1, 57 | className: LCUser.objectClassName()) 58 | query.whereKey("objectId", .equalTo(objectId)) 59 | return query 60 | }()) 61 | { (_, event) in 62 | switch event { 63 | case .login(user: let user): 64 | XCTAssertEqual(user.objectId?.value, objectId) 65 | exp.fulfill() 66 | default: 67 | break 68 | } 69 | } 70 | 71 | liveQuery1.subscribe { (result) in 72 | XCTAssertTrue(Thread.isMainThread) 73 | XCTAssertTrue(result.isSuccess) 74 | XCTAssertNil(result.error) 75 | exp.fulfill() 76 | 77 | liveQuery2 = try! LiveQuery( 78 | application: application1, 79 | query: { 80 | let query = LCQuery( 81 | application: application1, 82 | className: LCUser.objectClassName()) 83 | query.whereKey("createdAt", .existed) 84 | return query 85 | }()) 86 | { (_, event) in 87 | switch event { 88 | case .login(user: let user): 89 | XCTAssertEqual(user.objectId?.value, objectId) 90 | exp.fulfill() 91 | default: 92 | break 93 | } 94 | } 95 | 96 | liveQuery2.subscribe { (result) in 97 | XCTAssertTrue(Thread.isMainThread) 98 | XCTAssertTrue(result.isSuccess) 99 | XCTAssertNil(result.error) 100 | exp.fulfill() 101 | 102 | let user2LogInResult = LCUser.logIn( 103 | application: application2, 104 | username: username, 105 | password: password) 106 | XCTAssertNil(user2LogInResult.error) 107 | } 108 | } 109 | } 110 | } else { 111 | XCTFail() 112 | } 113 | 114 | application1.unregister() 115 | application2.unregister() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /LeanCloudTests/RTMBaseTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RTMBaseTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by zapcannon87 on 2019/1/21. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class RTMBaseTestCase: BaseTestCase { 13 | static let testableRTMURL: URL? = nil 14 | // static let testableRTMURL: URL? = URL(string: "wss://cn-n1-prod-k8s-cell-12.leancloud.cn") 15 | } 16 | -------------------------------------------------------------------------------- /LeanCloudTests/RTMRouterTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RTMRouterTestCase.swift 3 | // LeanCloudTests 4 | // 5 | // Created by Tianyong Tang on 2018/11/6. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import LeanCloud 11 | 12 | class RTMRouterTestCase: RTMBaseTestCase { 13 | 14 | func test() { 15 | 16 | let router = try! RTMRouter(application: .default) 17 | let fileURL = router.tableCacheURL! 18 | 19 | XCTAssertTrue(fileURL.path.contains("Library/Caches")) 20 | XCTAssertTrue(fileURL.path.contains(LocalStorageContext.Module.router.path)) 21 | XCTAssertTrue(fileURL.path.contains(LocalStorageContext.File.rtmServer.name)) 22 | 23 | router.clearTableCache() 24 | 25 | XCTAssertNil(router.table) 26 | XCTAssertFalse(FileManager.default.fileExists(atPath: fileURL.path)) 27 | 28 | let date = Date() 29 | 30 | let exp = expectation(description: "routing") 31 | router.route { (direct, result) in 32 | XCTAssertTrue(result.isSuccess) 33 | XCTAssertNil(result.error) 34 | XCTAssertFalse(direct) 35 | exp.fulfill() 36 | } 37 | wait(for: [exp], timeout: timeout) 38 | 39 | XCTAssertNotNil(router.table) 40 | XCTAssertNotNil(router.table?.secondary) 41 | XCTAssertEqual(router.table?.primary, router.table?.primaryURL?.absoluteString) 42 | XCTAssertEqual(router.table?.secondary, router.table?.secondaryURL?.absoluteString) 43 | XCTAssertTrue((router.table?.ttl ?? 0) > 0) 44 | XCTAssertEqual(router.table?.continuousFailureCount, 0) 45 | XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path)) 46 | XCTAssertGreaterThan(router.table?.createdTimestamp ?? 0, date.timeIntervalSince1970) 47 | XCTAssertEqual(router.table?.isExpired, false) 48 | 49 | router.updateFailureCount() 50 | XCTAssertEqual(router.table?.continuousFailureCount, 1) 51 | router.updateFailureCount(reset: true) 52 | XCTAssertEqual(router.table?.continuousFailureCount, 0) 53 | for _ in 0..<10 { 54 | router.updateFailureCount() 55 | } 56 | XCTAssertEqual(router.table?.shouldClear, true) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /LeanCloudTests/Resources/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/swift-sdk/472784ce0d5ea74f0dbbc275a0a7552d500b55ae/LeanCloudTests/Resources/test.jpg -------------------------------------------------------------------------------- /LeanCloudTests/Resources/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/swift-sdk/472784ce0d5ea74f0dbbc275a0a7552d500b55ae/LeanCloudTests/Resources/test.mp3 -------------------------------------------------------------------------------- /LeanCloudTests/Resources/test.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/swift-sdk/472784ce0d5ea74f0dbbc275a0a7552d500b55ae/LeanCloudTests/Resources/test.mp4 -------------------------------------------------------------------------------- /LeanCloudTests/Resources/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/swift-sdk/472784ce0d5ea74f0dbbc275a0a7552d500b55ae/LeanCloudTests/Resources/test.png -------------------------------------------------------------------------------- /LeanCloudTests/Resources/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancloud/swift-sdk/472784ce0d5ea74f0dbbc275a0a7552d500b55ae/LeanCloudTests/Resources/test.zip -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 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: "LeanCloud", 8 | platforms: [ 9 | .iOS(.v11), 10 | .macOS(.v10_13), 11 | .tvOS(.v11), 12 | .watchOS(.v4) 13 | ], 14 | products: [ 15 | .library(name: "LeanCloud", targets: ["LeanCloud"]), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.7.0")), 19 | .package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.22.0")), 20 | .package(url: "https://github.com/groue/GRDB.swift.git", .upToNextMajor(from: "6.15.0")) 21 | ], 22 | targets: [ 23 | .target( 24 | name: "LeanCloud", 25 | dependencies: [ 26 | "Alamofire", 27 | "SwiftProtobuf", 28 | "GRDB" 29 | ], 30 | path: "Sources" 31 | ) 32 | ], 33 | swiftLanguageVersions: [.v5] 34 | ) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeanCloud Swift SDK 2 | 3 | ![Build Status](https://github.com/leancloud/swift-sdk/workflows/Release%20Drafter/badge.svg) 4 | 5 | ## Features 6 | * [x] Data Storage 7 | * [x] Object Query 8 | * [x] Cloud Engine 9 | * [x] Short Message 10 | * [x] File Hosting 11 | * [x] Push Notification 12 | * [x] Instant Messaging 13 | 14 | ## Communication 15 | * If you **have some advice**, open an issue. 16 | * If you **found a bug**, open an issue, or open a ticket in [LeanTicket][LeanTicket]. 17 | * If you **want to contribute**, submit a pull request. 18 | 19 | [LeanTicket]: https://leanticket.cn/ 20 | 21 | ## Installation & Quick Start 22 | 23 | Please read [this documentation][doc]. 24 | 25 | [doc]: https://docs.leancloud.app/sdk_setup-swift.html 26 | -------------------------------------------------------------------------------- /RuntimeTests-iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RuntimeTests-iOS 4 | // 5 | // Created by zapcannon87 on 2018/11/8. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import LeanCloud 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | 19 | do { 20 | LCApplication.logLevel = .all 21 | try LCApplication.default.set( 22 | id: "S5vDI3IeCk1NLLiM1aFg3262-gzGzoHsz", 23 | key: "7g5pPsI55piz2PRLPWK5MPz0", 24 | serverURL: "https://s5vdi3ie.lc-cn-n1-shared.com") 25 | } catch { 26 | fatalError("\(error)") 27 | } 28 | 29 | return true 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /RuntimeTests-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /RuntimeTests-iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RuntimeTests-iOS/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 | -------------------------------------------------------------------------------- /RuntimeTests-iOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /RuntimeTests-iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /RuntimeTests-iOS/RuntimeTests-iOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Foundation/ACL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCACL.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 5/4/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud access control lists type. 13 | 14 | You can use it to set access control lists on an object. 15 | */ 16 | public final class LCACL: NSObject, LCValue, LCValueExtension { 17 | typealias Access = [String: Bool] 18 | typealias AccessTable = [String: Access] 19 | 20 | var value: AccessTable = [:] 21 | 22 | /// The key for public, aka, all users. 23 | static let publicAccessKey = "*" 24 | 25 | /// The key for `read` permission. 26 | static let readPermissionKey = "read" 27 | 28 | /// The key for `write` permission. 29 | static let writePermissionKey = "write" 30 | 31 | public override init() { 32 | super.init() 33 | } 34 | 35 | init?(jsonValue: Any?) { 36 | guard let value = jsonValue as? AccessTable else { 37 | return nil 38 | } 39 | 40 | self.value = value 41 | } 42 | 43 | public required init?(coder aDecoder: NSCoder) { 44 | value = (aDecoder.decodeObject(forKey: "value") as? AccessTable) ?? [:] 45 | } 46 | 47 | public func encode(with aCoder: NSCoder) { 48 | aCoder.encode(value, forKey: "value") 49 | } 50 | 51 | public func copy(with zone: NSZone?) -> Any { 52 | let copy = LCACL() 53 | 54 | copy.value = value 55 | 56 | return copy 57 | } 58 | 59 | public override func isEqual(_ object: Any?) -> Bool { 60 | if let object = object as? LCACL { 61 | return object === self || object.value == value 62 | } else { 63 | return false 64 | } 65 | } 66 | 67 | public var jsonValue: Any { 68 | return value 69 | } 70 | 71 | public var rawValue: Any { 72 | return self 73 | } 74 | 75 | var lconValue: Any? { 76 | return jsonValue 77 | } 78 | 79 | static func instance(application: LCApplication) -> LCValue { 80 | return self.init() 81 | } 82 | 83 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 84 | /* Nothing to do. */ 85 | } 86 | 87 | func add(_ other: LCValue) throws -> LCValue { 88 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 89 | } 90 | 91 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 92 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 93 | } 94 | 95 | func differ(_ other: LCValue) throws -> LCValue { 96 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 97 | } 98 | 99 | /** 100 | Permission type. 101 | */ 102 | public struct Permission: OptionSet { 103 | public let rawValue: UInt 104 | 105 | public init(rawValue: UInt) { 106 | self.rawValue = rawValue 107 | } 108 | 109 | public static let read = Permission(rawValue: 1 << 0) 110 | public static let write = Permission(rawValue: 1 << 1) 111 | } 112 | 113 | /** 114 | Generate access key for role name. 115 | 116 | - parameter roleName: The name of role. 117 | 118 | - returns: An access key for role name. 119 | */ 120 | static func accessKey(roleName: String) -> String { 121 | return "role:\(roleName)" 122 | } 123 | 124 | /** 125 | Get access permission for public. 126 | 127 | - parameter permission: The permission that you want to get. 128 | 129 | - returns: true if the permission is allowed, false otherwise. 130 | */ 131 | public func getAccess(_ permission: Permission) -> Bool { 132 | return getAccess(permission, key: LCACL.publicAccessKey) 133 | } 134 | 135 | /** 136 | Set access permission for public. 137 | 138 | - parameter permission: The permission to be set. 139 | - parameter allowed: A boolean value indicates whether permission is allowed or not. 140 | */ 141 | public func setAccess(_ permission: Permission, allowed: Bool) { 142 | setAccess(permission, key: LCACL.publicAccessKey, allowed: allowed) 143 | } 144 | 145 | /** 146 | Get access permission for user. 147 | 148 | - parameter permission: The permission that you want to get. 149 | - parameter userID: The user object ID for which you want to get. 150 | 151 | - returns: true if the permission is allowed, false otherwise. 152 | */ 153 | public func getAccess(_ permission: Permission, forUserID userID: String) -> Bool { 154 | return getAccess(permission, key: userID) 155 | } 156 | 157 | /** 158 | Set access permission for user. 159 | 160 | - parameter permission: The permission to be set. 161 | - parameter allowed: A boolean value indicates whether permission is allowed or not. 162 | - parameter userID: The user object ID for which the permission will be set. 163 | */ 164 | public func setAccess(_ permission: Permission, allowed: Bool, forUserID userID: String) { 165 | setAccess(permission, key: userID, allowed: allowed) 166 | } 167 | 168 | /** 169 | Get access permission for role. 170 | 171 | - parameter permission: The permission that you want to get. 172 | - parameter roleName: The role name for which you want to get. 173 | 174 | - returns: true if the permission is allowed, false otherwise. 175 | */ 176 | public func getAccess(_ permission: Permission, forRoleName roleName: String) -> Bool { 177 | return getAccess(permission, key: LCACL.accessKey(roleName: roleName)) 178 | } 179 | 180 | /** 181 | Set access permission for role. 182 | 183 | - parameter permission: The permission to be set. 184 | - parameter allowed: A boolean value indicates whether permission is allowed or not. 185 | - parameter roleName: The role name for which the permission will be set. 186 | */ 187 | public func setAccess(_ permission: Permission, allowed: Bool, forRoleName roleName: String) { 188 | setAccess(permission, key: LCACL.accessKey(roleName: roleName), allowed: allowed) 189 | } 190 | 191 | /** 192 | Get access for key. 193 | 194 | - parameter permission: The permission that you want to get. 195 | - parameter key: The key for which you want to get. 196 | 197 | - returns: true if all permission is allowed, false otherwise. 198 | */ 199 | func getAccess(_ permission: Permission, key: String) -> Bool { 200 | guard let access = value[key] else { 201 | return false 202 | } 203 | 204 | /* We use AND logic here. If any one of permissions is disallowed, return false. */ 205 | if permission.contains(.read) { 206 | if access[LCACL.readPermissionKey] == nil { 207 | return false 208 | } 209 | } 210 | if permission.contains(.write) { 211 | if access[LCACL.writePermissionKey] == nil { 212 | return false 213 | } 214 | } 215 | 216 | return true 217 | } 218 | 219 | /** 220 | Update permission for given key. 221 | 222 | - parameter permission: The permission. 223 | - parameter key: The key for which the permission to be updated. 224 | - parameter allowed: A boolean value indicates whether permission is allowed or not. 225 | */ 226 | func setAccess(_ permission: Permission, key: String, allowed: Bool) { 227 | var access = value[key] ?? [:] 228 | 229 | /* We reserve the allowed permissions only. */ 230 | if permission.contains(.read) { 231 | access[LCACL.readPermissionKey] = allowed ? allowed : nil 232 | } 233 | if permission.contains(.write) { 234 | access[LCACL.writePermissionKey] = allowed ? allowed : nil 235 | } 236 | 237 | value[key] = !access.isEmpty ? access : nil 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Sources/Foundation/Array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCArray.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/27/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LeanCloud List Type 12 | public class LCArray: NSObject, LCValue, Collection, ExpressibleByArrayLiteral { 13 | public typealias Index = Int 14 | public typealias Element = LCValue 15 | 16 | public private(set) var value: [Element] = [] 17 | 18 | public override init() { 19 | super.init() 20 | } 21 | 22 | public convenience init(_ value: [Element]) { 23 | self.init() 24 | self.value = value 25 | } 26 | 27 | public convenience init(_ value: [LCValueConvertible]) { 28 | self.init() 29 | self.value = value.map { $0.lcValue } 30 | } 31 | 32 | public convenience init(_ array: LCArray) { 33 | self.init() 34 | self.value = array.value 35 | } 36 | 37 | public convenience required init(arrayLiteral elements: LCValueConvertible...) { 38 | self.init(elements) 39 | } 40 | 41 | public convenience init( 42 | application: LCApplication = .default, 43 | unsafeObject: Any) 44 | throws 45 | { 46 | self.init() 47 | guard let object = unsafeObject as? [Any] else { 48 | throw LCError( 49 | code: .malformedData, 50 | reason: "Failed to construct \(LCArray.self) with a \(type(of: unsafeObject)) object.") 51 | } 52 | self.value = try object.map { element in 53 | try ObjectProfiler.shared.object(application: application, jsonValue: element) 54 | } 55 | } 56 | 57 | public required init?(coder aDecoder: NSCoder) { 58 | self.value = (aDecoder.decodeObject(forKey: "value") as? [Element]) ?? [] 59 | } 60 | 61 | public func encode(with aCoder: NSCoder) { 62 | aCoder.encode(self.value, forKey: "value") 63 | } 64 | 65 | public func copy(with zone: NSZone?) -> Any { 66 | return LCArray(self) 67 | } 68 | 69 | public override func isEqual(_ object: Any?) -> Bool { 70 | if let object = object as? LCArray { 71 | return object === self || object.value == self.value 72 | } else { 73 | return false 74 | } 75 | } 76 | 77 | public func makeIterator() -> IndexingIterator<[Element]> { 78 | return self.value.makeIterator() 79 | } 80 | 81 | public var startIndex: Int { 82 | return 0 83 | } 84 | 85 | public var endIndex: Int { 86 | return self.value.count 87 | } 88 | 89 | public func index(after i: Int) -> Int { 90 | return self.value.index(after: i) 91 | } 92 | 93 | public subscript(index: Int) -> LCValue { 94 | get { 95 | return self.value[index] 96 | } 97 | } 98 | 99 | public var jsonValue: Any { 100 | return self.value.map { $0.jsonValue } 101 | } 102 | 103 | public var rawValue: Any { 104 | return self.value.map { $0.rawValue } 105 | } 106 | } 107 | 108 | extension LCArray: LCValueExtension { 109 | 110 | var lconValue: Any? { 111 | return self.value.compactMap { ($0 as? LCValueExtension)?.lconValue } 112 | } 113 | 114 | static func instance(application: LCApplication) -> LCValue { 115 | return self.init() 116 | } 117 | 118 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 119 | try forEach { try body($0) } 120 | } 121 | 122 | func add(_ other: LCValue) throws -> LCValue { 123 | throw LCError( 124 | code: .invalidType, 125 | reason: "\(LCArray.self) cannot do `add(_:)`.") 126 | } 127 | 128 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 129 | guard let elements = (other as? LCArray)?.value else { 130 | throw LCError( 131 | code: .invalidType, 132 | reason: "\(LCArray.self) cannot do `concatenate(_:unique:)` with a \(type(of: other)) object.") 133 | } 134 | let result = LCArray(self.value) 135 | result.concatenateInPlace(elements, unique: unique) 136 | return result 137 | } 138 | 139 | func concatenateInPlace(_ elements: [Element], unique: Bool) { 140 | self.value = unique 141 | ? (self.value +~ elements) 142 | : (self.value + elements) 143 | } 144 | 145 | func differ(_ other: LCValue) throws -> LCValue { 146 | guard let elements = (other as? LCArray)?.value else { 147 | throw LCError( 148 | code: .invalidType, 149 | reason: "\(LCArray.self) cannot do `differ(_:)` with a \(type(of: other)) object.") 150 | } 151 | let result = LCArray(self.value) 152 | result.differInPlace(elements) 153 | return result 154 | } 155 | 156 | func differInPlace(_ elements: [Element]) { 157 | self.value = (self.value - elements) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Sources/Foundation/BatchRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatchRequest.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 3/22/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class BatchRequest { 12 | let object: LCObject 13 | let method: HTTPClient.Method? 14 | let operationTable: OperationTable? 15 | let parameters: [String: Any]? 16 | 17 | init( 18 | object: LCObject, 19 | method: HTTPClient.Method? = nil, 20 | operationTable: OperationTable? = nil, 21 | parameters: [String: Any]? = nil) 22 | { 23 | self.object = object 24 | self.method = method 25 | self.operationTable = operationTable 26 | self.parameters = parameters 27 | } 28 | 29 | var isNewborn: Bool { 30 | return !object.hasObjectId 31 | } 32 | 33 | var actualMethod: HTTPClient.Method { 34 | return method ?? (isNewborn ? .post : .put) 35 | } 36 | 37 | func getBody(internalId: String) -> [String: Any] { 38 | var body: [String: Any] = ["__internalId": internalId] 39 | var children: [(String, LCObject)] = [] 40 | self.operationTable?.forEach { (key, operation) in 41 | if case .set = operation.name, 42 | let child = operation.value as? LCObject, 43 | !child.hasObjectId { 44 | children.append((key, child)) 45 | } else { 46 | body[key] = operation.lconValue 47 | } 48 | } 49 | if !children.isEmpty { 50 | body["__children"] = children.map { (key, child) -> [String: String] in 51 | ["className": child.actualClassName, 52 | "cid": child.internalId, 53 | "key": key] 54 | } 55 | } 56 | return body 57 | } 58 | 59 | func jsonValue() throws -> Any { 60 | let method = actualMethod 61 | let path = try object.application.httpClient.getBatchRequestPath(object: object, method: method) 62 | let internalId = object.objectId?.value ?? object.internalId 63 | 64 | if let request = try object.preferredBatchRequest(method: method, path: path, internalId: internalId) { 65 | return request 66 | } 67 | 68 | var request: [String: Any] = [ 69 | "path": path, 70 | "method": method.rawValue 71 | ] 72 | if let params: [String: Any] = self.parameters { 73 | request["params"] = params 74 | } 75 | 76 | switch method { 77 | case .get: 78 | break 79 | case .post, .put: 80 | request["body"] = getBody(internalId: internalId) 81 | 82 | if isNewborn { 83 | request["new"] = true 84 | } 85 | case .delete: 86 | break 87 | } 88 | 89 | return request 90 | } 91 | } 92 | 93 | class BatchRequestBuilder { 94 | /** 95 | Get a list of requests of an object. 96 | 97 | - parameter object: The object from which you want to get. 98 | 99 | - returns: A list of request. 100 | */ 101 | static func buildRequests(_ object: LCObject, parameters: [String: Any]?) throws -> [BatchRequest] { 102 | return try operationTableList(object).map { element in 103 | BatchRequest(object: object, operationTable: element, parameters: parameters) 104 | } 105 | } 106 | 107 | /** 108 | Get initial operation table list of an object. 109 | 110 | - parameter object: The object from which to get. 111 | 112 | - returns: The operation table list. 113 | */ 114 | private static func initialOperationTableList(_ object: LCObject) throws -> OperationTableList { 115 | var operationTable: OperationTable = [:] 116 | 117 | /* Collect all non-null properties. */ 118 | try object.forEach { (key, value) in 119 | switch value { 120 | case let relation as LCRelation: 121 | /* If the property type is relation, 122 | We should use "AddRelation" instead of "Set" as operation type. 123 | Otherwise, the relations will added as an array. */ 124 | operationTable[key] = try Operation(name: .addRelation, key: key, value: LCArray(relation.value)) 125 | default: 126 | operationTable[key] = try Operation(name: .set, key: key, value: value) 127 | } 128 | } 129 | 130 | return [operationTable] 131 | } 132 | 133 | /** 134 | Get operation table list of object. 135 | 136 | - parameter object: The object from which you want to get. 137 | 138 | - returns: A list of operation tables. 139 | */ 140 | private static func operationTableList(_ object: LCObject) throws -> OperationTableList { 141 | if object.hasObjectId, 142 | let operationTableList = object.optionalSync(object.operationHub?.operationTableList()) { 143 | return operationTableList 144 | } else { 145 | return try initialOperationTableList(object) 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/Foundation/Bool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCBool.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/27/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud boolean type. 13 | 14 | It is a wrapper of `Swift.Bool` type, used to store a boolean value. 15 | */ 16 | public final class LCBool: NSObject, LCValue, LCValueExtension, ExpressibleByBooleanLiteral { 17 | public private(set) var value: Bool = false 18 | 19 | public override init() { 20 | super.init() 21 | } 22 | 23 | public convenience init(_ value: Bool) { 24 | self.init() 25 | self.value = value 26 | } 27 | 28 | public convenience init(_ bool: LCBool) { 29 | self.init() 30 | self.value = bool.value 31 | } 32 | 33 | public convenience required init(booleanLiteral value: BooleanLiteralType) { 34 | self.init(value) 35 | } 36 | 37 | public required init?(coder aDecoder: NSCoder) { 38 | value = aDecoder.decodeBool(forKey: "value") 39 | } 40 | 41 | public func encode(with aCoder: NSCoder) { 42 | aCoder.encode(value, forKey: "value") 43 | } 44 | 45 | public func copy(with zone: NSZone?) -> Any { 46 | return LCBool(self) 47 | } 48 | 49 | public override func isEqual(_ object: Any?) -> Bool { 50 | if let object = object as? LCBool { 51 | return object === self || object.value == value 52 | } else { 53 | return false 54 | } 55 | } 56 | 57 | public var jsonValue: Any { 58 | return value 59 | } 60 | 61 | public var rawValue: Any { 62 | return self.value 63 | } 64 | 65 | var lconValue: Any? { 66 | return jsonValue 67 | } 68 | 69 | static func instance(application: LCApplication) -> LCValue { 70 | return LCBool() 71 | } 72 | 73 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 74 | /* Nothing to do. */ 75 | } 76 | 77 | func add(_ other: LCValue) throws -> LCValue { 78 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 79 | } 80 | 81 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 82 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 83 | } 84 | 85 | func differ(_ other: LCValue) throws -> LCValue { 86 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Foundation/CQLClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CQLClient.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 5/30/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A type represents the result value of CQL execution. 13 | */ 14 | public final class LCCQLValue { 15 | let response: LCResponse 16 | 17 | init(response: LCResponse) { 18 | self.response = response 19 | } 20 | 21 | var results: [[String: Any]] { 22 | return (response.results as? [[String: Any]]) ?? [] 23 | } 24 | 25 | var className: String { 26 | return response["className"] ?? LCObject.objectClassName() 27 | } 28 | 29 | /** 30 | Get objects for object query. 31 | */ 32 | public var objects: [LCObject] { 33 | let results = self.results 34 | let className = self.className 35 | 36 | do { 37 | let objects = try results.map { dictionary in 38 | try ObjectProfiler.shared.object( 39 | application: self.response.application, 40 | dictionary: dictionary, 41 | className: className 42 | ) 43 | } 44 | 45 | return objects 46 | } catch { 47 | return [] 48 | } 49 | } 50 | 51 | /** 52 | Get count value for count query. 53 | */ 54 | public var count: Int { 55 | return response.count 56 | } 57 | } 58 | 59 | /** 60 | CQL client. 61 | 62 | CQLClient allow you to use CQL (Cloud Query Language) to make CRUD for object. 63 | */ 64 | public final class LCCQLClient { 65 | static let endpoint = "cloudQuery" 66 | 67 | /** 68 | Assemble parameters for CQL execution. 69 | 70 | - parameter cql: The CQL statement. 71 | - parameter parameters: The parameters for placeholders in CQL statement. 72 | 73 | - returns: The parameters for CQL execution. 74 | */ 75 | static func parameters(_ cql: String, parameters: LCArrayConvertible?) -> [String: Any] { 76 | var result = ["cql": cql] 77 | do { 78 | if let parameters = parameters?.lcArray, 79 | !parameters.isEmpty, 80 | let lconValue = parameters.lconValue, 81 | let jsonString = try Utility.jsonString(lconValue) { 82 | result["pvalues"] = jsonString 83 | } 84 | } catch { 85 | Logger.shared.error(error) 86 | } 87 | return result 88 | } 89 | 90 | /** 91 | Execute CQL statement synchronously. 92 | 93 | - parameter cql: The CQL statement to be executed. 94 | - parameter parameters: The parameters for placeholders in CQL statement. 95 | 96 | - returns: The result of CQL statement. 97 | */ 98 | public static func execute( 99 | application: LCApplication = LCApplication.default, 100 | _ cql: String, 101 | parameters: LCArrayConvertible? = nil) 102 | -> LCCQLResult 103 | { 104 | return expect { fulfill in 105 | execute(application: application, cql, parameters: parameters, completionInBackground: { result in 106 | fulfill(result) 107 | }) 108 | } 109 | } 110 | 111 | /** 112 | Execute CQL statement asynchronously. 113 | 114 | - parameter cql: The CQL statement to be executed. 115 | - parameter parameters: The parameters for placeholders in CQL statement. 116 | - parameter completion: The completion callback closure. 117 | */ 118 | @discardableResult 119 | public static func execute( 120 | application: LCApplication = .default, 121 | _ cql: String, 122 | parameters: LCArrayConvertible? = nil, 123 | completionQueue: DispatchQueue = .main, 124 | completion: @escaping (_ result: LCCQLResult) -> Void) 125 | -> LCRequest 126 | { 127 | return execute(application: application, cql, parameters: parameters, completionInBackground: { result in 128 | completionQueue.async { 129 | completion(result) 130 | } 131 | }) 132 | } 133 | 134 | @discardableResult 135 | private static func execute( 136 | application: LCApplication, 137 | _ cql: String, 138 | parameters: LCArrayConvertible? = nil, 139 | completionInBackground completion: @escaping (LCCQLResult) -> Void) 140 | -> LCRequest 141 | { 142 | let parameters = self.parameters(cql, parameters: parameters) 143 | let request = application.httpClient.request(.get, endpoint, parameters: parameters) { response in 144 | let result = LCCQLResult(response: response) 145 | completion(result) 146 | } 147 | 148 | return request 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Sources/Foundation/CaptchaClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CaptchaClient.swift 3 | // LeanCloud 4 | // 5 | // Created by zapcannon87 on 2019/5/4. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class LCCaptchaClient { 12 | 13 | /// Captcha 14 | public struct Captcha: Codable { 15 | public let token: String? 16 | public let url: String? 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case token = "captcha_token" 20 | case url = "captcha_url" 21 | } 22 | } 23 | 24 | /// Captcha Verification 25 | public struct Verification: Codable { 26 | public let token: String? 27 | 28 | enum CodingKeys: String, CodingKey { 29 | case token = "validate_token" 30 | } 31 | } 32 | 33 | /// Request a Captcha synchronously. 34 | /// 35 | /// - Parameters: 36 | /// - application: The application. 37 | /// - width: The width of the image. 38 | /// - height: The height of the image. 39 | /// - Returns: Result. 40 | public static func requestCaptcha( 41 | application: LCApplication = LCApplication.default, 42 | width: Double? = nil, 43 | height: Double? = nil) 44 | -> LCGenericResult 45 | { 46 | return expect { (fulfill) in 47 | self.requestCaptcha(application: application, width: width, height: height, completionInBackground: { (result) in 48 | fulfill(result) 49 | }) 50 | } 51 | } 52 | 53 | /// Request a Captcha asynchronously. 54 | /// 55 | /// - Parameters: 56 | /// - application: The application. 57 | /// - width: The width of the image. 58 | /// - height: The height of the image. 59 | /// - completion: The callback of the result. 60 | /// - Returns: Request. 61 | @discardableResult 62 | public static func requestCaptcha( 63 | application: LCApplication = .default, 64 | width: Double? = nil, 65 | height: Double? = nil, 66 | completionQueue: DispatchQueue = .main, 67 | completion: @escaping (LCGenericResult) -> Void) 68 | -> LCRequest 69 | { 70 | return self.requestCaptcha(application: application, width: width, height: height, completionInBackground: { (result) in 71 | completionQueue.async { 72 | completion(result) 73 | } 74 | }) 75 | } 76 | 77 | @discardableResult 78 | private static func requestCaptcha( 79 | application: LCApplication, 80 | width: Double?, 81 | height: Double?, 82 | completionInBackground completion: @escaping (LCGenericResult) -> Void) 83 | -> LCRequest 84 | { 85 | var parameters: [String: Any] = [:] 86 | if let width = width { 87 | parameters["width"] = width 88 | } 89 | if let height = height { 90 | parameters["height"] = height 91 | } 92 | 93 | let request = application.httpClient.request(.get, "requestCaptcha", parameters: (parameters.isEmpty ? nil : parameters)) { response in 94 | if let error = LCError(response: response) { 95 | completion(.failure(error: error)) 96 | } else { 97 | guard let data: Data = response.data else { 98 | completion(.failure(error: LCError(code: .notFound, reason: "Response data not found."))) 99 | return 100 | } 101 | do { 102 | let captcha: Captcha = try JSONDecoder().decode(Captcha.self, from: data) 103 | completion(.success(value: captcha)) 104 | } catch { 105 | completion(.failure(error: LCError(error: error))) 106 | } 107 | } 108 | } 109 | 110 | return request 111 | } 112 | 113 | /// Verify a Captcha synchronously. 114 | /// 115 | /// - Parameters: 116 | /// - application: The application. 117 | /// - code: The code of the captcha. 118 | /// - captchaToken: The token of the captcha. 119 | /// - Returns: Result. 120 | public static func verifyCaptcha( 121 | application: LCApplication = LCApplication.default, 122 | code: String, 123 | captchaToken: String) 124 | -> LCGenericResult 125 | { 126 | return expect { (fulfill) in 127 | self.verifyCaptcha(application: application, code: code, captchaToken: captchaToken, completionInBackground: { (result) in 128 | fulfill(result) 129 | }) 130 | } 131 | } 132 | 133 | /// Verify a Captcha asynchronously. 134 | /// 135 | /// - Parameters: 136 | /// - application: The application. 137 | /// - code: The code of the captcha. 138 | /// - captchaToken: The token of the captcha. 139 | /// - completion: The callback of the result. 140 | /// - Returns: Request. 141 | @discardableResult 142 | public static func verifyCaptcha( 143 | application: LCApplication = .default, 144 | code: String, 145 | captchaToken: String, 146 | completionQueue: DispatchQueue = .main, 147 | completion: @escaping (LCGenericResult) -> Void) 148 | -> LCRequest 149 | { 150 | return self.verifyCaptcha(application: application, code: code, captchaToken: captchaToken, completionInBackground: { (result) in 151 | completionQueue.async { 152 | completion(result) 153 | } 154 | }) 155 | } 156 | 157 | @discardableResult 158 | private static func verifyCaptcha( 159 | application: LCApplication, 160 | code: String, 161 | captchaToken: String, 162 | completionInBackground completion: @escaping (LCGenericResult) -> Void) 163 | -> LCRequest 164 | { 165 | let parameters: [String: Any] = [ 166 | "captcha_code": code, 167 | "captcha_token": captchaToken 168 | ] 169 | 170 | let request = application.httpClient.request(.post, "verifyCaptcha", parameters: parameters) { response in 171 | if let error = LCError(response: response) { 172 | completion(.failure(error: error)) 173 | } else { 174 | guard let data: Data = response.data else { 175 | completion(.failure(error: LCError(code: .notFound, reason: "Response data not found."))) 176 | return 177 | } 178 | do { 179 | let verification: Verification = try JSONDecoder().decode(Verification.self, from: data) 180 | completion(.success(value: verification)) 181 | } catch { 182 | completion(.failure(error: LCError(error: error))) 183 | } 184 | } 185 | } 186 | 187 | return request 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /Sources/Foundation/Data.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCData.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 4/1/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud data type. 13 | 14 | This type can be used to represent a byte buffers. 15 | */ 16 | public final class LCData: NSObject, LCValue, LCValueExtension { 17 | public private(set) var value: Data = Data() 18 | 19 | var base64EncodedString: String { 20 | return value.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) 21 | } 22 | 23 | static func dataFromString(_ string: String) -> Data? { 24 | return Data(base64Encoded: string, options: NSData.Base64DecodingOptions(rawValue: 0)) 25 | } 26 | 27 | public override init() { 28 | super.init() 29 | } 30 | 31 | public convenience init(_ value: Data) { 32 | self.init() 33 | self.value = value 34 | } 35 | 36 | public convenience init(_ data: LCData) { 37 | self.init() 38 | self.value = data.value 39 | } 40 | 41 | init?(base64EncodedString: String) { 42 | guard let data = LCData.dataFromString(base64EncodedString) else { 43 | return nil 44 | } 45 | 46 | value = data 47 | } 48 | 49 | init?(dictionary: [String: Any]) { 50 | guard let type = dictionary["__type"] as? String else { 51 | return nil 52 | } 53 | guard let dataType = HTTPClient.DataType(rawValue: type) else { 54 | return nil 55 | } 56 | guard case dataType = HTTPClient.DataType.bytes else { 57 | return nil 58 | } 59 | guard let base64EncodedString = dictionary["base64"] as? String else { 60 | return nil 61 | } 62 | guard let data = LCData.dataFromString(base64EncodedString) else { 63 | return nil 64 | } 65 | 66 | value = data 67 | } 68 | 69 | public required init?(coder aDecoder: NSCoder) { 70 | value = (aDecoder.decodeObject(forKey: "value") as? Data) ?? Data() 71 | } 72 | 73 | public func encode(with aCoder: NSCoder) { 74 | aCoder.encode(value, forKey: "value") 75 | } 76 | 77 | public func copy(with zone: NSZone?) -> Any { 78 | return LCData(self) 79 | } 80 | 81 | public override func isEqual(_ object: Any?) -> Bool { 82 | if let object = object as? LCData { 83 | return object === self || object.value == value 84 | } else { 85 | return false 86 | } 87 | } 88 | 89 | public var jsonValue: Any { 90 | return typedJSONValue 91 | } 92 | 93 | private var typedJSONValue: [String: String] { 94 | return [ 95 | "__type": "Bytes", 96 | "base64": base64EncodedString 97 | ] 98 | } 99 | 100 | public var rawValue: Any { 101 | return self.value 102 | } 103 | 104 | var lconValue: Any? { 105 | return jsonValue 106 | } 107 | 108 | static func instance(application: LCApplication) -> LCValue { 109 | return self.init() 110 | } 111 | 112 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 113 | /* Nothing to do. */ 114 | } 115 | 116 | func add(_ other: LCValue) throws -> LCValue { 117 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 118 | } 119 | 120 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 121 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 122 | } 123 | 124 | func differ(_ other: LCValue) throws -> LCValue { 125 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Sources/Foundation/Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCDate.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 4/1/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud date type. 13 | 14 | This type used to represent a point in UTC time. 15 | */ 16 | public final class LCDate: NSObject, LCValue, LCValueExtension { 17 | public private(set) var value: Date = Date() 18 | 19 | static let dateFormatter: DateFormatter = { 20 | let formatter = DateFormatter() 21 | formatter.locale = Locale(identifier: "en_US_POSIX") 22 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" 23 | formatter.timeZone = TimeZone(secondsFromGMT: 0) 24 | return formatter 25 | }() 26 | 27 | static func dateFromString(_ isoString: String) -> Date? { 28 | return dateFormatter.date(from: isoString) 29 | } 30 | 31 | static func stringFromDate(_ date: Date) -> String { 32 | return dateFormatter.string(from: date) 33 | } 34 | 35 | public var isoString: String { 36 | return LCDate.stringFromDate(value) 37 | } 38 | 39 | public override init() { 40 | super.init() 41 | } 42 | 43 | public convenience init(_ value: Date) { 44 | self.init() 45 | self.value = value 46 | } 47 | 48 | public convenience init(_ date: LCDate) { 49 | self.init() 50 | self.value = date.value 51 | } 52 | 53 | init?(isoString: String) { 54 | guard let date = LCDate.dateFromString(isoString) else { 55 | return nil 56 | } 57 | 58 | value = date 59 | } 60 | 61 | init?(dictionary: [String: Any]) { 62 | guard let type = dictionary["__type"] as? String else { 63 | return nil 64 | } 65 | guard let dataType = HTTPClient.DataType(rawValue: type) else { 66 | return nil 67 | } 68 | guard case dataType = HTTPClient.DataType.date else { 69 | return nil 70 | } 71 | guard let ISOString = dictionary["iso"] as? String else { 72 | return nil 73 | } 74 | guard let date = LCDate.dateFromString(ISOString) else { 75 | return nil 76 | } 77 | 78 | value = date 79 | } 80 | 81 | init?(jsonValue: Any?) { 82 | var value: Date? 83 | 84 | switch jsonValue { 85 | case let ISOString as String: 86 | value = LCDate.dateFromString(ISOString) 87 | case let dictionary as [String: Any]: 88 | if let date = LCDate(dictionary: dictionary) { 89 | value = date.value 90 | } 91 | case let date as LCDate: 92 | value = date.value 93 | default: 94 | break 95 | } 96 | 97 | guard let someValue = value else { 98 | return nil 99 | } 100 | 101 | self.value = someValue 102 | } 103 | 104 | public required init?(coder aDecoder: NSCoder) { 105 | value = (aDecoder.decodeObject(forKey: "value") as? Date) ?? Date() 106 | } 107 | 108 | public func encode(with aCoder: NSCoder) { 109 | aCoder.encode(value, forKey: "value") 110 | } 111 | 112 | public func copy(with zone: NSZone?) -> Any { 113 | return LCDate(self) 114 | } 115 | 116 | public override func isEqual(_ object: Any?) -> Bool { 117 | if let object = object as? LCDate { 118 | return object === self || object.value == value 119 | } else { 120 | return false 121 | } 122 | } 123 | 124 | public var jsonValue: Any { 125 | return typedJSONValue 126 | } 127 | 128 | private var typedJSONValue: [String: String] { 129 | return [ 130 | "__type": "Date", 131 | "iso": isoString 132 | ] 133 | } 134 | 135 | public var rawValue: Any { 136 | return self.value 137 | } 138 | 139 | var lconValue: Any? { 140 | return jsonValue 141 | } 142 | 143 | static func instance(application: LCApplication) -> LCValue { 144 | return self.init() 145 | } 146 | 147 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 148 | /* Nothing to do. */ 149 | } 150 | 151 | func add(_ other: LCValue) throws -> LCValue { 152 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 153 | } 154 | 155 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 156 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 157 | } 158 | 159 | func differ(_ other: LCValue) throws -> LCValue { 160 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sources/Foundation/Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCDictionary.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/27/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud dictionary type. 13 | 14 | It is a wrapper of `Swift.Dictionary` type, used to store a dictionary value. 15 | */ 16 | @dynamicMemberLookup 17 | public final class LCDictionary: NSObject, LCValue, LCValueExtension, Collection, ExpressibleByDictionaryLiteral { 18 | public typealias Key = String 19 | public typealias Value = LCValue 20 | public typealias Index = DictionaryIndex 21 | 22 | public private(set) var value: [Key: Value] = [:] 23 | 24 | var elementDidChange: ((Key, Value?) -> Void)? 25 | 26 | public override init() { 27 | super.init() 28 | } 29 | 30 | public convenience init(_ value: [Key: Value]) { 31 | self.init() 32 | self.value = value 33 | } 34 | 35 | public convenience init(_ value: [Key: LCValueConvertible]) { 36 | self.init() 37 | self.value = value.mapValue { value in value.lcValue } 38 | } 39 | 40 | /** 41 | Create copy of dictionary. 42 | 43 | - parameter dictionary: The dictionary to be copied. 44 | */ 45 | public convenience init(_ dictionary: LCDictionary) { 46 | self.init() 47 | self.value = dictionary.value 48 | } 49 | 50 | public convenience required init(dictionaryLiteral elements: (Key, Value)...) { 51 | self.init(Dictionary(elements: elements)) 52 | } 53 | 54 | public convenience init( 55 | application: LCApplication = LCApplication.default, 56 | unsafeObject: Any) 57 | throws 58 | { 59 | self.init() 60 | 61 | guard let object = unsafeObject as? [Key: Any] else { 62 | throw LCError( 63 | code: .malformedData, 64 | reason: "Failed to construct LCDictionary with non-dictionary object.") 65 | } 66 | 67 | value = try object.mapValue { value in 68 | try ObjectProfiler.shared.object(application: application, jsonValue: value) 69 | } 70 | } 71 | 72 | public required init?(coder aDecoder: NSCoder) { 73 | /* Note: We have to make type casting twice here, or it will crash for unknown reason. 74 | It seems that it's a bug of Swift. */ 75 | value = (aDecoder.decodeObject(forKey: "value") as? [String: AnyObject] as? [String: LCValue]) ?? [:] 76 | } 77 | 78 | public func encode(with aCoder: NSCoder) { 79 | aCoder.encode(value, forKey: "value") 80 | } 81 | 82 | public func copy(with zone: NSZone?) -> Any { 83 | return LCDictionary(self) 84 | } 85 | 86 | public override func isEqual(_ object: Any?) -> Bool { 87 | if let object = object as? LCDictionary { 88 | return object === self || object.value == value 89 | } else { 90 | return false 91 | } 92 | } 93 | 94 | public func makeIterator() -> DictionaryIterator { 95 | return value.makeIterator() 96 | } 97 | 98 | public var startIndex: DictionaryIndex { 99 | return value.startIndex 100 | } 101 | 102 | public var endIndex: DictionaryIndex { 103 | return value.endIndex 104 | } 105 | 106 | public func index(after i: DictionaryIndex) -> DictionaryIndex { 107 | return value.index(after: i) 108 | } 109 | 110 | public subscript(position: DictionaryIndex) -> (key: Key, value: Value) { 111 | return value[position] 112 | } 113 | 114 | public subscript(key: Key) -> LCValueConvertible? { 115 | get { 116 | return self.value[key] 117 | } 118 | set { 119 | let lcValue = newValue?.lcValue 120 | self.value[key] = lcValue 121 | self.elementDidChange?(key, lcValue) 122 | } 123 | } 124 | 125 | public subscript(dynamicMember key: String) -> LCValueConvertible? { 126 | get { 127 | return self[key] 128 | } 129 | set { 130 | self[key] = newValue 131 | } 132 | } 133 | 134 | /** 135 | Removes the given key and its associated value from dictionary. 136 | 137 | - parameter key: The key to remove along with its associated value. 138 | 139 | - returns: The value that was removed, or `nil` if the key was not found. 140 | */ 141 | @discardableResult 142 | public func removeValue(forKey key: Key) -> Value? { 143 | return value.removeValue(forKey: key) 144 | } 145 | 146 | public func removeAll(keepingCapacity keepCapacity: Bool = false) { 147 | return value.removeAll(keepingCapacity: keepCapacity) 148 | } 149 | 150 | func set(_ key: String, _ value: LCValue?) { 151 | self.value[key] = value 152 | } 153 | 154 | public var jsonValue: Any { 155 | return value.compactMapValue { value in value.jsonValue } 156 | } 157 | 158 | public var rawValue: Any { 159 | let v: [String: Any] = self.value.mapValue { value in value.rawValue } 160 | return v 161 | } 162 | 163 | var lconValue: Any? { 164 | return value.compactMapValue { value in (value as? LCValueExtension)?.lconValue } 165 | } 166 | 167 | static func instance(application: LCApplication) -> LCValue { 168 | return self.init([:]) 169 | } 170 | 171 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 172 | try forEach { (_, element) in try body(element) } 173 | } 174 | 175 | func add(_ other: LCValue) throws -> LCValue { 176 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 177 | } 178 | 179 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 180 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 181 | } 182 | 183 | func differ(_ other: LCValue) throws -> LCValue { 184 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Sources/Foundation/EngineClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EngineClient.swift 3 | // LeanCloud 4 | // 5 | // Created by zapcannon87 on 2019/7/4. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LeanCloud Cloud Engine Client 12 | public class LCEngine { 13 | 14 | /// Call the cloud function synchronously. 15 | /// - Parameters: 16 | /// - application: The application. 17 | /// - function: The name of the function in the cloud. 18 | /// - parameters: The parameters passing to the function in the cloud. 19 | public static func run( 20 | application: LCApplication = .default, 21 | _ function: String, 22 | parameters: [String: Any]? = nil) 23 | -> LCGenericResult 24 | { 25 | return expect { (fulfill) in 26 | self.run( 27 | application: application, 28 | function: function, 29 | parameters: parameters, 30 | completionInBackground: { (result) in 31 | fulfill(result) 32 | }) 33 | } 34 | } 35 | 36 | /// Call the cloud function asynchronously. 37 | /// - Parameters: 38 | /// - application: The application. 39 | /// - function: The name of the function in the cloud. 40 | /// - parameters: The parameters passing to the function in the cloud. 41 | /// - completionQueue: The queue where the `completion` be executed, default is main. 42 | /// - completion: Result callback. 43 | @discardableResult 44 | public static func run( 45 | application: LCApplication = .default, 46 | _ function: String, 47 | parameters: [String: Any]? = nil, 48 | completionQueue: DispatchQueue = .main, 49 | completion: @escaping (LCGenericResult) -> Void) 50 | -> LCRequest 51 | { 52 | return self.run( 53 | application: application, 54 | function: function, 55 | parameters: parameters, 56 | completionInBackground: { (result) in 57 | completionQueue.async { 58 | completion(result) 59 | } 60 | }) 61 | } 62 | 63 | @discardableResult 64 | private static func run( 65 | application: LCApplication, 66 | function: String, 67 | parameters: [String: Any]?, 68 | completionInBackground completion: @escaping (LCGenericResult) -> Void) 69 | -> LCRequest 70 | { 71 | return application.httpClient.request( 72 | .post, "functions/\(function)", 73 | parameters: parameters) 74 | { (response) in 75 | if let error: Error = LCError(response: response) { 76 | completion(.failure(error: LCError(error: error))) 77 | } else { 78 | if let result: Any = response["result"] { 79 | completion(.success(value: result)) 80 | } else { 81 | completion(.failure( 82 | error: LCError( 83 | code: .invalidType, 84 | reason: "invalid response data type."))) 85 | } 86 | } 87 | } 88 | } 89 | 90 | /// RPC call the cloud function synchronously. 91 | /// - Parameters: 92 | /// - application: The application. 93 | /// - function: The name of the function in the cloud. 94 | /// - parameters: The parameters passing to the function in the cloud. 95 | public static func call( 96 | application: LCApplication = .default, 97 | _ function: String, 98 | parameters: LCDictionaryConvertible? = nil) 99 | -> LCValueOptionalResult 100 | { 101 | return expect { (fulfill) in 102 | self.call( 103 | application: application, 104 | function: function, 105 | parameters: parameters, 106 | completionInBackground: { (result) in 107 | fulfill(result) 108 | }) 109 | } 110 | } 111 | 112 | /// RPC call the cloud function asynchronously. 113 | /// - Parameters: 114 | /// - application: The application. 115 | /// - function: The name of the function in the cloud. 116 | /// - parameters: The parameters passing to the function in the cloud. 117 | /// - completionQueue: The queue where the `completion` be executed, default is main. 118 | /// - completion: Result callback. 119 | @discardableResult 120 | public static func call( 121 | application: LCApplication = .default, 122 | _ function: String, 123 | parameters: LCDictionaryConvertible? = nil, 124 | completionQueue: DispatchQueue = .main, 125 | completion: @escaping (LCValueOptionalResult) -> Void) 126 | -> LCRequest 127 | { 128 | return self.call( 129 | application: application, 130 | function: function, 131 | parameters: parameters, 132 | completionInBackground: { (result) in 133 | completionQueue.async { 134 | completion(result) 135 | } 136 | }) 137 | } 138 | 139 | /// RPC call the cloud function synchronously. 140 | /// - Parameters: 141 | /// - application: The application. 142 | /// - function: The name of the function in the cloud. 143 | /// - parameters: The parameters passing to the function in the cloud. 144 | public static func call( 145 | application: LCApplication = .default, 146 | _ function: String, 147 | parameters: LCObject) 148 | -> LCValueOptionalResult 149 | { 150 | let dictionary = LCDictionary(parameters.dictionary) 151 | dictionary.removeValue(forKey: "__type") 152 | dictionary.removeValue(forKey: "className") 153 | return self.call( 154 | application: application, 155 | function, 156 | parameters: dictionary) 157 | } 158 | 159 | /// RPC call the cloud function asynchronously. 160 | /// - Parameters: 161 | /// - application: The application. 162 | /// - function: The name of the function in the cloud. 163 | /// - parameters: The parameters passing to the function in the cloud. 164 | /// - completionQueue: The queue where the `completion` be executed, default is main. 165 | /// - completion: Result callback. 166 | @discardableResult 167 | public static func call( 168 | application: LCApplication = .default, 169 | _ function: String, 170 | parameters: LCObject, 171 | completionQueue: DispatchQueue = .main, 172 | completion: @escaping (LCValueOptionalResult) -> Void) 173 | -> LCRequest 174 | { 175 | let dictionary = LCDictionary(parameters.dictionary) 176 | dictionary.removeValue(forKey: "__type") 177 | dictionary.removeValue(forKey: "className") 178 | return self.call( 179 | application: application, 180 | function, 181 | parameters: dictionary, 182 | completionQueue: completionQueue, 183 | completion: completion) 184 | } 185 | 186 | @discardableResult 187 | private static func call( 188 | application: LCApplication, 189 | function: String, 190 | parameters: LCDictionaryConvertible?, 191 | completionInBackground completion: @escaping (LCValueOptionalResult) -> Void) 192 | -> LCRequest 193 | { 194 | return application.httpClient.request( 195 | .post, "call/\(function)", 196 | parameters: parameters?.lcDictionary.lconValue as? [String: Any]) 197 | { response in 198 | completion(LCValueOptionalResult( 199 | response: response, 200 | keyPath: "result")) 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Sources/Foundation/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 4/26/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LeanCloud SDK Defining Error 12 | public struct LCError: Error { 13 | public typealias UserInfo = [String: Any] 14 | 15 | public let code: Int 16 | public let reason: String? 17 | public let userInfo: UserInfo? 18 | 19 | /// underlying error, only when `code` equals to `9977`, then this property is non-nil. 20 | public private(set) var underlyingError: Error? 21 | 22 | /// ref: https://github.com/leancloud/rfcs/wiki/SDK-Internal-Error-Definition 23 | public enum InternalErrorCode: Int { 24 | // session/client 25 | case commandTimeout = 9000 26 | case connectionLost = 9001 27 | case clientNotOpen = 9002 28 | case commandInvalid = 9003 29 | case commandDataLengthTooLong = 9008 30 | // conversation 31 | case conversationNotFound = 9100 32 | case updatingMessageNotAllowed = 9120 33 | case updatingMessageNotSent = 9121 34 | case ownerPromotionNotAllowed = 9130 35 | // other 36 | case notFound = 9973 37 | case invalidType = 9974 38 | case malformedData = 9975 39 | case inconsistency = 9976 40 | case underlyingError = 9977 41 | 42 | var message: String? { 43 | switch self { 44 | case .commandTimeout: 45 | return "Out command timeout" 46 | case .connectionLost: 47 | return "Connection lost" 48 | case .clientNotOpen: 49 | return "IM client not open" 50 | case .commandInvalid: 51 | return "In command invalid" 52 | case .commandDataLengthTooLong: 53 | return "Data length of out command is too long" 54 | case .conversationNotFound: 55 | return "Conversation not found" 56 | case .updatingMessageNotAllowed: 57 | return "Updating message from others is not allowed" 58 | case .updatingMessageNotSent: 59 | return "Message is not sent" 60 | case .ownerPromotionNotAllowed: 61 | return "Updating a member's role to owner is not allowed" 62 | case .notFound: 63 | return "Not found" 64 | case .invalidType: 65 | return "Data type invalid" 66 | case .malformedData: 67 | return "Data format invalid" 68 | case .inconsistency: 69 | return "Internal inconsistency exception" 70 | default: 71 | return nil 72 | } 73 | } 74 | } 75 | 76 | /// ref: https://leancloud.cn/docs/error_code.html 77 | public enum ServerErrorCode: Int { 78 | case objectNotFound = 101 79 | case sessionConflict = 4111 80 | case sessionTokenExpired = 4112 81 | } 82 | 83 | /** 84 | Convert an error to LCError. 85 | 86 | The non-LCError will be wrapped into an underlying LCError. 87 | 88 | - parameter error: The error to be converted. 89 | */ 90 | public init(error: Error) { 91 | if let error = error as? LCError { 92 | self = error 93 | } else { 94 | var err = LCError(code: .underlyingError) 95 | err.underlyingError = error 96 | self = err 97 | } 98 | } 99 | 100 | init(code: Int, reason: String? = nil, userInfo: UserInfo? = nil) { 101 | self.code = code 102 | self.reason = reason 103 | self.userInfo = userInfo 104 | } 105 | 106 | init(code: InternalErrorCode, reason: String? = nil, userInfo: UserInfo? = nil) { 107 | self = LCError(code: code.rawValue, reason: (reason ?? code.message), userInfo: userInfo) 108 | } 109 | 110 | init(code: ServerErrorCode, reason: String? = nil, userInfo: UserInfo? = nil) { 111 | self = LCError(code: code.rawValue, reason: reason, userInfo: userInfo) 112 | } 113 | } 114 | 115 | extension LCError: CustomStringConvertible, CustomDebugStringConvertible { 116 | 117 | public var description: String { 118 | var description = "\(LCError.self)(code: \(self.code)" 119 | if let reason = self.reason { 120 | description += ", reason: \"\(reason)\"" 121 | } 122 | if let userInfo = self.userInfo { 123 | description += ", userInfo: \(userInfo)" 124 | } 125 | if let underlyingError = self.underlyingError { 126 | description += ", underlyingError: \(underlyingError)" 127 | } 128 | return "\(description))" 129 | } 130 | 131 | public var debugDescription: String { 132 | return self.description 133 | } 134 | } 135 | 136 | extension LCError: LocalizedError { 137 | 138 | public var errorDescription: String? { 139 | return self.description 140 | } 141 | 142 | public var failureReason: String? { 143 | return self.reason 144 | ?? self.underlyingError?.localizedDescription 145 | } 146 | } 147 | 148 | extension LCError: CustomNSError { 149 | 150 | public static var errorDomain: String { 151 | return String(describing: self) 152 | } 153 | 154 | public var errorCode: Int { 155 | return self.code 156 | } 157 | 158 | public var errorUserInfo: [String : Any] { 159 | if let userInfo = self.userInfo { 160 | return userInfo 161 | } else if let underlyingError = self.underlyingError { 162 | return (underlyingError as NSError).userInfo 163 | } else { 164 | return [:] 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Sources/Foundation/GeoPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCGeoPoint.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 4/1/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud geography point type. 13 | 14 | This type can be used to represent a 2D location with latitude and longitude. 15 | */ 16 | public final class LCGeoPoint: NSObject, LCValue, LCValueExtension { 17 | public private(set) var latitude: Double = 0 18 | public private(set) var longitude: Double = 0 19 | 20 | public enum Unit: String { 21 | case mile = "Miles" 22 | case kilometer = "Kilometers" 23 | case radian = "Radians" 24 | } 25 | 26 | public struct Distance { 27 | let value: Double 28 | let unit: Unit 29 | 30 | public init(value: Double, unit: Unit) { 31 | self.value = value 32 | self.unit = unit 33 | } 34 | } 35 | 36 | public override init() { 37 | super.init() 38 | } 39 | 40 | public convenience init(latitude: Double, longitude: Double) { 41 | self.init() 42 | self.latitude = latitude 43 | self.longitude = longitude 44 | } 45 | 46 | public convenience init(_ geoPoint: LCGeoPoint) { 47 | self.init() 48 | self.latitude = geoPoint.latitude 49 | self.longitude = geoPoint.longitude 50 | } 51 | 52 | init?(dictionary: [String: Any]) { 53 | guard let type = dictionary["__type"] as? String else { 54 | return nil 55 | } 56 | guard let dataType = HTTPClient.DataType(rawValue: type) else { 57 | return nil 58 | } 59 | guard case dataType = HTTPClient.DataType.geoPoint else { 60 | return nil 61 | } 62 | guard let latitude = dictionary["latitude"] as? Double else { 63 | return nil 64 | } 65 | guard let longitude = dictionary["longitude"] as? Double else { 66 | return nil 67 | } 68 | 69 | self.latitude = latitude 70 | self.longitude = longitude 71 | } 72 | 73 | public required init?(coder aDecoder: NSCoder) { 74 | latitude = aDecoder.decodeDouble(forKey: "latitude") 75 | longitude = aDecoder.decodeDouble(forKey: "longitude") 76 | } 77 | 78 | public func encode(with aCoder: NSCoder) { 79 | aCoder.encode(latitude, forKey: "latitude") 80 | aCoder.encode(longitude, forKey: "longitude") 81 | } 82 | 83 | public func copy(with zone: NSZone?) -> Any { 84 | return LCGeoPoint(self) 85 | } 86 | 87 | public override func isEqual(_ object: Any?) -> Bool { 88 | if let object = object as? LCGeoPoint { 89 | return object === self || (object.latitude == latitude && object.longitude == longitude) 90 | } else { 91 | return false 92 | } 93 | } 94 | 95 | public var jsonValue: Any { 96 | return typedJSONValue 97 | } 98 | 99 | private var typedJSONValue: [String: LCValueConvertible] { 100 | return [ 101 | "__type" : "GeoPoint", 102 | "latitude" : latitude, 103 | "longitude" : longitude 104 | ] 105 | } 106 | 107 | public var rawValue: Any { 108 | return self 109 | } 110 | 111 | var lconValue: Any? { 112 | return jsonValue 113 | } 114 | 115 | static func instance(application: LCApplication) -> LCValue { 116 | return self.init() 117 | } 118 | 119 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 120 | /* Nothing to do. */ 121 | } 122 | 123 | func add(_ other: LCValue) throws -> LCValue { 124 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 125 | } 126 | 127 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 128 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 129 | } 130 | 131 | func differ(_ other: LCValue) throws -> LCValue { 132 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Sources/Foundation/LocalStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalStorage.swift 3 | // LeanCloud 4 | // 5 | // Created by Tianyong Tang on 2018/9/17. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class LocalStorageContext { 12 | static let domain: String = "com.leancloud.swift" 13 | 14 | enum Place { 15 | case systemCaches 16 | case persistentData 17 | 18 | var searchPathDirectory: FileManager.SearchPathDirectory { 19 | switch self { 20 | case .systemCaches: 21 | return .cachesDirectory 22 | case .persistentData: 23 | return .applicationSupportDirectory 24 | } 25 | } 26 | } 27 | 28 | enum Module { 29 | case router 30 | case storage 31 | case push 32 | case IM(clientID: String) 33 | 34 | var path: String { 35 | switch self { 36 | case .router: 37 | return "router" 38 | case .storage: 39 | return "storage" 40 | case .push: 41 | return "push" 42 | case .IM(clientID: let clientID): 43 | return ("IM" as NSString) 44 | .appendingPathComponent( 45 | clientID.md5.lowercased()) 46 | } 47 | } 48 | } 49 | 50 | enum File: String { 51 | // App Router Data 52 | case appServer = "app_server" 53 | // RTM Router Data 54 | case rtmServer = "rtm_server" 55 | // Application's Current User 56 | case user = "user" 57 | // Application's Current Installation 58 | case installation = "installation" 59 | // IMClient's local data 60 | case clientRecord = "client_record" 61 | // Database for IMConversation and IMMessage 62 | case database = "database.sqlite" 63 | 64 | var name: String { 65 | return self.rawValue 66 | } 67 | } 68 | 69 | let application: LCApplication 70 | 71 | init(application: LCApplication) { 72 | self.application = application 73 | } 74 | 75 | func fileURL(place: Place, module: Module, file: File) throws -> URL { 76 | let moduleDirectoryURL = ( 77 | try FileManager.default.url( 78 | for: place.searchPathDirectory, 79 | in: .userDomainMask, 80 | appropriateFor: nil, 81 | create: true)) 82 | .appendingPathComponent( 83 | LocalStorageContext.domain, 84 | isDirectory: true) 85 | .appendingPathComponent( 86 | self.application.id.md5.lowercased(), 87 | isDirectory: true) 88 | .appendingPathComponent( 89 | module.path, 90 | isDirectory: true) 91 | try FileManager.default.createDirectory( 92 | at: moduleDirectoryURL, 93 | withIntermediateDirectories: true) 94 | return moduleDirectoryURL.appendingPathComponent( 95 | file.name) 96 | } 97 | 98 | func save(table: T, to fileURL: URL, encoder: JSONEncoder = JSONEncoder()) throws { 99 | let tempFileURL: URL = URL(fileURLWithPath: NSTemporaryDirectory()) 100 | .appendingPathComponent(Utility.compactUUID) 101 | try (try encoder.encode(table)) 102 | .write(to: tempFileURL) 103 | if FileManager.default.fileExists(atPath: fileURL.path) { 104 | try FileManager.default.replaceItem( 105 | at: fileURL, 106 | withItemAt: tempFileURL, 107 | backupItemName: nil, 108 | resultingItemURL: nil) 109 | } else { 110 | try FileManager.default.moveItem( 111 | atPath: tempFileURL.path, 112 | toPath: fileURL.path) 113 | } 114 | } 115 | 116 | func table(from fileURL: URL, decoder: JSONDecoder = JSONDecoder()) throws -> T? { 117 | guard FileManager.default.fileExists(atPath: fileURL.path), 118 | let data = FileManager.default.contents(atPath: fileURL.path) else { 119 | return nil 120 | } 121 | return try decoder.decode(T.self, from: data) 122 | } 123 | 124 | func clear(file fileURL: URL) throws { 125 | if FileManager.default.fileExists(atPath: fileURL.path) { 126 | try FileManager.default.removeItem(atPath: fileURL.path) 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /Sources/Foundation/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 10/19/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Logger { 12 | 13 | static let shared = Logger() 14 | 15 | private init() {} 16 | 17 | static let dateFormatter: DateFormatter = { 18 | let dateFormatter = DateFormatter() 19 | 20 | dateFormatter.locale = NSLocale.current 21 | dateFormatter.dateFormat = "yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'SSS" 22 | 23 | return dateFormatter 24 | }() 25 | 26 | private func log( 27 | _ level: LCApplication.LogLevel, 28 | _ value: () -> T, 29 | _ file: String = #file, 30 | _ function: String = #function, 31 | _ line: Int = #line) 32 | { 33 | guard LCApplication.logLevel >= level else { 34 | return 35 | } 36 | 37 | let date = Logger.dateFormatter.string(from: Date()) 38 | let file = NSURL(string: file)?.lastPathComponent ?? "Unknown" 39 | 40 | var info = "[\(level)][LeanCloud][\(date)][\(file)][#\(line)][\(function)]:" 41 | #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) 42 | switch level { 43 | case .error: 44 | info = "[❤️]" + info 45 | case .debug: 46 | info = "[💙]" + info 47 | case .verbose: 48 | info = "[💛]" + info 49 | default: 50 | break 51 | } 52 | #endif 53 | 54 | print(info, value()) 55 | } 56 | 57 | func debug( 58 | _ value: @autoclosure () -> T, 59 | _ file: String = #file, 60 | _ function: String = #function, 61 | _ line: Int = #line) 62 | { 63 | log(.debug, value, file, function, line) 64 | } 65 | 66 | func debug( 67 | closure: () -> T, 68 | _ file: String = #file, 69 | _ function: String = #function, 70 | _ line: Int = #line) 71 | { 72 | log(.debug, closure, file, function, line) 73 | } 74 | 75 | func error( 76 | _ value: @autoclosure () -> T, 77 | _ file: String = #file, 78 | _ function: String = #function, 79 | _ line: Int = #line) 80 | { 81 | log(.error, value, file, function, line) 82 | } 83 | 84 | func error( 85 | closure: () -> T, 86 | _ file: String = #file, 87 | _ function: String = #function, 88 | _ line: Int = #line) 89 | { 90 | log(.error, closure, file, function, line) 91 | } 92 | 93 | func verbose( 94 | _ value: @autoclosure () -> T, 95 | _ file: String = #file, 96 | _ function: String = #function, 97 | _ line: Int = #line) 98 | { 99 | log(.verbose, value, file, function, line) 100 | } 101 | 102 | func verbose( 103 | closure: () -> T, 104 | _ file: String = #file, 105 | _ function: String = #function, 106 | _ line: Int = #line) 107 | { 108 | log(.verbose, closure, file, function, line) 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /Sources/Foundation/Null.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCNull.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 4/23/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud null type. 13 | 14 | A LeanCloud data type represents null value. 15 | 16 | - note: This type is not a singleton type, because Swift does not support singleton well currently. 17 | */ 18 | public final class LCNull: NSObject, LCValue, LCValueExtension { 19 | public override init() { 20 | super.init() 21 | } 22 | 23 | public required init?(coder aDecoder: NSCoder) { 24 | /* Nothing to decode. */ 25 | } 26 | 27 | public func encode(with aCoder: NSCoder) { 28 | /* Nothing to encode. */ 29 | } 30 | 31 | public func copy(with zone: NSZone?) -> Any { 32 | return LCNull() 33 | } 34 | 35 | public override func isEqual(_ object: Any?) -> Bool { 36 | return object is LCNull 37 | } 38 | 39 | public var jsonValue: Any { 40 | return NSNull() 41 | } 42 | 43 | public var rawValue: Any { 44 | return NSNull() 45 | } 46 | 47 | var lconValue: Any? { 48 | return jsonValue 49 | } 50 | 51 | static func instance(application: LCApplication) -> LCValue { 52 | return LCNull() 53 | } 54 | 55 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 56 | /* Nothing to do. */ 57 | } 58 | 59 | func add(_ other: LCValue) throws -> LCValue { 60 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 61 | } 62 | 63 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 64 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 65 | } 66 | 67 | func differ(_ other: LCValue) throws -> LCValue { 68 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/Foundation/Number.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCNumber.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/27/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud number type. 13 | 14 | It is a wrapper of `Swift.Double` type, used to store a number value. 15 | */ 16 | public final class LCNumber: NSObject, LCValue, LCValueExtension, ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral { 17 | public private(set) var value: Double = 0 18 | 19 | public override init() { 20 | super.init() 21 | } 22 | 23 | public convenience init(_ value: Double) { 24 | self.init() 25 | self.value = value 26 | } 27 | 28 | public convenience init(_ number: LCNumber) { 29 | self.init() 30 | self.value = number.value 31 | } 32 | 33 | public convenience required init(floatLiteral value: FloatLiteralType) { 34 | self.init(value) 35 | } 36 | 37 | public convenience required init(integerLiteral value: IntegerLiteralType) { 38 | self.init(Double(value)) 39 | } 40 | 41 | public required init?(coder aDecoder: NSCoder) { 42 | value = aDecoder.decodeDouble(forKey: "value") 43 | } 44 | 45 | public func encode(with aCoder: NSCoder) { 46 | aCoder.encode(value, forKey: "value") 47 | } 48 | 49 | public func copy(with zone: NSZone?) -> Any { 50 | return LCNumber(self) 51 | } 52 | 53 | public override func isEqual(_ object: Any?) -> Bool { 54 | if let object = object as? LCNumber { 55 | return object === self || object.value == value 56 | } else { 57 | return false 58 | } 59 | } 60 | 61 | public var jsonValue: Any { 62 | return value 63 | } 64 | 65 | public var rawValue: Any { 66 | return self.value 67 | } 68 | 69 | var lconValue: Any? { 70 | return jsonValue 71 | } 72 | 73 | static func instance(application: LCApplication) -> LCValue { 74 | return LCNumber() 75 | } 76 | 77 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 78 | /* Nothing to do. */ 79 | } 80 | 81 | func add(_ other: LCValue) throws -> LCValue { 82 | let result = LCNumber(value) 83 | 84 | result.addInPlace((other as! LCNumber).value) 85 | 86 | return result 87 | } 88 | 89 | func addInPlace(_ amount: Double) { 90 | value += amount 91 | } 92 | 93 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 94 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 95 | } 96 | 97 | func differ(_ other: LCValue) throws -> LCValue { 98 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/Foundation/PushClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushClient.swift 3 | // LeanCloud 4 | // 5 | // Created by zapcannon87 on 2019/7/9. 6 | // Copyright © 2019 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// LeanCloud Push Client 12 | public class LCPush { 13 | 14 | /// Send push notification synchronously. 15 | /// - Parameters: 16 | /// - application: The application, default is `LCApplication.default`. 17 | /// - data: The body data of the push message. 18 | /// - query: The query condition, if this parameter be set, then `channels` will be ignored. 19 | /// - channels: The channels condition, if `query` be set, then this parameter will be ignored. 20 | /// - pushDate: The date when to send. 21 | /// - expirationDate: The expiration date of this push notification. 22 | /// - expirationInterval: The expiration interval from `pushDate` of this push notification. 23 | /// - extraParameters: The extra parameters, for some specific configuration. 24 | /// - Returns: Boolean result, see `LCBooleanResult`. 25 | public static func send( 26 | application: LCApplication = .default, 27 | data: [String: Any], 28 | query: LCQuery? = nil, 29 | channels: [String]? = nil, 30 | pushDate: Date? = nil, 31 | expirationDate: Date? = nil, 32 | expirationInterval: TimeInterval? = nil, 33 | extraParameters: [String: Any]? = nil) 34 | -> LCBooleanResult 35 | { 36 | return expect { (fulfill) in 37 | self.send( 38 | application: application, 39 | data: data, 40 | query: query, 41 | channels: channels, 42 | pushDate: pushDate, 43 | expirationDate: expirationDate, 44 | expirationInterval: expirationInterval, 45 | extraParameters: extraParameters, 46 | completionInBackground: { (result) in 47 | fulfill(result) 48 | }) 49 | } 50 | } 51 | 52 | /// Send push notification asynchronously. 53 | /// - Parameters: 54 | /// - application: The application, default is `LCApplication.default`. 55 | /// - data: The body data of the push message. 56 | /// - query: The query condition, if this parameter be set, then `channels` will be ignored. 57 | /// - channels: The channels condition, if `query` be set, then this parameter will be ignored. 58 | /// - pushDate: The date when to send. 59 | /// - expirationDate: The expiration date of this push notification. 60 | /// - expirationInterval: The expiration interval from `pushDate` of this push notification. 61 | /// - extraParameters: The extra parameters, for some specific configuration. 62 | /// - completionQueue: The queue where `completion` be called. 63 | /// - completion: The result callback. 64 | /// - Returns: The request, see `LCRequest`. 65 | @discardableResult 66 | public static func send( 67 | application: LCApplication = .default, 68 | data: [String: Any], 69 | query: LCQuery? = nil, 70 | channels: [String]? = nil, 71 | pushDate: Date? = nil, 72 | expirationDate: Date? = nil, 73 | expirationInterval: TimeInterval? = nil, 74 | extraParameters: [String: Any]? = nil, 75 | completionQueue: DispatchQueue = .main, 76 | completion: @escaping (LCBooleanResult) -> Void) 77 | -> LCRequest 78 | { 79 | return self.send( 80 | application: application, 81 | data: data, 82 | query: query, 83 | channels: channels, 84 | pushDate: pushDate, 85 | expirationDate: expirationDate, 86 | expirationInterval: expirationInterval, 87 | extraParameters: extraParameters, 88 | completionInBackground: { (result) in 89 | completionQueue.async { 90 | completion(result) 91 | } 92 | }) 93 | } 94 | 95 | @discardableResult 96 | private static func send( 97 | application: LCApplication, 98 | data: [String: Any], 99 | query: LCQuery?, 100 | channels: [String]?, 101 | pushDate: Date?, 102 | expirationDate: Date?, 103 | expirationInterval: TimeInterval?, 104 | extraParameters: [String: Any]?, 105 | completionInBackground completion: @escaping (LCBooleanResult) -> Void) 106 | -> LCRequest 107 | { 108 | var parameters: [String: Any] = [ 109 | "prod": application.pushMode, 110 | "data": data, 111 | ] 112 | if let query = query { 113 | if let lconWhere = query.lconWhere { 114 | parameters["where"] = lconWhere 115 | } 116 | } else if let channels = channels { 117 | parameters["channels"] = channels 118 | } 119 | if let pushDate = pushDate { 120 | parameters["push_time"] = LCDate.stringFromDate(pushDate) 121 | } 122 | if let expirationDate = expirationDate { 123 | parameters["expiration_time"] = LCDate.stringFromDate(expirationDate) 124 | } 125 | if let expirationInterval = expirationInterval { 126 | if pushDate == nil { 127 | parameters["push_time"] = LCDate().isoString 128 | } 129 | parameters["expiration_interval"] = expirationInterval 130 | } 131 | if let extraParameters = extraParameters { 132 | parameters.merge(extraParameters) { (current, _) in current } 133 | } 134 | return application.httpClient.request( 135 | .post, "push", 136 | parameters: parameters) { (response) in 137 | completion(LCBooleanResult(response: response)) 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Sources/Foundation/Relation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCRelation.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 3/24/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud relation type. 13 | 14 | This type can be used to make one-to-many relationship between objects. 15 | */ 16 | public final class LCRelation: NSObject, LCValue, LCValueExtension, Sequence { 17 | public typealias Element = LCObject 18 | 19 | public let application: LCApplication 20 | 21 | /// The key where relationship based on. 22 | var key: String? 23 | 24 | /// The parent of all children in relation. 25 | weak var parent: LCObject? 26 | 27 | /// The class name of children. 28 | var objectClassName: String? 29 | 30 | /// An array of children added locally. 31 | var value: [Element] = [] 32 | 33 | /// Effective object class name. 34 | var effectiveObjectClassName: String? { 35 | return objectClassName ?? value.first?.actualClassName 36 | } 37 | 38 | override init() { 39 | self.application = LCApplication.default 40 | super.init() 41 | } 42 | 43 | init(application: LCApplication) { 44 | self.application = application 45 | super.init() 46 | } 47 | 48 | init(application: LCApplication, key: String, parent: LCObject) { 49 | self.application = application 50 | super.init() 51 | 52 | self.key = key 53 | self.parent = parent 54 | } 55 | 56 | init?(application: LCApplication, dictionary: [String: Any]) { 57 | self.application = application 58 | super.init() 59 | 60 | guard let type = dictionary["__type"] as? String else { 61 | return nil 62 | } 63 | guard let dataType = HTTPClient.DataType(rawValue: type) else { 64 | return nil 65 | } 66 | guard case dataType = HTTPClient.DataType.relation else { 67 | return nil 68 | } 69 | 70 | objectClassName = dictionary["className"] as? String 71 | } 72 | 73 | public required init?(coder aDecoder: NSCoder) { 74 | if let applicationID = aDecoder.decodeObject(forKey: "applicationID") as? String, 75 | let registeredApplication = LCApplication.registry[applicationID] { 76 | self.application = registeredApplication 77 | } else { 78 | self.application = LCApplication.default 79 | } 80 | super.init() 81 | value = (aDecoder.decodeObject(forKey: "value") as? [Element]) ?? [] 82 | objectClassName = aDecoder.decodeObject(forKey: "objectClassName") as? String 83 | } 84 | 85 | public func encode(with aCoder: NSCoder) { 86 | let applicationID: String = self.application.id 87 | aCoder.encode(applicationID, forKey: "applicationID") 88 | aCoder.encode(value, forKey: "value") 89 | 90 | if let objectClassName = objectClassName { 91 | aCoder.encode(objectClassName, forKey: "objectClassName") 92 | } 93 | } 94 | 95 | public func copy(with zone: NSZone?) -> Any { 96 | return self 97 | } 98 | 99 | public override func isEqual(_ object: Any?) -> Bool { 100 | if let object = object as? LCRelation { 101 | return object === self || (parent != nil && key != nil && object.parent == parent && object.key == key) 102 | } else { 103 | return false 104 | } 105 | } 106 | 107 | public func makeIterator() -> IndexingIterator<[Element]> { 108 | return value.makeIterator() 109 | } 110 | 111 | public var jsonValue: Any { 112 | return typedJSONValue 113 | } 114 | 115 | private var typedJSONValue: [String: String] { 116 | var result = [ 117 | "__type": "Relation" 118 | ] 119 | 120 | if let className = effectiveObjectClassName { 121 | result["className"] = className 122 | } 123 | 124 | return result 125 | } 126 | 127 | public var rawValue: Any { 128 | return self 129 | } 130 | 131 | var lconValue: Any? { 132 | return value.compactMap { (element) in element.lconValue } 133 | } 134 | 135 | static func instance(application: LCApplication) -> LCValue { 136 | return self.init(application: application) 137 | } 138 | 139 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 140 | try value.forEach { element in try body(element) } 141 | } 142 | 143 | func add(_ other: LCValue) throws -> LCValue { 144 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 145 | } 146 | 147 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 148 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 149 | } 150 | 151 | func differ(_ other: LCValue) throws -> LCValue { 152 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 153 | } 154 | 155 | func validateClassName(_ objects: [Element]) throws { 156 | guard !objects.isEmpty else { return } 157 | 158 | let className = effectiveObjectClassName ?? objects.first!.actualClassName 159 | 160 | for object in objects { 161 | guard object.actualClassName == className else { 162 | throw LCError(code: .invalidType, reason: "Invalid class name.", userInfo: nil) 163 | } 164 | } 165 | } 166 | 167 | /** 168 | Append elements. 169 | 170 | - parameter elements: The elements to be appended. 171 | */ 172 | func appendElements(_ elements: [Element]) throws { 173 | try validateClassName(elements) 174 | 175 | value = value + elements 176 | } 177 | 178 | /** 179 | Remove elements. 180 | 181 | - parameter elements: The elements to be removed. 182 | */ 183 | func removeElements(_ elements: [Element]) { 184 | value = value - elements 185 | } 186 | 187 | /** 188 | Insert a child into relation. 189 | 190 | - parameter child: The child that you want to insert. 191 | */ 192 | public func insert(_ child: LCObject) throws { 193 | guard let key = key else { 194 | throw LCError(code: .inconsistency, reason: "Failed to insert object to relation without key.") 195 | } 196 | guard let parent = parent else { 197 | throw LCError(code: .inconsistency, reason: "Failed to insert object to an unbound relation.") 198 | } 199 | try parent.insertRelation(key, object: child) 200 | } 201 | 202 | /** 203 | Remove a child from relation. 204 | 205 | - parameter child: The child that you want to remove. 206 | */ 207 | public func remove(_ child: LCObject) throws { 208 | guard let key = key else { 209 | throw LCError(code: .inconsistency, reason: "Failed to remove object from relation without key.") 210 | } 211 | guard let parent = parent else { 212 | throw LCError(code: .inconsistency, reason: "Failed to remove object from an unbound relation.") 213 | } 214 | try parent.removeRelation(key, object: child) 215 | } 216 | 217 | /** 218 | Get query of current relation. 219 | */ 220 | public var query: LCQuery { 221 | var query: LCQuery! 222 | 223 | let key = self.key! 224 | let parent = self.parent! 225 | 226 | /* If class name already known, use it. 227 | Otherwise, use class name redirection. */ 228 | if let objectClassName = objectClassName { 229 | query = LCQuery(application: self.application, className: objectClassName) 230 | } else { 231 | query = LCQuery(application: self.application, className: parent.actualClassName) 232 | query.extraParameters = [ 233 | "redirectClassNameForKey": key 234 | ] 235 | } 236 | 237 | query.whereKey(key, .relatedTo(parent)) 238 | 239 | return query 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Sources/Foundation/Request.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Request.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 3/30/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | /** 13 | This type represents HTTP request. 14 | */ 15 | public class LCRequest: NSObject { 16 | 17 | /** 18 | Request states. 19 | */ 20 | enum State { 21 | 22 | case resumed 23 | 24 | case suspended 25 | 26 | case cancelled 27 | 28 | } 29 | 30 | var state: State = .resumed 31 | 32 | private let lock = NSRecursiveLock() 33 | 34 | /* Override initializer to make it internal. */ 35 | override init() { 36 | /* Nop */ 37 | } 38 | 39 | func perform(body: () -> Void) { 40 | lock.lock() 41 | 42 | defer { 43 | lock.unlock() 44 | } 45 | 46 | body() 47 | } 48 | 49 | func setNewState(_ newState: State, beforeChange: () -> Void) { 50 | perform { 51 | switch state { 52 | case .resumed: 53 | if newState == .suspended || newState == .cancelled { 54 | beforeChange() 55 | state = newState 56 | } 57 | case .suspended: 58 | if newState == .resumed || newState == .cancelled { 59 | beforeChange() 60 | state = newState 61 | } 62 | case .cancelled: 63 | break 64 | } 65 | } 66 | } 67 | 68 | public func resume() { 69 | /* Nop */ 70 | } 71 | 72 | public func suspend() { 73 | /* Nop */ 74 | } 75 | 76 | public func cancel() { 77 | /* Nop */ 78 | } 79 | 80 | } 81 | 82 | /** 83 | This type represents a single HTTP request. 84 | */ 85 | class LCSingleRequest: LCRequest { 86 | 87 | let request: Request? 88 | 89 | init(request: Request?) { 90 | self.request = request 91 | } 92 | 93 | override func resume() { 94 | setNewState(.resumed) { 95 | request?.resume() 96 | } 97 | } 98 | 99 | override func suspend() { 100 | setNewState(.suspended) { 101 | request?.suspend() 102 | } 103 | } 104 | 105 | override func cancel() { 106 | setNewState(.cancelled) { 107 | request?.cancel() 108 | } 109 | } 110 | 111 | } 112 | 113 | /** 114 | This type represents a sequence of HTTP requests. 115 | */ 116 | class LCSequenceRequest: LCRequest { 117 | 118 | private(set) var request: LCRequest? 119 | 120 | func setCurrentRequest(_ request: LCRequest) { 121 | perform { 122 | self.request = request 123 | 124 | switch state { 125 | case .resumed: 126 | request.resume() 127 | case .suspended: 128 | request.suspend() 129 | case .cancelled: 130 | request.cancel() 131 | } 132 | } 133 | } 134 | 135 | func setCurrentRequest(_ request: Request) { 136 | let request = LCSingleRequest(request: request) 137 | 138 | setCurrentRequest(request) 139 | } 140 | 141 | override func resume() { 142 | setNewState(.resumed) { 143 | request?.resume() 144 | } 145 | } 146 | 147 | override func suspend() { 148 | setNewState(.suspended) { 149 | request?.suspend() 150 | } 151 | } 152 | 153 | override func cancel() { 154 | setNewState(.cancelled) { 155 | request?.cancel() 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /Sources/Foundation/Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Response.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 3/28/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | final class LCResponse { 13 | let application: LCApplication 14 | let response: Alamofire.DataResponse 15 | 16 | init(application: LCApplication, response: Alamofire.DataResponse) { 17 | self.application = application 18 | self.response = response 19 | } 20 | 21 | init(application: LCApplication, afDataResponse: AFDataResponse) { 22 | self.application = application 23 | let result: Result 24 | switch afDataResponse.result { 25 | case let .success(v): 26 | result = .success(v) 27 | case let .failure(e): 28 | result = .failure(e) 29 | } 30 | self.response = DataResponse( 31 | request: afDataResponse.request, 32 | response: afDataResponse.response, 33 | data: afDataResponse.data, 34 | metrics: afDataResponse.metrics, 35 | serializationDuration: afDataResponse.serializationDuration, 36 | result: result) 37 | } 38 | 39 | var error: Error? { 40 | return response.error 41 | } 42 | 43 | /** 44 | A boolean property indicates whether response is OK or not. 45 | */ 46 | var isSuccess: Bool { 47 | return error == nil 48 | } 49 | 50 | var data: Data? { 51 | return response.data 52 | } 53 | 54 | var value: Any? { 55 | return response.value 56 | } 57 | 58 | subscript(key: String) -> T? { 59 | guard let value = value as? [String: Any] else { 60 | return nil 61 | } 62 | return value[key] as? T 63 | } 64 | 65 | var results: [Any] { 66 | return self["results"] ?? [] 67 | } 68 | 69 | var count: Int { 70 | return self["count"] ?? 0 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Foundation/Role.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCRole.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 5/7/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud role type. 13 | 14 | A type to group user for access control. 15 | Conceptually, it is equivalent to UNIX user group. 16 | */ 17 | public class LCRole: LCObject { 18 | /** 19 | Name of role. 20 | 21 | The name must be unique throughout the application. 22 | It will be used as key in ACL to refer the role. 23 | */ 24 | @objc dynamic public var name: LCString? 25 | 26 | /// Relation of users. 27 | @objc dynamic public var users: LCRelation? 28 | 29 | /// Relation of roles. 30 | @objc dynamic public var roles: LCRelation? 31 | 32 | public final override class func objectClassName() -> String { 33 | return "_Role" 34 | } 35 | 36 | /** 37 | Create an role with name. 38 | 39 | - parameter name: The name of role. 40 | */ 41 | public convenience init( 42 | application: LCApplication = LCApplication.default, 43 | name: String) 44 | { 45 | self.init(application: application) 46 | self.name = LCString(name) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Foundation/Runtime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Runtime.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/23/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Runtime { 12 | /** 13 | Get all properties of a class. 14 | 15 | - parameter aClass: Target class. 16 | 17 | - returns: An array of all properties of the given class. 18 | */ 19 | static func properties(_ aClass: AnyClass) -> [objc_property_t] { 20 | var result = [objc_property_t]() 21 | 22 | var count: UInt32 = 0 23 | 24 | guard let properties: UnsafeMutablePointer = class_copyPropertyList(aClass, &count) else { 25 | return result 26 | } 27 | 28 | defer { 29 | properties.deallocate() 30 | } 31 | 32 | for i in 0.. [objc_property_t] { 48 | 49 | var properties: [objc_property_t] = self.properties(aClass) 50 | 51 | properties = properties.filter { property in 52 | if let varChars: UnsafeMutablePointer = property_copyAttributeValue(property, "V") { 53 | 54 | defer { 55 | let _ = String(validatingUTF8: varChars) 56 | varChars.deallocate() 57 | } 58 | 59 | return true 60 | } else { 61 | return false 62 | } 63 | } 64 | 65 | return properties 66 | } 67 | 68 | /** 69 | Get property type encoding. 70 | 71 | - parameter property: Inspected property. 72 | */ 73 | static func typeEncoding(_ property: objc_property_t) -> String? { 74 | 75 | guard let typeChars: UnsafeMutablePointer = property_copyAttributeValue(property, "T") else { 76 | return nil 77 | } 78 | 79 | let utf8Str = String(validatingUTF8: typeChars)! 80 | 81 | defer { 82 | typeChars.deallocate() 83 | } 84 | 85 | return utf8Str 86 | } 87 | 88 | /** 89 | Get property name. 90 | 91 | - parameter property: Inspected property. 92 | */ 93 | static func propertyName(_ property: objc_property_t) -> String { 94 | let propChars: UnsafePointer = property_getName(property) 95 | let utf8Str = String(validatingUTF8: propChars)! 96 | return utf8Str 97 | } 98 | 99 | /** 100 | Get property's backing instance variable from a class. 101 | 102 | - parameter aClass: The class from where you want to get. 103 | - parameter propertyName: The property name. 104 | 105 | - returns: Instance variable correspond to the property name. 106 | */ 107 | static func instanceVariable(_ aClass: AnyClass, _ propertyName: String) -> Ivar? { 108 | 109 | guard let property: objc_property_t = class_getProperty(aClass, propertyName) else { 110 | return nil 111 | } 112 | 113 | guard let varChars: UnsafeMutablePointer = property_copyAttributeValue(property, "V") else { 114 | return nil 115 | } 116 | 117 | defer { 118 | let _ = String(validatingUTF8: varChars) 119 | varChars.deallocate() 120 | } 121 | 122 | let ivar: Ivar? = class_getInstanceVariable(aClass, varChars) 123 | 124 | return ivar 125 | } 126 | 127 | /** 128 | Get instance variable value from an object. 129 | 130 | - parameter object: The object from where you want to get. 131 | - parameter propertyName: The property name. 132 | 133 | - returns: Value of instance variable correspond to the property name. 134 | */ 135 | static func instanceVariableValue(_ object: Any, _ propertyName: String) -> Any? { 136 | guard 137 | let aClass = object_getClass(object), 138 | let ivar = instanceVariable(aClass, propertyName) 139 | else { 140 | return nil 141 | } 142 | 143 | let ivarValue = object_getIvar(object, ivar) 144 | 145 | return ivarValue 146 | } 147 | 148 | /** 149 | Set instance variable value of a property. 150 | 151 | - parameter object: The object. 152 | - parameter propertyName: Property name on which you want to set. 153 | - parameter value: New property value. 154 | */ 155 | static func setInstanceVariable(_ object: Any?, _ propertyName: String, _ value: Any?) { 156 | guard 157 | let object = object, 158 | let aClass = object_getClass(object), 159 | let ivar = instanceVariable(aClass, propertyName) 160 | else { 161 | return 162 | } 163 | 164 | if let value = value { 165 | let ivarValue = retainedObject(value as AnyObject) 166 | object_setIvar(object, ivar, ivarValue) 167 | } else { 168 | object_setIvar(object, ivar, nil) 169 | } 170 | } 171 | 172 | /** 173 | Get retained object. 174 | 175 | - parameter object: The object which you want to retain. 176 | 177 | - returns: An retained object. 178 | */ 179 | static func retainedObject(_ object: T) -> T { 180 | return Unmanaged.passRetained(object).takeUnretainedValue() 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Sources/Foundation/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LCString.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/27/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | LeanCloud string type. 13 | 14 | It is a wrapper of `Swift.String` type, used to store a string value. 15 | */ 16 | public final class LCString: NSObject, LCValue, LCValueExtension, ExpressibleByStringLiteral { 17 | public private(set) var value: String = "" 18 | 19 | public typealias UnicodeScalarLiteralType = Character 20 | public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType 21 | 22 | public override init() { 23 | super.init() 24 | } 25 | 26 | public convenience init(_ value: String) { 27 | self.init() 28 | self.value = value 29 | } 30 | 31 | public convenience init(_ string: LCString) { 32 | self.init() 33 | self.value = string.value 34 | } 35 | 36 | public convenience required init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { 37 | self.init(String(value)) 38 | } 39 | 40 | public convenience required init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) { 41 | self.init(String(value)) 42 | } 43 | 44 | public convenience required init(stringLiteral value: StringLiteralType) { 45 | self.init(value) 46 | } 47 | 48 | public required init?(coder aDecoder: NSCoder) { 49 | value = (aDecoder.decodeObject(forKey: "value") as? String) ?? "" 50 | } 51 | 52 | public func encode(with aCoder: NSCoder) { 53 | aCoder.encode(value, forKey: "value") 54 | } 55 | 56 | public func copy(with zone: NSZone?) -> Any { 57 | return LCString(self) 58 | } 59 | 60 | public override func isEqual(_ object: Any?) -> Bool { 61 | if let object = object as? LCString { 62 | return object === self || object.value == value 63 | } else { 64 | return false 65 | } 66 | } 67 | 68 | public var jsonValue: Any { 69 | return value 70 | } 71 | 72 | public var rawValue: Any { 73 | return self.value 74 | } 75 | 76 | var lconValue: Any? { 77 | return jsonValue 78 | } 79 | 80 | static func instance(application: LCApplication) -> LCValue { 81 | return self.init() 82 | } 83 | 84 | func forEachChild(_ body: (_ child: LCValue) throws -> Void) rethrows { 85 | /* Nothing to do. */ 86 | } 87 | 88 | func add(_ other: LCValue) throws -> LCValue { 89 | throw LCError(code: .invalidType, reason: "Object cannot be added.") 90 | } 91 | 92 | func concatenate(_ other: LCValue, unique: Bool) throws -> LCValue { 93 | throw LCError(code: .invalidType, reason: "Object cannot be concatenated.") 94 | } 95 | 96 | func differ(_ other: LCValue) throws -> LCValue { 97 | throw LCError(code: .invalidType, reason: "Object cannot be differed.") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/Foundation/Utility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utility.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 3/25/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | #if os(iOS) || os(tvOS) 11 | import UIKit 12 | #elseif os(macOS) 13 | import IOKit 14 | #endif 15 | 16 | class Utility { 17 | static var compactUUID: String { 18 | return UUID().uuidString 19 | .replacingOccurrences(of: "-", with: "") 20 | .lowercased() 21 | } 22 | 23 | static var UDID: String { 24 | var udid: String? 25 | #if os(iOS) || os(tvOS) 26 | if let identifierForVendor = UIDevice.current 27 | .identifierForVendor?.uuidString { 28 | udid = identifierForVendor 29 | } 30 | #elseif os(macOS) 31 | let platformExpert = IOServiceGetMatchingService( 32 | kIOMasterPortDefault, 33 | IOServiceMatching("IOPlatformExpertDevice") 34 | ) 35 | if let serialNumber = IORegistryEntryCreateCFProperty( 36 | platformExpert, 37 | kIOPlatformSerialNumberKey as CFString, 38 | kCFAllocatorDefault, 39 | 0).takeRetainedValue() as? String { 40 | udid = serialNumber 41 | } 42 | IOObjectRelease(platformExpert) 43 | #endif 44 | if let udid = udid { 45 | return udid.lowercased() 46 | } else { 47 | return Utility.compactUUID 48 | } 49 | } 50 | 51 | static func jsonString( 52 | _ object: Any?, 53 | encoding: String.Encoding = .utf8) throws -> String? 54 | { 55 | guard let object = object else { 56 | return nil 57 | } 58 | let data = try JSONSerialization.data(withJSONObject: object) 59 | return String(data: data, encoding: encoding) 60 | } 61 | } 62 | 63 | protocol InternalSynchronizing { 64 | 65 | var mutex: NSLock { get } 66 | } 67 | 68 | extension InternalSynchronizing { 69 | 70 | func sync(_ closure: @autoclosure () throws -> T) rethrows -> T { 71 | return try self.sync(closure: closure) 72 | } 73 | 74 | func sync(closure: () throws -> T) rethrows -> T { 75 | self.mutex.lock() 76 | defer { 77 | self.mutex.unlock() 78 | } 79 | return try closure() 80 | } 81 | } 82 | 83 | protocol InternalOptionalSynchronizing { 84 | 85 | var optionalMutex: NSLock? { get } 86 | } 87 | 88 | extension InternalOptionalSynchronizing { 89 | 90 | func optionalSync(_ closure: @autoclosure () throws -> T) rethrows -> T { 91 | return try self.optionalSync(closure: closure) 92 | } 93 | 94 | func optionalSync(closure: () throws -> T) rethrows -> T { 95 | self.optionalMutex?.lock() 96 | defer { 97 | self.optionalMutex?.unlock() 98 | } 99 | return try closure() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/Foundation/Version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Version.swift 3 | // LeanCloud 4 | // 5 | // Created by Tang Tianyong on 2/23/16. 6 | // Copyright © 2016 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Version { 12 | public static let versionString = "17.11.0" 13 | } 14 | -------------------------------------------------------------------------------- /Sources/RTM/RTMRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RTMRouter.swift 3 | // LeanCloud 4 | // 5 | // Created by Tianyong Tang on 2018/11/5. 6 | // Copyright © 2018 LeanCloud. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class RTMRouter { 12 | 13 | let application: LCApplication 14 | 15 | let tableCacheURL: URL? 16 | 17 | private(set) var table: RTMRouter.Table? 18 | 19 | init(application: LCApplication) throws { 20 | 21 | self.application = application 22 | 23 | if let storageContext = application.localStorageContext { 24 | let fileURL = try storageContext.fileURL(place: .systemCaches, module: .router, file: .rtmServer) 25 | self.tableCacheURL = fileURL 26 | self.table = try storageContext.table(from: fileURL) 27 | } else { 28 | self.tableCacheURL = nil 29 | } 30 | } 31 | 32 | private func result(response: LCResponse) -> LCGenericResult { 33 | if let error = LCError(response: response) { 34 | return .failure(error: error) 35 | } 36 | 37 | guard 38 | let object = response.value as? [String: Any], 39 | let primaryServer = object[RTMRouter.Table.CodingKeys.primary.rawValue] as? String, 40 | let ttl = object[RTMRouter.Table.CodingKeys.ttl.rawValue] as? TimeInterval 41 | else 42 | { 43 | return .failure(error: LCError.RTMRouterResponseDataMalformed) 44 | } 45 | 46 | let secondaryServer = object[RTMRouter.Table.CodingKeys.secondary.rawValue] as? String 47 | 48 | let table = RTMRouter.Table( 49 | primary: primaryServer, 50 | secondary: secondaryServer, 51 | ttl: ttl, 52 | createdTimestamp: Date().timeIntervalSince1970, 53 | continuousFailureCount: 0 54 | ) 55 | 56 | return .success(value: table) 57 | } 58 | 59 | private func handle(response: LCResponse, completion: (LCGenericResult) -> Void) { 60 | let result = self.result(response: response) 61 | 62 | if let table = result.value { 63 | self.table = table 64 | if 65 | let cacheURL = self.tableCacheURL, 66 | let storageContext = self.application.localStorageContext 67 | { 68 | do { 69 | try storageContext.save(table: table, to: cacheURL) 70 | } catch { 71 | Logger.shared.error(error) 72 | } 73 | } 74 | } 75 | 76 | completion(result) 77 | } 78 | 79 | @discardableResult 80 | private func request(completion: @escaping (LCGenericResult) -> Void) -> LCRequest { 81 | guard let routerURL = self.application.appRouter.route(path: "v1/route") else { 82 | return self.application.httpClient.request( 83 | error: LCError.RTMRouterURLNotFound, 84 | completionHandler: completion 85 | ) 86 | } 87 | 88 | let parameters: [String: Any] = [ 89 | "appId": self.application.id!, 90 | "secure": 1 91 | ] 92 | 93 | return self.application.httpClient.request(url: routerURL, method: .get, parameters: parameters) { response in 94 | self.handle(response: response, completion: completion) 95 | } 96 | } 97 | 98 | func route(completion: @escaping (_ direct: Bool, _ result: LCGenericResult) -> Void) { 99 | if let table = self.table { 100 | if table.shouldClear { 101 | self.clearTableCache() 102 | self.request { (result) in 103 | completion(false, result) 104 | } 105 | } else { 106 | completion(true, .success(value: table)) 107 | } 108 | } else { 109 | self.request { (result) in 110 | completion(false, result) 111 | } 112 | } 113 | } 114 | 115 | func updateFailureCount(reset: Bool = false) { 116 | if reset { 117 | if self.table?.continuousFailureCount == 0 { 118 | return 119 | } 120 | self.table?.continuousFailureCount = 0 121 | } else { 122 | self.table?.continuousFailureCount += 1 123 | } 124 | if 125 | let table = self.table, 126 | let cacheURL = self.tableCacheURL, 127 | let storageContext = self.application.localStorageContext 128 | { 129 | do { 130 | try storageContext.save(table: table, to: cacheURL) 131 | } catch { 132 | Logger.shared.error(error) 133 | } 134 | } 135 | } 136 | 137 | func clearTableCache() { 138 | self.table = nil 139 | guard 140 | let cacheURL = self.tableCacheURL, 141 | let storageContext = self.application.localStorageContext 142 | else 143 | { 144 | return 145 | } 146 | do { 147 | try storageContext.clear(file: cacheURL) 148 | } catch { 149 | Logger.shared.error(error) 150 | } 151 | } 152 | 153 | } 154 | 155 | extension RTMRouter { 156 | 157 | struct Table: Codable { 158 | 159 | let primary: String 160 | let secondary: String? 161 | let ttl: TimeInterval 162 | let createdTimestamp: TimeInterval 163 | var continuousFailureCount: Int 164 | 165 | enum CodingKeys: String, CodingKey { 166 | case primary = "server" 167 | case secondary 168 | case ttl 169 | case createdTimestamp = "created_timestamp" 170 | case continuousFailureCount = "continuous_failure_count" 171 | } 172 | 173 | var primaryURL: URL? { 174 | return URL(string: self.primary) 175 | } 176 | 177 | var secondaryURL: URL? { 178 | if let secondary = self.secondary { 179 | return URL(string: secondary) 180 | } else { 181 | return nil 182 | } 183 | } 184 | 185 | var isExpired: Bool { 186 | return Date().timeIntervalSince1970 > self.ttl + self.createdTimestamp 187 | } 188 | 189 | var shouldClear: Bool { 190 | return (self.continuousFailureCount >= 10) || self.isExpired 191 | } 192 | } 193 | 194 | } 195 | 196 | extension LCError { 197 | 198 | static var RTMRouterURLNotFound: LCError { 199 | return LCError( 200 | code: .inconsistency, 201 | reason: "\(RTMRouter.self): URL not found." 202 | ) 203 | } 204 | 205 | static var RTMRouterResponseDataMalformed: LCError { 206 | return LCError( 207 | code: .malformedData, 208 | reason: "\(RTMRouter.self): response data malformed." 209 | ) 210 | } 211 | 212 | } 213 | --------------------------------------------------------------------------------