├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── ---1-report-an-issue.md │ ├── ---2-feature-request.md │ └── config.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .ruby-version ├── .swiftlint.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.resolved ├── Examples ├── LiveQueryDemo-ObjC.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── LiveQueryDemo-ObjC.xcscheme ├── LiveQueryDemo-ObjC │ ├── ChatRoomManager.h │ ├── ChatRoomManager.m │ ├── Info.plist │ ├── Message.h │ ├── Message.m │ ├── Room.h │ ├── Room.m │ └── main.m ├── LiveQueryDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── LiveQueryDemo.xcscheme └── LiveQueryDemo │ ├── Info.plist │ ├── Message.swift │ ├── Room.swift │ └── main.swift ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── PATENTS ├── ParseLiveQuery.podspec ├── ParseLiveQuery.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── README.md ├── Sources ├── ParseLiveQuery-tvOS │ ├── Info.plist │ └── ParseLiveQuery_tvOS.h ├── ParseLiveQuery-watchOS │ ├── Info.plist │ └── ParseLiveQuery_watchOS.h ├── ParseLiveQuery.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── ParseLiveQuery-OSX.xcscheme │ │ ├── ParseLiveQuery-iOS.xcscheme │ │ ├── ParseLiveQuery-tvOS.xcscheme │ │ └── ParseLiveQuery-watchOS.xcscheme └── ParseLiveQuery │ ├── Client.swift │ ├── Info.plist │ ├── Internal │ ├── ClientPrivate.swift │ ├── Errors.swift │ ├── Operation.swift │ └── QueryEncoder.swift │ ├── ObjCCompat.swift │ ├── PFQuery+Subscribe.swift │ ├── Parse+LiveQuery.swift │ └── Subscription.swift └── jazzy.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | macos: 3 | xcode: "11.7.0" 4 | shell: /bin/bash --login -eo pipefail 5 | aliases: 6 | - &cache-pull 7 | keys: 8 | - gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }} 9 | - gem-cache-v1-{{ arch }}-{{ .Branch }} 10 | - gem-cache-v1 11 | - &cache-push 12 | key: gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "Gemfile.lock" }} 13 | paths: 14 | - vendor/bundle 15 | - &prepare 16 | | 17 | git submodule update --init --recursive 18 | sudo gem install bundler 19 | bundle config set path 'vendor/bundle' 20 | bundle install 21 | - &filter-only-main 22 | branches: 23 | only: 24 | - main 25 | 26 | version: 2 27 | jobs: 28 | carthage: 29 | <<: *defaults 30 | steps: 31 | - checkout 32 | - restore_cache: *cache-pull 33 | - run: *prepare 34 | - save_cache: *cache-push 35 | - run: carthage build --no-skip-current --platform macos,ios,tvos,watchos 36 | 37 | workflows: 38 | version: 2 39 | pr: 40 | jobs: 41 | - carthage 42 | nightly: 43 | jobs: 44 | - carthage 45 | triggers: 46 | - schedule: 47 | cron: "0 1 * * *" 48 | filters: *filter-only-main 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---1-report-an-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Report an issue" 3 | about: A feature is not working as expected. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### New Issue Checklist 11 | 16 | 17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/security/policy). 18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/main/SUPPORT.md). 19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/issues?q=is%3Aissue). 20 | - [ ] I can reproduce the issue with the latest version of [Parse Server](https://github.com/parse-community/parse-server/releases) and the [Parse LiveQuery ObjC SDK](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/releases). 21 | 22 | ### Issue Description 23 | 24 | 25 | ### Steps to reproduce 26 | 27 | 28 | ### Actual Outcome 29 | 30 | 31 | ### Expected Outcome 32 | 33 | 34 | ### Environment 35 | 36 | 37 | Parse LiveQuery ObjC SDK 38 | - SDK version: `FILL_THIS_OUT` 39 | - Operating system version: `FILL_THIS_OUT` 40 | 41 | Server 42 | - Parse Server version: `FILL_THIS_OUT` 43 | - Operating system: `FILL_THIS_OUT` 44 | - Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): `FILL_THIS_OUT` 45 | 46 | Database 47 | - System (MongoDB or Postgres): `FILL_THIS_OUT` 48 | - Database version: `FILL_THIS_OUT` 49 | - Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): `FILL_THIS_OUT` 50 | 51 | ### Logs 52 | 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4A1 Request a feature" 3 | about: Suggest new functionality or an enhancement of existing functionality. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### New Feature / Enhancement Checklist 11 | 16 | 17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/security/policy). 18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/main/SUPPORT.md). 19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/issues?q=is%3Aissue). 20 | 21 | ### Current Limitation 22 | 23 | 24 | ### Feature / Enhancement Description 25 | 26 | 27 | ### Example Use Case 28 | 29 | 30 | ### Alternatives / Workarounds 31 | 32 | 33 | ### 3rd Party References 34 | 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 🙋🏽‍♀️ Getting help with code 4 | url: https://stackoverflow.com/questions/tagged/parse-platform 5 | about: Get help with code-level questions on Stack Overflow. 6 | - name: 🙋 Getting general help 7 | url: https://community.parseplatform.org 8 | about: Get help with other questions on our Community Forum. 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### New Pull Request Checklist 2 | 7 | 8 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/blob/master/SECURITY.md). 9 | - [ ] I am creating this PR in reference to an [issue](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/issues?q=is%3Aissue). 10 | 11 | ### Issue Description 12 | 13 | 14 | Related issue: #`FILL_THIS_OUT` 15 | 16 | ### Approach 17 | 18 | 19 | ### TODOs before merging 20 | 24 | 25 | - [ ] Add tests 26 | - [ ] Add changes to documentation (guides, repository pages, in-code descriptions) 27 | - [ ] Add changelog entry 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | env: 8 | CI_XCODE_VER: '/Applications/Xcode_11.7.app/Contents/Developer' 9 | 10 | jobs: 11 | ios: 12 | runs-on: macos-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Submodules 16 | run: | 17 | git submodule update --init --recursive 18 | - name: Build-Test 19 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build -workspace ParseLiveQuery.xcworkspace -sdk iphonesimulator -scheme ParseLiveQuery-iOS -configuration Debug -destination "platform=iOS Simulator,name=iPhone 11" GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c 20 | - name: Send codecov 21 | run: bash <(curl https://codecov.io/bash) 22 | 23 | macos: 24 | runs-on: macos-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Submodules 28 | run: | 29 | git submodule update --init --recursive 30 | - name: Build-Test 31 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build -workspace ParseLiveQuery.xcworkspace -sdk macosx -scheme ParseLiveQuery-OSX -configuration Debug GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c 32 | - name: Send codecov 33 | run: bash <(curl https://codecov.io/bash) 34 | 35 | tvos: 36 | runs-on: macos-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Submodules 40 | run: | 41 | git submodule update --init --recursive 42 | - name: Build-Test 43 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build -workspace ParseLiveQuery.xcworkspace -scheme ParseLiveQuery-tvOS -configuration Debug GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c 44 | - name: Send codecov 45 | run: bash <(curl https://codecov.io/bash) 46 | 47 | watchos: 48 | runs-on: macos-latest 49 | steps: 50 | - uses: actions/checkout@v2 51 | - name: Submodules 52 | run: | 53 | git submodule update --init --recursive 54 | - name: Build 55 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build -workspace ParseLiveQuery.xcworkspace -scheme ParseLiveQuery-watchOS -configuration Debug GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c 56 | - name: Send codecov 57 | run: bash <(curl https://codecov.io/bash) 58 | 59 | demo-swift: 60 | needs: ios 61 | runs-on: macos-latest 62 | steps: 63 | - uses: actions/checkout@v2 64 | - name: Submodules 65 | run: | 66 | git submodule update --init --recursive 67 | - name: Clean 68 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild clean -workspace ParseLiveQuery.xcworkspace -scheme LiveQueryDemo | xcpretty -c 69 | - name: Build 70 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build -workspace ParseLiveQuery.xcworkspace -scheme LiveQueryDemo -configuration Debug | xcpretty -c 71 | - name: Send codecov 72 | run: bash <(curl https://codecov.io/bash) 73 | 74 | demo-objective-c: 75 | needs: ios 76 | runs-on: macos-latest 77 | steps: 78 | - uses: actions/checkout@v2 79 | - name: Submodules 80 | run: | 81 | git submodule update --init --recursive 82 | - name: Clean 83 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild clean -workspace ParseLiveQuery.xcworkspace -scheme LiveQueryDemo-ObjC | xcpretty -c 84 | - name: Build 85 | run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild build -workspace ParseLiveQuery.xcworkspace -scheme LiveQueryDemo-ObjC -configuration Debug | xcpretty -c 86 | - name: Send codecov 87 | run: bash <(curl https://codecov.io/bash) 88 | 89 | cocoapods: 90 | runs-on: macos-latest 91 | steps: 92 | - uses: actions/checkout@v2 93 | - name: CocoaPods 94 | run: set -o pipefail && env NSUnbufferedIO=YES pod lib lint --allow-warnings --verbose 95 | 96 | docs: 97 | needs: ios 98 | runs-on: macos-latest 99 | steps: 100 | - uses: actions/checkout@v2 101 | - name: Cache Gems 102 | id: cache-gems 103 | uses: actions/cache@v2 104 | with: 105 | path: vendor/bundle 106 | key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} 107 | restore-keys: | 108 | ${{ runner.os }}-gem- 109 | - name: Install Bundle 110 | run: | 111 | bundle config path vendor/bundle 112 | bundle install 113 | - name: Create Jazzy Docs 114 | run: | 115 | ./jazzy.sh 116 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | release: 4 | types: [published] 5 | env: 6 | CI_XCODE_VER: '/Applications/Xcode_11.7.app/Contents/Developer' 7 | 8 | jobs: 9 | cocoapods: 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: CocoaPods 15 | run: set -o pipefail && env NSUnbufferedIO=YES pod lib lint --allow-warnings --verbose 16 | - name: Deploy CocoaPods 17 | run: set -o pipefail && env NSUnbufferedIO=YES pod trunk push ParseLiveQuery.podspec --allow-warnings --verbose 18 | env: 19 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} 20 | 21 | docs: 22 | runs-on: macos-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Cache Gems 26 | id: cache-gems 27 | uses: actions/cache@v2 28 | with: 29 | path: vendor/bundle 30 | key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-gem- 33 | - name: Install Bundle 34 | run: | 35 | bundle config path vendor/bundle 36 | bundle install 37 | - name: Create Jazzy Docs 38 | run: | 39 | ./jazzy.sh 40 | - name: Deploy Jazzy Docs 41 | uses: peaceiris/actions-gh-pages@v3 42 | with: 43 | github_token: ${{ secrets.GITHUB_TOKEN }} 44 | publish_dir: ./docs 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,objective-c,xcode 2 | 3 | ### OSX ### 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | 31 | ### Objective-C ### 32 | # Xcode 33 | # 34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 35 | 36 | ## Build generated 37 | build/ 38 | DerivedData 39 | docs/ 40 | 41 | ## Various settings 42 | *.pbxuser 43 | !default.pbxuser 44 | *.mode1v3 45 | !default.mode1v3 46 | *.mode2v3 47 | !default.mode2v3 48 | *.perspectivev3 49 | !default.perspectivev3 50 | xcuserdata 51 | 52 | ## Other 53 | *.xccheckout 54 | *.moved-aside 55 | *.xcuserstate 56 | *.xcscmblueprint 57 | 58 | ## Obj-C/Swift specific 59 | *.hmap 60 | *.ipa 61 | 62 | # CocoaPods 63 | # 64 | # We recommend against adding the Pods directory to your .gitignore. However 65 | # you should judge for yourself, the pros and cons are mentioned at: 66 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 67 | # 68 | Pods/ 69 | 70 | # Carthage 71 | # 72 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 73 | # Carthage/Checkouts 74 | 75 | Carthage/Build 76 | 77 | ### Objective-C Patch ### 78 | *.xcscmblueprint 79 | 80 | 81 | ### Xcode ### 82 | # Xcode 83 | # 84 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 85 | 86 | ## Build generated 87 | build/ 88 | DerivedData 89 | 90 | ## Various settings 91 | *.pbxuser 92 | !default.pbxuser 93 | *.mode1v3 94 | !default.mode1v3 95 | *.mode2v3 96 | !default.mode2v3 97 | *.perspectivev3 98 | !default.perspectivev3 99 | xcuserdata 100 | 101 | ## Other 102 | *.xccheckout 103 | *.moved-aside 104 | *.xcuserstate 105 | 106 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/Bolts-Swift"] 2 | path = Carthage/Checkouts/Bolts-Swift 3 | url = https://github.com/BoltsFramework/Bolts-Swift.git 4 | [submodule "Carthage/Checkouts/facebook-objc-sdk"] 5 | path = Carthage/Checkouts/facebook-objc-sdk 6 | url = https://github.com/facebook/facebook-objc-sdk.git 7 | [submodule "Carthage/Checkouts/Starscream"] 8 | path = Carthage/Checkouts/Starscream 9 | url = https://github.com/daltoniam/Starscream.git 10 | [submodule "Carthage/Checkouts/Parse-SDK-iOS-OSX"] 11 | path = Carthage/Checkouts/Parse-SDK-iOS-OSX 12 | url = https://github.com/ParsePlatform/Parse-SDK-iOS-OSX.git 13 | [submodule "Carthage/Checkouts/Bolts-ObjC"] 14 | path = Carthage/Checkouts/Bolts-ObjC 15 | url = https://github.com/BoltsFramework/Bolts-ObjC.git 16 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.6 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | line_length: 140 2 | file_length: 1000 3 | type_body_length: 500 4 | opt_in_rules: 5 | - empty_count 6 | disabled_rules: 7 | - cyclomatic_complexity 8 | excluded: 9 | - Pods 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## ParseLiveQuery-iOS-OSX Changelog 2 | 3 | ### Main 4 | 5 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.8.1...main) 6 | 7 | ### 2.8.1 8 | 9 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.8.0...2.8.1) 10 | 11 | - Add missing WebSocketEvent cases ([#250](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/250)), thanks to [tmg-mlyons](https://github.com/tmg-mlyons). 12 | - Move Event outside of ObjCCompat to work around a compiler issue ([#245](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/245)), thanks to [Frédéric Maquin](https://github.com/ephread). 13 | - Allows for nested inQuery and orQuery ([#240](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/240)), thanks to [Jonathan Lott](https://github.com/jlott1). 14 | 15 | ### 2.8.0 16 | 17 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.7.3...2.8.0) 18 | 19 | - Bump Starscream dependency to >= 4.0.4 ([#236](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/236)), thanks to [Corey Baker](https://github.com/cbaker6). 20 | - Bump Parse SDK to v.1.19.1 ([#236](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/236)), thanks to [Corey Baker](https://github.com/cbaker6). 21 | - Minimum support due to Xcode 12 and dependencies (iOS 9) ([#236](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/236)), thanks to [Corey Baker](https://github.com/cbaker6). 22 | 23 | ### 2.7.3 24 | 25 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.7.2...2.7.3) 26 | 27 | - Add watchOS and tvOS support ([#235](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/235)), thanks to [Corey Baker](https://github.com/cbaker6) 28 | - Add caching to CI for faster builds ([#235](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/235)), thanks to [Corey Baker](https://github.com/cbaker6) 29 | - Update project and podspecs with respect to dependencies ([#235](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/235)), thanks to [Corey Baker](https://github.com/cbaker6) 30 | - Update jazzy docs to newer version ([#235](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/235)), thanks to [Corey Baker](https://github.com/cbaker6) 31 | - Minimum support due to dependencies (iOS 8, macOS 10.10, tvOS 10, watchOS 2.0) ([#235](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/235)), thanks to [Corey Baker](https://github.com/cbaker6) 32 | 33 | ### 2.7.2 34 | 35 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.7.1...2.7.2) 36 | 37 | - Bump Parse SDK to v.1.19.0 ([#229](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/229)), thanks to [Tom Fox](https://github.com/TomWFox). 38 | 39 | ### 2.7.1 40 | 41 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.7.0...2.7.1) 42 | 43 | - Bump Parse SDK to v1.18.0 44 | ([#226](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/226)), thanks to [Nathan Kellert](https://github.com/noobs2ninjas). 45 | 46 | - Remove Bolts-ObjC as dependency 47 | ([#223](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/223)), thanks to [Joe Szymanski](https://github.com/JoeSzymanski). 48 | 49 | ### 2.7.0 50 | 51 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.6.1...2.7.0) 52 | 53 | - Build compatibility with Xcode 11 and iOS 13 ([#210](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/210)), thanks to [Nathan Kellert](https://github.com/noobs2ninjas) & [Darren Black](https://github.com/drdaz). 54 | - Bump Parse SDK to v1.17.3 ([#210](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/210)), thanks to [Nathan Kellert](https://github.com/noobs2ninjas) & [Darren Black](https://github.com/drdaz). 55 | - Moves to Swift 5 ([#210](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/210)), thanks to [Nathan Kellert](https://github.com/noobs2ninjas) & [Darren Black](https://github.com/drdaz). 56 | - Bump Bolts-Swift dependency to v1.5.0 ([#210](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/210)), thanks to [Nathan Kellert](https://github.com/noobs2ninjas) & [Darren Black](https://github.com/drdaz). 57 | - Properly encode Array of GeoPoints ([#208](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/208)), thanks to [Diamond Lewis](https://github.com/dplewis). 58 | 59 | ### 2.6.1 60 | 61 | [Full Changelog](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/compare/2.6.0...2.6.1) 62 | 63 | **This will be the final release for Swift 4.2** 64 | 65 | - Fix #190, thanks to [rostopira](https://github.com/rostopira). 66 | - Bumps Parse SDK to 1.17.1. 67 | - Bumps Starscream to 3.0.5. 68 | 69 | ### 2.6.0 70 | 71 | - Fixed issue where no "where" property sent when no constraints where added to a query. This is required by the LiveQuery protocol. 72 | - Support for `.or` queries. Fixes #156, #47, and #85. Allows orQuery to be encoded without throwing, thanks to [dblythy](https://github.com/dblythy). 73 | - Added @objc to compile with objective-c ([#184](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/184)), thanks to [Junya Yamaguchi](https://github.com/junya100). 74 | - Encode Date object with __type: Date ([#186](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/pull/186)), thanks to [anafang](https://github.com/ananfang). 75 | 76 | ### 2.5.0 77 | 78 | - Bumps Bolts-Swift to 1.4.0. 79 | - Bumps Swift version to 4.2. 80 | 81 | ### 2.4.0 82 | 83 | - Bumps Parse SDK to 1.17.0. 84 | - Bumps cocoapods to 1.4.0. 85 | - Set Swift version to 3.2. 86 | 87 | ### 2.3.0 88 | 89 | - Bumps Parse SDK to 1.16.0. 90 | - Bumps Starscream to 3.0.4. 91 | - Fixes warnings in Swift 4. 92 | 93 | ### 2.2.3 94 | 95 | - Bumps Parse SDK to 1.15.4 and Bolts to 1.9.0, thanks to [marcgovi](https://github.com/marcgovi). 96 | - Updates logging strategy for websockets, thanks to [Joe Szymanski](https://github.com/JoeSzymanski). 97 | - Ensures unsubscribed queries are removed from subscriptions list, thanks to [Joe Szymanski](https://github.com/JoeSzymanski). 98 | - Do not attempt to reconnect if a connection is already in progress, thanks to [Joe Szymanski](https://github.com/JoeSzymanski). 99 | 100 | ### 2.2.2 101 | 102 | - Adds ability to set the clientKey on the connect message, thanks to [bryandel](https://github.com/bryandel). 103 | - Adds ability to silence the logs, thanks to [ananfang](https://github.com/ananfang). 104 | - Ensures that `wss` URL's are properly handled, thanks to [Joe Szymanski](https://github.com/JoeSzymanski). 105 | 106 | ### 2.0.0 107 | 108 | - Full carthage support, thanks to [David Starke](https://github.com/dstarke). 109 | 110 | **Deprecates usage of SocketRocket in favour of Starscream** 111 | 112 | - Adds support for sessionToken and user authentication, thanks to [Andrew Gordeev](https://github.com/andrew8712). 113 | - Adds support for tvOS, thanks to [Kurt (kajensen)](https://github.com/kajensen). 114 | - Adds support for updating subscription, thanks to [Florent Vilmart](https://github.com/flovilmart). 115 | - Fixes for object decoding. 116 | 117 | ### 1.1.0 118 | 119 | - Breaking change: Swift 3 support. 120 | - Breaking change: OSX deployment target to 10.10. 121 | - New: Carthage support, thanks to [Florent Vilmart](https://github.com/flovilmart). 122 | - New: Supports PFGeoPoints, thanks to [Nikita Lutsenko](https://github.com/nlutsenko). 123 | - Fix: Deduplicates subscription requests, thanks to [Nathan Kellert](https://github.com/noobs2ninjas). 124 | - New: Support for wss, thanks to [@kajensen](https://github.com/kajensen). 125 | - Fix: Properly deliver events back to obj-c, thanks to [Richard Ross](https://github.com/richardjrossiii). 126 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at codeofconduct@parseplatform.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Parse LiveQueryClient for iOS/OS X 2 | We want to make contributing to this project as easy and transparent as possible. 3 | 4 | ## Code of Conduct 5 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. 6 | 7 | ## Our Development Process 8 | Most of our work will be done in public directly on GitHub. There may be changes done through our internal source control, but it will be rare and only as needed. 9 | 10 | ### `master` is unsafe 11 | Our goal is to keep `master` stable, but there may be changes that your application may not be compatible with. We'll do our best to publicize any breaking changes, but try to use our specific releases in any production environment. 12 | 13 | ### Pull Requests 14 | We actively welcome your pull requests. When we get one, we'll run some Parse-specific integration tests on it first. From here, we'll need to get a core member to sign off on the changes and then merge the pull request. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process. 15 | 16 | 1. Fork the repo and create your branch from `master`. 17 | 4. Add unit tests for any new code you add. 18 | 3. If you've changed APIs, update the documentation. 19 | 4. Ensure the test suite passes. 20 | 5. Make sure your code lints. 21 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 22 | 23 | ### Contributor License Agreement ("CLA") 24 | In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. 25 | 26 | Complete your CLA here: 27 | 28 | ## Bugs 29 | Although we try to keep developing on Parse easy, you still may run into some issues. General questions should be asked on [Google Groups][google-group], technical questions should be asked on [Stack Overflow][stack-overflow], and for everything else we'll be using GitHub issues. 30 | 31 | ### Known Issues 32 | We use GitHub issues to track public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new issue, try to make sure your problem doesn't already exist. 33 | 34 | ### Reporting New Issues 35 | 36 | Details are key. The more information you provide us the easier it'll be for us to debug and the faster you'll receive a fix. Some examples of useful tidbits: 37 | 38 | * A description. What did you expect to happen and what actually happened? Why do you think that was wrong? 39 | * A simple unit test that fails. You can submit a pull request with your failing unit test so that our CI verifies that the test fails. 40 | * What version does this reproduce on? What version did it last work on? 41 | * [Stacktrace or GTFO][stacktrace-or-gtfo]. In all honesty, full stacktraces with line numbers make a happy developer. 42 | * Anything else you find relevant. 43 | 44 | 45 | ### Security Bugs 46 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue. 47 | 48 | ## Style Guide 49 | We're still working on providing a code style for your IDE and getting a linter on GitHub, but for now try to keep the following: 50 | 51 | * Most importantly, match the existing code style as much as possible. 52 | * Try to keep lines under 120 characters, if possible. 53 | 54 | ## License 55 | By contributing to Parse Live Query, you agree that your contributions will be licensed under its license. 56 | 57 | [google-group]: https://groups.google.com/forum/#!forum/parse-developers 58 | [stack-overflow]: http://stackoverflow.com/tags/parse.com 59 | [rest-api]: https://www.parse.com/docs/rest/guide 60 | [parse-api-console]: http://blog.parse.com/announcements/introducing-the-parse-api-console/ 61 | [stacktrace-or-gtfo]: http://i.imgur.com/jacoj.jpg 62 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "BoltsFramework/Bolts-Swift" >= 1.5.0 2 | github "ParsePlatform/Parse-SDK-iOS-OSX" >= 1.19.1 3 | github "daltoniam/Starscream" >= 4.0.4 4 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "BoltsFramework/Bolts-ObjC" "1.9.1" 2 | github "BoltsFramework/Bolts-Swift" "1.5.0" 3 | github "ParsePlatform/Parse-SDK-iOS-OSX" "1.19.1" 4 | github "daltoniam/Starscream" "4.0.4" 5 | github "facebook/facebook-objc-sdk" "v6.5.2" 6 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F509D5461CA9E5B8007B15B0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBB41CA9CA04005295C0 /* main.m */; }; 11 | F509D5471CA9E5B8007B15B0 /* Message.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBBC1CA9CA2B005295C0 /* Message.m */; }; 12 | F509D5481CA9E5B8007B15B0 /* Room.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBBF1CA9CA34005295C0 /* Room.m */; }; 13 | F509D5491CA9E5B8007B15B0 /* ChatRoomManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBCF1CA9CC4D005295C0 /* ChatRoomManager.m */; }; 14 | F570348B1CAC837800EB3067 /* ParseLiveQuery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F519CBCA1CA9CB31005295C0 /* ParseLiveQuery.framework */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | F519CBC71CA9CB31005295C0 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = F519CBC21CA9CB31005295C0 /* ParseLiveQuery.xcodeproj */; 21 | proxyType = 2; 22 | remoteGlobalIDString = F5A9BFCA1BE0248D00E78326; 23 | remoteInfo = "ParseLiveQuery-iOS"; 24 | }; 25 | F519CBC91CA9CB31005295C0 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = F519CBC21CA9CB31005295C0 /* ParseLiveQuery.xcodeproj */; 28 | proxyType = 2; 29 | remoteGlobalIDString = F5903CEA1BD999C500C3EFFE; 30 | remoteInfo = "ParseLiveQuery-OSX"; 31 | }; 32 | F57034871CAC836900EB3067 /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = F519CBC21CA9CB31005295C0 /* ParseLiveQuery.xcodeproj */; 35 | proxyType = 1; 36 | remoteGlobalIDString = F5903CE91BD999C500C3EFFE; 37 | remoteInfo = "ParseLiveQuery-OSX"; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 46BB59B59BEB1C1B30D1528A /* Pods-LiveQueryDemo-ObjC.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LiveQueryDemo-ObjC.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-LiveQueryDemo-ObjC/Pods-LiveQueryDemo-ObjC.debug.xcconfig"; sourceTree = ""; }; 43 | B8FE714A9B78962AE71BE6E5 /* Pods_LiveQueryDemo_ObjC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LiveQueryDemo_ObjC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | BBA908F914DAEEBB466454E2 /* Pods-LiveQueryDemo-ObjC.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LiveQueryDemo-ObjC.release.xcconfig"; path = "../Pods/Target Support Files/Pods-LiveQueryDemo-ObjC/Pods-LiveQueryDemo-ObjC.release.xcconfig"; sourceTree = ""; }; 45 | F509D5321CA9E597007B15B0 /* LiveQueryDemo-ObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LiveQueryDemo-ObjC.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | F509D5441CA9E5AF007B15B0 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | F519CBB41CA9CA04005295C0 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | F519CBBB1CA9CA2B005295C0 /* Message.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Message.h; sourceTree = ""; }; 49 | F519CBBC1CA9CA2B005295C0 /* Message.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Message.m; sourceTree = ""; }; 50 | F519CBBE1CA9CA34005295C0 /* Room.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Room.h; sourceTree = ""; }; 51 | F519CBBF1CA9CA34005295C0 /* Room.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Room.m; sourceTree = ""; }; 52 | F519CBC21CA9CB31005295C0 /* ParseLiveQuery.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParseLiveQuery.xcodeproj; path = ../Sources/ParseLiveQuery.xcodeproj; sourceTree = ""; }; 53 | F519CBCE1CA9CC4D005295C0 /* ChatRoomManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChatRoomManager.h; sourceTree = ""; }; 54 | F519CBCF1CA9CC4D005295C0 /* ChatRoomManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChatRoomManager.m; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | F509D52F1CA9E597007B15B0 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | F570348B1CAC837800EB3067 /* ParseLiveQuery.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | E0B5CD933BEBE8518E7750F2 /* Frameworks */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | F519CBC21CA9CB31005295C0 /* ParseLiveQuery.xcodeproj */, 73 | B8FE714A9B78962AE71BE6E5 /* Pods_LiveQueryDemo_ObjC.framework */, 74 | ); 75 | name = Frameworks; 76 | sourceTree = ""; 77 | }; 78 | F458A68D8FC5F1A206FE6787 /* Pods */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 46BB59B59BEB1C1B30D1528A /* Pods-LiveQueryDemo-ObjC.debug.xcconfig */, 82 | BBA908F914DAEEBB466454E2 /* Pods-LiveQueryDemo-ObjC.release.xcconfig */, 83 | ); 84 | name = Pods; 85 | sourceTree = ""; 86 | }; 87 | F519CBA81CA9CA04005295C0 = { 88 | isa = PBXGroup; 89 | children = ( 90 | F519CBB31CA9CA04005295C0 /* LiveQueryDemo-ObjC */, 91 | F519CBB21CA9CA04005295C0 /* Products */, 92 | F458A68D8FC5F1A206FE6787 /* Pods */, 93 | E0B5CD933BEBE8518E7750F2 /* Frameworks */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | F519CBB21CA9CA04005295C0 /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | F509D5321CA9E597007B15B0 /* LiveQueryDemo-ObjC.app */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | F519CBB31CA9CA04005295C0 /* LiveQueryDemo-ObjC */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | F509D5441CA9E5AF007B15B0 /* Info.plist */, 109 | F519CBB41CA9CA04005295C0 /* main.m */, 110 | F519CBBB1CA9CA2B005295C0 /* Message.h */, 111 | F519CBBC1CA9CA2B005295C0 /* Message.m */, 112 | F519CBBE1CA9CA34005295C0 /* Room.h */, 113 | F519CBBF1CA9CA34005295C0 /* Room.m */, 114 | F519CBCE1CA9CC4D005295C0 /* ChatRoomManager.h */, 115 | F519CBCF1CA9CC4D005295C0 /* ChatRoomManager.m */, 116 | ); 117 | path = "LiveQueryDemo-ObjC"; 118 | sourceTree = ""; 119 | }; 120 | F519CBC31CA9CB31005295C0 /* Products */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | F519CBC81CA9CB31005295C0 /* ParseLiveQuery.framework */, 124 | F519CBCA1CA9CB31005295C0 /* ParseLiveQuery.framework */, 125 | ); 126 | name = Products; 127 | sourceTree = ""; 128 | }; 129 | /* End PBXGroup section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | F509D5311CA9E597007B15B0 /* LiveQueryDemo-ObjC */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = F509D5431CA9E597007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo-ObjC" */; 135 | buildPhases = ( 136 | F509D52E1CA9E597007B15B0 /* Sources */, 137 | F509D52F1CA9E597007B15B0 /* Frameworks */, 138 | F509D5301CA9E597007B15B0 /* Resources */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | F57034881CAC836900EB3067 /* PBXTargetDependency */, 144 | ); 145 | name = "LiveQueryDemo-ObjC"; 146 | productName = LiveQueryDemo; 147 | productReference = F509D5321CA9E597007B15B0 /* LiveQueryDemo-ObjC.app */; 148 | productType = "com.apple.product-type.application"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | F519CBA91CA9CA04005295C0 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | LastUpgradeCheck = 1100; 157 | ORGANIZATIONNAME = parse; 158 | TargetAttributes = { 159 | F509D5311CA9E597007B15B0 = { 160 | CreatedOnToolsVersion = 7.3; 161 | }; 162 | }; 163 | }; 164 | buildConfigurationList = F519CBAC1CA9CA04005295C0 /* Build configuration list for PBXProject "LiveQueryDemo-ObjC" */; 165 | compatibilityVersion = "Xcode 3.2"; 166 | developmentRegion = en; 167 | hasScannedForEncodings = 0; 168 | knownRegions = ( 169 | en, 170 | Base, 171 | ); 172 | mainGroup = F519CBA81CA9CA04005295C0; 173 | productRefGroup = F519CBB21CA9CA04005295C0 /* Products */; 174 | projectDirPath = ""; 175 | projectReferences = ( 176 | { 177 | ProductGroup = F519CBC31CA9CB31005295C0 /* Products */; 178 | ProjectRef = F519CBC21CA9CB31005295C0 /* ParseLiveQuery.xcodeproj */; 179 | }, 180 | ); 181 | projectRoot = ""; 182 | targets = ( 183 | F509D5311CA9E597007B15B0 /* LiveQueryDemo-ObjC */, 184 | ); 185 | }; 186 | /* End PBXProject section */ 187 | 188 | /* Begin PBXReferenceProxy section */ 189 | F519CBC81CA9CB31005295C0 /* ParseLiveQuery.framework */ = { 190 | isa = PBXReferenceProxy; 191 | fileType = wrapper.framework; 192 | path = ParseLiveQuery.framework; 193 | remoteRef = F519CBC71CA9CB31005295C0 /* PBXContainerItemProxy */; 194 | sourceTree = BUILT_PRODUCTS_DIR; 195 | }; 196 | F519CBCA1CA9CB31005295C0 /* ParseLiveQuery.framework */ = { 197 | isa = PBXReferenceProxy; 198 | fileType = wrapper.framework; 199 | path = ParseLiveQuery.framework; 200 | remoteRef = F519CBC91CA9CB31005295C0 /* PBXContainerItemProxy */; 201 | sourceTree = BUILT_PRODUCTS_DIR; 202 | }; 203 | /* End PBXReferenceProxy section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | F509D5301CA9E597007B15B0 /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXResourcesBuildPhase section */ 214 | 215 | /* Begin PBXSourcesBuildPhase section */ 216 | F509D52E1CA9E597007B15B0 /* Sources */ = { 217 | isa = PBXSourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | F509D5491CA9E5B8007B15B0 /* ChatRoomManager.m in Sources */, 221 | F509D5481CA9E5B8007B15B0 /* Room.m in Sources */, 222 | F509D5461CA9E5B8007B15B0 /* main.m in Sources */, 223 | F509D5471CA9E5B8007B15B0 /* Message.m in Sources */, 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXSourcesBuildPhase section */ 228 | 229 | /* Begin PBXTargetDependency section */ 230 | F57034881CAC836900EB3067 /* PBXTargetDependency */ = { 231 | isa = PBXTargetDependency; 232 | name = "ParseLiveQuery-OSX"; 233 | targetProxy = F57034871CAC836900EB3067 /* PBXContainerItemProxy */; 234 | }; 235 | /* End PBXTargetDependency section */ 236 | 237 | /* Begin XCBuildConfiguration section */ 238 | F509D5401CA9E597007B15B0 /* Debug */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 242 | CODE_SIGN_IDENTITY = "-"; 243 | COMBINE_HIDPI_IMAGES = YES; 244 | INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo-ObjC/Info.plist"; 245 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 246 | PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo; 247 | PRODUCT_NAME = "$(TARGET_NAME)"; 248 | SWIFT_VERSION = 4.2; 249 | }; 250 | name = Debug; 251 | }; 252 | F509D5411CA9E597007B15B0 /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 256 | CODE_SIGN_IDENTITY = "-"; 257 | COMBINE_HIDPI_IMAGES = YES; 258 | INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo-ObjC/Info.plist"; 259 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 260 | PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo; 261 | PRODUCT_NAME = "$(TARGET_NAME)"; 262 | SWIFT_VERSION = 4.2; 263 | }; 264 | name = Release; 265 | }; 266 | F519CBB61CA9CA04005295C0 /* Debug */ = { 267 | isa = XCBuildConfiguration; 268 | buildSettings = { 269 | ALWAYS_SEARCH_USER_PATHS = NO; 270 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 273 | CLANG_CXX_LIBRARY = "libc++"; 274 | CLANG_ENABLE_MODULES = YES; 275 | CLANG_ENABLE_OBJC_ARC = YES; 276 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 277 | CLANG_WARN_BOOL_CONVERSION = YES; 278 | CLANG_WARN_COMMA = YES; 279 | CLANG_WARN_CONSTANT_CONVERSION = YES; 280 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_EMPTY_BODY = YES; 283 | CLANG_WARN_ENUM_CONVERSION = YES; 284 | CLANG_WARN_INFINITE_RECURSION = YES; 285 | CLANG_WARN_INT_CONVERSION = YES; 286 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 288 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 291 | CLANG_WARN_STRICT_PROTOTYPES = YES; 292 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | CODE_SIGN_IDENTITY = "-"; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = dwarf; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | ENABLE_TESTABILITY = YES; 300 | GCC_C_LANGUAGE_STANDARD = gnu99; 301 | GCC_DYNAMIC_NO_PIC = NO; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_OPTIMIZATION_LEVEL = 0; 304 | GCC_PREPROCESSOR_DEFINITIONS = ( 305 | "DEBUG=1", 306 | "$(inherited)", 307 | ); 308 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 309 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 310 | GCC_WARN_UNDECLARED_SELECTOR = YES; 311 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 312 | GCC_WARN_UNUSED_FUNCTION = YES; 313 | GCC_WARN_UNUSED_VARIABLE = YES; 314 | MACOSX_DEPLOYMENT_TARGET = 10.12; 315 | MTL_ENABLE_DEBUG_INFO = YES; 316 | ONLY_ACTIVE_ARCH = YES; 317 | SDKROOT = macosx; 318 | }; 319 | name = Debug; 320 | }; 321 | F519CBB71CA9CA04005295C0 /* Release */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 326 | CLANG_ANALYZER_NONNULL = YES; 327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 328 | CLANG_CXX_LIBRARY = "libc++"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 332 | CLANG_WARN_BOOL_CONVERSION = YES; 333 | CLANG_WARN_COMMA = YES; 334 | CLANG_WARN_CONSTANT_CONVERSION = YES; 335 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INFINITE_RECURSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 343 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 346 | CLANG_WARN_STRICT_PROTOTYPES = YES; 347 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | CODE_SIGN_IDENTITY = "-"; 351 | COPY_PHASE_STRIP = NO; 352 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 353 | ENABLE_NS_ASSERTIONS = NO; 354 | ENABLE_STRICT_OBJC_MSGSEND = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu99; 356 | GCC_NO_COMMON_BLOCKS = YES; 357 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 358 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 359 | GCC_WARN_UNDECLARED_SELECTOR = YES; 360 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 361 | GCC_WARN_UNUSED_FUNCTION = YES; 362 | GCC_WARN_UNUSED_VARIABLE = YES; 363 | MACOSX_DEPLOYMENT_TARGET = 10.12; 364 | MTL_ENABLE_DEBUG_INFO = NO; 365 | SDKROOT = macosx; 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | F509D5431CA9E597007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo-ObjC" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | F509D5401CA9E597007B15B0 /* Debug */, 376 | F509D5411CA9E597007B15B0 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | F519CBAC1CA9CA04005295C0 /* Build configuration list for PBXProject "LiveQueryDemo-ObjC" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | F519CBB61CA9CA04005295C0 /* Debug */, 385 | F519CBB71CA9CA04005295C0 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | }; 392 | rootObject = F519CBA91CA9CA04005295C0 /* Project object */; 393 | } 394 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC.xcodeproj/xcshareddata/xcschemes/LiveQueryDemo-ObjC.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 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/ChatRoomManager.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | @import Foundation; 11 | @import Parse; 12 | @import ParseLiveQuery; 13 | 14 | #import "Message.h" 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | @class ChatRoomManager; 19 | 20 | @protocol ChatRoomManagerDataSource 21 | 22 | - (PFQuery *)queryForChatRoomManager:(ChatRoomManager *)manager; 23 | - (PFLiveQueryClient *)liveQueryClientForChatRoomManager:(ChatRoomManager *)manager; 24 | 25 | @end 26 | 27 | @protocol ChatRoomManagerDelegate 28 | 29 | - (void)chatRoomManager:(ChatRoomManager *)manager didReceiveMessage:(Message *)message; 30 | 31 | @end 32 | 33 | @interface ChatRoomManager : NSObject 34 | 35 | @property (nonatomic, assign, readonly, getter=isConnected) BOOL connected; 36 | @property (nonatomic, weak, readonly) id dataSource; 37 | @property (nonatomic, weak, readonly) id delegate; 38 | 39 | - (instancetype)initWithDataSource:(id)dataSource delegate:(id)delegate; 40 | 41 | - (void)connect; 42 | - (void)disconnect; 43 | 44 | @end 45 | 46 | NS_ASSUME_NONNULL_END 47 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/ChatRoomManager.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "ChatRoomManager.h" 11 | 12 | @interface ChatRoomManager() 13 | 14 | @property (nonatomic, strong) PFLiveQueryClient *client; 15 | @property (nonatomic, strong) PFQuery *query; 16 | @property (nonatomic, strong) PFLiveQuerySubscription *subscription; 17 | 18 | @end 19 | 20 | @implementation ChatRoomManager 21 | 22 | - (instancetype)initWithDataSource:(id)dataSource delegate:(id)delegate{ 23 | self = [super init]; 24 | if (!self) return self; 25 | 26 | _dataSource = dataSource; 27 | _delegate = delegate; 28 | 29 | return self; 30 | } 31 | 32 | - (BOOL)isConnected { 33 | return self.subscription != nil; 34 | } 35 | 36 | - (void)connect { 37 | self.client = [self.dataSource liveQueryClientForChatRoomManager:self]; 38 | self.query = [self.dataSource queryForChatRoomManager:self]; 39 | 40 | __weak typeof(self) weakSelf = self; 41 | 42 | self.subscription = [[self.client subscribeToQuery:self.query] addCreateHandler:^(PFQuery *query, PFObject *message) { 43 | [weakSelf.delegate chatRoomManager:weakSelf didReceiveMessage:(Message *)message]; 44 | }]; 45 | } 46 | 47 | - (void)disconnect { 48 | self.client = nil; 49 | self.query = nil; 50 | self.subscription = nil; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Parse. All rights reserved. 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/Message.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | @import Parse; 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface Message : PFObject 15 | 16 | @property (nullable, nonatomic, strong) PFUser *author; 17 | @property (nullable, nonatomic, strong) NSString *authorName; 18 | @property (nullable, nonatomic, strong) NSString *message; 19 | @property (nullable, nonatomic, strong) PFObject *room; 20 | @property (nullable, nonatomic, strong) NSString *roomName; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/Message.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "Message.h" 11 | 12 | @implementation Message 13 | 14 | @dynamic author, authorName, message, room, roomName; 15 | 16 | + (NSString *)parseClassName { 17 | return @"Message"; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/Room.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface Room : PFObject 15 | 16 | @property (nullable, nonatomic, strong) NSString *name; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/Room.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "Room.h" 11 | 12 | @implementation Room 13 | 14 | @dynamic name; 15 | 16 | + (NSString *)parseClassName { 17 | return @"Room"; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo-ObjC/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | @import Foundation; 11 | @import Parse; 12 | @import ParseLiveQuery; 13 | 14 | #import "ChatRoomManager.h" 15 | #import "Message.h" 16 | #import "Room.h" 17 | 18 | BFTask *AttemptLogin() { 19 | puts("Enter username: "); 20 | char buffer[1024]; 21 | fgets(buffer, 1024, stdin); 22 | 23 | NSString *username = [NSString stringWithUTF8String:buffer]; 24 | 25 | NSString *prompt = [NSString stringWithFormat:@"Enter password for %@", username]; 26 | NSString *password = [NSString stringWithUTF8String:getpass([prompt UTF8String])]; 27 | 28 | return [[PFUser logInWithUsernameInBackground:username password:password] continueWithBlock:^id (BFTask *task) { 29 | if (task.result) { 30 | return task.result; 31 | } 32 | 33 | puts("Login failed, please try again."); 34 | return AttemptLogin(); 35 | }]; 36 | } 37 | 38 | BFTask *AttemptRoom() { 39 | puts("Enter chat room to connect to: "); 40 | char buffer[1024]; 41 | fgets(buffer, 1024, stdin); 42 | 43 | NSString *roomName = [NSString stringWithUTF8String:buffer]; 44 | 45 | return [[[[Room query] whereKey:@"name" 46 | equalTo:roomName] 47 | getFirstObjectInBackground] 48 | continueWithBlock:^id _Nullable(BFTask * _Nonnull task) { 49 | if (task.result) { 50 | return task.result; 51 | } 52 | 53 | puts("Room not found, please try again."); 54 | return AttemptRoom(); 55 | }]; 56 | } 57 | 58 | @interface ChatRoomHandler : NSObject 59 | 60 | @property (nonatomic, strong, readonly) Room *room; 61 | @property (nonatomic, strong, readonly) PFLiveQueryClient *client; 62 | 63 | @end 64 | 65 | @implementation ChatRoomHandler 66 | 67 | - (instancetype)initWithRoom:(Room *)room { 68 | self = [super init]; 69 | if (!self) return self; 70 | 71 | _room = room; 72 | _client = [[PFLiveQueryClient alloc] init]; 73 | 74 | return self; 75 | } 76 | 77 | - (PFQuery *)queryForChatRoomManager:(ChatRoomManager *)manager { 78 | return [[[Message query] whereKey:@"room_name" 79 | equalTo:self.room.name] 80 | orderByAscending:@"createdAt"]; 81 | } 82 | 83 | - (PFLiveQueryClient *)liveQueryClientForChatRoomManager:(ChatRoomManager *)manager { 84 | return _client; 85 | } 86 | 87 | - (void)chatRoomManager:(ChatRoomManager *)manager didReceiveMessage:(Message *)message { 88 | NSString *formatted = [NSString stringWithFormat:@"%@ %@ %@", message.createdAt, message.authorName, message.message]; 89 | printf("%s\n", formatted.UTF8String); 90 | } 91 | 92 | @end 93 | 94 | int main(int argc, const char * argv[]) { 95 | @autoreleasepool { 96 | [Message registerSubclass]; 97 | [Room registerSubclass]; 98 | 99 | [Parse initializeWithConfiguration:[ParseClientConfiguration configurationWithBlock:^(id configuration) { 100 | configuration.applicationId = @"myAppId"; 101 | configuration.server = @"http://localhost:1337/parse"; 102 | }]]; 103 | 104 | [[AttemptLogin() continueWithBlock:^id (BFTask *task) { 105 | return AttemptRoom(); 106 | }] continueWithBlock:^id (BFTask *task) { 107 | Room *room = task.result; 108 | ChatRoomHandler *handler = [[ChatRoomHandler alloc] initWithRoom:room]; 109 | ChatRoomManager *manager = [[ChatRoomManager alloc] initWithDataSource:handler delegate:handler]; 110 | 111 | // Print out the previous messages 112 | PFQuery *query = [handler queryForChatRoomManager:manager]; 113 | [[query findObjectsInBackground] continueWithBlock:^id (BFTask *task) { 114 | for (Message *message in task.result) { 115 | [handler chatRoomManager:manager didReceiveMessage:message]; 116 | } 117 | 118 | [manager connect]; 119 | return nil; 120 | }]; 121 | 122 | dispatch_io_t stdinChannel = dispatch_io_create(DISPATCH_IO_STREAM, STDIN_FILENO, dispatch_get_main_queue(), ^(int error) { 123 | perror("dispatch_io_create"); 124 | }); 125 | 126 | dispatch_io_set_low_water(stdinChannel, 1); 127 | dispatch_io_read(stdinChannel, 0, SIZE_MAX, dispatch_get_main_queue(), ^(bool done, dispatch_data_t data, int error) { 128 | NSString *messageText = [[[NSString alloc] initWithData:(NSData *)data 129 | encoding:NSUTF8StringEncoding] 130 | stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 131 | 132 | 133 | Message *message = [[Message alloc] init]; 134 | message.author = [PFUser currentUser]; 135 | message.authorName = [PFUser currentUser].username; 136 | message.message = messageText; 137 | message.room = room; 138 | message.roomName = room.name; 139 | 140 | [message saveInBackground]; 141 | }); 142 | 143 | return nil; 144 | }]; 145 | 146 | dispatch_main(); 147 | } 148 | 149 | return 0; 150 | } 151 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F509D5281CA9E4D9007B15B0 /* ParseLiveQuery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8199A4061CA1EF1800BF61E1 /* ParseLiveQuery.framework */; }; 11 | F509D5291CA9E53D007B15B0 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F85B61C9BB4B600566A29 /* Message.swift */; }; 12 | F509D52A1CA9E53D007B15B0 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F85B71C9BB4B600566A29 /* Room.swift */; }; 13 | F509D52B1CA9E53D007B15B0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F85AF1C9BB48200566A29 /* main.swift */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXContainerItemProxy section */ 17 | 8199A4031CA1EF1800BF61E1 /* PBXContainerItemProxy */ = { 18 | isa = PBXContainerItemProxy; 19 | containerPortal = 8199A3FE1CA1EF1800BF61E1 /* ParseLiveQuery.xcodeproj */; 20 | proxyType = 2; 21 | remoteGlobalIDString = F5A9BFCA1BE0248D00E78326; 22 | remoteInfo = "ParseLiveQuery-iOS"; 23 | }; 24 | 8199A4051CA1EF1800BF61E1 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 8199A3FE1CA1EF1800BF61E1 /* ParseLiveQuery.xcodeproj */; 27 | proxyType = 2; 28 | remoteGlobalIDString = F5903CEA1BD999C500C3EFFE; 29 | remoteInfo = "ParseLiveQuery-OSX"; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 3AC9312CEDA0007F8EAA9880 /* Pods-LiveQueryDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LiveQueryDemo.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-LiveQueryDemo/Pods-LiveQueryDemo.debug.xcconfig"; sourceTree = ""; }; 35 | 497772719B97C861F0896BFC /* Pods-LiveQueryDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LiveQueryDemo.release.xcconfig"; path = "../Pods/Target Support Files/Pods-LiveQueryDemo/Pods-LiveQueryDemo.release.xcconfig"; sourceTree = ""; }; 36 | 8199A3FE1CA1EF1800BF61E1 /* ParseLiveQuery.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParseLiveQuery.xcodeproj; path = ../Sources/ParseLiveQuery.xcodeproj; sourceTree = ""; }; 37 | E6A6F02FD57E57309877DA38 /* Pods_LiveQueryDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LiveQueryDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | F509D5171CA9E4AE007B15B0 /* LiveQueryDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveQueryDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | F509D5241CA9E4AE007B15B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | F59F85AF1C9BB48200566A29 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 41 | F59F85B61C9BB4B600566A29 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; 42 | F59F85B71C9BB4B600566A29 /* Room.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = ""; }; 43 | F59F85BC1C9BB66C00566A29 /* ParseLiveQuery.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ParseLiveQuery.framework; path = "../../../../Library/Developer/Xcode/DerivedData/ParseLiveQuery-dstaosuetnlsavabgvfvntbqxpxo/Build/Products/Debug/ParseLiveQuery.framework"; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | F509D5141CA9E4AE007B15B0 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | F509D5281CA9E4D9007B15B0 /* ParseLiveQuery.framework in Frameworks */, 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 2E2DAD338FCB65EC95CB7AA9 /* Frameworks */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 8199A3FE1CA1EF1800BF61E1 /* ParseLiveQuery.xcodeproj */, 62 | F59F85BC1C9BB66C00566A29 /* ParseLiveQuery.framework */, 63 | E6A6F02FD57E57309877DA38 /* Pods_LiveQueryDemo.framework */, 64 | ); 65 | name = Frameworks; 66 | sourceTree = ""; 67 | }; 68 | 8199A3FF1CA1EF1800BF61E1 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 8199A4041CA1EF1800BF61E1 /* ParseLiveQuery.framework */, 72 | 8199A4061CA1EF1800BF61E1 /* ParseLiveQuery.framework */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | B4367C9D9257525F9D28B542 /* Pods */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 3AC9312CEDA0007F8EAA9880 /* Pods-LiveQueryDemo.debug.xcconfig */, 81 | 497772719B97C861F0896BFC /* Pods-LiveQueryDemo.release.xcconfig */, 82 | ); 83 | name = Pods; 84 | sourceTree = ""; 85 | }; 86 | F59F85A31C9BB48200566A29 = { 87 | isa = PBXGroup; 88 | children = ( 89 | F59F85AE1C9BB48200566A29 /* LiveQueryDemo */, 90 | F59F85AD1C9BB48200566A29 /* Products */, 91 | B4367C9D9257525F9D28B542 /* Pods */, 92 | 2E2DAD338FCB65EC95CB7AA9 /* Frameworks */, 93 | ); 94 | indentWidth = 4; 95 | sourceTree = ""; 96 | tabWidth = 4; 97 | }; 98 | F59F85AD1C9BB48200566A29 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | F509D5171CA9E4AE007B15B0 /* LiveQueryDemo.app */, 102 | ); 103 | name = Products; 104 | sourceTree = ""; 105 | }; 106 | F59F85AE1C9BB48200566A29 /* LiveQueryDemo */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | F509D5241CA9E4AE007B15B0 /* Info.plist */, 110 | F59F85B61C9BB4B600566A29 /* Message.swift */, 111 | F59F85B71C9BB4B600566A29 /* Room.swift */, 112 | F59F85AF1C9BB48200566A29 /* main.swift */, 113 | ); 114 | path = LiveQueryDemo; 115 | sourceTree = ""; 116 | }; 117 | /* End PBXGroup section */ 118 | 119 | /* Begin PBXNativeTarget section */ 120 | F509D5161CA9E4AE007B15B0 /* LiveQueryDemo */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = F509D5251CA9E4AE007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo" */; 123 | buildPhases = ( 124 | F509D5131CA9E4AE007B15B0 /* Sources */, 125 | F509D5141CA9E4AE007B15B0 /* Frameworks */, 126 | F509D5151CA9E4AE007B15B0 /* Resources */, 127 | ); 128 | buildRules = ( 129 | ); 130 | dependencies = ( 131 | ); 132 | name = LiveQueryDemo; 133 | productName = AppKitDemo; 134 | productReference = F509D5171CA9E4AE007B15B0 /* LiveQueryDemo.app */; 135 | productType = "com.apple.product-type.application"; 136 | }; 137 | /* End PBXNativeTarget section */ 138 | 139 | /* Begin PBXProject section */ 140 | F59F85A41C9BB48200566A29 /* Project object */ = { 141 | isa = PBXProject; 142 | attributes = { 143 | LastSwiftUpdateCheck = 0720; 144 | LastUpgradeCheck = 1100; 145 | ORGANIZATIONNAME = Parse; 146 | TargetAttributes = { 147 | F509D5161CA9E4AE007B15B0 = { 148 | CreatedOnToolsVersion = 7.3; 149 | LastSwiftMigration = 1120; 150 | }; 151 | }; 152 | }; 153 | buildConfigurationList = F59F85A71C9BB48200566A29 /* Build configuration list for PBXProject "LiveQueryDemo" */; 154 | compatibilityVersion = "Xcode 3.2"; 155 | developmentRegion = en; 156 | hasScannedForEncodings = 0; 157 | knownRegions = ( 158 | en, 159 | Base, 160 | ); 161 | mainGroup = F59F85A31C9BB48200566A29; 162 | productRefGroup = F59F85AD1C9BB48200566A29 /* Products */; 163 | projectDirPath = ""; 164 | projectReferences = ( 165 | { 166 | ProductGroup = 8199A3FF1CA1EF1800BF61E1 /* Products */; 167 | ProjectRef = 8199A3FE1CA1EF1800BF61E1 /* ParseLiveQuery.xcodeproj */; 168 | }, 169 | ); 170 | projectRoot = ""; 171 | targets = ( 172 | F509D5161CA9E4AE007B15B0 /* LiveQueryDemo */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXReferenceProxy section */ 178 | 8199A4041CA1EF1800BF61E1 /* ParseLiveQuery.framework */ = { 179 | isa = PBXReferenceProxy; 180 | fileType = wrapper.framework; 181 | path = ParseLiveQuery.framework; 182 | remoteRef = 8199A4031CA1EF1800BF61E1 /* PBXContainerItemProxy */; 183 | sourceTree = BUILT_PRODUCTS_DIR; 184 | }; 185 | 8199A4061CA1EF1800BF61E1 /* ParseLiveQuery.framework */ = { 186 | isa = PBXReferenceProxy; 187 | fileType = wrapper.framework; 188 | path = ParseLiveQuery.framework; 189 | remoteRef = 8199A4051CA1EF1800BF61E1 /* PBXContainerItemProxy */; 190 | sourceTree = BUILT_PRODUCTS_DIR; 191 | }; 192 | /* End PBXReferenceProxy section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | F509D5151CA9E4AE007B15B0 /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXResourcesBuildPhase section */ 203 | 204 | /* Begin PBXSourcesBuildPhase section */ 205 | F509D5131CA9E4AE007B15B0 /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | F509D52A1CA9E53D007B15B0 /* Room.swift in Sources */, 210 | F509D5291CA9E53D007B15B0 /* Message.swift in Sources */, 211 | F509D52B1CA9E53D007B15B0 /* main.swift in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin XCBuildConfiguration section */ 218 | F509D5261CA9E4AE007B15B0 /* Debug */ = { 219 | isa = XCBuildConfiguration; 220 | buildSettings = { 221 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 222 | CLANG_ANALYZER_NONNULL = YES; 223 | CODE_SIGN_IDENTITY = "-"; 224 | COMBINE_HIDPI_IMAGES = YES; 225 | INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo/Info.plist"; 226 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 227 | MACOSX_DEPLOYMENT_TARGET = 10.12; 228 | PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo; 229 | PRODUCT_NAME = "$(TARGET_NAME)"; 230 | SWIFT_VERSION = 5.0; 231 | }; 232 | name = Debug; 233 | }; 234 | F509D5271CA9E4AE007B15B0 /* Release */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CODE_SIGN_IDENTITY = "-"; 240 | COMBINE_HIDPI_IMAGES = YES; 241 | INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo/Info.plist"; 242 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 243 | MACOSX_DEPLOYMENT_TARGET = 10.12; 244 | PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo; 245 | PRODUCT_NAME = "$(TARGET_NAME)"; 246 | SWIFT_VERSION = 5.0; 247 | }; 248 | name = Release; 249 | }; 250 | F59F85B11C9BB48200566A29 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_COMMA = YES; 262 | CLANG_WARN_CONSTANT_CONVERSION = YES; 263 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INFINITE_RECURSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 274 | CLANG_WARN_STRICT_PROTOTYPES = YES; 275 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | CODE_SIGN_IDENTITY = "-"; 279 | COPY_PHASE_STRIP = NO; 280 | DEBUG_INFORMATION_FORMAT = dwarf; 281 | ENABLE_STRICT_OBJC_MSGSEND = YES; 282 | ENABLE_TESTABILITY = YES; 283 | GCC_C_LANGUAGE_STANDARD = gnu99; 284 | GCC_DYNAMIC_NO_PIC = NO; 285 | GCC_NO_COMMON_BLOCKS = YES; 286 | GCC_OPTIMIZATION_LEVEL = 0; 287 | GCC_PREPROCESSOR_DEFINITIONS = ( 288 | "DEBUG=1", 289 | "$(inherited)", 290 | ); 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 293 | GCC_WARN_UNDECLARED_SELECTOR = YES; 294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 295 | GCC_WARN_UNUSED_FUNCTION = YES; 296 | GCC_WARN_UNUSED_VARIABLE = YES; 297 | MACOSX_DEPLOYMENT_TARGET = 10.10; 298 | MTL_ENABLE_DEBUG_INFO = YES; 299 | ONLY_ACTIVE_ARCH = YES; 300 | SDKROOT = macosx; 301 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 302 | }; 303 | name = Debug; 304 | }; 305 | F59F85B21C9BB48200566A29 /* Release */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | CODE_SIGN_IDENTITY = "-"; 334 | COPY_PHASE_STRIP = NO; 335 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 336 | ENABLE_NS_ASSERTIONS = NO; 337 | ENABLE_STRICT_OBJC_MSGSEND = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_NO_COMMON_BLOCKS = YES; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | MACOSX_DEPLOYMENT_TARGET = 10.10; 347 | MTL_ENABLE_DEBUG_INFO = NO; 348 | SDKROOT = macosx; 349 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 350 | }; 351 | name = Release; 352 | }; 353 | /* End XCBuildConfiguration section */ 354 | 355 | /* Begin XCConfigurationList section */ 356 | F509D5251CA9E4AE007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo" */ = { 357 | isa = XCConfigurationList; 358 | buildConfigurations = ( 359 | F509D5261CA9E4AE007B15B0 /* Debug */, 360 | F509D5271CA9E4AE007B15B0 /* Release */, 361 | ); 362 | defaultConfigurationIsVisible = 0; 363 | defaultConfigurationName = Release; 364 | }; 365 | F59F85A71C9BB48200566A29 /* Build configuration list for PBXProject "LiveQueryDemo" */ = { 366 | isa = XCConfigurationList; 367 | buildConfigurations = ( 368 | F59F85B11C9BB48200566A29 /* Debug */, 369 | F59F85B21C9BB48200566A29 /* Release */, 370 | ); 371 | defaultConfigurationIsVisible = 0; 372 | defaultConfigurationName = Release; 373 | }; 374 | /* End XCConfigurationList section */ 375 | }; 376 | rootObject = F59F85A41C9BB48200566A29 /* Project object */; 377 | } 378 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo.xcodeproj/xcshareddata/xcschemes/LiveQueryDemo.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 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIconFile 15 | 16 | CFBundleIdentifier 17 | $(PRODUCT_BUNDLE_IDENTIFIER) 18 | CFBundleInfoDictionaryVersion 19 | 6.0 20 | CFBundleName 21 | $(PRODUCT_NAME) 22 | CFBundlePackageType 23 | APPL 24 | CFBundleShortVersionString 25 | 1.0 26 | CFBundleSignature 27 | ???? 28 | CFBundleVersion 29 | 1 30 | LSMinimumSystemVersion 31 | $(MACOSX_DEPLOYMENT_TARGET) 32 | NSHumanReadableCopyright 33 | Copyright © 2016 Parse. All rights reserved. 34 | NSPrincipalClass 35 | NSApplication 36 | 37 | 38 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo/Message.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | 13 | class Message: PFObject, PFSubclassing { 14 | @NSManaged var author: PFUser? 15 | @NSManaged var authorName: String? 16 | @NSManaged var message: String? 17 | @NSManaged var room: PFObject? 18 | @NSManaged var roomName: String? 19 | 20 | class func parseClassName() -> String { 21 | return "Message" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo/Room.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | 13 | class Room: PFObject, PFSubclassing { 14 | @NSManaged var name: String? 15 | 16 | static func parseClassName() -> String { 17 | return "Room" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/LiveQueryDemo/main.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | import ParseLiveQuery 13 | 14 | Message.registerSubclass() 15 | Room.registerSubclass() 16 | 17 | Parse.initialize(with: ParseClientConfiguration { 18 | $0.applicationId = "myAppId" 19 | $0.server = "http://localhost:1337/parse" 20 | }) 21 | 22 | let liveQueryClient = ParseLiveQuery.Client() 23 | 24 | class ChatRoomManager { 25 | fileprivate var currentChatRoom: Room? 26 | fileprivate var subscription: Subscription? 27 | 28 | var connected: Bool { return currentChatRoom != nil } 29 | var messagesQuery: PFQuery { 30 | return (Message.query()? 31 | .whereKey("roomName", equalTo: currentChatRoom!.name!) 32 | .order(byAscending: "createdAt")) as! PFQuery 33 | } 34 | 35 | func connectToChatRoom(_ room: String) { 36 | if connected { 37 | disconnectFromChatRoom() 38 | } 39 | 40 | Room.query()?.whereKey("name", equalTo: room).getFirstObjectInBackground() 41 | .continueOnSuccessWith(block: { task -> Any? in 42 | self.currentChatRoom = task.result as? Room 43 | print("Connected to room \(self.currentChatRoom?.name ?? "null")") 44 | 45 | self.printPriorMessages() 46 | self.subscribeToUpdates() 47 | 48 | return nil 49 | }) 50 | } 51 | 52 | func disconnectFromChatRoom() { 53 | liveQueryClient.unsubscribe(messagesQuery, handler: subscription!) 54 | } 55 | 56 | func sendMessage(_ msg: String) { 57 | let message = Message() 58 | message.author = PFUser.current() 59 | message.authorName = message.author?.username 60 | message.message = msg 61 | message.room = currentChatRoom 62 | message.roomName = currentChatRoom?.name 63 | 64 | message.saveInBackground() 65 | } 66 | 67 | func printPriorMessages() { 68 | messagesQuery.findObjectsInBackground() 69 | .continueOnSuccessWith(block: { task -> Any? in 70 | (task.result as? [Message])?.forEach(self.printMessage) 71 | 72 | return nil 73 | }) 74 | } 75 | 76 | func subscribeToUpdates() { 77 | subscription = liveQueryClient 78 | .subscribe(messagesQuery) 79 | .handle(Event.created) { _, message in 80 | self.printMessage(message) 81 | } 82 | } 83 | 84 | fileprivate func printMessage(_ message: Message) { 85 | let createdAt = message.createdAt ?? Date() 86 | 87 | print("\(createdAt) \(message.authorName ?? "unknown"): \(message.message ?? "")") 88 | } 89 | } 90 | 91 | class InputManager { 92 | let stdinChannel = DispatchIO(__type: DispatchIO.StreamType.stream.rawValue, fd: STDIN_FILENO, queue: DispatchQueue.main) { _ in } 93 | let chatManager: ChatRoomManager 94 | 95 | init(chatManager: ChatRoomManager) { 96 | self.chatManager = chatManager 97 | 98 | stdinChannel.setLimit(lowWater: 1) 99 | stdinChannel.read(offset: 0, length: Int.max, queue: DispatchQueue.main, ioHandler: handleInput) 100 | } 101 | 102 | fileprivate func handleInput(_ done: Bool, data: DispatchData?, error: Int32) { 103 | guard 104 | let inputString = data?.withUnsafeBytes(body: {(b: UnsafePointer) -> String? in 105 | return String(cString: b) 106 | })?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) else { 107 | return 108 | } 109 | 110 | if chatManager.connected { 111 | chatManager.sendMessage(inputString) 112 | } else { 113 | chatManager.connectToChatRoom(inputString) 114 | } 115 | } 116 | } 117 | 118 | print("Enter username: ") 119 | 120 | let username = readLine()! 121 | let password = "Enter password for \(username): ".withCString { 122 | String(validatingUTF8: getpass($0))! 123 | } 124 | 125 | let chatManager = ChatRoomManager() 126 | let inputManager = InputManager(chatManager: chatManager) 127 | 128 | PFUser.logInWithUsername(inBackground: username, password: password) 129 | .continueOnSuccessWith(block: { task -> Any? in 130 | print("Enter chat room to connect to: ") 131 | return nil 132 | }) 133 | 134 | dispatchMain() 135 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jazzy', '0.13.1' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.2) 5 | activesupport (5.2.4.4) 6 | concurrent-ruby (~> 1.0, >= 1.0.2) 7 | i18n (>= 0.7, < 2) 8 | minitest (~> 5.1) 9 | tzinfo (~> 1.1) 10 | addressable (2.8.0) 11 | public_suffix (>= 2.0.2, < 5.0) 12 | algoliasearch (1.27.5) 13 | httpclient (~> 2.8, >= 2.8.3) 14 | json (>= 1.5.1) 15 | atomos (0.1.3) 16 | claide (1.0.3) 17 | cocoapods (1.10.0) 18 | addressable (~> 2.6) 19 | claide (>= 1.0.2, < 2.0) 20 | cocoapods-core (= 1.10.0) 21 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 22 | cocoapods-downloader (>= 1.4.0, < 2.0) 23 | cocoapods-plugins (>= 1.0.0, < 2.0) 24 | cocoapods-search (>= 1.0.0, < 2.0) 25 | cocoapods-trunk (>= 1.4.0, < 2.0) 26 | cocoapods-try (>= 1.1.0, < 2.0) 27 | colored2 (~> 3.1) 28 | escape (~> 0.0.4) 29 | fourflusher (>= 2.3.0, < 3.0) 30 | gh_inspector (~> 1.0) 31 | molinillo (~> 0.6.6) 32 | nap (~> 1.0) 33 | ruby-macho (~> 1.4) 34 | xcodeproj (>= 1.19.0, < 2.0) 35 | cocoapods-core (1.10.0) 36 | activesupport (> 5.0, < 6) 37 | addressable (~> 2.6) 38 | algoliasearch (~> 1.0) 39 | concurrent-ruby (~> 1.1) 40 | fuzzy_match (~> 2.0.4) 41 | nap (~> 1.0) 42 | netrc (~> 0.11) 43 | public_suffix 44 | typhoeus (~> 1.0) 45 | cocoapods-deintegrate (1.0.4) 46 | cocoapods-downloader (1.4.0) 47 | cocoapods-plugins (1.0.0) 48 | nap 49 | cocoapods-search (1.0.0) 50 | cocoapods-trunk (1.5.0) 51 | nap (>= 0.8, < 2.0) 52 | netrc (~> 0.11) 53 | cocoapods-try (1.2.0) 54 | colored2 (3.1.2) 55 | concurrent-ruby (1.1.7) 56 | escape (0.0.4) 57 | ethon (0.12.0) 58 | ffi (>= 1.3.0) 59 | ffi (1.13.1) 60 | fourflusher (2.3.1) 61 | fuzzy_match (2.0.4) 62 | gh_inspector (1.1.3) 63 | httpclient (2.8.3) 64 | i18n (1.8.5) 65 | concurrent-ruby (~> 1.0) 66 | jazzy (0.13.1) 67 | cocoapods (~> 1.5) 68 | mustache (~> 1.1) 69 | open4 70 | redcarpet (~> 3.4) 71 | rouge (>= 2.0.6, < 4.0) 72 | sassc (~> 2.1) 73 | sqlite3 (~> 1.3) 74 | xcinvoke (~> 0.3.0) 75 | json (2.3.1) 76 | liferaft (0.0.6) 77 | minitest (5.14.2) 78 | molinillo (0.6.6) 79 | mustache (1.1.1) 80 | nanaimo (0.3.0) 81 | nap (1.1.0) 82 | netrc (0.11.0) 83 | open4 (1.3.4) 84 | public_suffix (4.0.6) 85 | redcarpet (3.5.1) 86 | rouge (3.25.0) 87 | ruby-macho (1.4.0) 88 | sassc (2.4.0) 89 | ffi (~> 1.9) 90 | sqlite3 (1.4.2) 91 | thread_safe (0.3.6) 92 | typhoeus (1.4.0) 93 | ethon (>= 0.9.0) 94 | tzinfo (1.2.8) 95 | thread_safe (~> 0.1) 96 | xcinvoke (0.3.0) 97 | liferaft (~> 0.0.6) 98 | xcodeproj (1.19.0) 99 | CFPropertyList (>= 2.3.3, < 4.0) 100 | atomos (~> 0.1.3) 101 | claide (>= 1.0.2, < 2.0) 102 | colored2 (~> 3.1) 103 | nanaimo (~> 0.3.0) 104 | 105 | PLATFORMS 106 | ruby 107 | 108 | DEPENDENCIES 109 | jazzy (= 0.13.1) 110 | 111 | BUNDLED WITH 112 | 2.1.4 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For Parse LiveQueryClient for iOS/OS X software 4 | 5 | Copyright (c) 2016-present, Parse, LLC. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Parse nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | ----- 33 | 34 | As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code. 35 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the Parse LiveQueryClient for iOS/OS X software distributed by Parse, LLC. 4 | 5 | Parse, LLC. ("Parse") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Parse’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Parse or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Parse or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Parse or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Parse that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | 35 | ----- 36 | 37 | As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code. 38 | -------------------------------------------------------------------------------- /ParseLiveQuery.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'ParseLiveQuery' 3 | s.version = '2.8.1' 4 | s.license = { :type => 'BSD' } 5 | s.summary = 'Allows for subscriptions to queries in conjunction with parse-server.' 6 | s.homepage = 'http://parseplatform.org' 7 | s.social_media_url = 'https://twitter.com/ParsePlatform' 8 | s.authors = { 'Parse Community' => 'info@parseplatform.org', 'Richard Ross' => 'richardross@fb.com', 'Nikita Lutsenko' => 'nlutsenko@me.com', 'Florent Vilmart' => 'florent@flovilmart.com' } 9 | 10 | s.source = { :git => 'https://github.com/ParsePlatform/ParseLiveQuery-iOS-OSX.git', :tag => s.version.to_s } 11 | 12 | s.requires_arc = true 13 | 14 | s.platform = :ios, :osx, :tvos, :watchos 15 | s.swift_version = '5.0' 16 | s.cocoapods_version = '>= 1.4' 17 | 18 | s.ios.deployment_target = '9.0' 19 | s.tvos.deployment_target = '10.0' 20 | s.watchos.deployment_target = '2.0' 21 | s.osx.deployment_target = '10.10' 22 | 23 | s.source_files = 'Sources/ParseLiveQuery/**/*.{swift,h}' 24 | s.module_name = 'ParseLiveQuery' 25 | 26 | s.dependency 'Parse', '~> 1.19.0' 27 | s.dependency 'Bolts-Swift', '~> 1.5.0' 28 | s.dependency 'Starscream', '~> 4.0.4' 29 | 30 | end 31 | -------------------------------------------------------------------------------- /ParseLiveQuery.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 10 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ParseLiveQuery.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ParseLiveQuery.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parse LiveQuery Client for iOS/OSX 2 | 3 | --- 4 | 5 | ### ⚠️ The LiveQuery feature has been added as a module to the [Parse Apple SDK](https://github.com/parse-community/Parse-SDK-iOS-OSX). This repository is archived and will no longer receive any updates. 6 | 7 | --- 8 | 9 | [![Platforms][platforms-svg]][platforms-link] 10 | [![Carthage compatible][carthage-svg]][carthage-link] 11 | [![Podspec][podspec-svg]][podspec-link] 12 | [![License][license-svg]][license-link] 13 | ![ci](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/workflows/ci/badge.svg?branch=main) 14 | ![release](https://github.com/parse-community/ParseLiveQuery-iOS-OSX/workflows/release/badge.svg) 15 | [![Build Status][circleci-status-svg]][circleci-status-link] 16 | [![Join The Conversation](https://img.shields.io/discourse/https/community.parseplatform.org/topics.svg)](https://community.parseplatform.org/c/parse-server) 17 | [![Backers on Open Collective](https://opencollective.com/parse-server/backers/badge.svg)](#backers) 18 | [![Sponsors on Open Collective](https://opencollective.com/parse-server/sponsors/badge.svg)](#sponsors) 19 | ![Twitter Follow](https://img.shields.io/twitter/follow/ParsePlatform.svg?label=Follow%20us%20on%20Twitter&style=social) 20 | 21 | `PFQuery` is one of the key concepts for Parse. It allows you to retrieve `PFObject`s by specifying some conditions, making it easy to build apps such as a dashboard, a todo list or even some strategy games. However, `PFQuery` is based on a pull model, which is not suitable for apps that need real-time support. 22 | 23 | Suppose you are building an app that allows multiple users to edit the same file at the same time. `PFQuery` would not be an ideal tool since you can not know when to query from the server to get the updates. 24 | 25 | To solve this problem, we introduce Parse LiveQuery. This tool allows you to subscribe to a `PFQuery` you are interested in. Once subscribed, the server will notify clients whenever a `PFObject` that matches the `PFQuery` is created or updated, in real-time. 26 | 27 | ## Setup Server 28 | 29 | Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients. In order to use live queries, you need to set up both of them. 30 | 31 | The easiest way to setup the LiveQuery server is to make it run with the [Open Source Parse Server](https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery#server-setup). 32 | 33 | ## Install Client 34 | 35 | ### Cocoapods 36 | 37 | You can install the LiveQuery client via including it in your Podfile: 38 | 39 | pod 'ParseLiveQuery' 40 | 41 | 42 | ## Use Client 43 | 44 | The LiveQuery client interface is based around the concept of `Subscription`s. You can register any `PFQuery` for live updates from the associated live query server, by simply calling `subscribe()` on a query: 45 | ```swift 46 | let myQuery = Message.query()!.where(....) 47 | let subscription: Subscription = Client.shared.subscribe(myQuery) 48 | ``` 49 | 50 | Where `Message` is a registered subclass of PFObject. 51 | 52 | Once you've subscribed to a query, you can `handle` events on them, like so: 53 | ```swift 54 | subscription.handleEvent { query, event in 55 | // Handle event 56 | } 57 | ``` 58 | 59 | You can also handle a single type of event, if that's all you're interested in: 60 | ```swift 61 | // Note it's handle(), not handleEvent() 62 | subscription.handle(Event.created) { query, object in 63 | // Called whenever an object was created 64 | } 65 | ``` 66 | 67 | By default, it will print the logs from WebSocket / WebSocketDelegate. You can turn it off: 68 | ```swift 69 | Client.shared.shouldPrintWebSocketLog = false 70 | ``` 71 | 72 | You can also enable socket trace messages for all sent and received strings. By default, these trace messages are disabled. 73 | ```swift 74 | Client.shared.shouldPrintWebSocketTrace = true 75 | ``` 76 | 77 | Handling errors is and other events is similar, take a look at the `Subscription` class for more information. 78 | 79 | ## Advanced Usage 80 | 81 | You are not limited to a single Live Query Client - you can create your own instances of `Client` to manually control things like reconnecting, server URLs, and more. 82 | 83 | ## How Do I Contribute? 84 | 85 | We want to make contributing to this project as easy and transparent as possible. Please refer to the [Contribution Guidelines][contributing]. 86 | 87 | ----- 88 | 89 | As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code. 90 | 91 | [releases]: https://github.com/parse-community/ParseLiveQuery-iOS-OSX/releases 92 | [contributing]: https://github.com/parse-community/ParseLiveQuery-iOS-OSX/blob/master/CONTRIBUTING.md 93 | 94 | [build-status-svg]: https://img.shields.io/travis/parse-community/ParseLiveQuery-iOS-OSX/master.svg 95 | [build-status-link]: https://travis-ci.org/parse-community/ParseLiveQuery-iOS-OSX/branches 96 | 97 | [circleci-status-svg]: https://circleci.com/gh/parse-community/ParseLiveQuery-iOS-OSX.svg?style=shield 98 | [circleci-status-link]: https://circleci.com/build-insights/gh/parse-community/ParseLiveQuery-iOS-OSX/master 99 | 100 | [coverage-status-svg]: https://img.shields.io/codecov/c/github/parse-community/ParseLiveQuery-iOS-OSX/master.svg 101 | [coverage-status-link]: https://codecov.io/github/parse-community/ParseLiveQuery-iOS-OSX?branch=master 102 | 103 | [license-svg]: https://img.shields.io/badge/license-BSD-lightgrey.svg 104 | [license-link]: https://github.com/parse-community/ParseLiveQuery-iOS-OSX/blob/master/LICENSE 105 | 106 | [podspec-svg]: https://img.shields.io/cocoapods/v/ParseLiveQuery.svg 107 | [podspec-link]: https://cocoapods.org/pods/ParseLiveQuery 108 | 109 | [platforms-svg]: http://img.shields.io/cocoapods/p/ParseLiveQuery.svg?style=flat 110 | [platforms-link]: https://github.com/parse-community/ParseLiveQuery-iOS-OSX 111 | 112 | [carthage-svg]:https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat 113 | [carthage-link]:https://github.com/Carthage/Carthage 114 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery-tvOS/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery-tvOS/ParseLiveQuery_tvOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // ParseLiveQuery_tvOS.h 3 | // ParseLiveQuery-tvOS 4 | // 5 | // Created by Corey Baker on 11/15/20. 6 | // Copyright © 2020 Parse. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ParseLiveQuery_tvOS. 12 | FOUNDATION_EXPORT double ParseLiveQuery_tvOSVersionNumber; 13 | 14 | //! Project version string for ParseLiveQuery_tvOS. 15 | FOUNDATION_EXPORT const unsigned char ParseLiveQuery_tvOSVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery-watchOS/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery-watchOS/ParseLiveQuery_watchOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // ParseLiveQuery_watchOS.h 3 | // ParseLiveQuery-watchOS 4 | // 5 | // Created by Corey Baker on 11/15/20. 6 | // Copyright © 2020 Parse. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ParseLiveQuery_watchOS. 12 | FOUNDATION_EXPORT double ParseLiveQuery_watchOSVersionNumber; 13 | 14 | //! Project version string for ParseLiveQuery_watchOS. 15 | FOUNDATION_EXPORT const unsigned char ParseLiveQuery_watchOSVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery.xcodeproj/xcshareddata/xcschemes/ParseLiveQuery-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery.xcodeproj/xcshareddata/xcschemes/ParseLiveQuery-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery.xcodeproj/xcshareddata/xcschemes/ParseLiveQuery-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery.xcodeproj/xcshareddata/xcschemes/ParseLiveQuery-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/Client.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | import BoltsSwift 13 | import Starscream 14 | 15 | /** 16 | This is the 'advanced' view of live query subscriptions. It allows you to customize your subscriptions 17 | to a live query server, have connections to multiple servers, cleanly handle disconnect and reconnect. 18 | */ 19 | @objc(PFLiveQueryClient) 20 | open class Client: NSObject { 21 | let host: URL 22 | let applicationId: String 23 | let clientKey: String? 24 | 25 | var socket: WebSocket? 26 | public var shouldPrintWebSocketLog = true 27 | public var shouldPrintWebSocketTrace = false 28 | public var userDisconnected = false 29 | var isConnecting = false 30 | 31 | // This allows us to easily plug in another request ID generation scheme, or more easily change the request id type 32 | // if needed (technically this could be a string). 33 | let requestIdGenerator: () -> RequestId 34 | var subscriptions = [SubscriptionRecord]() 35 | 36 | let queue = DispatchQueue(label: "com.parse.livequery", attributes: []) 37 | 38 | /** 39 | Creates a Client which automatically attempts to connect to the custom parse-server URL set in Parse.currentConfiguration(). 40 | */ 41 | public override convenience init() { 42 | self.init(server: Parse.validatedCurrentConfiguration().server) 43 | } 44 | 45 | /** 46 | Creates a client which will connect to a specific server with an optional application id and client key 47 | 48 | - parameter server: The server to connect to 49 | - parameter applicationId: The application id to use 50 | - parameter clientKey: The client key to use 51 | */ 52 | @objc(initWithServer:applicationId:clientKey:) 53 | public init(server: String, applicationId: String? = nil, clientKey: String? = nil) { 54 | guard let cmpts = URLComponents(string: server) else { 55 | fatalError("Server should be a valid URL.") 56 | } 57 | var components = cmpts 58 | components.scheme = (components.scheme == "https" || components.scheme == "wss") ? "wss" : "ws" 59 | 60 | // Simple incrementing generator - can't use ++, that operator is deprecated! 61 | var currentRequestId = 0 62 | requestIdGenerator = { 63 | currentRequestId += 1 64 | return RequestId(value: currentRequestId) 65 | } 66 | 67 | self.applicationId = applicationId ?? Parse.validatedCurrentConfiguration().applicationId! 68 | self.clientKey = clientKey ?? Parse.validatedCurrentConfiguration().clientKey 69 | 70 | self.host = components.url! 71 | } 72 | } 73 | 74 | extension Client { 75 | // Swift is lame and doesn't allow storage to directly be in extensions. 76 | // So we create an inner struct to wrap it up. 77 | fileprivate class Storage { 78 | private static var __once: () = { 79 | sharedStorage = Storage() 80 | }() 81 | static var onceToken: Int = 0 82 | static var sharedStorage: Storage! 83 | static var shared: Storage { 84 | _ = Storage.__once 85 | return sharedStorage 86 | } 87 | 88 | let queue: DispatchQueue = DispatchQueue(label: "com.parse.livequery.client.storage", attributes: []) 89 | var client: Client? 90 | } 91 | 92 | /// Gets or sets shared live query client to be used for default subscriptions 93 | @objc(sharedClient) 94 | public static var shared: Client! { 95 | get { 96 | let storage = Storage.shared 97 | var client: Client? 98 | storage.queue.sync { 99 | client = storage.client 100 | if client == nil { 101 | let configuration = Parse.validatedCurrentConfiguration() 102 | client = Client( 103 | server: configuration.server, 104 | applicationId: configuration.applicationId, 105 | clientKey: configuration.clientKey 106 | ) 107 | storage.client = client 108 | } 109 | } 110 | return client 111 | } 112 | set { 113 | let storage = Storage.shared 114 | storage.queue.sync { 115 | storage.client = newValue 116 | } 117 | } 118 | } 119 | } 120 | 121 | extension Client { 122 | /** 123 | Registers a query for live updates, using the default subscription handler 124 | 125 | - parameter query: The query to register for updates. 126 | - parameter subclassType: The subclass of PFObject to be used as the type of the Subscription. 127 | This parameter can be automatically inferred from context most of the time 128 | 129 | - returns: The subscription that has just been registered 130 | */ 131 | public func subscribe( 132 | _ query: PFQuery, 133 | subclassType: T.Type = T.self 134 | ) -> Subscription { 135 | return subscribe(query, handler: Subscription()) 136 | } 137 | 138 | /** 139 | Registers a query for live updates, using a custom subscription handler 140 | 141 | - parameter query: The query to register for updates. 142 | - parameter handler: A custom subscription handler. 143 | 144 | - returns: Your subscription handler, for easy chaining. 145 | */ 146 | public func subscribe( 147 | _ query: PFQuery, 148 | handler: T 149 | ) -> T where T: SubscriptionHandling { 150 | let subscriptionRecord = SubscriptionRecord( 151 | query: query, 152 | requestId: requestIdGenerator(), 153 | handler: handler 154 | ) 155 | 156 | self.subscriptions.append(subscriptionRecord) 157 | 158 | if socket != nil { 159 | _ = self.sendOperationAsync(.subscribe(requestId: subscriptionRecord.requestId, query: query as! PFQuery, 160 | sessionToken: PFUser.current()?.sessionToken)) 161 | } else if !self.userDisconnected { 162 | self.reconnect() 163 | self.subscriptions.removeLast() 164 | return self.subscribe(query, handler: handler) 165 | } else { 166 | NSLog("ParseLiveQuery: Warning: The client was explicitly disconnected! You must explicitly call .reconnect() in order to process your subscriptions.") 167 | } 168 | 169 | return handler 170 | } 171 | 172 | /** 173 | Updates an existing subscription with a new query. 174 | Upon completing the registration, the subscribe handler will be called with the new query 175 | 176 | - parameter handler: The specific handler to update. 177 | - parameter query: The new query for that handler. 178 | */ 179 | public func update( 180 | _ handler: T, 181 | toQuery query: PFQuery 182 | ) where T: SubscriptionHandling { 183 | subscriptions = subscriptions.map { 184 | if $0.subscriptionHandler === handler { 185 | _ = sendOperationAsync(.update(requestId: $0.requestId, query: query as! PFQuery)) 186 | return SubscriptionRecord(query: query, requestId: $0.requestId, handler: $0.subscriptionHandler as! T) 187 | } 188 | return $0 189 | } 190 | } 191 | 192 | /** 193 | Unsubscribes all current subscriptions for a given query. 194 | 195 | - parameter query: The query to unsubscribe from. 196 | */ 197 | @objc(unsubscribeFromQuery:) 198 | public func unsubscribe(_ query: PFQuery) { 199 | unsubscribe { $0.query == query } 200 | } 201 | 202 | /** 203 | Unsubscribes from a specific query-handler pair. 204 | 205 | - parameter query: The query to unsubscribe from. 206 | - parameter handler: The specific handler to unsubscribe from. 207 | */ 208 | public func unsubscribe(_ query: PFQuery, handler: T) where T: SubscriptionHandling { 209 | unsubscribe { $0.query == query && $0.subscriptionHandler === handler } 210 | } 211 | 212 | func unsubscribe(matching matcher: @escaping (SubscriptionRecord) -> Bool) { 213 | var temp = [SubscriptionRecord]() 214 | subscriptions.forEach { 215 | if matcher($0) { 216 | _ = sendOperationAsync(.unsubscribe(requestId: $0.requestId)) 217 | } else { 218 | temp.append($0) 219 | } 220 | } 221 | subscriptions = temp 222 | } 223 | } 224 | 225 | extension Client { 226 | /** 227 | Reconnects this client to the server. 228 | 229 | This will disconnect and resubscribe all existing subscriptions. This is not required to be called the first time 230 | you use the client, and should usually only be called when an error occurs. 231 | */ 232 | @objc(reconnect) 233 | public func reconnect() { 234 | guard socket == nil || !isConnecting else { return } 235 | socket?.disconnect() 236 | socket = { 237 | let socket = WebSocket(request: .init(url: host)) 238 | socket.delegate = self 239 | socket.callbackQueue = queue 240 | socket.connect() 241 | isConnecting = true 242 | userDisconnected = false 243 | return socket 244 | }() 245 | } 246 | 247 | /** 248 | Explicitly disconnects this client from the server. 249 | 250 | This does not remove any subscriptions - if you `reconnect()` your existing subscriptions will be restored. 251 | Use this if you wish to dispose of the live query client. 252 | */ 253 | @objc(disconnect) 254 | public func disconnect() { 255 | isConnecting = false 256 | guard let socket = socket 257 | else { 258 | return 259 | } 260 | socket.disconnect() 261 | self.socket = nil 262 | userDisconnected = true 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/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 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/Internal/ClientPrivate.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | import Starscream 13 | import BoltsSwift 14 | 15 | private func parseObject(_ objectDictionary: [String:AnyObject]) throws -> T { 16 | guard let _ = objectDictionary["className"] as? String else { 17 | throw LiveQueryErrors.InvalidJSONError(json: objectDictionary, expectedKey: "parseClassName") 18 | } 19 | guard let _ = objectDictionary["objectId"] as? String else { 20 | throw LiveQueryErrors.InvalidJSONError(json: objectDictionary, expectedKey: "objectId") 21 | } 22 | 23 | guard let object = PFDecoder.object().decode(objectDictionary) as? T else { 24 | throw LiveQueryErrors.InvalidJSONObject(json: objectDictionary, details: "cannot decode json into \(T.self)") 25 | } 26 | 27 | return object 28 | } 29 | 30 | // --------------- 31 | // MARK: Subscriptions 32 | // --------------- 33 | 34 | extension Client { 35 | class SubscriptionRecord { 36 | var subscriptionHandler: AnyObject? 37 | // HandlerClosure captures the generic type info passed into the constructor of SubscriptionRecord, 38 | // and 'unwraps' it so that it can be used with just a 'PFObject' instance. 39 | // Technically, this should be a compiler no-op, as no witness tables should be used as 'PFObject' currently inherits from NSObject. 40 | // Should we make PFObject ever a native swift class without the backing Objective-C runtime however, 41 | // this becomes extremely important to have, and makes a ton more sense than just unsafeBitCast-ing everywhere. 42 | var eventHandlerClosure: (Event, Client) -> Void 43 | var errorHandlerClosure: (Error, Client) -> Void 44 | var subscribeHandlerClosure: (Client) -> Void 45 | var unsubscribeHandlerClosure: (Client) -> Void 46 | 47 | let query: PFQuery 48 | let requestId: RequestId 49 | 50 | init(query: PFQuery, requestId: RequestId, handler: T) where T:SubscriptionHandling { 51 | self.query = query as! PFQuery 52 | self.requestId = requestId 53 | 54 | subscriptionHandler = handler 55 | 56 | // This is needed because swift requires 'handlerClosure' to be fully initialized before we setup the 57 | // capture list for the closure. 58 | eventHandlerClosure = { _, _ in } 59 | errorHandlerClosure = { _, _ in } 60 | subscribeHandlerClosure = { _ in } 61 | unsubscribeHandlerClosure = { _ in } 62 | 63 | eventHandlerClosure = { [weak self] event, client in 64 | guard let handler = self?.subscriptionHandler as? T else { 65 | return 66 | } 67 | 68 | handler.didReceive(Event(event: event), forQuery: query, inClient: client) 69 | } 70 | 71 | errorHandlerClosure = { [weak self] error, client in 72 | guard let handler = self?.subscriptionHandler as? T else { 73 | return 74 | } 75 | 76 | handler.didEncounter(error, forQuery: query, inClient: client) 77 | } 78 | 79 | subscribeHandlerClosure = { [weak self] client in 80 | guard let handler = self?.subscriptionHandler as? T else { 81 | return 82 | } 83 | 84 | handler.didSubscribe(toQuery: query, inClient: client) 85 | } 86 | 87 | unsubscribeHandlerClosure = { [weak self] client in 88 | guard let handler = self?.subscriptionHandler as? T else { 89 | return 90 | } 91 | 92 | handler.didUnsubscribe(fromQuery: query, inClient: client) 93 | } 94 | } 95 | } 96 | } 97 | extension Client { 98 | // An opaque placeholder structed used to ensure that we type-safely create request IDs and don't shoot ourself in 99 | // the foot with array indexes. 100 | struct RequestId: Equatable { 101 | let value: Int 102 | 103 | init(value: Int) { 104 | self.value = value 105 | } 106 | } 107 | } 108 | 109 | func == (first: Client.RequestId, second: Client.RequestId) -> Bool { 110 | return first.value == second.value 111 | } 112 | 113 | // --------------- 114 | // MARK: Web Socket 115 | // --------------- 116 | 117 | extension Client: WebSocketDelegate { 118 | public func didReceive(event: WebSocketEvent, client: WebSocket) { 119 | switch event { 120 | 121 | case .connected(_): 122 | isConnecting = false 123 | let sessionToken = PFUser.current()?.sessionToken ?? "" 124 | _ = self.sendOperationAsync(.connect(applicationId: applicationId, sessionToken: sessionToken, clientKey: clientKey)) 125 | case .disconnected(let reason, let code): 126 | isConnecting = false 127 | if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket did disconnect with error: \(reason) code:\(code)") } 128 | 129 | // TODO: Better retry logic, unless `disconnect()` was explicitly called 130 | if !userDisconnected { 131 | reconnect() 132 | } 133 | case .text(let text): 134 | handleOperationAsync(text).continueWith { [weak self] task in 135 | if let error = task.error, self?.shouldPrintWebSocketLog == true { 136 | NSLog("ParseLiveQuery: Error processing message: \(error)") 137 | } 138 | } 139 | case .binary(_): 140 | if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: Received binary data but we don't handle it...") } 141 | case .error(let error): 142 | NSLog("ParseLiveQuery: Error processing message: \(String(describing: error))") 143 | case .viabilityChanged(let isViable): 144 | if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket viability channged to \(isViable ? "" : "not-")viable") } 145 | if !isViable { 146 | isConnecting = false 147 | } 148 | // TODO: Better retry logic, unless `disconnect()` was explicitly called 149 | if !userDisconnected, isViable { 150 | reconnect() 151 | } 152 | case .reconnectSuggested(let isSuggested): 153 | if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket reconnect is \(isSuggested ? "" : "not ")suggested") } 154 | // TODO: Better retry logic, unless `disconnect()` was explicitly called 155 | if !userDisconnected, isSuggested { 156 | reconnect() 157 | } 158 | case .cancelled: 159 | isConnecting = false 160 | if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket connection cancelled...") } 161 | // TODO: Better retry logic, unless `disconnect()` was explicitly called 162 | if !userDisconnected { 163 | reconnect() 164 | } 165 | case .pong(_): 166 | if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: Received pong but we don't handle it...") } 167 | case .ping(_): 168 | if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: Received ping but we don't handle it...") } 169 | } 170 | } 171 | } 172 | 173 | // ------------------- 174 | // MARK: Operations 175 | // ------------------- 176 | 177 | extension Event { 178 | init(serverResponse: ServerResponse, requestId: inout Client.RequestId) throws { 179 | switch serverResponse { 180 | case .enter(let reqId, let object): 181 | requestId = reqId 182 | self = .entered(try parseObject(object)) 183 | 184 | case .leave(let reqId, let object): 185 | requestId = reqId 186 | self = .left(try parseObject(object)) 187 | 188 | case .create(let reqId, let object): 189 | requestId = reqId 190 | self = .created(try parseObject(object)) 191 | 192 | case .update(let reqId, let object): 193 | requestId = reqId 194 | self = .updated(try parseObject(object)) 195 | 196 | case .delete(let reqId, let object): 197 | requestId = reqId 198 | self = .deleted(try parseObject(object)) 199 | 200 | default: fatalError("Invalid state reached") 201 | } 202 | } 203 | } 204 | 205 | extension Client { 206 | fileprivate func subscriptionRecord(_ requestId: RequestId) -> SubscriptionRecord? { 207 | guard 208 | let recordIndex = self.subscriptions.firstIndex(where: { $0.requestId == requestId }) else { 209 | return nil 210 | } 211 | let record = self.subscriptions[recordIndex] 212 | return record.subscriptionHandler != nil ? record : nil 213 | } 214 | 215 | func sendOperationAsync(_ operation: ClientOperation) -> Task { 216 | return Task(.queue(queue)) { 217 | let jsonEncoded = operation.JSONObjectRepresentation 218 | let jsonData = try JSONSerialization.data(withJSONObject: jsonEncoded, options: JSONSerialization.WritingOptions(rawValue: 0)) 219 | let jsonString = String(data: jsonData, encoding: String.Encoding.utf8) 220 | if self.shouldPrintWebSocketTrace { NSLog("ParseLiveQuery: Sending message: \(jsonString!)") } 221 | self.socket?.write(string: jsonString!) 222 | } 223 | } 224 | 225 | func handleOperationAsync(_ string: String) -> Task { 226 | return Task(.queue(queue)) { 227 | if self.shouldPrintWebSocketTrace { NSLog("ParseLiveQuery: Received message: \(string)") } 228 | guard 229 | let jsonData = string.data(using: String.Encoding.utf8), 230 | let jsonDecoded = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions(rawValue: 0)) 231 | as? [String:AnyObject], 232 | let response: ServerResponse = try? ServerResponse(json: jsonDecoded) 233 | else { 234 | throw LiveQueryErrors.InvalidResponseError(response: string) 235 | } 236 | 237 | switch response { 238 | case .connected: 239 | let sessionToken = PFUser.current()?.sessionToken 240 | self.subscriptions.forEach { 241 | _ = self.sendOperationAsync(.subscribe(requestId: $0.requestId, query: $0.query, sessionToken: sessionToken)) 242 | } 243 | 244 | case .redirect: 245 | // TODO: Handle redirect. 246 | break 247 | 248 | case .subscribed(let requestId): 249 | self.subscriptionRecord(requestId)?.subscribeHandlerClosure(self) 250 | 251 | case .unsubscribed(let requestId): 252 | guard 253 | let recordIndex = self.subscriptions.firstIndex(where: { $0.requestId == requestId }) 254 | else { 255 | break 256 | } 257 | let record: SubscriptionRecord = self.subscriptions[recordIndex] 258 | record.unsubscribeHandlerClosure(self) 259 | self.subscriptions.remove(at: recordIndex) 260 | 261 | case .create, .delete, .enter, .leave, .update: 262 | var requestId: RequestId = RequestId(value: 0) 263 | guard 264 | let event: Event = try? Event(serverResponse: response, requestId: &requestId), 265 | let record = self.subscriptionRecord(requestId) 266 | else { 267 | break 268 | } 269 | record.eventHandlerClosure(event, self) 270 | 271 | case .error(let requestId, let code, let error, let reconnect): 272 | let error = LiveQueryErrors.ServerReportedError(code: code, error: error, reconnect: reconnect) 273 | if let requestId = requestId { 274 | self.subscriptionRecord(requestId)?.errorHandlerClosure(error, self) 275 | } else { 276 | throw error 277 | } 278 | } 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/Internal/Errors.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | /** 11 | Namespace struct for all errors reported by the Live Query SDK. 12 | */ 13 | public struct LiveQueryErrors { 14 | fileprivate init() {} 15 | 16 | /** 17 | An error that is reported when the server returns a response that cannot be parsed. 18 | */ 19 | public struct InvalidResponseError: Error { 20 | /// Response string of the error. 21 | public let response: String 22 | } 23 | 24 | /** 25 | An error that is reported when the server does not accept a query we've sent to it. 26 | */ 27 | public struct InvalidQueryError: Error { 28 | } 29 | 30 | /** 31 | An error that is reported when the server returns valid JSON, but it doesn't match the format we expect. 32 | */ 33 | public struct InvalidJSONError: Error { 34 | /// JSON used for matching. 35 | public let json: [String:AnyObject] 36 | /// Key that was expected to match. 37 | public let expectedKey: String 38 | } 39 | 40 | /** 41 | An error that is reported when the server returns valid JSON, but it doesn't match the format we expect. 42 | */ 43 | public struct InvalidJSONObject: Error { 44 | /// JSON used for matching. 45 | public let json: [String:AnyObject] 46 | /// Details about the error 47 | public let details: String 48 | } 49 | 50 | /** 51 | An error that is reported when the live query server encounters an internal error. 52 | */ 53 | public struct ServerReportedError: Error { 54 | /// Error code reported by the server. 55 | public let code: Int 56 | /// String error reported by the server. 57 | public let error: String 58 | /// Boolean value representing whether a client should reconnect. 59 | public let reconnect: Bool 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/Internal/Operation.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | 13 | enum ClientOperation { 14 | case connect(applicationId: String, sessionToken: String, clientKey: String?) 15 | case subscribe(requestId: Client.RequestId, query: PFQuery, sessionToken: String?) 16 | case update(requestId: Client.RequestId, query: PFQuery) 17 | case unsubscribe(requestId: Client.RequestId) 18 | 19 | var JSONObjectRepresentation: [String : Any] { 20 | switch self { 21 | case .connect(let applicationId, let sessionToken, let clientKey): 22 | var message: [String: Any] = [ "op": "connect", "applicationId": applicationId, "sessionToken": sessionToken ] 23 | if let clientKey = clientKey { 24 | message.updateValue(clientKey, forKey: "clientKey") 25 | } 26 | return message 27 | 28 | case .subscribe(let requestId, let query, let sessionToken): 29 | var result: [String: Any] = [ "op": "subscribe", "requestId": requestId.value, "query": Dictionary(query: query) ] 30 | if let sessionToken = sessionToken { 31 | result["sessionToken"] = sessionToken 32 | } 33 | return result 34 | 35 | case .update(let requestId, let query): 36 | return [ "op": "update", "requestId": requestId.value, "query": Dictionary(query: query) ] 37 | 38 | case .unsubscribe(let requestId): 39 | return [ "op": "unsubscribe", "requestId": requestId.value ] 40 | } 41 | } 42 | } 43 | 44 | enum ServerResponse { 45 | case redirect(url: String) 46 | case connected 47 | 48 | case subscribed(requestId: Client.RequestId) 49 | case unsubscribed(requestId: Client.RequestId) 50 | 51 | case enter(requestId: Client.RequestId, object: [String : AnyObject]) 52 | case leave(requestId: Client.RequestId, object: [String : AnyObject]) 53 | case update(requestId: Client.RequestId, object: [String : AnyObject]) 54 | case create(requestId: Client.RequestId, object: [String : AnyObject]) 55 | case delete(requestId: Client.RequestId, object: [String : AnyObject]) 56 | 57 | case error(requestId: Client.RequestId?, code: Int, error: String, reconnect: Bool) 58 | 59 | init(json: [String : AnyObject]) throws { 60 | func jsonValue(_ json: [String:AnyObject], _ key: String) throws -> T { 61 | guard let value = json[key] as? T 62 | else { 63 | throw LiveQueryErrors.InvalidJSONError(json: json, expectedKey: key) 64 | } 65 | return value 66 | } 67 | 68 | func jsonRequestId(_ json: [String:AnyObject]) throws -> Client.RequestId { 69 | let requestId: Int = try jsonValue(json, "requestId") 70 | return Client.RequestId(value: requestId) 71 | } 72 | 73 | func subscriptionEvent( 74 | _ json: [String:AnyObject], 75 | _ eventType: (Client.RequestId, [String : AnyObject]) -> ServerResponse 76 | ) throws -> ServerResponse { 77 | return eventType(try jsonRequestId(json), try jsonValue(json, "object")) 78 | } 79 | 80 | let rawOperation: String = try jsonValue(json, "op") 81 | switch rawOperation { 82 | case "connected": 83 | self = .connected 84 | 85 | case "redirect": 86 | self = .redirect(url: try jsonValue(json, "url")) 87 | 88 | case "subscribed": 89 | self = .subscribed(requestId: try jsonRequestId(json)) 90 | case "unsubscribed": 91 | self = .unsubscribed(requestId: try jsonRequestId(json)) 92 | 93 | case "enter": self = try subscriptionEvent(json, ServerResponse.enter) 94 | case "leave": self = try subscriptionEvent(json, ServerResponse.leave) 95 | case "update": self = try subscriptionEvent(json, ServerResponse.update) 96 | case "create": self = try subscriptionEvent(json, ServerResponse.create) 97 | case "delete": self = try subscriptionEvent(json, ServerResponse.delete) 98 | 99 | case "error": 100 | self = .error( 101 | requestId: try? jsonRequestId(json), 102 | code: try jsonValue(json, "code"), 103 | error: try jsonValue(json, "error"), 104 | reconnect: try jsonValue(json, "reconnect") 105 | ) 106 | 107 | default: 108 | throw LiveQueryErrors.InvalidJSONError(json: json, expectedKey: "op") 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/Internal/QueryEncoder.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | 13 | /** 14 | NOTE: This is super hacky, and we need a better answer for this. 15 | */ 16 | extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject { 17 | init(query: PFQuery) { 18 | self.init() 19 | let queryState = query.value(forKey: "state") as AnyObject? 20 | if let className = queryState?.value(forKey: "parseClassName") { 21 | self["className"] = className as? Value 22 | } 23 | if let conditions = queryState?.value(forKey: "conditions") as? [String:AnyObject] { 24 | self["where"] = conditions.encodedQueryDictionary as? Value 25 | } else { 26 | self["where"] = [:] as? Value 27 | } 28 | } 29 | } 30 | 31 | extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject { 32 | var encodedQueryDictionary: Dictionary { 33 | var encodedQueryDictionary = Dictionary() 34 | for (key, val) in self { 35 | if let array = val as? [PFQuery] { 36 | var queries:[Value] = [] 37 | for query in array { 38 | let queryState = query.value(forKey: "state") as AnyObject? 39 | if let conditions: [String:AnyObject] = queryState?.value(forKey: "conditions") as? [String:AnyObject], let encoded = conditions.encodedQueryDictionary as? Value { 40 | queries.append(encoded) 41 | } 42 | } 43 | encodedQueryDictionary[key] = queries as? Value 44 | } else if let geoPoints = val as? [PFGeoPoint] { 45 | var points:[Value] = [] 46 | for point in geoPoints { 47 | points.append(point.encodedDictionary as! Value) 48 | } 49 | encodedQueryDictionary[key] = points as? Value 50 | } else if let dict = val as? [String:AnyObject] { 51 | encodedQueryDictionary[key] = dict.encodedQueryDictionary as? Value 52 | } else if let geoPoint = val as? PFGeoPoint { 53 | encodedQueryDictionary[key] = geoPoint.encodedDictionary as? Value 54 | } else if let object = val as? PFObject { 55 | encodedQueryDictionary[key] = (try? PFPointerObjectEncoder.object().encode(object)) as? Value 56 | } else if let query = val as? PFQuery { 57 | let queryState = query.value(forKey: "state") as AnyObject? 58 | if let conditions: [String:AnyObject] = queryState?.value(forKey: "conditions") as? [String:AnyObject], let encoded = conditions.encodedQueryDictionary as? Value { 59 | encodedQueryDictionary[key] = encoded 60 | } 61 | } else if let date = val as? Date { 62 | encodedQueryDictionary[key] = ["__type": "Date", "iso": date.encodedString] as? Value 63 | } else { 64 | encodedQueryDictionary[key] = val 65 | } 66 | } 67 | return encodedQueryDictionary 68 | } 69 | } 70 | 71 | extension PFGeoPoint { 72 | var encodedDictionary: [String:Any] { 73 | return ["__type": "GeoPoint", 74 | "latitude": latitude, 75 | "longitude": longitude] 76 | } 77 | } 78 | 79 | fileprivate extension Formatter { 80 | static let iso8601: DateFormatter = { 81 | let formatter = DateFormatter() 82 | formatter.calendar = Calendar(identifier: .iso8601) 83 | formatter.locale = Locale(identifier: "en_US_POSIX") 84 | formatter.timeZone = TimeZone(secondsFromGMT: 0) 85 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX" 86 | return formatter 87 | }() 88 | } 89 | 90 | fileprivate extension Date { 91 | var encodedString: String { 92 | return Formatter.iso8601.string(from: self) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/ObjCCompat.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | import BoltsSwift 13 | 14 | /** 15 | This protocol describes the interface for handling events from a live query client. 16 | 17 | You can use this protocol on any custom class of yours, instead of Subscription, if it fits your use case better. 18 | */ 19 | @objc(PFLiveQuerySubscriptionHandling) 20 | public protocol ObjCCompat_SubscriptionHandling { 21 | 22 | /** 23 | Tells the handler that an event has been received from the live query server. 24 | 25 | - parameter query: The query that the event occurred on. 26 | - parameter event: The event that has been recieved from the server. 27 | - parameter client: The live query client which received this event. 28 | */ 29 | @objc(liveQuery:didRecieveEvent:inClient:) 30 | optional func didRecieveEvent(_ query: PFQuery, event: PFLiveQueryEvent, client: Client) 31 | 32 | /** 33 | Tells the handler that an error has been received from the live query server. 34 | 35 | - parameter query: The query that the error occurred on. 36 | - parameter error: The error that the server has encountered. 37 | - parameter client: The live query client which received this error. 38 | */ 39 | @objc(liveQuery:didEncounterError:inClient:) 40 | optional func didRecieveError(_ query: PFQuery, error: NSError, client: Client) 41 | 42 | /** 43 | Tells the handler that a query has been successfully registered with the server. 44 | 45 | - note: This may be invoked multiple times if the client disconnects/reconnects. 46 | 47 | - parameter query: The query that has been subscribed. 48 | - parameter client: The live query client which subscribed this query. 49 | */ 50 | @objc(liveQuery:didSubscribeInClient:) 51 | optional func didSubscribe(_ query: PFQuery, client: Client) 52 | 53 | /** 54 | Tells the handler that a query has been successfully deregistered from the server. 55 | 56 | - note: This is not called unless `unregister()` is explicitly called. 57 | 58 | - parameter query: The query that has been unsubscribed. 59 | - parameter client: The live query client which unsubscribed this query. 60 | */ 61 | @objc(liveQuery:didUnsubscribeInClient:) 62 | optional func didUnsubscribe(_ query: PFQuery, client: Client) 63 | } 64 | 65 | // HACK: Compiler bug causes enums (and sometimes classes) that are declared in structs that are marked as @objc 66 | // to not actually be emitted by the compiler (lolwut?). Moving this to global scope fixes the problem, but we can't 67 | // change the objc name of an enum either, so we pollute the swift namespace here. 68 | // TODO: Fix this eventually. 69 | 70 | /** 71 | A type of an update event on a specific object from the live query server. 72 | */ 73 | @objc 74 | public enum PFLiveQueryEventType: Int { 75 | /// The object has been updated, and is now included in the query. 76 | case entered 77 | /// The object has been updated, and is no longer included in the query. 78 | case left 79 | /// The object has been created, and is a part of the query. 80 | case created 81 | /// The object has been updated, and is still a part of the query. 82 | case updated 83 | /// The object has been deleted, and is no longer included in the query. 84 | case deleted 85 | } 86 | 87 | /** 88 | Represents an update on a specific object from the live query server. 89 | */ 90 | @objc 91 | open class PFLiveQueryEvent: NSObject { 92 | /// Type of the event. 93 | @objc 94 | public let type: PFLiveQueryEventType 95 | 96 | /// Object this event is for. 97 | @objc 98 | public let object: PFObject 99 | 100 | init(type: PFLiveQueryEventType, object: PFObject) { 101 | self.type = type 102 | self.object = object 103 | } 104 | } 105 | 106 | /** 107 | This struct wraps up all of our Objective-C compatibility layer. You should never need to touch this if you're using Swift. 108 | */ 109 | public struct ObjCCompat { 110 | fileprivate init() { } 111 | 112 | /** 113 | A default implementation of the SubscriptionHandling protocol, using blocks for callbacks. 114 | */ 115 | @objc(PFLiveQuerySubscription) 116 | open class Subscription: NSObject { 117 | public typealias SubscribeHandler = @convention(block) (PFQuery) -> Void 118 | public typealias ErrorHandler = @convention(block) (PFQuery, NSError) -> Void 119 | public typealias EventHandler = @convention(block) (PFQuery, PFLiveQueryEvent) -> Void 120 | public typealias ObjectHandler = @convention(block) (PFQuery, PFObject) -> Void 121 | 122 | var subscribeHandlers = [SubscribeHandler]() 123 | var unsubscribeHandlers = [SubscribeHandler]() 124 | var errorHandlers = [ErrorHandler]() 125 | var eventHandlers = [EventHandler]() 126 | 127 | /** 128 | Register a callback for when a client succesfully subscribes to a query. 129 | 130 | - parameter handler: The callback to register. 131 | 132 | - returns: The same subscription, for easy chaining. 133 | */ 134 | @objc(addSubscribeHandler:) 135 | open func addSubscribeHandler(_ handler: @escaping SubscribeHandler) -> Subscription { 136 | subscribeHandlers.append(handler) 137 | return self 138 | } 139 | 140 | /** 141 | Register a callback for when a query has been unsubscribed. 142 | 143 | - parameter handler: The callback to register. 144 | 145 | - returns: The same subscription, for easy chaining. 146 | */ 147 | @objc(addUnsubscribeHandler:) 148 | open func addUnsubscribeHandler(_ handler: @escaping SubscribeHandler) -> Subscription { 149 | unsubscribeHandlers.append(handler) 150 | return self 151 | } 152 | 153 | /** 154 | Register a callback for when an error occurs. 155 | 156 | - parameter handler: The callback to register. 157 | 158 | - returns: The same subscription, for easy chaining. 159 | */ 160 | @objc(addErrorHandler:) 161 | open func addErrorHandler(_ handler: @escaping ErrorHandler) -> Subscription { 162 | errorHandlers.append(handler) 163 | return self 164 | } 165 | 166 | /** 167 | Register a callback for when an event occurs. 168 | 169 | - parameter handler: The callback to register. 170 | 171 | - returns: The same subscription, for easy chaining. 172 | */ 173 | @objc(addEventHandler:) 174 | open func addEventHandler(_ handler: @escaping EventHandler) -> Subscription { 175 | eventHandlers.append(handler) 176 | return self 177 | } 178 | 179 | /** 180 | Register a callback for when an object enters a query. 181 | 182 | - parameter handler: The callback to register. 183 | 184 | - returns: The same subscription, for easy chaining. 185 | */ 186 | @objc(addEnterHandler:) 187 | open func addEnterHandler(_ handler: @escaping ObjectHandler) -> Subscription { 188 | return addEventHandler { $1.type == .entered ? handler($0, $1.object) : () } 189 | } 190 | 191 | /** 192 | Register a callback for when an object leaves a query. 193 | 194 | - parameter handler: The callback to register. 195 | 196 | - returns: The same subscription, for easy chaining. 197 | */ 198 | @objc(addLeaveHandler:) 199 | open func addLeaveHandler(_ handler: @escaping ObjectHandler) -> Subscription { 200 | return addEventHandler { $1.type == .left ? handler($0, $1.object) : () } 201 | } 202 | 203 | /** 204 | Register a callback for when an object that matches the query is created. 205 | 206 | - parameter handler: The callback to register. 207 | 208 | - returns: The same subscription, for easy chaining. 209 | */ 210 | @objc(addCreateHandler:) 211 | open func addCreateHandler(_ handler: @escaping ObjectHandler) -> Subscription { 212 | return addEventHandler { $1.type == .created ? handler($0, $1.object) : () } 213 | } 214 | 215 | /** 216 | Register a callback for when an object that matches the query is updated. 217 | 218 | - parameter handler: The callback to register. 219 | 220 | - returns: The same subscription, for easy chaining. 221 | */ 222 | @objc(addUpdateHandler:) 223 | open func addUpdateHandler(_ handler: @escaping ObjectHandler) -> Subscription { 224 | return addEventHandler { $1.type == .updated ? handler($0, $1.object) : () } 225 | } 226 | 227 | /** 228 | Register a callback for when an object that matches the query is deleted. 229 | 230 | - parameter handler: The callback to register. 231 | 232 | - returns: The same subscription, for easy chaining. 233 | */ 234 | @objc(addDeleteHandler:) 235 | open func addDeleteHandler(_ handler: @escaping ObjectHandler) -> Subscription { 236 | return addEventHandler { $1.type == .deleted ? handler($0, $1.object) : () } 237 | } 238 | } 239 | } 240 | 241 | extension ObjCCompat.Subscription: ObjCCompat_SubscriptionHandling { 242 | public func didRecieveEvent(_ query: PFQuery, event: PFLiveQueryEvent, client: Client) { 243 | eventHandlers.forEach { $0(query, event) } 244 | } 245 | 246 | public func didRecieveError(_ query: PFQuery, error: NSError, client: Client) { 247 | errorHandlers.forEach { $0(query, error) } 248 | } 249 | 250 | public func didSubscribe(_ query: PFQuery, client: Client) { 251 | subscribeHandlers.forEach { $0(query) } 252 | } 253 | 254 | public func didUnsubscribe(_ query: PFQuery, client: Client) { 255 | unsubscribeHandlers.forEach { $0(query) } 256 | } 257 | } 258 | 259 | extension Client { 260 | fileprivate class HandlerConverter: SubscriptionHandling { 261 | typealias T = PFObject 262 | 263 | fileprivate static var associatedObjectKey: Int = 0 264 | fileprivate weak var handler: ObjCCompat_SubscriptionHandling? 265 | 266 | init(handler: ObjCCompat_SubscriptionHandling) { 267 | self.handler = handler 268 | 269 | objc_setAssociatedObject(handler, &HandlerConverter.associatedObjectKey, self, .OBJC_ASSOCIATION_RETAIN) 270 | } 271 | 272 | fileprivate func didReceive(_ event: Event, forQuery query: PFQuery, inClient client: Client) { 273 | handler?.didRecieveEvent?(query, event: PFLiveQueryEvent(event: event), client: client) 274 | } 275 | 276 | fileprivate func didEncounter(_ error: Error, forQuery query: PFQuery, inClient client: Client) { 277 | handler?.didRecieveError?(query, error: error as NSError, client: client) 278 | } 279 | 280 | fileprivate func didSubscribe(toQuery query: PFQuery, inClient client: Client) { 281 | handler?.didSubscribe?(query, client: client) 282 | } 283 | 284 | fileprivate func didUnsubscribe(fromQuery query: PFQuery, inClient client: Client) { 285 | handler?.didUnsubscribe?(query, client: client) 286 | } 287 | } 288 | 289 | /** 290 | Registers a query for live updates, using a custom subscription handler. 291 | 292 | - parameter query: The query to register for updates. 293 | - parameter handler: A custom subscription handler. 294 | 295 | - returns: The subscription that has just been registered. 296 | */ 297 | @objc(subscribeToQuery:withHandler:) 298 | public func _PF_objc_subscribe( 299 | _ query: PFQuery, handler: ObjCCompat_SubscriptionHandling 300 | ) -> ObjCCompat_SubscriptionHandling { 301 | let swiftHandler = HandlerConverter(handler: handler) 302 | _ = subscribe(query, handler: swiftHandler) 303 | return handler 304 | } 305 | 306 | /** 307 | Registers a query for live updates, using the default subscription handler. 308 | 309 | - parameter query: The query to register for updates. 310 | 311 | - returns: The subscription that has just been registered. 312 | */ 313 | @objc(subscribeToQuery:) 314 | public func _PF_objc_subscribe(_ query: PFQuery) -> ObjCCompat.Subscription { 315 | let subscription = ObjCCompat.Subscription() 316 | _ = _PF_objc_subscribe(query, handler: subscription) 317 | return subscription 318 | } 319 | 320 | /** 321 | Unsubscribes a specific handler from a query. 322 | 323 | - parameter query: The query to unsubscribe from. 324 | - parameter handler: The specific handler to unsubscribe from. 325 | */ 326 | @objc(unsubscribeFromQuery:withHandler:) 327 | public func _PF_objc_unsubscribe(_ query: PFQuery, subscriptionHandler: ObjCCompat_SubscriptionHandling) { 328 | unsubscribe { record in 329 | guard let handler = record.subscriptionHandler as? HandlerConverter 330 | else { 331 | return false 332 | } 333 | return record.query == query && handler.handler === subscriptionHandler 334 | } 335 | } 336 | } 337 | 338 | // HACK: Another compiler bug - if you have a required initializer with a generic type, the compiler simply refuses to 339 | // emit the entire class altogether. Moving this to an extension for now solves the issue. 340 | 341 | extension PFLiveQueryEvent { 342 | convenience init(event: ParseLiveQuery.Event) { 343 | let results: (type: PFLiveQueryEventType, object: PFObject) = { 344 | switch event { 345 | case .entered(let object): return (.entered, object) 346 | case .left(let object): return (.left, object) 347 | case .created(let object): return (.created, object) 348 | case .updated(let object): return (.updated, object) 349 | case .deleted(let object): return (.deleted, object) 350 | } 351 | }() 352 | 353 | self.init(type: results.type, object: results.object) 354 | } 355 | } 356 | 357 | extension PFQuery { 358 | /** 359 | Register this PFQuery for updates with Live Queries. 360 | This uses the shared live query client, and creates a default subscription handler for you. 361 | 362 | - returns: The created subscription for observing. 363 | */ 364 | @objc(subscribe) 365 | public func _PF_objc_subscribe() -> ObjCCompat.Subscription { 366 | return Client.shared._PF_objc_subscribe(self as! PFQuery) 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/PFQuery+Subscribe.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | 13 | 14 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/Parse+LiveQuery.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Parse 11 | 12 | extension Parse { 13 | static func validatedCurrentConfiguration() -> ParseClientConfiguration { 14 | guard let configuration = Parse.currentConfiguration else { 15 | preconditionFailure("Parse SDK is not initialized. Call Parse.initializeWithConfiguration() before loading live query client.") 16 | } 17 | return configuration 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/ParseLiveQuery/Subscription.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Foundation 11 | import Parse 12 | import BoltsSwift 13 | 14 | /** 15 | This protocol describes the interface for handling events from a liveQuery client. 16 | 17 | You can use this protocol on any custom class of yours, instead of Subscription, if it fits your use case better. 18 | */ 19 | public protocol SubscriptionHandling: AnyObject { 20 | /// The type of the PFObject subclass that this handler uses. 21 | associatedtype PFObjectSubclass: PFObject 22 | 23 | /** 24 | Tells the handler that an event has been received from the live query server. 25 | 26 | - parameter event: The event that has been recieved from the server. 27 | - parameter query: The query that the event occurred on. 28 | - parameter client: The live query client which received this event. 29 | */ 30 | func didReceive(_ event: Event, forQuery query: PFQuery, inClient client: Client) 31 | 32 | /** 33 | Tells the handler that an error has been received from the live query server. 34 | 35 | - parameter error: The error that the server has encountered. 36 | - parameter query: The query that the error occurred on. 37 | - parameter client: The live query client which received this error. 38 | */ 39 | func didEncounter(_ error: Error, forQuery query: PFQuery, inClient client: Client) 40 | 41 | /** 42 | Tells the handler that a query has been successfully registered with the server. 43 | 44 | - note: This may be invoked multiple times if the client disconnects/reconnects. 45 | 46 | - parameter query: The query that has been subscribed. 47 | - parameter client: The live query client which subscribed this query. 48 | */ 49 | func didSubscribe(toQuery query: PFQuery, inClient client: Client) 50 | 51 | /** 52 | Tells the handler that a query has been successfully deregistered from the server. 53 | 54 | - note: This is not called unless `unregister()` is explicitly called. 55 | 56 | - parameter query: The query that has been unsubscribed. 57 | - parameter client: The live query client which unsubscribed this query. 58 | */ 59 | func didUnsubscribe(fromQuery query: PFQuery, inClient client: Client) 60 | } 61 | 62 | /** 63 | Represents an update on a specific object from the live query server. 64 | 65 | - Entered: The object has been updated, and is now included in the query. 66 | - Left: The object has been updated, and is no longer included in the query. 67 | - Created: The object has been created, and is a part of the query. 68 | - Updated: The object has been updated, and is still a part of the query. 69 | - Deleted: The object has been deleted, and is no longer included in the query. 70 | */ 71 | public enum Event where T: PFObject { 72 | /// The object has been updated, and is now included in the query 73 | case entered(T) 74 | 75 | /// The object has been updated, and is no longer included in the query 76 | case left(T) 77 | 78 | /// The object has been created, and is a part of the query 79 | case created(T) 80 | 81 | /// The object has been updated, and is still a part of the query 82 | case updated(T) 83 | 84 | /// The object has been deleted, and is no longer included in the query 85 | case deleted(T) 86 | 87 | init(event: Event) { 88 | switch event { 89 | case .entered(let value as T): self = .entered(value) 90 | case .left(let value as T): self = .left(value) 91 | case .created(let value as T): self = .created(value) 92 | case .updated(let value as T): self = .updated(value) 93 | case .deleted(let value as T): self = .deleted(value) 94 | default: fatalError() 95 | } 96 | } 97 | } 98 | 99 | private func == (lhs: Event, rhs: Event) -> Bool { 100 | switch (lhs, rhs) { 101 | case (.entered(let obj1), .entered(let obj2)): return obj1 == obj2 102 | case (.left(let obj1), .left(let obj2)): return obj1 == obj2 103 | case (.created(let obj1), .created(let obj2)): return obj1 == obj2 104 | case (.updated(let obj1), .updated(let obj2)): return obj1 == obj2 105 | case (.deleted(let obj1), .deleted(let obj2)): return obj1 == obj2 106 | default: return false 107 | } 108 | } 109 | 110 | /** 111 | A default implementation of the SubscriptionHandling protocol, using closures for callbacks. 112 | */ 113 | open class Subscription: SubscriptionHandling where T: PFObject { 114 | fileprivate var errorHandlers: [(PFQuery, Error) -> Void] = [] 115 | fileprivate var eventHandlers: [(PFQuery, Event) -> Void] = [] 116 | fileprivate var subscribeHandlers: [(PFQuery) -> Void] = [] 117 | fileprivate var unsubscribeHandlers: [(PFQuery) -> Void] = [] 118 | 119 | /** 120 | Creates a new subscription that can be used to handle updates. 121 | */ 122 | public init() { 123 | } 124 | 125 | /** 126 | Register a callback for when an error occurs. 127 | 128 | - parameter handler: The callback to register. 129 | 130 | - returns: The same subscription, for easy chaining 131 | */ 132 | @discardableResult open func handleError(_ handler: @escaping (PFQuery, Error) -> Void) -> Subscription { 133 | errorHandlers.append(handler) 134 | return self 135 | } 136 | 137 | /** 138 | Register a callback for when an event occurs. 139 | 140 | - parameter handler: The callback to register. 141 | 142 | - returns: The same subscription, for easy chaining. 143 | */ 144 | @discardableResult open func handleEvent(_ handler: @escaping (PFQuery, Event) -> Void) -> Subscription { 145 | eventHandlers.append(handler) 146 | return self 147 | } 148 | 149 | /** 150 | Register a callback for when a client succesfully subscribes to a query. 151 | 152 | - parameter handler: The callback to register. 153 | 154 | - returns: The same subscription, for easy chaining. 155 | */ 156 | @discardableResult open func handleSubscribe(_ handler: @escaping (PFQuery) -> Void) -> Subscription { 157 | subscribeHandlers.append(handler) 158 | return self 159 | } 160 | 161 | /** 162 | Register a callback for when a query has been unsubscribed. 163 | 164 | - parameter handler: The callback to register. 165 | 166 | - returns: The same subscription, for easy chaining. 167 | */ 168 | @discardableResult open func handleUnsubscribe(_ handler: @escaping (PFQuery) -> Void) -> Subscription { 169 | unsubscribeHandlers.append(handler) 170 | return self 171 | } 172 | 173 | // --------------- 174 | // MARK: SubscriptionHandling 175 | // TODO: Move to extension once swift compiler is less crashy 176 | // --------------- 177 | public typealias PFObjectSubclass = T 178 | 179 | open func didReceive(_ event: Event, forQuery query: PFQuery, inClient client: Client) { 180 | eventHandlers.forEach { $0(query, event) } 181 | } 182 | 183 | open func didEncounter(_ error: Error, forQuery query: PFQuery, inClient client: Client) { 184 | errorHandlers.forEach { $0(query, error) } 185 | } 186 | 187 | open func didSubscribe(toQuery query: PFQuery, inClient client: Client) { 188 | subscribeHandlers.forEach { $0(query) } 189 | } 190 | 191 | open func didUnsubscribe(fromQuery query: PFQuery, inClient client: Client) { 192 | unsubscribeHandlers.forEach { $0(query) } 193 | } 194 | } 195 | 196 | extension Subscription { 197 | /** 198 | Register a callback for when an error occcurs of a specific type 199 | 200 | Example: 201 | 202 | subscription.handle(LiveQueryErrors.InvalidJSONError.self) { query, error in 203 | print(error) 204 | } 205 | 206 | - parameter errorType: The error type to register for 207 | - parameter handler: The callback to register 208 | 209 | - returns: The same subscription, for easy chaining 210 | */ 211 | @discardableResult public func handle( 212 | _ errorType: E.Type = E.self, 213 | _ handler: @escaping (PFQuery, E) -> Void 214 | ) -> Subscription { 215 | errorHandlers.append { query, error in 216 | if let error = error as? E { 217 | handler(query, error) 218 | } 219 | } 220 | return self 221 | } 222 | 223 | /** 224 | Register a callback for when an event occurs of a specific type 225 | 226 | Example: 227 | 228 | subscription.handle(Event.Created) { query, object in 229 | // Called whenever an object is creaated 230 | } 231 | 232 | - parameter eventType: The event type to handle. You should pass one of the enum cases in `Event` 233 | - parameter handler: The callback to register 234 | 235 | - returns: The same subscription, for easy chaining 236 | 237 | */ 238 | @discardableResult public func handle(_ eventType: @escaping (T) -> Event, _ handler: @escaping (PFQuery, T) -> Void) -> Subscription { 239 | return handleEvent { query, event in 240 | switch event { 241 | case .entered(let obj) where eventType(obj) == event: handler(query, obj) 242 | case .left(let obj) where eventType(obj) == event: handler(query, obj) 243 | case .created(let obj) where eventType(obj) == event: handler(query, obj) 244 | case .updated(let obj) where eventType(obj) == event: handler(query, obj) 245 | case .deleted(let obj) where eventType(obj) == event: handler(query, obj) 246 | default: return 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /jazzy.sh: -------------------------------------------------------------------------------- 1 | ver=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" Sources/ParseLiveQuery/Info.plist` 2 | bundle exec jazzy \ 3 | --clean \ 4 | --author "Parse Community" \ 5 | --author_url http://parseplatform.org \ 6 | --github_url https://github.com/parse-community/ParseLiveQuery-iOS-OSX \ 7 | --root-url http://parseplatform.org/ParseLiveQuery-iOS-OSX/ \ 8 | --module-version ${ver} \ 9 | --theme fullwidth \ 10 | --skip-undocumented \ 11 | --output docs/api \ 12 | --module ParseLiveQuery \ 13 | --build-tool-arguments -scheme,ParseLiveQuery-iOS \ 14 | --------------------------------------------------------------------------------