├── .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 | 
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 |
--------------------------------------------------------------------------------