Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e5d146ba1 | ||
|
|
cdc0729c8b | ||
|
|
a3a5b3f03b | ||
|
|
44468f858c | ||
|
|
adbe40a86a | ||
|
|
b5ce589c4c | ||
|
|
b426994d83 | ||
|
|
279b5f9eb8 | ||
|
|
20a17327a6 | ||
|
|
2a001cc575 | ||
|
|
9a763fdab6 | ||
|
|
c3e3bddac9 | ||
|
|
3b419eb2e3 | ||
|
|
cb838776ce | ||
|
|
8a5c88c9fa | ||
|
|
91f86ea8da | ||
|
|
64c3fbe198 | ||
|
|
9a30a196ec | ||
|
|
2abaad19b6 | ||
|
|
7e5dab08d1 | ||
|
|
d7233e04a3 | ||
|
|
c80f006649 | ||
|
|
23f184c1dd | ||
|
|
db58a91497 | ||
|
|
3f398df659 | ||
|
|
8536201df9 | ||
|
|
417f4d8e91 | ||
|
|
3053fa59e2 | ||
|
|
1d2388b30f | ||
|
|
5396dcd1f9 | ||
|
|
203315f93e | ||
|
|
523824ea57 | ||
|
|
306443aaaf | ||
|
|
db17e6865f | ||
|
|
d14376c277 | ||
|
|
70cdda1ea2 | ||
|
|
168fc856ff | ||
|
|
f83f4ddcb7 | ||
|
|
7d02f612f8 | ||
|
|
0d4d0ba602 | ||
|
|
6f16206cef | ||
|
|
ee89952cc4 | ||
|
|
6b98348ceb | ||
|
|
b63125bfe0 | ||
|
|
5ca2018292 | ||
|
|
bbb1edb0fb | ||
|
|
b98c5981af | ||
|
|
13c6951b35 | ||
|
|
8806258058 | ||
|
|
e672a408f4 | ||
|
|
386b7c09af | ||
|
|
27dedad85d | ||
|
|
c22ad09d8e | ||
|
|
9f87ff2113 | ||
|
|
bde5ff2983 | ||
|
|
ae78589d3a | ||
|
|
abec74ed3f | ||
|
|
a994876d98 | ||
|
|
9d5464c693 | ||
|
|
f00ad7e91b | ||
|
|
4b0d5ffe18 | ||
|
|
d93abe66ec | ||
|
|
196abf93b8 | ||
|
|
a0079fdee0 | ||
|
|
fdb4ffa000 | ||
|
|
cc047bdc6f | ||
|
|
7231919408 | ||
|
|
e9e2188cda | ||
|
|
413b1d2bca | ||
|
|
d92d385431 | ||
|
|
9ebfd397fd | ||
|
|
7dffc4b143 | ||
|
|
37cd028730 | ||
|
|
cc8993e6e7 | ||
|
|
a0b35f490b | ||
|
|
ed0614d8cf | ||
|
|
ea70eb937a | ||
|
|
b5ecff7e03 | ||
|
|
8589cbedbb | ||
|
|
edc604bfec | ||
|
|
8995e0faeb | ||
|
|
67a68bef8c | ||
|
|
7c4b43d2d1 | ||
|
|
694f96abec | ||
|
|
369854b103 | ||
|
|
104ebb9442 | ||
|
|
e1f56bfdc6 | ||
|
|
4a50411246 | ||
|
|
fc3ce8158c | ||
|
|
6e1e11809e | ||
|
|
9d67b82d5c | ||
|
|
09a3b74f93 | ||
|
|
c108f4aac4 | ||
|
|
0ac595efd9 | ||
|
|
ce1b59c1cf | ||
|
|
f7438e91b3 | ||
|
|
656d2cdd29 | ||
|
|
d8a244dd1a | ||
|
|
2dea99045d | ||
|
|
483eb38d73 | ||
|
|
475c50a8e3 | ||
|
|
4384ccb485 | ||
|
|
4b4bb7c87c | ||
|
|
cd6ccb9c7b | ||
|
|
0c91d7fe9c | ||
|
|
0d8fe0f642 | ||
|
|
b6f4e70731 | ||
|
|
efd944e337 | ||
|
|
cc86db04e2 | ||
|
|
c9ce161632 | ||
|
|
f924fc3023 | ||
|
|
5bfd32eeae | ||
|
|
7c365d304f | ||
|
|
a6a782aefb | ||
|
|
aa72c42c3b | ||
|
|
bee2be5dfb | ||
|
|
9bcc86be72 | ||
|
|
d0c932a90d | ||
|
|
4a0aa59d25 | ||
|
|
70a838c834 | ||
|
|
19dc11c735 | ||
|
|
87e854e8d4 | ||
|
|
5640bbe863 | ||
|
|
843ee9fa22 | ||
|
|
95ce084ddd | ||
|
|
ed92f6aad2 | ||
|
|
56f52ccfe4 | ||
|
|
2cc8a7d4ab | ||
|
|
0026cef7bf | ||
|
|
f0fee4a4e8 | ||
|
|
1898bbde62 | ||
|
|
031a7cd850 | ||
|
|
27f7777ba5 | ||
|
|
8caa2fa200 | ||
|
|
2fe45e940f | ||
|
|
e50720470c | ||
|
|
cebf4a85d6 | ||
|
|
5f96d1e565 | ||
|
|
1f3e0ccbe5 | ||
|
|
6252f3dbda | ||
|
|
1e71daa4f7 | ||
|
|
8eb2517d6b | ||
|
|
46e044726f | ||
|
|
b30ee65933 | ||
|
|
ae89c0a1f6 | ||
|
|
54f765b19e | ||
|
|
d8a1185c6b | ||
|
|
dbf430683f | ||
|
|
d0634fc8e6 | ||
|
|
178648f373 | ||
|
|
3a4428cfb3 | ||
|
|
dab5bf2892 | ||
|
|
e3aa7a6767 | ||
|
|
0c8394f6f4 | ||
|
|
6779322eea | ||
|
|
5d8ffc9070 | ||
|
|
72345d4c18 | ||
|
|
52a6dc2fd2 | ||
|
|
5b05c23877 | ||
|
|
81c0841201 | ||
|
|
747e183b98 | ||
|
|
62a39671fc | ||
|
|
3be9c89f49 | ||
|
|
ceb4f34827 | ||
|
|
22e785a637 | ||
|
|
221519e3ad | ||
|
|
7faed9ec18 | ||
|
|
c74ecf9c07 | ||
|
|
04233f8f7c | ||
|
|
378e3891bf | ||
|
|
9e040347f5 | ||
|
|
c99ce7dfbb | ||
|
|
6b017d5d3f | ||
|
|
b1f4ecf615 | ||
|
|
fd29bc392f | ||
|
|
61b5ceb1cb | ||
|
|
38a8914b55 | ||
|
|
ede35f29d8 | ||
|
|
7e8abd13c4 | ||
|
|
d776817d8d | ||
|
|
82d79b65c3 | ||
|
|
8cf49c6fef | ||
|
|
caba5b81af | ||
|
|
98efd568df |
+10
@@ -0,0 +1,10 @@
|
||||
osx_image: xcode9.4
|
||||
language: objective-c
|
||||
|
||||
install: script/bootstrap
|
||||
script: script/cibuild
|
||||
notifications:
|
||||
email: false
|
||||
env:
|
||||
global:
|
||||
secure: L4C3hHzXc/sgL35dIz3kjqIzPWnXCHsFOgbOcz0f249zlnegeKHQdChm4HUDtfrbmKLMYQCIvVfcgPeLaHkCSeHqXUihXLT2LglK+0GX3MBbFzde9x1uln2O68hH1tTmBg2O2i4Dtc4D3e+2tVv3XmhQDZt0znIVO6n9YgevDqw=
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
github "jspahrsummers/xcconfigs" >= 0.7
|
||||
github "Quick/Quick" ~> 0.2
|
||||
github "Quick/Nimble" ~> 0.2
|
||||
github "jspahrsummers/xcconfigs" ~> 0.8.1
|
||||
github "Quick/Quick" ~> 1.1.0
|
||||
github "Quick/Nimble" ~> 6.1.0
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
github "Mantle/Mantle" "1.5.3"
|
||||
github "Quick/Nimble" "v0.2.0"
|
||||
github "Quick/Quick" "v0.2.2"
|
||||
github "ReactiveCocoa/ReactiveCocoa" "v2.4.3"
|
||||
github "jspahrsummers/xcconfigs" "0.7"
|
||||
github "Mantle/Mantle" "1.5.8"
|
||||
github "Quick/Nimble" "v6.1.0"
|
||||
github "Quick/Quick" "v1.1.0"
|
||||
github "ReactiveCocoa/ReactiveCocoa" "v2.5"
|
||||
github "jspahrsummers/xcconfigs" "0.8.1"
|
||||
|
||||
Vendored
+1
-1
Submodule Carthage/Checkouts/Mantle updated: f797bd4837...78d3966b3c
Vendored
+1
-1
Submodule Carthage/Checkouts/Nimble updated: 6f787eeb75...e3c1a78af3
Vendored
+1
-1
Submodule Carthage/Checkouts/Quick updated: b0e98286db...e4fa1e85c0
Vendored
+1
-1
Submodule Carthage/Checkouts/ReactiveCocoa updated: a6bc3a918d...32364b9040
Vendored
+1
-1
Submodule Carthage/Checkouts/xcconfigs updated: 6c64238566...99624a6af3
@@ -6,6 +6,7 @@ and transparent as updates to a website**.
|
||||
Instead of publishing a feed of versions from which your app must select,
|
||||
Squirrel updates to the version your server tells it to. This allows you to
|
||||
intelligently update your clients based on the request you give to Squirrel.
|
||||
The server can remotely drive behaviors like rolling back or phased rollouts.
|
||||
|
||||
Your request can include authentication details, custom headers or a request
|
||||
body so that your server has the context it needs in order to supply the most
|
||||
@@ -46,7 +47,7 @@ version as Squirrel.
|
||||
|
||||
Otherwise, add a target dependency and Copy Files build phase entry for the
|
||||
ReactiveCocoa.framework target included in Squirrel's repository, in
|
||||
External/ReactiveCocoa.
|
||||
`Carthage/Checkouts/ReactiveCocoa`.
|
||||
|
||||
Similarly, ensure your application includes Mantle, or copies in the Squirrel
|
||||
version.
|
||||
@@ -65,7 +66,7 @@ Once Squirrel is added to your project, you need to configure and start it.
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
NSURLComponents *components = [[NSURLComponents alloc] init];
|
||||
|
||||
components.scheme = @"http";
|
||||
components.scheme = @"https";
|
||||
components.host = @"mycompany.com";
|
||||
components.path = @"/myapp/latest";
|
||||
|
||||
@@ -113,12 +114,18 @@ To know when an update is ready to be installed, you can subscribe to the
|
||||
}];
|
||||
```
|
||||
|
||||
If you've been notified of an available update, and don't want to wait for it to
|
||||
be installed automatically, you can terminate the app to begin the installation
|
||||
process immediately.
|
||||
## Installing Updates
|
||||
|
||||
If you want to install a downloaded update and automatically relaunch afterward,
|
||||
`SQRLUpdater` can do that:
|
||||
While downloaded updates are automatically installed when your application
|
||||
terminates, if don't want to wait you can manually terminate the app to begin
|
||||
the installation process immediately.
|
||||
|
||||
Once an [update available notification](#update-available-notifications) has
|
||||
been received, you may want to present an interface informing the user about
|
||||
the update and offering the ability to install and relaunch.
|
||||
|
||||
To explicitly install a downloaded update and automatically relaunch afterward,
|
||||
subscribe to the `relaunchToInstallUpdate` signal on `SQRLUpdater`:
|
||||
|
||||
```objc
|
||||
[[self.updater relaunchToInstallUpdate] subscribeError:^(NSError *error) {
|
||||
@@ -133,7 +140,7 @@ Your server should determine whether an update is required based on the
|
||||
|
||||
If an update is required your server should respond with a status code of
|
||||
[200 OK](http://tools.ietf.org/html/rfc2616#section-10.2.1) and include the
|
||||
[update JSON](#update-json-format) in the body. Squirrel **will** download and
|
||||
[update JSON](#update-server-json-format) in the body. Squirrel **will** download and
|
||||
install this update, even if the version of the update is the same as the
|
||||
currently running version. To save redundantly downloading the same version
|
||||
multiple times your server must not inform the client to update.
|
||||
@@ -142,17 +149,17 @@ If no update is required your server must respond with a status code of
|
||||
[204 No Content](http://tools.ietf.org/html/rfc2616#section-10.2.5). Squirrel
|
||||
will check for an update again at the interval you specify.
|
||||
|
||||
## Update JSON Format
|
||||
## Update Server JSON Format
|
||||
|
||||
When an update is available, Squirrel expects the following schema in response
|
||||
to the update request provided:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "http://mycompany.com/myapp/releases/myrelease",
|
||||
"url": "https://mycompany.example.com/myapp/releases/myrelease",
|
||||
"name": "My Release Name",
|
||||
"notes": "Theses are some release notes innit",
|
||||
"pub_date": "2013-09-18T12:29:53+01:00",
|
||||
"pub_date": "2013-09-18T12:29:53+01:00"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -165,6 +172,40 @@ appropriate format.
|
||||
|
||||
"pub_date" if present must be formatted according to ISO 8601.
|
||||
|
||||
## Update File JSON Format
|
||||
|
||||
The alternate update technique uses a plain JSON file meaning you can store your
|
||||
update metadata on S3 or another static file store. The format of this file is
|
||||
detailed below:
|
||||
|
||||
```json
|
||||
{
|
||||
"currentRelease": "1.2.3",
|
||||
"releases": [
|
||||
{
|
||||
"version": "1.2.1",
|
||||
"updateTo": {
|
||||
"version": "1.2.1",
|
||||
"pub_date": "2013-09-18T12:29:53+01:00",
|
||||
"notes": "Theses are some release notes innit",
|
||||
"name": "1.2.1",
|
||||
"url": "https://mycompany.example.com/myapp/releases/myrelease"
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "1.2.3",
|
||||
"updateTo": {
|
||||
"version": "1.2.3",
|
||||
"pub_date": "2014-09-18T12:29:53+01:00",
|
||||
"notes": "Theses are some more release notes innit",
|
||||
"name": "1.2.3",
|
||||
"url": "https://mycompany.example.com/myapp/releases/myrelease3"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# User Interface
|
||||
|
||||
Squirrel does not provide any GUI components for presenting updates. If you want
|
||||
|
||||
@@ -8,13 +8,11 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
533076EB17EB688300BDCCE0 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D097B9BC17BB4777006C3FEB /* IOKit.framework */; };
|
||||
534FF35B17D8E8B90020A51A /* unused-helper in Copy LoginItems */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; };
|
||||
534FF35B17D8E8B90020A51A /* unused-helper in Copy LoginItems */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
534FF36117D8E90A0020A51A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F60CA7EA179FC4F60069F69A /* Foundation.framework */; };
|
||||
534FF36717D8E90A0020A51A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 534FF36517D8E90A0020A51A /* InfoPlist.strings */; };
|
||||
534FF36A17D8E90A0020A51A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 534FF36917D8E90A0020A51A /* main.m */; };
|
||||
534FF37317D8E9370020A51A /* com.github.Squirrel.TestApplication.TestService.xpc in Copy XPCServices */ = {isa = PBXBuildFile; fileRef = 534FF36017D8E90A0020A51A /* com.github.Squirrel.TestApplication.TestService.xpc */; };
|
||||
53710D7417D8F59700A992DE /* SQRLDeepCodesignSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */; };
|
||||
53710D8417D8F5CB00A992DE /* deep-codesign in Resources */ = {isa = PBXBuildFile; fileRef = 53710D7F17D8F5C300A992DE /* deep-codesign */; };
|
||||
534FF37317D8E9370020A51A /* com.github.Squirrel.TestApplication.TestService.xpc in Copy XPCServices */ = {isa = PBXBuildFile; fileRef = 534FF36017D8E90A0020A51A /* com.github.Squirrel.TestApplication.TestService.xpc */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
5371815F18A29DC8005ED798 /* TestAppConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5371815E18A29DC8005ED798 /* TestAppConstants.m */; };
|
||||
5371816018A29DC8005ED798 /* TestAppConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 5371815E18A29DC8005ED798 /* TestAppConstants.m */; };
|
||||
5374DCC6187AD0D8006B7056 /* SQRLAuthorization.h in Headers */ = {isa = PBXBuildFile; fileRef = 5374DCC4187AD0D8006B7056 /* SQRLAuthorization.h */; };
|
||||
@@ -27,6 +25,8 @@
|
||||
5397A69D187DF8610014A477 /* SQRLInstallerOwnedBundle.m in Sources */ = {isa = PBXBuildFile; fileRef = 5397A5EB187DA2570014A477 /* SQRLInstallerOwnedBundle.m */; };
|
||||
53AACF4B17E9CA0500B41027 /* SQRLUpdate.h in Headers */ = {isa = PBXBuildFile; fileRef = 53AACF4917E9CA0500B41027 /* SQRLUpdate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
53AACF4C17E9CA0500B41027 /* SQRLUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53AACF4A17E9CA0500B41027 /* SQRLUpdate.m */; };
|
||||
88CA21971BD980FC006693B7 /* MTLEXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CA21941BD980FC006693B7 /* MTLEXTRuntimeExtensions.m */; };
|
||||
88CA21981BD980FC006693B7 /* MTLEXTScope.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CA21961BD980FC006693B7 /* MTLEXTScope.m */; };
|
||||
D000218F17BACEFF0050109A /* SQRLZipArchiver.m in Sources */ = {isa = PBXBuildFile; fileRef = D000218D17BACEFF0050109A /* SQRLZipArchiver.m */; };
|
||||
D000219717BAD34D0050109A /* TestApplication.app.zip in Resources */ = {isa = PBXBuildFile; fileRef = D000219617BAD34D0050109A /* TestApplication.app.zip */; };
|
||||
D000219917BAD35C0050109A /* SQRLZipArchiverSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D000219817BAD35C0050109A /* SQRLZipArchiverSpec.m */; };
|
||||
@@ -37,7 +37,7 @@
|
||||
D014ABBA17B97403007D79D0 /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D014ABB917B97403007D79D0 /* ServiceManagement.framework */; };
|
||||
D014AC0517B97885007D79D0 /* ShipIt-main.m in Sources */ = {isa = PBXBuildFile; fileRef = D014AC0417B97885007D79D0 /* ShipIt-main.m */; };
|
||||
D014AC1117B9789C007D79D0 /* SQRLInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = D014AC1017B9789C007D79D0 /* SQRLInstaller.m */; };
|
||||
D014AC1817B97945007D79D0 /* ShipIt in Copy ShipIt */ = {isa = PBXBuildFile; fileRef = D014AC0117B97885007D79D0 /* ShipIt */; };
|
||||
D014AC1817B97945007D79D0 /* ShipIt in Copy ShipIt */ = {isa = PBXBuildFile; fileRef = D014AC0117B97885007D79D0 /* ShipIt */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
D014AC1917B979A8007D79D0 /* SQRLCodeSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F2ED8217A33C4900F4D220 /* SQRLCodeSignature.m */; };
|
||||
D014AC1A17B979AA007D79D0 /* NSError+SQRLVerbosityExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBE9B17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m */; };
|
||||
D014AC1D17B979B7007D79D0 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6EB217F179D0E4D001108CF /* Security.framework */; };
|
||||
@@ -55,6 +55,7 @@
|
||||
D06B58B518032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D06B58B318032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m */; };
|
||||
D06B58B618032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D06B58B318032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m */; };
|
||||
D06B58D118035B5A00656D97 /* SQRLTerminationListenerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D06B58D018035B5A00656D97 /* SQRLTerminationListenerSpec.m */; };
|
||||
D06F7B101AD72DD30009A3BC /* RACKVOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D06F7B0F1AD72DD30009A3BC /* RACKVOProxy.m */; };
|
||||
D08D4E0D17B451500012B22D /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BDA179CC00E00158214 /* Cocoa.framework */; };
|
||||
D08D4E1317B451500012B22D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D08D4E1117B451500012B22D /* InfoPlist.strings */; };
|
||||
D08D4E1517B451500012B22D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D08D4E1417B451500012B22D /* main.m */; };
|
||||
@@ -62,7 +63,7 @@
|
||||
D08D4E1C17B451500012B22D /* TestAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D08D4E1B17B451500012B22D /* TestAppDelegate.m */; };
|
||||
D08D4E2117B451500012B22D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D08D4E2017B451500012B22D /* Images.xcassets */; };
|
||||
D08D4E4717B451E50012B22D /* Squirrel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; };
|
||||
D08D4E4917B451F20012B22D /* Squirrel.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; };
|
||||
D08D4E4917B451F20012B22D /* Squirrel.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
D08D4E4A17B451FD0012B22D /* TestApplication.app in Resources */ = {isa = PBXBuildFile; fileRef = D08D4E0C17B451500012B22D /* TestApplication.app */; };
|
||||
D093682417FF732200BA0EAE /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D014ABB917B97403007D79D0 /* ServiceManagement.framework */; };
|
||||
D0964B3817F2E01500D88BF7 /* SQRLDownloadedUpdate.h in Headers */ = {isa = PBXBuildFile; fileRef = D0964B3617F2E01500D88BF7 /* SQRLDownloadedUpdate.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -74,7 +75,6 @@
|
||||
D09C66291A00520B007D7ED0 /* NSArray+RACSequenceAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66281A00520B007D7ED0 /* NSArray+RACSequenceAdditions.m */; };
|
||||
D09C662C1A005226007D7ED0 /* NSObject+RACKVOWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C662A1A005226007D7ED0 /* NSObject+RACKVOWrapper.m */; };
|
||||
D09C662D1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C662B1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m */; };
|
||||
D09C66331A0052DE007D7ED0 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66321A0052DE007D7ED0 /* EXTRuntimeExtensions.m */; };
|
||||
D09C66351A00535B007D7ED0 /* RACKVOTrampoline.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66341A00535B007D7ED0 /* RACKVOTrampoline.m */; };
|
||||
D09C66381A00537A007D7ED0 /* NSObject+RACDeallocating.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66361A00537A007D7ED0 /* NSObject+RACDeallocating.m */; };
|
||||
D09C66391A00537A007D7ED0 /* NSObject+RACDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = D09C66371A00537A007D7ED0 /* NSObject+RACDescription.m */; };
|
||||
@@ -135,7 +135,7 @@
|
||||
D0AFE3901A00294100C6048F /* SwiftSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AFE38F1A00294100C6048F /* SwiftSpec.swift */; };
|
||||
D0AFE3931A00297500C6048F /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AFE3921A00297500C6048F /* Quick.framework */; };
|
||||
D0AFE3951A0029A700C6048F /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AFE3941A0029A700C6048F /* Nimble.framework */; };
|
||||
D0B1DDCE17B487FB0059C355 /* unused-helper in Copy Executables */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; };
|
||||
D0B1DDCE17B487FB0059C355 /* unused-helper in Copy Executables */ = {isa = PBXBuildFile; fileRef = D0B1DDCB17B487D90059C355 /* unused-helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
D0C22BDB179CC00E00158214 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BDA179CC00E00158214 /* Cocoa.framework */; };
|
||||
D0C22BF2179CC00E00158214 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BDA179CC00E00158214 /* Cocoa.framework */; };
|
||||
D0C22BF5179CC00E00158214 /* Squirrel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C22BD7179CC00E00158214 /* Squirrel.framework */; };
|
||||
@@ -145,7 +145,6 @@
|
||||
D0C22C52179CC23900158214 /* Squirrel.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C22BE7179CC00E00158214 /* Squirrel.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D0D2B6261804E903000EA901 /* SQRLDirectoryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D2B6241804E903000EA901 /* SQRLDirectoryManager.m */; };
|
||||
D0D2B6271804E903000EA901 /* SQRLDirectoryManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D2B6241804E903000EA901 /* SQRLDirectoryManager.m */; };
|
||||
D0D97E4517B58FC400301C72 /* ResourceRules.plist in Resources */ = {isa = PBXBuildFile; fileRef = D0D97E4417B58FC400301C72 /* ResourceRules.plist */; };
|
||||
D0EDBE9D17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBE9B17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m */; };
|
||||
D0EDBEDD17B0DB3E0058BC3C /* QuickSpec+SQRLFixtures.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBEDC17B0DB3E0058BC3C /* QuickSpec+SQRLFixtures.m */; };
|
||||
D0EDBEE117B0E3650058BC3C /* SQRLCodeSignatureSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = D0EDBEE017B0E3650058BC3C /* SQRLCodeSignatureSpec.m */; };
|
||||
@@ -156,8 +155,8 @@
|
||||
D4ACA50118DB9F3F00EBD899 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4ACA50018DB9F3F00EBD899 /* OHHTTPStubs.framework */; };
|
||||
D4C4909918E46DE900786EFE /* Mantle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909818E46DE900786EFE /* Mantle.framework */; };
|
||||
D4C4909B18E46DFE00786EFE /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909A18E46DFE00786EFE /* ReactiveCocoa.framework */; };
|
||||
D4C4909E18E46E1000786EFE /* ReactiveCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909A18E46DFE00786EFE /* ReactiveCocoa.framework */; };
|
||||
D4C4909F18E46E1000786EFE /* Mantle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909818E46DE900786EFE /* Mantle.framework */; };
|
||||
D4C4909E18E46E1000786EFE /* ReactiveCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909A18E46DFE00786EFE /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
D4C4909F18E46E1000786EFE /* Mantle.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D4C4909818E46DE900786EFE /* Mantle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
F6EB2162179CFD93001108CF /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6EB2161179CFD93001108CF /* SystemConfiguration.framework */; };
|
||||
F6EB2180179D0E4D001108CF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6EB217F179D0E4D001108CF /* Security.framework */; };
|
||||
F6F2ED8417A33C4900F4D220 /* SQRLCodeSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = F6F2ED8217A33C4900F4D220 /* SQRLCodeSignature.m */; };
|
||||
@@ -267,10 +266,8 @@
|
||||
534FF36617D8E90A0020A51A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
534FF36817D8E90A0020A51A /* TestService-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestService-Prefix.pch"; sourceTree = "<group>"; };
|
||||
534FF36917D8E90A0020A51A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLDeepCodesignSpec.m; sourceTree = "<group>"; };
|
||||
53710D7D17D8F5C300A992DE /* bootstrap */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = bootstrap; sourceTree = "<group>"; };
|
||||
53710D7E17D8F5C300A992DE /* cibuild */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = cibuild; sourceTree = "<group>"; };
|
||||
53710D7F17D8F5C300A992DE /* deep-codesign */ = {isa = PBXFileReference; lastKnownFileType = text; path = "deep-codesign"; sourceTree = "<group>"; };
|
||||
53710D8017D8F5C300A992DE /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.md; sourceTree = "<group>"; };
|
||||
53710D8117D8F5C300A992DE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = "<group>"; };
|
||||
53710D8217D8F5C300A992DE /* targets.awk */ = {isa = PBXFileReference; lastKnownFileType = text; path = targets.awk; sourceTree = "<group>"; };
|
||||
@@ -287,6 +284,12 @@
|
||||
5397A69C187DF6490014A477 /* SQRLInstaller+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SQRLInstaller+Private.h"; sourceTree = "<group>"; };
|
||||
53AACF4917E9CA0500B41027 /* SQRLUpdate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQRLUpdate.h; sourceTree = "<group>"; };
|
||||
53AACF4A17E9CA0500B41027 /* SQRLUpdate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLUpdate.m; sourceTree = "<group>"; };
|
||||
88CA21911BD980FC006693B7 /* metamacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = metamacros.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/metamacros.h; sourceTree = SOURCE_ROOT; };
|
||||
88CA21921BD980FC006693B7 /* MTLEXTKeyPathCoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTLEXTKeyPathCoding.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTKeyPathCoding.h; sourceTree = SOURCE_ROOT; };
|
||||
88CA21931BD980FC006693B7 /* MTLEXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTLEXTRuntimeExtensions.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTRuntimeExtensions.h; sourceTree = SOURCE_ROOT; };
|
||||
88CA21941BD980FC006693B7 /* MTLEXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTLEXTRuntimeExtensions.m; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTRuntimeExtensions.m; sourceTree = SOURCE_ROOT; };
|
||||
88CA21951BD980FC006693B7 /* MTLEXTScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTLEXTScope.h; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTScope.h; sourceTree = SOURCE_ROOT; };
|
||||
88CA21961BD980FC006693B7 /* MTLEXTScope.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MTLEXTScope.m; path = Carthage/Checkouts/Mantle/Mantle/extobjc/MTLEXTScope.m; sourceTree = SOURCE_ROOT; };
|
||||
D000218C17BACEFF0050109A /* SQRLZipArchiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQRLZipArchiver.h; sourceTree = "<group>"; };
|
||||
D000218D17BACEFF0050109A /* SQRLZipArchiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLZipArchiver.m; sourceTree = "<group>"; };
|
||||
D000219617BAD34D0050109A /* TestApplication.app.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = TestApplication.app.zip; sourceTree = "<group>"; };
|
||||
@@ -314,6 +317,7 @@
|
||||
D06B58B218032B1500656D97 /* RACSignal+SQRLTransactionExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACSignal+SQRLTransactionExtensions.h"; sourceTree = "<group>"; };
|
||||
D06B58B318032B1500656D97 /* RACSignal+SQRLTransactionExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RACSignal+SQRLTransactionExtensions.m"; sourceTree = "<group>"; };
|
||||
D06B58D018035B5A00656D97 /* SQRLTerminationListenerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLTerminationListenerSpec.m; sourceTree = "<group>"; };
|
||||
D06F7B0F1AD72DD30009A3BC /* RACKVOProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RACKVOProxy.m; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/RACKVOProxy.m; sourceTree = SOURCE_ROOT; };
|
||||
D08D4E0C17B451500012B22D /* TestApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApplication.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D08D4E1017B451500012B22D /* TestApplication-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TestApplication-Info.plist"; sourceTree = "<group>"; };
|
||||
D08D4E1217B451500012B22D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@@ -331,8 +335,6 @@
|
||||
D09C66281A00520B007D7ED0 /* NSArray+RACSequenceAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSArray+RACSequenceAdditions.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSArray+RACSequenceAdditions.m"; sourceTree = SOURCE_ROOT; };
|
||||
D09C662A1A005226007D7ED0 /* NSObject+RACKVOWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACKVOWrapper.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACKVOWrapper.m"; sourceTree = SOURCE_ROOT; };
|
||||
D09C662B1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACPropertySubscribing.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACPropertySubscribing.m"; sourceTree = SOURCE_ROOT; };
|
||||
D09C66311A0052DE007D7ED0 /* EXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EXTRuntimeExtensions.h; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc/EXTRuntimeExtensions.h; sourceTree = SOURCE_ROOT; };
|
||||
D09C66321A0052DE007D7ED0 /* EXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EXTRuntimeExtensions.m; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc/EXTRuntimeExtensions.m; sourceTree = SOURCE_ROOT; };
|
||||
D09C66341A00535B007D7ED0 /* RACKVOTrampoline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RACKVOTrampoline.m; path = Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/RACKVOTrampoline.m; sourceTree = SOURCE_ROOT; };
|
||||
D09C66361A00537A007D7ED0 /* NSObject+RACDeallocating.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACDeallocating.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACDeallocating.m"; sourceTree = SOURCE_ROOT; };
|
||||
D09C66371A00537A007D7ED0 /* NSObject+RACDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+RACDescription.m"; path = "Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/NSObject+RACDescription.m"; sourceTree = SOURCE_ROOT; };
|
||||
@@ -422,7 +424,6 @@
|
||||
D0C22C4F179CC18C00158214 /* SQRLUpdaterSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLUpdaterSpec.m; sourceTree = "<group>"; };
|
||||
D0D2B6231804E903000EA901 /* SQRLDirectoryManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQRLDirectoryManager.h; sourceTree = "<group>"; };
|
||||
D0D2B6241804E903000EA901 /* SQRLDirectoryManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQRLDirectoryManager.m; sourceTree = "<group>"; };
|
||||
D0D97E4417B58FC400301C72 /* ResourceRules.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ResourceRules.plist; sourceTree = "<group>"; };
|
||||
D0EDBE9A17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSError+SQRLVerbosityExtensions.h"; path = "Squirrel/NSError+SQRLVerbosityExtensions.h"; sourceTree = SOURCE_ROOT; };
|
||||
D0EDBE9B17B0C68E0058BC3C /* NSError+SQRLVerbosityExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSError+SQRLVerbosityExtensions.m"; path = "Squirrel/NSError+SQRLVerbosityExtensions.m"; sourceTree = SOURCE_ROOT; };
|
||||
D0EDBEDB17B0DB3E0058BC3C /* QuickSpec+SQRLFixtures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "QuickSpec+SQRLFixtures.h"; sourceTree = "<group>"; };
|
||||
@@ -529,7 +530,6 @@
|
||||
children = (
|
||||
53710D7D17D8F5C300A992DE /* bootstrap */,
|
||||
53710D7E17D8F5C300A992DE /* cibuild */,
|
||||
53710D7F17D8F5C300A992DE /* deep-codesign */,
|
||||
53710D8017D8F5C300A992DE /* LICENSE.md */,
|
||||
53710D8117D8F5C300A992DE /* README.md */,
|
||||
53710D8217D8F5C300A992DE /* targets.awk */,
|
||||
@@ -621,7 +621,6 @@
|
||||
D08D4E0F17B451500012B22D /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0D97E4417B58FC400301C72 /* ResourceRules.plist */,
|
||||
D0B1DDCB17B487D90059C355 /* unused-helper */,
|
||||
D08D4E1017B451500012B22D /* TestApplication-Info.plist */,
|
||||
D08D4E1117B451500012B22D /* InfoPlist.strings */,
|
||||
@@ -687,8 +686,12 @@
|
||||
D0AFE3371A00282000C6048F /* Static Dependencies */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D09C66311A0052DE007D7ED0 /* EXTRuntimeExtensions.h */,
|
||||
D09C66321A0052DE007D7ED0 /* EXTRuntimeExtensions.m */,
|
||||
88CA21911BD980FC006693B7 /* metamacros.h */,
|
||||
88CA21921BD980FC006693B7 /* MTLEXTKeyPathCoding.h */,
|
||||
88CA21931BD980FC006693B7 /* MTLEXTRuntimeExtensions.h */,
|
||||
88CA21941BD980FC006693B7 /* MTLEXTRuntimeExtensions.m */,
|
||||
88CA21951BD980FC006693B7 /* MTLEXTScope.h */,
|
||||
88CA21961BD980FC006693B7 /* MTLEXTScope.m */,
|
||||
D0AFE3381A00282C00C6048F /* MTLJSONAdapter.m */,
|
||||
D0AFE3391A00282C00C6048F /* MTLModel.m */,
|
||||
D09C66401A005592007D7ED0 /* MTLModel+NSCoding.m */,
|
||||
@@ -722,6 +725,7 @@
|
||||
D0AFE3551A0028A400C6048F /* RACEvent.m */,
|
||||
D0AFE3561A0028A400C6048F /* RACGroupedSignal.m */,
|
||||
D0AFE3571A0028A400C6048F /* RACImmediateScheduler.m */,
|
||||
D06F7B0F1AD72DD30009A3BC /* RACKVOProxy.m */,
|
||||
D09C66341A00535B007D7ED0 /* RACKVOTrampoline.m */,
|
||||
D0AFE3581A0028A400C6048F /* RACMulticastConnection.m */,
|
||||
D09C663A1A00538D007D7ED0 /* RACObjCRuntime.m */,
|
||||
@@ -922,7 +926,6 @@
|
||||
children = (
|
||||
D00F5B8D17E82DE6009A4818 /* NSProcessInfoExtensionsSpec.m */,
|
||||
D0EDBEE017B0E3650058BC3C /* SQRLCodeSignatureSpec.m */,
|
||||
53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */,
|
||||
D049059A180554BA004E683E /* SQRLDirectoryManagerSpec.m */,
|
||||
D09D244017B595470001FAF8 /* SQRLInstallerSpec.m */,
|
||||
D049059C18055671004E683E /* SQRLShipItRequestSpec.m */,
|
||||
@@ -1070,8 +1073,18 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
CLASSPREFIX = SQRL;
|
||||
LastUpgradeCheck = 0510;
|
||||
LastSwiftUpdateCheck = 0710;
|
||||
LastUpgradeCheck = 0830;
|
||||
ORGANIZATIONNAME = GitHub;
|
||||
TargetAttributes = {
|
||||
D08D4E0B17B451500012B22D = {
|
||||
DevelopmentTeam = C4RRYD56A6;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
D0C22BEE179CC00E00158214 = {
|
||||
LastSwiftMigration = 0830;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = D0C22BD1179CC00E00158214 /* Build configuration list for PBXProject "Squirrel" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
@@ -1111,7 +1124,6 @@
|
||||
D08D4E1317B451500012B22D /* InfoPlist.strings in Resources */,
|
||||
D08D4E2117B451500012B22D /* Images.xcassets in Resources */,
|
||||
D08D4E1917B451500012B22D /* Credits.rtf in Resources */,
|
||||
D0D97E4517B58FC400301C72 /* ResourceRules.plist in Resources */,
|
||||
D03BE98D17B6174500458593 /* MainMenu.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1130,7 +1142,6 @@
|
||||
D08D4E4A17B451FD0012B22D /* TestApplication.app in Resources */,
|
||||
D09D244A17B59AB30001FAF8 /* TestApplication 2.1.app in Resources */,
|
||||
D000219717BAD34D0050109A /* TestApplication.app.zip in Resources */,
|
||||
53710D8417D8F5CB00A992DE /* deep-codesign in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1164,6 +1175,7 @@
|
||||
D0AFE37C1A0028B000C6048F /* RACCompoundDisposableProvider.d in Sources */,
|
||||
D0AFE3441A00285000C6048F /* NSValueTransformer+MTLPredefinedTransformerAdditions.m in Sources */,
|
||||
D0AFE38B1A0028EC00C6048F /* RACArraySequence.m in Sources */,
|
||||
D06F7B101AD72DD30009A3BC /* RACKVOProxy.m in Sources */,
|
||||
D0AFE3751A0028A400C6048F /* RACSubject.m in Sources */,
|
||||
D014AC0517B97885007D79D0 /* ShipIt-main.m in Sources */,
|
||||
D0AFE3431A00285000C6048F /* MTLReflection.m in Sources */,
|
||||
@@ -1194,11 +1206,11 @@
|
||||
D0AFE38E1A0028EC00C6048F /* RACUnarySequence.m in Sources */,
|
||||
D0AFE33B1A00282C00C6048F /* MTLJSONAdapter.m in Sources */,
|
||||
D09C66351A00535B007D7ED0 /* RACKVOTrampoline.m in Sources */,
|
||||
88CA21981BD980FC006693B7 /* MTLEXTScope.m in Sources */,
|
||||
D0AFE3691A0028A400C6048F /* RACEmptySignal.m in Sources */,
|
||||
D09C66391A00537A007D7ED0 /* NSObject+RACDescription.m in Sources */,
|
||||
D0D2B6271804E903000EA901 /* SQRLDirectoryManager.m in Sources */,
|
||||
D0AFE36D1A0028A400C6048F /* RACImmediateScheduler.m in Sources */,
|
||||
D09C66331A0052DE007D7ED0 /* EXTRuntimeExtensions.m in Sources */,
|
||||
D0AFE36A1A0028A400C6048F /* RACErrorSignal.m in Sources */,
|
||||
D0AFE3791A0028A400C6048F /* RACTuple.m in Sources */,
|
||||
D0AFE3811A0028CC00C6048F /* RACPassthroughSubscriber.m in Sources */,
|
||||
@@ -1207,6 +1219,7 @@
|
||||
D0AFE36C1A0028A400C6048F /* RACGroupedSignal.m in Sources */,
|
||||
D0AFE34B1A00287100C6048F /* RACDisposable.m in Sources */,
|
||||
D0AFE3731A0028A400C6048F /* RACSignalSequence.m in Sources */,
|
||||
88CA21971BD980FC006693B7 /* MTLEXTRuntimeExtensions.m in Sources */,
|
||||
D09C662D1A005226007D7ED0 /* NSObject+RACPropertySubscribing.m in Sources */,
|
||||
D0AFE3861A0028D500C6048F /* RACSignalProvider.d in Sources */,
|
||||
D0AFE36B1A0028A400C6048F /* RACEvent.m in Sources */,
|
||||
@@ -1265,7 +1278,6 @@
|
||||
D06B58D118035B5A00656D97 /* SQRLTerminationListenerSpec.m in Sources */,
|
||||
5371815F18A29DC8005ED798 /* TestAppConstants.m in Sources */,
|
||||
D0EDBEE117B0E3650058BC3C /* SQRLCodeSignatureSpec.m in Sources */,
|
||||
53710D7417D8F59700A992DE /* SQRLDeepCodesignSpec.m in Sources */,
|
||||
D00F5B8E17E82DE6009A4818 /* NSProcessInfoExtensionsSpec.m in Sources */,
|
||||
D049059B180554BA004E683E /* SQRLDirectoryManagerSpec.m in Sources */,
|
||||
5397A69D187DF8610014A477 /* SQRLInstallerOwnedBundle.m in Sources */,
|
||||
@@ -1340,6 +1352,7 @@
|
||||
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestService/TestService-Info.plist";
|
||||
MACH_O_TYPE = mh_execute;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
|
||||
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
|
||||
WRAPPER_EXTENSION = xpc;
|
||||
};
|
||||
@@ -1352,6 +1365,7 @@
|
||||
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestService/TestService-Info.plist";
|
||||
MACH_O_TYPE = mh_execute;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
|
||||
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
|
||||
WRAPPER_EXTENSION = xpc;
|
||||
};
|
||||
@@ -1364,6 +1378,7 @@
|
||||
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestService/TestService-Info.plist";
|
||||
MACH_O_TYPE = mh_execute;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
|
||||
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
|
||||
WRAPPER_EXTENSION = xpc;
|
||||
};
|
||||
@@ -1376,6 +1391,7 @@
|
||||
GCC_PREFIX_HEADER = "TestService/TestService-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestService/TestService-Info.plist";
|
||||
MACH_O_TYPE = mh_execute;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "${PRODUCT_NAME}";
|
||||
PRODUCT_NAME = "com.github.Squirrel.TestApplication.$(TARGET_NAME:rfc1034identifier)";
|
||||
WRAPPER_EXTENSION = xpc;
|
||||
};
|
||||
@@ -1462,12 +1478,14 @@
|
||||
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
DEVELOPMENT_TEAM = C4RRYD56A6;
|
||||
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
|
||||
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -1478,12 +1496,14 @@
|
||||
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
DEVELOPMENT_TEAM = C4RRYD56A6;
|
||||
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
|
||||
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -1494,12 +1514,14 @@
|
||||
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
DEVELOPMENT_TEAM = C4RRYD56A6;
|
||||
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
|
||||
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -1510,12 +1532,14 @@
|
||||
baseConfigurationReference = D0C22C17179CC02E00158214 /* Mac-Application.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_RESOURCE_RULES_PATH = TestApplication/ResourceRules.plist;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
DEVELOPMENT_TEAM = C4RRYD56A6;
|
||||
GCC_PREFIX_HEADER = "TestApplication/TestApplication-Prefix.pch";
|
||||
INFOPLIST_FILE = "TestApplication/TestApplication-Info.plist";
|
||||
OTHER_CODE_SIGN_FLAGS = "--timestamp=none";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.Squirrel.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WRAPPER_EXTENSION = app;
|
||||
};
|
||||
@@ -1525,7 +1549,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C0B179CC02E00158214 /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1534,8 +1563,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C0D179CC02E00158214 /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1543,6 +1576,8 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
FRAMEWORK_VERSION = A;
|
||||
@@ -1551,6 +1586,7 @@
|
||||
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
|
||||
);
|
||||
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
WRAPPER_EXTENSION = framework;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1559,6 +1595,8 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
FRAMEWORK_VERSION = A;
|
||||
@@ -1567,6 +1605,7 @@
|
||||
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
|
||||
);
|
||||
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
WRAPPER_EXTENSION = framework;
|
||||
};
|
||||
name = Release;
|
||||
@@ -1585,7 +1624,9 @@
|
||||
);
|
||||
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1603,7 +1644,9 @@
|
||||
);
|
||||
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -1611,8 +1654,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C0C179CC02E00158214 /* Profile.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
@@ -1620,6 +1667,8 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
FRAMEWORK_VERSION = A;
|
||||
@@ -1628,6 +1677,7 @@
|
||||
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
|
||||
);
|
||||
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
WRAPPER_EXTENSION = framework;
|
||||
};
|
||||
name = Profile;
|
||||
@@ -1646,7 +1696,9 @@
|
||||
);
|
||||
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
@@ -1654,6 +1706,9 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C0E179CC02E00158214 /* Test.xcconfig */;
|
||||
buildSettings = {
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
@@ -1663,6 +1718,8 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D0C22C1A179CC02E00158214 /* Mac-Framework.xcconfig */;
|
||||
buildSettings = {
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
FRAMEWORK_VERSION = A;
|
||||
@@ -1671,6 +1728,7 @@
|
||||
Carthage/Checkouts/ReactiveCocoa/ReactiveCocoa/extobjc,
|
||||
);
|
||||
INFOPLIST_FILE = "Squirrel/Squirrel-Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
WRAPPER_EXTENSION = framework;
|
||||
};
|
||||
name = Test;
|
||||
@@ -1689,7 +1747,9 @@
|
||||
);
|
||||
INFOPLIST_FILE = "SquirrelTests/SquirrelTests-Info.plist";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.github.${PRODUCT_NAME:rfc1034identifier}";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Test;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0610"
|
||||
LastUpgradeVersion = "0830"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -37,10 +37,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "NO"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "NO">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -68,17 +68,30 @@
|
||||
value = "com.github.Squirrel.SquirrelTests"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SQUIRREL_TEST_LOCAL_CDN"
|
||||
value = "True"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "SQUIRREL_TEST_LOCAL_SERVER"
|
||||
value = "True"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugXPCServices = "NO"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -100,10 +113,10 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Profile"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0610"
|
||||
LastUpgradeVersion = "0830"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,10 +23,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Test"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Test">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
@@ -38,17 +38,21 @@
|
||||
ReferencedContainer = "container:Squirrel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D08D4E0B17B451500012B22D"
|
||||
@@ -61,12 +65,13 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D08D4E0B17B451500012B22D"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0610"
|
||||
LastUpgradeVersion = "0830"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,21 +23,24 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Test"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Test">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -52,10 +55,10 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
|
||||
@@ -14,4 +14,7 @@
|
||||
// Info.plist, or nil if the key is not present.
|
||||
@property (nonatomic, copy, readonly) NSString *sqrl_bundleVersion;
|
||||
|
||||
/// The value of the `kCFBundleExecutableKey` key.
|
||||
@property (nonatomic, copy, readonly) NSString *sqrl_executableName;
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,4 +14,8 @@
|
||||
return [self objectForInfoDictionaryKey:(id)kCFBundleVersionKey];
|
||||
}
|
||||
|
||||
- (NSString *)sqrl_executableName {
|
||||
return [self objectForInfoDictionaryKey:(id)kCFBundleExecutableKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,14 +29,24 @@
|
||||
// on-disk locations for. This must not be nil.
|
||||
- (instancetype)initWithApplicationIdentifier:(NSString *)appIdentifier;
|
||||
|
||||
// Finds or creates an Application Support folder.
|
||||
// Finds or creates a storage folder.
|
||||
//
|
||||
// Returns a signal which synchronously sends a URL then completes, or errors.
|
||||
- (RACSignal *)applicationSupportURL;
|
||||
- (RACSignal *)storageURL;
|
||||
|
||||
// Determines where archived `SQRLShipItState` should be saved.
|
||||
//
|
||||
// Returns a signal which synchronously sends a URL then completes, or errors.
|
||||
- (RACSignal *)shipItStateURL;
|
||||
|
||||
// Determines where ShipIt's stdout log should be saved.
|
||||
//
|
||||
// Returns a signal which synchronously sends a URL then completes, or errors.
|
||||
- (RACSignal *)shipItStdoutURL;
|
||||
|
||||
// Determines where ShipIt's stderr log should be saved.
|
||||
//
|
||||
// Returns a signal which synchronously sends a URL then completes, or errors.
|
||||
- (RACSignal *)shipItStderrURL;
|
||||
|
||||
@end
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
|
||||
#pragma mark Folder URLs
|
||||
|
||||
- (RACSignal *)applicationSupportURL {
|
||||
- (RACSignal *)storageURL {
|
||||
return [[RACSignal
|
||||
defer:^{
|
||||
NSError *error = nil;
|
||||
NSURL *folderURL = [NSFileManager.defaultManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
|
||||
NSURL *folderURL = [NSFileManager.defaultManager URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error];
|
||||
if (folderURL == nil) {
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
@@ -63,16 +63,55 @@
|
||||
|
||||
return [RACSignal return:folderURL];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -applicationSupportURL", self];
|
||||
setNameWithFormat:@"%@ -storageURL", self];
|
||||
}
|
||||
|
||||
// Returns a signal to a URL with the given filename and job name inside of the storage folder
|
||||
//
|
||||
// filename - The name of the file in the storage folder to return in this URL
|
||||
// jobname - The name to give the returned RACSignal
|
||||
// ensureWritable - If set to true, we will append an integer to the end of the filename in order to find a writeable URL.
|
||||
// This fixes an issue wherein the stdout and stderr files may have been owned by root, and thus prevented ShipIt from launching.
|
||||
- (RACSignal *)URLForFileNamed:(NSString *)filename withJobNamed:(NSString *)jobname ensureWritable:(BOOL)ensureWritable {
|
||||
return [[[self
|
||||
storageURL]
|
||||
map:^(NSURL *folderURL) {
|
||||
NSURL *fileURL = [folderURL URLByAppendingPathComponent:filename];
|
||||
if (ensureWritable) {
|
||||
|
||||
int attempts = 0;
|
||||
while (attempts < 100) {
|
||||
|
||||
NSNumber *writable = nil;
|
||||
NSError *writableError = nil;
|
||||
BOOL gotWritable = [fileURL getResourceValue:&writable forKey:NSURLIsWritableKey error:&writableError];
|
||||
|
||||
if (!gotWritable || writable.boolValue) {
|
||||
return fileURL;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
fileURL = [folderURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%i", filename, attempts]];
|
||||
}
|
||||
|
||||
NSLog(@"Error: Unable to find writable log file after 100 attempts.");
|
||||
}
|
||||
|
||||
return fileURL;
|
||||
}]
|
||||
setNameWithFormat:@"%@ -%@", self, jobname];
|
||||
}
|
||||
|
||||
- (RACSignal *)shipItStateURL {
|
||||
return [[[self
|
||||
applicationSupportURL]
|
||||
map:^(NSURL *folderURL) {
|
||||
return [folderURL URLByAppendingPathComponent:@"ShipItState.plist"];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -shipItStateURL", self];
|
||||
return [self URLForFileNamed:@"ShipItState.plist" withJobNamed:@"shipItStateURL" ensureWritable:false];
|
||||
}
|
||||
|
||||
- (RACSignal *)shipItStdoutURL {
|
||||
return [self URLForFileNamed:@"ShipIt_stdout.log" withJobNamed:@"shipItStdoutURL" ensureWritable:true];
|
||||
}
|
||||
|
||||
- (RACSignal *)shipItStderrURL {
|
||||
return [self URLForFileNamed:@"ShipIt_stderr.log" withJobNamed:@"shipItStderrURL" ensureWritable:true];
|
||||
}
|
||||
|
||||
#pragma mark NSObject
|
||||
|
||||
@@ -40,9 +40,9 @@
|
||||
#pragma mark MTLJSONSerializing
|
||||
|
||||
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
|
||||
return [super.JSONKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
|
||||
return @{
|
||||
@keypath(SQRLDownloadedUpdate.new, bundleURL): NSNull.null
|
||||
}];
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+89
-18
@@ -22,7 +22,6 @@
|
||||
#import "NSError+SQRLVerbosityExtensions.h"
|
||||
#import "RACSignal+SQRLTransactionExtensions.h"
|
||||
#import "SQRLCodeSignature.h"
|
||||
#import "SQRLDirectoryManager.h"
|
||||
#import "SQRLShipItRequest.h"
|
||||
#import "SQRLTerminationListener.h"
|
||||
#import "SQRLInstallerOwnedBundle.h"
|
||||
@@ -262,27 +261,49 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
|
||||
setNameWithFormat:@"%@ -acquireTargetBundleURLForRequest: %@", self, request];
|
||||
}
|
||||
|
||||
- (RACSignal *)renameIfNeeded:(SQRLShipItRequest *)request updateBundleURL:(NSURL *)updateBundleURL {
|
||||
if (!request.useUpdateBundleName) return [RACSignal return:request];
|
||||
|
||||
return [[self
|
||||
renamedTargetIfNeededWithTargetURL:request.targetBundleURL sourceURL:updateBundleURL]
|
||||
flattenMap:^(NSURL *newTargetURL) {
|
||||
if ([newTargetURL isEqual:request.targetBundleURL]) return [RACSignal return:request];
|
||||
|
||||
SQRLShipItRequest *updatedRequest = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:newTargetURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:request.launchAfterInstallation useUpdateBundleName:request.useUpdateBundleName];
|
||||
return [[self
|
||||
installItemToURL:newTargetURL fromURL:request.targetBundleURL]
|
||||
concat:[RACSignal return:updatedRequest]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (RACSignal *)installRequest:(SQRLShipItRequest *)request {
|
||||
NSParameterAssert(request != nil);
|
||||
|
||||
return [[[[self
|
||||
prepareAndValidateUpdateBundleURLForRequest:request]
|
||||
flattenMap:^(NSURL *updateBundleURL) {
|
||||
return [[[[[[[self
|
||||
acquireTargetBundleURLForRequest:request]
|
||||
concat:[self installItemToURL:request.targetBundleURL fromURL:updateBundleURL]]
|
||||
concat:[RACSignal return:request.updateBundleURL]]
|
||||
concat:[RACSignal return:updateBundleURL]]
|
||||
concat:[RACSignal defer:^{
|
||||
return [RACSignal return:self.ownedBundle.temporaryURL];
|
||||
}]]
|
||||
flattenMap:^(NSURL *location) {
|
||||
return [[[self
|
||||
deleteOwnedBundleAtURL:location]
|
||||
doError:^(NSError *error) {
|
||||
NSLog(@"Couldn't remove owned bundle at location %@, error %@", location, error.sqrl_verboseDescription);
|
||||
return [[[[self
|
||||
renameIfNeeded:request updateBundleURL:updateBundleURL]
|
||||
flattenMap:^(SQRLShipItRequest *request) {
|
||||
return [[self acquireTargetBundleURLForRequest:request] concat:[RACSignal return:request]];
|
||||
}]
|
||||
flattenMap:^(SQRLShipItRequest *request) {
|
||||
return [[[[[[self
|
||||
installItemToURL:request.targetBundleURL fromURL:updateBundleURL]
|
||||
concat:[RACSignal return:request.updateBundleURL]]
|
||||
concat:[RACSignal return:updateBundleURL]]
|
||||
concat:[RACSignal defer:^{
|
||||
return [RACSignal return:self.ownedBundle.temporaryURL];
|
||||
}]]
|
||||
flattenMap:^(NSURL *location) {
|
||||
return [[[self
|
||||
deleteOwnedBundleAtURL:location]
|
||||
doError:^(NSError *error) {
|
||||
NSLog(@"Couldn't remove owned bundle at location %@, error %@", location, error.sqrl_verboseDescription);
|
||||
}]
|
||||
catchTo:[RACSignal empty]];
|
||||
}]
|
||||
catchTo:[RACSignal empty]];
|
||||
concat:[RACSignal return:request]];
|
||||
}]
|
||||
doCompleted:^{
|
||||
self.ownedBundle = nil;
|
||||
@@ -380,7 +401,20 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
|
||||
if (rmdir(temporaryDirectoryURL.path.fileSystemRepresentation) == 0) {
|
||||
return [RACSignal empty];
|
||||
} else {
|
||||
return [RACSignal error:[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]];
|
||||
int code = errno;
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
|
||||
const char *desc = strerror(code);
|
||||
if (desc != NULL) {
|
||||
userInfo[NSLocalizedDescriptionKey] = @(desc);
|
||||
} else {
|
||||
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Unknown POSIX error", @"");
|
||||
}
|
||||
|
||||
userInfo[NSLocalizedFailureReasonErrorKey] = [NSString stringWithFormat:NSLocalizedString(@"Couldn't remove temp dir \"%@\"", @""), temporaryDirectoryURL.path];
|
||||
userInfo[NSURLErrorKey] = temporaryDirectoryURL;
|
||||
|
||||
return [RACSignal error:[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]];
|
||||
}
|
||||
}]
|
||||
setNameWithFormat:@"%@ -deleteOwnedBundleAtURL: %@", self, bundleURL];
|
||||
@@ -413,6 +447,36 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
|
||||
|
||||
#pragma mark Installation
|
||||
|
||||
/// Check if the target should be renamed and provide the renamed URL.
|
||||
///
|
||||
/// targetURL - The URL for the target. Cannot be nil.
|
||||
/// sourceURL - The URL for the source. Cannot be nil.
|
||||
///
|
||||
/// Returns a signal which will send the URL for the renamed target. If a rename
|
||||
/// isn't needed then it will send `targetURL`.
|
||||
- (RACSignal *)renamedTargetIfNeededWithTargetURL:(NSURL *)targetURL sourceURL:(NSURL *)sourceURL {
|
||||
return [RACSignal defer:^{
|
||||
NSBundle *sourceBundle = [NSBundle bundleWithURL:sourceURL];
|
||||
NSString *targetExecutableName = targetURL.lastPathComponent.stringByDeletingPathExtension;
|
||||
NSString *sourceExecutableName = sourceBundle.sqrl_executableName;
|
||||
|
||||
// If they're already the same then we're good.
|
||||
if ([targetExecutableName isEqual:sourceExecutableName]) {
|
||||
return [RACSignal return:targetURL];
|
||||
}
|
||||
|
||||
NSString *newAppName = [sourceExecutableName stringByAppendingPathExtension:@"app"];
|
||||
NSURL *newTargetURL = [targetURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:newAppName];
|
||||
|
||||
// If there's already something there then don't rename to it.
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:newTargetURL.path]) {
|
||||
return [RACSignal return:targetURL];
|
||||
}
|
||||
|
||||
return [RACSignal return:newTargetURL];
|
||||
}];
|
||||
}
|
||||
|
||||
- (RACSignal *)installItemToURL:(NSURL *)targetURL fromURL:(NSURL *)sourceURL {
|
||||
NSParameterAssert(targetURL != nil);
|
||||
NSParameterAssert(sourceURL != nil);
|
||||
@@ -453,7 +517,7 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
|
||||
setNameWithFormat:@"%@ -installItemAtURL: %@ fromURL: %@", self, targetURL, sourceURL];
|
||||
}
|
||||
|
||||
#pragma Quarantine Bit Removal
|
||||
#pragma mark Quarantine Bit Removal
|
||||
|
||||
- (RACSignal *)clearQuarantineForDirectory:(NSURL *)directory {
|
||||
NSParameterAssert(directory != nil);
|
||||
@@ -479,7 +543,14 @@ NSString * const SQRLInstallerOwnedBundleKey = @"SQRLInstallerOwnedBundle";
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
|
||||
const char *desc = strerror(code);
|
||||
if (desc != NULL) userInfo[NSLocalizedDescriptionKey] = @(desc);
|
||||
if (desc != NULL) {
|
||||
userInfo[NSLocalizedDescriptionKey] = @(desc);
|
||||
} else {
|
||||
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Unknown POSIX error", @"");
|
||||
}
|
||||
|
||||
userInfo[NSLocalizedFailureReasonErrorKey] = [NSString stringWithFormat:NSLocalizedString(@"Couldn't remove quarantine attribute from \"%@\". This most likely means the file is read-only.", @""), URL.path];
|
||||
userInfo[NSURLErrorKey] = URL;
|
||||
|
||||
return [RACSignal error:[NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:userInfo]];
|
||||
}
|
||||
|
||||
@@ -31,9 +31,13 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
|
||||
return [[[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:jobLabel];
|
||||
return [directoryManager applicationSupportURL];
|
||||
return [RACSignal zip:@[
|
||||
[directoryManager shipItStdoutURL],
|
||||
[directoryManager shipItStderrURL],
|
||||
[directoryManager shipItStateURL]
|
||||
]];
|
||||
}]
|
||||
map:^(NSURL *appSupportURL) {
|
||||
reduceEach:^(NSURL *stdoutURL, NSURL *stderrURL, NSURL *shipItStateURL) {
|
||||
NSBundle *squirrelBundle = [NSBundle bundleForClass:self.class];
|
||||
NSAssert(squirrelBundle != nil, @"Could not open Squirrel.framework bundle");
|
||||
|
||||
@@ -56,9 +60,15 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
|
||||
// Pass in the service name so ShipIt knows how to broadcast itself.
|
||||
[arguments addObject:jobLabel];
|
||||
|
||||
// We need to pass the path to ShipIt rather than having ShipIt
|
||||
// calculate the path itself. This is because ShipIt may run as a
|
||||
// different user if it had to escalate to be able to replace the
|
||||
// bundle.
|
||||
[arguments addObject:shipItStateURL.path];
|
||||
|
||||
jobDict[@(LAUNCH_JOBKEY_PROGRAMARGUMENTS)] = arguments;
|
||||
jobDict[@(LAUNCH_JOBKEY_STANDARDOUTPATH)] = [appSupportURL URLByAppendingPathComponent:@"ShipIt_stdout.log"].path;
|
||||
jobDict[@(LAUNCH_JOBKEY_STANDARDERRORPATH)] = [appSupportURL URLByAppendingPathComponent:@"ShipIt_stderr.log"].path;
|
||||
jobDict[@(LAUNCH_JOBKEY_STANDARDOUTPATH)] = stdoutURL.path;
|
||||
jobDict[@(LAUNCH_JOBKEY_STANDARDERRORPATH)] = stderrURL.path;
|
||||
|
||||
return jobDict;
|
||||
}]
|
||||
@@ -117,6 +127,9 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
|
||||
setNameWithFormat:@"+shipItAuthorization"];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
+ (RACSignal *)launchPrivileged:(BOOL)privileged {
|
||||
return [[[RACSignal
|
||||
zip:@[
|
||||
@@ -147,4 +160,6 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
|
||||
setNameWithFormat:@"+launchPrivileged: %i", (int)privileged];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
|
||||
@@ -73,10 +73,11 @@ extern NSString * const SQRLShipItRequestPropertyErrorKey;
|
||||
// installing. Can be nil.
|
||||
// launchAfterInstallation - Whether the updated application should be launched
|
||||
// after installation.
|
||||
// useUpdateBundleName - Should the target use the update bundle's name?
|
||||
//
|
||||
// Returns a request which can be written to disk for ShipIt to read and
|
||||
// perform.
|
||||
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation;
|
||||
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation useUpdateBundleName:(BOOL)useUpdateBundleName;
|
||||
|
||||
// The URL to the downloaded update's app bundle.
|
||||
@property (nonatomic, copy, readonly) NSURL *updateBundleURL;
|
||||
@@ -93,4 +94,7 @@ extern NSString * const SQRLShipItRequestPropertyErrorKey;
|
||||
// Whether to launch the application after an update is successfully installed.
|
||||
@property (nonatomic, assign, readonly) BOOL launchAfterInstallation;
|
||||
|
||||
// Whether the app should use the update bundle's name.
|
||||
@property (nonatomic, assign, readonly) BOOL useUpdateBundleName;
|
||||
|
||||
@end
|
||||
|
||||
@@ -47,12 +47,13 @@ NSString * const SQRLShipItRequestPropertyErrorKey = @"SQRLShipItRequestProperty
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation {
|
||||
- (instancetype)initWithUpdateBundleURL:(NSURL *)updateBundleURL targetBundleURL:(NSURL *)targetBundleURL bundleIdentifier:(NSString *)bundleIdentifier launchAfterInstallation:(BOOL)launchAfterInstallation useUpdateBundleName:(BOOL)useUpdateBundleName {
|
||||
return [self initWithDictionary:@{
|
||||
@keypath(self.updateBundleURL): updateBundleURL,
|
||||
@keypath(self.targetBundleURL): targetBundleURL,
|
||||
@keypath(self.bundleIdentifier): bundleIdentifier ?: NSNull.null,
|
||||
@keypath(self.launchAfterInstallation): @(launchAfterInstallation),
|
||||
@keypath(self.useUpdateBundleName): @(useUpdateBundleName),
|
||||
} error:NULL];
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ NSString * const SQRLUpdateJSONPublicationDateKey = @"pub_date";
|
||||
|
||||
+ (NSValueTransformer *)releaseDateJSONTransformer {
|
||||
// ISO 8601 Time Zone with ':'
|
||||
NSString * const ISO8601DateFormat = @"yyyy'-'MM'-'DD'T'HH':'mm':'ssZZZZZ";
|
||||
NSString * const ISO8601DateFormat = @"yyyy'-'MM'-'dd'T'HH':'mm':'ssZZZZZ";
|
||||
|
||||
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^ NSDate * (NSString *dateString) {
|
||||
if (![dateString isKindOfClass:NSString.class]) return nil;
|
||||
|
||||
@@ -23,6 +23,9 @@ typedef enum : NSUInteger {
|
||||
SQRLUpdaterStateAwaitingRelaunch,
|
||||
} SQRLUpdaterState;
|
||||
|
||||
// Block for providing download requests given a download url
|
||||
typedef NSURLRequest * (^SQRLRequestForDownload)(NSURL *);
|
||||
|
||||
// The domain for errors originating within SQRLUpdater.
|
||||
extern NSString * const SQRLUpdaterErrorDomain;
|
||||
|
||||
@@ -64,6 +67,12 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
|
||||
@class RACDisposable;
|
||||
@class RACSignal;
|
||||
|
||||
/// Type of mode used to download the release
|
||||
typedef enum {
|
||||
JSONFILE=1,
|
||||
RELEASESERVER
|
||||
} SQRLUpdaterMode;
|
||||
|
||||
// Checks for, downloads, and installs updates.
|
||||
@interface SQRLUpdater : NSObject
|
||||
|
||||
@@ -92,6 +101,15 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
|
||||
// This property must never be set to nil.
|
||||
@property (atomic, copy) NSURLRequest *updateRequest;
|
||||
|
||||
// The block used for fetching a given download request
|
||||
//
|
||||
// The default value is the argument that was originally passed to
|
||||
// -initWithUpdateRequest:requestForDownload:.
|
||||
//
|
||||
// If initialized with -initWithUpdateRequest: this block will
|
||||
// return a generic NSURLRequest with the provided url.
|
||||
@property (nonatomic, copy) SQRLRequestForDownload requestForDownload;
|
||||
|
||||
// The `SQRLUpdate` subclass to instantiate with the server's response.
|
||||
//
|
||||
// By default, this is `SQRLUpdate` itself, but it can be set to a custom
|
||||
@@ -111,6 +129,45 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
|
||||
// Returns the initialized `SQRLUpdater`.
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest;
|
||||
|
||||
// Initializes an updater that will send the given request to check for updates
|
||||
// on a CDN reading a release file in json format.
|
||||
//
|
||||
// updateRequest - A request to send to check for updates. This request can be
|
||||
// customized as desired, like by including an `Authorization`
|
||||
// header to authenticate with a private update server, or
|
||||
// pointing to a local URL for testing. This must not be nil.
|
||||
// forVersion - the currently installed version
|
||||
//
|
||||
// Returns the initialized `SQRLUpdater`.
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest
|
||||
forVersion:(NSString*)version;
|
||||
|
||||
// Initializes an updater that will send the given request to check for updates
|
||||
// and passes a block to provide requests for the update downloads.
|
||||
//
|
||||
// updateRequest - Same as with initWithUpdateRequest
|
||||
// requestForDownload - Once the update url is found for the update download, allow
|
||||
// providing custom requests that can be costomized as desired.
|
||||
// Useful for including `Authorization` headers just like the
|
||||
// updateRequest param.
|
||||
//
|
||||
// Returns the initialized `SQRLUpdater`.
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload;
|
||||
|
||||
// Initializes an updater that will send the given request to check for updates
|
||||
// and passes a block to provide requests for the update downloads.
|
||||
//
|
||||
// updateRequest - Same as with initWithUpdateRequest
|
||||
// requestForDownload - Once the update url is found for the update download, allow
|
||||
// providing custom requests that can be costomized as desired.
|
||||
// Useful for including `Authorization` headers just like the
|
||||
// updateRequest param.
|
||||
// forVersion - currently running version
|
||||
// useMode - either RELEASESERVER or JSONFILE
|
||||
//
|
||||
// Returns the initialized `SQRLUpdater`.
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload forVersion:(NSString*) version useMode:(SQRLUpdaterMode) mode;
|
||||
|
||||
// Executes `checkForUpdatesCommand` (if enabled) every `interval` seconds.
|
||||
//
|
||||
// The first check will not occur until `interval` seconds have passed.
|
||||
@@ -136,6 +193,9 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
|
||||
// wrong before termination. The signal will never complete.
|
||||
- (RACSignal *)relaunchToInstallUpdate;
|
||||
|
||||
- (BOOL)isRunningOnReadOnlyVolume;
|
||||
- (RACSignal *)updateFromJSONData:(NSData *)data;
|
||||
|
||||
@end
|
||||
|
||||
@interface SQRLUpdater (Unavailable)
|
||||
|
||||
+300
-100
@@ -20,6 +20,7 @@
|
||||
#import "SQRLShipItRequest.h"
|
||||
#import <ReactiveCocoa/EXTScope.h>
|
||||
#import <ReactiveCocoa/ReactiveCocoa.h>
|
||||
#import <sys/mount.h>
|
||||
|
||||
NSString * const SQRLUpdaterErrorDomain = @"SQRLUpdaterErrorDomain";
|
||||
NSString * const SQRLUpdaterServerDataErrorKey = @"SQRLUpdaterServerDataErrorKey";
|
||||
@@ -32,6 +33,11 @@ const NSInteger SQRLUpdaterErrorInvalidServerResponse = 5;
|
||||
const NSInteger SQRLUpdaterErrorInvalidJSON = 6;
|
||||
const NSInteger SQRLUpdaterErrorInvalidServerBody = 7;
|
||||
|
||||
/// The application's being run on a read-only volume.
|
||||
const NSInteger SQRLUpdaterErrorReadOnlyVolume = 8;
|
||||
|
||||
const NSTimeInterval SQURLUpdaterZipDownloadTimeoutSeconds = 20 * 60;
|
||||
|
||||
// The prefix used when creating temporary directories for updates. This will be
|
||||
// followed by a random string of characters.
|
||||
static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
@@ -40,6 +46,10 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
|
||||
@property (atomic, readwrite) SQRLUpdaterState state;
|
||||
|
||||
/// The etag of the currently downloaded update, nil if no update has been
|
||||
/// downloaded.
|
||||
@property (atomic, copy) NSString *etag;
|
||||
|
||||
// The code signature for the running application, used to check updates before
|
||||
// sending them to ShipIt.
|
||||
@property (nonatomic, strong, readonly) SQRLCodeSignature *signature;
|
||||
@@ -49,16 +59,6 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
// Sends completed or error.
|
||||
@property (nonatomic, strong, readonly) RACSignal *shipItLauncher;
|
||||
|
||||
// Lazily removes outdated temporary directories (used for previous updates)
|
||||
// upon first subscription.
|
||||
//
|
||||
// Pruning directories while an update is pending or in progress will result in
|
||||
// undefined behavior.
|
||||
//
|
||||
// Sends each removed directory then completes, or errors, on an unspecified
|
||||
// thread.
|
||||
@property (nonatomic, strong, readonly) RACSignal *prunedUpdateDirectories;
|
||||
|
||||
// Parses an update model from downloaded data.
|
||||
//
|
||||
// data - JSON data representing an update manifest. This must not be nil.
|
||||
@@ -146,14 +146,46 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
}
|
||||
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest {
|
||||
return [self initWithUpdateRequest:updateRequest requestForDownload:^(NSURL *downloadURL) {
|
||||
return [NSURLRequest requestWithURL:downloadURL];
|
||||
}];
|
||||
}
|
||||
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest forVersion: (NSString*) version {
|
||||
return [self initWithUpdateRequest:updateRequest requestForDownload:^(NSURL *downloadURL) {
|
||||
return [NSURLRequest requestWithURL:downloadURL];
|
||||
} forVersion:version useMode:JSONFILE];
|
||||
}
|
||||
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload {
|
||||
return [self initWithUpdateRequest:updateRequest requestForDownload:^(NSURL *downloadURL) {
|
||||
return [NSURLRequest requestWithURL:downloadURL];
|
||||
} forVersion:nil useMode:RELEASESERVER];
|
||||
}
|
||||
|
||||
- (id)initWithUpdateRequest:(NSURLRequest *)updateRequest requestForDownload:(SQRLRequestForDownload)requestForDownload
|
||||
forVersion:(NSString*) version useMode:(SQRLUpdaterMode) mode {
|
||||
|
||||
//! download simple file
|
||||
|
||||
NSParameterAssert(updateRequest != nil);
|
||||
NSParameterAssert(requestForDownload != nil);
|
||||
|
||||
if (mode == JSONFILE) {
|
||||
NSParameterAssert(version != nil);
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self == nil) return nil;
|
||||
|
||||
_updateRequest = [updateRequest copy];
|
||||
_updateClass = SQRLUpdate.class;
|
||||
_requestForDownload = [requestForDownload copy];
|
||||
NSMutableURLRequest* mutableUpdateRequest = [updateRequest mutableCopy];
|
||||
|
||||
if (mode == JSONFILE) {
|
||||
mutableUpdateRequest.cachePolicy = NSURLRequestReloadIgnoringCacheData;
|
||||
}
|
||||
_updateRequest = mutableUpdateRequest;
|
||||
_updateClass = SQRLUpdate.class;
|
||||
NSError *error = nil;
|
||||
_signature = [SQRLCodeSignature currentApplicationSignature:&error];
|
||||
if (_signature == nil) {
|
||||
@@ -169,33 +201,6 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
BOOL updatesDisabled = (getenv("DISABLE_UPDATE_CHECK") != NULL);
|
||||
@weakify(self);
|
||||
|
||||
_prunedUpdateDirectories = [[[[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager applicationSupportURL];
|
||||
}]
|
||||
flattenMap:^(NSURL *appSupportURL) {
|
||||
NSFileManager *manager = [[NSFileManager alloc] init];
|
||||
NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:appSupportURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
|
||||
NSLog(@"Error enumerating item %@ within directory %@: %@", URL, appSupportURL, error);
|
||||
return YES;
|
||||
}];
|
||||
|
||||
return [[enumerator.rac_sequence.signal
|
||||
filter:^(NSURL *enumeratedURL) {
|
||||
NSString *name = enumeratedURL.lastPathComponent;
|
||||
return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
|
||||
}]
|
||||
doNext:^(NSURL *directoryURL) {
|
||||
NSError *error = nil;
|
||||
if (![manager removeItemAtURL:directoryURL error:&error]) {
|
||||
NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
|
||||
}
|
||||
}];
|
||||
}]
|
||||
replayLazily]
|
||||
setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
|
||||
|
||||
_checkForUpdatesCommand = [[RACCommand alloc] initWithEnabled:[RACSignal return:@(!updatesDisabled)] signalBlock:^(id _) {
|
||||
@strongify(self);
|
||||
NSParameterAssert(self.updateRequest != nil);
|
||||
@@ -204,12 +209,10 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
NSMutableURLRequest *request = [self.updateRequest mutableCopy];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
|
||||
|
||||
// Prune old updates before the first update check.
|
||||
return [[[[[[[[self.prunedUpdateDirectories
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error pruning old updates: %@", error);
|
||||
return [RACSignal empty];
|
||||
}]
|
||||
return [[[[[[[[self
|
||||
performHousekeeping]
|
||||
|
||||
//! get file from server
|
||||
then:^{
|
||||
self.state = SQRLUpdaterStateCheckingForUpdate;
|
||||
|
||||
@@ -218,21 +221,82 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
reduceEach:^(NSURLResponse *response, NSData *bodyData) {
|
||||
if ([response isKindOfClass:NSHTTPURLResponse.class]) {
|
||||
NSHTTPURLResponse *httpResponse = (id)response;
|
||||
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
|
||||
|
||||
BOOL readOnlyVolume = [self isRunningOnReadOnlyVolume];
|
||||
if (readOnlyVolume) {
|
||||
NSDictionary *errorInfo = @{
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"Update check failed", nil),
|
||||
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The server sent an invalid response. Try again later.", nil),
|
||||
SQRLUpdaterServerDataErrorKey: bodyData,
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"Cannot update while running on a read-only volume", nil),
|
||||
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The application is on a read-only volume. Please move the application and try again. If you're on macOS Sierra or later, you'll need to move the application out of the Downloads directory. See https://github.com/Squirrel/Squirrel.Mac/issues/182 for more information.", nil),
|
||||
};
|
||||
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
|
||||
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorReadOnlyVolume userInfo:errorInfo];
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
|
||||
if (httpResponse.statusCode == 204 /* No Content */) {
|
||||
return [RACSignal empty];
|
||||
if (mode == RELEASESERVER) {
|
||||
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
|
||||
NSDictionary *errorInfo = @{
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"Update check failed", nil),
|
||||
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The server sent an invalid response. Try again later.", nil),
|
||||
SQRLUpdaterServerDataErrorKey: bodyData,
|
||||
};
|
||||
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
|
||||
if (httpResponse.statusCode == 204 /* No Content */) {
|
||||
return [RACSignal empty];
|
||||
}
|
||||
} else if (mode == JSONFILE) {
|
||||
NSError *error = nil;
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:bodyData options:0 error:&error];
|
||||
|
||||
if (dict == nil) {
|
||||
NSMutableDictionary *userInfo = [error.userInfo mutableCopy] ?: [NSMutableDictionary dictionary];
|
||||
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Update check failed", nil);
|
||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = NSLocalizedString(@"The server sent an invalid response. Try again later.", nil);
|
||||
userInfo[SQRLUpdaterServerDataErrorKey] = bodyData;
|
||||
if (error != nil) userInfo[NSUnderlyingErrorKey] = error;
|
||||
|
||||
return [RACSignal error:[NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerBody userInfo:userInfo]];
|
||||
}
|
||||
|
||||
NSString *currentRelease = dict[@"currentRelease"];
|
||||
if(currentRelease) {
|
||||
//! if CDN points to the currently running version as the latest version, bail out
|
||||
if([currentRelease isEqualToString:version]) {
|
||||
NSLog(@"The running client is already the latest version.");
|
||||
return [RACSignal empty];
|
||||
}
|
||||
|
||||
if ([version compare:currentRelease options:NSNumericSearch] == NSOrderedDescending) {
|
||||
// currentRelease is lower than version.
|
||||
// Might be a new version for testing that is not deployed yet
|
||||
// no roll back
|
||||
NSLog(@"The running client is newer than the latest deployed release. Not downgrading.");
|
||||
return [RACSignal empty];
|
||||
}
|
||||
|
||||
//! @todo find latest
|
||||
NSArray *releases = dict[@"releases"];
|
||||
for(NSDictionary* release in releases) {
|
||||
if([currentRelease isEqualToString:release[@"version"]]) {
|
||||
bodyData = [NSJSONSerialization dataWithJSONObject:release[@"updateTo"]
|
||||
options:0 error:&error];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bodyData == nil) {
|
||||
NSMutableDictionary *userInfo = [error.userInfo mutableCopy] ?: [NSMutableDictionary dictionary];
|
||||
userInfo[NSLocalizedDescriptionKey] = NSLocalizedString(@"Update check failed", nil);
|
||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = NSLocalizedString(@"The server sent an invalid response. Try again later.", nil);
|
||||
userInfo[SQRLUpdaterServerDataErrorKey] = bodyData;
|
||||
if (error != nil) userInfo[NSUnderlyingErrorKey] = error;
|
||||
|
||||
return [RACSignal error:[NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerBody userInfo:userInfo]];
|
||||
}
|
||||
return [RACSignal return:bodyData];
|
||||
}]
|
||||
flatten]
|
||||
@@ -259,15 +323,13 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
|
||||
_shipItLauncher = [[[RACSignal
|
||||
defer:^{
|
||||
@strongify(self);
|
||||
|
||||
NSURL *targetURL = NSRunningApplication.currentApplication.bundleURL;
|
||||
|
||||
NSNumber *targetWritable = nil;
|
||||
NSError *targetWritableError = nil;
|
||||
BOOL gotWritable = [targetURL getResourceValue:&targetWritable forKey:NSURLIsWritableKey error:&targetWritableError];
|
||||
|
||||
// If we can't determine whether it can be written, assume nonprivileged and
|
||||
// wait for another, more canonical error.
|
||||
return [SQRLShipItLauncher launchPrivileged:(gotWritable && !targetWritable.boolValue)];
|
||||
BOOL targetWritable = [self canWriteToURL:targetURL];
|
||||
BOOL parentWritable = [self canWriteToURL:targetURL.URLByDeletingLastPathComponent];
|
||||
return [SQRLShipItLauncher launchPrivileged:!targetWritable || !parentWritable];
|
||||
}]
|
||||
replayLazily]
|
||||
setNameWithFormat:@"shipItLauncher"];
|
||||
@@ -275,6 +337,15 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canWriteToURL:(NSURL *)fileURL {
|
||||
NSNumber *writable = nil;
|
||||
NSError *writableError = nil;
|
||||
BOOL gotWritable = [fileURL getResourceValue:&writable forKey:NSURLIsWritableKey error:&writableError];
|
||||
// If we can't determine whether it can be written, assume nonprivileged and
|
||||
// wait for another, more canonical error.
|
||||
return !gotWritable || writable.boolValue;
|
||||
}
|
||||
|
||||
#pragma mark Checking for Updates
|
||||
|
||||
- (RACDisposable *)startAutomaticChecksWithInterval:(NSTimeInterval)interval {
|
||||
@@ -341,36 +412,90 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
return [[[self
|
||||
uniqueTemporaryDirectoryForUpdate]
|
||||
flattenMap:^(NSURL *downloadDirectory) {
|
||||
void (^cleanUp)(void) = ^{
|
||||
NSError *error;
|
||||
if (![NSFileManager.defaultManager removeItemAtURL:downloadDirectory error:&error]) {
|
||||
NSLog(@"Error removing temporary download directory at %@: %@", downloadDirectory, error.sqrl_verboseDescription);
|
||||
}
|
||||
};
|
||||
|
||||
return [[[self
|
||||
downloadBundleForUpdate:update intoDirectory:downloadDirectory]
|
||||
flattenMap:^(NSBundle *updateBundle) {
|
||||
// If the bundle is nil it means our conditional GET told us
|
||||
// we already downloaded the update. So just clean up.
|
||||
if (updateBundle == nil) {
|
||||
cleanUp();
|
||||
return [RACSignal empty];
|
||||
}
|
||||
|
||||
return [self verifyAndPrepareUpdate:update fromBundle:updateBundle];
|
||||
}]
|
||||
doError:^(id _) {
|
||||
NSError *error = nil;
|
||||
if (![NSFileManager.defaultManager removeItemAtURL:downloadDirectory error:&error]) {
|
||||
NSLog(@"Error removing temporary download directory at %@: %@", downloadDirectory, error.sqrl_verboseDescription);
|
||||
}
|
||||
cleanUp();
|
||||
}];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -downloadAndPrepareUpdate: %@", self, update];
|
||||
}
|
||||
|
||||
- (RACSignal *)unarchiveAndPrepareData:(NSData *)data withName:(NSString *)name intoDirectory:(NSURL *)downloadDirectory {
|
||||
return [[[[[RACSignal
|
||||
defer:^{
|
||||
NSURL *zipOutputURL = [downloadDirectory URLByAppendingPathComponent:name];
|
||||
NSError *error = nil;
|
||||
if ([data writeToURL:zipOutputURL options:NSDataWritingAtomic error:&error]) {
|
||||
return [RACSignal return:zipOutputURL];
|
||||
} else {
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
}]
|
||||
doNext:^(NSURL *zipOutputURL) {
|
||||
NSLog(@"Download completed to: %@", zipOutputURL);
|
||||
}]
|
||||
flattenMap:^(NSURL *zipOutputURL) {
|
||||
return [[[[SQRLZipArchiver
|
||||
unzipArchiveAtURL:zipOutputURL intoDirectoryAtURL:downloadDirectory]
|
||||
ignoreValues]
|
||||
concat:[RACSignal return:zipOutputURL]]
|
||||
doCompleted:^{
|
||||
NSError *error = nil;
|
||||
if (![NSFileManager.defaultManager removeItemAtURL:zipOutputURL error:&error]) {
|
||||
NSLog(@"Error removing downloaded archive at %@: %@", zipOutputURL, error.sqrl_verboseDescription);
|
||||
}
|
||||
}];
|
||||
}]
|
||||
flattenMap:^(NSURL *zipOutputURL) {
|
||||
return [self updateBundleMatchingCurrentApplicationInDirectory:downloadDirectory];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -unarchiveAndPrepareData:withName: %@ intoDirectory: %@", self, name, downloadDirectory];
|
||||
}
|
||||
|
||||
- (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL *)downloadDirectory {
|
||||
NSParameterAssert(update != nil);
|
||||
NSParameterAssert(downloadDirectory != nil);
|
||||
|
||||
return [[[[[RACSignal
|
||||
return [[RACSignal
|
||||
defer:^{
|
||||
NSURL *zipDownloadURL = update.updateURL;
|
||||
NSMutableURLRequest *zipDownloadRequest = [NSMutableURLRequest requestWithURL:zipDownloadURL];
|
||||
[zipDownloadRequest setValue:@"application/zip" forHTTPHeaderField:@"Accept"];
|
||||
NSMutableURLRequest *zipDownloadRequest = [self.requestForDownload(zipDownloadURL) mutableCopy];
|
||||
|
||||
return [[[[NSURLConnection
|
||||
[zipDownloadRequest setValue:@"application/zip" forHTTPHeaderField:@"Accept"];
|
||||
if (self.etag != nil) {
|
||||
[zipDownloadRequest setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
|
||||
}
|
||||
|
||||
[zipDownloadRequest setTimeoutInterval:SQURLUpdaterZipDownloadTimeoutSeconds];
|
||||
|
||||
return [[[NSURLConnection
|
||||
rac_sendAsynchronousRequest:zipDownloadRequest]
|
||||
reduceEach:^(NSURLResponse *response, NSData *bodyData) {
|
||||
if ([response isKindOfClass:NSHTTPURLResponse.class]) {
|
||||
NSHTTPURLResponse *httpResponse = (id)response;
|
||||
|
||||
if (httpResponse.statusCode == 304 /* Not Modified */) {
|
||||
return [RACSignal return:nil];
|
||||
}
|
||||
|
||||
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
|
||||
NSDictionary *errorInfo = @{
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"Update download failed", nil),
|
||||
@@ -380,37 +505,13 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
|
||||
self.etag = httpResponse.allHeaderFields[@"ETag"];
|
||||
}
|
||||
|
||||
return [RACSignal return:bodyData];
|
||||
return [self unarchiveAndPrepareData:bodyData withName:zipDownloadURL.lastPathComponent intoDirectory:downloadDirectory];
|
||||
}]
|
||||
flatten]
|
||||
flattenMap:^(NSData *data) {
|
||||
NSURL *zipOutputURL = [downloadDirectory URLByAppendingPathComponent:zipDownloadURL.lastPathComponent];
|
||||
|
||||
NSError *error = nil;
|
||||
if ([data writeToURL:zipOutputURL options:NSDataWritingAtomic error:&error]) {
|
||||
return [RACSignal return:zipOutputURL];
|
||||
} else {
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
}];
|
||||
}]
|
||||
doNext:^(NSURL *zipOutputURL) {
|
||||
NSLog(@"Download completed to: %@", zipOutputURL);
|
||||
}]
|
||||
flattenMap:^(NSURL *zipOutputURL) {
|
||||
return [[SQRLZipArchiver
|
||||
unzipArchiveAtURL:zipOutputURL intoDirectoryAtURL:downloadDirectory]
|
||||
doCompleted:^{
|
||||
NSError *error = nil;
|
||||
if (![NSFileManager.defaultManager removeItemAtURL:zipOutputURL error:&error]) {
|
||||
NSLog(@"Error removing downloaded archive at %@: %@", zipOutputURL, error.sqrl_verboseDescription);
|
||||
}
|
||||
}];
|
||||
}]
|
||||
then:^{
|
||||
return [self updateBundleMatchingCurrentApplicationInDirectory:downloadDirectory];
|
||||
flatten];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -downloadBundleForUpdate: %@ intoDirectory: %@", self, update, downloadDirectory];
|
||||
}
|
||||
@@ -421,10 +522,10 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
return [[[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager applicationSupportURL];
|
||||
return [directoryManager storageURL];
|
||||
}]
|
||||
flattenMap:^(NSURL *appSupportURL) {
|
||||
NSURL *updateDirectoryTemplate = [appSupportURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
|
||||
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
|
||||
@onExit {
|
||||
free(updateDirectoryCString);
|
||||
@@ -502,6 +603,100 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
setNameWithFormat:@"%@ -shipItStateURL", self];
|
||||
}
|
||||
|
||||
/// Is the host app running on a read-only volume?
|
||||
- (BOOL)isRunningOnReadOnlyVolume {
|
||||
struct statfs statfsInfo;
|
||||
NSURL *bundleURL = NSRunningApplication.currentApplication.bundleURL;
|
||||
int result = statfs(bundleURL.fileSystemRepresentation, &statfsInfo);
|
||||
if (result == 0) {
|
||||
return (statfsInfo.f_flags & MNT_RDONLY) != 0;
|
||||
} else {
|
||||
// If we can't even check if the volume is read-only, assume it is.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
- (RACSignal *)performHousekeeping {
|
||||
return [[RACSignal
|
||||
merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]]
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error doing housekeeping: %@", error);
|
||||
return [RACSignal empty];
|
||||
}];
|
||||
}
|
||||
|
||||
/// Lazily removes outdated temporary directories (used for previous updates)
|
||||
/// upon subscription.
|
||||
///
|
||||
/// Pruning directories while an update is pending or in progress will result in
|
||||
/// undefined behavior.
|
||||
///
|
||||
/// Sends each removed directory then completes, or errors, on an unspecified
|
||||
/// thread.
|
||||
- (RACSignal *)pruneUpdateDirectories {
|
||||
return [[[RACSignal
|
||||
defer:^{
|
||||
// If we already have updates downloaded we don't wanna prune them.
|
||||
if (self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
|
||||
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
}]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
NSFileManager *manager = [[NSFileManager alloc] init];
|
||||
NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
|
||||
NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
|
||||
return YES;
|
||||
}];
|
||||
|
||||
return [[enumerator.rac_sequence.signal
|
||||
filter:^(NSURL *enumeratedURL) {
|
||||
NSString *name = enumeratedURL.lastPathComponent;
|
||||
return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
|
||||
}]
|
||||
doNext:^(NSURL *directoryURL) {
|
||||
NSError *error = nil;
|
||||
if (![manager removeItemAtURL:directoryURL error:&error]) {
|
||||
NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
|
||||
}
|
||||
}];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
|
||||
}
|
||||
|
||||
|
||||
// Like truncation, but backwards.
|
||||
- (RACSignal *)backwardTruncateFile:(NSURL *)fileURL {
|
||||
return [RACSignal defer:^{
|
||||
static const NSInteger MAX_LENGTH = 1024 * 1024 * 8;
|
||||
|
||||
NSError *error;
|
||||
NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:fileURL error:&error];
|
||||
if (handle == nil) return [RACSignal error:error];
|
||||
|
||||
unsigned long long fileLength = [handle seekToEndOfFile];
|
||||
if (fileLength <= MAX_LENGTH) return [RACSignal empty];
|
||||
|
||||
[handle seekToFileOffset:fileLength - MAX_LENGTH];
|
||||
NSData *mostRecentData = [handle readDataToEndOfFile];
|
||||
[handle truncateFileAtOffset:0];
|
||||
[handle writeData:mostRecentData];
|
||||
|
||||
return [RACSignal empty];
|
||||
}];
|
||||
}
|
||||
|
||||
- (RACSignal *)truncateLogs {
|
||||
return [[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [RACSignal zip:@[ [directoryManager shipItStdoutURL], [directoryManager shipItStderrURL] ]];
|
||||
}]
|
||||
reduceEach:^(NSURL *stdoutURL, NSURL *stderrURL) {
|
||||
return [RACSignal merge:@[ [self backwardTruncateFile:stdoutURL], [self backwardTruncateFile:stderrURL] ]];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark Installing Updates
|
||||
|
||||
- (RACSignal *)verifyAndPrepareUpdate:(SQRLUpdate *)update fromBundle:(NSBundle *)updateBundle {
|
||||
@@ -528,7 +723,12 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
return [[[[RACSignal
|
||||
defer:^{
|
||||
NSRunningApplication *currentApplication = NSRunningApplication.currentApplication;
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:update.bundle.bundleURL targetBundleURL:currentApplication.bundleURL bundleIdentifier:currentApplication.bundleIdentifier launchAfterInstallation:NO];
|
||||
NSBundle *appBundle = [NSBundle bundleWithURL:currentApplication.bundleURL];
|
||||
// Only use the update bundle's name if the user hasn't renamed the
|
||||
// app themselves.
|
||||
BOOL useUpdateBundleName = [appBundle.sqrl_executableName isEqual:currentApplication.bundleURL.lastPathComponent.stringByDeletingPathExtension];
|
||||
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:update.bundle.bundleURL targetBundleURL:currentApplication.bundleURL bundleIdentifier:currentApplication.bundleIdentifier launchAfterInstallation:NO useUpdateBundleName:useUpdateBundleName];
|
||||
return [request writeUsingURL:self.shipItStateURL];
|
||||
}]
|
||||
then:^{
|
||||
@@ -542,7 +742,7 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
return [[[[[[[[SQRLShipItRequest
|
||||
readUsingURL:self.shipItStateURL]
|
||||
map:^(SQRLShipItRequest *request) {
|
||||
return [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:request.targetBundleURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:YES];
|
||||
return [[SQRLShipItRequest alloc] initWithUpdateBundleURL:request.updateBundleURL targetBundleURL:request.targetBundleURL bundleIdentifier:request.bundleIdentifier launchAfterInstallation:YES useUpdateBundleName:request.useUpdateBundleName];
|
||||
}]
|
||||
flattenMap:^(SQRLShipItRequest *request) {
|
||||
return [[request
|
||||
|
||||
@@ -54,7 +54,7 @@ const NSInteger SQRLZipArchiverShellTaskFailed = 1;
|
||||
|
||||
_dittoTask = [[NSTask alloc] init];
|
||||
_dittoTask.launchPath = @"/usr/bin/ditto";
|
||||
_dittoTask.environment = @{ @"DITTOABORT": @1 };
|
||||
_dittoTask.environment = @{ @"DITTOABORT": @"1" };
|
||||
_dittoTask.standardError = self.standardErrorPipe;
|
||||
|
||||
@weakify(self);
|
||||
@@ -63,6 +63,7 @@ const NSInteger SQRLZipArchiverShellTaskFailed = 1;
|
||||
if (self == nil) return;
|
||||
|
||||
[self->_taskTerminated sendNext:@(task.terminationStatus)];
|
||||
[self.standardErrorPipe.fileHandleForReading closeFile];
|
||||
};
|
||||
|
||||
RACSubject *errorDataChunks = [[RACSubject subject] setNameWithFormat:@"errorDataChunks"];
|
||||
|
||||
+56
-20
@@ -15,7 +15,6 @@
|
||||
|
||||
#import "NSError+SQRLVerbosityExtensions.h"
|
||||
#import "RACSignal+SQRLTransactionExtensions.h"
|
||||
#import "SQRLDirectoryManager.h"
|
||||
#import "SQRLInstaller.h"
|
||||
#import "SQRLInstaller+Private.h"
|
||||
#import "SQRLTerminationListener.h"
|
||||
@@ -28,6 +27,8 @@
|
||||
// updating will abort.
|
||||
static const NSUInteger SQRLShipItMaximumInstallationAttempts = 3;
|
||||
|
||||
static NSString * launchSignal = @"___launch___";
|
||||
|
||||
// The domain for errors generated here.
|
||||
static NSString * const SQRLShipItErrorDomain = @"SQRLShipItErrorDomain";
|
||||
|
||||
@@ -58,9 +59,7 @@ static RACSignal *waitForTerminationIfNecessary(SQRLShipItRequest *request) {
|
||||
setNameWithFormat:@"waitForTerminationIfNecessary"];
|
||||
}
|
||||
|
||||
static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *directoryManager) {
|
||||
NSString *applicationIdentifier = directoryManager.applicationIdentifier;
|
||||
|
||||
static void installRequest(RACSignal *readRequestSignal, NSString *applicationIdentifier) {
|
||||
[[[[[readRequestSignal
|
||||
flattenMap:^(SQRLShipItRequest *request) {
|
||||
return waitForTerminationIfNecessary(request);
|
||||
@@ -75,7 +74,7 @@ static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *d
|
||||
|
||||
RACSignal *action;
|
||||
if (attempt > SQRLShipItMaximumInstallationAttempts) {
|
||||
action = [[[installer.abortInstallationCommand
|
||||
action = [[[[installer.abortInstallationCommand
|
||||
execute:request]
|
||||
initially:^{
|
||||
NSLog(@"Too many attempts to install, aborting update");
|
||||
@@ -86,7 +85,8 @@ static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *d
|
||||
// Exit successfully so launchd doesn't restart us
|
||||
// again.
|
||||
return [RACSignal empty];
|
||||
}];
|
||||
}]
|
||||
concat:[RACSignal return:request]];
|
||||
} else {
|
||||
action = [[[[installer.installUpdateCommand
|
||||
execute:request]
|
||||
@@ -114,20 +114,44 @@ static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *d
|
||||
// Launch regardless of whether installation succeeds or fails.
|
||||
action = [[action
|
||||
deliverOn:RACScheduler.mainThreadScheduler]
|
||||
finally:^{
|
||||
NSURL *bundleURL = request.targetBundleURL;
|
||||
doNext:^(SQRLShipItRequest *finalRequest) {
|
||||
NSLog(@"On main thread and launching: %@", finalRequest.targetBundleURL);
|
||||
NSURL *bundleURL = finalRequest.targetBundleURL;
|
||||
if (bundleURL == nil) {
|
||||
NSLog(@"Missing target bundle URL, cannot launch application");
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
if (![NSWorkspace.sharedWorkspace launchApplicationAtURL:bundleURL options:NSWorkspaceLaunchDefault configuration:nil error:&error]) {
|
||||
NSLog(@"Could not launch application at %@: %@", bundleURL, error);
|
||||
return;
|
||||
}
|
||||
NSLog(@"Bundle URL is valid");
|
||||
|
||||
NSLog(@"Application launched at %@", bundleURL);
|
||||
NSError *error;
|
||||
// Temporary workaround, on Big Sur and higher the executable
|
||||
// using NSWorkspace needs to actually exist on disk, at this point
|
||||
// this executable no longer exists on disk so we need to launch the
|
||||
// new one (which should be in the exact same spot) and ask for it
|
||||
// to launch the new app bundle URL
|
||||
if (@available(macOS 11.0, *)) {
|
||||
NSLog(@"Attempting to launch app on 11.0 or higher");
|
||||
|
||||
NSString *exe = NSProcessInfo.processInfo.arguments[0];
|
||||
NSLog(@"Launching new ShipIt at %@ with instructions to launch %@", exe, bundleURL);
|
||||
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
[task setLaunchPath: exe];
|
||||
[task setArguments: @[launchSignal, bundleURL.path]];
|
||||
[task launch];
|
||||
[task waitUntilExit];
|
||||
|
||||
NSLog(@"New ShipIt exited");
|
||||
} else {
|
||||
NSLog(@"Attempting to launch app on lower than 11.0");
|
||||
if (![NSWorkspace.sharedWorkspace launchApplicationAtURL:bundleURL options:NSWorkspaceLaunchDefault configuration:@{} error:&error]) {
|
||||
NSLog(@"Could not launch application at %@: %@", bundleURL, error);
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"Application launched at %@", bundleURL);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -147,17 +171,29 @@ int main(int argc, const char * argv[]) {
|
||||
NSLog(@"ShipIt quitting");
|
||||
});
|
||||
|
||||
if (argc < 2) {
|
||||
NSLog(@"Missing launchd job label for ShipIt");
|
||||
if (argc < 3) {
|
||||
NSLog(@"Missing launchd job label or state path for ShipIt");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
char const *jobLabel = argv[1];
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:@(jobLabel)];
|
||||
const char *statePath = argv[2];
|
||||
NSURL *shipItStateURL = [NSURL fileURLWithPath:@(statePath)];
|
||||
|
||||
installRequest([SQRLShipItRequest readUsingURL:directoryManager.shipItStateURL], directoryManager);
|
||||
|
||||
dispatch_main();
|
||||
if (strcmp(jobLabel, [launchSignal UTF8String]) == 0) {
|
||||
NSLog(@"Detected this as a launch request");
|
||||
NSError *error;
|
||||
if (![NSWorkspace.sharedWorkspace launchApplicationAtURL:shipItStateURL options:NSWorkspaceLaunchDefault configuration:@{} error:&error]) {
|
||||
NSLog(@"Could not launch application at %@: %@", shipItStateURL, error);
|
||||
} else {
|
||||
NSLog(@"Successfully launched application at %@", shipItStateURL);
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
} else {
|
||||
NSLog(@"Detected this as an install request");
|
||||
installRequest([SQRLShipItRequest readUsingURL:[RACSignal return:shipItStateURL]], @(jobLabel));
|
||||
dispatch_main();
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.github.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#import <ReactiveCocoa/ReactiveCocoa.h>
|
||||
#import <Squirrel/Squirrel.h>
|
||||
|
||||
/// See https://github.com/Squirrel/Squirrel.Mac/pull/154#issuecomment-151287744
|
||||
#define SKIP_IF_RUNNING_ON_TRAVIS if (self.runningOnTravis) return;
|
||||
|
||||
// The short version string for the `testApplicationBundle`.
|
||||
extern NSString * const SQRLTestApplicationOriginalShortVersionString;
|
||||
|
||||
@@ -21,6 +24,10 @@ extern NSString * const SQRLTestApplicationUpdatedShortVersionString;
|
||||
// The Info.plist key associated with a bundle's short version string.
|
||||
extern NSString * const SQRLBundleShortVersionStringKey;
|
||||
|
||||
/// The longer timeout we should use for things that take a long time. This is
|
||||
/// especially needed on slower machines (e.g., Travis CI).
|
||||
extern const NSTimeInterval SQRLLongTimeout;
|
||||
|
||||
@class SQRLCodeSignature;
|
||||
@class SQRLDirectoryManager;
|
||||
@class SQRLShipItRequest;
|
||||
@@ -54,6 +61,9 @@ extern NSString * const SQRLBundleShortVersionStringKey;
|
||||
// A directory manager for finding URLs that apply to ShipIt.
|
||||
@property (nonatomic, strong, readonly) SQRLDirectoryManager *shipItDirectoryManager;
|
||||
|
||||
/// Are the tests currently being run on Travis?
|
||||
@property (nonatomic, readonly, assign, getter = isRunningOnTravis) BOOL runningOnTravis;
|
||||
|
||||
// Launches a new copy of TestApplication.app from the `testApplicationBundle`.
|
||||
//
|
||||
// Invoking this method multiple times will result in multiple running instances
|
||||
|
||||
@@ -23,6 +23,8 @@ NSString * const SQRLTestApplicationOriginalShortVersionString = @"1.0";
|
||||
NSString * const SQRLTestApplicationUpdatedShortVersionString = @"2.1";
|
||||
NSString * const SQRLBundleShortVersionStringKey = @"CFBundleShortVersionString";
|
||||
|
||||
const NSTimeInterval SQRLLongTimeout = 20;
|
||||
|
||||
static void SQRLKillAllTestApplications(void) {
|
||||
// Forcibly kill all copies of the TestApplication that may be running.
|
||||
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.github.Squirrel.TestApplication"];
|
||||
@@ -43,24 +45,6 @@ static void SQRLSignalHandler(int sig) {
|
||||
abort();
|
||||
}
|
||||
|
||||
// Returns an _unlaunched_ task that will follow log files at the given paths,
|
||||
// then pipe that output through this process.
|
||||
static NSTask *SQRLTailTaskWithPaths(RACSequence *paths) {
|
||||
NSPipe *outputPipe = [NSPipe pipe];
|
||||
NSFileHandle *outputHandle = outputPipe.fileHandleForReading;
|
||||
|
||||
outputHandle.readabilityHandler = ^(NSFileHandle *handle) {
|
||||
NSString *output = [[NSString alloc] initWithData:handle.availableData encoding:NSUTF8StringEncoding];
|
||||
NSLog(@"\n%@", output);
|
||||
};
|
||||
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = @"/usr/bin/tail";
|
||||
task.standardOutput = outputPipe;
|
||||
task.arguments = [[paths startWith:@"-f"] array];
|
||||
return task;
|
||||
}
|
||||
|
||||
static NSBundle *SQRLTestBundle(void) {
|
||||
return [NSBundle bundleWithIdentifier:@"com.github.SquirrelTests"];
|
||||
}
|
||||
@@ -90,12 +74,6 @@ QuickConfigurationBegin(Fixtures)
|
||||
].rac_sequence;
|
||||
}];
|
||||
|
||||
RACSequence *paths = [URLs map:^(NSURL *URL) {
|
||||
return URL.path;
|
||||
}];
|
||||
|
||||
NSTask *readShipIt = SQRLTailTaskWithPaths(paths);
|
||||
|
||||
[configuration beforeSuite:^{
|
||||
signal(SIGILL, &SQRLSignalHandler);
|
||||
NSSetUncaughtExceptionHandler(&SQRLUncaughtExceptionHandler);
|
||||
@@ -108,14 +86,6 @@ QuickConfigurationBegin(Fixtures)
|
||||
NSLog(@"Could not touch log file at %@", URL);
|
||||
}
|
||||
}
|
||||
|
||||
[readShipIt launch];
|
||||
|
||||
NSAssert([readShipIt isRunning], @"Could not start task %@", readShipIt);
|
||||
}];
|
||||
|
||||
[configuration afterSuite:^{
|
||||
[readShipIt terminate];
|
||||
}];
|
||||
|
||||
// We want to run any enqueued cleanup blocks after _and before_ each spec,
|
||||
@@ -193,15 +163,6 @@ QuickConfigurationEnd
|
||||
|
||||
NSURL *testAppLog = [fixtureURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:@"TestApplication.log"];
|
||||
[[NSData data] writeToURL:testAppLog atomically:YES];
|
||||
|
||||
NSTask *readTestApp = SQRLTailTaskWithPaths([RACSequence return:testAppLog.path]);
|
||||
[readTestApp launch];
|
||||
|
||||
XCTAssertTrue([readTestApp isRunning], @"Could not start task %@ to read %@", readTestApp, testAppLog);
|
||||
|
||||
[self addCleanupBlock:^{
|
||||
[readTestApp terminate];
|
||||
}];
|
||||
}
|
||||
|
||||
return fixtureURL;
|
||||
@@ -365,6 +326,10 @@ QuickConfigurationEnd
|
||||
return [NSURL fileURLWithPath:path isDirectory:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isRunningOnTravis {
|
||||
return NSProcessInfo.processInfo.environment[@"TRAVIS"] != nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
***
|
||||
Test updates via release server or cdn-based updates
|
||||
***
|
||||
|
||||
Control test behavior via env variables:
|
||||
|
||||
SQUIRREL_TEST_LOCAL_SERVER=True
|
||||
SQUIRREL_LOCAL_SERVER_URL=http://localhost:8123/update/osx/1.0.1-stable/stable
|
||||
|
||||
SQUIRREL_TEST_LOCAL_CDN=True
|
||||
SQUIRREL_CDN_URL=@"http://localhost/RELEASES.json"
|
||||
|
||||
|
||||
|
||||
Test Setup:
|
||||
Create a fake dmg release file
|
||||
|
||||
Release Server:
|
||||
git clone https://github.com/ArekSredzki/electron-release-server.git
|
||||
|
||||
refer to their doc, here is a short summary:
|
||||
- install a local PostgreSQL, any default setup will do it
|
||||
- edit config/local.js to match your PostgreSQL setup
|
||||
- npm start
|
||||
|
||||
point your browser to the server and upload your release dmg file
|
||||
|
||||
CDN:
|
||||
setup a local webserver such as apache or nginx and copy your dmg file there
|
||||
create a RELEASE.json file like this:
|
||||
|
||||
{
|
||||
"currentRelease" : "1.0.1",
|
||||
"releases" : [
|
||||
{
|
||||
"version" : "1.0.1",
|
||||
"updateTo" : {
|
||||
"pub_date" : "2017-03-09T15:24:55-05:00",
|
||||
"notes" : "the next release via CDN",
|
||||
"name" : "TestApplication_1.0.1",
|
||||
"url" : "http:\/\/localhost/TestApplication_1.0.1.dmg",
|
||||
"version" : "0.0.145-alpha"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -88,7 +88,7 @@ describe(@"main executable changes", ^{
|
||||
|
||||
expect(error).notTo(beNil());
|
||||
expect(error.domain).to(equal(SQRLCodeSignatureErrorDomain));
|
||||
expect(@(error.code)).to(equal(@(SQRLCodeSignatureErrorCouldNotCreateStaticCode)));
|
||||
expect(@(error.code)).to(equal(@(SQRLCodeSignatureErrorDidNotPass)));
|
||||
});
|
||||
|
||||
it(@"should fail to verify a bundle with a corrupt executable", ^{
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
//
|
||||
// SQRLDeepCodesignSpec.m
|
||||
// Squirrel
|
||||
//
|
||||
// Created by Keith Duncan on 2013-09-05.
|
||||
// Copyright (c) 2013 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Nimble/Nimble.h>
|
||||
#import <Quick/Quick.h>
|
||||
#import <ReactiveCocoa/ReactiveCocoa.h>
|
||||
#import <Squirrel/Squirrel.h>
|
||||
|
||||
#import "QuickSpec+SQRLFixtures.h"
|
||||
|
||||
QuickSpecBegin(SQRLDeepCodesign)
|
||||
|
||||
NSMutableDictionary * (^environmentSuitableForChildProcess)(void) = ^ {
|
||||
// Remove environment variables that configure the Obj-C runtime
|
||||
// Specifically OBJC_DISABLE_GC so that child processes aren't forced to
|
||||
// adopt the GC preference of the test suite
|
||||
NSMutableDictionary *environment = [NSProcessInfo.processInfo.environment mutableCopy];
|
||||
NSSet *objcEnvironmentVariables = [environment keysOfEntriesPassingTest:^(NSString *variable, id obj, BOOL *stop) {
|
||||
return [variable hasPrefix:@"OBJC"];
|
||||
}];
|
||||
[environment removeObjectsForKeys:objcEnvironmentVariables.allObjects];
|
||||
return environment;
|
||||
};
|
||||
|
||||
NSTask * (^codesignTaskWithArguments)(NSArray *) = ^ (NSArray *arguments) {
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = @"/usr/bin/xcrun";
|
||||
task.arguments = [@[ @"codesign" ] arrayByAddingObjectsFromArray:arguments];
|
||||
task.environment = environmentSuitableForChildProcess();
|
||||
return task;
|
||||
};
|
||||
|
||||
void (^resignTestApplicationPreserveEverythingButTheRequirements)(void) = ^{
|
||||
NSURL *testApplicationLocation = self.testApplicationURL;
|
||||
|
||||
NSTask *resignCodesignTask = codesignTaskWithArguments(@[
|
||||
@"--sign", @"-",
|
||||
@"--force",
|
||||
@"--verbose=4",
|
||||
@"--preserve-metadata=identifier,entitlements,resource-rules",
|
||||
testApplicationLocation.path,
|
||||
]);
|
||||
|
||||
[resignCodesignTask launch];
|
||||
[resignCodesignTask waitUntilExit];
|
||||
|
||||
expect(@(resignCodesignTask.terminationStatus)).to(equal(@0));
|
||||
};
|
||||
|
||||
void (^deepCodesignTestApplication)(void) = ^{
|
||||
NSURL *testApplicationLocation = self.testApplicationURL;
|
||||
|
||||
NSBundle *testsBundle = [NSBundle bundleForClass:self.class];
|
||||
NSURL *deepCodesignLocation = [testsBundle URLForResource:@"deep-codesign" withExtension:nil];
|
||||
|
||||
NSNumber *executable = nil;
|
||||
NSError *executableError = nil;
|
||||
BOOL getExecutable = [deepCodesignLocation getResourceValue:&executable forKey:NSURLIsExecutableKey error:&executableError];
|
||||
expect(@(getExecutable)).to(beTruthy());
|
||||
expect(@(executable.boolValue)).to(beTruthy());
|
||||
expect(executableError).to(beNil());
|
||||
|
||||
NSTask *deepCodesignTask = [[NSTask alloc] init];
|
||||
deepCodesignTask.launchPath = deepCodesignLocation.path;
|
||||
deepCodesignTask.standardError = [NSPipe pipe];
|
||||
deepCodesignTask.standardOutput = [NSPipe pipe];
|
||||
|
||||
NSMutableDictionary *environment = environmentSuitableForChildProcess();
|
||||
[environment addEntriesFromDictionary:@{
|
||||
@"CODE_SIGN_IDENTITY": @"-",
|
||||
@"CONFIGURATION_BUILD_DIR": testApplicationLocation.URLByDeletingLastPathComponent.path,
|
||||
@"FULL_PRODUCT_NAME": testApplicationLocation.lastPathComponent,
|
||||
}];
|
||||
|
||||
deepCodesignTask.environment = environment;
|
||||
|
||||
[deepCodesignTask launch];
|
||||
[deepCodesignTask waitUntilExit];
|
||||
|
||||
expect(@(deepCodesignTask.terminationStatus)).to(equal(@0));
|
||||
|
||||
/*
|
||||
By signing test application's contents, which are covered by test
|
||||
application's ResourceRules it's signature becomes invalid. Usually,
|
||||
Xcode would sign the application after deep-codesign has run so the
|
||||
signature would include the _signed_ child resources which are covered
|
||||
by test application's ResourceRules
|
||||
|
||||
test application is already signed, so we need to manually resign it
|
||||
|
||||
deep-codesign preserves the requirements, we can't use it to resign the
|
||||
root target, the new signing identity wouldn't verify against the
|
||||
original designated requirement
|
||||
*/
|
||||
resignTestApplicationPreserveEverythingButTheRequirements();
|
||||
};
|
||||
|
||||
BOOL (^deepVerify)(void) = ^ BOOL {
|
||||
NSTask *deepVerifyTask = codesignTaskWithArguments(@[
|
||||
@"--deep-verify",
|
||||
@"--verbose=4",
|
||||
self.testApplicationURL.path
|
||||
]);
|
||||
|
||||
[deepVerifyTask launch];
|
||||
[deepVerifyTask waitUntilExit];
|
||||
|
||||
return deepVerifyTask.terminationStatus == 0;
|
||||
};
|
||||
|
||||
it(@"should deep sign the test application", ^{
|
||||
deepCodesignTestApplication();
|
||||
});
|
||||
|
||||
xit(@"should deep verify after signing", ^{
|
||||
expect(@(deepVerify())).to(beFalsy());
|
||||
deepCodesignTestApplication();
|
||||
expect(@(deepVerify())).to(beTruthy());
|
||||
});
|
||||
|
||||
QuickSpecEnd
|
||||
@@ -37,7 +37,7 @@ it(@"should send an Application Support URL", ^{
|
||||
SQRLDirectoryManager *manager = SQRLDirectoryManager.currentApplicationManager;
|
||||
|
||||
NSError *error = nil;
|
||||
NSURL *appSupportURL = [[manager applicationSupportURL] firstOrDefault:nil success:NULL error:&error];
|
||||
NSURL *appSupportURL = [[manager storageURL] firstOrDefault:nil success:NULL error:&error];
|
||||
expect(appSupportURL).notTo(beNil());
|
||||
expect(error).to(beNil());
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ beforeEach(^{
|
||||
});
|
||||
|
||||
it(@"should install an update using ShipIt", ^{
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
@@ -48,7 +48,7 @@ it(@"should install an update using ShipIt", ^{
|
||||
});
|
||||
|
||||
it(@"should install an update in process", ^{
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
|
||||
[self installWithRequest:request remote:NO];
|
||||
|
||||
@@ -60,7 +60,7 @@ it(@"should install an update and relaunch", ^{
|
||||
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:bundleIdentifier];
|
||||
expect(@(apps.count)).to(equal(@0));
|
||||
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:YES];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:YES useUpdateBundleName:NO];
|
||||
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
@@ -72,7 +72,7 @@ it(@"should install an update from another volume", ^{
|
||||
NSURL *diskImageURL = [self createAndMountDiskImageNamed:@"TestApplication 2.1" fromDirectory:updateURL.URLByDeletingLastPathComponent];
|
||||
updateURL = [diskImageURL URLByAppendingPathComponent:updateURL.lastPathComponent];
|
||||
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
@@ -83,12 +83,12 @@ it(@"should install an update to another volume", ^{
|
||||
NSURL *diskImageURL = [self createAndMountDiskImageNamed:@"TestApplication" fromDirectory:self.testApplicationURL.URLByDeletingLastPathComponent];
|
||||
NSURL *targetURL = [diskImageURL URLByAppendingPathComponent:self.testApplicationURL.lastPathComponent];
|
||||
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
NSURL *plistURL = [targetURL URLByAppendingPathComponent:@"Contents/Info.plist"];
|
||||
expect([NSDictionary dictionaryWithContentsOfURL:plistURL][SQRLBundleShortVersionStringKey]).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
expect([NSDictionary dictionaryWithContentsOfURL:plistURL][SQRLBundleShortVersionStringKey]).withTimeout(SQRLLongTimeout).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
});
|
||||
|
||||
describe(@"with backup restoration", ^{
|
||||
@@ -118,7 +118,7 @@ describe(@"with backup restoration", ^{
|
||||
});
|
||||
|
||||
it(@"should not install an update after too many attempts", ^{
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
__block NSError *error;
|
||||
@@ -129,11 +129,11 @@ describe(@"with backup restoration", ^{
|
||||
});
|
||||
|
||||
it(@"should relaunch even after failing to install an update", ^{
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:YES];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:targetURL bundleIdentifier:nil launchAfterInstallation:YES useUpdateBundleName:NO];
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
expect(@([NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.github.Squirrel.TestApplication"].count)).toEventually(equal(@1));
|
||||
|
||||
|
||||
__block NSError *error;
|
||||
expect(@([[self.testApplicationSignature verifyBundleAtURL:targetURL] waitUntilCompleted:&error])).to(beTruthy());
|
||||
expect(error).to(beNil());
|
||||
@@ -149,7 +149,7 @@ it(@"should disallow writing the updated application except by the owner", ^{
|
||||
expect(@(modeOfURL(updateURL))).to(equal(@0777));
|
||||
expect(@(modeOfURL([updateURL URLByAppendingPathComponent:@"Contents/MacOS/TestApplication"]))).to(equal(@0777));
|
||||
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
@@ -165,7 +165,7 @@ describe(@"signal handling", ^{
|
||||
void (^verifyUpdate)(void) = ^{
|
||||
// Wait up to the launchd throttle interval, then verify that ShipIt
|
||||
// relaunched and finished installing the update.
|
||||
expect(self.testApplicationBundleVersion).withTimeout(5).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
expect(self.testApplicationBundleVersion).withTimeout(SQRLLongTimeout).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
|
||||
NSError *error;
|
||||
BOOL success = [[self.testApplicationSignature verifyBundleAtURL:targetURL] waitUntilCompleted:&error];
|
||||
@@ -178,7 +178,7 @@ describe(@"signal handling", ^{
|
||||
// accessing the property.
|
||||
targetURL = self.testApplicationURL;
|
||||
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
SQRLShipItRequest *request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
|
||||
[self installWithRequest:request remote:YES];
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ beforeEach(^{
|
||||
directoryManager = SQRLDirectoryManager.currentApplicationManager;
|
||||
|
||||
NSURL *updateURL = [self createTestApplicationUpdate];
|
||||
request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO];
|
||||
request = [[SQRLShipItRequest alloc] initWithUpdateBundleURL:updateURL targetBundleURL:self.testApplicationURL bundleIdentifier:nil launchAfterInstallation:NO useUpdateBundleName:NO];
|
||||
expect(request).notTo(beNil());
|
||||
|
||||
expect(request.targetBundleURL).to(equal(self.testApplicationURL));
|
||||
|
||||
@@ -34,6 +34,8 @@ it(@"should complete immediately when the app is not running", ^{
|
||||
});
|
||||
|
||||
it(@"should wait until one instance terminates", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
NSRunningApplication *app = [self launchTestApplicationWithEnvironment:nil];
|
||||
|
||||
__block NSRunningApplication *observedApp = nil;
|
||||
@@ -53,6 +55,8 @@ it(@"should wait until one instance terminates", ^{
|
||||
});
|
||||
|
||||
it(@"should wait until multiple instances terminate", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
NSRunningApplication *app1 = [self launchTestApplicationWithEnvironment:nil];
|
||||
NSRunningApplication *app2 = [self launchTestApplicationWithEnvironment:nil];
|
||||
|
||||
|
||||
@@ -45,12 +45,12 @@ it(@"should validate release name and notes", ^{
|
||||
|
||||
it(@"should parse Central style dates", ^{
|
||||
SQRLUpdate *update = [MTLJSONAdapter modelOfClass:SQRLUpdate.class fromJSONDictionary:@{ @"url": @"http://example.com/update", @"pub_date": @"Tue Sep 17 10:24:27 -0700 2013" } error:NULL];
|
||||
expect(update.releaseDate).notTo(beNil());
|
||||
expect(update.releaseDate).to(equal([NSDate dateWithTimeIntervalSince1970:1379438667]));
|
||||
});
|
||||
|
||||
it(@"should parse ISO 8601 dates", ^{
|
||||
SQRLUpdate *update = [MTLJSONAdapter modelOfClass:SQRLUpdate.class fromJSONDictionary:@{ @"url": @"http://example.com/update", @"pub_date": @"2013-09-18T13:17:07+01:00" } error:NULL];
|
||||
expect(update.releaseDate).notTo(beNil());
|
||||
expect(update.releaseDate).to(equal([NSDate dateWithTimeIntervalSince1970:1379506627]));
|
||||
});
|
||||
|
||||
QuickSpecEnd
|
||||
|
||||
+172
-10
@@ -12,12 +12,31 @@
|
||||
#import <Squirrel/Squirrel.h>
|
||||
|
||||
#import "SQRLDirectoryManager.h"
|
||||
#import "SQRLUpdater.h"
|
||||
#import "SQRLZipArchiver.h"
|
||||
|
||||
#import "OHHTTPStubs/OHHTTPStubs.h"
|
||||
#import "QuickSpec+SQRLFixtures.h"
|
||||
#import "SQRLTestUpdate.h"
|
||||
#import "TestAppConstants.h"
|
||||
#import <objc/objc-class.h>
|
||||
|
||||
//! force Updater to believe we are not on readonly, to continue downloading the release
|
||||
bool isRunningOnReadOnlyVolumeImp(id self, SEL _cmd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//! we don't want updateFromJSONData to be executed. It is enough to know if it was called
|
||||
bool updateFromJSONDataIsCalled = false;
|
||||
RACSignal * updateFromJSONDataImp(id self, SEL _cmd, NSData * data)
|
||||
{
|
||||
NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
|
||||
NSLog(@"updateFromJSONDataImp called with %@", str);
|
||||
updateFromJSONDataIsCalled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
QuickSpecBegin(SQRLUpdaterSpec)
|
||||
|
||||
@@ -59,6 +78,134 @@ beforeEach(^{
|
||||
JSONURL = [self.temporaryDirectoryURL URLByAppendingPathComponent:@"update.json"];
|
||||
});
|
||||
|
||||
|
||||
describe(@"checkForUpdatesCommand", ^{
|
||||
|
||||
/**
|
||||
control test behavior via env variables:
|
||||
|
||||
SQUIRREL_TEST_LOCAL_SERVER=True
|
||||
SQUIRREL_LOCAL_SERVER_URL=http://localhost:8123/update/osx/1.0.1-stable/stable
|
||||
SQUIRREL_TEST_LOCAL_CDN=True
|
||||
SQUIRREL_CDN_URL=@"http://localhost/RELEASES.json"
|
||||
|
||||
*/
|
||||
|
||||
OHHTTPStubs *stubsJson = [OHHTTPStubs shouldStubRequestsPassingTest:^(NSURLRequest *request) {
|
||||
return [request.URL.absoluteString isEqualToString:@"http://localhost/RELEASES.json?method=Json"];
|
||||
} withStubResponse:^(NSURLRequest *request) {
|
||||
NSDictionary *newReleaseJSON = @{
|
||||
@"version": @"0.0.145",
|
||||
@"name": @"my-stub-release",
|
||||
@"notes": @"mock release for automated tests, json",
|
||||
@"pub_date": @"2017-03-09T15:24:55-05:00",
|
||||
@"url": @"http://localhost/myapp-0.0.145.zip"
|
||||
};
|
||||
|
||||
NSError * err;
|
||||
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:newReleaseJSON options:0 error:&err];
|
||||
|
||||
return [OHHTTPStubsResponse responseWithData:jsonData statusCode:200 responseTime:0 headers:nil];
|
||||
}];
|
||||
|
||||
OHHTTPStubs *stubsReleaseServer = [OHHTTPStubs shouldStubRequestsPassingTest:^(NSURLRequest *request) {
|
||||
return [request.URL.absoluteString isEqualToString:@"http://localhost:8123/update/osx/1.0.1-stable/stable?method=ReleaseServer"];
|
||||
} withStubResponse:^(NSURLRequest *request) {
|
||||
NSDictionary *newReleaseJSON = @{
|
||||
@"version": @"0.0.145",
|
||||
@"name": @"my-stub-release",
|
||||
@"notes": @"mock release for automated tests, release server",
|
||||
@"pub_date": @"2017-03-09T15:24:55-05:00",
|
||||
@"url": @"http://localmyappng-0.0.145.zip"
|
||||
};
|
||||
|
||||
NSError * err;
|
||||
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:newReleaseJSON options:0 error:&err];
|
||||
|
||||
return [OHHTTPStubsResponse responseWithData:jsonData statusCode:200 responseTime:0 headers:nil];
|
||||
}];
|
||||
|
||||
[self addCleanupBlock:^{
|
||||
[OHHTTPStubs removeRequestHandler:stubsJson];
|
||||
[OHHTTPStubs removeRequestHandler:stubsReleaseServer];
|
||||
}];
|
||||
|
||||
BOOL testLocalServer = [[[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_TEST_LOCAL_SERVER"] boolValue];
|
||||
NSString* localServerURL = [[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_LOCAL_SERVER_URL"];
|
||||
if(!localServerURL) {
|
||||
localServerURL = @"http://localhost:8123/update/osx/1.0.1-stable/stable?method=ReleaseServer";
|
||||
}
|
||||
BOOL testLocalCdn = [[[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_TEST_LOCAL_CDN"] boolValue];
|
||||
NSString* localCdnURL = [[[NSProcessInfo processInfo]environment]objectForKey:@"SQUIRREL_CDN_URL"];
|
||||
if(!localCdnURL) {
|
||||
localCdnURL = @"http://localhost/RELEASES.json?method=Json";
|
||||
}
|
||||
|
||||
NSLog(@"testLocalServer %d %@", testLocalServer, localServerURL);
|
||||
NSLog(@"testLocalCdn %d %@", testLocalCdn, localCdnURL);
|
||||
|
||||
__block SQRLUpdater *updater = nil;
|
||||
__block NSURLRequest *localRequest = nil;
|
||||
|
||||
it(@"Squirrel should work with a release server", ^{
|
||||
|
||||
if(testLocalServer) {
|
||||
|
||||
updateFromJSONDataIsCalled = false;
|
||||
|
||||
//! setup the updater
|
||||
localRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:localServerURL]];
|
||||
updater = [[SQRLUpdater alloc] initWithUpdateRequest:localRequest];
|
||||
|
||||
//! replace updateFromJSONData() and isRunningOnReadOnlyVolume() methods
|
||||
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
|
||||
, @selector(updateFromJSONData:))
|
||||
, (IMP) updateFromJSONDataImp);
|
||||
|
||||
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
|
||||
, @selector(isRunningOnReadOnlyVolume))
|
||||
, (IMP) isRunningOnReadOnlyVolumeImp);
|
||||
|
||||
NSError *error = nil;
|
||||
BOOL result = [[updater.checkForUpdatesCommand execute:nil] asynchronouslyWaitUntilCompleted:&error];
|
||||
|
||||
//! now check the results
|
||||
expect((int)updater.state).toEventually(equal((int)SQRLUpdaterStateIdle));
|
||||
expect( (BOOL) updateFromJSONDataIsCalled ).to(beTrue());
|
||||
expect( (BOOL) result ).to(beTrue());
|
||||
}
|
||||
});
|
||||
|
||||
it(@"Squirrel should work with a CDN", ^{
|
||||
|
||||
if(testLocalCdn) {
|
||||
updateFromJSONDataIsCalled = false;
|
||||
|
||||
//! setup the updater
|
||||
localRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:localCdnURL]];
|
||||
updater = [[SQRLUpdater alloc] initWithUpdateRequest:localRequest];
|
||||
|
||||
//! replace updateFromJSONData() and isRunningOnReadOnlyVolume() methods
|
||||
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
|
||||
, @selector(updateFromJSONData:))
|
||||
, (IMP) updateFromJSONDataImp);
|
||||
|
||||
method_setImplementation(class_getInstanceMethod([SQRLUpdater class]
|
||||
, @selector(isRunningOnReadOnlyVolume))
|
||||
, (IMP) isRunningOnReadOnlyVolumeImp);
|
||||
|
||||
NSError *error = nil;
|
||||
BOOL result = [[updater.checkForUpdatesCommand execute:nil] asynchronouslyWaitUntilCompleted:&error];
|
||||
|
||||
//! now check the results
|
||||
expect((int)updater.state).toEventually(equal((int)SQRLUpdaterStateIdle));
|
||||
expect( (BOOL) updateFromJSONDataIsCalled ).to(beTrue());
|
||||
expect( (BOOL) result ).to(beTrue());
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe(@"updating", ^{
|
||||
__block NSURL *updateURL;
|
||||
|
||||
@@ -67,6 +214,8 @@ describe(@"updating", ^{
|
||||
});
|
||||
|
||||
it(@"should use the application's bundled version of Squirrel and update in-place", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
NSError *error = nil;
|
||||
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
|
||||
@"updateURL": zipUpdate(updateURL),
|
||||
@@ -78,11 +227,13 @@ describe(@"updating", ^{
|
||||
writeUpdate(update);
|
||||
|
||||
NSRunningApplication *app = launchWithEnvironment(nil);
|
||||
expect(@(app.terminated)).toEventually(beTruthy());
|
||||
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
|
||||
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
});
|
||||
|
||||
it(@"should not install a corrupt update", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
NSURL *codeSignatureURL = [updateURL URLByAppendingPathComponent:@"Contents/_CodeSignature"];
|
||||
expect(@([NSFileManager.defaultManager removeItemAtURL:codeSignatureURL error:NULL])).to(beTruthy());
|
||||
|
||||
@@ -94,7 +245,7 @@ describe(@"updating", ^{
|
||||
writeUpdate(update);
|
||||
|
||||
NSRunningApplication *app = launchWithEnvironment(nil);
|
||||
expect(@(app.terminated)).withTimeout(5).toEventually(beTruthy());
|
||||
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
|
||||
|
||||
// Give the update some time to finish installing.
|
||||
[NSThread sleepForTimeInterval:0.2];
|
||||
@@ -102,6 +253,8 @@ describe(@"updating", ^{
|
||||
});
|
||||
|
||||
it(@"should update to the most recently enqueued job", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
|
||||
@"updateURL": zipUpdate(self.testApplicationURL)
|
||||
} error:NULL];
|
||||
@@ -125,11 +278,13 @@ describe(@"updating", ^{
|
||||
|
||||
writeUpdate(update);
|
||||
|
||||
expect(@(app.terminated)).withTimeout(5).toEventually(beTruthy());
|
||||
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
|
||||
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
});
|
||||
|
||||
it(@"should use the application's bundled version of Squirrel and update in-place after a significant delay", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
|
||||
@"updateURL": zipUpdate(updateURL),
|
||||
@"final": @YES
|
||||
@@ -140,7 +295,7 @@ describe(@"updating", ^{
|
||||
NSTimeInterval delay = 15;
|
||||
NSRunningApplication *app = launchWithEnvironment(@{ @"SQRLUpdateDelay": [NSString stringWithFormat:@"%f", delay] });
|
||||
|
||||
expect(@(app.terminated)).withTimeout(delay + 5).toEventually(beTruthy());
|
||||
expect(@(app.terminated)).withTimeout(delay + SQRLLongTimeout).toEventually(beTruthy());
|
||||
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
});
|
||||
|
||||
@@ -151,7 +306,7 @@ describe(@"updating", ^{
|
||||
beforeEach(^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:@"com.github.Squirrel.TestApplication.ShipIt"];
|
||||
|
||||
appSupportURL = [[directoryManager applicationSupportURL] first];
|
||||
appSupportURL = [[directoryManager storageURL] first];
|
||||
expect(appSupportURL).notTo(beNil());
|
||||
|
||||
updateDirectoryURLs = [[RACSignal
|
||||
@@ -167,6 +322,8 @@ describe(@"updating", ^{
|
||||
});
|
||||
|
||||
it(@"should remove downloaded archives after updating", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
NSError *error = nil;
|
||||
SQRLTestUpdate *update = [SQRLTestUpdate modelWithDictionary:@{
|
||||
@"updateURL": zipUpdate(updateURL),
|
||||
@@ -178,8 +335,9 @@ describe(@"updating", ^{
|
||||
|
||||
writeUpdate(update);
|
||||
|
||||
NSRunningApplication *app = launchWithEnvironment(nil);
|
||||
expect(@(app.terminated)).withTimeout(5).toEventually(beTruthy());
|
||||
NSRunningApplication *app = launchWithEnvironment(@{ @"SQRLUpdateRequestCount": @2 });
|
||||
expect([updateDirectoryURLs toArray]).toEventuallyNot(equal(@[]));
|
||||
expect(@(app.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
|
||||
expect(self.testApplicationBundleVersion).toEventually(equal(SQRLTestApplicationUpdatedShortVersionString));
|
||||
|
||||
expect([updateDirectoryURLs toArray]).toEventually(equal(@[]));
|
||||
@@ -242,6 +400,8 @@ static RACSignal * (^stateNotificationListener)(void) = ^ {
|
||||
|
||||
describe(@"state", ^{
|
||||
it(@"should transition through idle, checking and idle, when there is no update", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
NSMutableArray *states = [NSMutableArray array];
|
||||
[stateNotificationListener() subscribeNext:^(NSNumber *state) {
|
||||
[states addObject:state];
|
||||
@@ -256,10 +416,12 @@ describe(@"state", ^{
|
||||
];
|
||||
expect(states).toEventually(equal(expectedStates));
|
||||
|
||||
expect(@(testApplication.terminated)).withTimeout(5).toEventually(beTruthy());
|
||||
expect(@(testApplication.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
|
||||
});
|
||||
|
||||
it(@"should transition through idle, checking, downloading and awaiting relaunch, when there is an update", ^{
|
||||
SKIP_IF_RUNNING_ON_TRAVIS
|
||||
|
||||
NSMutableArray *states = [NSMutableArray array];
|
||||
[stateNotificationListener() subscribeNext:^(NSNumber *state) {
|
||||
[states addObject:state];
|
||||
@@ -283,9 +445,9 @@ describe(@"state", ^{
|
||||
@(SQRLUpdaterStateDownloadingUpdate),
|
||||
@(SQRLUpdaterStateAwaitingRelaunch),
|
||||
];
|
||||
expect(states).toEventually(equal(expectedStates));
|
||||
expect(states).withTimeout(SQRLLongTimeout).toEventually(equal(expectedStates));
|
||||
|
||||
expect(@(testApplication.terminated)).withTimeout(5).toEventually(beTruthy());
|
||||
expect(@(testApplication.terminated)).withTimeout(SQRLLongTimeout).toEventually(beTruthy());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.github.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/Headers
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/Mantle
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/Modules
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/Resources
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// MTLJSONAdapter.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-02-12.
|
||||
// Copyright (c) 2013 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MTLModel;
|
||||
|
||||
// A MTLModel object that supports being parsed from and serialized to JSON.
|
||||
@protocol MTLJSONSerializing
|
||||
@required
|
||||
|
||||
// Specifies how to map property keys to different key paths in JSON.
|
||||
//
|
||||
// Subclasses overriding this method should combine their values with those of
|
||||
// `super`.
|
||||
//
|
||||
// Any property keys not present in the dictionary are assumed to match the JSON
|
||||
// key that should be used. Any keys associated with NSNull will not participate
|
||||
// in JSON serialization.
|
||||
//
|
||||
// Returns a dictionary mapping property keys to JSON key paths (as strings) or
|
||||
// NSNull values.
|
||||
+ (NSDictionary *)JSONKeyPathsByPropertyKey;
|
||||
|
||||
@optional
|
||||
|
||||
// Specifies how to convert a JSON value to the given property key. If
|
||||
// reversible, the transformer will also be used to convert the property value
|
||||
// back to JSON.
|
||||
//
|
||||
// If the receiver implements a `+<key>JSONTransformer` method, MTLJSONAdapter
|
||||
// will use the result of that method instead.
|
||||
//
|
||||
// Returns a value transformer, or nil if no transformation should be performed.
|
||||
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key;
|
||||
|
||||
// Overridden to parse the receiver as a different class, based on information
|
||||
// in the provided dictionary.
|
||||
//
|
||||
// This is mostly useful for class clusters, where the abstract base class would
|
||||
// be passed into -[MTLJSONAdapter initWithJSONDictionary:modelClass:], but
|
||||
// a subclass should be instantiated instead.
|
||||
//
|
||||
// JSONDictionary - The JSON dictionary that will be parsed.
|
||||
//
|
||||
// Returns the class that should be parsed (which may be the receiver), or nil
|
||||
// to abort parsing (e.g., if the data is invalid).
|
||||
+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary;
|
||||
|
||||
@end
|
||||
|
||||
// The domain for errors originating from MTLJSONAdapter.
|
||||
extern NSString * const MTLJSONAdapterErrorDomain;
|
||||
|
||||
// +classForParsingJSONDictionary: returned nil for the given dictionary.
|
||||
extern const NSInteger MTLJSONAdapterErrorNoClassFound;
|
||||
|
||||
// The provided JSONDictionary is not valid.
|
||||
extern const NSInteger MTLJSONAdapterErrorInvalidJSONDictionary;
|
||||
|
||||
// The model's implementation of +JSONKeyPathsByPropertyKey included a key which
|
||||
// does not actually exist in +propertyKeys.
|
||||
extern const NSInteger MTLJSONAdapterErrorInvalidJSONMapping;
|
||||
|
||||
// Converts a MTLModel object to and from a JSON dictionary.
|
||||
@interface MTLJSONAdapter : NSObject
|
||||
|
||||
// The model object that the receiver was initialized with, or that the receiver
|
||||
// parsed from a JSON dictionary.
|
||||
@property (nonatomic, strong, readonly) MTLModel<MTLJSONSerializing> *model;
|
||||
|
||||
// Attempts to parse a JSON dictionary into a model object.
|
||||
//
|
||||
// modelClass - The MTLModel subclass to attempt to parse from the JSON.
|
||||
// This class must conform to <MTLJSONSerializing>. This
|
||||
// argument must not be nil.
|
||||
// JSONDictionary - A dictionary representing JSON data. This should match the
|
||||
// format returned by NSJSONSerialization. If this argument is
|
||||
// nil, the method returns nil.
|
||||
// error - If not NULL, this may be set to an error that occurs during
|
||||
// parsing or initializing an instance of `modelClass`.
|
||||
//
|
||||
// Returns an instance of `modelClass` upon success, or nil if a parsing error
|
||||
// occurred.
|
||||
+ (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary error:(NSError **)error;
|
||||
|
||||
// Attempts to parse an array of JSON dictionary objects into a model objects
|
||||
// of a specific class.
|
||||
//
|
||||
// modelClass - The MTLModel subclass to attempt to parse from the JSON. This
|
||||
// class must conform to <MTLJSONSerializing>. This argument must
|
||||
// not be nil.
|
||||
// JSONArray - A array of dictionaries representing JSON data. This should
|
||||
// match the format returned by NSJSONSerialization. If this
|
||||
// argument is nil, the method returns nil.
|
||||
// error - If not NULL, this may be set to an error that occurs during
|
||||
// parsing or initializing an any of the instances of
|
||||
// `modelClass`.
|
||||
//
|
||||
// Returns an array of `modelClass` instances upon success, or nil if a parsing
|
||||
// error occurred.
|
||||
+ (NSArray *)modelsOfClass:(Class)modelClass fromJSONArray:(NSArray *)JSONArray error:(NSError **)error;
|
||||
|
||||
// Converts a model into a JSON representation.
|
||||
//
|
||||
// model - The model to use for JSON serialization. This argument must not be
|
||||
// nil.
|
||||
//
|
||||
// Returns a JSON dictionary, or nil if a serialization error occurred.
|
||||
+ (NSDictionary *)JSONDictionaryFromModel:(MTLModel<MTLJSONSerializing> *)model;
|
||||
|
||||
// Converts a array of models into a JSON representation.
|
||||
//
|
||||
// models - The array of models to use for JSON serialization. This argument
|
||||
// must not be nil.
|
||||
//
|
||||
// Returns a JSON array, or nil if a serialization error occurred for any
|
||||
// model.
|
||||
+ (NSArray *)JSONArrayFromModels:(NSArray *)models;
|
||||
|
||||
// Initializes the receiver by attempting to parse a JSON dictionary into
|
||||
// a model object.
|
||||
//
|
||||
// JSONDictionary - A dictionary representing JSON data. This should match the
|
||||
// format returned by NSJSONSerialization. If this argument is
|
||||
// nil, the method returns nil and an error with code
|
||||
// MTLJSONAdapterErrorInvalidJSONDictionary.
|
||||
// modelClass - The MTLModel subclass to attempt to parse from the JSON.
|
||||
// This class must conform to <MTLJSONSerializing>. This
|
||||
// argument must not be nil.
|
||||
// error - If not NULL, this may be set to an error that occurs during
|
||||
// parsing or initializing an instance of `modelClass`.
|
||||
//
|
||||
// Returns an initialized adapter upon success, or nil if a parsing error
|
||||
// occurred.
|
||||
- (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)modelClass error:(NSError **)error;
|
||||
|
||||
// Initializes the receiver with an existing model.
|
||||
//
|
||||
// model - The model to use for JSON serialization. This argument must not be
|
||||
// nil.
|
||||
- (id)initWithModel:(MTLModel<MTLJSONSerializing> *)model;
|
||||
|
||||
// Serializes the receiver's `model` into JSON.
|
||||
//
|
||||
// Returns a JSON dictionary, or nil if a serialization error occurred.
|
||||
- (NSDictionary *)JSONDictionary;
|
||||
|
||||
// Looks up the JSON key path in the model's +propertyKeys.
|
||||
//
|
||||
// Subclasses may override this method to customize the adapter's seralizing
|
||||
// behavior. You should not call this method directly.
|
||||
//
|
||||
// key - The property key to retrieve the corresponding JSON key path for. This
|
||||
// argument must not be nil.
|
||||
//
|
||||
// Returns a key path to use, or nil to omit the property from JSON.
|
||||
- (NSString *)JSONKeyPathForPropertyKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
@interface MTLJSONAdapter (Deprecated)
|
||||
|
||||
+ (id)modelOfClass:(Class)modelClass fromJSONDictionary:(NSDictionary *)JSONDictionary __attribute__((deprecated("Replaced by +modelOfClass:fromJSONDictionary:error:")));
|
||||
- (id)initWithJSONDictionary:(NSDictionary *)JSONDictionary modelClass:(Class)modelClass __attribute__((deprecated("Replaced by -initWithJSONDictionary:modelClass:error:")));
|
||||
|
||||
@end
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// MTLManagedObjectAdapter.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-03-29.
|
||||
// Copyright (c) 2013 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
@class MTLModel;
|
||||
|
||||
// A MTLModel object that supports being serialized to and from Core Data as an
|
||||
// NSManagedObject.
|
||||
@protocol MTLManagedObjectSerializing
|
||||
@required
|
||||
|
||||
// The name of the Core Data entity that the receiver serializes to and
|
||||
// deserializes from.
|
||||
//
|
||||
// This method must not return nil.
|
||||
+ (NSString *)managedObjectEntityName;
|
||||
|
||||
// Specifies how to map property keys to different keys on the receiver's
|
||||
// +managedObjectEntity.
|
||||
//
|
||||
// Entity attributes will be mapped to and from the receiver's properties using
|
||||
// +entityAttributeTransformerForKey:. Entity relationships will be mapped to
|
||||
// and from MTLModel objects using +relationshipModelClassesByPropertyKey.
|
||||
// Fetched properties are not supported.
|
||||
//
|
||||
// Subclasses overriding this method should combine their values with those of
|
||||
// `super`.
|
||||
//
|
||||
// Any property keys not present in the dictionary are assumed to match the
|
||||
// entity key that should be used. Any keys associated with NSNull will not
|
||||
// participate in managed object serialization.
|
||||
//
|
||||
// Returns a dictionary mapping property keys to entity keys (as strings) or
|
||||
// NSNull values.
|
||||
+ (NSDictionary *)managedObjectKeysByPropertyKey;
|
||||
|
||||
@optional
|
||||
|
||||
// Specifies a set of property keys used by the adapter to check for an already
|
||||
// existing managed object when converting the MTLModel to its related
|
||||
// NSManagedObject.
|
||||
//
|
||||
// The adapter will first map any keys provided by this method to the correct
|
||||
// keys in managedObjectKeysByPropertyKey.
|
||||
//
|
||||
// The adapter will then perform a fetch request in the provided context for a
|
||||
// managed object that matches the MTLModel's managedObjectEntityName and has
|
||||
// equal values set for the property keys on the MTLModel.
|
||||
//
|
||||
// The managed object returned by the fetch request will then be set with all
|
||||
// values from the MTLModel that the managed object is being converted from.
|
||||
//
|
||||
// If a property value of our MTLModel is yet another MTLModel which needs to be
|
||||
// converted to a managed object, the class for that MTLModel can also implement
|
||||
// this method to perform its own uniqing.
|
||||
//
|
||||
// For example:
|
||||
// 1. An MTLModel subclass has id_number = 10.
|
||||
// 2. An NSManagedObject accessible to the adapter's context has idnumber = 10.
|
||||
// 3. managedObjectKeysByPropertyKey returns @{@"id_number" : @"idnumber"}
|
||||
// 4. propertyKeysForManagedObjectUniquing returns
|
||||
// [NSSet setWithObject:@"id_number"];
|
||||
// 5. Then our fetch request may return this managed object (or another managed
|
||||
// object with idnumber = 10).
|
||||
//
|
||||
// NOTE: If multiple managed objects follow the same uniquing criteria only one
|
||||
// of them will be set with our MTLModel's values.
|
||||
+ (NSSet *)propertyKeysForManagedObjectUniquing;
|
||||
|
||||
// Specifies how to convert the given property key to a managed object
|
||||
// attribute. If reversible, the transformer will also be used to convert the
|
||||
// managed object attribute back to the property.
|
||||
//
|
||||
// If the receiver implements a `+<key>EntityAttributeTransformer` method,
|
||||
// MTLManagedObjectAdapter will use the result of that method instead.
|
||||
//
|
||||
// Returns a value transformer, or nil if no transformation should be performed.
|
||||
+ (NSValueTransformer *)entityAttributeTransformerForKey:(NSString *)key;
|
||||
|
||||
// Specifies the MTLModel subclasses that should be deserialized to the
|
||||
// receiver's property keys when a property key corresponds to an entity
|
||||
// relationship.
|
||||
//
|
||||
// In other words, the dictionary returned by this method is used to decode
|
||||
// managed object relationships into MTLModels (or NSArrays or NSSets thereof)
|
||||
// set on the receiver.
|
||||
//
|
||||
// If a property key is omitted from the returned dictionary, but present in
|
||||
// +managedObjectKeysByPropertyKey, and the receiver's +managedObjectEntity has
|
||||
// a relationship at the corresponding key, an exception will be thrown during
|
||||
// deserialization.
|
||||
//
|
||||
// Subclasses overriding this method should combine their values with those of
|
||||
// `super`.
|
||||
//
|
||||
// Returns a dictionary mapping property keys to the Class objects that should
|
||||
// be used.
|
||||
+ (NSDictionary *)relationshipModelClassesByPropertyKey;
|
||||
|
||||
// Overridden to deserialize a different class instead of the receiver, based on
|
||||
// information in the provided object.
|
||||
//
|
||||
// This is mostly useful for class clusters, where the abstract base class would
|
||||
// be passed into +[MTLManagedObjectAdapter
|
||||
// modelOfClass:fromManagedObject:error:], but a subclass should be instantiated
|
||||
// instead.
|
||||
//
|
||||
// managedObject - The object that will be deserialized.
|
||||
//
|
||||
// Returns the class that should be instantiated (which may be the receiver), or
|
||||
// nil to abort parsing (e.g., if the data is invalid).
|
||||
+ (Class)classForDeserializingManagedObject:(NSManagedObject *)managedObject;
|
||||
|
||||
// Overriden when merging the value of the given key on the receiver with the
|
||||
// value of the same key from the given `NSManagedObject` requires custom
|
||||
// handling.
|
||||
//
|
||||
// By default, this method is not implemented, and precedence will be given to
|
||||
// the value of the receiving model implicitly.
|
||||
//
|
||||
// When implemented, this method is called when an existing `NSManagedObject`
|
||||
// is found for the receiving model, before updating the `NSManagedObject`'s
|
||||
// properties.
|
||||
//
|
||||
// When implementing, you should use `+managedObjectKeysByPropertyKey` to map
|
||||
// the given `key` to the appropriate `NSManagedObject` property.
|
||||
- (void)mergeValueForKey:(NSString *)key fromManagedObject:(NSManagedObject *)managedObject;
|
||||
|
||||
// Overriden when merging values on the receiver with the given
|
||||
// `NSManagedObject` requires custom handling.
|
||||
//
|
||||
// By default, this method is not implemented, and precedence will be given to
|
||||
// the values of the receiving model implicitly.
|
||||
//
|
||||
// When implemented, this method is called when an existing `NSManagedObject`
|
||||
// is found for the receiving model, before updating the `NSManagedObject`'s
|
||||
// properties.
|
||||
//
|
||||
// When implementing, you should use `+managedObjectKeysByPropertyKey` to map
|
||||
// the given `key` to the appropriate `NSManagedObject` property.
|
||||
//
|
||||
// If you have also implemented `mergeValueForKey:fromManagedObject:` you have
|
||||
// to make sure to call `mergeValueForKey:fromManagedObject:` from this method
|
||||
// when appropriate.
|
||||
- (void)mergeValuesForKeysFromManagedObject:(NSManagedObject *)managedObject;
|
||||
|
||||
@end
|
||||
|
||||
// The domain for errors originating from MTLManagedObjectAdapter.
|
||||
extern NSString * const MTLManagedObjectAdapterErrorDomain;
|
||||
|
||||
// +classForDeserializingManagedObject: returned nil for the given object.
|
||||
extern const NSInteger MTLManagedObjectAdapterErrorNoClassFound;
|
||||
|
||||
// An NSManagedObject failed to initialize.
|
||||
extern const NSInteger MTLManagedObjectAdapterErrorInitializationFailed;
|
||||
|
||||
// The managed object key specified by +managedObjectKeysByPropertyKey does not
|
||||
// exist in the NSEntityDescription.
|
||||
extern const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectKey;
|
||||
|
||||
// The managed object property specified has a type that isn't supported by
|
||||
// MTLManagedObjectAdapter.
|
||||
extern const NSInteger MTLManagedObjectAdapterErrorUnsupportedManagedObjectPropertyType;
|
||||
|
||||
// The fetch request to find an existing managed object based on
|
||||
// `+propertyKeysForManagedObjectUniquing` failed.
|
||||
extern const NSInteger MTLManagedObjectAdapterErrorUniqueFetchRequestFailed;
|
||||
|
||||
// A MTLModel property cannot be serialized to or deserialized from an
|
||||
// NSManagedObject relationship.
|
||||
//
|
||||
// For a to-one relationship, this means that the property does not contain
|
||||
// a MTLModel, or the MTLModel does not conform to <MTLManagedObjectSerializing>.
|
||||
//
|
||||
// For a to-many relationship, this means that the property does not contain an
|
||||
// NSArray or NSSet of MTLModel<MTLManagedObjectSerializing> instances.
|
||||
extern const NSInteger MTLManagedObjectAdapterErrorUnsupportedRelationshipClass;
|
||||
|
||||
// The model's implementation of +managedObjectKeysByPropertyKey included a key
|
||||
// which does not actually exist in +propertyKeys.
|
||||
extern const NSInteger MTLManagedObjectAdapterErrorInvalidManagedObjectMapping;
|
||||
|
||||
// Converts a MTLModel object to and from an NSManagedObject.
|
||||
@interface MTLManagedObjectAdapter : NSObject
|
||||
|
||||
// Attempts to deserialize an NSManagedObject into a MTLModel object.
|
||||
//
|
||||
// modelClass - The MTLModel subclass to return. This class must conform to
|
||||
// <MTLManagedObjectSerializing>. This argument must not be nil.
|
||||
// managedObject - The managed object to deserialize. If this argument is nil,
|
||||
// the method returns nil.
|
||||
// error - If not NULL, this may be set to an error that occurs during
|
||||
// deserialization or initializing an instance of `modelClass`.
|
||||
//
|
||||
// Returns an instance of `modelClass` upon success, or nil if an error
|
||||
// occurred.
|
||||
+ (id)modelOfClass:(Class)modelClass fromManagedObject:(NSManagedObject *)managedObject error:(NSError **)error;
|
||||
|
||||
// Serializes a MTLModel into an NSManagedObject.
|
||||
//
|
||||
// model - The model object to serialize. This argument must not be nil.
|
||||
// context - The context into which to insert the created managed object. This
|
||||
// argument must not be nil.
|
||||
// error - If not NULL, this may be set to an error that occurs during
|
||||
// serialization or insertion.
|
||||
+ (id)managedObjectFromModel:(MTLModel<MTLManagedObjectSerializing> *)model insertingIntoContext:(NSManagedObjectContext *)context error:(NSError **)error;
|
||||
|
||||
@end
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// MTLModel+NSCoding.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-02-12.
|
||||
// Copyright (c) 2013 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MTLModel.h"
|
||||
|
||||
// Defines how a MTLModel property key should be encoded into an archive.
|
||||
//
|
||||
// MTLModelEncodingBehaviorExcluded - The property should never be encoded.
|
||||
// MTLModelEncodingBehaviorUnconditional - The property should always be
|
||||
// encoded.
|
||||
// MTLModelEncodingBehaviorConditional - The object should be encoded only
|
||||
// if unconditionally encoded elsewhere.
|
||||
// This should only be used for object
|
||||
// properties.
|
||||
typedef enum : NSUInteger {
|
||||
MTLModelEncodingBehaviorExcluded = 0,
|
||||
MTLModelEncodingBehaviorUnconditional,
|
||||
MTLModelEncodingBehaviorConditional,
|
||||
} MTLModelEncodingBehavior;
|
||||
|
||||
// Implements default archiving and unarchiving behaviors for MTLModel.
|
||||
@interface MTLModel (NSCoding) <NSCoding>
|
||||
|
||||
// Initializes the receiver from an archive.
|
||||
//
|
||||
// This will decode the original +modelVersion of the archived object, then
|
||||
// invoke -decodeValueForKey:withCoder:modelVersion: for each of the receiver's
|
||||
// +propertyKeys.
|
||||
//
|
||||
// Returns an initialized model object, or nil if a decoding error occurred.
|
||||
- (id)initWithCoder:(NSCoder *)coder;
|
||||
|
||||
// Archives the receiver using the given coder.
|
||||
//
|
||||
// This will encode the receiver's +modelVersion, then the receiver's properties
|
||||
// according to the behaviors specified in +encodingBehaviorsByPropertyKey.
|
||||
- (void)encodeWithCoder:(NSCoder *)coder;
|
||||
|
||||
// Determines how the +propertyKeys of the class are encoded into an archive.
|
||||
// The values of this dictionary should be boxed MTLModelEncodingBehavior
|
||||
// values.
|
||||
//
|
||||
// Any keys not present in the dictionary will be excluded from the archive.
|
||||
//
|
||||
// Subclasses overriding this method should combine their values with those of
|
||||
// `super`.
|
||||
//
|
||||
// Returns a dictionary mapping the receiver's +propertyKeys to default encoding
|
||||
// behaviors. If a property is an object with `weak` semantics, the default
|
||||
// behavior is MTLModelEncodingBehaviorConditional; otherwise, the default is
|
||||
// MTLModelEncodingBehaviorUnconditional.
|
||||
+ (NSDictionary *)encodingBehaviorsByPropertyKey;
|
||||
|
||||
// Determines the classes that are allowed to be decoded for each of the
|
||||
// receiver's properties when using <NSSecureCoding>. The values of this
|
||||
// dictionary should be NSArrays of Class objects.
|
||||
//
|
||||
// If any encodable keys (as determined by +encodingBehaviorsByPropertyKey) are
|
||||
// not present in the dictionary, an exception will be thrown during secure
|
||||
// encoding or decoding.
|
||||
//
|
||||
// Subclasses overriding this method should combine their values with those of
|
||||
// `super`.
|
||||
//
|
||||
// Returns a dictionary mapping the receiver's encodable keys (as determined by
|
||||
// +encodingBehaviorsByPropertyKey) to default allowed classes, based on the
|
||||
// type that each property is declared as. If type of an encodable property
|
||||
// cannot be determined (e.g., it is declared as `id`), it will be omitted from
|
||||
// the dictionary, and subclasses must provide a valid value to prevent an
|
||||
// exception being thrown during encoding/decoding.
|
||||
+ (NSDictionary *)allowedSecureCodingClassesByPropertyKey;
|
||||
|
||||
// Decodes the value of the given property key from an archive.
|
||||
//
|
||||
// By default, this method looks for a `-decode<Key>WithCoder:modelVersion:`
|
||||
// method on the receiver, and invokes it if found.
|
||||
//
|
||||
// If the custom method is not implemented and `coder` does not require secure
|
||||
// coding, `-[NSCoder decodeObjectForKey:]` will be invoked with the given
|
||||
// `key`.
|
||||
//
|
||||
// If the custom method is not implemented and `coder` requires secure coding,
|
||||
// `-[NSCoder decodeObjectOfClasses:forKey:]` will be invoked with the
|
||||
// information from +allowedSecureCodingClassesByPropertyKey and the given `key`. The
|
||||
// receiver must conform to <NSSecureCoding> for this to work correctly.
|
||||
//
|
||||
// key - The property key to decode the value for. This argument cannot
|
||||
// be nil.
|
||||
// coder - The NSCoder representing the archive being decoded. This
|
||||
// argument cannot be nil.
|
||||
// modelVersion - The version of the original model object that was encoded.
|
||||
//
|
||||
// Returns the decoded and boxed value, or nil if the key was not present.
|
||||
- (id)decodeValueForKey:(NSString *)key withCoder:(NSCoder *)coder modelVersion:(NSUInteger)modelVersion;
|
||||
|
||||
// The version of this MTLModel subclass.
|
||||
//
|
||||
// This version number is saved in archives so that later model changes can be
|
||||
// made backwards-compatible with old versions.
|
||||
//
|
||||
// Subclasses should override this method to return a higher version number
|
||||
// whenever a breaking change is made to the model.
|
||||
//
|
||||
// Returns 0.
|
||||
+ (NSUInteger)modelVersion;
|
||||
|
||||
@end
|
||||
|
||||
// This method must be overridden to support archives created by older versions
|
||||
// of Mantle (before the `MTLModel+NSCoding` interface existed).
|
||||
@interface MTLModel (OldArchiveSupport)
|
||||
|
||||
// Converts an archived external representation to a dictionary suitable for
|
||||
// passing to -initWithDictionary:.
|
||||
//
|
||||
// externalRepresentation - The decoded external representation of the receiver.
|
||||
// fromVersion - The model version at the time the external
|
||||
// representation was encoded.
|
||||
//
|
||||
// Returns nil by default, indicating that conversion failed.
|
||||
+ (NSDictionary *)dictionaryValueFromArchivedExternalRepresentation:(NSDictionary *)externalRepresentation version:(NSUInteger)fromVersion;
|
||||
|
||||
@end
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// MTLModel.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-09-11.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// An abstract base class for model objects, using reflection to provide
|
||||
// sensible default behaviors.
|
||||
//
|
||||
// The default implementations of <NSCopying>, -hash, and -isEqual: make use of
|
||||
// the +propertyKeys method.
|
||||
@interface MTLModel : NSObject <NSCopying>
|
||||
|
||||
// Returns a new instance of the receiver initialized using
|
||||
// -initWithDictionary:error:.
|
||||
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
|
||||
|
||||
// Initializes the receiver with default values.
|
||||
//
|
||||
// This is the designated initializer for this class.
|
||||
- (instancetype)init;
|
||||
|
||||
// Initializes the receiver using key-value coding, setting the keys and values
|
||||
// in the given dictionary.
|
||||
//
|
||||
// Subclass implementations may override this method, calling the super
|
||||
// implementation, in order to perform further processing and initialization
|
||||
// after deserialization.
|
||||
//
|
||||
// dictionaryValue - Property keys and values to set on the receiver. Any NSNull
|
||||
// values will be converted to nil before being used. KVC
|
||||
// validation methods will automatically be invoked for all of
|
||||
// the properties given. If nil, this method is equivalent to
|
||||
// -init.
|
||||
// error - If not NULL, this may be set to any error that occurs
|
||||
// (like a KVC validation error).
|
||||
//
|
||||
// Returns an initialized model object, or nil if validation failed.
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
|
||||
|
||||
// Returns the keys for all @property declarations, except for `readonly`
|
||||
// properties without ivars, or properties on MTLModel itself.
|
||||
+ (NSSet *)propertyKeys;
|
||||
|
||||
// A dictionary representing the properties of the receiver.
|
||||
//
|
||||
// The default implementation combines the values corresponding to all
|
||||
// +propertyKeys into a dictionary, with any nil values represented by NSNull.
|
||||
//
|
||||
// This property must never be nil.
|
||||
@property (nonatomic, copy, readonly) NSDictionary *dictionaryValue;
|
||||
|
||||
// Merges the value of the given key on the receiver with the value of the same
|
||||
// key from the given model object, giving precedence to the other model object.
|
||||
//
|
||||
// By default, this method looks for a `-merge<Key>FromModel:` method on the
|
||||
// receiver, and invokes it if found. If not found, and `model` is not nil, the
|
||||
// value for the given key is taken from `model`.
|
||||
- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model;
|
||||
|
||||
// Merges the values of the given model object into the receiver, using
|
||||
// -mergeValueForKey:fromModel: for each key in +propertyKeys.
|
||||
//
|
||||
// `model` must be an instance of the receiver's class or a subclass thereof.
|
||||
- (void)mergeValuesForKeysFromModel:(MTLModel *)model;
|
||||
|
||||
// Compares the receiver with another object for equality.
|
||||
//
|
||||
// The default implementation is equivalent to comparing both models'
|
||||
// -dictionaryValue.
|
||||
//
|
||||
// Note that this may lead to infinite loops if the receiver holds a circular
|
||||
// reference to another MTLModel and both use the default behavior.
|
||||
// It is recommended to override -isEqual: in this scenario.
|
||||
- (BOOL)isEqual:(id)object;
|
||||
|
||||
// A string that describes the contents of the receiver.
|
||||
//
|
||||
// The default implementation is based on the receiver's class and its
|
||||
// -dictionaryValue.
|
||||
//
|
||||
// Note that this may lead to infinite loops if the receiver holds a circular
|
||||
// reference to another MTLModel and both use the default behavior.
|
||||
// It is recommended to override -description in this scenario.
|
||||
- (NSString *)description;
|
||||
|
||||
@end
|
||||
|
||||
// Implements validation logic for MTLModel.
|
||||
@interface MTLModel (Validation)
|
||||
|
||||
// Validates the model.
|
||||
//
|
||||
// The default implementation simply invokes -validateValue:forKey:error: with
|
||||
// all +propertyKeys and their current value. If -validateValue:forKey:error:
|
||||
// returns a new value, the property is set to that new value.
|
||||
//
|
||||
// error - If not NULL, this may be set to any error that occurs during
|
||||
// validation
|
||||
//
|
||||
// Returns YES if the model is valid, or NO if the validation failed.
|
||||
- (BOOL)validate:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
@interface MTLModel (Unavailable)
|
||||
|
||||
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionaryValue __attribute__((deprecated("Replaced by +modelWithDictionary:error:")));
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue __attribute__((deprecated("Replaced by -initWithDictionary:error:")));
|
||||
|
||||
+ (instancetype)modelWithExternalRepresentation:(NSDictionary *)externalRepresentation __attribute__((deprecated("Replaced by -[MTLJSONAdapter initWithJSONDictionary:modelClass:]")));
|
||||
- (instancetype)initWithExternalRepresentation:(NSDictionary *)externalRepresentation __attribute__((deprecated("Replaced by -[MTLJSONAdapter initWithJSONDictionary:modelClass:]")));
|
||||
|
||||
@property (nonatomic, copy, readonly) NSDictionary *externalRepresentation __attribute__((deprecated("Replaced by MTLJSONAdapter.JSONDictionary")));
|
||||
|
||||
+ (NSDictionary *)externalRepresentationKeyPathsByPropertyKey __attribute__((deprecated("Replaced by +JSONKeyPathsByPropertyKey in <MTLJSONSerializing>")));
|
||||
+ (NSValueTransformer *)transformerForKey:(NSString *)key __attribute__((deprecated("Replaced by +JSONTransformerForKey: in <MTLJSONSerializing>")));
|
||||
|
||||
+ (NSDictionary *)migrateExternalRepresentation:(NSDictionary *)externalRepresentation fromVersion:(NSUInteger)fromVersion __attribute__((deprecated("Replaced by -decodeValueForKey:withCoder:modelVersion:")));
|
||||
|
||||
@end
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// MTLValueTransformer.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-09-11.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef id (^MTLValueTransformerBlock)(id);
|
||||
|
||||
//
|
||||
// A value transformer supporting block-based transformation.
|
||||
//
|
||||
@interface MTLValueTransformer : NSValueTransformer
|
||||
|
||||
// Returns a transformer which transforms values using the given block. Reverse
|
||||
// transformations will not be allowed.
|
||||
+ (instancetype)transformerWithBlock:(MTLValueTransformerBlock)transformationBlock;
|
||||
|
||||
// Returns a transformer which transforms values using the given block, for
|
||||
// forward or reverse transformations.
|
||||
+ (instancetype)reversibleTransformerWithBlock:(MTLValueTransformerBlock)transformationBlock;
|
||||
|
||||
// Returns a transformer which transforms values using the given blocks.
|
||||
+ (instancetype)reversibleTransformerWithForwardBlock:(MTLValueTransformerBlock)forwardBlock reverseBlock:(MTLValueTransformerBlock)reverseBlock;
|
||||
|
||||
@end
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Mantle.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-09-04.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for Mantle.
|
||||
FOUNDATION_EXPORT double MantleVersionNumber;
|
||||
|
||||
//! Project version string for Mantle.
|
||||
FOUNDATION_EXPORT const unsigned char MantleVersionString[];
|
||||
|
||||
#import <Mantle/MTLJSONAdapter.h>
|
||||
#import <Mantle/MTLManagedObjectAdapter.h>
|
||||
#import <Mantle/MTLModel.h>
|
||||
#import <Mantle/MTLModel+NSCoding.h>
|
||||
#import <Mantle/MTLValueTransformer.h>
|
||||
#import <Mantle/NSArray+MTLManipulationAdditions.h>
|
||||
#import <Mantle/NSDictionary+MTLManipulationAdditions.h>
|
||||
#import <Mantle/NSObject+MTLComparisonAdditions.h>
|
||||
#import <Mantle/NSValueTransformer+MTLInversionAdditions.h>
|
||||
#import <Mantle/NSValueTransformer+MTLPredefinedTransformerAdditions.h>
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// NSArray+MTLManipulationAdditions.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Josh Abernathy on 9/19/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSArray (MTLManipulationAdditions)
|
||||
|
||||
// The first object in the array or nil if the array is empty.
|
||||
// Forwards to `firstObject` which has been first declared in iOS7, but works with iOS4/10.6.
|
||||
@property (nonatomic, readonly, strong) id mtl_firstObject;
|
||||
|
||||
// Returns a new array without all instances of the given object.
|
||||
- (NSArray *)mtl_arrayByRemovingObject:(id)object;
|
||||
|
||||
// Returns a new array without the first object. If the array is empty, it
|
||||
// returns the empty array.
|
||||
- (NSArray *)mtl_arrayByRemovingFirstObject;
|
||||
|
||||
// Returns a new array without the last object. If the array is empty, it
|
||||
// returns the empty array.
|
||||
- (NSArray *)mtl_arrayByRemovingLastObject;
|
||||
|
||||
@end
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// NSDictionary+MTLManipulationAdditions.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-09-24.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSDictionary (MTLManipulationAdditions)
|
||||
|
||||
// Merges the keys and values from the given dictionary into the receiver. If
|
||||
// both the receiver and `dictionary` have a given key, the value from
|
||||
// `dictionary` is used.
|
||||
//
|
||||
// Returns a new dictionary containing the entries of the receiver combined with
|
||||
// those of `dictionary`.
|
||||
- (NSDictionary *)mtl_dictionaryByAddingEntriesFromDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
// Creates a new dictionary with all the entries for the given keys removed from
|
||||
// the receiver.
|
||||
- (NSDictionary *)mtl_dictionaryByRemovingEntriesWithKeys:(NSSet *)keys;
|
||||
|
||||
@end
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// NSObject+MTLComparisonAdditions.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Josh Vera on 10/26/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
// Portions copyright (c) 2011 Bitswift. All rights reserved.
|
||||
// See the LICENSE file for more information.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Returns whether both objects are identical or equal via -isEqual:
|
||||
BOOL MTLEqualObjects(id obj1, id obj2);
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// NSValueTransformer+MTLInversionAdditions.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-05-18.
|
||||
// Copyright (c) 2013 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSValueTransformer (MTLInversionAdditions)
|
||||
|
||||
// Flips the direction of the receiver's transformation, such that
|
||||
// -transformedValue: will become -reverseTransformedValue:, and vice-versa.
|
||||
//
|
||||
// The receiver must allow reverse transformation.
|
||||
//
|
||||
// Returns an inverted transformer.
|
||||
- (NSValueTransformer *)mtl_invertedTransformer;
|
||||
|
||||
@end
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// NSValueTransformer+MTLPredefinedTransformerAdditions.h
|
||||
// Mantle
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-09-27.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// The name for a value transformer that converts strings into URLs and back.
|
||||
extern NSString * const MTLURLValueTransformerName;
|
||||
|
||||
// Ensure an NSNumber is backed by __NSCFBoolean/CFBooleanRef
|
||||
//
|
||||
// NSJSONSerialization, and likely other serialization libraries, ordinarily
|
||||
// serialize NSNumbers as numbers, and thus booleans would be serialized as
|
||||
// 0/1. The exception is when the NSNumber is backed by __NSCFBoolean, which,
|
||||
// though very much an implementation detail, is detected and serialized as a
|
||||
// proper boolean.
|
||||
extern NSString * const MTLBooleanValueTransformerName;
|
||||
|
||||
@interface NSValueTransformer (MTLPredefinedTransformerAdditions)
|
||||
|
||||
// Creates a reversible transformer to convert a JSON dictionary into a MTLModel
|
||||
// object, and vice-versa.
|
||||
//
|
||||
// modelClass - The MTLModel subclass to attempt to parse from the JSON. This
|
||||
// class must conform to <MTLJSONSerializing>. This argument must
|
||||
// not be nil.
|
||||
//
|
||||
// Returns a reversible transformer which uses MTLJSONAdapter for transforming
|
||||
// values back and forth.
|
||||
+ (NSValueTransformer *)mtl_JSONDictionaryTransformerWithModelClass:(Class)modelClass;
|
||||
|
||||
// Creates a reversible transformer to convert an array of JSON dictionaries
|
||||
// into an array of MTLModel objects, and vice-versa.
|
||||
//
|
||||
// modelClass - The MTLModel subclass to attempt to parse from each JSON
|
||||
// dictionary. This class must conform to <MTLJSONSerializing>.
|
||||
// This argument must not be nil.
|
||||
//
|
||||
// Returns a reversible transformer which uses MTLJSONAdapter for transforming
|
||||
// array elements back and forth.
|
||||
+ (NSValueTransformer *)mtl_JSONArrayTransformerWithModelClass:(Class)modelClass;
|
||||
|
||||
// A reversible value transformer to transform between the keys and objects of a
|
||||
// dictionary.
|
||||
//
|
||||
// dictionary - The dictionary whose keys and values should be
|
||||
// transformed between. This argument must not be nil.
|
||||
// defaultValue - The result to fall back to, in case no key matching the
|
||||
// input value was found during a forward transformation.
|
||||
// reverseDefaultValue - The result to fall back to, in case no value matching
|
||||
// the input value was found during a reverse
|
||||
// transformation.
|
||||
//
|
||||
// Can for example be used for transforming between enum values and their string
|
||||
// representation.
|
||||
//
|
||||
// NSValueTransformer *valueTransformer = [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
|
||||
// @"foo": @(EnumDataTypeFoo),
|
||||
// @"bar": @(EnumDataTypeBar),
|
||||
// } defaultValue: @(EnumDataTypeUndefined) reverseDefaultValue: @"undefined"];
|
||||
//
|
||||
// Returns a transformer that will map from keys to values in dictionary
|
||||
// for forward transformation, and from values to keys for reverse
|
||||
// transformations. If no matching key or value can be found, the respective
|
||||
// default value is returned.
|
||||
+ (NSValueTransformer *)mtl_valueMappingTransformerWithDictionary:(NSDictionary *)dictionary defaultValue:(id)defaultValue reverseDefaultValue:(id)reverseDefaultValue;
|
||||
|
||||
// Returns a value transformer created by calling
|
||||
// `+mtl_valueMappingTransformerWithDictionary:defaultValue:reverseDefaultValue:`
|
||||
// with a default value of `nil` and a reverse default value of `nil`.
|
||||
+ (NSValueTransformer *)mtl_valueMappingTransformerWithDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSValueTransformer (UnavailableMTLPredefinedTransformerAdditions)
|
||||
|
||||
+ (NSValueTransformer *)mtl_externalRepresentationTransformerWithModelClass:(Class)modelClass __attribute__((deprecated("Replaced by +mtl_JSONDictionaryTransformerWithModelClass:")));
|
||||
+ (NSValueTransformer *)mtl_externalRepresentationArrayTransformerWithModelClass:(Class)modelClass __attribute__((deprecated("Replaced by +mtl_JSONArrayTransformerWithModelClass:")));
|
||||
|
||||
@end
|
||||
Executable
BIN
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
framework module Mantle {
|
||||
umbrella header "Mantle.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?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>BuildMachineOSBuild</key>
|
||||
<string>16F2104</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Mantle</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.mantle.Mantle</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Mantle</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string></string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>8E3004b</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>16E185</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.12</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0833</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>8E3004b</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
<?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>files</key>
|
||||
<dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<data>
|
||||
5gOxsGY3VGVvebnR8eP1z3BDrls=
|
||||
</data>
|
||||
</dict>
|
||||
<key>files2</key>
|
||||
<dict>
|
||||
<key>Headers/MTLJSONAdapter.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
HIypP9pGI7Wk97RKy3thGsC7LSg=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
YhksDrIzFk7TQllZD4eF2qEubtCfFlDrddfBeJEvpoc=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/MTLManagedObjectAdapter.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
tjTyEh7TlZBAXcyaY1I1SmXHbE4=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
WxYp7uqr+H3R5UnwMpDSHlxSt7MaAPhQob/BDXlWbVI=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/MTLModel+NSCoding.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
2tdybjH/C8ZU8VcZrX4oliXkuM8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
QuzPKm76puk2/uZN0s9Jqn7g5AFF6xFbnBsN450IXfc=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/MTLModel.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
9c2w4hZo7o/vLu/3TaZ2vXXjwxc=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
2JEc6fCePU0odH+dDY8947O0/wS1Ey96Vuun1HCP564=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/MTLValueTransformer.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
m6veNGSL5/Kif8yDXH5zU+2CaK8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
AnO4t0Ur5lBz44l14XRXgB3g6JEiUE1JBfPdvV+H7fA=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/Mantle.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
TYIHtg+qqzl8yG0InqTB7Oyr63A=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
YKN3UxwC4VdZ19A/VN8Ndpdf5PVHbZlYMb9K1etJKKg=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/NSArray+MTLManipulationAdditions.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
JkGLHSSIKlmCin6flems7m4llvE=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
fQTG4WuuoxdNhgn6wDU29iy18nNqFwlK9F45NRbpD6w=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/NSDictionary+MTLManipulationAdditions.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
bqGP1ngOZeJ16AY5Vi/2ObZCmlw=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
0Jf2Da8WIPOoV0BkwF1D69nsUwbgypRDMVoedgtuJ24=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/NSObject+MTLComparisonAdditions.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
2oBONz9+tBIWVjYGxYBIBT0b0n8=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
86zL7jX96Spa7xT5HQJ+aYmtXD1oKLUrU4JtlO2cIqU=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/NSValueTransformer+MTLInversionAdditions.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
sxXToKdUH8yMt45aTwPeM3hKmjs=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
6xpkrCw6Z2B3EOFKGMBi6o78Hau0v0zsfGJeQMAk354=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Headers/NSValueTransformer+MTLPredefinedTransformerAdditions.h</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
HhBKFxBsCIBfvD6Rm6d3yjVk540=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
QAg6gyiNeq39mqhxZ/OttPualO+F1x2E99sNuyNE1ZE=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Modules/module.modulemap</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
Bey/HDj6mfWAMaHrd42tLo2G6HA=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
kjfaRu6wmQ32w9Db79wuDbkttAjOMC2jrLbtCbIzrSM=
|
||||
</data>
|
||||
</dict>
|
||||
<key>Resources/Info.plist</key>
|
||||
<dict>
|
||||
<key>hash</key>
|
||||
<data>
|
||||
5gOxsGY3VGVvebnR8eP1z3BDrls=
|
||||
</data>
|
||||
<key>hash2</key>
|
||||
<data>
|
||||
e6zwAZD1Y0oZhr5vq5p/9RM9J6llqdDsogAev9b3loI=
|
||||
</data>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^Resources/</key>
|
||||
<true/>
|
||||
<key>^Resources/.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Resources/Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^version.plist$</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>rules2</key>
|
||||
<dict>
|
||||
<key>.*\.dSYM($|/)</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>11</real>
|
||||
</dict>
|
||||
<key>^(.*/)?\.DS_Store$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>2000</real>
|
||||
</dict>
|
||||
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
|
||||
<dict>
|
||||
<key>nested</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
<key>^Info\.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^PkgInfo$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^Resources/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/</key>
|
||||
<dict>
|
||||
<key>optional</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1000</real>
|
||||
</dict>
|
||||
<key>^Resources/.*\.lproj/locversion.plist$</key>
|
||||
<dict>
|
||||
<key>omit</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>1100</real>
|
||||
</dict>
|
||||
<key>^Resources/Base\.lproj/</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>1010</real>
|
||||
</dict>
|
||||
<key>^[^/]+$</key>
|
||||
<dict>
|
||||
<key>nested</key>
|
||||
<true/>
|
||||
<key>weight</key>
|
||||
<real>10</real>
|
||||
</dict>
|
||||
<key>^embedded\.provisionprofile$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
<key>^version\.plist$</key>
|
||||
<dict>
|
||||
<key>weight</key>
|
||||
<real>20</real>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
+1
@@ -0,0 +1 @@
|
||||
A
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/Headers
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/Modules
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/ReactiveCocoa
|
||||
+1
@@ -0,0 +1 @@
|
||||
Versions/Current/Resources
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// EXTKeyPathCoding.h
|
||||
// extobjc
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 19.06.12.
|
||||
// Copyright (C) 2012 Justin Spahr-Summers.
|
||||
// Released under the MIT license.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "metamacros.h"
|
||||
|
||||
/**
|
||||
* \@keypath allows compile-time verification of key paths. Given a real object
|
||||
* receiver and key path:
|
||||
*
|
||||
* @code
|
||||
|
||||
NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String);
|
||||
// => @"lowercaseString.UTF8String"
|
||||
|
||||
NSString *versionPath = @keypath(NSObject, version);
|
||||
// => @"version"
|
||||
|
||||
NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString);
|
||||
// => @"lowercaseString"
|
||||
|
||||
* @endcode
|
||||
*
|
||||
* ... the macro returns an \c NSString containing all but the first path
|
||||
* component or argument (e.g., @"lowercaseString.UTF8String", @"version").
|
||||
*
|
||||
* In addition to simply creating a key path, this macro ensures that the key
|
||||
* path is valid at compile-time (causing a syntax error if not), and supports
|
||||
* refactoring, such that changing the name of the property will also update any
|
||||
* uses of \@keypath.
|
||||
*/
|
||||
#define keypath(...) \
|
||||
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
|
||||
|
||||
#define keypath1(PATH) \
|
||||
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
|
||||
|
||||
#define keypath2(OBJ, PATH) \
|
||||
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
|
||||
|
||||
/**
|
||||
* \@collectionKeypath allows compile-time verification of key paths across collections NSArray/NSSet etc. Given a real object
|
||||
* receiver, collection object receiver and related keypaths:
|
||||
*
|
||||
* @code
|
||||
|
||||
NSString *employessFirstNamePath = @collectionKeypath(department.employees, Employee.new, firstName)
|
||||
// => @"employees.firstName"
|
||||
|
||||
NSString *employessFirstNamePath = @collectionKeypath(Department.new, employees, Employee.new, firstName)
|
||||
// => @"employees.firstName"
|
||||
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
#define collectionKeypath(...) \
|
||||
metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__))
|
||||
|
||||
#define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String])
|
||||
|
||||
#define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(OBJ, PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String])
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// EXTScope.h
|
||||
// extobjc
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2011-05-04.
|
||||
// Copyright (C) 2012 Justin Spahr-Summers.
|
||||
// Released under the MIT license.
|
||||
//
|
||||
|
||||
#import "metamacros.h"
|
||||
|
||||
/**
|
||||
* \@onExit defines some code to be executed when the current scope exits. The
|
||||
* code must be enclosed in braces and terminated with a semicolon, and will be
|
||||
* executed regardless of how the scope is exited, including from exceptions,
|
||||
* \c goto, \c return, \c break, and \c continue.
|
||||
*
|
||||
* Provided code will go into a block to be executed later. Keep this in mind as
|
||||
* it pertains to memory management, restrictions on assignment, etc. Because
|
||||
* the code is used within a block, \c return is a legal (though perhaps
|
||||
* confusing) way to exit the cleanup block early.
|
||||
*
|
||||
* Multiple \@onExit statements in the same scope are executed in reverse
|
||||
* lexical order. This helps when pairing resource acquisition with \@onExit
|
||||
* statements, as it guarantees teardown in the opposite order of acquisition.
|
||||
*
|
||||
* @note This statement cannot be used within scopes defined without braces
|
||||
* (like a one line \c if). In practice, this is not an issue, since \@onExit is
|
||||
* a useless construct in such a case anyways.
|
||||
*/
|
||||
#define onExit \
|
||||
rac_keywordify \
|
||||
__strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^
|
||||
|
||||
/**
|
||||
* Creates \c __weak shadow variables for each of the variables provided as
|
||||
* arguments, which can later be made strong again with #strongify.
|
||||
*
|
||||
* This is typically used to weakly reference variables in a block, but then
|
||||
* ensure that the variables stay alive during the actual execution of the block
|
||||
* (if they were live upon entry).
|
||||
*
|
||||
* See #strongify for an example of usage.
|
||||
*/
|
||||
#define weakify(...) \
|
||||
rac_keywordify \
|
||||
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Like #weakify, but uses \c __unsafe_unretained instead, for targets or
|
||||
* classes that do not support weak references.
|
||||
*/
|
||||
#define unsafeify(...) \
|
||||
rac_keywordify \
|
||||
metamacro_foreach_cxt(rac_weakify_,, __unsafe_unretained, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Strongly references each of the variables provided as arguments, which must
|
||||
* have previously been passed to #weakify.
|
||||
*
|
||||
* The strong references created will shadow the original variable names, such
|
||||
* that the original names can be used without issue (and a significantly
|
||||
* reduced risk of retain cycles) in the current scope.
|
||||
*
|
||||
* @code
|
||||
|
||||
id foo = [[NSObject alloc] init];
|
||||
id bar = [[NSObject alloc] init];
|
||||
|
||||
@weakify(foo, bar);
|
||||
|
||||
// this block will not keep 'foo' or 'bar' alive
|
||||
BOOL (^matchesFooOrBar)(id) = ^ BOOL (id obj){
|
||||
// but now, upon entry, 'foo' and 'bar' will stay alive until the block has
|
||||
// finished executing
|
||||
@strongify(foo, bar);
|
||||
|
||||
return [foo isEqual:obj] || [bar isEqual:obj];
|
||||
};
|
||||
|
||||
* @endcode
|
||||
*/
|
||||
#define strongify(...) \
|
||||
rac_keywordify \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
/*** implementation details follow ***/
|
||||
typedef void (^rac_cleanupBlock_t)();
|
||||
|
||||
static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) {
|
||||
(*block)();
|
||||
}
|
||||
|
||||
#define rac_weakify_(INDEX, CONTEXT, VAR) \
|
||||
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
|
||||
|
||||
#define rac_strongify_(INDEX, VAR) \
|
||||
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
|
||||
|
||||
// Details about the choice of backing keyword:
|
||||
//
|
||||
// The use of @try/@catch/@finally can cause the compiler to suppress
|
||||
// return-type warnings.
|
||||
// The use of @autoreleasepool {} is not optimized away by the compiler,
|
||||
// resulting in superfluous creation of autorelease pools.
|
||||
//
|
||||
// Since neither option is perfect, and with no other alternatives, the
|
||||
// compromise is to use @autorelease in DEBUG builds to maintain compiler
|
||||
// analysis, and to use @try/@catch otherwise to avoid insertion of unnecessary
|
||||
// autorelease pools.
|
||||
#if DEBUG
|
||||
#define rac_keywordify autoreleasepool {}
|
||||
#else
|
||||
#define rac_keywordify try {} @catch (...) {}
|
||||
#endif
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// NSArray+RACSequenceAdditions.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-10-29.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSequence;
|
||||
|
||||
@interface NSArray (RACSequenceAdditions)
|
||||
|
||||
/// Creates and returns a sequence corresponding to the receiver.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
|
||||
|
||||
@end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// NSControl+RACCommandSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 3/3/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class RACCommand;
|
||||
|
||||
@interface NSControl (RACCommandSupport)
|
||||
|
||||
/// Sets the control's command. When the control is clicked, the command is
|
||||
/// executed with the sender of the event. The control's enabledness is bound
|
||||
/// to the command's `canExecute`.
|
||||
///
|
||||
/// Note: this will reset the control's target and action.
|
||||
@property (nonatomic, strong) RACCommand *rac_command;
|
||||
|
||||
@end
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// NSControl+RACTextSignalSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-03-08.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSControl (RACTextSignalSupport)
|
||||
|
||||
/// Observes a text-based control for changes.
|
||||
///
|
||||
/// Using this method on a control without editable text is considered undefined
|
||||
/// behavior.
|
||||
///
|
||||
/// Returns a signal which sends the current string value of the receiver, then
|
||||
/// the new value any time it changes.
|
||||
- (RACSignal *)rac_textSignal;
|
||||
|
||||
@end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// NSData+RACSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 5/11/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACScheduler;
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSData (RACSupport)
|
||||
|
||||
// Read the data at the URL using -[NSData initWithContentsOfURL:options:error:].
|
||||
// Sends the data or the error.
|
||||
//
|
||||
// scheduler - cannot be nil.
|
||||
+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler;
|
||||
|
||||
@end
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// NSDictionary+RACSequenceAdditions.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-10-29.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSequence;
|
||||
|
||||
@interface NSDictionary (RACSequenceAdditions)
|
||||
|
||||
/// Creates and returns a sequence of RACTuple key/value pairs. The key will be
|
||||
/// the first element in the tuple, and the value will be the second.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
|
||||
|
||||
/// Creates and returns a sequence corresponding to the keys in the receiver.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_keySequence;
|
||||
|
||||
/// Creates and returns a sequence corresponding to the values in the receiver.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_valueSequence;
|
||||
|
||||
@end
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// NSEnumerator+RACSequenceAdditions.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Uri Baghin on 07/01/2013.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSequence;
|
||||
|
||||
@interface NSEnumerator (RACSequenceAdditions)
|
||||
|
||||
/// Creates and returns a sequence corresponding to the receiver.
|
||||
///
|
||||
/// The receiver is exhausted lazily as the sequence is enumerated.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
|
||||
|
||||
@end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// NSFileHandle+RACSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 5/10/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSFileHandle (RACSupport)
|
||||
|
||||
// Read any available data in the background and send it. Completes when data
|
||||
// length is <= 0.
|
||||
- (RACSignal *)rac_readInBackground;
|
||||
|
||||
@end
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// NSIndexSet+RACSequenceAdditions.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Sergey Gavrilyuk on 12/17/13.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSequence;
|
||||
|
||||
@interface NSIndexSet (RACSequenceAdditions)
|
||||
|
||||
/// Creates and returns a sequence of indexes (as `NSNumber`s) corresponding to
|
||||
/// the receiver.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
|
||||
|
||||
@end
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// NSNotificationCenter+RACSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 5/10/12.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSNotificationCenter (RACSupport)
|
||||
|
||||
// Sends the NSNotification every time the notification is posted.
|
||||
- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object;
|
||||
|
||||
@end
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// NSObject+RACAppKitBindings.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 4/17/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class RACChannelTerminal;
|
||||
|
||||
@interface NSObject (RACAppKitBindings)
|
||||
|
||||
/// Invokes -rac_channelToBinding:options: without any options.
|
||||
- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding;
|
||||
|
||||
/// Applies a Cocoa binding to the receiver, then exposes a RACChannel-based
|
||||
/// interface for manipulating it.
|
||||
///
|
||||
/// Creating two of the same bindings on the same object will result in undefined
|
||||
/// behavior.
|
||||
///
|
||||
/// binding - The name of the binding. This must not be nil.
|
||||
/// options - Any options to pass to Cocoa Bindings. This may be nil.
|
||||
///
|
||||
/// Returns a RACChannelTerminal which will send future values from the receiver,
|
||||
/// and update the receiver when values are sent to the terminal.
|
||||
- (RACChannelTerminal *)rac_channelToBinding:(NSString *)binding options:(NSDictionary *)options;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSObject (RACAppKitBindingsDeprecated)
|
||||
|
||||
- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath __attribute__((deprecated("Use -rac_bind:options: instead")));
|
||||
- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath nilValue:(id)nilValue __attribute__((deprecated("Use -rac_bind:options: instead")));
|
||||
- (void)rac_bind:(NSString *)binding toObject:(id)object withKeyPath:(NSString *)keyPath transform:(id (^)(id value))transformBlock __attribute__((deprecated("Use -rac_bind:options: instead")));
|
||||
- (void)rac_bind:(NSString *)binding toObject:(id)object withNegatedKeyPath:(NSString *)keyPath __attribute__((deprecated("Use -rac_bind:options: instead")));
|
||||
|
||||
@end
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// NSObject+RACDeallocating.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Kazuo Koga on 2013/03/15.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACCompoundDisposable;
|
||||
@class RACDisposable;
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSObject (RACDeallocating)
|
||||
|
||||
/// The compound disposable which will be disposed of when the receiver is
|
||||
/// deallocated.
|
||||
@property (atomic, readonly, strong) RACCompoundDisposable *rac_deallocDisposable;
|
||||
|
||||
/// Returns a signal that will complete immediately before the receiver is fully
|
||||
/// deallocated. If already deallocated when the signal is subscribed to,
|
||||
/// a `completed` event will be sent immediately.
|
||||
- (RACSignal *)rac_willDeallocSignal;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSObject (RACDeallocatingDeprecated)
|
||||
|
||||
- (RACSignal *)rac_didDeallocSignal __attribute__((deprecated("Use -rac_willDeallocSignal")));
|
||||
|
||||
- (void)rac_addDeallocDisposable:(RACDisposable *)disposable __attribute__((deprecated("Add disposables to -rac_deallocDisposable instead")));
|
||||
|
||||
@end
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// NSObject+RACLifting.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 10/13/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSObject (RACLifting)
|
||||
|
||||
/// Lifts the selector on the receiver into the reactive world. The selector will
|
||||
/// be invoked whenever any signal argument sends a value, but only after each
|
||||
/// signal has sent an initial value.
|
||||
///
|
||||
/// It will replay the most recently sent value to new subscribers.
|
||||
///
|
||||
/// This does not support C arrays or unions.
|
||||
///
|
||||
/// selector - The selector on self to invoke.
|
||||
/// firstSignal - The signal corresponding to the first method argument. This
|
||||
/// must not be nil.
|
||||
/// ... - A list of RACSignals corresponding to the remaining arguments.
|
||||
/// There must be a non-nil signal for each method argument.
|
||||
///
|
||||
/// Examples
|
||||
///
|
||||
/// [button rac_liftSelector:@selector(setTitleColor:forState:) withSignals:textColorSignal, [RACSignal return:@(UIControlStateNormal)], nil];
|
||||
///
|
||||
/// Returns a signal which sends the return value from each invocation of the
|
||||
/// selector. If the selector returns void, it instead sends RACUnit.defaultUnit.
|
||||
/// It completes only after all the signal arguments complete.
|
||||
- (RACSignal *)rac_liftSelector:(SEL)selector withSignals:(RACSignal *)firstSignal, ... NS_REQUIRES_NIL_TERMINATION;
|
||||
|
||||
/// Like -rac_liftSelector:withSignals:, but accepts an array instead of
|
||||
/// a variadic list of arguments.
|
||||
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals;
|
||||
|
||||
/// Like -rac_liftSelector:withSignals:, but accepts a signal sending tuples of
|
||||
/// arguments instead of a variadic list of arguments.
|
||||
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalOfArguments:(RACSignal *)arguments;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSObject (RACLiftingDeprecated)
|
||||
|
||||
- (RACSignal *)rac_liftSelector:(SEL)selector withObjects:(id)arg, ... __attribute__((deprecated("Use -rac_liftSelector:withSignals: instead")));
|
||||
- (RACSignal *)rac_liftSelector:(SEL)selector withObjectsFromArray:(NSArray *)args __attribute__((deprecated("Use -rac_liftSelector:withSignalsFromArray: instead")));
|
||||
- (RACSignal *)rac_liftBlock:(id)block withArguments:(id)arg, ... NS_REQUIRES_NIL_TERMINATION __attribute__((deprecated("Use +combineLatest:reduce: instead")));
|
||||
- (RACSignal *)rac_liftBlock:(id)block withArgumentsFromArray:(NSArray *)args __attribute__((deprecated("Use +combineLatest:reduce: instead")));
|
||||
|
||||
@end
|
||||
|
||||
@interface NSObject (RACLiftingUnavailable)
|
||||
|
||||
- (instancetype)rac_lift __attribute__((unavailable("Use -rac_liftSelector:withSignals: instead")));
|
||||
|
||||
@end
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// NSObject+RACPropertySubscribing.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 3/2/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "EXTKeyPathCoding.h"
|
||||
#import "metamacros.h"
|
||||
|
||||
/// Creates a signal which observes `KEYPATH` on `TARGET` for changes.
|
||||
///
|
||||
/// In either case, the observation continues until `TARGET` _or self_ is
|
||||
/// deallocated. If any intermediate object is deallocated instead, it will be
|
||||
/// assumed to have been set to nil.
|
||||
///
|
||||
/// Make sure to `@strongify(self)` when using this macro within a block! The
|
||||
/// macro will _always_ reference `self`, which can silently introduce a retain
|
||||
/// cycle within a block. As a result, you should make sure that `self` is a weak
|
||||
/// reference (e.g., created by `@weakify` and `@strongify`) before the
|
||||
/// expression that uses `RACObserve`.
|
||||
///
|
||||
/// Examples
|
||||
///
|
||||
/// // Observes self, and doesn't stop until self is deallocated.
|
||||
/// RACSignal *selfSignal = RACObserve(self, arrayController.items);
|
||||
///
|
||||
/// // Observes the array controller, and stops when self _or_ the array
|
||||
/// // controller is deallocated.
|
||||
/// RACSignal *arrayControllerSignal = RACObserve(self.arrayController, items);
|
||||
///
|
||||
/// // Observes obj.arrayController, and stops when self _or_ the array
|
||||
/// // controller is deallocated.
|
||||
/// RACSignal *signal2 = RACObserve(obj.arrayController, items);
|
||||
///
|
||||
/// @weakify(self);
|
||||
/// RACSignal *signal3 = [anotherSignal flattenMap:^(NSArrayController *arrayController) {
|
||||
/// // Avoids a retain cycle because of RACObserve implicitly referencing
|
||||
/// // self.
|
||||
/// @strongify(self);
|
||||
/// return RACObserve(arrayController, items);
|
||||
/// }];
|
||||
///
|
||||
/// Returns a signal which sends the current value of the key path on
|
||||
/// subscription, then sends the new value every time it changes, and sends
|
||||
/// completed if self or observer is deallocated.
|
||||
#define RACObserve(TARGET, KEYPATH) \
|
||||
({ \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
|
||||
__weak id target_ = (TARGET); \
|
||||
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
|
||||
_Pragma("clang diagnostic pop") \
|
||||
})
|
||||
|
||||
@class RACDisposable;
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSObject (RACPropertySubscribing)
|
||||
|
||||
/// Creates a signal to observe the value at the given key path.
|
||||
///
|
||||
/// The initial value is sent on subscription, the subsequent values are sent
|
||||
/// from whichever thread the change occured on, even if it doesn't have a valid
|
||||
/// scheduler.
|
||||
///
|
||||
/// Returns a signal that immediately sends the receiver's current value at the
|
||||
/// given keypath, then any changes thereafter.
|
||||
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer;
|
||||
|
||||
/// Creates a signal to observe the changes of the given key path.
|
||||
///
|
||||
/// The initial value is sent on subscription, the subsequent values are sent
|
||||
/// from whichever thread the change occured on, even if it doesn't have a valid
|
||||
/// scheduler.
|
||||
///
|
||||
/// Returns a signal that sends tuples containing the current value at the key
|
||||
/// path and the change dictionary for each KVO callback.
|
||||
- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer;
|
||||
|
||||
@end
|
||||
|
||||
#define RACAble(...) \
|
||||
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
|
||||
(_RACAbleObject(self, __VA_ARGS__)) \
|
||||
(_RACAbleObject(__VA_ARGS__))
|
||||
|
||||
#define _RACAbleObject(object, property) [object rac_signalForKeyPath:@keypath(object, property) observer:self]
|
||||
|
||||
#define RACAbleWithStart(...) \
|
||||
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
|
||||
(_RACAbleWithStartObject(self, __VA_ARGS__)) \
|
||||
(_RACAbleWithStartObject(__VA_ARGS__))
|
||||
|
||||
#define _RACAbleWithStartObject(object, property) [object rac_signalWithStartingValueForKeyPath:@keypath(object, property) observer:self]
|
||||
|
||||
@interface NSObject (RACPropertySubscribingDeprecated)
|
||||
|
||||
+ (RACSignal *)rac_signalFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
|
||||
+ (RACSignal *)rac_signalWithStartingValueFor:(NSObject *)object keyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
|
||||
+ (RACSignal *)rac_signalWithChangesFor:(NSObject *)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesAndChangesForKeyPath:options:observer: instead.")));
|
||||
- (RACSignal *)rac_signalForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
|
||||
- (RACSignal *)rac_signalWithStartingValueForKeyPath:(NSString *)keyPath observer:(NSObject *)observer __attribute__((deprecated("Use -rac_valuesForKeyPath:observer: or RACObserve() instead.")));
|
||||
- (RACDisposable *)rac_deriveProperty:(NSString *)keyPath from:(RACSignal *)signal __attribute__((deprecated("Use -[RACSignal setKeyPath:onObject:] instead")));
|
||||
|
||||
@end
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// NSObject+RACSelectorSignal.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 3/18/13.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
/// The domain for any errors originating from -rac_signalForSelector:.
|
||||
extern NSString * const RACSelectorSignalErrorDomain;
|
||||
|
||||
/// -rac_signalForSelector: was going to add a new method implementation for
|
||||
/// `selector`, but another thread added an implementation before it was able to.
|
||||
///
|
||||
/// This will _not_ occur for cases where a method implementation exists before
|
||||
/// -rac_signalForSelector: is invoked.
|
||||
extern const NSInteger RACSelectorSignalErrorMethodSwizzlingRace;
|
||||
|
||||
@interface NSObject (RACSelectorSignal)
|
||||
|
||||
/// Creates a signal associated with the receiver, which will send a tuple of the
|
||||
/// method's arguments each time the given selector is invoked.
|
||||
///
|
||||
/// If the selector is already implemented on the receiver, the existing
|
||||
/// implementation will be invoked _before_ the signal fires.
|
||||
///
|
||||
/// If the selector is not yet implemented on the receiver, the injected
|
||||
/// implementation will have a `void` return type and accept only object
|
||||
/// arguments. Invoking the added implementation with non-object values, or
|
||||
/// expecting a return value, will result in undefined behavior.
|
||||
///
|
||||
/// This is useful for changing an event or delegate callback into a signal. For
|
||||
/// example, on an NSView:
|
||||
///
|
||||
/// [[view rac_signalForSelector:@selector(mouseDown:)] subscribeNext:^(RACTuple *args) {
|
||||
/// NSEvent *event = args.first;
|
||||
/// NSLog(@"mouse button pressed: %@", event);
|
||||
/// }];
|
||||
///
|
||||
/// selector - The selector for whose invocations are to be observed. If it
|
||||
/// doesn't exist, it will be implemented to accept object arguments
|
||||
/// and return void. This cannot have C arrays or unions as arguments
|
||||
/// or C arrays, unions, structs, complex or vector types as return
|
||||
/// type.
|
||||
///
|
||||
/// Returns a signal which will send a tuple of arguments upon each invocation of
|
||||
/// the selector, then completes when the receiver is deallocated. `next` events
|
||||
/// will be sent synchronously from the thread that invoked the method. If
|
||||
/// a runtime call fails, the signal will send an error in the
|
||||
/// RACSelectorSignalErrorDomain.
|
||||
- (RACSignal *)rac_signalForSelector:(SEL)selector;
|
||||
|
||||
/// Behaves like -rac_signalForSelector:, but if the selector is not yet
|
||||
/// implemented on the receiver, its method signature is looked up within
|
||||
/// `protocol`, and may accept non-object arguments.
|
||||
///
|
||||
/// If the selector is not yet implemented and has a return value, the injected
|
||||
/// method will return all zero bits (equal to `nil`, `NULL`, 0, 0.0f, etc.).
|
||||
///
|
||||
/// selector - The selector for whose invocations are to be observed. If it
|
||||
/// doesn't exist, it will be implemented using information from
|
||||
/// `protocol`, and may accept non-object arguments and return
|
||||
/// a value. This cannot have C arrays or unions as arguments or
|
||||
/// return type.
|
||||
/// protocol - The protocol in which `selector` is declared. This will be used
|
||||
/// for type information if the selector is not already implemented on
|
||||
/// the receiver. This must not be `NULL`, and `selector` must exist
|
||||
/// in this protocol.
|
||||
///
|
||||
/// Returns a signal which will send a tuple of arguments on each invocation of
|
||||
/// the selector, or an error in RACSelectorSignalErrorDomain if a runtime
|
||||
/// call fails.
|
||||
- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol;
|
||||
|
||||
@end
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// NSOrderedSet+RACSequenceAdditions.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-10-29.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSequence;
|
||||
|
||||
@interface NSOrderedSet (RACSequenceAdditions)
|
||||
|
||||
/// Creates and returns a sequence corresponding to the receiver.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
|
||||
|
||||
@end
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// NSSet+RACSequenceAdditions.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-10-29.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSequence;
|
||||
|
||||
@interface NSSet (RACSequenceAdditions)
|
||||
|
||||
/// Creates and returns a sequence corresponding to the receiver.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
|
||||
|
||||
@end
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// NSString+RACSequenceAdditions.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2012-10-29.
|
||||
// Copyright (c) 2012 GitHub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSequence;
|
||||
|
||||
@interface NSString (RACSequenceAdditions)
|
||||
|
||||
/// Creates and returns a sequence containing strings corresponding to each
|
||||
/// composed character sequence in the receiver.
|
||||
///
|
||||
/// Mutating the receiver will not affect the sequence after it's been created.
|
||||
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
|
||||
|
||||
@end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// NSString+RACSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 5/11/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACScheduler;
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSString (RACSupport)
|
||||
|
||||
// Reads in the contents of the file using +[NSString stringWithContentsOfURL:usedEncoding:error:].
|
||||
// Note that encoding won't be valid until the signal completes successfully.
|
||||
//
|
||||
// scheduler - cannot be nil.
|
||||
+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler;
|
||||
|
||||
@end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// NSText+RACSignalSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-03-08.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSText (RACSignalSupport)
|
||||
|
||||
/// Returns a signal which sends the current `string` of the receiver, then the
|
||||
/// new value any time it changes.
|
||||
- (RACSignal *)rac_textSignal;
|
||||
|
||||
@end
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// NSURLConnection+RACSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-10-01.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
@interface NSURLConnection (RACSupport)
|
||||
|
||||
// Lazily loads data for the given request in the background.
|
||||
//
|
||||
// request - The URL request to load. This must not be nil.
|
||||
//
|
||||
// Returns a signal which will begin loading the request upon each subscription,
|
||||
// then send a `RACTuple` of the received `NSURLResponse` and downloaded
|
||||
// `NSData`, and complete on a background thread. If any errors occur, the
|
||||
// returned signal will error out.
|
||||
+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request;
|
||||
|
||||
@end
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// NSUserDefaults+RACSupport.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Matt Diephouse on 12/19/13.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACChannelTerminal;
|
||||
|
||||
@interface NSUserDefaults (RACSupport)
|
||||
|
||||
/// Creates and returns a terminal for binding the user defaults key.
|
||||
///
|
||||
/// **Note:** The value in the user defaults is *asynchronously* updated with
|
||||
/// values sent to the channel.
|
||||
///
|
||||
/// key - The user defaults key to create the channel terminal for.
|
||||
///
|
||||
/// Returns a channel terminal that sends the value of the user defaults key
|
||||
/// upon subscription, sends an updated value whenever the default changes, and
|
||||
/// updates the default asynchronously with values it receives.
|
||||
- (RACChannelTerminal *)rac_channelTerminalForKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// RACBehaviorSubject.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 3/16/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACSubject.h"
|
||||
|
||||
/// A behavior subject sends the last value it received when it is subscribed to.
|
||||
@interface RACBehaviorSubject : RACSubject
|
||||
|
||||
/// Creates a new behavior subject with a default value. If it hasn't received
|
||||
/// any values when it gets subscribed to, it sends the default value.
|
||||
+ (instancetype)behaviorSubjectWithDefaultValue:(id)value;
|
||||
|
||||
@end
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
//
|
||||
// RACChannel.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Uri Baghin on 01/01/2013.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACSignal.h"
|
||||
#import "RACSubscriber.h"
|
||||
|
||||
@class RACChannelTerminal;
|
||||
|
||||
/// A two-way channel.
|
||||
///
|
||||
/// Conceptually, RACChannel can be thought of as a bidirectional connection,
|
||||
/// composed of two controllable signals that work in parallel.
|
||||
///
|
||||
/// For example, when connecting between a view and a model:
|
||||
///
|
||||
/// Model View
|
||||
/// `leadingTerminal` ------> `followingTerminal`
|
||||
/// `leadingTerminal` <------ `followingTerminal`
|
||||
///
|
||||
/// The initial value of the model and all future changes to it are _sent on_ the
|
||||
/// `leadingTerminal`, and _received by_ subscribers of the `followingTerminal`.
|
||||
///
|
||||
/// Likewise, whenever the user changes the value of the view, that value is sent
|
||||
/// on the `followingTerminal`, and received in the model from the
|
||||
/// `leadingTerminal`. However, the initial value of the view is not received
|
||||
/// from the `leadingTerminal` (only future changes).
|
||||
@interface RACChannel : NSObject
|
||||
|
||||
/// The terminal which "leads" the channel, by sending its latest value
|
||||
/// immediately to new subscribers of the `followingTerminal`.
|
||||
///
|
||||
/// New subscribers to this terminal will not receive a starting value, but will
|
||||
/// receive all future values that are sent to the `followingTerminal`.
|
||||
@property (nonatomic, strong, readonly) RACChannelTerminal *leadingTerminal;
|
||||
|
||||
/// The terminal which "follows" the lead of the other terminal, only sending
|
||||
/// _future_ values to the subscribers of the `leadingTerminal`.
|
||||
///
|
||||
/// The latest value sent to the `leadingTerminal` (if any) will be sent
|
||||
/// immediately to new subscribers of this terminal, and then all future values
|
||||
/// as well.
|
||||
@property (nonatomic, strong, readonly) RACChannelTerminal *followingTerminal;
|
||||
|
||||
@end
|
||||
|
||||
/// Represents one end of a RACChannel.
|
||||
///
|
||||
/// An terminal is similar to a socket or pipe -- it represents one end of
|
||||
/// a connection (the RACChannel, in this case). Values sent to this terminal
|
||||
/// will _not_ be received by its subscribers. Instead, the values will be sent
|
||||
/// to the subscribers of the RACChannel's _other_ terminal.
|
||||
///
|
||||
/// For example, when using the `followingTerminal`, _sent_ values can only be
|
||||
/// _received_ from the `leadingTerminal`, and vice versa.
|
||||
///
|
||||
/// To make it easy to terminate a RACChannel, `error` and `completed` events
|
||||
/// sent to either terminal will be received by the subscribers of _both_
|
||||
/// terminals.
|
||||
///
|
||||
/// Do not instantiate this class directly. Create a RACChannel instead.
|
||||
@interface RACChannelTerminal : RACSignal <RACSubscriber>
|
||||
|
||||
- (id)init __attribute__((unavailable("Instantiate a RACChannel instead")));
|
||||
|
||||
@end
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// RACCommand.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 3/3/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACSignal;
|
||||
|
||||
/// The domain for errors originating within `RACCommand`.
|
||||
extern NSString * const RACCommandErrorDomain;
|
||||
|
||||
/// -execute: was invoked while the command was disabled.
|
||||
extern const NSInteger RACCommandErrorNotEnabled;
|
||||
|
||||
/// A `userInfo` key for an error, associated with the `RACCommand` that the
|
||||
/// error originated from.
|
||||
///
|
||||
/// This is included only when the error code is `RACCommandErrorNotEnabled`.
|
||||
extern NSString * const RACUnderlyingCommandErrorKey;
|
||||
|
||||
/// A command is a signal triggered in response to some action, typically
|
||||
/// UI-related.
|
||||
@interface RACCommand : NSObject
|
||||
|
||||
/// A signal of the signals returned by successful invocations of -execute:
|
||||
/// (i.e., while the receiver is `enabled`).
|
||||
///
|
||||
/// Errors will be automatically caught upon the inner signals, and sent upon
|
||||
/// `errors` instead. If you _want_ to receive inner errors, use -execute: or
|
||||
/// -[RACSignal materialize].
|
||||
///
|
||||
/// Only executions that begin _after_ subscription will be sent upon this
|
||||
/// signal. All inner signals will arrive upon the main thread.
|
||||
@property (nonatomic, strong, readonly) RACSignal *executionSignals;
|
||||
|
||||
/// A signal of whether this command is currently executing.
|
||||
///
|
||||
/// This will send YES whenever -execute: is invoked and the created signal has
|
||||
/// not yet terminated. Once all executions have terminated, `executing` will
|
||||
/// send NO.
|
||||
///
|
||||
/// This signal will send its current value upon subscription, and then all
|
||||
/// future values on the main thread.
|
||||
@property (nonatomic, strong, readonly) RACSignal *executing;
|
||||
|
||||
/// A signal of whether this command is able to execute.
|
||||
///
|
||||
/// This will send NO if:
|
||||
///
|
||||
/// - The command was created with an `enabledSignal`, and NO is sent upon that
|
||||
/// signal, or
|
||||
/// - `allowsConcurrentExecution` is NO and the command has started executing.
|
||||
///
|
||||
/// Once the above conditions are no longer met, the signal will send YES.
|
||||
///
|
||||
/// This signal will send its current value upon subscription, and then all
|
||||
/// future values on the main thread.
|
||||
@property (nonatomic, strong, readonly) RACSignal *enabled;
|
||||
|
||||
/// Forwards any errors that occur within signals returned by -execute:.
|
||||
///
|
||||
/// When an error occurs on a signal returned from -execute:, this signal will
|
||||
/// send the associated NSError value as a `next` event (since an `error` event
|
||||
/// would terminate the stream).
|
||||
///
|
||||
/// After subscription, this signal will send all future errors on the main
|
||||
/// thread.
|
||||
@property (nonatomic, strong, readonly) RACSignal *errors;
|
||||
|
||||
/// Whether the command allows multiple executions to proceed concurrently.
|
||||
///
|
||||
/// The default value for this property is NO.
|
||||
@property (atomic, assign) BOOL allowsConcurrentExecution;
|
||||
|
||||
/// Invokes -initWithEnabled:signalBlock: with a nil `enabledSignal`.
|
||||
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
|
||||
|
||||
/// Initializes a command that is conditionally enabled.
|
||||
///
|
||||
/// This is the designated initializer for this class.
|
||||
///
|
||||
/// enabledSignal - A signal of BOOLs which indicate whether the command should
|
||||
/// be enabled. `enabled` will be based on the latest value sent
|
||||
/// from this signal. Before any values are sent, `enabled` will
|
||||
/// default to YES. This argument may be nil.
|
||||
/// signalBlock - A block which will map each input value (passed to -execute:)
|
||||
/// to a signal of work. The returned signal will be multicasted
|
||||
/// to a replay subject, sent on `executionSignals`, then
|
||||
/// subscribed to synchronously. Neither the block nor the
|
||||
/// returned signal may be nil.
|
||||
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;
|
||||
|
||||
/// If the receiver is enabled, this method will:
|
||||
///
|
||||
/// 1. Invoke the `signalBlock` given at the time of initialization.
|
||||
/// 2. Multicast the returned signal to a RACReplaySubject.
|
||||
/// 3. Send the multicasted signal on `executionSignals`.
|
||||
/// 4. Subscribe (connect) to the original signal on the main thread.
|
||||
///
|
||||
/// input - The input value to pass to the receiver's `signalBlock`. This may be
|
||||
/// nil.
|
||||
///
|
||||
/// Returns the multicasted signal, after subscription. If the receiver is not
|
||||
/// enabled, returns a signal that will send an error with code
|
||||
/// RACCommandErrorNotEnabled.
|
||||
- (RACSignal *)execute:(id)input;
|
||||
|
||||
@end
|
||||
|
||||
@interface RACCommand (Unavailable)
|
||||
|
||||
@property (atomic, readonly) BOOL canExecute __attribute__((unavailable("Use the 'enabled' signal instead")));
|
||||
|
||||
+ (instancetype)command __attribute__((unavailable("Use -initWithSignalBlock: instead")));
|
||||
+ (instancetype)commandWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead")));
|
||||
- (id)initWithCanExecuteSignal:(RACSignal *)canExecuteSignal __attribute__((unavailable("Use -initWithEnabled:signalBlock: instead")));
|
||||
- (RACSignal *)addSignalBlock:(RACSignal * (^)(id value))signalBlock __attribute__((unavailable("Pass the signalBlock to -initWithSignalBlock: instead")));
|
||||
|
||||
@end
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// RACCompoundDisposable.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 11/30/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACDisposable.h"
|
||||
|
||||
/// A disposable of disposables. When it is disposed, it disposes of all its
|
||||
/// contained disposables.
|
||||
///
|
||||
/// If -addDisposable: is called after the compound disposable has been disposed
|
||||
/// of, the given disposable is immediately disposed. This allows a compound
|
||||
/// disposable to act as a stand-in for a disposable that will be delivered
|
||||
/// asynchronously.
|
||||
@interface RACCompoundDisposable : RACDisposable
|
||||
|
||||
/// Creates and returns a new compound disposable.
|
||||
+ (instancetype)compoundDisposable;
|
||||
|
||||
/// Creates and returns a new compound disposable containing the given
|
||||
/// disposables.
|
||||
+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables;
|
||||
|
||||
/// Adds the given disposable. If the receiving disposable has already been
|
||||
/// disposed of, the given disposable is disposed immediately.
|
||||
///
|
||||
/// This method is thread-safe.
|
||||
///
|
||||
/// disposable - The disposable to add. This may be nil, in which case nothing
|
||||
/// happens.
|
||||
- (void)addDisposable:(RACDisposable *)disposable;
|
||||
|
||||
/// Removes the specified disposable from the compound disposable (regardless of
|
||||
/// its disposed status), or does nothing if it's not in the compound disposable.
|
||||
///
|
||||
/// This is mainly useful for limiting the memory usage of the compound
|
||||
/// disposable for long-running operations.
|
||||
///
|
||||
/// This method is thread-safe.
|
||||
///
|
||||
/// disposable - The disposable to remove. This argument may be nil (to make the
|
||||
/// use of weak references easier).
|
||||
- (void)removeDisposable:(RACDisposable *)disposable;
|
||||
|
||||
@end
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// RACDisposable.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 3/16/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACScopedDisposable;
|
||||
|
||||
/// A disposable encapsulates the work necessary to tear down and cleanup a
|
||||
/// subscription.
|
||||
@interface RACDisposable : NSObject
|
||||
|
||||
/// Whether the receiver has been disposed.
|
||||
///
|
||||
/// Use of this property is discouraged, since it may be set to `YES`
|
||||
/// concurrently at any time.
|
||||
///
|
||||
/// This property is not KVO-compliant.
|
||||
@property (atomic, assign, getter = isDisposed, readonly) BOOL disposed;
|
||||
|
||||
+ (instancetype)disposableWithBlock:(void (^)(void))block;
|
||||
|
||||
/// Performs the disposal work. Can be called multiple times, though subsequent
|
||||
/// calls won't do anything.
|
||||
- (void)dispose;
|
||||
|
||||
/// Returns a new disposable which will dispose of this disposable when it gets
|
||||
/// dealloc'd.
|
||||
- (RACScopedDisposable *)asScopedDisposable;
|
||||
|
||||
@end
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// RACEvent.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Justin Spahr-Summers on 2013-01-07.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/// Describes the type of a RACEvent.
|
||||
///
|
||||
/// RACEventTypeCompleted - A `completed` event.
|
||||
/// RACEventTypeError - An `error` event.
|
||||
/// RACEventTypeNext - A `next` event.
|
||||
typedef enum : NSUInteger {
|
||||
RACEventTypeCompleted,
|
||||
RACEventTypeError,
|
||||
RACEventTypeNext
|
||||
} RACEventType;
|
||||
|
||||
/// Represents an event sent by a RACSignal.
|
||||
///
|
||||
/// This corresponds to the `Notification` class in Rx.
|
||||
@interface RACEvent : NSObject <NSCopying>
|
||||
|
||||
/// Returns a singleton RACEvent representing the `completed` event.
|
||||
+ (instancetype)completedEvent;
|
||||
|
||||
/// Returns a new event of type RACEventTypeError, containing the given error.
|
||||
+ (instancetype)eventWithError:(NSError *)error;
|
||||
|
||||
/// Returns a new event of type RACEventTypeNext, containing the given value.
|
||||
+ (instancetype)eventWithValue:(id)value;
|
||||
|
||||
/// The type of event represented by the receiver.
|
||||
@property (nonatomic, assign, readonly) RACEventType eventType;
|
||||
|
||||
/// Returns whether the receiver is of type RACEventTypeCompleted or
|
||||
/// RACEventTypeError.
|
||||
@property (nonatomic, getter = isFinished, assign, readonly) BOOL finished;
|
||||
|
||||
/// The error associated with an event of type RACEventTypeError. This will be
|
||||
/// nil for all other event types.
|
||||
@property (nonatomic, strong, readonly) NSError *error;
|
||||
|
||||
/// The value associated with an event of type RACEventTypeNext. This will be
|
||||
/// nil for all other event types.
|
||||
@property (nonatomic, strong, readonly) id value;
|
||||
|
||||
@end
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// RACGroupedSignal.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 5/2/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACSubject.h"
|
||||
|
||||
/// A grouped signal is used by -[RACSignal groupBy:transform:].
|
||||
@interface RACGroupedSignal : RACSubject
|
||||
|
||||
/// The key shared by the group.
|
||||
@property (nonatomic, readonly, copy) id<NSCopying> key;
|
||||
|
||||
+ (instancetype)signalWithKey:(id<NSCopying>)key;
|
||||
|
||||
@end
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// RACKVOChannel.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Uri Baghin on 27/12/2012.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACChannel.h"
|
||||
#import "EXTKeyPathCoding.h"
|
||||
#import "metamacros.h"
|
||||
|
||||
/// Creates a RACKVOChannel to the given key path. When the targeted object
|
||||
/// deallocates, the channel will complete.
|
||||
///
|
||||
/// If RACChannelTo() is used as an expression, it returns a RACChannelTerminal that
|
||||
/// can be used to watch the specified property for changes, and set new values
|
||||
/// for it. The terminal will start with the property's current value upon
|
||||
/// subscription.
|
||||
///
|
||||
/// If RACChannelTo() is used on the left-hand side of an assignment, there must a
|
||||
/// RACChannelTerminal on the right-hand side of the assignment. The two will be
|
||||
/// subscribed to one another: the property's value is immediately set to the
|
||||
/// value of the channel terminal on the right-hand side, and subsequent changes
|
||||
/// to either terminal will be reflected on the other.
|
||||
///
|
||||
/// There are two different versions of this macro:
|
||||
///
|
||||
/// - RACChannelTo(TARGET, KEYPATH, NILVALUE) will create a channel to the `KEYPATH`
|
||||
/// of `TARGET`. If the terminal is ever sent a `nil` value, the property will
|
||||
/// be set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object
|
||||
/// properties, but an NSValue should be used for primitive properties, to
|
||||
/// avoid an exception if `nil` is sent (which might occur if an intermediate
|
||||
/// object is set to `nil`).
|
||||
/// - RACChannelTo(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to
|
||||
/// `nil`.
|
||||
///
|
||||
/// Examples
|
||||
///
|
||||
/// RACChannelTerminal *integerChannel = RACChannelTo(self, integerProperty, @42);
|
||||
///
|
||||
/// // Sets self.integerProperty to 5.
|
||||
/// [integerChannel sendNext:@5];
|
||||
///
|
||||
/// // Logs the current value of self.integerProperty, and all future changes.
|
||||
/// [integerChannel subscribeNext:^(id value) {
|
||||
/// NSLog(@"value: %@", value);
|
||||
/// }];
|
||||
///
|
||||
/// // Binds properties to each other, taking the initial value from the right
|
||||
/// side.
|
||||
/// RACChannelTo(view, objectProperty) = RACChannelTo(model, objectProperty);
|
||||
/// RACChannelTo(view, integerProperty, @2) = RACChannelTo(model, integerProperty, @10);
|
||||
#define RACChannelTo(TARGET, ...) \
|
||||
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
|
||||
(RACChannelTo_(TARGET, __VA_ARGS__, nil)) \
|
||||
(RACChannelTo_(TARGET, __VA_ARGS__))
|
||||
|
||||
/// Do not use this directly. Use the RACChannelTo macro above.
|
||||
#define RACChannelTo_(TARGET, KEYPATH, NILVALUE) \
|
||||
[[RACKVOChannel alloc] initWithTarget:(TARGET) keyPath:@keypath(TARGET, KEYPATH) nilValue:(NILVALUE)][@keypath(RACKVOChannel.new, followingTerminal)]
|
||||
|
||||
/// A RACChannel that observes a KVO-compliant key path for changes.
|
||||
@interface RACKVOChannel : RACChannel
|
||||
|
||||
/// Initializes a channel that will observe the given object and key path.
|
||||
///
|
||||
/// The current value of the key path, and future KVO notifications for the given
|
||||
/// key path, will be sent to subscribers of the channel's `followingTerminal`.
|
||||
/// Values sent to the `followingTerminal` will be set at the given key path using
|
||||
/// key-value coding.
|
||||
///
|
||||
/// When the target object deallocates, the channel will complete. Signal errors
|
||||
/// are considered undefined behavior.
|
||||
///
|
||||
/// This is the designated initializer for this class.
|
||||
///
|
||||
/// target - The object to bind to.
|
||||
/// keyPath - The key path to observe and set the value of.
|
||||
/// nilValue - The value to set at the key path whenever a `nil` value is
|
||||
/// received. This may be nil when connecting to object properties, but
|
||||
/// an NSValue should be used for primitive properties, to avoid an
|
||||
/// exception if `nil` is received (which might occur if an intermediate
|
||||
/// object is set to `nil`).
|
||||
- (id)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue;
|
||||
|
||||
- (id)init __attribute__((unavailable("Use -initWithTarget:keyPath:nilValue: instead")));
|
||||
|
||||
@end
|
||||
|
||||
/// Methods needed for the convenience macro. Do not call explicitly.
|
||||
@interface RACKVOChannel (RACChannelTo)
|
||||
|
||||
- (RACChannelTerminal *)objectForKeyedSubscript:(NSString *)key;
|
||||
- (void)setObject:(RACChannelTerminal *)otherTerminal forKeyedSubscript:(NSString *)key;
|
||||
|
||||
@end
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// RACMulticastConnection.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 4/11/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RACDisposable;
|
||||
@class RACSignal;
|
||||
|
||||
/// A multicast connection encapsulates the idea of sharing one subscription to a
|
||||
/// signal to many subscribers. This is most often needed if the subscription to
|
||||
/// the underlying signal involves side-effects or shouldn't be called more than
|
||||
/// once.
|
||||
///
|
||||
/// The multicasted signal is only subscribed to when
|
||||
/// -[RACMulticastConnection connect] is called. Until that happens, no values
|
||||
/// will be sent on `signal`. See -[RACMulticastConnection autoconnect] for how
|
||||
/// -[RACMulticastConnection connect] can be called automatically.
|
||||
///
|
||||
/// Note that you shouldn't create RACMulticastConnection manually. Instead use
|
||||
/// -[RACSignal publish] or -[RACSignal multicast:].
|
||||
@interface RACMulticastConnection : NSObject
|
||||
|
||||
/// The multicasted signal.
|
||||
@property (nonatomic, strong, readonly) RACSignal *signal;
|
||||
|
||||
/// Connect to the underlying signal by subscribing to it. Calling this multiple
|
||||
/// times does nothing but return the existing connection's disposable.
|
||||
///
|
||||
/// Returns the disposable for the subscription to the multicasted signal.
|
||||
- (RACDisposable *)connect;
|
||||
|
||||
/// Connects to the underlying signal when the returned signal is first
|
||||
/// subscribed to, and disposes of the subscription to the multicasted signal
|
||||
/// when the returned signal has no subscribers.
|
||||
///
|
||||
/// If new subscribers show up after being disposed, they'll subscribe and then
|
||||
/// be immediately disposed of. The returned signal will never re-connect to the
|
||||
/// multicasted signal.
|
||||
///
|
||||
/// Returns the autoconnecting signal.
|
||||
- (RACSignal *)autoconnect;
|
||||
|
||||
@end
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// RACQueueScheduler+Subclass.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 6/6/13.
|
||||
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACQueueScheduler.h"
|
||||
#import "RACScheduler+Subclass.h"
|
||||
|
||||
/// An interface for use by GCD queue-based subclasses.
|
||||
///
|
||||
/// See RACScheduler+Subclass.h for subclassing notes.
|
||||
@interface RACQueueScheduler ()
|
||||
|
||||
/// The queue on which blocks are enqueued.
|
||||
#if OS_OBJECT_HAVE_OBJC_SUPPORT
|
||||
@property (nonatomic, strong, readonly) dispatch_queue_t queue;
|
||||
#else
|
||||
// Swift builds with OS_OBJECT_HAVE_OBJC_SUPPORT=0 for Playgrounds and LLDB :(
|
||||
@property (nonatomic, assign, readonly) dispatch_queue_t queue;
|
||||
#endif
|
||||
|
||||
/// Initializes the receiver with the name of the scheduler and the queue which
|
||||
/// the scheduler should use.
|
||||
///
|
||||
/// name - The name of the scheduler. If nil, a default name will be used.
|
||||
/// queue - The queue upon which the receiver should enqueue scheduled blocks.
|
||||
/// This argument must not be NULL.
|
||||
///
|
||||
/// Returns the initialized object.
|
||||
- (id)initWithName:(NSString *)name queue:(dispatch_queue_t)queue;
|
||||
|
||||
/// Converts a date into a GCD time using dispatch_walltime().
|
||||
///
|
||||
/// date - The date to convert. This must not be nil.
|
||||
+ (dispatch_time_t)wallTimeWithDate:(NSDate *)date;
|
||||
|
||||
@end
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// RACQueueScheduler.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 11/30/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACScheduler.h"
|
||||
|
||||
/// An abstract scheduler which asynchronously enqueues all its work to a Grand
|
||||
/// Central Dispatch queue.
|
||||
///
|
||||
/// Because RACQueueScheduler is abstract, it should not be instantiated
|
||||
/// directly. Create a subclass using the `RACQueueScheduler+Subclass.h`
|
||||
/// interface and use that instead.
|
||||
@interface RACQueueScheduler : RACScheduler
|
||||
@end
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// RACReplaySubject.h
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Josh Abernathy on 3/14/12.
|
||||
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RACSubject.h"
|
||||
|
||||
extern const NSUInteger RACReplaySubjectUnlimitedCapacity;
|
||||
|
||||
/// A replay subject saves the values it is sent (up to its defined capacity)
|
||||
/// and resends those to new subscribers. It will also replay an error or
|
||||
/// completion.
|
||||
@interface RACReplaySubject : RACSubject
|
||||
|
||||
/// Creates a new replay subject with the given capacity. A capacity of
|
||||
/// RACReplaySubjectUnlimitedCapacity means values are never trimmed.
|
||||
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity;
|
||||
|
||||
@end
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// RACScheduler.m
|
||||
// ReactiveCocoa
|
||||
//
|
||||
// Created by Miķelis Vindavs on 5/27/14.
|
||||
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "RACScheduler.h"
|
||||
|
||||
/// An interface for use by subclasses.
|
||||
///
|
||||
/// Subclasses should use `-performAsCurrentScheduler:` to do the actual block
|
||||
/// invocation so that +[RACScheduler currentScheduler] behaves as expected.
|
||||
///
|
||||
/// **Note that RACSchedulers are expected to be serial**. Subclasses must honor
|
||||
/// that contract. See `RACTargetQueueScheduler` for a queue-based scheduler
|
||||
/// which will enforce the serialization guarantee.
|
||||
@interface RACScheduler ()
|
||||
|
||||
/// Performs the given block with the receiver as the current scheduler for
|
||||
/// its thread. This should only be called by subclasses to perform their
|
||||
/// scheduled blocks.
|
||||
///
|
||||
/// block - The block to execute. Cannot be NULL.
|
||||
- (void)performAsCurrentScheduler:(void (^)(void))block;
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user