feat: Add LiveQuery module to SDK; this deprecates the separate [Parse LiveQuery SDK](https://github.com/parse-community/ParseLiveQuery-iOS-OSX) (#1712)

This commit is contained in:
Volodymyr Nazarkevych 2023-06-08 20:05:38 +03:00 committed by GitHub
parent cbab34c66a
commit 154da34b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 5241 additions and 24 deletions

View File

@ -23,6 +23,7 @@ jobs:
- test:facebook_utils:ios
- test:twitter_utils:ios
- test:parseui:all
- test:parse_live_query:all
- package:release
fail-fast: false
runs-on: ${{ (matrix.script == 'package:release' && 'macos-11') || 'macos-12' }}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -1,3 +1,5 @@
github "BoltsFramework/Bolts-ObjC" ~> 1.9.1
github "BoltsFramework/Bolts-Swift" >= 1.5.0
github "facebook/facebook-ios-sdk" == 15.1.0
github "daltoniam/Starscream" >= 4.0.4

View File

@ -1,2 +1,4 @@
github "BoltsFramework/Bolts-ObjC" "1.9.1"
github "BoltsFramework/Bolts-Swift" "1.5.0"
github "daltoniam/Starscream" "4.0.4"
github "facebook/facebook-ios-sdk" "v15.1.0"

View File

@ -3,11 +3,20 @@
"pins": [
{
"package": "Bolts",
"repositoryURL": "https://github.com/rocxteady/Bolts-ObjC",
"repositoryURL": "https://github.com/parse-community/Bolts-ObjC.git",
"state": {
"branch": null,
"revision": "0419586ce3df0a004fbf94533198132de9c9aa0a",
"version": null
"revision": "1eee96ad3bcfc8964c0a5815ce94f491eb6ac8c2",
"version": "1.10.0"
}
},
{
"package": "BoltsSwift",
"repositoryURL": "https://github.com/BoltsFramework/Bolts-Swift.git",
"state": {
"branch": null,
"revision": "d8c07eee2045a13f34330c0a4664053b5176e3f0",
"version": "1.5.0"
}
},
{
@ -18,6 +27,15 @@
"revision": "7fd8a930a5b2c940a22efafe0e214ed0df671312",
"version": "15.1.0"
}
},
{
"package": "Starscream",
"repositoryURL": "https://github.com/daltoniam/Starscream.git",
"state": {
"branch": null,
"revision": "df8d82047f6654d8e4b655d1b1525c64e1059d21",
"version": "4.0.4"
}
}
]
},

View File

@ -6,7 +6,7 @@ let package = Package(
name: "ParseObjC",
defaultLocalization: "en",
platforms: [.iOS(.v12),
.macOS(.v10_10),
.macOS(.v10_15),
.tvOS(.v12),
.watchOS(.v2)],
products: [
@ -14,10 +14,13 @@ let package = Package(
.library(name: "ParseFacebookUtilsiOS", targets: ["ParseFacebookUtilsiOS"]),
.library(name: "ParseFacebookUtilsTvOS", targets: ["ParseFacebookUtilsTvOS"]),
.library(name: "ParseTwitterUtils", targets: ["ParseTwitterUtils"]),
.library(name: "ParseUI", targets: ["ParseUI"])
.library(name: "ParseUI", targets: ["ParseUI"]),
.library(name: "ParseLiveQuery", targets: ["ParseLiveQuery"])
],
dependencies: [
.package(url: "https://github.com/parse-community/Bolts-ObjC.git", from: "1.10.0"),
.package(url: "https://github.com/BoltsFramework/Bolts-Swift.git", from: "1.5.0"),
.package(url: "https://github.com/daltoniam/Starscream.git", from: "4.0.4"),
.package(url: "https://github.com/facebook/facebook-ios-sdk.git", from: "15.1.0")
],
targets: [
@ -37,7 +40,7 @@ let package = Package(
.product(name: "FacebookCore", package: "facebook-ios-sdk", condition: .when(platforms: [.iOS, .tvOS])),
.product(name: "FacebookLogin", package: "facebook-ios-sdk", condition: .when(platforms: [.iOS, .tvOS]))],
path: "ParseFacebookUtils/ParseFacebookUtils",
exclude: ["exclude", "Resources/Info-tvOS.plist", "Resources/Info-iOS.plist"],
exclude: ["Resources/Info-tvOS.plist", "Resources/Info-iOS.plist"],
resources: [.process("Resources")],
publicHeadersPath: "Source"),
.target(name: "ParseFacebookUtilsiOS",
@ -45,7 +48,7 @@ let package = Package(
"ParseFacebookUtils"
],
path: "ParseFacebookUtilsiOS/ParseFacebookUtilsiOS",
exclude: ["exclude", "Resources/Info-iOS.plist"],
exclude: ["Resources/Info-iOS.plist"],
resources: [.process("Resources")],
publicHeadersPath: "Source",
cSettings: [.headerSearchPath("Internal/**")]),
@ -55,7 +58,7 @@ let package = Package(
.product(name: "FacebookTV", package: "facebook-ios-sdk", condition: .when(platforms: [.tvOS]))
],
path: "ParseFacebookUtilsTvOS/ParseFacebookUtilsTvOS",
exclude: ["exclude", "Resources/Info-tvOS.plist"],
exclude: ["Resources/Info-tvOS.plist"],
resources: [.process("Resources")],
publicHeadersPath: "Source",
cSettings: [.headerSearchPath("Internal/**")]),
@ -78,5 +81,14 @@ let package = Package(
resources: [.process("Resources")],
publicHeadersPath: "Source",
cSettings: [.headerSearchPath("Internal/**")]),
.target(name: "ParseLiveQuery",
dependencies: [
.product(name: "BoltsSwift", package: "Bolts-Swift"),
"Starscream",
"ParseCore"
],
path: "ParseLiveQuery/ParseLiveQuery",
exclude: ["Resources/Info.plist"],
resources: [.process("Resources")])
]
)

View File

@ -16,6 +16,9 @@
<FileRef
location = "group:ParseTwitterUtils/ParseTwitterUtils.xcodeproj">
</FileRef>
<FileRef
location = "group:ParseLiveQuery/ParseLiveQuery.xcodeproj">
</FileRef>
<FileRef
location = "group:ParseUI/ParseUI.xcodeproj">
</FileRef>

View File

@ -2962,6 +2962,69 @@
remoteGlobalIDString = 81C3821B19CCA89E0066284A;
remoteInfo = "Parse-iOS";
};
9575FF1E299136C60057B4CE /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9575FF10299136C60057B4CE /* Starscream.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 33CCF0921F5DDC030099B092;
remoteInfo = Starscream;
};
9575FF20299136C60057B4CE /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 9575FF10299136C60057B4CE /* Starscream.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 335FA2021F5DF71D00F6D2EC;
remoteInfo = "Starscream Tests";
};
95AEEB192991373F00165C0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87FEF3661A9085FA00C60678;
remoteInfo = "BoltsSwift-iOS";
};
95AEEB1B2991373F00165C0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87FEF3711A9085FA00C60678;
remoteInfo = "BoltsSwiftTests-iOS";
};
95AEEB1D2991373F00165C0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 81CC14EC1A9BE0A100B28F86;
remoteInfo = "BoltsSwift-macOS";
};
95AEEB1F2991373F00165C0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 81CC14F61A9BE0A100B28F86;
remoteInfo = "BoltsSwiftTests-macOS";
};
95AEEB212991373F00165C0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 065894FF1C9A93B7000FDDA6;
remoteInfo = "BoltsSwift-tvOS";
};
95AEEB232991373F00165C0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 0658951B1C9A947B000FDDA6;
remoteInfo = "BoltsSwiftTests-tvOS";
};
95AEEB252991373F00165C0D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 065894E71C9A933B000FDDA6;
remoteInfo = "BoltsSwift-watchOS";
};
BC105FC424C5D0C900295EF7 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BC105FBA24C5D0C900295EF7 /* OCMock.xcodeproj */;
@ -3575,6 +3638,8 @@
91DF24941A09BAF100CFC7D4 /* PFPinningEventuallyQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFPinningEventuallyQueue.h; sourceTree = "<group>"; };
91DF24951A09BAF100CFC7D4 /* PFPinningEventuallyQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFPinningEventuallyQueue.m; sourceTree = "<group>"; };
91DF24981A0B0FF200CFC7D4 /* PFEventuallyQueue_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFEventuallyQueue_Private.h; sourceTree = "<group>"; };
9575FF10299136C60057B4CE /* Starscream.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Starscream.xcodeproj; path = ../Carthage/Checkouts/Starscream/Starscream.xcodeproj; sourceTree = "<group>"; };
95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BoltsSwift.xcodeproj; path = "../Carthage/Checkouts/Bolts-Swift/BoltsSwift.xcodeproj"; sourceTree = "<group>"; };
97010FAC1630B18F00AB761E /* Parse.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Parse.framework; sourceTree = BUILT_PRODUCTS_DIR; };
97AA93B816780B7600445C2D /* Parse-OSX.Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Parse-OSX.Info.plist"; sourceTree = "<group>"; };
97E18AE41623835600B17A67 /* PFLocationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFLocationManager.h; sourceTree = "<group>"; };
@ -3872,6 +3937,8 @@
7CE6ABDD292074C70054D9D2 /* AudioToolbox.framework */,
7CE6ABD2292074C10054D9D2 /* libsqlite3.tbd */,
BC105FBA24C5D0C900295EF7 /* OCMock.xcodeproj */,
9575FF10299136C60057B4CE /* Starscream.xcodeproj */,
95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */,
4A1351082027FCFB000F5FD5 /* Bolts.xcodeproj */,
);
name = Frameworks;
@ -5148,6 +5215,29 @@
path = CurrentUserController;
sourceTree = "<group>";
};
9575FF11299136C60057B4CE /* Products */ = {
isa = PBXGroup;
children = (
9575FF1F299136C60057B4CE /* Starscream.framework */,
9575FF21299136C60057B4CE /* Starscream Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
95AEEB102991373F00165C0D /* Products */ = {
isa = PBXGroup;
children = (
95AEEB1A2991373F00165C0D /* BoltsSwift.framework */,
95AEEB1C2991373F00165C0D /* BoltsSwiftTests.xctest */,
95AEEB1E2991373F00165C0D /* BoltsSwift.framework */,
95AEEB202991373F00165C0D /* BoltsSwiftTests.xctest */,
95AEEB222991373F00165C0D /* BoltsSwift.framework */,
95AEEB242991373F00165C0D /* BoltsSwiftTests.xctest */,
95AEEB262991373F00165C0D /* BoltsSwift.framework */,
);
name = Products;
sourceTree = "<group>";
};
BC105FBB24C5D0C900295EF7 /* Products */ = {
isa = PBXGroup;
children = (
@ -7076,10 +7166,18 @@
ProductGroup = 4A13517620281768000F5FD5 /* Products */;
ProjectRef = 4A1351082027FCFB000F5FD5 /* Bolts.xcodeproj */;
},
{
ProductGroup = 95AEEB102991373F00165C0D /* Products */;
ProjectRef = 95AEEB0F2991373F00165C0D /* BoltsSwift.xcodeproj */;
},
{
ProductGroup = BC105FBB24C5D0C900295EF7 /* Products */;
ProjectRef = BC105FBA24C5D0C900295EF7 /* OCMock.xcodeproj */;
},
{
ProductGroup = 9575FF11299136C60057B4CE /* Products */;
ProjectRef = 9575FF10299136C60057B4CE /* Starscream.xcodeproj */;
},
);
projectRoot = "";
targets = (
@ -7175,6 +7273,69 @@
remoteRef = 4A13519720281768000F5FD5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
9575FF1F299136C60057B4CE /* Starscream.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Starscream.framework;
remoteRef = 9575FF1E299136C60057B4CE /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
9575FF21299136C60057B4CE /* Starscream Tests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = "Starscream Tests.xctest";
remoteRef = 9575FF20299136C60057B4CE /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
95AEEB1A2991373F00165C0D /* BoltsSwift.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = BoltsSwift.framework;
remoteRef = 95AEEB192991373F00165C0D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
95AEEB1C2991373F00165C0D /* BoltsSwiftTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = BoltsSwiftTests.xctest;
remoteRef = 95AEEB1B2991373F00165C0D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
95AEEB1E2991373F00165C0D /* BoltsSwift.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = BoltsSwift.framework;
remoteRef = 95AEEB1D2991373F00165C0D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
95AEEB202991373F00165C0D /* BoltsSwiftTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = BoltsSwiftTests.xctest;
remoteRef = 95AEEB1F2991373F00165C0D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
95AEEB222991373F00165C0D /* BoltsSwift.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = BoltsSwift.framework;
remoteRef = 95AEEB212991373F00165C0D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
95AEEB242991373F00165C0D /* BoltsSwiftTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = BoltsSwiftTests.xctest;
remoteRef = 95AEEB232991373F00165C0D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
95AEEB262991373F00165C0D /* BoltsSwift.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = BoltsSwift.framework;
remoteRef = 95AEEB252991373F00165C0D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
BC105FC524C5D0C900295EF7 /* OCMock.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
@ -8868,7 +9029,7 @@
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.15;
};
name = Debug;
};
@ -8885,7 +9046,7 @@
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.15;
SWIFT_COMPILATION_MODE = wholemodule;
};
name = Release;
@ -9070,7 +9231,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = F55ABB5A1B4F39DA00A0ECD5 /* ParseUnitTests-macOS.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.12;
MACOSX_DEPLOYMENT_TARGET = 10.15;
SWIFT_VERSION = 5.0;
};
name = Debug;
@ -9079,7 +9240,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = F55ABB5A1B4F39DA00A0ECD5 /* ParseUnitTests-macOS.xcconfig */;
buildSettings = {
MACOSX_DEPLOYMENT_TARGET = 10.12;
MACOSX_DEPLOYMENT_TARGET = 10.15;
SWIFT_VERSION = 5.0;
};
name = Release;
@ -9098,7 +9259,7 @@
);
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 5.7;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -9116,7 +9277,7 @@
"@loader_path/Frameworks",
);
SUPPORTS_MACCATALYST = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 5.7;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@ -9188,7 +9349,7 @@
baseConfigurationReference = F55ABB541B4F39DA00A0ECD5 /* Parse-macOS.xcconfig */;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MACOSX_DEPLOYMENT_TARGET = 10.15;
};
name = Debug;
};
@ -9197,7 +9358,7 @@
baseConfigurationReference = F55ABB541B4F39DA00A0ECD5 /* Parse-macOS.xcconfig */;
buildSettings = {
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MACOSX_DEPLOYMENT_TARGET = 10.15;
};
name = Release;
};

View File

@ -0,0 +1,393 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
395FFA7429D761B4006502C5 /* ParseLiveQuery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 395FFA4429D75F1F006502C5 /* ParseLiveQuery.framework */; };
F509D5461CA9E5B8007B15B0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBB41CA9CA04005295C0 /* main.m */; };
F509D5471CA9E5B8007B15B0 /* Message.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBBC1CA9CA2B005295C0 /* Message.m */; };
F509D5481CA9E5B8007B15B0 /* Room.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBBF1CA9CA34005295C0 /* Room.m */; };
F509D5491CA9E5B8007B15B0 /* ChatRoomManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F519CBCF1CA9CC4D005295C0 /* ChatRoomManager.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
395FFA4129D75F1F006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA3A29D75F1F006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = F5A9BFCA1BE0248D00E78326;
remoteInfo = "ParseLiveQuery-iOS";
};
395FFA4329D75F1F006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA3A29D75F1F006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = F5903CEA1BD999C500C3EFFE;
remoteInfo = "ParseLiveQuery-OSX";
};
395FFA4529D75F1F006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA3A29D75F1F006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 708836722561F502005B32F0;
remoteInfo = "ParseLiveQuery-watchOS";
};
395FFA4729D75F1F006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA3A29D75F1F006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 708836942561F55B005B32F0;
remoteInfo = "ParseLiveQuery-tvOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
395FFA3A29D75F1F006502C5 /* ParseLiveQuery.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParseLiveQuery.xcodeproj; path = ../ParseLiveQuery.xcodeproj; sourceTree = "<group>"; };
F509D5321CA9E597007B15B0 /* LiveQueryDemo-ObjC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LiveQueryDemo-ObjC.app"; sourceTree = BUILT_PRODUCTS_DIR; };
F509D5441CA9E5AF007B15B0 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F519CBB41CA9CA04005295C0 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
F519CBBB1CA9CA2B005295C0 /* Message.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Message.h; sourceTree = "<group>"; };
F519CBBC1CA9CA2B005295C0 /* Message.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Message.m; sourceTree = "<group>"; };
F519CBBE1CA9CA34005295C0 /* Room.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Room.h; sourceTree = "<group>"; };
F519CBBF1CA9CA34005295C0 /* Room.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Room.m; sourceTree = "<group>"; };
F519CBCE1CA9CC4D005295C0 /* ChatRoomManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChatRoomManager.h; sourceTree = "<group>"; };
F519CBCF1CA9CC4D005295C0 /* ChatRoomManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChatRoomManager.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
F509D52F1CA9E597007B15B0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
395FFA7429D761B4006502C5 /* ParseLiveQuery.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
395FFA3B29D75F1F006502C5 /* Products */ = {
isa = PBXGroup;
children = (
395FFA4229D75F1F006502C5 /* ParseLiveQuery.framework */,
395FFA4429D75F1F006502C5 /* ParseLiveQuery.framework */,
395FFA4629D75F1F006502C5 /* ParseLiveQuery_watchOS.framework */,
395FFA4829D75F1F006502C5 /* ParseLiveQuery_tvOS.framework */,
);
name = Products;
sourceTree = "<group>";
};
E0B5CD933BEBE8518E7750F2 /* Frameworks */ = {
isa = PBXGroup;
children = (
395FFA3A29D75F1F006502C5 /* ParseLiveQuery.xcodeproj */,
);
name = Frameworks;
sourceTree = "<group>";
};
F519CBA81CA9CA04005295C0 = {
isa = PBXGroup;
children = (
F519CBB31CA9CA04005295C0 /* LiveQueryDemo-ObjC */,
F519CBB21CA9CA04005295C0 /* Products */,
E0B5CD933BEBE8518E7750F2 /* Frameworks */,
);
sourceTree = "<group>";
};
F519CBB21CA9CA04005295C0 /* Products */ = {
isa = PBXGroup;
children = (
F509D5321CA9E597007B15B0 /* LiveQueryDemo-ObjC.app */,
);
name = Products;
sourceTree = "<group>";
};
F519CBB31CA9CA04005295C0 /* LiveQueryDemo-ObjC */ = {
isa = PBXGroup;
children = (
F509D5441CA9E5AF007B15B0 /* Info.plist */,
F519CBB41CA9CA04005295C0 /* main.m */,
F519CBBB1CA9CA2B005295C0 /* Message.h */,
F519CBBC1CA9CA2B005295C0 /* Message.m */,
F519CBBE1CA9CA34005295C0 /* Room.h */,
F519CBBF1CA9CA34005295C0 /* Room.m */,
F519CBCE1CA9CC4D005295C0 /* ChatRoomManager.h */,
F519CBCF1CA9CC4D005295C0 /* ChatRoomManager.m */,
);
path = "LiveQueryDemo-ObjC";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
F509D5311CA9E597007B15B0 /* LiveQueryDemo-ObjC */ = {
isa = PBXNativeTarget;
buildConfigurationList = F509D5431CA9E597007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo-ObjC" */;
buildPhases = (
F509D52E1CA9E597007B15B0 /* Sources */,
F509D52F1CA9E597007B15B0 /* Frameworks */,
F509D5301CA9E597007B15B0 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "LiveQueryDemo-ObjC";
productName = LiveQueryDemo;
productReference = F509D5321CA9E597007B15B0 /* LiveQueryDemo-ObjC.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
F519CBA91CA9CA04005295C0 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1100;
ORGANIZATIONNAME = parse;
TargetAttributes = {
F509D5311CA9E597007B15B0 = {
CreatedOnToolsVersion = 7.3;
};
};
};
buildConfigurationList = F519CBAC1CA9CA04005295C0 /* Build configuration list for PBXProject "LiveQueryDemo-ObjC" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = F519CBA81CA9CA04005295C0;
productRefGroup = F519CBB21CA9CA04005295C0 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 395FFA3B29D75F1F006502C5 /* Products */;
ProjectRef = 395FFA3A29D75F1F006502C5 /* ParseLiveQuery.xcodeproj */;
},
);
projectRoot = "";
targets = (
F509D5311CA9E597007B15B0 /* LiveQueryDemo-ObjC */,
);
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
395FFA4229D75F1F006502C5 /* ParseLiveQuery.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery.framework;
remoteRef = 395FFA4129D75F1F006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
395FFA4429D75F1F006502C5 /* ParseLiveQuery.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery.framework;
remoteRef = 395FFA4329D75F1F006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
395FFA4629D75F1F006502C5 /* ParseLiveQuery_watchOS.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery_watchOS.framework;
remoteRef = 395FFA4529D75F1F006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
395FFA4829D75F1F006502C5 /* ParseLiveQuery_tvOS.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery_tvOS.framework;
remoteRef = 395FFA4729D75F1F006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
F509D5301CA9E597007B15B0 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
F509D52E1CA9E597007B15B0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F509D5491CA9E5B8007B15B0 /* ChatRoomManager.m in Sources */,
F509D5481CA9E5B8007B15B0 /* Room.m in Sources */,
F509D5461CA9E5B8007B15B0 /* main.m in Sources */,
F509D5471CA9E5B8007B15B0 /* Message.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
F509D5401CA9E597007B15B0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo-ObjC/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
};
name = Debug;
};
F509D5411CA9E597007B15B0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo-ObjC/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
};
name = Release;
};
F519CBB61CA9CA04005295C0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
F519CBB71CA9CA04005295C0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
F509D5431CA9E597007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo-ObjC" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F509D5401CA9E597007B15B0 /* Debug */,
F509D5411CA9E597007B15B0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F519CBAC1CA9CA04005295C0 /* Build configuration list for PBXProject "LiveQueryDemo-ObjC" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F519CBB61CA9CA04005295C0 /* Debug */,
F519CBB71CA9CA04005295C0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = F519CBA91CA9CA04005295C0 /* Project object */;
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5311CA9E597007B15B0"
BuildableName = "LiveQueryDemo-ObjC.app"
BlueprintName = "LiveQueryDemo-ObjC"
ReferencedContainer = "container:LiveQueryDemo-ObjC.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5311CA9E597007B15B0"
BuildableName = "LiveQueryDemo-ObjC.app"
BlueprintName = "LiveQueryDemo-ObjC"
ReferencedContainer = "container:LiveQueryDemo-ObjC.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5311CA9E597007B15B0"
BuildableName = "LiveQueryDemo-ObjC.app"
BlueprintName = "LiveQueryDemo-ObjC"
ReferencedContainer = "container:LiveQueryDemo-ObjC.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5311CA9E597007B15B0"
BuildableName = "LiveQueryDemo-ObjC.app"
BlueprintName = "LiveQueryDemo-ObjC"
ReferencedContainer = "container:LiveQueryDemo-ObjC.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
@import Foundation;
@import ParseCore;
@import ParseLiveQuery;
#import "Message.h"
NS_ASSUME_NONNULL_BEGIN
@class ChatRoomManager;
@protocol ChatRoomManagerDataSource <NSObject>
- (PFQuery *)queryForChatRoomManager:(ChatRoomManager *)manager;
- (PFLiveQueryClient *)liveQueryClientForChatRoomManager:(ChatRoomManager *)manager;
@end
@protocol ChatRoomManagerDelegate <NSObject>
- (void)chatRoomManager:(ChatRoomManager *)manager didReceiveMessage:(Message *)message;
@end
@interface ChatRoomManager : NSObject
@property (nonatomic, assign, readonly, getter=isConnected) BOOL connected;
@property (nonatomic, weak, readonly) id<ChatRoomManagerDataSource> dataSource;
@property (nonatomic, weak, readonly) id<ChatRoomManagerDelegate> delegate;
- (instancetype)initWithDataSource:(id<ChatRoomManagerDataSource>)dataSource delegate:(id<ChatRoomManagerDelegate>)delegate;
- (void)connect;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ChatRoomManager.h"
@interface ChatRoomManager()
@property (nonatomic, strong) PFLiveQueryClient *client;
@property (nonatomic, strong) PFQuery *query;
@property (nonatomic, strong) PFLiveQuerySubscription *subscription;
@end
@implementation ChatRoomManager
- (instancetype)initWithDataSource:(id<ChatRoomManagerDataSource>)dataSource delegate:(id<ChatRoomManagerDelegate>)delegate{
self = [super init];
if (!self) return self;
_dataSource = dataSource;
_delegate = delegate;
return self;
}
- (BOOL)isConnected {
return self.subscription != nil;
}
- (void)connect {
self.client = [self.dataSource liveQueryClientForChatRoomManager:self];
self.query = [self.dataSource queryForChatRoomManager:self];
__weak typeof(self) weakSelf = self;
self.subscription = [[self.client subscribeToQuery:self.query] addCreateHandler:^(PFQuery *query, PFObject *message) {
[weakSelf.delegate chatRoomManager:weakSelf didReceiveMessage:(Message *)message];
}];
}
- (void)disconnect {
self.client = nil;
self.query = nil;
self.subscription = nil;
}
@end

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Parse. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
@import ParseCore;
NS_ASSUME_NONNULL_BEGIN
@interface Message : PFObject <PFSubclassing>
@property (nullable, nonatomic, strong) PFUser *author;
@property (nullable, nonatomic, strong) NSString *authorName;
@property (nullable, nonatomic, strong) NSString *message;
@property (nullable, nonatomic, strong) PFObject *room;
@property (nullable, nonatomic, strong) NSString *roomName;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "Message.h"
@implementation Message
@dynamic author, authorName, message, room, roomName;
+ (NSString *)parseClassName {
return @"Message";
}
@end

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Parse/Parse.h>
NS_ASSUME_NONNULL_BEGIN
@interface Room : PFObject <PFSubclassing>
@property (nullable, nonatomic, strong) NSString *name;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "Room.h"
@implementation Room
@dynamic name;
+ (NSString *)parseClassName {
return @"Room";
}
@end

View File

@ -0,0 +1,154 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
@import Foundation;
@import ParseCore;
@import ParseLiveQuery;
#import "ChatRoomManager.h"
#import "Message.h"
#import "Room.h"
BFTask<PFUser *> *AttemptLogin() {
puts("Enter username: ");
char buffer[1024];
fgets(buffer, 1024, stdin);
NSString *usernameInput = [NSString stringWithUTF8String:buffer];
NSString *prompt = [NSString stringWithFormat:@"Enter password for %@", usernameInput];
NSString *passwordInput = [NSString stringWithUTF8String:getpass([prompt UTF8String])];
NSString *username = [usernameInput stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSString *password = [passwordInput stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
return [[PFUser logInWithUsernameInBackground:username password:password] continueWithBlock:^id (BFTask<PFUser *> *task) {
if (task.result) {
return task.result;
}
puts("Login failed, please try again.");
return AttemptLogin();
}];
}
BFTask<Room *> *AttemptRoom() {
puts("Enter chat room to connect to: ");
char buffer[1024];
fgets(buffer, 1024, stdin);
NSString *roomName = [NSString stringWithUTF8String:buffer];
return [[[[Room query] whereKey:@"name"
equalTo:[roomName stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]]
getFirstObjectInBackground]
continueWithBlock:^id _Nullable(BFTask * _Nonnull task) {
if (task.result) {
return task.result;
}
puts("Room not found, please try again.");
return AttemptRoom();
}];
}
@interface ChatRoomHandler : NSObject <ChatRoomManagerDataSource, ChatRoomManagerDelegate>
@property (nonatomic, strong, readonly) Room *room;
@property (nonatomic, strong, readonly) PFLiveQueryClient *client;
@end
@implementation ChatRoomHandler
- (instancetype)initWithRoom:(Room *)room {
self = [super init];
if (!self) return self;
_room = room;
_client = [[PFLiveQueryClient alloc] init];
return self;
}
- (PFQuery *)queryForChatRoomManager:(ChatRoomManager *)manager {
return [[[Message query] whereKey:@"roomName"
equalTo:self.room.name]
orderByAscending:@"createdAt"];
}
- (PFLiveQueryClient *)liveQueryClientForChatRoomManager:(ChatRoomManager *)manager {
return _client;
}
- (void)chatRoomManager:(ChatRoomManager *)manager didReceiveMessage:(Message *)message {
NSString *formatted = [NSString stringWithFormat:@"%@ %@ %@", message.createdAt, message.authorName, message.message];
printf("%s\n", formatted.UTF8String);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Message registerSubclass];
[Room registerSubclass];
[Parse initializeWithConfiguration:[ParseClientConfiguration configurationWithBlock:^(id<ParseMutableClientConfiguration> configuration) {
configuration.applicationId = @"myAppId";
// configuration.clientKey = @"myClientKey";
configuration.server = @"http://localhost:1337/parse";
}]];
[[AttemptLogin() continueWithBlock:^id (BFTask<PFUser *> *task) {
return AttemptRoom();
}] continueWithBlock:^id (BFTask<Room *> *task) {
Room *room = task.result;
ChatRoomHandler *handler = [[ChatRoomHandler alloc] initWithRoom:room];
ChatRoomManager *manager = [[ChatRoomManager alloc] initWithDataSource:handler delegate:handler];
// Print out the previous messages
PFQuery *query = [handler queryForChatRoomManager:manager];
[[query findObjectsInBackground] continueWithBlock:^id (BFTask *task) {
for (Message *message in task.result) {
[handler chatRoomManager:manager didReceiveMessage:message];
}
[manager connect];
return nil;
}];
dispatch_io_t stdinChannel = dispatch_io_create(DISPATCH_IO_STREAM, STDIN_FILENO, dispatch_get_main_queue(), ^(int error) {
perror("dispatch_io_create");
});
dispatch_io_set_low_water(stdinChannel, 1);
dispatch_io_read(stdinChannel, 0, SIZE_MAX, dispatch_get_main_queue(), ^(bool done, dispatch_data_t data, int error) {
NSString *messageText = [[[NSString alloc] initWithData:(NSData *)data
encoding:NSUTF8StringEncoding]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
Message *message = [[Message alloc] init];
message.author = [PFUser currentUser];
message.authorName = [PFUser currentUser].username;
message.message = messageText;
message.room = room;
message.roomName = room.name;
[message saveInBackground];
});
return nil;
}];
dispatch_main();
}
return 0;
}

View File

@ -0,0 +1,389 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
F509D5291CA9E53D007B15B0 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F85B61C9BB4B600566A29 /* Message.swift */; };
F509D52A1CA9E53D007B15B0 /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F85B71C9BB4B600566A29 /* Room.swift */; };
F509D52B1CA9E53D007B15B0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F85AF1C9BB48200566A29 /* main.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
395FFA9129D7740B006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA8A29D7740B006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = F5A9BFCA1BE0248D00E78326;
remoteInfo = "ParseLiveQuery-iOS";
};
395FFA9329D7740B006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA8A29D7740B006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = F5903CEA1BD999C500C3EFFE;
remoteInfo = "ParseLiveQuery-OSX";
};
395FFA9529D7740B006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA8A29D7740B006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 708836722561F502005B32F0;
remoteInfo = "ParseLiveQuery-watchOS";
};
395FFA9729D7740B006502C5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 395FFA8A29D7740B006502C5 /* ParseLiveQuery.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 708836942561F55B005B32F0;
remoteInfo = "ParseLiveQuery-tvOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
395FFA8A29D7740B006502C5 /* ParseLiveQuery.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ParseLiveQuery.xcodeproj; path = ../ParseLiveQuery.xcodeproj; sourceTree = "<group>"; };
F509D5171CA9E4AE007B15B0 /* LiveQueryDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveQueryDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
F509D5241CA9E4AE007B15B0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F59F85AF1C9BB48200566A29 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
F59F85B61C9BB4B600566A29 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
F59F85B71C9BB4B600566A29 /* Room.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
F509D5141CA9E4AE007B15B0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2E2DAD338FCB65EC95CB7AA9 /* Frameworks */ = {
isa = PBXGroup;
children = (
395FFA8A29D7740B006502C5 /* ParseLiveQuery.xcodeproj */,
);
name = Frameworks;
sourceTree = "<group>";
};
395FFA8B29D7740B006502C5 /* Products */ = {
isa = PBXGroup;
children = (
395FFA9229D7740B006502C5 /* ParseLiveQuery.framework */,
395FFA9429D7740B006502C5 /* ParseLiveQuery.framework */,
395FFA9629D7740B006502C5 /* ParseLiveQuery_watchOS.framework */,
395FFA9829D7740B006502C5 /* ParseLiveQuery_tvOS.framework */,
);
name = Products;
sourceTree = "<group>";
};
F59F85A31C9BB48200566A29 = {
isa = PBXGroup;
children = (
F59F85AE1C9BB48200566A29 /* LiveQueryDemo */,
F59F85AD1C9BB48200566A29 /* Products */,
2E2DAD338FCB65EC95CB7AA9 /* Frameworks */,
);
indentWidth = 4;
sourceTree = "<group>";
tabWidth = 4;
};
F59F85AD1C9BB48200566A29 /* Products */ = {
isa = PBXGroup;
children = (
F509D5171CA9E4AE007B15B0 /* LiveQueryDemo.app */,
);
name = Products;
sourceTree = "<group>";
};
F59F85AE1C9BB48200566A29 /* LiveQueryDemo */ = {
isa = PBXGroup;
children = (
F509D5241CA9E4AE007B15B0 /* Info.plist */,
F59F85B61C9BB4B600566A29 /* Message.swift */,
F59F85B71C9BB4B600566A29 /* Room.swift */,
F59F85AF1C9BB48200566A29 /* main.swift */,
);
path = LiveQueryDemo;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
F509D5161CA9E4AE007B15B0 /* LiveQueryDemo */ = {
isa = PBXNativeTarget;
buildConfigurationList = F509D5251CA9E4AE007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo" */;
buildPhases = (
F509D5131CA9E4AE007B15B0 /* Sources */,
F509D5141CA9E4AE007B15B0 /* Frameworks */,
F509D5151CA9E4AE007B15B0 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = LiveQueryDemo;
productName = AppKitDemo;
productReference = F509D5171CA9E4AE007B15B0 /* LiveQueryDemo.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
F59F85A41C9BB48200566A29 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0720;
LastUpgradeCheck = 1100;
ORGANIZATIONNAME = Parse;
TargetAttributes = {
F509D5161CA9E4AE007B15B0 = {
CreatedOnToolsVersion = 7.3;
LastSwiftMigration = 1120;
};
};
};
buildConfigurationList = F59F85A71C9BB48200566A29 /* Build configuration list for PBXProject "LiveQueryDemo" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = F59F85A31C9BB48200566A29;
productRefGroup = F59F85AD1C9BB48200566A29 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 395FFA8B29D7740B006502C5 /* Products */;
ProjectRef = 395FFA8A29D7740B006502C5 /* ParseLiveQuery.xcodeproj */;
},
);
projectRoot = "";
targets = (
F509D5161CA9E4AE007B15B0 /* LiveQueryDemo */,
);
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
395FFA9229D7740B006502C5 /* ParseLiveQuery.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery.framework;
remoteRef = 395FFA9129D7740B006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
395FFA9429D7740B006502C5 /* ParseLiveQuery.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery.framework;
remoteRef = 395FFA9329D7740B006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
395FFA9629D7740B006502C5 /* ParseLiveQuery_watchOS.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery_watchOS.framework;
remoteRef = 395FFA9529D7740B006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
395FFA9829D7740B006502C5 /* ParseLiveQuery_tvOS.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = ParseLiveQuery_tvOS.framework;
remoteRef = 395FFA9729D7740B006502C5 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
F509D5151CA9E4AE007B15B0 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
F509D5131CA9E4AE007B15B0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F509D52A1CA9E53D007B15B0 /* Room.swift in Sources */,
F509D5291CA9E53D007B15B0 /* Message.swift in Sources */,
F509D52B1CA9E53D007B15B0 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
F509D5261CA9E4AE007B15B0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_ANALYZER_NONNULL = YES;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
F509D5271CA9E4AE007B15B0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CLANG_ANALYZER_NONNULL = YES;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "$(SRCROOT)/LiveQueryDemo/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.LiveQueryDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
F59F85B11C9BB48200566A29 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
F59F85B21C9BB48200566A29 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
F509D5251CA9E4AE007B15B0 /* Build configuration list for PBXNativeTarget "LiveQueryDemo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F509D5261CA9E4AE007B15B0 /* Debug */,
F509D5271CA9E4AE007B15B0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F59F85A71C9BB48200566A29 /* Build configuration list for PBXProject "LiveQueryDemo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F59F85B11C9BB48200566A29 /* Debug */,
F59F85B21C9BB48200566A29 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = F59F85A41C9BB48200566A29 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5161CA9E4AE007B15B0"
BuildableName = "LiveQueryDemo.app"
BlueprintName = "LiveQueryDemo"
ReferencedContainer = "container:LiveQueryDemo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5161CA9E4AE007B15B0"
BuildableName = "LiveQueryDemo.app"
BlueprintName = "LiveQueryDemo"
ReferencedContainer = "container:LiveQueryDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5161CA9E4AE007B15B0"
BuildableName = "LiveQueryDemo.app"
BlueprintName = "LiveQueryDemo"
ReferencedContainer = "container:LiveQueryDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F509D5161CA9E4AE007B15B0"
BuildableName = "LiveQueryDemo.app"
BlueprintName = "LiveQueryDemo"
ReferencedContainer = "container:LiveQueryDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Parse. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
import ParseCore
class Message: PFObject, PFSubclassing {
@NSManaged var author: PFUser?
@NSManaged var authorName: String?
@NSManaged var message: String?
@NSManaged var room: PFObject?
@NSManaged var roomName: String?
class func parseClassName() -> String {
return "Message"
}
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
import ParseCore
class Room: PFObject, PFSubclassing {
@NSManaged var name: String?
static func parseClassName() -> String {
return "Room"
}
}

View File

@ -0,0 +1,135 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
import ParseCore
import ParseLiveQuery
Message.registerSubclass()
Room.registerSubclass()
Parse.initialize(with: ParseClientConfiguration {
$0.applicationId = "myAppId"
// $0.clientKey = "myClientKey"
$0.server = "http://localhost:1337/parse"
})
let liveQueryClient = ParseLiveQuery.Client()
class ChatRoomManager {
fileprivate var currentChatRoom: Room?
fileprivate var subscription: Subscription<Message>?
var connected: Bool { return currentChatRoom != nil }
var messagesQuery: PFQuery<Message> {
return (Message.query()?
.whereKey("roomName", equalTo: currentChatRoom!.name!)
.order(byAscending: "createdAt")) as! PFQuery<Message>
}
func connectToChatRoom(_ room: String) {
if connected {
disconnectFromChatRoom()
}
Room.query()?.whereKey("name", equalTo: room).getFirstObjectInBackground()
.continueOnSuccessWith(block: { task -> Any? in
self.currentChatRoom = task.result as? Room
print("Connected to room \(self.currentChatRoom?.name ?? "null")")
self.printPriorMessages()
self.subscribeToUpdates()
return nil
})
}
func disconnectFromChatRoom() {
liveQueryClient.unsubscribe(messagesQuery, handler: subscription!)
}
func sendMessage(_ msg: String) {
let message = Message()
message.author = PFUser.current()
message.authorName = message.author?.username
message.message = msg
message.room = currentChatRoom
message.roomName = currentChatRoom?.name
message.saveInBackground()
}
func printPriorMessages() {
messagesQuery.findObjectsInBackground()
.continueOnSuccessWith(block: { task -> Any? in
(task.result as? [Message])?.forEach(self.printMessage)
return nil
})
}
func subscribeToUpdates() {
subscription = liveQueryClient
.subscribe(messagesQuery)
.handle(Event.created) { _, message in
self.printMessage(message)
}
}
fileprivate func printMessage(_ message: Message) {
let createdAt = message.createdAt ?? Date()
print("\(createdAt) \(message.authorName ?? "unknown"): \(message.message ?? "")")
}
}
class InputManager {
let stdinChannel = DispatchIO(__type: DispatchIO.StreamType.stream.rawValue, fd: STDIN_FILENO, queue: DispatchQueue.main) { _ in }
let chatManager: ChatRoomManager
init(chatManager: ChatRoomManager) {
self.chatManager = chatManager
stdinChannel.setLimit(lowWater: 1)
stdinChannel.read(offset: 0, length: Int.max, queue: DispatchQueue.main, ioHandler: handleInput)
}
fileprivate func handleInput(_ done: Bool, data: DispatchData?, error: Int32) {
guard
let inputString = data?.withUnsafeBytes(body: {(b: UnsafePointer<UInt8>) -> String? in
return String(cString: b)
})?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) else {
return
}
if chatManager.connected {
chatManager.sendMessage(inputString)
} else {
chatManager.connectToChatRoom(inputString)
}
}
}
print("Enter username: ")
let username = readLine()!
let password = "Enter password for \(username): ".withCString {
String(validatingUTF8: getpass($0))!
}
let chatManager = ChatRoomManager()
let inputManager = InputManager(chatManager: chatManager)
PFUser.logInWithUsername(inBackground: username, password: password)
.continueOnSuccessWith(block: { task -> Any? in
print("Enter chat room to connect to: ")
return nil
})
dispatchMain()

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>2.2.0</string>
<key>CFBundleVersion</key>
<string>2.2.0</string>
</dict>
</plist>

View File

@ -0,0 +1,19 @@
//
// ParseLiveQuery_tvOS.h
// ParseLiveQuery-tvOS
//
// Created by Corey Baker on 11/15/20.
// Copyright © 2020 Parse. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for ParseLiveQuery_tvOS.
FOUNDATION_EXPORT double ParseLiveQuery_tvOSVersionNumber;
//! Project version string for ParseLiveQuery_tvOS.
FOUNDATION_EXPORT const unsigned char ParseLiveQuery_tvOSVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <ParseLiveQuery_tvOS/PublicHeader.h>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>2.2.0</string>
<key>CFBundleVersion</key>
<string>2.2.0</string>
</dict>
</plist>

View File

@ -0,0 +1,19 @@
//
// ParseLiveQuery_watchOS.h
// ParseLiveQuery-watchOS
//
// Created by Corey Baker on 11/15/20.
// Copyright © 2020 Parse. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for ParseLiveQuery_watchOS.
FOUNDATION_EXPORT double ParseLiveQuery_watchOSVersionNumber;
//! Project version string for ParseLiveQuery_watchOS.
FOUNDATION_EXPORT const unsigned char ParseLiveQuery_watchOSVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <ParseLiveQuery_watchOS/PublicHeader.h>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:ParseLiveQuery.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5903CE91BD999C500C3EFFE"
BuildableName = "ParseLiveQuery.framework"
BlueprintName = "ParseLiveQuery-OSX"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5903CE91BD999C500C3EFFE"
BuildableName = "ParseLiveQuery.framework"
BlueprintName = "ParseLiveQuery-OSX"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5903CE91BD999C500C3EFFE"
BuildableName = "ParseLiveQuery.framework"
BlueprintName = "ParseLiveQuery-OSX"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5A9BFB61BE0248D00E78326"
BuildableName = "ParseLiveQuery.framework"
BlueprintName = "ParseLiveQuery-iOS"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5A9BFB61BE0248D00E78326"
BuildableName = "ParseLiveQuery.framework"
BlueprintName = "ParseLiveQuery-iOS"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</MacroExpansion>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F5A9BFB61BE0248D00E78326"
BuildableName = "ParseLiveQuery.framework"
BlueprintName = "ParseLiveQuery-iOS"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "708836932561F55B005B32F0"
BuildableName = "ParseLiveQuery_tvOS.framework"
BlueprintName = "ParseLiveQuery-tvOS"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "708836932561F55B005B32F0"
BuildableName = "ParseLiveQuery_tvOS.framework"
BlueprintName = "ParseLiveQuery-tvOS"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "708836712561F502005B32F0"
BuildableName = "ParseLiveQuery_watchOS.framework"
BlueprintName = "ParseLiveQuery-watchOS"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "708836712561F502005B32F0"
BuildableName = "ParseLiveQuery_watchOS.framework"
BlueprintName = "ParseLiveQuery-watchOS"
ReferencedContainer = "container:ParseLiveQuery.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,263 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
import BoltsSwift
import Starscream
/**
This is the 'advanced' view of live query subscriptions. It allows you to customize your subscriptions
to a live query server, have connections to multiple servers, cleanly handle disconnect and reconnect.
*/
@objc(PFLiveQueryClient)
open class Client: NSObject {
let host: URL
let applicationId: String
let clientKey: String?
var socket: WebSocket?
public var shouldPrintWebSocketLog = true
public var shouldPrintWebSocketTrace = false
public var userDisconnected = false
var isConnecting = false
// This allows us to easily plug in another request ID generation scheme, or more easily change the request id type
// if needed (technically this could be a string).
let requestIdGenerator: () -> RequestId
var subscriptions = [SubscriptionRecord]()
let queue = DispatchQueue(label: "com.parse.livequery", attributes: [])
/**
Creates a Client which automatically attempts to connect to the custom parse-server URL set in Parse.currentConfiguration().
*/
public override convenience init() {
self.init(server: Parse.validatedCurrentConfiguration().server)
}
/**
Creates a client which will connect to a specific server with an optional application id and client key
- parameter server: The server to connect to
- parameter applicationId: The application id to use
- parameter clientKey: The client key to use
*/
@objc(initWithServer:applicationId:clientKey:)
public init(server: String, applicationId: String? = nil, clientKey: String? = nil) {
guard let cmpts = URLComponents(string: server) else {
fatalError("Server should be a valid URL.")
}
var components = cmpts
components.scheme = (components.scheme == "https" || components.scheme == "wss") ? "wss" : "ws"
// Simple incrementing generator - can't use ++, that operator is deprecated!
var currentRequestId = 0
requestIdGenerator = {
currentRequestId += 1
return RequestId(value: currentRequestId)
}
self.applicationId = applicationId ?? Parse.validatedCurrentConfiguration().applicationId!
self.clientKey = clientKey ?? Parse.validatedCurrentConfiguration().clientKey
self.host = components.url!
}
}
extension Client {
// Swift is lame and doesn't allow storage to directly be in extensions.
// So we create an inner struct to wrap it up.
fileprivate class Storage {
private static var __once: () = {
sharedStorage = Storage()
}()
static var onceToken: Int = 0
static var sharedStorage: Storage!
static var shared: Storage {
_ = Storage.__once
return sharedStorage
}
let queue: DispatchQueue = DispatchQueue(label: "com.parse.livequery.client.storage", attributes: [])
var client: Client?
}
/// Gets or sets shared live query client to be used for default subscriptions
@objc(sharedClient)
public static var shared: Client! {
get {
let storage = Storage.shared
var client: Client?
storage.queue.sync {
client = storage.client
if client == nil {
let configuration = Parse.validatedCurrentConfiguration()
client = Client(
server: configuration.server,
applicationId: configuration.applicationId,
clientKey: configuration.clientKey
)
storage.client = client
}
}
return client
}
set {
let storage = Storage.shared
storage.queue.sync {
storage.client = newValue
}
}
}
}
extension Client {
/**
Registers a query for live updates, using the default subscription handler
- parameter query: The query to register for updates.
- parameter subclassType: The subclass of PFObject to be used as the type of the Subscription.
This parameter can be automatically inferred from context most of the time
- returns: The subscription that has just been registered
*/
public func subscribe<T>(
_ query: PFQuery<T>,
subclassType: T.Type = T.self
) -> Subscription<T> {
return subscribe(query, handler: Subscription<T>())
}
/**
Registers a query for live updates, using a custom subscription handler
- parameter query: The query to register for updates.
- parameter handler: A custom subscription handler.
- returns: Your subscription handler, for easy chaining.
*/
public func subscribe<T>(
_ query: PFQuery<T.PFObjectSubclass>,
handler: T
) -> T where T: SubscriptionHandling {
let subscriptionRecord = SubscriptionRecord(
query: query,
requestId: requestIdGenerator(),
handler: handler
)
self.subscriptions.append(subscriptionRecord)
if socket != nil {
_ = self.sendOperationAsync(.subscribe(requestId: subscriptionRecord.requestId, query: query as! PFQuery<PFObject>,
sessionToken: PFUser.current()?.sessionToken))
} else if !self.userDisconnected {
self.reconnect()
self.subscriptions.removeLast()
return self.subscribe(query, handler: handler)
} else {
NSLog("ParseLiveQuery: Warning: The client was explicitly disconnected! You must explicitly call .reconnect() in order to process your subscriptions.")
}
return handler
}
/**
Updates an existing subscription with a new query.
Upon completing the registration, the subscribe handler will be called with the new query
- parameter handler: The specific handler to update.
- parameter query: The new query for that handler.
*/
public func update<T>(
_ handler: T,
toQuery query: PFQuery<T.PFObjectSubclass>
) where T: SubscriptionHandling {
subscriptions = subscriptions.map {
if $0.subscriptionHandler === handler {
_ = sendOperationAsync(.update(requestId: $0.requestId, query: query as! PFQuery<PFObject>))
return SubscriptionRecord(query: query, requestId: $0.requestId, handler: $0.subscriptionHandler as! T)
}
return $0
}
}
/**
Unsubscribes all current subscriptions for a given query.
- parameter query: The query to unsubscribe from.
*/
@objc(unsubscribeFromQuery:)
public func unsubscribe(_ query: PFQuery<PFObject>) {
unsubscribe { $0.query == query }
}
/**
Unsubscribes from a specific query-handler pair.
- parameter query: The query to unsubscribe from.
- parameter handler: The specific handler to unsubscribe from.
*/
public func unsubscribe<T>(_ query: PFQuery<T.PFObjectSubclass>, handler: T) where T: SubscriptionHandling {
unsubscribe { $0.query == query && $0.subscriptionHandler === handler }
}
func unsubscribe(matching matcher: @escaping (SubscriptionRecord) -> Bool) {
var temp = [SubscriptionRecord]()
subscriptions.forEach {
if matcher($0) {
_ = sendOperationAsync(.unsubscribe(requestId: $0.requestId))
} else {
temp.append($0)
}
}
subscriptions = temp
}
}
extension Client {
/**
Reconnects this client to the server.
This will disconnect and resubscribe all existing subscriptions. This is not required to be called the first time
you use the client, and should usually only be called when an error occurs.
*/
@objc(reconnect)
public func reconnect() {
guard socket == nil || !isConnecting else { return }
socket?.disconnect()
socket = {
let socket = WebSocket(request: .init(url: host))
socket.delegate = self
socket.callbackQueue = queue
socket.connect()
isConnecting = true
userDisconnected = false
return socket
}()
}
/**
Explicitly disconnects this client from the server.
This does not remove any subscriptions - if you `reconnect()` your existing subscriptions will be restored.
Use this if you wish to dispose of the live query client.
*/
@objc(disconnect)
public func disconnect() {
isConnecting = false
guard let socket = socket
else {
return
}
socket.disconnect()
self.socket = nil
userDisconnected = true
}
}

View File

@ -0,0 +1,280 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
import Starscream
import BoltsSwift
private func parseObject<T: PFObject>(_ objectDictionary: [String:AnyObject]) throws -> T {
guard let _ = objectDictionary["className"] as? String else {
throw LiveQueryErrors.InvalidJSONError(json: objectDictionary, expectedKey: "parseClassName")
}
guard let _ = objectDictionary["objectId"] as? String else {
throw LiveQueryErrors.InvalidJSONError(json: objectDictionary, expectedKey: "objectId")
}
guard let object = PFDecoder.object().decode(objectDictionary) as? T else {
throw LiveQueryErrors.InvalidJSONObject(json: objectDictionary, details: "cannot decode json into \(T.self)")
}
return object
}
// ---------------
// MARK: Subscriptions
// ---------------
extension Client {
class SubscriptionRecord {
var subscriptionHandler: AnyObject?
// HandlerClosure captures the generic type info passed into the constructor of SubscriptionRecord,
// and 'unwraps' it so that it can be used with just a 'PFObject' instance.
// Technically, this should be a compiler no-op, as no witness tables should be used as 'PFObject' currently inherits from NSObject.
// Should we make PFObject ever a native swift class without the backing Objective-C runtime however,
// this becomes extremely important to have, and makes a ton more sense than just unsafeBitCast-ing everywhere.
var eventHandlerClosure: (Event<PFObject>, Client) -> Void
var errorHandlerClosure: (Error, Client) -> Void
var subscribeHandlerClosure: (Client) -> Void
var unsubscribeHandlerClosure: (Client) -> Void
let query: PFQuery<PFObject>
let requestId: RequestId
init<T>(query: PFQuery<T.PFObjectSubclass>, requestId: RequestId, handler: T) where T:SubscriptionHandling {
self.query = query as! PFQuery<PFObject>
self.requestId = requestId
subscriptionHandler = handler
// This is needed because swift requires 'handlerClosure' to be fully initialized before we setup the
// capture list for the closure.
eventHandlerClosure = { _, _ in }
errorHandlerClosure = { _, _ in }
subscribeHandlerClosure = { _ in }
unsubscribeHandlerClosure = { _ in }
eventHandlerClosure = { [weak self] event, client in
guard let handler = self?.subscriptionHandler as? T else {
return
}
handler.didReceive(Event(event: event), forQuery: query, inClient: client)
}
errorHandlerClosure = { [weak self] error, client in
guard let handler = self?.subscriptionHandler as? T else {
return
}
handler.didEncounter(error, forQuery: query, inClient: client)
}
subscribeHandlerClosure = { [weak self] client in
guard let handler = self?.subscriptionHandler as? T else {
return
}
handler.didSubscribe(toQuery: query, inClient: client)
}
unsubscribeHandlerClosure = { [weak self] client in
guard let handler = self?.subscriptionHandler as? T else {
return
}
handler.didUnsubscribe(fromQuery: query, inClient: client)
}
}
}
}
extension Client {
// An opaque placeholder structed used to ensure that we type-safely create request IDs and don't shoot ourself in
// the foot with array indexes.
struct RequestId: Equatable {
let value: Int
init(value: Int) {
self.value = value
}
}
}
func == (first: Client.RequestId, second: Client.RequestId) -> Bool {
return first.value == second.value
}
// ---------------
// MARK: Web Socket
// ---------------
extension Client: WebSocketDelegate {
public func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(_):
isConnecting = false
let sessionToken = PFUser.current()?.sessionToken ?? ""
_ = self.sendOperationAsync(.connect(applicationId: applicationId, sessionToken: sessionToken, clientKey: clientKey))
case .disconnected(let reason, let code):
isConnecting = false
if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket did disconnect with error: \(reason) code:\(code)") }
// TODO: Better retry logic, unless `disconnect()` was explicitly called
if !userDisconnected {
reconnect()
}
case .text(let text):
handleOperationAsync(text).continueWith { [weak self] task in
if let error = task.error, self?.shouldPrintWebSocketLog == true {
NSLog("ParseLiveQuery: Error processing message: \(error)")
}
}
case .binary(_):
if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: Received binary data but we don't handle it...") }
case .error(let error):
NSLog("ParseLiveQuery: Error processing message: \(String(describing: error))")
case .viabilityChanged(let isViable):
if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket viability channged to \(isViable ? "" : "not-")viable") }
if !isViable {
isConnecting = false
}
// TODO: Better retry logic, unless `disconnect()` was explicitly called
if !userDisconnected, isViable {
reconnect()
}
case .reconnectSuggested(let isSuggested):
if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket reconnect is \(isSuggested ? "" : "not ")suggested") }
// TODO: Better retry logic, unless `disconnect()` was explicitly called
if !userDisconnected, isSuggested {
reconnect()
}
case .cancelled:
isConnecting = false
if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: WebSocket connection cancelled...") }
// TODO: Better retry logic, unless `disconnect()` was explicitly called
if !userDisconnected {
reconnect()
}
case .pong(_):
if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: Received pong but we don't handle it...") }
case .ping(_):
if shouldPrintWebSocketLog { NSLog("ParseLiveQuery: Received ping but we don't handle it...") }
}
}
}
// -------------------
// MARK: Operations
// -------------------
extension Event {
init(serverResponse: ServerResponse, requestId: inout Client.RequestId) throws {
switch serverResponse {
case .enter(let reqId, let object):
requestId = reqId
self = .entered(try parseObject(object))
case .leave(let reqId, let object):
requestId = reqId
self = .left(try parseObject(object))
case .create(let reqId, let object):
requestId = reqId
self = .created(try parseObject(object))
case .update(let reqId, let object):
requestId = reqId
self = .updated(try parseObject(object))
case .delete(let reqId, let object):
requestId = reqId
self = .deleted(try parseObject(object))
default: fatalError("Invalid state reached")
}
}
}
extension Client {
fileprivate func subscriptionRecord(_ requestId: RequestId) -> SubscriptionRecord? {
guard
let recordIndex = self.subscriptions.firstIndex(where: { $0.requestId == requestId }) else {
return nil
}
let record = self.subscriptions[recordIndex]
return record.subscriptionHandler != nil ? record : nil
}
func sendOperationAsync(_ operation: ClientOperation) -> Task<Void> {
return Task(.queue(queue)) {
let jsonEncoded = operation.JSONObjectRepresentation
let jsonData = try JSONSerialization.data(withJSONObject: jsonEncoded, options: JSONSerialization.WritingOptions(rawValue: 0))
let jsonString = String(data: jsonData, encoding: String.Encoding.utf8)
if self.shouldPrintWebSocketTrace { NSLog("ParseLiveQuery: Sending message: \(jsonString!)") }
self.socket?.write(string: jsonString!)
}
}
func handleOperationAsync(_ string: String) -> Task<Void> {
return Task(.queue(queue)) {
if self.shouldPrintWebSocketTrace { NSLog("ParseLiveQuery: Received message: \(string)") }
guard
let jsonData = string.data(using: String.Encoding.utf8),
let jsonDecoded = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions(rawValue: 0))
as? [String:AnyObject],
let response: ServerResponse = try? ServerResponse(json: jsonDecoded)
else {
throw LiveQueryErrors.InvalidResponseError(response: string)
}
switch response {
case .connected:
let sessionToken = PFUser.current()?.sessionToken
self.subscriptions.forEach {
_ = self.sendOperationAsync(.subscribe(requestId: $0.requestId, query: $0.query, sessionToken: sessionToken))
}
case .redirect:
// TODO: Handle redirect.
break
case .subscribed(let requestId):
self.subscriptionRecord(requestId)?.subscribeHandlerClosure(self)
case .unsubscribed(let requestId):
guard
let recordIndex = self.subscriptions.firstIndex(where: { $0.requestId == requestId })
else {
break
}
let record: SubscriptionRecord = self.subscriptions[recordIndex]
record.unsubscribeHandlerClosure(self)
self.subscriptions.remove(at: recordIndex)
case .create, .delete, .enter, .leave, .update:
var requestId: RequestId = RequestId(value: 0)
guard
let event: Event<PFObject> = try? Event(serverResponse: response, requestId: &requestId),
let record = self.subscriptionRecord(requestId)
else {
break
}
record.eventHandlerClosure(event, self)
case .error(let requestId, let code, let error, let reconnect):
let error = LiveQueryErrors.ServerReportedError(code: code, error: error, reconnect: reconnect)
if let requestId = requestId {
self.subscriptionRecord(requestId)?.errorHandlerClosure(error, self)
} else {
throw error
}
}
}
}
}

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/**
* Add support for both SPM and Dynamic Framework Imports TODO: (@dplewis)
*/
#if canImport(ParseCore)
@_exported import ParseCore
#else
@_exported import Parse
#endif

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/**
Namespace struct for all errors reported by the Live Query SDK.
*/
public struct LiveQueryErrors {
fileprivate init() {}
/**
An error that is reported when the server returns a response that cannot be parsed.
*/
public struct InvalidResponseError: Error {
/// Response string of the error.
public let response: String
}
/**
An error that is reported when the server does not accept a query we've sent to it.
*/
public struct InvalidQueryError: Error {
}
/**
An error that is reported when the server returns valid JSON, but it doesn't match the format we expect.
*/
public struct InvalidJSONError: Error {
/// JSON used for matching.
public let json: [String:AnyObject]
/// Key that was expected to match.
public let expectedKey: String
}
/**
An error that is reported when the server returns valid JSON, but it doesn't match the format we expect.
*/
public struct InvalidJSONObject: Error {
/// JSON used for matching.
public let json: [String:AnyObject]
/// Details about the error
public let details: String
}
/**
An error that is reported when the live query server encounters an internal error.
*/
public struct ServerReportedError: Error {
/// Error code reported by the server.
public let code: Int
/// String error reported by the server.
public let error: String
/// Boolean value representing whether a client should reconnect.
public let reconnect: Bool
}
}

View File

@ -0,0 +1,110 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
enum ClientOperation {
case connect(applicationId: String, sessionToken: String, clientKey: String?)
case subscribe(requestId: Client.RequestId, query: PFQuery<PFObject>, sessionToken: String?)
case update(requestId: Client.RequestId, query: PFQuery<PFObject>)
case unsubscribe(requestId: Client.RequestId)
var JSONObjectRepresentation: [String : Any] {
switch self {
case .connect(let applicationId, let sessionToken, let clientKey):
var message: [String: Any] = [ "op": "connect", "applicationId": applicationId, "sessionToken": sessionToken ]
if let clientKey = clientKey {
message.updateValue(clientKey, forKey: "clientKey")
}
return message
case .subscribe(let requestId, let query, let sessionToken):
var result: [String: Any] = [ "op": "subscribe", "requestId": requestId.value, "query": Dictionary<String, AnyObject>(query: query) ]
if let sessionToken = sessionToken {
result["sessionToken"] = sessionToken
}
return result
case .update(let requestId, let query):
return [ "op": "update", "requestId": requestId.value, "query": Dictionary<String, AnyObject>(query: query) ]
case .unsubscribe(let requestId):
return [ "op": "unsubscribe", "requestId": requestId.value ]
}
}
}
enum ServerResponse {
case redirect(url: String)
case connected
case subscribed(requestId: Client.RequestId)
case unsubscribed(requestId: Client.RequestId)
case enter(requestId: Client.RequestId, object: [String : AnyObject])
case leave(requestId: Client.RequestId, object: [String : AnyObject])
case update(requestId: Client.RequestId, object: [String : AnyObject])
case create(requestId: Client.RequestId, object: [String : AnyObject])
case delete(requestId: Client.RequestId, object: [String : AnyObject])
case error(requestId: Client.RequestId?, code: Int, error: String, reconnect: Bool)
init(json: [String : AnyObject]) throws {
func jsonValue<T>(_ json: [String:AnyObject], _ key: String) throws -> T {
guard let value = json[key] as? T
else {
throw LiveQueryErrors.InvalidJSONError(json: json, expectedKey: key)
}
return value
}
func jsonRequestId(_ json: [String:AnyObject]) throws -> Client.RequestId {
let requestId: Int = try jsonValue(json, "requestId")
return Client.RequestId(value: requestId)
}
func subscriptionEvent(
_ json: [String:AnyObject],
_ eventType: (Client.RequestId, [String : AnyObject]) -> ServerResponse
) throws -> ServerResponse {
return eventType(try jsonRequestId(json), try jsonValue(json, "object"))
}
let rawOperation: String = try jsonValue(json, "op")
switch rawOperation {
case "connected":
self = .connected
case "redirect":
self = .redirect(url: try jsonValue(json, "url"))
case "subscribed":
self = .subscribed(requestId: try jsonRequestId(json))
case "unsubscribed":
self = .unsubscribed(requestId: try jsonRequestId(json))
case "enter": self = try subscriptionEvent(json, ServerResponse.enter)
case "leave": self = try subscriptionEvent(json, ServerResponse.leave)
case "update": self = try subscriptionEvent(json, ServerResponse.update)
case "create": self = try subscriptionEvent(json, ServerResponse.create)
case "delete": self = try subscriptionEvent(json, ServerResponse.delete)
case "error":
self = .error(
requestId: try? jsonRequestId(json),
code: try jsonValue(json, "code"),
error: try jsonValue(json, "error"),
reconnect: try jsonValue(json, "reconnect")
)
default:
throw LiveQueryErrors.InvalidJSONError(json: json, expectedKey: "op")
}
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
/**
NOTE: This is super hacky, and we need a better answer for this.
*/
extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject {
init<T>(query: PFQuery<T>) {
self.init()
let queryState = query.value(forKey: "state") as AnyObject?
if let className = queryState?.value(forKey: "parseClassName") {
self["className"] = className as? Value
}
if let conditions = queryState?.value(forKey: "conditions") as? [String:AnyObject] {
self["where"] = conditions.encodedQueryDictionary as? Value
} else {
self["where"] = [:] as? Value
}
}
}
extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject {
var encodedQueryDictionary: Dictionary {
var encodedQueryDictionary = Dictionary()
for (key, val) in self {
if let array = val as? [PFQuery] {
var queries:[Value] = []
for query in array {
let queryState = query.value(forKey: "state") as AnyObject?
if let conditions: [String:AnyObject] = queryState?.value(forKey: "conditions") as? [String:AnyObject], let encoded = conditions.encodedQueryDictionary as? Value {
queries.append(encoded)
}
}
encodedQueryDictionary[key] = queries as? Value
} else if let geoPoints = val as? [PFGeoPoint] {
var points:[Value] = []
for point in geoPoints {
points.append(point.encodedDictionary as! Value)
}
encodedQueryDictionary[key] = points as? Value
} else if let dict = val as? [String:AnyObject] {
encodedQueryDictionary[key] = dict.encodedQueryDictionary as? Value
} else if let geoPoint = val as? PFGeoPoint {
encodedQueryDictionary[key] = geoPoint.encodedDictionary as? Value
} else if let object = val as? PFObject {
encodedQueryDictionary[key] = (try? PFPointerObjectEncoder.object().encode(object)) as? Value
} else if let query = val as? PFQuery {
let queryState = query.value(forKey: "state") as AnyObject?
if let conditions: [String:AnyObject] = queryState?.value(forKey: "conditions") as? [String:AnyObject], let encoded = conditions.encodedQueryDictionary as? Value {
encodedQueryDictionary[key] = encoded
}
} else if let date = val as? Date {
encodedQueryDictionary[key] = ["__type": "Date", "iso": date.encodedString] as? Value
} else {
encodedQueryDictionary[key] = val
}
}
return encodedQueryDictionary
}
}
extension PFGeoPoint {
var encodedDictionary: [String:Any] {
return ["__type": "GeoPoint",
"latitude": latitude,
"longitude": longitude]
}
}
fileprivate extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
}
fileprivate extension Date {
var encodedString: String {
return Formatter.iso8601.string(from: self)
}
}

View File

@ -0,0 +1,367 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
import BoltsSwift
/**
This protocol describes the interface for handling events from a live query client.
You can use this protocol on any custom class of yours, instead of Subscription, if it fits your use case better.
*/
@objc(PFLiveQuerySubscriptionHandling)
public protocol ObjCCompat_SubscriptionHandling {
/**
Tells the handler that an event has been received from the live query server.
- parameter query: The query that the event occurred on.
- parameter event: The event that has been recieved from the server.
- parameter client: The live query client which received this event.
*/
@objc(liveQuery:didRecieveEvent:inClient:)
optional func didRecieveEvent(_ query: PFQuery<PFObject>, event: PFLiveQueryEvent, client: Client)
/**
Tells the handler that an error has been received from the live query server.
- parameter query: The query that the error occurred on.
- parameter error: The error that the server has encountered.
- parameter client: The live query client which received this error.
*/
@objc(liveQuery:didEncounterError:inClient:)
optional func didRecieveError(_ query: PFQuery<PFObject>, error: NSError, client: Client)
/**
Tells the handler that a query has been successfully registered with the server.
- note: This may be invoked multiple times if the client disconnects/reconnects.
- parameter query: The query that has been subscribed.
- parameter client: The live query client which subscribed this query.
*/
@objc(liveQuery:didSubscribeInClient:)
optional func didSubscribe(_ query: PFQuery<PFObject>, client: Client)
/**
Tells the handler that a query has been successfully deregistered from the server.
- note: This is not called unless `unregister()` is explicitly called.
- parameter query: The query that has been unsubscribed.
- parameter client: The live query client which unsubscribed this query.
*/
@objc(liveQuery:didUnsubscribeInClient:)
optional func didUnsubscribe(_ query: PFQuery<PFObject>, client: Client)
}
// HACK: Compiler bug causes enums (and sometimes classes) that are declared in structs that are marked as @objc
// to not actually be emitted by the compiler (lolwut?). Moving this to global scope fixes the problem, but we can't
// change the objc name of an enum either, so we pollute the swift namespace here.
// TODO: Fix this eventually.
/**
A type of an update event on a specific object from the live query server.
*/
@objc
public enum PFLiveQueryEventType: Int {
/// The object has been updated, and is now included in the query.
case entered
/// The object has been updated, and is no longer included in the query.
case left
/// The object has been created, and is a part of the query.
case created
/// The object has been updated, and is still a part of the query.
case updated
/// The object has been deleted, and is no longer included in the query.
case deleted
}
/**
Represents an update on a specific object from the live query server.
*/
@objc
open class PFLiveQueryEvent: NSObject {
/// Type of the event.
@objc
public let type: PFLiveQueryEventType
/// Object this event is for.
@objc
public let object: PFObject
init(type: PFLiveQueryEventType, object: PFObject) {
self.type = type
self.object = object
}
}
/**
This struct wraps up all of our Objective-C compatibility layer. You should never need to touch this if you're using Swift.
*/
public struct ObjCCompat {
fileprivate init() { }
/**
A default implementation of the SubscriptionHandling protocol, using blocks for callbacks.
*/
@objc(PFLiveQuerySubscription)
open class Subscription: NSObject {
public typealias SubscribeHandler = @convention(block) (PFQuery<PFObject>) -> Void
public typealias ErrorHandler = @convention(block) (PFQuery<PFObject>, NSError) -> Void
public typealias EventHandler = @convention(block) (PFQuery<PFObject>, PFLiveQueryEvent) -> Void
public typealias ObjectHandler = @convention(block) (PFQuery<PFObject>, PFObject) -> Void
var subscribeHandlers = [SubscribeHandler]()
var unsubscribeHandlers = [SubscribeHandler]()
var errorHandlers = [ErrorHandler]()
var eventHandlers = [EventHandler]()
/**
Register a callback for when a client succesfully subscribes to a query.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addSubscribeHandler:)
open func addSubscribeHandler(_ handler: @escaping SubscribeHandler) -> Subscription {
subscribeHandlers.append(handler)
return self
}
/**
Register a callback for when a query has been unsubscribed.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addUnsubscribeHandler:)
open func addUnsubscribeHandler(_ handler: @escaping SubscribeHandler) -> Subscription {
unsubscribeHandlers.append(handler)
return self
}
/**
Register a callback for when an error occurs.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addErrorHandler:)
open func addErrorHandler(_ handler: @escaping ErrorHandler) -> Subscription {
errorHandlers.append(handler)
return self
}
/**
Register a callback for when an event occurs.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addEventHandler:)
open func addEventHandler(_ handler: @escaping EventHandler) -> Subscription {
eventHandlers.append(handler)
return self
}
/**
Register a callback for when an object enters a query.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addEnterHandler:)
open func addEnterHandler(_ handler: @escaping ObjectHandler) -> Subscription {
return addEventHandler { $1.type == .entered ? handler($0, $1.object) : () }
}
/**
Register a callback for when an object leaves a query.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addLeaveHandler:)
open func addLeaveHandler(_ handler: @escaping ObjectHandler) -> Subscription {
return addEventHandler { $1.type == .left ? handler($0, $1.object) : () }
}
/**
Register a callback for when an object that matches the query is created.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addCreateHandler:)
open func addCreateHandler(_ handler: @escaping ObjectHandler) -> Subscription {
return addEventHandler { $1.type == .created ? handler($0, $1.object) : () }
}
/**
Register a callback for when an object that matches the query is updated.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addUpdateHandler:)
open func addUpdateHandler(_ handler: @escaping ObjectHandler) -> Subscription {
return addEventHandler { $1.type == .updated ? handler($0, $1.object) : () }
}
/**
Register a callback for when an object that matches the query is deleted.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@objc(addDeleteHandler:)
open func addDeleteHandler(_ handler: @escaping ObjectHandler) -> Subscription {
return addEventHandler { $1.type == .deleted ? handler($0, $1.object) : () }
}
}
}
extension ObjCCompat.Subscription: ObjCCompat_SubscriptionHandling {
public func didRecieveEvent(_ query: PFQuery<PFObject>, event: PFLiveQueryEvent, client: Client) {
eventHandlers.forEach { $0(query, event) }
}
public func didRecieveError(_ query: PFQuery<PFObject>, error: NSError, client: Client) {
errorHandlers.forEach { $0(query, error) }
}
public func didSubscribe(_ query: PFQuery<PFObject>, client: Client) {
subscribeHandlers.forEach { $0(query) }
}
public func didUnsubscribe(_ query: PFQuery<PFObject>, client: Client) {
unsubscribeHandlers.forEach { $0(query) }
}
}
extension Client {
fileprivate class HandlerConverter: SubscriptionHandling {
typealias T = PFObject
fileprivate static var associatedObjectKey: Int = 0
fileprivate weak var handler: ObjCCompat_SubscriptionHandling?
init(handler: ObjCCompat_SubscriptionHandling) {
self.handler = handler
objc_setAssociatedObject(handler, &HandlerConverter.associatedObjectKey, self, .OBJC_ASSOCIATION_RETAIN)
}
fileprivate func didReceive(_ event: Event<T>, forQuery query: PFQuery<T>, inClient client: Client) {
handler?.didRecieveEvent?(query, event: PFLiveQueryEvent(event: event), client: client)
}
fileprivate func didEncounter(_ error: Error, forQuery query: PFQuery<T>, inClient client: Client) {
handler?.didRecieveError?(query, error: error as NSError, client: client)
}
fileprivate func didSubscribe(toQuery query: PFQuery<T>, inClient client: Client) {
handler?.didSubscribe?(query, client: client)
}
fileprivate func didUnsubscribe(fromQuery query: PFQuery<T>, inClient client: Client) {
handler?.didUnsubscribe?(query, client: client)
}
}
/**
Registers a query for live updates, using a custom subscription handler.
- parameter query: The query to register for updates.
- parameter handler: A custom subscription handler.
- returns: The subscription that has just been registered.
*/
@objc(subscribeToQuery:withHandler:)
public func _PF_objc_subscribe(
_ query: PFQuery<PFObject>, handler: ObjCCompat_SubscriptionHandling
) -> ObjCCompat_SubscriptionHandling {
let swiftHandler = HandlerConverter(handler: handler)
_ = subscribe(query, handler: swiftHandler)
return handler
}
/**
Registers a query for live updates, using the default subscription handler.
- parameter query: The query to register for updates.
- returns: The subscription that has just been registered.
*/
@objc(subscribeToQuery:)
public func _PF_objc_subscribe(_ query: PFQuery<PFObject>) -> ObjCCompat.Subscription {
let subscription = ObjCCompat.Subscription()
_ = _PF_objc_subscribe(query, handler: subscription)
return subscription
}
/**
Unsubscribes a specific handler from a query.
- parameter query: The query to unsubscribe from.
- parameter handler: The specific handler to unsubscribe from.
*/
@objc(unsubscribeFromQuery:withHandler:)
public func _PF_objc_unsubscribe(_ query: PFQuery<PFObject>, subscriptionHandler: ObjCCompat_SubscriptionHandling) {
unsubscribe { record in
guard let handler = record.subscriptionHandler as? HandlerConverter
else {
return false
}
return record.query == query && handler.handler === subscriptionHandler
}
}
}
// HACK: Another compiler bug - if you have a required initializer with a generic type, the compiler simply refuses to
// emit the entire class altogether. Moving this to an extension for now solves the issue.
extension PFLiveQueryEvent {
convenience init<T>(event: ParseLiveQuery.Event<T>) {
let results: (type: PFLiveQueryEventType, object: PFObject) = {
switch event {
case .entered(let object): return (.entered, object)
case .left(let object): return (.left, object)
case .created(let object): return (.created, object)
case .updated(let object): return (.updated, object)
case .deleted(let object): return (.deleted, object)
}
}()
self.init(type: results.type, object: results.object)
}
}
extension PFQuery {
/**
Register this PFQuery for updates with Live Queries.
This uses the shared live query client, and creates a default subscription handler for you.
- returns: The created subscription for observing.
*/
@objc(subscribe)
public func _PF_objc_subscribe() -> ObjCCompat.Subscription {
return Client.shared._PF_objc_subscribe(self as! PFQuery<PFObject>)
}
}

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
//import ParseCore
extension Parse {
static func validatedCurrentConfiguration() -> ParseClientConfiguration {
guard let configuration = Parse.currentConfiguration else {
preconditionFailure("Parse SDK is not initialized. Call Parse.initializeWithConfiguration() before loading live query client.")
}
return configuration
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>2.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.2.0</string>
</dict>
</plist>

View File

@ -0,0 +1,250 @@
/**
* Copyright (c) 2016-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import Foundation
//import ParseCore
import BoltsSwift
/**
This protocol describes the interface for handling events from a liveQuery client.
You can use this protocol on any custom class of yours, instead of Subscription, if it fits your use case better.
*/
public protocol SubscriptionHandling: AnyObject {
/// The type of the PFObject subclass that this handler uses.
associatedtype PFObjectSubclass: PFObject
/**
Tells the handler that an event has been received from the live query server.
- parameter event: The event that has been recieved from the server.
- parameter query: The query that the event occurred on.
- parameter client: The live query client which received this event.
*/
func didReceive(_ event: Event<PFObjectSubclass>, forQuery query: PFQuery<PFObjectSubclass>, inClient client: Client)
/**
Tells the handler that an error has been received from the live query server.
- parameter error: The error that the server has encountered.
- parameter query: The query that the error occurred on.
- parameter client: The live query client which received this error.
*/
func didEncounter(_ error: Error, forQuery query: PFQuery<PFObjectSubclass>, inClient client: Client)
/**
Tells the handler that a query has been successfully registered with the server.
- note: This may be invoked multiple times if the client disconnects/reconnects.
- parameter query: The query that has been subscribed.
- parameter client: The live query client which subscribed this query.
*/
func didSubscribe(toQuery query: PFQuery<PFObjectSubclass>, inClient client: Client)
/**
Tells the handler that a query has been successfully deregistered from the server.
- note: This is not called unless `unregister()` is explicitly called.
- parameter query: The query that has been unsubscribed.
- parameter client: The live query client which unsubscribed this query.
*/
func didUnsubscribe(fromQuery query: PFQuery<PFObjectSubclass>, inClient client: Client)
}
/**
Represents an update on a specific object from the live query server.
- Entered: The object has been updated, and is now included in the query.
- Left: The object has been updated, and is no longer included in the query.
- Created: The object has been created, and is a part of the query.
- Updated: The object has been updated, and is still a part of the query.
- Deleted: The object has been deleted, and is no longer included in the query.
*/
public enum Event<T> where T: PFObject {
/// The object has been updated, and is now included in the query
case entered(T)
/// The object has been updated, and is no longer included in the query
case left(T)
/// The object has been created, and is a part of the query
case created(T)
/// The object has been updated, and is still a part of the query
case updated(T)
/// The object has been deleted, and is no longer included in the query
case deleted(T)
init<V>(event: Event<V>) {
switch event {
case .entered(let value as T): self = .entered(value)
case .left(let value as T): self = .left(value)
case .created(let value as T): self = .created(value)
case .updated(let value as T): self = .updated(value)
case .deleted(let value as T): self = .deleted(value)
default: fatalError()
}
}
}
private func == <T>(lhs: Event<T>, rhs: Event<T>) -> Bool {
switch (lhs, rhs) {
case (.entered(let obj1), .entered(let obj2)): return obj1 == obj2
case (.left(let obj1), .left(let obj2)): return obj1 == obj2
case (.created(let obj1), .created(let obj2)): return obj1 == obj2
case (.updated(let obj1), .updated(let obj2)): return obj1 == obj2
case (.deleted(let obj1), .deleted(let obj2)): return obj1 == obj2
default: return false
}
}
/**
A default implementation of the SubscriptionHandling protocol, using closures for callbacks.
*/
open class Subscription<T>: SubscriptionHandling where T: PFObject {
fileprivate var errorHandlers: [(PFQuery<T>, Error) -> Void] = []
fileprivate var eventHandlers: [(PFQuery<T>, Event<T>) -> Void] = []
fileprivate var subscribeHandlers: [(PFQuery<T>) -> Void] = []
fileprivate var unsubscribeHandlers: [(PFQuery<T>) -> Void] = []
/**
Creates a new subscription that can be used to handle updates.
*/
public init() {
}
/**
Register a callback for when an error occurs.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining
*/
@discardableResult open func handleError(_ handler: @escaping (PFQuery<T>, Error) -> Void) -> Subscription {
errorHandlers.append(handler)
return self
}
/**
Register a callback for when an event occurs.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@discardableResult open func handleEvent(_ handler: @escaping (PFQuery<T>, Event<T>) -> Void) -> Subscription {
eventHandlers.append(handler)
return self
}
/**
Register a callback for when a client succesfully subscribes to a query.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@discardableResult open func handleSubscribe(_ handler: @escaping (PFQuery<T>) -> Void) -> Subscription {
subscribeHandlers.append(handler)
return self
}
/**
Register a callback for when a query has been unsubscribed.
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@discardableResult open func handleUnsubscribe(_ handler: @escaping (PFQuery<T>) -> Void) -> Subscription {
unsubscribeHandlers.append(handler)
return self
}
// ---------------
// MARK: SubscriptionHandling
// TODO: Move to extension once swift compiler is less crashy
// ---------------
public typealias PFObjectSubclass = T
open func didReceive(_ event: Event<PFObjectSubclass>, forQuery query: PFQuery<T>, inClient client: Client) {
eventHandlers.forEach { $0(query, event) }
}
open func didEncounter(_ error: Error, forQuery query: PFQuery<T>, inClient client: Client) {
errorHandlers.forEach { $0(query, error) }
}
open func didSubscribe(toQuery query: PFQuery<T>, inClient client: Client) {
subscribeHandlers.forEach { $0(query) }
}
open func didUnsubscribe(fromQuery query: PFQuery<T>, inClient client: Client) {
unsubscribeHandlers.forEach { $0(query) }
}
}
extension Subscription {
/**
Register a callback for when an error occcurs of a specific type
Example:
subscription.handle(LiveQueryErrors.InvalidJSONError.self) { query, error in
print(error)
}
- parameter errorType: The error type to register for
- parameter handler: The callback to register
- returns: The same subscription, for easy chaining
*/
@discardableResult public func handle<E: Error>(
_ errorType: E.Type = E.self,
_ handler: @escaping (PFQuery<T>, E) -> Void
) -> Subscription {
errorHandlers.append { query, error in
if let error = error as? E {
handler(query, error)
}
}
return self
}
/**
Register a callback for when an event occurs of a specific type
Example:
subscription.handle(Event.Created) { query, object in
// Called whenever an object is creaated
}
- parameter eventType: The event type to handle. You should pass one of the enum cases in `Event`
- parameter handler: The callback to register
- returns: The same subscription, for easy chaining
*/
@discardableResult public func handle(_ eventType: @escaping (T) -> Event<T>, _ handler: @escaping (PFQuery<T>, T) -> Void) -> Subscription {
return handleEvent { query, event in
switch event {
case .entered(let obj) where eventType(obj) == event: handler(query, obj)
case .left(let obj) where eventType(obj) == event: handler(query, obj)
case .created(let obj) where eventType(obj) == event: handler(query, obj)
case .updated(let obj) where eventType(obj) == event: handler(query, obj)
case .deleted(let obj) where eventType(obj) == event: handler(query, obj)
default: return
}
}
}
}

View File

@ -575,7 +575,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@ -622,7 +622,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";

View File

@ -605,7 +605,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.15;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
@ -648,7 +648,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.15;
SDKROOT = macosx;
};
name = Release;

View File

@ -9,7 +9,7 @@
#import "AppDelegate.h"
#import <Parse/Parse.h>
#import <Parse.h>
@implementation AppDelegate

View File

@ -7,7 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Parse/Parse.h>
#import <Parse.h>
// If you want to use any of the UI components, uncomment this line
// #import <ParseUI/ParseUI.h>

View File

@ -9,7 +9,7 @@
#import "ParseStarterProjectViewController.h"
#import <Parse/Parse.h>
#import <Parse.h>
@implementation ParseStarterProjectViewController

190
Rakefile
View File

@ -16,7 +16,8 @@ release_folder = File.join(build_folder, 'release')
bolts_build_folder = File.join(script_folder, 'Carthage', 'Build')
bolts_folder = File.join(script_folder, 'Carthage', 'Checkouts', 'Bolts-ObjC')
ios_simulator = 'platform="iOS Simulator",name="iPhone 14"'
tvos_simulator = 'platform="tvOS Simulator",name="Apple TV 4K"'
tvos_simulator = 'platform="tvOS Simulator",name="Apple TV"'
watchos_simulator = 'platform="watchOS Simulator",name="Apple Watch Series 8 (45mm)"'
module Constants
require 'plist'
@ -34,6 +35,9 @@ module Constants
File.join(script_folder, 'ParseFacebookUtils', 'ParseFacebookUtils', 'Resources', 'Info-tvOS.plist'),
File.join(script_folder, 'ParseTwitterUtils', 'ParseTwitterUtils', 'Resources', 'Info-iOS.plist'),
File.join(script_folder, 'ParseUI', 'ParseUI', 'Resources', 'Info-iOS.plist'),
File.join(script_folder, 'ParseLiveQuery', 'ParseLiveQuery', 'Resources', 'Info.plist'),
File.join(script_folder, 'ParseLiveQuery', 'ParseLiveQuery-tvOS', 'Info.plist'),
File.join(script_folder, 'ParseLiveQuery', 'ParseLiveQuery-watchOS', 'Info.plist'),
File.join(script_folder, 'ParseStarterProject', 'iOS', 'ParseStarterProject', 'Resources', 'Info.plist'),
File.join(script_folder, 'ParseStarterProject', 'iOS', 'ParseStarterProject-Swift', 'Resources', 'Info.plist'),
File.join(script_folder, 'ParseStarterProject', 'OSX', 'ParseOSXStarterProject', 'Resources', 'Info.plist'),
@ -148,6 +152,80 @@ namespace :build do
end
end
namespace :parse_live_query do
desc 'Build iOS LiveQuery framework.'
task :ios do
task = XCTask::BuildFrameworkTask.new do |t|
t.directory = script_folder
t.build_directory = File.join(build_folder, 'iOS')
t.framework_type = XCTask::FrameworkType::IOS
t.framework_name = 'ParseLiveQuery.framework'
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-iOS'
t.configuration = 'Release'
end
result = task.execute
unless result
puts 'Failed to build iOS LiveQuery Framework.'
exit(1)
end
end
desc 'Build macOS LiveQuery framework.'
task :macos do
task = XCTask::BuildFrameworkTask.new do |t|
t.directory = script_folder
t.build_directory = File.join(build_folder, 'macOS')
t.framework_type = XCTask::FrameworkType::OSX
t.framework_name = 'ParseLiveQuery.framework'
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-OSX'
t.configuration = 'Release'
end
result = task.execute
unless result
puts 'Failed to build macOS LiveQuery Framework.'
exit(1)
end
end
desc 'Build watchOS LiveQuery framework.'
task :watchos do
task = XCTask::BuildFrameworkTask.new do |t|
t.directory = script_folder
t.build_directory = File.join(build_folder, 'watchOS')
t.framework_type = XCTask::FrameworkType::WATCHOS
t.framework_name = 'ParseLiveQuery_watchOS.framework'
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-watchOS'
t.configuration = 'Release'
end
result = task.execute
unless result
puts 'Failed to build watchOS LiveQuery Framework.'
exit(1)
end
end
desc 'Build tvOS LiveQuery framework.'
task :tvos do
task = XCTask::BuildFrameworkTask.new do |t|
t.directory = script_folder
t.build_directory = File.join(build_folder, 'tvOS')
t.framework_type = XCTask::FrameworkType::TVOS
t.framework_name = 'ParseLiveQuery_tvOS.framework'
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-tvOS'
t.configuration = 'Release'
end
result = task.execute
unless result
puts 'Failed to build tvOS LiveQuery Framework.'
exit(1)
end
end
end
namespace :facebook_utils do
desc 'Build iOS FacebookUtils framework.'
task :ios do
@ -356,6 +434,23 @@ namespace :package do
make_package(release_folder,
[parseui_framework_path],
package_parseui_name)
Rake::Task['build:parse_live_query:ios'].invoke
ios_lq_framework_path = File.join(build_folder, 'iOS', 'ParseLiveQuery.framework')
make_package(release_folder, [ios_lq_framework_path], 'ParseLiveQuery-iOS.zip')
Rake::Task['build:parse_live_query:watchos'].invoke
watchos_lq_fb_utils_framework_path = File.join(build_folder, 'watchOS', 'ParseLiveQuery_watchOS.framework')
make_package(release_folder, [watchos_lq_fb_utils_framework_path], 'ParseLiveQuery-watchOS.zip')
Rake::Task['build:parse_live_query:tvos'].invoke
tvos_lq_framework_path = File.join(build_folder, 'tvOS', 'ParseLiveQuery_tvOS.framework')
make_package(release_folder, [tvos_lq_framework_path], 'ParseLiveQuery-tvOS.zip')
Rake::Task['build:parse_live_query:macos'].invoke
macos_lq_utils_framework_path = File.join(build_folder, 'macOS', 'ParseLiveQuery.framework')
make_package(release_folder, [macos_lq_utils_framework_path], 'ParseLiveQuery-OSX.zip')
end
desc 'Build and package all starter projects for the release'
@ -610,6 +705,99 @@ namespace :test do
end
end
namespace :parse_live_query do
task :all do
Rake::Task['test:parse_live_query:ios'].invoke
Rake::Task['test:parse_live_query:tvos'].invoke
Rake::Task['test:parse_live_query:watchos'].invoke
Rake::Task['test:parse_live_query:osx'].invoke
end
task :ios do
task = XCTask::BuildTask.new do |t|
t.directory = script_folder
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-iOS'
t.sdk = 'iphonesimulator'
t.destinations = [ios_simulator]
t.configuration = 'Debug'
t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD]
t.formatter = XCTask::BuildFormatter::XCPRETTY
end
result = task.execute
unless result
puts 'Failed to build ParseLiveQuery'
exit(1)
end
end
task :tvos do
task = XCTask::BuildTask.new do |t|
t.directory = script_folder
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-tvOS'
t.destinations = [tvos_simulator]
t.configuration = 'Debug'
t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD]
t.formatter = XCTask::BuildFormatter::XCPRETTY
end
result = task.execute
unless result
puts 'Failed to build ParseLiveQuery-tvOS.'
exit(1)
end
end
task :watchos do
task = XCTask::BuildTask.new do |t|
t.directory = script_folder
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-watchOS'
t.destinations = [watchos_simulator]
t.configuration = 'Debug'
t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD]
t.formatter = XCTask::BuildFormatter::XCPRETTY
end
result = task.execute
unless result
puts 'Failed to build ParseLiveQuery-watchOS.'
exit(1)
end
end
task :osx do
task = XCTask::BuildTask.new do |t|
t.directory = script_folder
t.workspace = 'Parse.xcworkspace'
t.scheme = 'ParseLiveQuery-OSX'
t.configuration = 'Debug'
t.actions = [XCTask::BuildAction::CLEAN, XCTask::BuildAction::BUILD]
t.formatter = XCTask::BuildFormatter::XCPRETTY
end
result = task.execute
unless result
puts 'Failed to build ParseLiveQuery-OSX.'
exit(1)
end
end
end
desc 'Run Starter Project Tests'
task :starters do |_|
results = []

View File

@ -0,0 +1,11 @@
import XCTest
@testable import Parse_SDK_iOS_OSX
final class Parse_SDK_iOS_OSXTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Parse_SDK_iOS_OSX().text, "Hello, World!")
}
}